prjct-cli 0.13.2 → 0.15.0

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 (193) hide show
  1. package/CHANGELOG.md +106 -0
  2. package/bin/prjct +10 -13
  3. package/core/agentic/memory-system/semantic-memories.ts +2 -1
  4. package/core/agentic/plan-mode/plan-mode.ts +2 -1
  5. package/core/agentic/prompt-builder.ts +22 -43
  6. package/core/agentic/services.ts +5 -5
  7. package/core/agentic/smart-context.ts +7 -2
  8. package/core/command-registry/core-commands.ts +54 -29
  9. package/core/command-registry/optional-commands.ts +64 -0
  10. package/core/command-registry/setup-commands.ts +18 -3
  11. package/core/commands/analysis.ts +21 -68
  12. package/core/commands/analytics.ts +247 -213
  13. package/core/commands/base.ts +1 -1
  14. package/core/commands/index.ts +41 -36
  15. package/core/commands/maintenance.ts +300 -31
  16. package/core/commands/planning.ts +233 -22
  17. package/core/commands/setup.ts +3 -8
  18. package/core/commands/shipping.ts +14 -18
  19. package/core/commands/types.ts +8 -6
  20. package/core/commands/workflow.ts +105 -100
  21. package/core/context/generator.ts +317 -0
  22. package/core/context-sync.ts +7 -350
  23. package/core/data/index.ts +13 -32
  24. package/core/data/md-ideas-manager.ts +155 -0
  25. package/core/data/md-queue-manager.ts +4 -3
  26. package/core/data/md-shipped-manager.ts +90 -0
  27. package/core/data/md-state-manager.ts +11 -7
  28. package/core/domain/agent-generator.ts +23 -63
  29. package/core/events/index.ts +143 -0
  30. package/core/index.ts +17 -14
  31. package/core/infrastructure/capability-installer.ts +13 -149
  32. package/core/infrastructure/migrator/project-scanner.ts +2 -1
  33. package/core/infrastructure/path-manager.ts +4 -6
  34. package/core/infrastructure/setup.ts +3 -0
  35. package/core/infrastructure/uuid-migration.ts +750 -0
  36. package/core/outcomes/recorder.ts +2 -1
  37. package/core/plugin/loader.ts +4 -7
  38. package/core/plugin/registry.ts +3 -3
  39. package/core/schemas/index.ts +23 -25
  40. package/core/schemas/state.ts +1 -0
  41. package/core/serializers/ideas-serializer.ts +187 -0
  42. package/core/serializers/index.ts +16 -0
  43. package/core/serializers/shipped-serializer.ts +108 -0
  44. package/core/session/utils.ts +3 -9
  45. package/core/storage/ideas-storage.ts +273 -0
  46. package/core/storage/index.ts +204 -0
  47. package/core/storage/queue-storage.ts +297 -0
  48. package/core/storage/shipped-storage.ts +223 -0
  49. package/core/storage/state-storage.ts +235 -0
  50. package/core/storage/storage-manager.ts +175 -0
  51. package/package.json +1 -1
  52. package/packages/web/app/api/projects/[id]/momentum/route.ts +257 -0
  53. package/packages/web/app/api/sessions/current/route.ts +132 -0
  54. package/packages/web/app/api/sessions/history/route.ts +96 -14
  55. package/packages/web/app/globals.css +5 -0
  56. package/packages/web/app/layout.tsx +2 -0
  57. package/packages/web/app/project/[id]/code/layout.tsx +18 -0
  58. package/packages/web/app/project/[id]/code/page.tsx +408 -0
  59. package/packages/web/app/project/[id]/page.tsx +359 -389
  60. package/packages/web/app/project/[id]/reports/page.tsx +59 -0
  61. package/packages/web/app/project/[id]/reports/print/page.tsx +58 -0
  62. package/packages/web/components/ActivityTimeline/ActivityTimeline.tsx +0 -1
  63. package/packages/web/components/AgentsCard/AgentsCard.tsx +64 -34
  64. package/packages/web/components/AgentsCard/AgentsCard.types.ts +1 -0
  65. package/packages/web/components/AppSidebar/AppSidebar.tsx +135 -11
  66. package/packages/web/components/BentoCard/BentoCard.constants.ts +3 -3
  67. package/packages/web/components/BentoCard/BentoCard.tsx +2 -1
  68. package/packages/web/components/BentoGrid/BentoGrid.tsx +2 -2
  69. package/packages/web/components/BlockersCard/BlockersCard.tsx +65 -57
  70. package/packages/web/components/BlockersCard/BlockersCard.types.ts +1 -0
  71. package/packages/web/components/CommandBar/CommandBar.tsx +67 -0
  72. package/packages/web/components/CommandBar/index.ts +1 -0
  73. package/packages/web/components/DashboardContent/DashboardContent.tsx +35 -5
  74. package/packages/web/components/DateGroup/DateGroup.tsx +1 -1
  75. package/packages/web/components/EmptyState/EmptyState.tsx +39 -21
  76. package/packages/web/components/EmptyState/EmptyState.types.ts +1 -0
  77. package/packages/web/components/EventRow/EventRow.tsx +4 -4
  78. package/packages/web/components/EventRow/EventRow.utils.ts +3 -3
  79. package/packages/web/components/HeroSection/HeroSection.tsx +52 -15
  80. package/packages/web/components/HeroSection/HeroSection.types.ts +4 -4
  81. package/packages/web/components/HeroSection/HeroSection.utils.ts +7 -3
  82. package/packages/web/components/IdeasCard/IdeasCard.tsx +94 -27
  83. package/packages/web/components/IdeasCard/IdeasCard.types.ts +1 -0
  84. package/packages/web/components/MasonryGrid/MasonryGrid.tsx +18 -0
  85. package/packages/web/components/MasonryGrid/index.ts +1 -0
  86. package/packages/web/components/MomentumWidget/MomentumWidget.tsx +119 -0
  87. package/packages/web/components/MomentumWidget/MomentumWidget.types.ts +16 -0
  88. package/packages/web/components/MomentumWidget/index.ts +2 -0
  89. package/packages/web/components/NowCard/NowCard.tsx +81 -56
  90. package/packages/web/components/NowCard/NowCard.types.ts +1 -0
  91. package/packages/web/components/PageHeader/PageHeader.tsx +24 -0
  92. package/packages/web/components/PageHeader/index.ts +1 -0
  93. package/packages/web/components/ProgressRing/ProgressRing.constants.ts +2 -2
  94. package/packages/web/components/ProjectAvatar/ProjectAvatar.tsx +2 -2
  95. package/packages/web/components/ProjectColorDot/ProjectColorDot.tsx +37 -0
  96. package/packages/web/components/ProjectColorDot/index.ts +1 -0
  97. package/packages/web/components/ProjectSelectorModal/ProjectSelectorModal.tsx +104 -0
  98. package/packages/web/components/ProjectSelectorModal/index.ts +1 -0
  99. package/packages/web/components/Providers/Providers.tsx +4 -1
  100. package/packages/web/components/QueueCard/QueueCard.tsx +78 -25
  101. package/packages/web/components/QueueCard/QueueCard.types.ts +1 -0
  102. package/packages/web/components/QueueCard/QueueCard.utils.ts +3 -3
  103. package/packages/web/components/RecoverCard/RecoverCard.tsx +72 -0
  104. package/packages/web/components/RecoverCard/RecoverCard.types.ts +16 -0
  105. package/packages/web/components/RecoverCard/index.ts +2 -0
  106. package/packages/web/components/RoadmapCard/RoadmapCard.tsx +101 -33
  107. package/packages/web/components/RoadmapCard/RoadmapCard.types.ts +1 -0
  108. package/packages/web/components/ShipsCard/ShipsCard.tsx +71 -28
  109. package/packages/web/components/ShipsCard/ShipsCard.types.ts +2 -0
  110. package/packages/web/components/SparklineChart/SparklineChart.tsx +20 -18
  111. package/packages/web/components/StatsMasonry/StatsMasonry.tsx +95 -0
  112. package/packages/web/components/StatsMasonry/index.ts +1 -0
  113. package/packages/web/components/StreakCard/StreakCard.tsx +37 -35
  114. package/packages/web/components/TasksCounter/TasksCounter.tsx +1 -1
  115. package/packages/web/components/TechStackBadges/TechStackBadges.tsx +12 -4
  116. package/packages/web/components/TerminalDock/DockToggleTab.tsx +29 -0
  117. package/packages/web/components/TerminalDock/TerminalDock.tsx +386 -0
  118. package/packages/web/components/TerminalDock/TerminalDockTab.tsx +130 -0
  119. package/packages/web/components/TerminalDock/TerminalTabBar.tsx +142 -0
  120. package/packages/web/components/TerminalDock/index.ts +2 -0
  121. package/packages/web/components/VelocityBadge/VelocityBadge.tsx +8 -3
  122. package/packages/web/components/VelocityCard/VelocityCard.tsx +49 -47
  123. package/packages/web/components/WeeklyReports/PrintableReport.tsx +259 -0
  124. package/packages/web/components/WeeklyReports/ReportPreviewCard.tsx +187 -0
  125. package/packages/web/components/WeeklyReports/WeekCalendar.tsx +288 -0
  126. package/packages/web/components/WeeklyReports/WeeklyReports.tsx +149 -0
  127. package/packages/web/components/WeeklyReports/index.ts +4 -0
  128. package/packages/web/components/WeeklySparkline/WeeklySparkline.tsx +16 -4
  129. package/packages/web/components/WeeklySparkline/WeeklySparkline.types.ts +1 -0
  130. package/packages/web/components/charts/SessionsChart.tsx +6 -3
  131. package/packages/web/components/ui/dialog.tsx +143 -0
  132. package/packages/web/components/ui/drawer.tsx +135 -0
  133. package/packages/web/components/ui/select.tsx +187 -0
  134. package/packages/web/context/GlobalTerminalContext.tsx +538 -0
  135. package/packages/web/lib/commands.ts +81 -0
  136. package/packages/web/lib/generate-week-report.ts +285 -0
  137. package/packages/web/lib/parse-prjct-files.ts +56 -55
  138. package/packages/web/lib/project-colors.ts +58 -0
  139. package/packages/web/lib/projects.ts +58 -5
  140. package/packages/web/lib/services/projects.server.ts +11 -1
  141. package/packages/web/next-env.d.ts +1 -1
  142. package/packages/web/package.json +5 -1
  143. package/templates/commands/analyze.md +39 -3
  144. package/templates/commands/ask.md +58 -3
  145. package/templates/commands/bug.md +117 -26
  146. package/templates/commands/dash.md +95 -158
  147. package/templates/commands/done.md +130 -148
  148. package/templates/commands/feature.md +125 -103
  149. package/templates/commands/git.md +18 -3
  150. package/templates/commands/idea.md +121 -38
  151. package/templates/commands/init.md +124 -20
  152. package/templates/commands/migrate-all.md +63 -28
  153. package/templates/commands/migrate.md +140 -0
  154. package/templates/commands/next.md +115 -5
  155. package/templates/commands/now.md +146 -82
  156. package/templates/commands/pause.md +89 -74
  157. package/templates/commands/redo.md +6 -4
  158. package/templates/commands/resume.md +141 -59
  159. package/templates/commands/ship.md +103 -231
  160. package/templates/commands/spec.md +98 -8
  161. package/templates/commands/suggest.md +22 -2
  162. package/templates/commands/sync.md +192 -203
  163. package/templates/commands/undo.md +6 -4
  164. package/core/data/agents-manager.ts +0 -76
  165. package/core/data/analysis-manager.ts +0 -83
  166. package/core/data/base-manager.ts +0 -156
  167. package/core/data/ideas-manager.ts +0 -81
  168. package/core/data/outcomes-manager.ts +0 -96
  169. package/core/data/project-manager.ts +0 -75
  170. package/core/data/roadmap-manager.ts +0 -118
  171. package/core/data/shipped-manager.ts +0 -65
  172. package/core/data/state-manager.ts +0 -214
  173. package/core/state/index.ts +0 -25
  174. package/core/state/manager.ts +0 -376
  175. package/core/state/types.ts +0 -185
  176. package/core/utils/project-capabilities.ts +0 -156
  177. package/core/view-generator.ts +0 -536
  178. package/packages/web/app/project/[id]/stats/loading.tsx +0 -43
  179. package/packages/web/app/project/[id]/stats/page.tsx +0 -253
  180. package/templates/agent-assignment.md +0 -72
  181. package/templates/analysis/project-analysis.md +0 -78
  182. package/templates/checklists/accessibility.md +0 -33
  183. package/templates/commands/build.md +0 -17
  184. package/templates/commands/decision.md +0 -226
  185. package/templates/commands/fix.md +0 -79
  186. package/templates/commands/help.md +0 -61
  187. package/templates/commands/progress.md +0 -14
  188. package/templates/commands/recap.md +0 -14
  189. package/templates/commands/roadmap.md +0 -52
  190. package/templates/commands/status.md +0 -17
  191. package/templates/commands/task.md +0 -63
  192. package/templates/commands/work.md +0 -44
  193. package/templates/commands/workflow.md +0 -12
@@ -1,19 +1,16 @@
1
1
  /**
2
- * Agentic Commands Handler for prjct CLI
2
+ * prjct CLI Commands Handler
3
3
  *
4
- * 100% AGENTIC - Claude decides everything based on templates.
5
- * ZERO if/else business logic.
4
+ * MD-First Architecture - All state in Markdown files.
6
5
  *
7
- * All commands use the agentic execution engine.
8
- * Templates define what Claude should do.
9
- *
10
- * MIGRATED COMMANDS (18 total):
11
- * - Sprint 1 (9 CRITICAL): init, analyze, sync, feature, bug, now, done, next, ship
12
- * - Sprint 2 (4 IMPORTANT): context, recap, stuck, design
13
- * - Sprint 3 (5 OPTIONAL): cleanup, progress, roadmap, status, build
14
- *
15
- * PENDING (3 total):
16
- * - Sprint 4 (3 SETUP): start, setup, migrateAll
6
+ * COMMANDS (22 total):
7
+ * - Workflow (5): work, done, next, pause, resume
8
+ * - Planning (5): init, feature, bug, idea, spec
9
+ * - Shipping (1): ship
10
+ * - Analytics (2): dash, help
11
+ * - Maintenance (5): cleanup, design, recover, undo, redo, history
12
+ * - Analysis (2): analyze, sync
13
+ * - Setup (3): start, setup, migrateAll
17
14
  */
18
15
 
19
16
  import { WorkflowCommands } from './workflow'
@@ -72,7 +69,7 @@ class PrjctCommands {
72
69
 
73
70
  // ========== Workflow Commands ==========
74
71
 
75
- async now(task: string | null = null, projectPath: string = process.cwd()): Promise<CommandResult> {
72
+ async work(task: string | null = null, projectPath: string = process.cwd()): Promise<CommandResult> {
76
73
  return this.workflow.now(task, projectPath)
77
74
  }
78
75
 
@@ -84,8 +81,12 @@ class PrjctCommands {
84
81
  return this.workflow.next(projectPath)
85
82
  }
86
83
 
87
- async build(taskOrNumber: string, projectPath: string = process.cwd()): Promise<CommandResult> {
88
- return this.workflow.build(taskOrNumber, projectPath)
84
+ async pause(reason: string = '', projectPath: string = process.cwd()): Promise<CommandResult> {
85
+ return this.workflow.pause(reason, projectPath)
86
+ }
87
+
88
+ async resume(taskId: string | null = null, projectPath: string = process.cwd()): Promise<CommandResult> {
89
+ return this.workflow.resume(taskId, projectPath)
89
90
  }
90
91
 
91
92
  // ========== Planning Commands ==========
@@ -102,8 +103,12 @@ class PrjctCommands {
102
103
  return this.planning.bug(description, projectPath)
103
104
  }
104
105
 
105
- async architect(action: string = 'execute', projectPath: string = process.cwd()): Promise<CommandResult> {
106
- return this.planning.architect(action, projectPath)
106
+ async idea(description: string, projectPath: string = process.cwd()): Promise<CommandResult> {
107
+ return this.planning.idea(description, projectPath)
108
+ }
109
+
110
+ async spec(featureName: string | null = null, projectPath: string = process.cwd()): Promise<CommandResult> {
111
+ return this.planning.spec(featureName, projectPath)
107
112
  }
108
113
 
109
114
  // ========== Shipping Commands ==========
@@ -114,38 +119,38 @@ class PrjctCommands {
114
119
 
115
120
  // ========== Analytics Commands ==========
116
121
 
117
- async context(projectPath: string = process.cwd()): Promise<CommandResult> {
118
- return this.analytics.context(projectPath)
122
+ async dash(view: string = 'default', projectPath: string = process.cwd()): Promise<CommandResult> {
123
+ return this.analytics.dash(view, projectPath)
119
124
  }
120
125
 
121
- async recap(projectPath: string = process.cwd()): Promise<CommandResult> {
122
- return this.analytics.recap(projectPath)
126
+ async help(topic: string = '', projectPath: string = process.cwd()): Promise<CommandResult> {
127
+ return this.analytics.help(topic, projectPath)
123
128
  }
124
129
 
125
- async stuck(issue: string, projectPath: string = process.cwd()): Promise<CommandResult> {
126
- return this.analytics.stuck(issue, projectPath)
127
- }
130
+ // ========== Maintenance Commands ==========
128
131
 
129
- async progress(period: string = 'week', projectPath: string = process.cwd()): Promise<CommandResult> {
130
- return this.analytics.progress(period, projectPath)
132
+ async cleanup(options: CleanupOptions = {}, projectPath: string = process.cwd()): Promise<CommandResult> {
133
+ return this.maintenance.cleanup(options, projectPath)
131
134
  }
132
135
 
133
- async roadmap(projectPath: string = process.cwd()): Promise<CommandResult> {
134
- return this.analytics.roadmap(projectPath)
136
+ async design(target: string | null = null, options: DesignOptions = {}, projectPath: string = process.cwd()): Promise<CommandResult> {
137
+ return this.maintenance.design(target, options, projectPath)
135
138
  }
136
139
 
137
- async status(projectPath: string = process.cwd()): Promise<CommandResult> {
138
- return this.analytics.status(projectPath)
140
+ async recover(projectPath: string = process.cwd()): Promise<CommandResult> {
141
+ return this.maintenance.recover(projectPath)
139
142
  }
140
143
 
141
- // ========== Maintenance Commands ==========
144
+ async undo(projectPath: string = process.cwd()): Promise<CommandResult> {
145
+ return this.maintenance.undo(projectPath)
146
+ }
142
147
 
143
- async cleanup(options: CleanupOptions = {}, projectPath: string = process.cwd()): Promise<CommandResult> {
144
- return this.maintenance.cleanup(options, projectPath)
148
+ async redo(projectPath: string = process.cwd()): Promise<CommandResult> {
149
+ return this.maintenance.redo(projectPath)
145
150
  }
146
151
 
147
- async design(target: string | null = null, options: DesignOptions = {}, projectPath: string = process.cwd()): Promise<CommandResult> {
148
- return this.maintenance.design(target, options, projectPath)
152
+ async history(projectPath: string = process.cwd()): Promise<CommandResult> {
153
+ return this.maintenance.history(projectPath)
149
154
  }
150
155
 
151
156
  // ========== Analysis Commands ==========
@@ -1,15 +1,13 @@
1
1
  /**
2
- * Maintenance Commands: cleanup, design
3
- * Analysis commands moved to analysis.ts
2
+ * Maintenance Commands: cleanup, design, recover, undo, redo, history
3
+ * Git-based snapshots for undo/redo functionality
4
4
  */
5
5
 
6
6
  import path from 'path'
7
7
 
8
- import type { CommandResult, CleanupOptions, DesignOptions, Context } from './types'
8
+ import type { CommandResult, CleanupOptions, DesignOptions } from './types'
9
9
  import {
10
10
  PrjctCommandsBase,
11
- contextBuilder,
12
- toolRegistry,
13
11
  pathManager,
14
12
  configManager,
15
13
  fileHelper,
@@ -17,6 +15,7 @@ import {
17
15
  dateHelper,
18
16
  out
19
17
  } from './base'
18
+ import { mdIdeasManager, mdQueueManager } from '../data'
20
19
 
21
20
  export class MaintenanceCommands extends PrjctCommandsBase {
22
21
  /**
@@ -79,12 +78,16 @@ export class MaintenanceCommands extends PrjctCommandsBase {
79
78
 
80
79
  out.spin('cleaning up...')
81
80
 
82
- const context = await contextBuilder.build(projectPath) as Context
83
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
+ }
84
86
 
85
87
  const cleaned: string[] = []
86
88
 
87
- const memoryPath = pathManager.getFilePath(projectId!, 'memory', 'context.jsonl')
89
+ // Clean memory (keep last 100 entries)
90
+ const memoryPath = pathManager.getFilePath(projectId, 'memory', 'context.jsonl')
88
91
  try {
89
92
  const entries = await jsonlHelper.readJsonLines(memoryPath)
90
93
 
@@ -99,28 +102,11 @@ export class MaintenanceCommands extends PrjctCommandsBase {
99
102
  cleaned.push('Memory: No file found')
100
103
  }
101
104
 
102
- const ideasPath = context.paths.ideas
105
+ // Clean ideas using mdIdeasManager
103
106
  try {
104
- const ideasContent = (await toolRegistry.get('Read')!(ideasPath)) as string
105
- const sections = ideasContent.split('##').filter((s) => s.trim())
106
-
107
- const nonEmpty = sections.filter((section) => {
108
- const lines = section
109
- .trim()
110
- .split('\n')
111
- .filter((l) => l.trim())
112
- return lines.length > 1
113
- })
114
-
115
- if (sections.length !== nonEmpty.length) {
116
- const newContent =
117
- '# IDEAS šŸ’”\n\n## Brain Dump\n\n' +
118
- nonEmpty
119
- .slice(1)
120
- .map((s) => '## ' + s.trim())
121
- .join('\n\n')
122
- await toolRegistry.get('Write')!(ideasPath, newContent)
123
- cleaned.push(`Ideas: ${sections.length - nonEmpty.length} empty sections removed`)
107
+ const result = await mdIdeasManager.cleanup(projectId)
108
+ if (result.removed > 0) {
109
+ cleaned.push(`Ideas: ${result.removed} old archived ideas removed`)
124
110
  } else {
125
111
  cleaned.push('Ideas: No cleanup needed')
126
112
  }
@@ -128,10 +114,10 @@ export class MaintenanceCommands extends PrjctCommandsBase {
128
114
  cleaned.push('Ideas: No file found')
129
115
  }
130
116
 
131
- const nextPath = context.paths.next
117
+ // Check queue for completed tasks using mdQueueManager
132
118
  try {
133
- const nextContent = (await toolRegistry.get('Read')!(nextPath)) as string
134
- const completedTasks = (nextContent.match(/\[x\]/gi) || []).length
119
+ const tasks = await mdQueueManager.getActiveTasks(projectId)
120
+ const completedTasks = tasks.filter(t => t.completed).length
135
121
 
136
122
  if (completedTasks > 0) {
137
123
  cleaned.push(
@@ -223,4 +209,287 @@ export class MaintenanceCommands extends PrjctCommandsBase {
223
209
  return { success: false, error: (error as Error).message }
224
210
  }
225
211
  }
212
+
213
+ /**
214
+ * /p:recover - Recover abandoned session with context restoration
215
+ */
216
+ 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
+ }
265
+ }
266
+
267
+ /**
268
+ * /p:undo - Git-based undo (stash current changes)
269
+ */
270
+ 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
+ }
348
+ }
349
+
350
+ /**
351
+ * /p:redo - Restore previously undone changes
352
+ */
353
+ 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
+ }
435
+ }
436
+
437
+ /**
438
+ * /p:history - Show snapshot history for undo/redo
439
+ */
440
+ 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
+ }
494
+ }
226
495
  }