prjct-cli 0.13.3 ā 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.
- package/CHANGELOG.md +106 -0
- package/bin/prjct +10 -13
- package/core/agentic/memory-system/semantic-memories.ts +2 -1
- package/core/agentic/plan-mode/plan-mode.ts +2 -1
- package/core/agentic/prompt-builder.ts +22 -43
- package/core/agentic/services.ts +5 -5
- package/core/agentic/smart-context.ts +7 -2
- package/core/command-registry/core-commands.ts +54 -29
- package/core/command-registry/optional-commands.ts +64 -0
- package/core/command-registry/setup-commands.ts +18 -3
- package/core/commands/analysis.ts +21 -68
- package/core/commands/analytics.ts +247 -213
- package/core/commands/base.ts +1 -1
- package/core/commands/index.ts +41 -36
- package/core/commands/maintenance.ts +300 -31
- package/core/commands/planning.ts +233 -22
- package/core/commands/setup.ts +3 -8
- package/core/commands/shipping.ts +14 -18
- package/core/commands/types.ts +8 -6
- package/core/commands/workflow.ts +105 -100
- package/core/context/generator.ts +317 -0
- package/core/context-sync.ts +7 -350
- package/core/data/index.ts +13 -32
- package/core/data/md-ideas-manager.ts +155 -0
- package/core/data/md-queue-manager.ts +4 -3
- package/core/data/md-shipped-manager.ts +90 -0
- package/core/data/md-state-manager.ts +11 -7
- package/core/domain/agent-generator.ts +23 -63
- package/core/events/index.ts +143 -0
- package/core/index.ts +17 -14
- package/core/infrastructure/capability-installer.ts +13 -149
- package/core/infrastructure/migrator/project-scanner.ts +2 -1
- package/core/infrastructure/path-manager.ts +4 -6
- package/core/infrastructure/setup.ts +3 -0
- package/core/infrastructure/uuid-migration.ts +750 -0
- package/core/outcomes/recorder.ts +2 -1
- package/core/plugin/loader.ts +4 -7
- package/core/plugin/registry.ts +3 -3
- package/core/schemas/index.ts +23 -25
- package/core/schemas/state.ts +1 -0
- package/core/serializers/ideas-serializer.ts +187 -0
- package/core/serializers/index.ts +16 -0
- package/core/serializers/shipped-serializer.ts +108 -0
- package/core/session/utils.ts +3 -9
- package/core/storage/ideas-storage.ts +273 -0
- package/core/storage/index.ts +204 -0
- package/core/storage/queue-storage.ts +297 -0
- package/core/storage/shipped-storage.ts +223 -0
- package/core/storage/state-storage.ts +235 -0
- package/core/storage/storage-manager.ts +175 -0
- package/package.json +1 -1
- package/packages/web/app/api/projects/[id]/momentum/route.ts +257 -0
- package/packages/web/app/api/sessions/current/route.ts +132 -0
- package/packages/web/app/api/sessions/history/route.ts +96 -14
- package/packages/web/app/globals.css +5 -0
- package/packages/web/app/layout.tsx +2 -0
- package/packages/web/app/project/[id]/code/layout.tsx +18 -0
- package/packages/web/app/project/[id]/code/page.tsx +408 -0
- package/packages/web/app/project/[id]/page.tsx +359 -389
- package/packages/web/app/project/[id]/reports/page.tsx +59 -0
- package/packages/web/app/project/[id]/reports/print/page.tsx +58 -0
- package/packages/web/components/ActivityTimeline/ActivityTimeline.tsx +0 -1
- package/packages/web/components/AgentsCard/AgentsCard.tsx +64 -34
- package/packages/web/components/AgentsCard/AgentsCard.types.ts +1 -0
- package/packages/web/components/AppSidebar/AppSidebar.tsx +135 -11
- package/packages/web/components/BentoCard/BentoCard.constants.ts +3 -3
- package/packages/web/components/BentoCard/BentoCard.tsx +2 -1
- package/packages/web/components/BentoGrid/BentoGrid.tsx +2 -2
- package/packages/web/components/BlockersCard/BlockersCard.tsx +65 -57
- package/packages/web/components/BlockersCard/BlockersCard.types.ts +1 -0
- package/packages/web/components/CommandBar/CommandBar.tsx +67 -0
- package/packages/web/components/CommandBar/index.ts +1 -0
- package/packages/web/components/DashboardContent/DashboardContent.tsx +35 -5
- package/packages/web/components/DateGroup/DateGroup.tsx +1 -1
- package/packages/web/components/EmptyState/EmptyState.tsx +39 -21
- package/packages/web/components/EmptyState/EmptyState.types.ts +1 -0
- package/packages/web/components/EventRow/EventRow.tsx +4 -4
- package/packages/web/components/EventRow/EventRow.utils.ts +3 -3
- package/packages/web/components/HeroSection/HeroSection.tsx +52 -15
- package/packages/web/components/HeroSection/HeroSection.types.ts +4 -4
- package/packages/web/components/HeroSection/HeroSection.utils.ts +7 -3
- package/packages/web/components/IdeasCard/IdeasCard.tsx +94 -27
- package/packages/web/components/IdeasCard/IdeasCard.types.ts +1 -0
- package/packages/web/components/MasonryGrid/MasonryGrid.tsx +18 -0
- package/packages/web/components/MasonryGrid/index.ts +1 -0
- package/packages/web/components/MomentumWidget/MomentumWidget.tsx +119 -0
- package/packages/web/components/MomentumWidget/MomentumWidget.types.ts +16 -0
- package/packages/web/components/MomentumWidget/index.ts +2 -0
- package/packages/web/components/NowCard/NowCard.tsx +81 -56
- package/packages/web/components/NowCard/NowCard.types.ts +1 -0
- package/packages/web/components/PageHeader/PageHeader.tsx +24 -0
- package/packages/web/components/PageHeader/index.ts +1 -0
- package/packages/web/components/ProgressRing/ProgressRing.constants.ts +2 -2
- package/packages/web/components/ProjectAvatar/ProjectAvatar.tsx +2 -2
- package/packages/web/components/ProjectColorDot/ProjectColorDot.tsx +37 -0
- package/packages/web/components/ProjectColorDot/index.ts +1 -0
- package/packages/web/components/ProjectSelectorModal/ProjectSelectorModal.tsx +104 -0
- package/packages/web/components/ProjectSelectorModal/index.ts +1 -0
- package/packages/web/components/Providers/Providers.tsx +4 -1
- package/packages/web/components/QueueCard/QueueCard.tsx +78 -25
- package/packages/web/components/QueueCard/QueueCard.types.ts +1 -0
- package/packages/web/components/QueueCard/QueueCard.utils.ts +3 -3
- package/packages/web/components/RecoverCard/RecoverCard.tsx +72 -0
- package/packages/web/components/RecoverCard/RecoverCard.types.ts +16 -0
- package/packages/web/components/RecoverCard/index.ts +2 -0
- package/packages/web/components/RoadmapCard/RoadmapCard.tsx +101 -33
- package/packages/web/components/RoadmapCard/RoadmapCard.types.ts +1 -0
- package/packages/web/components/ShipsCard/ShipsCard.tsx +71 -28
- package/packages/web/components/ShipsCard/ShipsCard.types.ts +2 -0
- package/packages/web/components/SparklineChart/SparklineChart.tsx +20 -18
- package/packages/web/components/StatsMasonry/StatsMasonry.tsx +95 -0
- package/packages/web/components/StatsMasonry/index.ts +1 -0
- package/packages/web/components/StreakCard/StreakCard.tsx +37 -35
- package/packages/web/components/TasksCounter/TasksCounter.tsx +1 -1
- package/packages/web/components/TechStackBadges/TechStackBadges.tsx +12 -4
- package/packages/web/components/TerminalDock/DockToggleTab.tsx +29 -0
- package/packages/web/components/TerminalDock/TerminalDock.tsx +386 -0
- package/packages/web/components/TerminalDock/TerminalDockTab.tsx +130 -0
- package/packages/web/components/TerminalDock/TerminalTabBar.tsx +142 -0
- package/packages/web/components/TerminalDock/index.ts +2 -0
- package/packages/web/components/VelocityBadge/VelocityBadge.tsx +8 -3
- package/packages/web/components/VelocityCard/VelocityCard.tsx +49 -47
- package/packages/web/components/WeeklyReports/PrintableReport.tsx +259 -0
- package/packages/web/components/WeeklyReports/ReportPreviewCard.tsx +187 -0
- package/packages/web/components/WeeklyReports/WeekCalendar.tsx +288 -0
- package/packages/web/components/WeeklyReports/WeeklyReports.tsx +149 -0
- package/packages/web/components/WeeklyReports/index.ts +4 -0
- package/packages/web/components/WeeklySparkline/WeeklySparkline.tsx +16 -4
- package/packages/web/components/WeeklySparkline/WeeklySparkline.types.ts +1 -0
- package/packages/web/components/charts/SessionsChart.tsx +6 -3
- package/packages/web/components/ui/dialog.tsx +143 -0
- package/packages/web/components/ui/drawer.tsx +135 -0
- package/packages/web/components/ui/select.tsx +187 -0
- package/packages/web/context/GlobalTerminalContext.tsx +538 -0
- package/packages/web/lib/commands.ts +81 -0
- package/packages/web/lib/generate-week-report.ts +285 -0
- package/packages/web/lib/parse-prjct-files.ts +56 -55
- package/packages/web/lib/project-colors.ts +58 -0
- package/packages/web/lib/projects.ts +58 -5
- package/packages/web/lib/services/projects.server.ts +11 -1
- package/packages/web/next-env.d.ts +1 -1
- package/packages/web/package.json +5 -1
- package/templates/commands/analyze.md +39 -3
- package/templates/commands/ask.md +58 -3
- package/templates/commands/bug.md +117 -26
- package/templates/commands/dash.md +95 -158
- package/templates/commands/done.md +130 -148
- package/templates/commands/feature.md +125 -103
- package/templates/commands/git.md +18 -3
- package/templates/commands/idea.md +121 -38
- package/templates/commands/init.md +124 -20
- package/templates/commands/migrate-all.md +63 -28
- package/templates/commands/migrate.md +140 -0
- package/templates/commands/next.md +115 -5
- package/templates/commands/now.md +146 -82
- package/templates/commands/pause.md +89 -74
- package/templates/commands/redo.md +6 -4
- package/templates/commands/resume.md +141 -59
- package/templates/commands/ship.md +103 -231
- package/templates/commands/spec.md +98 -8
- package/templates/commands/suggest.md +22 -2
- package/templates/commands/sync.md +192 -203
- package/templates/commands/undo.md +6 -4
- package/core/data/agents-manager.ts +0 -76
- package/core/data/analysis-manager.ts +0 -83
- package/core/data/base-manager.ts +0 -156
- package/core/data/ideas-manager.ts +0 -81
- package/core/data/outcomes-manager.ts +0 -96
- package/core/data/project-manager.ts +0 -75
- package/core/data/roadmap-manager.ts +0 -118
- package/core/data/shipped-manager.ts +0 -65
- package/core/data/state-manager.ts +0 -214
- package/core/state/index.ts +0 -25
- package/core/state/manager.ts +0 -376
- package/core/state/types.ts +0 -185
- package/core/utils/project-capabilities.ts +0 -156
- package/core/view-generator.ts +0 -536
- package/packages/web/app/project/[id]/stats/loading.tsx +0 -43
- package/packages/web/app/project/[id]/stats/page.tsx +0 -253
- package/templates/agent-assignment.md +0 -72
- package/templates/analysis/project-analysis.md +0 -78
- package/templates/checklists/accessibility.md +0 -33
- package/templates/commands/build.md +0 -17
- package/templates/commands/decision.md +0 -226
- package/templates/commands/fix.md +0 -79
- package/templates/commands/help.md +0 -61
- package/templates/commands/progress.md +0 -14
- package/templates/commands/recap.md +0 -14
- package/templates/commands/roadmap.md +0 -52
- package/templates/commands/status.md +0 -17
- package/templates/commands/task.md +0 -63
- package/templates/commands/work.md +0 -44
- package/templates/commands/workflow.md +0 -12
package/core/commands/index.ts
CHANGED
|
@@ -1,19 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* prjct CLI Commands Handler
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* ZERO if/else business logic.
|
|
4
|
+
* MD-First Architecture - All state in Markdown files.
|
|
6
5
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* -
|
|
12
|
-
* -
|
|
13
|
-
* -
|
|
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
|
|
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
|
|
88
|
-
return this.workflow.
|
|
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
|
|
106
|
-
return this.planning.
|
|
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
|
|
118
|
-
return this.analytics.
|
|
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
|
|
122
|
-
return this.analytics.
|
|
126
|
+
async help(topic: string = '', projectPath: string = process.cwd()): Promise<CommandResult> {
|
|
127
|
+
return this.analytics.help(topic, projectPath)
|
|
123
128
|
}
|
|
124
129
|
|
|
125
|
-
|
|
126
|
-
return this.analytics.stuck(issue, projectPath)
|
|
127
|
-
}
|
|
130
|
+
// ========== Maintenance Commands ==========
|
|
128
131
|
|
|
129
|
-
async
|
|
130
|
-
return this.
|
|
132
|
+
async cleanup(options: CleanupOptions = {}, projectPath: string = process.cwd()): Promise<CommandResult> {
|
|
133
|
+
return this.maintenance.cleanup(options, projectPath)
|
|
131
134
|
}
|
|
132
135
|
|
|
133
|
-
async
|
|
134
|
-
return this.
|
|
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
|
|
138
|
-
return this.
|
|
140
|
+
async recover(projectPath: string = process.cwd()): Promise<CommandResult> {
|
|
141
|
+
return this.maintenance.recover(projectPath)
|
|
139
142
|
}
|
|
140
143
|
|
|
141
|
-
|
|
144
|
+
async undo(projectPath: string = process.cwd()): Promise<CommandResult> {
|
|
145
|
+
return this.maintenance.undo(projectPath)
|
|
146
|
+
}
|
|
142
147
|
|
|
143
|
-
async
|
|
144
|
-
return this.maintenance.
|
|
148
|
+
async redo(projectPath: string = process.cwd()): Promise<CommandResult> {
|
|
149
|
+
return this.maintenance.redo(projectPath)
|
|
145
150
|
}
|
|
146
151
|
|
|
147
|
-
async
|
|
148
|
-
return this.maintenance.
|
|
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
|
-
*
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
105
|
+
// Clean ideas using mdIdeasManager
|
|
103
106
|
try {
|
|
104
|
-
const
|
|
105
|
-
|
|
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
|
-
|
|
117
|
+
// Check queue for completed tasks using mdQueueManager
|
|
132
118
|
try {
|
|
133
|
-
const
|
|
134
|
-
const completedTasks =
|
|
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
|
}
|