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.
- 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
|
@@ -1,16 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Workflow Commands: now, done, next,
|
|
3
|
-
* Core task management
|
|
2
|
+
* Workflow Commands: work (now), done, next, pause, resume
|
|
3
|
+
* Core task management - Write-Through Architecture
|
|
4
|
+
*
|
|
5
|
+
* Uses storage layer: JSON (source) → MD (context) → Event (sync)
|
|
4
6
|
*/
|
|
5
7
|
|
|
6
8
|
import type { CommandResult, Context } from './types'
|
|
9
|
+
import { generateUUID } from '../schemas'
|
|
7
10
|
import {
|
|
8
11
|
PrjctCommandsBase,
|
|
9
12
|
contextBuilder,
|
|
10
|
-
|
|
13
|
+
configManager,
|
|
11
14
|
dateHelper,
|
|
12
15
|
out
|
|
13
16
|
} from './base'
|
|
17
|
+
import { stateStorage, queueStorage } from '../storage'
|
|
14
18
|
|
|
15
19
|
export class WorkflowCommands extends PrjctCommandsBase {
|
|
16
20
|
/**
|
|
@@ -21,39 +25,43 @@ export class WorkflowCommands extends PrjctCommandsBase {
|
|
|
21
25
|
const initResult = await this.ensureProjectInit(projectPath)
|
|
22
26
|
if (!initResult.success) return initResult
|
|
23
27
|
|
|
24
|
-
const
|
|
28
|
+
const projectId = await configManager.getProjectId(projectPath)
|
|
29
|
+
if (!projectId) {
|
|
30
|
+
out.fail('no project ID')
|
|
31
|
+
return { success: false, error: 'No project ID found' }
|
|
32
|
+
}
|
|
25
33
|
|
|
26
34
|
if (task) {
|
|
35
|
+
const context = await contextBuilder.build(projectPath, { task }) as Context
|
|
27
36
|
const agentResult = await this._assignAgentForTask(task, projectPath, context)
|
|
28
37
|
const agent = agentResult.agent?.name || 'generalist'
|
|
29
|
-
const confidence = agentResult.routing?.confidence || 0.5
|
|
30
38
|
|
|
31
|
-
|
|
32
|
-
await
|
|
39
|
+
// Write-through: JSON → MD → Event
|
|
40
|
+
await stateStorage.startTask(projectId, {
|
|
41
|
+
id: generateUUID(),
|
|
42
|
+
description: task,
|
|
43
|
+
sessionId: generateUUID()
|
|
44
|
+
})
|
|
33
45
|
|
|
34
46
|
out.done(`${task} [${agent}]`)
|
|
35
47
|
|
|
36
48
|
await this.logToMemory(projectPath, 'task_started', {
|
|
37
49
|
task,
|
|
38
50
|
agent,
|
|
39
|
-
confidence,
|
|
40
51
|
timestamp: dateHelper.getTimestamp(),
|
|
41
52
|
})
|
|
42
53
|
return { success: true, task, agent }
|
|
43
54
|
} else {
|
|
44
|
-
|
|
55
|
+
// Read from storage (JSON is source of truth)
|
|
56
|
+
const currentTask = await stateStorage.getCurrentTask(projectId)
|
|
45
57
|
|
|
46
|
-
if (!
|
|
58
|
+
if (!currentTask) {
|
|
47
59
|
out.warn('no active task')
|
|
48
60
|
return { success: true, message: 'No active task' }
|
|
49
61
|
}
|
|
50
62
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
const currentTask = taskMatch ? taskMatch[1] : 'unknown'
|
|
54
|
-
const currentAgent = agentMatch ? agentMatch[1] : ''
|
|
55
|
-
out.done(`working on: ${currentTask}${currentAgent ? ` [${currentAgent}]` : ''}`)
|
|
56
|
-
return { success: true, content: nowContent }
|
|
63
|
+
out.done(`working on: ${currentTask.description}`)
|
|
64
|
+
return { success: true, task: currentTask.description, currentTask }
|
|
57
65
|
}
|
|
58
66
|
} catch (error) {
|
|
59
67
|
out.fail((error as Error).message)
|
|
@@ -69,26 +77,29 @@ export class WorkflowCommands extends PrjctCommandsBase {
|
|
|
69
77
|
const initResult = await this.ensureProjectInit(projectPath)
|
|
70
78
|
if (!initResult.success) return initResult
|
|
71
79
|
|
|
72
|
-
const
|
|
73
|
-
|
|
80
|
+
const projectId = await configManager.getProjectId(projectPath)
|
|
81
|
+
if (!projectId) {
|
|
82
|
+
out.fail('no project ID')
|
|
83
|
+
return { success: false, error: 'No project ID found' }
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Read from storage
|
|
87
|
+
const currentTask = await stateStorage.getCurrentTask(projectId)
|
|
74
88
|
|
|
75
|
-
if (!
|
|
89
|
+
if (!currentTask) {
|
|
76
90
|
out.warn('no active task')
|
|
77
91
|
return { success: true, message: 'No active task to complete' }
|
|
78
92
|
}
|
|
79
93
|
|
|
80
|
-
const
|
|
81
|
-
const task = taskMatch ? taskMatch[1] : 'task'
|
|
82
|
-
|
|
83
|
-
const startedMatch = nowContent.match(/Started: (.+)/)
|
|
94
|
+
const task = currentTask.description
|
|
84
95
|
let duration = ''
|
|
85
|
-
if (
|
|
86
|
-
const started = new Date(
|
|
96
|
+
if (currentTask.startedAt) {
|
|
97
|
+
const started = new Date(currentTask.startedAt)
|
|
87
98
|
duration = dateHelper.calculateDuration(started)
|
|
88
99
|
}
|
|
89
100
|
|
|
90
|
-
|
|
91
|
-
await
|
|
101
|
+
// Write-through: Complete task (JSON → MD → Event)
|
|
102
|
+
await stateStorage.completeTask(projectId)
|
|
92
103
|
|
|
93
104
|
out.done(`${task}${duration ? ` (${duration})` : ''}`)
|
|
94
105
|
|
|
@@ -112,18 +123,23 @@ export class WorkflowCommands extends PrjctCommandsBase {
|
|
|
112
123
|
const initResult = await this.ensureProjectInit(projectPath)
|
|
113
124
|
if (!initResult.success) return initResult
|
|
114
125
|
|
|
115
|
-
const
|
|
116
|
-
|
|
126
|
+
const projectId = await configManager.getProjectId(projectPath)
|
|
127
|
+
if (!projectId) {
|
|
128
|
+
out.fail('no project ID')
|
|
129
|
+
return { success: false, error: 'No project ID found' }
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Read queue from storage
|
|
133
|
+
const tasks = await queueStorage.getActiveTasks(projectId)
|
|
117
134
|
|
|
118
|
-
if (
|
|
135
|
+
if (tasks.length === 0) {
|
|
119
136
|
out.warn('queue empty')
|
|
120
137
|
return { success: true, message: 'Queue is empty' }
|
|
121
138
|
}
|
|
122
139
|
|
|
123
|
-
|
|
124
|
-
out.done(`${taskCount} task${taskCount !== 1 ? 's' : ''} queued`)
|
|
140
|
+
out.done(`${tasks.length} task${tasks.length !== 1 ? 's' : ''} queued`)
|
|
125
141
|
|
|
126
|
-
return { success: true,
|
|
142
|
+
return { success: true, tasks, count: tasks.length }
|
|
127
143
|
} catch (error) {
|
|
128
144
|
out.fail((error as Error).message)
|
|
129
145
|
return { success: false, error: (error as Error).message }
|
|
@@ -131,95 +147,84 @@ export class WorkflowCommands extends PrjctCommandsBase {
|
|
|
131
147
|
}
|
|
132
148
|
|
|
133
149
|
/**
|
|
134
|
-
* /p:
|
|
150
|
+
* /p:pause - Pause active task to handle interruption
|
|
135
151
|
*/
|
|
136
|
-
async
|
|
152
|
+
async pause(reason: string = '', projectPath: string = process.cwd()): Promise<CommandResult> {
|
|
137
153
|
try {
|
|
138
154
|
const initResult = await this.ensureProjectInit(projectPath)
|
|
139
155
|
if (!initResult.success) return initResult
|
|
140
156
|
|
|
141
|
-
const
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
console.log('⚠️ Already working on a task!')
|
|
146
|
-
console.log(' Complete it with /p:done first\n')
|
|
147
|
-
const taskMatch = nowContent.match(/\*\*(.+?)\*\*/)
|
|
148
|
-
const currentTask = taskMatch ? taskMatch[1] : 'current task'
|
|
149
|
-
console.log(` Current: ${currentTask}`)
|
|
150
|
-
return { success: false, message: 'Task already active' }
|
|
157
|
+
const projectId = await configManager.getProjectId(projectPath)
|
|
158
|
+
if (!projectId) {
|
|
159
|
+
out.fail('no project ID')
|
|
160
|
+
return { success: false, error: 'No project ID found' }
|
|
151
161
|
}
|
|
152
162
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
if (!isNaN(Number(taskOrNumber))) {
|
|
156
|
-
const nextContent = await toolRegistry.get('Read')!(context.paths.next) as string
|
|
157
|
-
const tasks = nextContent
|
|
158
|
-
.split('\n')
|
|
159
|
-
.filter((line) => line.trim().match(/^\d+\./) || line.includes('[ ]'))
|
|
160
|
-
|
|
161
|
-
const index = parseInt(taskOrNumber) - 1
|
|
162
|
-
if (index >= 0 && index < tasks.length) {
|
|
163
|
-
task = tasks[index].replace(/^\d+\.\s*\[.\]\s*/, '').trim()
|
|
164
|
-
console.log(`📋 Selected from queue: ${task}\n`)
|
|
165
|
-
} else {
|
|
166
|
-
console.log(`❌ Invalid task number. Queue has ${tasks.length} tasks.`)
|
|
167
|
-
console.log(' Use /p:next to see queue')
|
|
168
|
-
return { success: false, error: 'Invalid task number' }
|
|
169
|
-
}
|
|
170
|
-
}
|
|
163
|
+
const currentTask = await stateStorage.getCurrentTask(projectId)
|
|
171
164
|
|
|
172
|
-
if (!
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
console.log(' or: /p:build 1 (select from queue)')
|
|
176
|
-
return { success: false, error: 'Task required' }
|
|
165
|
+
if (!currentTask) {
|
|
166
|
+
out.warn('no active task to pause')
|
|
167
|
+
return { success: false, message: 'No active task to pause' }
|
|
177
168
|
}
|
|
178
169
|
|
|
179
|
-
|
|
170
|
+
// Write-through: Pause task (JSON → MD → Event)
|
|
171
|
+
await stateStorage.pauseTask(projectId, reason)
|
|
180
172
|
|
|
181
|
-
const
|
|
182
|
-
|
|
173
|
+
const taskDesc = currentTask.description.slice(0, 40)
|
|
174
|
+
out.done(`paused: ${taskDesc}${reason ? ` (${reason})` : ''}`)
|
|
183
175
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
176
|
+
await this.logToMemory(projectPath, 'task_paused', {
|
|
177
|
+
task: currentTask.description,
|
|
178
|
+
reason,
|
|
179
|
+
timestamp: dateHelper.getTimestamp(),
|
|
180
|
+
})
|
|
188
181
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
182
|
+
return { success: true, task: currentTask.description, reason }
|
|
183
|
+
} catch (error) {
|
|
184
|
+
out.fail((error as Error).message)
|
|
185
|
+
return { success: false, error: (error as Error).message }
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* /p:resume - Resume most recently paused task
|
|
191
|
+
*/
|
|
192
|
+
async resume(taskId: string | null = null, projectPath: string = process.cwd()): Promise<CommandResult> {
|
|
193
|
+
try {
|
|
194
|
+
const initResult = await this.ensureProjectInit(projectPath)
|
|
195
|
+
if (!initResult.success) return initResult
|
|
193
196
|
|
|
194
|
-
const
|
|
197
|
+
const projectId = await configManager.getProjectId(projectPath)
|
|
198
|
+
if (!projectId) {
|
|
199
|
+
out.fail('no project ID')
|
|
200
|
+
return { success: false, error: 'No project ID found' }
|
|
201
|
+
}
|
|
195
202
|
|
|
196
|
-
|
|
203
|
+
// Check if already working on a task
|
|
204
|
+
const currentTask = await stateStorage.getCurrentTask(projectId)
|
|
205
|
+
if (currentTask) {
|
|
206
|
+
out.warn('already working on a task')
|
|
207
|
+
return { success: false, message: `Already working on: ${currentTask.description}` }
|
|
208
|
+
}
|
|
197
209
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
Complexity: ${complexity.level}
|
|
201
|
-
Agent: ${agent} (${Math.round(confidence * 100)}% confidence)
|
|
202
|
-
`
|
|
203
|
-
await toolRegistry.get('Write')!(context.paths.now, nowContentNew)
|
|
210
|
+
// Write-through: Resume task (JSON → MD → Event)
|
|
211
|
+
const resumed = await stateStorage.resumeTask(projectId)
|
|
204
212
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
console.log('• /p:stuck → Get help if needed')
|
|
213
|
+
if (!resumed) {
|
|
214
|
+
out.warn('no paused task to resume')
|
|
215
|
+
return { success: false, message: 'No paused task found' }
|
|
216
|
+
}
|
|
210
217
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
agent,
|
|
216
|
-
confidence,
|
|
218
|
+
out.done(`resumed: ${resumed.description.slice(0, 40)}`)
|
|
219
|
+
|
|
220
|
+
await this.logToMemory(projectPath, 'task_resumed', {
|
|
221
|
+
task: resumed.description,
|
|
217
222
|
timestamp: dateHelper.getTimestamp(),
|
|
218
223
|
})
|
|
219
224
|
|
|
220
|
-
return { success: true, task
|
|
225
|
+
return { success: true, task: resumed.description }
|
|
221
226
|
} catch (error) {
|
|
222
|
-
|
|
227
|
+
out.fail((error as Error).message)
|
|
223
228
|
return { success: false, error: (error as Error).message }
|
|
224
229
|
}
|
|
225
230
|
}
|
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Context Generator
|
|
3
|
+
*
|
|
4
|
+
* Generates MD files in context/ from JSON data in data/
|
|
5
|
+
* These MD files are for Claude to read.
|
|
6
|
+
*
|
|
7
|
+
* context/
|
|
8
|
+
* ├── CLAUDE.md - Full project context
|
|
9
|
+
* ├── now.md - Current task context
|
|
10
|
+
* ├── queue.md - Task queue
|
|
11
|
+
* └── summary.md - Project summary
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import fs from 'fs/promises'
|
|
15
|
+
import path from 'path'
|
|
16
|
+
import os from 'os'
|
|
17
|
+
import { exec } from 'child_process'
|
|
18
|
+
import { promisify } from 'util'
|
|
19
|
+
import { getStorage } from '../storage'
|
|
20
|
+
|
|
21
|
+
const execAsync = promisify(exec)
|
|
22
|
+
|
|
23
|
+
interface Task {
|
|
24
|
+
id: string
|
|
25
|
+
description: string
|
|
26
|
+
status: string
|
|
27
|
+
priority?: string
|
|
28
|
+
startedAt?: string
|
|
29
|
+
completedAt?: string
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
interface Feature {
|
|
33
|
+
id: string
|
|
34
|
+
name: string
|
|
35
|
+
status: string
|
|
36
|
+
description?: string
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
interface Idea {
|
|
40
|
+
id: string
|
|
41
|
+
title: string
|
|
42
|
+
status?: string
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
interface ProjectData {
|
|
46
|
+
name?: string
|
|
47
|
+
repoPath?: string
|
|
48
|
+
techStack?: string[]
|
|
49
|
+
version?: string
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
interface Agent {
|
|
53
|
+
name: string
|
|
54
|
+
role?: string
|
|
55
|
+
domain?: string
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Generate all context MD files from JSON data
|
|
60
|
+
*/
|
|
61
|
+
export async function generateContext(projectId: string, repoPath: string): Promise<void> {
|
|
62
|
+
const globalPath = path.join(os.homedir(), '.prjct-cli/projects', projectId)
|
|
63
|
+
const contextPath = path.join(globalPath, 'context')
|
|
64
|
+
const storage = getStorage(projectId)
|
|
65
|
+
|
|
66
|
+
// Ensure context directory exists
|
|
67
|
+
await fs.mkdir(contextPath, { recursive: true })
|
|
68
|
+
|
|
69
|
+
// Read all data
|
|
70
|
+
const project = await storage.read<ProjectData>(['project']) || {}
|
|
71
|
+
const taskPaths = await storage.list(['task'])
|
|
72
|
+
const featurePaths = await storage.list(['feature'])
|
|
73
|
+
const ideaPaths = await storage.list(['idea'])
|
|
74
|
+
const agentPaths = await storage.list(['agent'])
|
|
75
|
+
|
|
76
|
+
const tasks: Task[] = []
|
|
77
|
+
for (const p of taskPaths) {
|
|
78
|
+
const task = await storage.read<Task>(p)
|
|
79
|
+
if (task) tasks.push({ ...task, id: p[1] })
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const features: Feature[] = []
|
|
83
|
+
for (const p of featurePaths) {
|
|
84
|
+
const feature = await storage.read<Feature>(p)
|
|
85
|
+
if (feature) features.push({ ...feature, id: p[1] })
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const ideas: Idea[] = []
|
|
89
|
+
for (const p of ideaPaths) {
|
|
90
|
+
const idea = await storage.read<Idea>(p)
|
|
91
|
+
if (idea) ideas.push({ ...idea, id: p[1] })
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const agents: Agent[] = []
|
|
95
|
+
for (const p of agentPaths) {
|
|
96
|
+
const agent = await storage.read<Agent>(p)
|
|
97
|
+
if (agent) agents.push({ ...agent, name: p[1] })
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Get git data
|
|
101
|
+
const gitData = await getGitData(repoPath)
|
|
102
|
+
|
|
103
|
+
// Get package.json data
|
|
104
|
+
const pkgData = await getPackageData(repoPath)
|
|
105
|
+
|
|
106
|
+
// Generate each context file
|
|
107
|
+
await generateClaudeMd(contextPath, projectId, project, tasks, features, ideas, agents, gitData, pkgData, repoPath)
|
|
108
|
+
await generateNowMd(contextPath, tasks)
|
|
109
|
+
await generateQueueMd(contextPath, tasks)
|
|
110
|
+
await generateSummaryMd(contextPath, project, gitData, pkgData)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async function getGitData(repoPath: string) {
|
|
114
|
+
const data = {
|
|
115
|
+
branch: 'main',
|
|
116
|
+
commits: 0,
|
|
117
|
+
contributors: 0,
|
|
118
|
+
hasChanges: false,
|
|
119
|
+
recentCommits: [] as { hash: string; message: string; date: string }[]
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
try {
|
|
123
|
+
const { stdout: branch } = await execAsync('git branch --show-current', { cwd: repoPath })
|
|
124
|
+
data.branch = branch.trim() || 'main'
|
|
125
|
+
|
|
126
|
+
const { stdout: commits } = await execAsync('git rev-list --count HEAD', { cwd: repoPath })
|
|
127
|
+
data.commits = parseInt(commits.trim()) || 0
|
|
128
|
+
|
|
129
|
+
const { stdout: contributors } = await execAsync('git shortlog -sn --all | wc -l', { cwd: repoPath })
|
|
130
|
+
data.contributors = parseInt(contributors.trim()) || 0
|
|
131
|
+
|
|
132
|
+
const { stdout: status } = await execAsync('git status --porcelain', { cwd: repoPath })
|
|
133
|
+
data.hasChanges = status.trim().length > 0
|
|
134
|
+
|
|
135
|
+
const { stdout: log } = await execAsync('git log --oneline -10 --pretty=format:"%h|%s|%ad" --date=short', { cwd: repoPath })
|
|
136
|
+
data.recentCommits = log.split('\n').filter(Boolean).map(line => {
|
|
137
|
+
const [hash, message, date] = line.split('|')
|
|
138
|
+
return { hash, message, date }
|
|
139
|
+
})
|
|
140
|
+
} catch { /* not git */ }
|
|
141
|
+
|
|
142
|
+
return data
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
async function getPackageData(repoPath: string) {
|
|
146
|
+
const data = {
|
|
147
|
+
dependencies: {} as Record<string, string>,
|
|
148
|
+
devDependencies: {} as Record<string, string>,
|
|
149
|
+
scripts: {} as Record<string, string>
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
try {
|
|
153
|
+
const pkgPath = path.join(repoPath, 'package.json')
|
|
154
|
+
const pkg = JSON.parse(await fs.readFile(pkgPath, 'utf-8'))
|
|
155
|
+
data.dependencies = pkg.dependencies || {}
|
|
156
|
+
data.devDependencies = pkg.devDependencies || {}
|
|
157
|
+
data.scripts = pkg.scripts || {}
|
|
158
|
+
} catch { /* no package.json */ }
|
|
159
|
+
|
|
160
|
+
return data
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
async function generateClaudeMd(
|
|
164
|
+
contextPath: string,
|
|
165
|
+
projectId: string,
|
|
166
|
+
project: ProjectData,
|
|
167
|
+
tasks: Task[],
|
|
168
|
+
features: Feature[],
|
|
169
|
+
ideas: Idea[],
|
|
170
|
+
agents: Agent[],
|
|
171
|
+
gitData: Awaited<ReturnType<typeof getGitData>>,
|
|
172
|
+
pkgData: Awaited<ReturnType<typeof getPackageData>>,
|
|
173
|
+
repoPath: string
|
|
174
|
+
) {
|
|
175
|
+
const projectName = project.name || path.basename(repoPath)
|
|
176
|
+
const currentTask = tasks.find(t => t.status === 'in_progress')
|
|
177
|
+
const pendingTasks = tasks.filter(t => t.status === 'pending')
|
|
178
|
+
const activeFeatures = features.filter(f => f.status === 'in_progress' || f.status === 'active')
|
|
179
|
+
|
|
180
|
+
const deps = Object.keys(pkgData.dependencies)
|
|
181
|
+
const devDeps = Object.keys(pkgData.devDependencies)
|
|
182
|
+
|
|
183
|
+
const content = `# ${projectName} - Project Context
|
|
184
|
+
<!-- projectId: ${projectId} -->
|
|
185
|
+
<!-- Generated: ${new Date().toISOString()} -->
|
|
186
|
+
|
|
187
|
+
## PROJECT DATA
|
|
188
|
+
|
|
189
|
+
### Dependencies (${deps.length + devDeps.length})
|
|
190
|
+
|
|
191
|
+
**Production** (${deps.length}):
|
|
192
|
+
${deps.length > 0 ? deps.map(d => `- ${d}: ${pkgData.dependencies[d]}`).join('\n') : '_None_'}
|
|
193
|
+
|
|
194
|
+
**Dev** (${devDeps.length}):
|
|
195
|
+
${devDeps.length > 0 ? devDeps.map(d => `- ${d}: ${pkgData.devDependencies[d]}`).join('\n') : '_None_'}
|
|
196
|
+
|
|
197
|
+
### Scripts
|
|
198
|
+
|
|
199
|
+
${Object.keys(pkgData.scripts).length > 0 ? Object.entries(pkgData.scripts).map(([k, v]) => `- \`${k}\`: ${v}`).join('\n') : '_None_'}
|
|
200
|
+
|
|
201
|
+
### Git
|
|
202
|
+
|
|
203
|
+
- Branch: ${gitData.branch}
|
|
204
|
+
- Commits: ${gitData.commits}
|
|
205
|
+
- Contributors: ${gitData.contributors}
|
|
206
|
+
- Uncommitted: ${gitData.hasChanges ? 'Yes' : 'No'}
|
|
207
|
+
|
|
208
|
+
**Recent:**
|
|
209
|
+
${gitData.recentCommits.length > 0 ? gitData.recentCommits.slice(0, 5).map(c => `- \`${c.hash}\` ${c.message}`).join('\n') : '_None_'}
|
|
210
|
+
|
|
211
|
+
---
|
|
212
|
+
|
|
213
|
+
## CURRENT STATE
|
|
214
|
+
|
|
215
|
+
**Now:** ${currentTask ? currentTask.description : '_No active task_'}
|
|
216
|
+
|
|
217
|
+
**Queue (${pendingTasks.length}):**
|
|
218
|
+
${pendingTasks.length > 0 ? pendingTasks.slice(0, 10).map((t, i) => `${i + 1}. ${t.description}`).join('\n') : '_Empty_'}
|
|
219
|
+
|
|
220
|
+
**Active Features (${activeFeatures.length}):**
|
|
221
|
+
${activeFeatures.length > 0 ? activeFeatures.map(f => `- ${f.name}`).join('\n') : '_None_'}
|
|
222
|
+
|
|
223
|
+
**Ideas (${ideas.length}):**
|
|
224
|
+
${ideas.length > 0 ? ideas.slice(0, 5).map(i => `- ${i.title}`).join('\n') : '_None_'}
|
|
225
|
+
|
|
226
|
+
---
|
|
227
|
+
|
|
228
|
+
## AGENTS
|
|
229
|
+
|
|
230
|
+
${agents.length > 0 ? agents.map(a => `- **${a.name}**: ${a.role || 'Specialist'}`).join('\n') : '_None_'}
|
|
231
|
+
|
|
232
|
+
---
|
|
233
|
+
|
|
234
|
+
## DATA LOCATION
|
|
235
|
+
|
|
236
|
+
\`\`\`
|
|
237
|
+
~/.prjct-cli/projects/${projectId}/
|
|
238
|
+
├── data/ # JSON (source of truth)
|
|
239
|
+
│ ├── project.json
|
|
240
|
+
│ ├── tasks/
|
|
241
|
+
│ ├── features/
|
|
242
|
+
│ ├── ideas/
|
|
243
|
+
│ └── agents/
|
|
244
|
+
├── context/ # MD (for Claude)
|
|
245
|
+
│ ├── CLAUDE.md
|
|
246
|
+
│ ├── now.md
|
|
247
|
+
│ └── queue.md
|
|
248
|
+
└── sync/ # Sync state
|
|
249
|
+
└── pending.json
|
|
250
|
+
\`\`\`
|
|
251
|
+
`
|
|
252
|
+
|
|
253
|
+
await fs.writeFile(path.join(contextPath, 'CLAUDE.md'), content, 'utf-8')
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
async function generateNowMd(contextPath: string, tasks: Task[]) {
|
|
257
|
+
const currentTask = tasks.find(t => t.status === 'in_progress')
|
|
258
|
+
|
|
259
|
+
const content = currentTask
|
|
260
|
+
? `# NOW
|
|
261
|
+
|
|
262
|
+
**Task:** ${currentTask.description}
|
|
263
|
+
|
|
264
|
+
**Started:** ${currentTask.startedAt || 'Unknown'}
|
|
265
|
+
|
|
266
|
+
**Priority:** ${currentTask.priority || 'medium'}
|
|
267
|
+
`
|
|
268
|
+
: `# NOW
|
|
269
|
+
|
|
270
|
+
_No active task. Use /p:now to start._
|
|
271
|
+
`
|
|
272
|
+
|
|
273
|
+
await fs.writeFile(path.join(contextPath, 'now.md'), content, 'utf-8')
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
async function generateQueueMd(contextPath: string, tasks: Task[]) {
|
|
277
|
+
const pendingTasks = tasks.filter(t => t.status === 'pending')
|
|
278
|
+
|
|
279
|
+
const content = `# QUEUE
|
|
280
|
+
|
|
281
|
+
${pendingTasks.length > 0
|
|
282
|
+
? pendingTasks.map((t, i) => `${i + 1}. ${t.description}${t.priority ? ` [${t.priority}]` : ''}`).join('\n')
|
|
283
|
+
: '_Empty queue. Use /p:next to add tasks._'
|
|
284
|
+
}
|
|
285
|
+
`
|
|
286
|
+
|
|
287
|
+
await fs.writeFile(path.join(contextPath, 'queue.md'), content, 'utf-8')
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
async function generateSummaryMd(
|
|
291
|
+
contextPath: string,
|
|
292
|
+
project: ProjectData,
|
|
293
|
+
gitData: Awaited<ReturnType<typeof getGitData>>,
|
|
294
|
+
pkgData: Awaited<ReturnType<typeof getPackageData>>
|
|
295
|
+
) {
|
|
296
|
+
const content = `# PROJECT SUMMARY
|
|
297
|
+
|
|
298
|
+
**Name:** ${project.name || 'Unknown'}
|
|
299
|
+
**Version:** ${project.version || 'N/A'}
|
|
300
|
+
**Stack:** ${project.techStack?.join(', ') || 'Not detected'}
|
|
301
|
+
|
|
302
|
+
## Git
|
|
303
|
+
|
|
304
|
+
- Branch: ${gitData.branch}
|
|
305
|
+
- Commits: ${gitData.commits}
|
|
306
|
+
- Status: ${gitData.hasChanges ? 'Has uncommitted changes' : 'Clean'}
|
|
307
|
+
|
|
308
|
+
## Dependencies
|
|
309
|
+
|
|
310
|
+
- Production: ${Object.keys(pkgData.dependencies).length}
|
|
311
|
+
- Dev: ${Object.keys(pkgData.devDependencies).length}
|
|
312
|
+
`
|
|
313
|
+
|
|
314
|
+
await fs.writeFile(path.join(contextPath, 'summary.md'), content, 'utf-8')
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
export default { generateContext }
|