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,10 +1,13 @@
1
1
  /**
2
- * Planning Commands: init, feature, bug, architect
2
+ * Planning Commands: init, feature, bug, idea, spec
3
+ * Write-Through Architecture: JSON → MD → Event
3
4
  */
4
5
 
5
6
  import path from 'path'
6
7
 
7
8
  import type { CommandResult, AnalyzeOptions, Context } from './types'
9
+ import { generateUUID } from '../schemas'
10
+ import type { Priority, TaskType, TaskSection } from '../schemas/state'
8
11
  import {
9
12
  PrjctCommandsBase,
10
13
  contextBuilder,
@@ -15,7 +18,9 @@ import {
15
18
  dateHelper,
16
19
  out
17
20
  } from './base'
21
+ import { queueStorage, ideasStorage } from '../storage'
18
22
  import authorDetector from '../infrastructure/author-detector'
23
+ import commandInstaller from '../infrastructure/command-installer'
19
24
 
20
25
  export class PlanningCommands extends PrjctCommandsBase {
21
26
  /**
@@ -98,14 +103,12 @@ export class PlanningCommands extends PrjctCommandsBase {
98
103
  const sessionContent = `# Architect Session\n\n## Idea\n${idea}\n\n## Status\nInitialized - awaiting stack recommendation\n\nGenerated: ${new Date().toLocaleString()}\n`
99
104
  await toolRegistry.get('Write')!(sessionPath, sessionContent)
100
105
 
101
- const commandInstaller = require('../infrastructure/command-installer')
102
106
  await commandInstaller.installGlobalConfig()
103
107
 
104
108
  out.done('architect mode ready')
105
109
  return { success: true, mode: 'architect', projectId, idea }
106
110
  }
107
111
 
108
- const commandInstaller = require('../infrastructure/command-installer')
109
112
  await commandInstaller.installGlobalConfig()
110
113
 
111
114
  out.done('initialized')
@@ -129,10 +132,17 @@ export class PlanningCommands extends PrjctCommandsBase {
129
132
  return { success: false, error: 'Description required' }
130
133
  }
131
134
 
135
+ const projectId = await configManager.getProjectId(projectPath)
136
+ if (!projectId) {
137
+ out.fail('no project ID')
138
+ return { success: false, error: 'No project ID found' }
139
+ }
140
+
132
141
  out.spin(`planning ${description}...`)
133
142
 
134
143
  const context = await contextBuilder.build(projectPath, { description }) as Context
135
144
  const tasks = this._breakdownFeatureTasks(description)
145
+ const featureId = generateUUID()
136
146
 
137
147
  const tasksWithAgents: { task: string; agent: string }[] = []
138
148
  for (const taskDesc of tasks) {
@@ -141,17 +151,20 @@ export class PlanningCommands extends PrjctCommandsBase {
141
151
  tasksWithAgents.push({ task: taskDesc, agent })
142
152
  }
143
153
 
144
- const nextContent =
145
- (await toolRegistry.get('Read')!(context.paths.next) as string) || '# NEXT\n\n## Priority Queue\n\n'
146
- const taskSection =
147
- `\n## Feature: ${description}\n\n` +
148
- tasksWithAgents.map((t, i) => `${i + 1}. [${t.agent}] [ ] ${t.task}`).join('\n') +
149
- `\n\nEstimated: ${tasks.length * 2}h\n`
150
-
151
- await toolRegistry.get('Write')!(context.paths.next, nextContent + taskSection)
154
+ // Write-through: Add tasks (JSON → MD → Event)
155
+ await queueStorage.addTasks(projectId, tasksWithAgents.map(t => ({
156
+ description: t.task,
157
+ priority: 'medium' as Priority,
158
+ type: 'feature' as TaskType,
159
+ section: 'active' as TaskSection,
160
+ featureId,
161
+ originFeature: description,
162
+ agent: t.agent
163
+ })))
152
164
 
153
165
  await this.logToMemory(projectPath, 'feature_planned', {
154
166
  feature: description,
167
+ featureId,
155
168
  tasks: tasksWithAgents.length,
156
169
  assignments: tasksWithAgents.map(t => ({ task: t.task, agent: t.agent })),
157
170
  timestamp: dateHelper.getTimestamp(),
@@ -165,7 +178,7 @@ export class PlanningCommands extends PrjctCommandsBase {
165
178
 
166
179
  out.done(`${tasks.length} tasks [${agentSummary}]`)
167
180
 
168
- return { success: true, feature: description, tasks: tasksWithAgents }
181
+ return { success: true, feature: description, featureId, tasks: tasksWithAgents }
169
182
  } catch (error) {
170
183
  out.fail((error as Error).message)
171
184
  return { success: false, error: (error as Error).message }
@@ -185,6 +198,12 @@ export class PlanningCommands extends PrjctCommandsBase {
185
198
  return { success: false, error: 'Description required' }
186
199
  }
187
200
 
201
+ const projectId = await configManager.getProjectId(projectPath)
202
+ if (!projectId) {
203
+ out.fail('no project ID')
204
+ return { success: false, error: 'No project ID found' }
205
+ }
206
+
188
207
  out.spin('tracking bug...')
189
208
 
190
209
  const context = await contextBuilder.build(projectPath, { description }) as Context
@@ -193,20 +212,28 @@ export class PlanningCommands extends PrjctCommandsBase {
193
212
  const agentResult = await this._assignAgentForTask(`fix bug: ${description}`, projectPath, context)
194
213
  const agent = agentResult.agent?.name || 'generalist'
195
214
 
196
- const nextContent =
197
- (await toolRegistry.get('Read')!(context.paths.next) as string) || '# NEXT\n\n## Priority Queue\n\n'
198
- const bugEntry = `\n## 🐛 BUG [${severity.toUpperCase()}] [${agent}]: ${description}\n\nReported: ${new Date().toLocaleString()}\nPriority: ${severity === 'critical' ? '⚠️ URGENT' : severity === 'high' ? '🔴 High' : '🟡 Normal'}\nAssigned: ${agent}\n`
199
-
200
- const updatedContent =
201
- severity === 'critical' || severity === 'high'
202
- ? nextContent.replace('## Priority Queue\n\n', `## Priority Queue\n\n${bugEntry}\n`)
203
- : nextContent + bugEntry
204
-
205
- await toolRegistry.get('Write')!(context.paths.next, updatedContent)
215
+ // Map severity to Priority type
216
+ const priorityMap: Record<string, Priority> = {
217
+ 'critical': 'critical',
218
+ 'high': 'high',
219
+ 'medium': 'medium',
220
+ 'low': 'low'
221
+ }
222
+ const priority = priorityMap[severity] || 'medium'
223
+
224
+ // Write-through: Add bug task (JSON → MD → Event)
225
+ await queueStorage.addTask(projectId, {
226
+ description: `🐛 ${description}`,
227
+ priority,
228
+ type: 'bug' as TaskType,
229
+ section: 'active' as TaskSection,
230
+ agent
231
+ })
206
232
 
207
233
  await this.logToMemory(projectPath, 'bug_reported', {
208
234
  bug: description,
209
235
  severity,
236
+ priority,
210
237
  agent,
211
238
  timestamp: dateHelper.getTimestamp(),
212
239
  })
@@ -308,4 +335,188 @@ export class PlanningCommands extends PrjctCommandsBase {
308
335
  return { success: false, error: (error as Error).message }
309
336
  }
310
337
  }
338
+
339
+ /**
340
+ * /p:idea - Transform ideas into architectures or quick captures
341
+ */
342
+ async idea(description: string, projectPath: string = process.cwd()): Promise<CommandResult> {
343
+ try {
344
+ const initResult = await this.ensureProjectInit(projectPath)
345
+ if (!initResult.success) return initResult
346
+
347
+ if (!description) {
348
+ out.fail('idea description required')
349
+ return { success: false, error: 'Idea description required' }
350
+ }
351
+
352
+ const projectId = await configManager.getProjectId(projectPath)
353
+ if (!projectId) {
354
+ out.fail('no project ID')
355
+ return { success: false, error: 'No project ID found' }
356
+ }
357
+
358
+ // Determine if simple or complex idea
359
+ const wordCount = description.split(/\s+/).length
360
+ const isComplex = wordCount > 20 || description.includes('with') || description.includes('that')
361
+
362
+ if (isComplex) {
363
+ // Complex idea → Create architecture session
364
+ out.spin('analyzing idea...')
365
+
366
+ const globalPath = pathManager.getGlobalProjectPath(projectId)
367
+ const sessionPath = path.join(globalPath, 'planning', 'architect-session.md')
368
+ const sessionContent = `# Architect Session
369
+
370
+ ## Idea
371
+ ${description}
372
+
373
+ ## Status
374
+ Initialized - awaiting architecture design
375
+
376
+ ## Next Steps
377
+ 1. Define tech stack
378
+ 2. Create system design
379
+ 3. Break down into features
380
+ 4. Generate roadmap
381
+
382
+ Generated: ${new Date().toLocaleString()}
383
+ `
384
+ await toolRegistry.get('Write')!(sessionPath, sessionContent)
385
+
386
+ await this.logToMemory(projectPath, 'idea_architecture_started', {
387
+ idea: description,
388
+ timestamp: dateHelper.getTimestamp(),
389
+ })
390
+
391
+ out.done('architecture session created')
392
+ console.log('\n💡 Use /p:architect execute to continue planning\n')
393
+
394
+ return { success: true, mode: 'architecture', idea: description }
395
+ } else {
396
+ // Simple idea → Quick capture (JSON → MD → Event)
397
+ out.spin('capturing idea...')
398
+
399
+ await ideasStorage.addIdea(projectId, description)
400
+
401
+ await this.logToMemory(projectPath, 'idea_captured', {
402
+ idea: description,
403
+ timestamp: dateHelper.getTimestamp(),
404
+ })
405
+
406
+ out.done(`idea captured: ${description.slice(0, 40)}`)
407
+
408
+ return { success: true, mode: 'capture', idea: description }
409
+ }
410
+ } catch (error) {
411
+ out.fail((error as Error).message)
412
+ return { success: false, error: (error as Error).message }
413
+ }
414
+ }
415
+
416
+ /**
417
+ * /p:spec - Create detailed specifications for complex features
418
+ */
419
+ async spec(featureName: string | null = null, projectPath: string = process.cwd()): Promise<CommandResult> {
420
+ try {
421
+ const initResult = await this.ensureProjectInit(projectPath)
422
+ if (!initResult.success) return initResult
423
+
424
+ const projectId = await configManager.getProjectId(projectPath)
425
+ if (!projectId) {
426
+ out.fail('no project ID')
427
+ return { success: false, error: 'No project ID found' }
428
+ }
429
+
430
+ if (!featureName) {
431
+ // List existing specs
432
+ out.spin('loading specs...')
433
+
434
+ const globalPath = pathManager.getGlobalProjectPath(projectId)
435
+ const specsPath = path.join(globalPath, 'planning', 'specs')
436
+
437
+ try {
438
+ const fs = await import('fs/promises')
439
+ const files = await fs.readdir(specsPath)
440
+ const specs = files.filter(f => f.endsWith('.md') && f !== '.gitkeep')
441
+
442
+ if (specs.length === 0) {
443
+ out.warn('no specs yet')
444
+ console.log('\n💡 Create one with /p:spec "feature name"\n')
445
+ return { success: true, specs: [] }
446
+ }
447
+
448
+ console.log('\n📋 SPECIFICATIONS\n')
449
+ console.log('═'.repeat(50))
450
+ specs.forEach((s, i) => {
451
+ const name = s.replace('.md', '').replace(/-/g, ' ')
452
+ console.log(` ${i + 1}. ${name}`)
453
+ })
454
+ console.log('═'.repeat(50) + '\n')
455
+
456
+ return { success: true, specs }
457
+ } catch {
458
+ out.warn('no specs directory')
459
+ return { success: true, specs: [] }
460
+ }
461
+ }
462
+
463
+ // Create new spec
464
+ out.spin('creating spec...')
465
+
466
+ const globalPath = pathManager.getGlobalProjectPath(projectId)
467
+ const specsPath = path.join(globalPath, 'planning', 'specs')
468
+ await fileHelper.ensureDir(specsPath)
469
+
470
+ const slug = featureName.toLowerCase().replace(/\s+/g, '-')
471
+ const specFile = path.join(specsPath, `${slug}.md`)
472
+
473
+ const specContent = `# Specification: ${featureName}
474
+
475
+ ## Overview
476
+ [Brief description of the feature]
477
+
478
+ ## Requirements
479
+ - [ ] Requirement 1
480
+ - [ ] Requirement 2
481
+ - [ ] Requirement 3
482
+
483
+ ## Design Decisions
484
+ | Decision | Rationale |
485
+ |----------|-----------|
486
+ | | |
487
+
488
+ ## Tasks (20-30 min each)
489
+ 1. [ ] Task 1 - [description]
490
+ 2. [ ] Task 2 - [description]
491
+ 3. [ ] Task 3 - [description]
492
+
493
+ ## Acceptance Criteria
494
+ - [ ] Criterion 1
495
+ - [ ] Criterion 2
496
+
497
+ ## Notes
498
+ [Additional notes and considerations]
499
+
500
+ ---
501
+ Created: ${new Date().toLocaleString()}
502
+ Status: Draft
503
+ `
504
+
505
+ await toolRegistry.get('Write')!(specFile, specContent)
506
+
507
+ await this.logToMemory(projectPath, 'spec_created', {
508
+ feature: featureName,
509
+ timestamp: dateHelper.getTimestamp(),
510
+ })
511
+
512
+ out.done(`spec created: ${slug}.md`)
513
+ console.log(`\n📝 Edit: ~/.prjct-cli/projects/${projectId}/planning/specs/${slug}.md`)
514
+ console.log('💡 When ready, use /p:feature to add tasks to queue\n')
515
+
516
+ return { success: true, feature: featureName, specPath: specFile }
517
+ } catch (error) {
518
+ out.fail((error as Error).message)
519
+ return { success: false, error: (error as Error).message }
520
+ }
521
+ }
311
522
  }
@@ -4,9 +4,12 @@
4
4
 
5
5
  import path from 'path'
6
6
  import fs from 'fs'
7
+ import { promises as fsPromises } from 'fs'
7
8
  import os from 'os'
9
+ import chalk from 'chalk'
8
10
 
9
11
  import migrator from '../infrastructure/migrator'
12
+ import commandInstaller from '../infrastructure/command-installer'
10
13
  import type { CommandResult, SetupOptions, MigrateOptions, GlobalConfig, MigrationResult } from './types'
11
14
  import {
12
15
  PrjctCommandsBase,
@@ -20,8 +23,6 @@ export class SetupCommands extends PrjctCommandsBase {
20
23
  * First-time setup - Install commands to editors
21
24
  */
22
25
  async start(): Promise<CommandResult> {
23
- const commandInstaller = require('../infrastructure/command-installer')
24
-
25
26
  console.log('🚀 Setting up prjct for Claude...\n')
26
27
 
27
28
  const status = await commandInstaller.checkInstallation()
@@ -69,8 +70,6 @@ export class SetupCommands extends PrjctCommandsBase {
69
70
  * Reconfigure editor installations
70
71
  */
71
72
  async setup(options: SetupOptions = {}): Promise<CommandResult> {
72
- const commandInstaller = require('../infrastructure/command-installer')
73
-
74
73
  console.log('🔧 Reconfiguring prjct...\n')
75
74
 
76
75
  if (options.force) {
@@ -188,8 +187,6 @@ fi
188
187
  * Show beautiful ASCII art with quick start
189
188
  */
190
189
  showAsciiArt(): void {
191
- const chalk = require('chalk')
192
-
193
190
  console.log(chalk.cyan('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'))
194
191
  console.log('')
195
192
  console.log(chalk.bold.cyan(' ██████╗ ██████╗ ██╗ ██████╗████████╗'))
@@ -232,8 +229,6 @@ fi
232
229
  * Migrate all legacy projects
233
230
  */
234
231
  async migrateAll(options: MigrateOptions = {}): Promise<CommandResult> {
235
- const fsPromises = require('fs').promises
236
-
237
232
  console.log('🔄 Scanning for legacy prjct projects...\n')
238
233
 
239
234
  const homeDir = os.homedir()
@@ -1,20 +1,21 @@
1
1
  /**
2
2
  * Shipping Commands: ship and related helpers
3
+ * Write-Through Architecture: JSON → MD → Event
3
4
  */
4
5
 
5
6
  import path from 'path'
6
7
 
7
8
  import memorySystem from '../agentic/memory-system'
8
- import type { CommandResult, Context } from './types'
9
+ import type { CommandResult } from './types'
9
10
  import {
10
11
  PrjctCommandsBase,
11
- contextBuilder,
12
12
  toolRegistry,
13
13
  configManager,
14
14
  fileHelper,
15
15
  dateHelper,
16
16
  out
17
17
  } from './base'
18
+ import { stateStorage, shippedStorage } from '../storage'
18
19
 
19
20
  export class ShippingCommands extends PrjctCommandsBase {
20
21
  /**
@@ -25,16 +26,14 @@ export class ShippingCommands extends PrjctCommandsBase {
25
26
  const initResult = await this.ensureProjectInit(projectPath)
26
27
  if (!initResult.success) return initResult
27
28
 
29
+ const config = await configManager.readConfig(projectPath)
30
+ const projectId = config!.projectId
31
+
28
32
  let featureName = feature
29
33
  if (!featureName) {
30
- const context = await contextBuilder.build(projectPath) as Context
31
- const nowContent = await toolRegistry.get('Read')!(context.paths.now) as string
32
- if (nowContent && nowContent.includes('**')) {
33
- const match = nowContent.match(/\*\*(.+?)\*\*/)
34
- featureName = match ? match[1] : 'current work'
35
- } else {
36
- featureName = 'current work'
37
- }
34
+ // Read from storage (JSON is source of truth)
35
+ const currentTask = await stateStorage.getCurrentTask(projectId)
36
+ featureName = currentTask?.description || 'current work'
38
37
  }
39
38
 
40
39
  out.spin(`shipping ${featureName}...`)
@@ -56,11 +55,11 @@ export class ShippingCommands extends PrjctCommandsBase {
56
55
  await this._gitPush(projectPath)
57
56
  }
58
57
 
59
- const context = await contextBuilder.build(projectPath) as Context
60
- const shippedContent =
61
- (await toolRegistry.get('Read')!(context.paths.shipped) as string) || '# SHIPPED 🚀\n\n'
62
- const shippedEntry = `\n## ${featureName}\n\nShipped: ${new Date().toLocaleString()}\nVersion: ${newVersion}\n`
63
- await toolRegistry.get('Write')!(context.paths.shipped, shippedContent + shippedEntry)
58
+ // Write-through: Record shipped feature (JSON MD → Event)
59
+ await shippedStorage.addShipped(projectId, {
60
+ name: featureName,
61
+ version: newVersion
62
+ })
64
63
 
65
64
  await this.logToMemory(projectPath, 'feature_shipped', {
66
65
  feature: featureName,
@@ -68,9 +67,6 @@ export class ShippingCommands extends PrjctCommandsBase {
68
67
  timestamp: dateHelper.getTimestamp(),
69
68
  })
70
69
 
71
- const config = await configManager.readConfig(projectPath)
72
- const projectId = config!.projectId
73
-
74
70
  await memorySystem.learnDecision(projectId, 'commit_footer', 'prjct', 'ship')
75
71
 
76
72
  if (testResult.success) {
@@ -28,6 +28,8 @@ export interface CommandResult {
28
28
  feature?: string
29
29
  /** Files that were modified */
30
30
  filesModified?: string[]
31
+ /** Allow any additional properties for command-specific data */
32
+ [key: string]: unknown
31
33
  }
32
34
 
33
35
  // Agent types
@@ -68,8 +70,8 @@ export interface HealthResult {
68
70
  * Options for the design command.
69
71
  */
70
72
  export interface DesignOptions {
71
- /** Type of design (e.g., 'system', 'component', 'api') */
72
- type?: 'system' | 'component' | 'api' | 'database'
73
+ /** Type of design */
74
+ type?: 'architecture' | 'api' | 'component' | 'database' | 'flow'
73
75
  /** Output format */
74
76
  format?: 'markdown' | 'mermaid'
75
77
  }
@@ -151,11 +153,11 @@ export interface MigrationConfig {
151
153
 
152
154
  // Type-safe command method names (for dynamic invocation)
153
155
  export type CommandMethodName =
154
- | 'now' | 'done' | 'next' | 'build'
155
- | 'init' | 'feature' | 'bug' | 'architect'
156
+ | 'work' | 'now' | 'done' | 'next' | 'pause' | 'resume'
157
+ | 'init' | 'feature' | 'bug' | 'idea' | 'spec'
156
158
  | 'ship'
157
- | 'context' | 'recap' | 'stuck' | 'progress' | 'roadmap' | 'status'
158
- | 'cleanup' | 'design'
159
+ | 'dash' | 'help'
160
+ | 'cleanup' | 'design' | 'recover' | 'undo' | 'redo' | 'history'
159
161
  | 'analyze' | 'sync'
160
162
  | 'start' | 'setup' | 'migrateAll'
161
163