prjct-cli 0.19.0 → 0.20.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (230) hide show
  1. package/CHANGELOG.md +66 -6
  2. package/CLAUDE.md +56 -15
  3. package/README.md +5 -6
  4. package/bin/prjct +59 -42
  5. package/bin/prjct.ts +60 -0
  6. package/core/__tests__/agentic/memory-system.test.ts +18 -3
  7. package/core/__tests__/agentic/plan-mode.test.ts +55 -26
  8. package/core/__tests__/agentic/prompt-builder.test.ts +6 -6
  9. package/core/__tests__/utils/project-commands.test.ts +72 -0
  10. package/core/agentic/agent-router.ts +3 -12
  11. package/core/agentic/command-executor.ts +372 -3
  12. package/core/agentic/context-builder.ts +7 -27
  13. package/core/agentic/ground-truth.ts +604 -5
  14. package/core/agentic/index.ts +180 -0
  15. package/core/agentic/loop-detector.ts +418 -4
  16. package/core/agentic/memory-system.ts +857 -3
  17. package/core/agentic/plan-mode.ts +491 -4
  18. package/core/agentic/prompt-builder.ts +44 -65
  19. package/core/agentic/services.ts +13 -5
  20. package/core/agentic/skill-loader.ts +112 -0
  21. package/core/agentic/smart-context.ts +37 -122
  22. package/core/agentic/template-loader.ts +79 -122
  23. package/core/agentic/tool-registry.ts +5 -11
  24. package/core/agents/index.ts +1 -1
  25. package/core/agents/performance.ts +4 -2
  26. package/core/bus/bus.ts +262 -0
  27. package/core/bus/index.ts +3 -313
  28. package/core/commands/analysis.ts +5 -5
  29. package/core/commands/analytics.ts +11 -11
  30. package/core/commands/base.ts +33 -209
  31. package/core/commands/cleanup.ts +148 -0
  32. package/core/commands/command-data.ts +346 -0
  33. package/core/commands/commands.ts +216 -0
  34. package/core/commands/design.ts +83 -0
  35. package/core/commands/index.ts +13 -207
  36. package/core/commands/maintenance.ts +52 -473
  37. package/core/commands/planning.ts +3 -3
  38. package/core/commands/register.ts +104 -0
  39. package/core/commands/registry.ts +441 -0
  40. package/core/commands/setup.ts +25 -9
  41. package/core/commands/shipping.ts +48 -11
  42. package/core/commands/snapshots.ts +299 -0
  43. package/core/commands/workflow.ts +2 -2
  44. package/core/constants/index.ts +254 -4
  45. package/core/domain/agent-loader.ts +5 -6
  46. package/core/domain/task-stack.ts +555 -4
  47. package/core/errors.ts +127 -1
  48. package/core/events/events.ts +87 -0
  49. package/core/events/index.ts +4 -138
  50. package/core/index.ts +15 -23
  51. package/core/infrastructure/agent-detector.ts +126 -201
  52. package/core/infrastructure/author-detector.ts +99 -171
  53. package/core/infrastructure/command-installer.ts +476 -4
  54. package/core/infrastructure/config-manager.ts +41 -37
  55. package/core/infrastructure/path-manager.ts +59 -9
  56. package/core/infrastructure/permission-manager.ts +286 -0
  57. package/core/outcomes/analyzer.ts +7 -41
  58. package/core/outcomes/index.ts +1 -1
  59. package/core/outcomes/recorder.ts +1 -1
  60. package/core/{plugins → plugin/builtin}/webhook.ts +6 -22
  61. package/core/plugin/loader.ts +5 -5
  62. package/core/plugin/registry.ts +2 -2
  63. package/core/schemas/ideas.ts +85 -54
  64. package/core/schemas/index.ts +14 -33
  65. package/core/schemas/permissions.ts +177 -0
  66. package/core/schemas/project.ts +39 -12
  67. package/core/schemas/roadmap.ts +94 -59
  68. package/core/schemas/schemas.ts +39 -0
  69. package/core/schemas/shipped.ts +87 -60
  70. package/core/schemas/state.ts +110 -70
  71. package/core/server/index.ts +21 -0
  72. package/core/server/routes.ts +165 -0
  73. package/core/server/server.ts +136 -0
  74. package/core/server/sse.ts +135 -0
  75. package/core/services/agent-service.ts +170 -0
  76. package/core/services/breakdown-service.ts +126 -0
  77. package/core/services/index.ts +21 -0
  78. package/core/services/memory-service.ts +108 -0
  79. package/core/services/project-service.ts +146 -0
  80. package/core/services/skill-service.ts +253 -0
  81. package/core/session/compaction.ts +257 -0
  82. package/core/session/index.ts +20 -8
  83. package/core/{infrastructure/session-manager/migration.ts → session/log-migration.ts} +9 -9
  84. package/core/{infrastructure/session-manager/session-manager.ts → session/session-log-manager.ts} +27 -26
  85. package/core/session/{session-manager.ts → task-session-manager.ts} +7 -4
  86. package/core/session/utils.ts +1 -1
  87. package/core/storage/ideas-storage.ts +10 -26
  88. package/core/storage/index.ts +14 -162
  89. package/core/storage/queue-storage.ts +13 -11
  90. package/core/storage/shipped-storage.ts +4 -17
  91. package/core/storage/state-storage.ts +35 -43
  92. package/core/storage/storage-manager.ts +42 -52
  93. package/core/storage/storage.ts +160 -0
  94. package/core/sync/auth-config.ts +1 -8
  95. package/core/sync/index.ts +17 -10
  96. package/core/sync/oauth-handler.ts +1 -6
  97. package/core/sync/sync-client.ts +6 -34
  98. package/core/sync/sync-manager.ts +11 -40
  99. package/core/types/agentic.ts +577 -0
  100. package/core/types/agents.ts +145 -0
  101. package/core/types/bus.ts +82 -0
  102. package/core/types/commands.ts +366 -0
  103. package/core/types/config.ts +66 -0
  104. package/core/types/core.ts +96 -0
  105. package/core/types/domain.ts +71 -0
  106. package/core/types/events.ts +42 -0
  107. package/core/types/fs.ts +56 -0
  108. package/core/types/index.ts +387 -500
  109. package/core/types/infrastructure.ts +196 -0
  110. package/core/{agentic/memory-system/types.ts → types/memory.ts} +33 -8
  111. package/core/{outcomes/types.ts → types/outcomes.ts} +53 -8
  112. package/core/types/plugin.ts +25 -0
  113. package/core/types/server.ts +54 -0
  114. package/core/types/services.ts +65 -0
  115. package/core/types/session.ts +135 -0
  116. package/core/types/storage.ts +148 -0
  117. package/core/types/sync.ts +121 -0
  118. package/core/types/task.ts +72 -0
  119. package/core/types/template.ts +24 -0
  120. package/core/types/utils.ts +90 -0
  121. package/core/utils/cache.ts +195 -0
  122. package/core/utils/collection-filters.ts +245 -0
  123. package/core/utils/date-helper.ts +1 -5
  124. package/core/utils/file-helper.ts +20 -10
  125. package/core/utils/jsonl-helper.ts +5 -8
  126. package/core/utils/markdown-builder.ts +277 -0
  127. package/core/utils/project-commands.ts +132 -0
  128. package/core/utils/runtime.ts +119 -0
  129. package/dist/bin/prjct.mjs +12568 -0
  130. package/package.json +13 -8
  131. package/scripts/build.js +106 -0
  132. package/scripts/postinstall.js +50 -8
  133. package/templates/agentic/agents/uxui.md +210 -0
  134. package/templates/agentic/subagent-generation.md +1 -1
  135. package/templates/commands/bug.md +219 -41
  136. package/templates/commands/feature.md +368 -80
  137. package/templates/commands/serve.md +118 -0
  138. package/templates/commands/ship.md +152 -14
  139. package/templates/commands/skill.md +110 -0
  140. package/templates/commands/sync.md +63 -4
  141. package/templates/commands/test.md +40 -188
  142. package/templates/mcp-config.json +0 -36
  143. package/templates/permissions/default.jsonc +60 -0
  144. package/templates/permissions/permissive.jsonc +49 -0
  145. package/templates/permissions/strict.jsonc +62 -0
  146. package/templates/skills/code-review.md +47 -0
  147. package/templates/skills/debug.md +61 -0
  148. package/templates/skills/refactor.md +47 -0
  149. package/templates/subagents/domain/devops.md +1 -1
  150. package/templates/subagents/domain/testing.md +6 -10
  151. package/templates/subagents/workflow/prjct-shipper.md +16 -7
  152. package/templates/tools/bash.txt +22 -0
  153. package/templates/tools/edit.txt +18 -0
  154. package/templates/tools/glob.txt +19 -0
  155. package/templates/tools/grep.txt +21 -0
  156. package/templates/tools/read.txt +14 -0
  157. package/templates/tools/task.txt +20 -0
  158. package/templates/tools/webfetch.txt +16 -0
  159. package/templates/tools/websearch.txt +18 -0
  160. package/templates/tools/write.txt +17 -0
  161. package/core/agentic/command-executor/command-executor.ts +0 -312
  162. package/core/agentic/command-executor/index.ts +0 -16
  163. package/core/agentic/command-executor/status-signal.ts +0 -38
  164. package/core/agentic/command-executor/types.ts +0 -79
  165. package/core/agentic/ground-truth/index.ts +0 -76
  166. package/core/agentic/ground-truth/types.ts +0 -33
  167. package/core/agentic/ground-truth/utils.ts +0 -48
  168. package/core/agentic/ground-truth/verifiers/analyze.ts +0 -54
  169. package/core/agentic/ground-truth/verifiers/done.ts +0 -75
  170. package/core/agentic/ground-truth/verifiers/feature.ts +0 -70
  171. package/core/agentic/ground-truth/verifiers/index.ts +0 -37
  172. package/core/agentic/ground-truth/verifiers/init.ts +0 -52
  173. package/core/agentic/ground-truth/verifiers/now.ts +0 -57
  174. package/core/agentic/ground-truth/verifiers/ship.ts +0 -85
  175. package/core/agentic/ground-truth/verifiers/spec.ts +0 -45
  176. package/core/agentic/ground-truth/verifiers/sync.ts +0 -47
  177. package/core/agentic/ground-truth/verifiers.ts +0 -6
  178. package/core/agentic/loop-detector/error-analysis.ts +0 -97
  179. package/core/agentic/loop-detector/hallucination.ts +0 -71
  180. package/core/agentic/loop-detector/index.ts +0 -41
  181. package/core/agentic/loop-detector/loop-detector.ts +0 -222
  182. package/core/agentic/loop-detector/types.ts +0 -66
  183. package/core/agentic/memory-system/history.ts +0 -53
  184. package/core/agentic/memory-system/index.ts +0 -192
  185. package/core/agentic/memory-system/patterns.ts +0 -156
  186. package/core/agentic/memory-system/semantic-memories.ts +0 -278
  187. package/core/agentic/memory-system/session.ts +0 -21
  188. package/core/agentic/plan-mode/approval.ts +0 -57
  189. package/core/agentic/plan-mode/constants.ts +0 -44
  190. package/core/agentic/plan-mode/index.ts +0 -28
  191. package/core/agentic/plan-mode/plan-mode.ts +0 -407
  192. package/core/agentic/plan-mode/types.ts +0 -193
  193. package/core/agents/types.ts +0 -126
  194. package/core/command-registry/categories.ts +0 -23
  195. package/core/command-registry/commands.ts +0 -15
  196. package/core/command-registry/core-commands.ts +0 -344
  197. package/core/command-registry/index.ts +0 -158
  198. package/core/command-registry/optional-commands.ts +0 -163
  199. package/core/command-registry/setup-commands.ts +0 -83
  200. package/core/command-registry/types.ts +0 -59
  201. package/core/command-registry.ts +0 -9
  202. package/core/commands/types.ts +0 -185
  203. package/core/commands.ts +0 -11
  204. package/core/constants/formats.ts +0 -187
  205. package/core/context-sync.ts +0 -18
  206. package/core/data/index.ts +0 -27
  207. package/core/data/md-base-manager.ts +0 -203
  208. package/core/data/md-ideas-manager.ts +0 -155
  209. package/core/data/md-queue-manager.ts +0 -180
  210. package/core/data/md-shipped-manager.ts +0 -90
  211. package/core/data/md-state-manager.ts +0 -137
  212. package/core/domain/task-stack/index.ts +0 -19
  213. package/core/domain/task-stack/parser.ts +0 -86
  214. package/core/domain/task-stack/storage.ts +0 -123
  215. package/core/domain/task-stack/task-stack.ts +0 -340
  216. package/core/domain/task-stack/types.ts +0 -51
  217. package/core/infrastructure/command-installer/command-installer.ts +0 -327
  218. package/core/infrastructure/command-installer/global-config.ts +0 -136
  219. package/core/infrastructure/command-installer/index.ts +0 -25
  220. package/core/infrastructure/command-installer/types.ts +0 -41
  221. package/core/infrastructure/session-manager/index.ts +0 -23
  222. package/core/infrastructure/session-manager/types.ts +0 -45
  223. package/core/infrastructure/session-manager.ts +0 -8
  224. package/core/serializers/ideas-serializer.ts +0 -187
  225. package/core/serializers/index.ts +0 -36
  226. package/core/serializers/queue-serializer.ts +0 -210
  227. package/core/serializers/shipped-serializer.ts +0 -108
  228. package/core/serializers/state-serializer.ts +0 -136
  229. package/core/session/types.ts +0 -29
  230. /package/core/infrastructure/{agents/claude-agent.ts → claude-agent.ts} +0 -0
@@ -1,495 +1,74 @@
1
1
  /**
2
- * Maintenance Commands: cleanup, design, recover, undo, redo, history
3
- * Git-based snapshots for undo/redo functionality
2
+ * Maintenance Commands
3
+ *
4
+ * Composed from individual modules:
5
+ * - cleanup: Memory and project file cleanup
6
+ * - design: System architecture and component design
7
+ * - snapshots: Git-based undo/redo and session recovery
4
8
  */
5
9
 
6
- import path from 'path'
10
+ import type { CommandResult, CleanupOptions, DesignOptions } from '../types'
11
+ import { PrjctCommandsBase } from './base'
7
12
 
8
- import type { CommandResult, CleanupOptions, DesignOptions } from './types'
9
- import {
10
- PrjctCommandsBase,
11
- pathManager,
12
- configManager,
13
- fileHelper,
14
- jsonlHelper,
15
- dateHelper,
16
- out
17
- } from './base'
18
- import { mdIdeasManager, mdQueueManager } from '../data'
13
+ // Import individual command functions
14
+ import { cleanup, cleanupMemory, cleanupMemoryInternal } from './cleanup'
15
+ import { design } from './design'
16
+ import { recover, undo, redo, history } from './snapshots'
19
17
 
18
+ /**
19
+ * MaintenanceCommands - Facade class for maintenance operations
20
+ *
21
+ * Delegates to individual modules for implementation.
22
+ */
20
23
  export class MaintenanceCommands extends PrjctCommandsBase {
21
- /**
22
- * Memory cleanup helper
23
- */
24
- async _cleanupMemory(projectPath: string): Promise<{ success: boolean; results: { rotated: string[]; totalSize: number; freedSpace: number } }> {
25
- const projectId = await configManager.getProjectId(projectPath)
26
-
27
- const results = { rotated: [] as string[], totalSize: 0, freedSpace: 0 }
28
- const jsonlFiles = [
29
- pathManager.getFilePath(projectId!, 'memory', 'context.jsonl'),
30
- pathManager.getFilePath(projectId!, 'progress', 'shipped.md'),
31
- pathManager.getFilePath(projectId!, 'planning', 'ideas.md'),
32
- ]
33
-
34
- for (const filePath of jsonlFiles) {
35
- try {
36
- const sizeMB = await jsonlHelper.getFileSizeMB(filePath)
37
- if (sizeMB > 0) {
38
- results.totalSize += sizeMB
39
- const rotated = await jsonlHelper.rotateJsonLinesIfNeeded(filePath, 10)
40
- if (rotated) {
41
- results.rotated.push(path.basename(filePath))
42
- results.freedSpace += sizeMB
43
- }
44
- }
45
- } catch {
46
- // skip
47
- }
48
- }
49
-
50
- return { success: true, results }
24
+ // Cleanup operations
25
+ _cleanupMemory = cleanupMemory
26
+ _cleanupMemoryInternal = cleanupMemoryInternal
27
+
28
+ async cleanup(options: CleanupOptions = {}, projectPath: string = process.cwd()): Promise<CommandResult> {
29
+ const initResult = await this.ensureProjectInit(projectPath)
30
+ if (!initResult.success) return initResult
31
+ return cleanup(options, projectPath)
51
32
  }
52
33
 
53
- /**
54
- * Internal cleanup helper for memory during normal cleanup
55
- */
56
- async _cleanupMemoryInternal(projectPath: string): Promise<void> {
57
- const projectId = await configManager.getProjectId(projectPath)
58
- const memoryPath = pathManager.getFilePath(projectId!, 'memory', 'context.jsonl')
59
- await jsonlHelper.rotateJsonLinesIfNeeded(memoryPath, 10)
60
- }
61
-
62
- /**
63
- * /p:cleanup - Clean temp files and old entries
64
- */
65
- async cleanup(_options: CleanupOptions = {}, projectPath: string = process.cwd()): Promise<CommandResult> {
66
- try {
67
- const initResult = await this.ensureProjectInit(projectPath)
68
- if (!initResult.success) return initResult
69
-
70
- const isMemoryMode = _options.memory === true || _options.type === 'memory'
71
-
72
- if (isMemoryMode) {
73
- out.spin('cleaning memory...')
74
- const result = await this._cleanupMemory(projectPath)
75
- out.done('memory cleaned')
76
- return result
77
- }
78
-
79
- out.spin('cleaning up...')
80
-
81
- const projectId = await configManager.getProjectId(projectPath)
82
- if (!projectId) {
83
- out.fail('no project ID')
84
- return { success: false, error: 'No project ID found' }
85
- }
86
-
87
- const cleaned: string[] = []
88
-
89
- // Clean memory (keep last 100 entries)
90
- const memoryPath = pathManager.getFilePath(projectId, 'memory', 'context.jsonl')
91
- try {
92
- const entries = await jsonlHelper.readJsonLines(memoryPath)
93
-
94
- if (entries.length > 100) {
95
- const kept = entries.slice(-100)
96
- await jsonlHelper.writeJsonLines(memoryPath, kept)
97
- cleaned.push(`Memory: ${entries.length - 100} old entries removed`)
98
- } else {
99
- cleaned.push('Memory: No cleanup needed')
100
- }
101
- } catch {
102
- cleaned.push('Memory: No file found')
103
- }
104
-
105
- // Clean ideas using mdIdeasManager
106
- try {
107
- const result = await mdIdeasManager.cleanup(projectId)
108
- if (result.removed > 0) {
109
- cleaned.push(`Ideas: ${result.removed} old archived ideas removed`)
110
- } else {
111
- cleaned.push('Ideas: No cleanup needed')
112
- }
113
- } catch {
114
- cleaned.push('Ideas: No file found')
115
- }
116
-
117
- // Check queue for completed tasks using mdQueueManager
118
- try {
119
- const tasks = await mdQueueManager.getActiveTasks(projectId)
120
- const completedTasks = tasks.filter(t => t.completed).length
121
-
122
- if (completedTasks > 0) {
123
- cleaned.push(
124
- `Queue: ${completedTasks} completed tasks found (not removed - use /p:done to clear)`
125
- )
126
- } else {
127
- cleaned.push('Queue: No completed tasks')
128
- }
129
- } catch {
130
- cleaned.push('Queue: No file found')
131
- }
132
-
133
- await this._cleanupMemoryInternal(projectPath)
134
-
135
- await this.logToMemory(projectPath, 'cleanup_performed', {
136
- items: cleaned.length,
137
- timestamp: dateHelper.getTimestamp(),
138
- })
139
-
140
- out.done(`${cleaned.length} items cleaned`)
141
- return { success: true, cleaned }
142
- } catch (error) {
143
- out.fail((error as Error).message)
144
- return { success: false, error: (error as Error).message }
145
- }
34
+ // Design operations
35
+ async design(
36
+ target: string | null = null,
37
+ options: DesignOptions = {},
38
+ projectPath: string = process.cwd()
39
+ ): Promise<CommandResult> {
40
+ const initResult = await this.ensureProjectInit(projectPath)
41
+ if (!initResult.success) return initResult
42
+ return design(target, options, projectPath)
146
43
  }
147
44
 
148
- /**
149
- * /p:design - Design system architecture, APIs, and components
150
- */
151
- async design(target: string | null = null, options: DesignOptions = {}, projectPath: string = process.cwd()): Promise<CommandResult> {
152
- try {
153
- const initResult = await this.ensureProjectInit(projectPath)
154
- if (!initResult.success) return initResult
155
-
156
- const designType = options.type || 'architecture'
157
- const validTypes = ['architecture', 'api', 'component', 'database', 'flow']
158
-
159
- if (!validTypes.includes(designType)) {
160
- out.fail(`invalid type: ${designType}`)
161
- return { success: false, error: 'Invalid design type' }
162
- }
163
-
164
- const designTarget = target || 'system'
165
- out.spin(`designing ${designType}...`)
166
-
167
- const projectId = await configManager.getProjectId(projectPath)
168
- const designsPath = path.join(
169
- pathManager.getGlobalProjectPath(projectId!),
170
- 'planning',
171
- 'designs'
172
- )
173
- await fileHelper.ensureDir(designsPath)
174
-
175
- let designContent = ''
176
-
177
- switch (designType) {
178
- case 'architecture':
179
- designContent = `# Architecture Design: ${designTarget}\n\n*Use templates/design/architecture.md for full design*\n`
180
- break
181
- case 'api':
182
- designContent = `# API Design: ${designTarget}\n\n*Use templates/design/api.md for full design*\n`
183
- break
184
- case 'component':
185
- designContent = `# Component Design: ${designTarget}\n\n*Use templates/design/component.md for full design*\n`
186
- break
187
- case 'database':
188
- designContent = `# Database Design: ${designTarget}\n\n*Use templates/design/database.md for full design*\n`
189
- break
190
- case 'flow':
191
- designContent = `# Flow Design: ${designTarget}\n\n*Use templates/design/flow.md for full design*\n`
192
- break
193
- }
194
-
195
- const designFileName = `${designType}-${designTarget.toLowerCase().replace(/\s+/g, '-')}.md`
196
- const designFilePath = path.join(designsPath, designFileName)
197
- await fileHelper.writeFile(designFilePath, designContent)
198
-
199
- await this.logToMemory(projectPath, 'design_created', {
200
- type: designType,
201
- target: designTarget,
202
- timestamp: dateHelper.getTimestamp(),
203
- })
204
-
205
- out.done(`${designType} design created`)
206
- return { success: true, designPath: designFilePath, type: designType, target: designTarget }
207
- } catch (error) {
208
- out.fail((error as Error).message)
209
- return { success: false, error: (error as Error).message }
210
- }
211
- }
212
-
213
- /**
214
- * /p:recover - Recover abandoned session with context restoration
215
- */
45
+ // Snapshot operations
216
46
  async recover(projectPath: string = process.cwd()): Promise<CommandResult> {
217
- try {
218
- const initResult = await this.ensureProjectInit(projectPath)
219
- if (!initResult.success) return initResult
220
-
221
- const projectId = await configManager.getProjectId(projectPath)
222
- if (!projectId) {
223
- out.fail('no project ID')
224
- return { success: false, error: 'No project ID found' }
225
- }
226
-
227
- out.spin('checking for abandoned sessions...')
228
-
229
- // Check for current session file
230
- const sessionPath = pathManager.getFilePath(projectId, 'progress', 'sessions/current.json')
231
-
232
- let sessionData: { task?: string; startedAt?: string; context?: string } | null = null
233
- try {
234
- const content = await fileHelper.readFile(sessionPath)
235
- sessionData = JSON.parse(content)
236
- } catch {
237
- sessionData = null
238
- }
239
-
240
- if (!sessionData || !sessionData.task) {
241
- out.warn('no abandoned session found')
242
- return { success: true, message: 'No abandoned session found' }
243
- }
244
-
245
- console.log('\n🔍 Found abandoned session:\n')
246
- console.log(` Task: ${sessionData.task}`)
247
- if (sessionData.startedAt) {
248
- const elapsed = dateHelper.calculateDuration(new Date(sessionData.startedAt))
249
- console.log(` Started: ${elapsed} ago`)
250
- }
251
- if (sessionData.context) {
252
- console.log(` Context: ${sessionData.context.slice(0, 100)}...`)
253
- }
254
-
255
- console.log('\n💡 Options:')
256
- console.log(' 1. Use /p:work to resume working')
257
- console.log(' 2. Use /p:done to mark as complete')
258
- console.log(' 3. Delete session file to discard\n')
259
-
260
- return { success: true, session: sessionData }
261
- } catch (error) {
262
- out.fail((error as Error).message)
263
- return { success: false, error: (error as Error).message }
264
- }
47
+ const initResult = await this.ensureProjectInit(projectPath)
48
+ if (!initResult.success) return initResult
49
+ return recover(projectPath)
265
50
  }
266
51
 
267
- /**
268
- * /p:undo - Git-based undo (stash current changes)
269
- */
270
52
  async undo(projectPath: string = process.cwd()): Promise<CommandResult> {
271
- try {
272
- const initResult = await this.ensureProjectInit(projectPath)
273
- if (!initResult.success) return initResult
274
-
275
- out.spin('creating undo point...')
276
-
277
- const projectId = await configManager.getProjectId(projectPath)
278
- if (!projectId) {
279
- out.fail('no project ID')
280
- return { success: false, error: 'No project ID found' }
281
- }
282
-
283
- // Create snapshots directory
284
- const snapshotsPath = path.join(
285
- pathManager.getGlobalProjectPath(projectId),
286
- 'snapshots'
287
- )
288
- await fileHelper.ensureDir(snapshotsPath)
289
-
290
- // Check git status
291
- const { execSync } = await import('child_process')
292
-
293
- try {
294
- const status = execSync('git status --porcelain', {
295
- cwd: projectPath,
296
- encoding: 'utf-8'
297
- }).trim()
298
-
299
- if (!status) {
300
- out.warn('nothing to undo (no changes)')
301
- return { success: true, message: 'No changes to undo' }
302
- }
303
-
304
- // Create stash with timestamp
305
- const timestamp = new Date().toISOString().replace(/[:.]/g, '-')
306
- const stashMessage = `prjct-undo-${timestamp}`
307
-
308
- execSync(`git stash push -m "${stashMessage}"`, {
309
- cwd: projectPath,
310
- encoding: 'utf-8'
311
- })
312
-
313
- // Save snapshot metadata
314
- const snapshotFile = path.join(snapshotsPath, 'history.json')
315
- let history: { snapshots: { id: string; timestamp: string; message: string }[]; current: number } = { snapshots: [], current: -1 }
316
-
317
- try {
318
- const content = await fileHelper.readFile(snapshotFile)
319
- history = JSON.parse(content)
320
- } catch {
321
- // New history
322
- }
323
-
324
- history.snapshots.push({
325
- id: stashMessage,
326
- timestamp: new Date().toISOString(),
327
- message: stashMessage
328
- })
329
- history.current = history.snapshots.length - 1
330
-
331
- await fileHelper.writeFile(snapshotFile, JSON.stringify(history, null, 2))
332
-
333
- await this.logToMemory(projectPath, 'undo_performed', {
334
- snapshotId: stashMessage,
335
- timestamp: dateHelper.getTimestamp(),
336
- })
337
-
338
- out.done('changes stashed (use /p:redo to restore)')
339
- return { success: true, snapshotId: stashMessage }
340
- } catch (gitError) {
341
- out.fail('git operation failed')
342
- return { success: false, error: (gitError as Error).message }
343
- }
344
- } catch (error) {
345
- out.fail((error as Error).message)
346
- return { success: false, error: (error as Error).message }
347
- }
53
+ const initResult = await this.ensureProjectInit(projectPath)
54
+ if (!initResult.success) return initResult
55
+ return undo(projectPath)
348
56
  }
349
57
 
350
- /**
351
- * /p:redo - Restore previously undone changes
352
- */
353
58
  async redo(projectPath: string = process.cwd()): Promise<CommandResult> {
354
- try {
355
- const initResult = await this.ensureProjectInit(projectPath)
356
- if (!initResult.success) return initResult
357
-
358
- out.spin('restoring changes...')
359
-
360
- const projectId = await configManager.getProjectId(projectPath)
361
- if (!projectId) {
362
- out.fail('no project ID')
363
- return { success: false, error: 'No project ID found' }
364
- }
365
-
366
- const snapshotsPath = path.join(
367
- pathManager.getGlobalProjectPath(projectId),
368
- 'snapshots'
369
- )
370
- const snapshotFile = path.join(snapshotsPath, 'history.json')
371
-
372
- let history: { snapshots: { id: string; timestamp: string; message: string }[]; current: number }
373
-
374
- try {
375
- const content = await fileHelper.readFile(snapshotFile)
376
- history = JSON.parse(content)
377
- } catch {
378
- out.warn('no undo history found')
379
- return { success: false, message: 'No undo history found' }
380
- }
381
-
382
- if (history.snapshots.length === 0) {
383
- out.warn('nothing to redo')
384
- return { success: false, message: 'Nothing to redo' }
385
- }
386
-
387
- const { execSync } = await import('child_process')
388
-
389
- try {
390
- // Get latest stash
391
- const stashList = execSync('git stash list', {
392
- cwd: projectPath,
393
- encoding: 'utf-8'
394
- }).trim()
395
-
396
- if (!stashList) {
397
- out.warn('no stashed changes')
398
- return { success: false, message: 'No stashed changes found' }
399
- }
400
-
401
- // Find prjct stash
402
- const prjctStash = stashList.split('\n').find(line => line.includes('prjct-undo-'))
403
-
404
- if (!prjctStash) {
405
- out.warn('no prjct undo point found')
406
- return { success: false, message: 'No prjct undo point found' }
407
- }
408
-
409
- // Pop the stash
410
- execSync('git stash pop', {
411
- cwd: projectPath,
412
- encoding: 'utf-8'
413
- })
414
-
415
- // Remove from history
416
- history.snapshots.pop()
417
- history.current = Math.max(0, history.current - 1)
418
-
419
- await fileHelper.writeFile(snapshotFile, JSON.stringify(history, null, 2))
420
-
421
- await this.logToMemory(projectPath, 'redo_performed', {
422
- timestamp: dateHelper.getTimestamp(),
423
- })
424
-
425
- out.done('changes restored')
426
- return { success: true }
427
- } catch (gitError) {
428
- out.fail('git operation failed')
429
- return { success: false, error: (gitError as Error).message }
430
- }
431
- } catch (error) {
432
- out.fail((error as Error).message)
433
- return { success: false, error: (error as Error).message }
434
- }
59
+ const initResult = await this.ensureProjectInit(projectPath)
60
+ if (!initResult.success) return initResult
61
+ return redo(projectPath)
435
62
  }
436
63
 
437
- /**
438
- * /p:history - Show snapshot history for undo/redo
439
- */
440
64
  async history(projectPath: string = process.cwd()): Promise<CommandResult> {
441
- try {
442
- const initResult = await this.ensureProjectInit(projectPath)
443
- if (!initResult.success) return initResult
444
-
445
- const projectId = await configManager.getProjectId(projectPath)
446
- if (!projectId) {
447
- out.fail('no project ID')
448
- return { success: false, error: 'No project ID found' }
449
- }
450
-
451
- const snapshotsPath = path.join(
452
- pathManager.getGlobalProjectPath(projectId),
453
- 'snapshots'
454
- )
455
- const snapshotFile = path.join(snapshotsPath, 'history.json')
456
-
457
- let history: { snapshots: { id: string; timestamp: string; message: string }[]; current: number }
458
-
459
- try {
460
- const content = await fileHelper.readFile(snapshotFile)
461
- history = JSON.parse(content)
462
- } catch {
463
- console.log('\n📜 SNAPSHOT HISTORY\n')
464
- console.log('═'.repeat(50))
465
- console.log(' No snapshots yet.')
466
- console.log(' Use /p:undo to create a snapshot.\n')
467
- return { success: true, snapshots: [] }
468
- }
469
-
470
- console.log('\n📜 SNAPSHOT HISTORY\n')
471
- console.log('═'.repeat(50))
472
-
473
- if (history.snapshots.length === 0) {
474
- console.log(' No snapshots yet.')
475
- console.log(' Use /p:undo to create a snapshot.\n')
476
- } else {
477
- history.snapshots.forEach((snap, i) => {
478
- const marker = i === history.current ? '→' : ' '
479
- const date = new Date(snap.timestamp).toLocaleString()
480
- console.log(` ${marker} ${i + 1}. ${date}`)
481
- })
482
- console.log('')
483
- console.log(` ${history.snapshots.length} snapshot(s) available`)
484
- console.log(' Use /p:redo to restore the latest\n')
485
- }
486
-
487
- console.log('═'.repeat(50) + '\n')
488
-
489
- return { success: true, snapshots: history.snapshots, current: history.current }
490
- } catch (error) {
491
- out.fail((error as Error).message)
492
- return { success: false, error: (error as Error).message }
493
- }
65
+ const initResult = await this.ensureProjectInit(projectPath)
66
+ if (!initResult.success) return initResult
67
+ return history(projectPath)
494
68
  }
495
69
  }
70
+
71
+ // Re-export individual functions for direct use
72
+ export { cleanup, cleanupMemory, cleanupMemoryInternal } from './cleanup'
73
+ export { design } from './design'
74
+ export { recover, undo, redo, history } from './snapshots'
@@ -5,7 +5,7 @@
5
5
 
6
6
  import path from 'path'
7
7
 
8
- import type { CommandResult, AnalyzeOptions, Context } from './types'
8
+ import type { CommandResult, ProjectContext } from '../types'
9
9
  import { generateUUID } from '../schemas'
10
10
  import type { Priority, TaskType, TaskSection } from '../schemas/state'
11
11
  import {
@@ -151,7 +151,7 @@ export class PlanningCommands extends PrjctCommandsBase {
151
151
 
152
152
  out.spin(`planning ${description}...`)
153
153
 
154
- const context = await contextBuilder.build(projectPath, { description }) as Context
154
+ const context = await contextBuilder.build(projectPath, { description }) as ProjectContext
155
155
  const tasks = this._breakdownFeatureTasks(description)
156
156
  const featureId = generateUUID()
157
157
 
@@ -217,7 +217,7 @@ export class PlanningCommands extends PrjctCommandsBase {
217
217
 
218
218
  out.spin('tracking bug...')
219
219
 
220
- const context = await contextBuilder.build(projectPath, { description }) as Context
220
+ const context = await contextBuilder.build(projectPath, { description }) as ProjectContext
221
221
  const severity = this._detectBugSeverity(description)
222
222
 
223
223
  const agentResult = await this._assignAgentForTask(`fix bug: ${description}`, projectPath, context)
@@ -0,0 +1,104 @@
1
+ /**
2
+ * Command Registration - Bridges existing command groups to the registry
3
+ *
4
+ * This module registers all commands from the existing command groups
5
+ * into the central CommandRegistry. This enables:
6
+ * - Uniform command execution via registry.execute()
7
+ * - Command introspection and metadata
8
+ * - Future migration to pure handler pattern
9
+ */
10
+
11
+ import { commandRegistry } from './registry'
12
+ import { COMMANDS, CATEGORIES } from './command-data'
13
+ import { WorkflowCommands } from './workflow'
14
+ import { PlanningCommands } from './planning'
15
+ import { ShippingCommands } from './shipping'
16
+ import { AnalyticsCommands } from './analytics'
17
+ import { MaintenanceCommands } from './maintenance'
18
+ import { AnalysisCommands } from './analysis'
19
+ import { SetupCommands } from './setup'
20
+
21
+ // Singleton instances of command groups
22
+ const workflow = new WorkflowCommands()
23
+ const planning = new PlanningCommands()
24
+ const shipping = new ShippingCommands()
25
+ const analytics = new AnalyticsCommands()
26
+ const maintenance = new MaintenanceCommands()
27
+ const analysis = new AnalysisCommands()
28
+ const setup = new SetupCommands()
29
+
30
+ /**
31
+ * Register categories
32
+ */
33
+ function registerCategories(): void {
34
+ for (const [name, info] of Object.entries(CATEGORIES)) {
35
+ commandRegistry.registerCategory(name, info)
36
+ }
37
+ }
38
+
39
+ /**
40
+ * Register all commands from existing command groups
41
+ */
42
+ export function registerAllCommands(): void {
43
+ // Skip if already registered
44
+ if (commandRegistry.has('work')) return
45
+
46
+ // Register categories first
47
+ registerCategories()
48
+
49
+ // Helper to get metadata from COMMANDS
50
+ const getMeta = (name: string) => COMMANDS.find(c => c.name === name)
51
+
52
+ // Workflow commands
53
+ commandRegistry.registerMethod('work', workflow, 'now', getMeta('work'))
54
+ commandRegistry.registerMethod('now', workflow, 'now', getMeta('now'))
55
+ commandRegistry.registerMethod('done', workflow, 'done', getMeta('done'))
56
+ commandRegistry.registerMethod('next', workflow, 'next', getMeta('next'))
57
+ commandRegistry.registerMethod('pause', workflow, 'pause', getMeta('pause'))
58
+ commandRegistry.registerMethod('resume', workflow, 'resume', getMeta('resume'))
59
+
60
+ // Planning commands
61
+ commandRegistry.registerMethod('init', planning, 'init', getMeta('init'))
62
+ commandRegistry.registerMethod('feature', planning, 'feature', getMeta('feature'))
63
+ commandRegistry.registerMethod('bug', planning, 'bug', getMeta('bug'))
64
+ commandRegistry.registerMethod('idea', planning, 'idea', getMeta('idea'))
65
+ commandRegistry.registerMethod('spec', planning, 'spec', getMeta('spec'))
66
+
67
+ // Shipping commands
68
+ commandRegistry.registerMethod('ship', shipping, 'ship', getMeta('ship'))
69
+
70
+ // Analytics commands
71
+ commandRegistry.registerMethod('dash', analytics, 'dash', getMeta('dash'))
72
+ commandRegistry.registerMethod('help', analytics, 'help', getMeta('help'))
73
+
74
+ // Maintenance commands
75
+ commandRegistry.registerMethod('cleanup', maintenance, 'cleanup', getMeta('cleanup'))
76
+ commandRegistry.registerMethod('design', maintenance, 'design', getMeta('design'))
77
+ commandRegistry.registerMethod('recover', maintenance, 'recover', getMeta('recover'))
78
+ commandRegistry.registerMethod('undo', maintenance, 'undo', getMeta('undo'))
79
+ commandRegistry.registerMethod('redo', maintenance, 'redo', getMeta('redo'))
80
+ commandRegistry.registerMethod('history', maintenance, 'history', getMeta('history'))
81
+
82
+ // Analysis commands
83
+ commandRegistry.registerMethod('analyze', analysis, 'analyze', getMeta('analyze'))
84
+ commandRegistry.registerMethod('sync', analysis, 'sync', getMeta('sync'))
85
+
86
+ // Setup commands
87
+ commandRegistry.registerMethod('start', setup, 'start', getMeta('start'))
88
+ commandRegistry.registerMethod('setup', setup, 'setup', getMeta('setup'))
89
+ commandRegistry.registerMethod('migrateAll', setup, 'migrateAll', getMeta('migrate-all'))
90
+ }
91
+
92
+ // Auto-register on import
93
+ registerAllCommands()
94
+
95
+ // Export command group instances for direct access (legacy support)
96
+ export {
97
+ workflow,
98
+ planning,
99
+ shipping,
100
+ analytics,
101
+ maintenance,
102
+ analysis,
103
+ setup
104
+ }