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
@@ -7,6 +7,7 @@
7
7
 
8
8
  import { MdBaseManager } from './md-base-manager'
9
9
  import { parseState, serializeState } from '../serializers'
10
+ import { generateUUID } from '../schemas'
10
11
  import type { StateJson, CurrentTask, PreviousTask } from '../schemas/state'
11
12
 
12
13
  class MdStateManager extends MdBaseManager<StateJson> {
@@ -71,7 +72,7 @@ class MdStateManager extends MdBaseManager<StateJson> {
71
72
  }))
72
73
  }
73
74
 
74
- async pauseTask(projectId: string): Promise<StateJson> {
75
+ async pauseTask(projectId: string, reason?: string): Promise<StateJson> {
75
76
  const state = await this.read(projectId)
76
77
  if (!state.currentTask) {
77
78
  throw new Error('No active task to pause')
@@ -82,7 +83,8 @@ class MdStateManager extends MdBaseManager<StateJson> {
82
83
  description: state.currentTask.description,
83
84
  status: 'paused',
84
85
  startedAt: state.currentTask.startedAt,
85
- pausedAt: new Date().toISOString()
86
+ pausedAt: new Date().toISOString(),
87
+ pauseReason: reason
86
88
  }
87
89
 
88
90
  return this.update(projectId, () => ({
@@ -92,24 +94,26 @@ class MdStateManager extends MdBaseManager<StateJson> {
92
94
  }))
93
95
  }
94
96
 
95
- async resumeTask(projectId: string): Promise<StateJson> {
97
+ async resumeTask(projectId: string, _taskId?: string): Promise<CurrentTask | null> {
96
98
  const state = await this.read(projectId)
97
99
  if (!state.previousTask) {
98
- throw new Error('No paused task to resume')
100
+ return null
99
101
  }
100
102
 
101
103
  const currentTask: CurrentTask = {
102
104
  id: state.previousTask.id,
103
105
  description: state.previousTask.description,
104
- startedAt: new Date().toISOString(), // Reset start time
105
- sessionId: `sess_${Date.now()}`
106
+ startedAt: new Date().toISOString(),
107
+ sessionId: generateUUID()
106
108
  }
107
109
 
108
- return this.update(projectId, () => ({
110
+ await this.update(projectId, () => ({
109
111
  currentTask,
110
112
  previousTask: null,
111
113
  lastUpdated: new Date().toISOString()
112
114
  }))
115
+
116
+ return currentTask
113
117
  }
114
118
 
115
119
  async clearTask(projectId: string): Promise<StateJson> {
@@ -18,21 +18,8 @@ interface AgentConfig {
18
18
  contextFilter?: string
19
19
  }
20
20
 
21
- interface TechSummary {
22
- languages: string[]
23
- frameworks: string[]
24
- buildTools: string[]
25
- testFrameworks: string[]
26
- databases: string[]
27
- tools: string[]
28
- allDependencies: string[]
29
- }
30
-
31
- interface GenerateContext {
32
- detectedTech: TechSummary
33
- analysisSummary?: string
34
- projectPath?: string
35
- }
21
+ // TechSummary and GenerateContext interfaces REMOVED.
22
+ // Agent generation is AGENTIC - Claude reads raw data and decides.
36
23
 
37
24
  interface Agent {
38
25
  name: string
@@ -51,8 +38,9 @@ class AgentGenerator {
51
38
 
52
39
  constructor(projectId: string | null = null) {
53
40
  this.projectId = projectId
41
+ // NEW: Write to data/agents/ for JSON storage (OpenCode-style)
54
42
  this.outputDir = projectId
55
- ? path.join(os.homedir(), '.prjct-cli', 'projects', projectId, 'agents')
43
+ ? path.join(os.homedir(), '.prjct-cli', 'projects', projectId, 'data', 'agents')
56
44
  : path.join(os.homedir(), '.prjct-cli', 'agents')
57
45
  this.loader = new AgentLoader(projectId)
58
46
  }
@@ -60,61 +48,33 @@ class AgentGenerator {
60
48
  /**
61
49
  * Generate specialized agent with deep expertise
62
50
  * Universal - works with ANY technology stack
51
+ * Writes JSON to data/agents/{name}.json
63
52
  */
64
53
  async generateDynamicAgent(agentName: string, config: AgentConfig): Promise<{ name: string }> {
65
54
  log.debug(`Generating ${agentName} agent...`)
66
55
  await fs.mkdir(this.outputDir, { recursive: true })
67
56
 
68
- // Generate concise, actionable agent prompt
69
- const content = this.buildAgentPrompt(agentName, config)
57
+ // Write as JSON (OpenCode-style storage)
58
+ const agent = {
59
+ name: agentName,
60
+ role: config.role || agentName,
61
+ domain: config.domain || 'general',
62
+ expertise: config.expertise || '',
63
+ contextFilter: config.contextFilter || 'Only relevant files',
64
+ createdAt: new Date().toISOString()
65
+ }
70
66
 
71
- const outputPath = path.join(this.outputDir, `${agentName}.md`)
72
- await fs.writeFile(outputPath, content, 'utf-8')
67
+ const outputPath = path.join(this.outputDir, `${agentName}.json`)
68
+ await fs.writeFile(outputPath, JSON.stringify(agent, null, 2), 'utf-8')
73
69
  log.debug(`${agentName} agent created`)
74
70
 
75
71
  return { name: agentName }
76
72
  }
77
73
 
78
- /**
79
- * Generate agents from tech analysis - 100% AGENTIC
80
- * Claude decides what agents are needed based on actual project tech
81
- * NO HARDCODED LISTS - reads analysis and decides
82
- */
83
- async generateAgentsFromTech(context: GenerateContext): Promise<Array<{ name: string }>> {
84
- const { detectedTech } = context
85
- const agents: string[] = []
86
-
87
- // Read agent generation template - Claude will use this to decide
88
- const templatePath = path.join(__dirname, '../../templates/agents/AGENTS.md')
89
- try {
90
- await fs.readFile(templatePath, 'utf-8')
91
- } catch {
92
- // Fallback if template doesn't exist
93
- }
94
-
95
- // Build context for Claude to decide
96
- const techSummary: TechSummary = {
97
- languages: detectedTech.languages || [],
98
- frameworks: detectedTech.frameworks || [],
99
- buildTools: detectedTech.buildTools || [],
100
- testFrameworks: detectedTech.testFrameworks || [],
101
- databases: detectedTech.databases || [],
102
- tools: detectedTech.tools || [],
103
- allDependencies: detectedTech.allDependencies || [],
104
- }
105
-
106
- // Generate agents for detected domains
107
- if (techSummary.languages.length > 0 || techSummary.frameworks.length > 0) {
108
- await this.generateDynamicAgent('developer', {
109
- role: 'Development Specialist',
110
- domain: 'general',
111
- projectContext: techSummary,
112
- })
113
- agents.push('developer')
114
- }
115
-
116
- return agents.map((name) => ({ name }))
117
- }
74
+ // NOTE: generateAgentsFromTech() was REMOVED intentionally.
75
+ // Agent generation is AGENTIC - Claude reads analysis/repo-summary.md
76
+ // and DECIDES what specialists to create by calling generateDynamicAgent().
77
+ // DO NOT add any automatic agent generation logic here.
118
78
 
119
79
  /**
120
80
  * Build comprehensive agent prompt
@@ -175,10 +135,10 @@ ${config.contextFilter || 'Only relevant files'}
175
135
 
176
136
  try {
177
137
  const files = await fs.readdir(this.outputDir)
178
- const agentFiles = files.filter((f) => f.endsWith('.md') && !f.startsWith('.'))
138
+ const agentFiles = files.filter((f) => f.endsWith('.json') && !f.startsWith('.'))
179
139
 
180
140
  for (const file of agentFiles) {
181
- const type = file.replace('.md', '')
141
+ const type = file.replace('.json', '')
182
142
 
183
143
  if (!requiredAgents.includes(type)) {
184
144
  const filePath = path.join(this.outputDir, file)
@@ -200,7 +160,7 @@ ${config.contextFilter || 'Only relevant files'}
200
160
  async listAgents(): Promise<string[]> {
201
161
  try {
202
162
  const files = await fs.readdir(this.outputDir)
203
- return files.filter((f) => f.endsWith('.md') && !f.startsWith('.')).map((f) => f.replace('.md', ''))
163
+ return files.filter((f) => f.endsWith('.json') && !f.startsWith('.')).map((f) => f.replace('.json', ''))
204
164
  } catch {
205
165
  return []
206
166
  }
@@ -0,0 +1,143 @@
1
+ /**
2
+ * Event Bus for Sync
3
+ *
4
+ * Events are published on every Storage write/delete.
5
+ * Events accumulate in sync/pending.json until /p:ship or /p:sync.
6
+ */
7
+
8
+ import fs from 'fs/promises'
9
+ import path from 'path'
10
+ import os from 'os'
11
+
12
+ export interface SyncEvent {
13
+ type: string
14
+ path: string[]
15
+ data: unknown
16
+ timestamp: string
17
+ projectId: string
18
+ }
19
+
20
+ export type EventType =
21
+ | 'task.created'
22
+ | 'task.updated'
23
+ | 'task.completed'
24
+ | 'task.deleted'
25
+ | 'feature.created'
26
+ | 'feature.updated'
27
+ | 'feature.shipped'
28
+ | 'feature.deleted'
29
+ | 'idea.created'
30
+ | 'idea.updated'
31
+ | 'idea.deleted'
32
+ | 'session.started'
33
+ | 'session.completed'
34
+ | 'shipped.created'
35
+ | 'agent.created'
36
+ | 'agent.updated'
37
+ | 'agent.deleted'
38
+ | 'project.updated'
39
+
40
+ /**
41
+ * Infer event type from path and action
42
+ */
43
+ export function inferEventType(pathArray: string[], action: 'write' | 'delete'): EventType {
44
+ const entity = pathArray[0]
45
+
46
+ if (action === 'delete') {
47
+ return `${entity}.deleted` as EventType
48
+ }
49
+
50
+ // For writes, we'd need to check if it's new or update
51
+ // For simplicity, use 'updated' (can be refined later)
52
+ return `${entity}.updated` as EventType
53
+ }
54
+
55
+ class EventBus {
56
+ private pendingPath(projectId: string): string {
57
+ return path.join(os.homedir(), '.prjct-cli/projects', projectId, 'sync/pending.json')
58
+ }
59
+
60
+ /**
61
+ * Publish event to pending queue
62
+ */
63
+ async publish(event: SyncEvent): Promise<void> {
64
+ const filePath = this.pendingPath(event.projectId)
65
+
66
+ // Read existing pending events
67
+ let pending: SyncEvent[] = []
68
+ try {
69
+ const content = await fs.readFile(filePath, 'utf-8')
70
+ pending = JSON.parse(content)
71
+ } catch {
72
+ // File doesn't exist yet
73
+ }
74
+
75
+ // Add new event
76
+ pending.push(event)
77
+
78
+ // Ensure directory exists
79
+ await fs.mkdir(path.dirname(filePath), { recursive: true })
80
+
81
+ // Write back
82
+ await fs.writeFile(filePath, JSON.stringify(pending, null, 2), 'utf-8')
83
+ }
84
+
85
+ /**
86
+ * Get all pending events for a project
87
+ */
88
+ async getPending(projectId: string): Promise<SyncEvent[]> {
89
+ const filePath = this.pendingPath(projectId)
90
+
91
+ try {
92
+ const content = await fs.readFile(filePath, 'utf-8')
93
+ return JSON.parse(content)
94
+ } catch {
95
+ return []
96
+ }
97
+ }
98
+
99
+ /**
100
+ * Clear pending events after successful sync
101
+ */
102
+ async clearPending(projectId: string): Promise<void> {
103
+ const filePath = this.pendingPath(projectId)
104
+
105
+ try {
106
+ await fs.writeFile(filePath, '[]', 'utf-8')
107
+ } catch {
108
+ // Ignore
109
+ }
110
+ }
111
+
112
+ /**
113
+ * Update last sync timestamp
114
+ */
115
+ async updateLastSync(projectId: string): Promise<void> {
116
+ const filePath = path.join(os.homedir(), '.prjct-cli/projects', projectId, 'sync/last-sync.json')
117
+
118
+ const data = {
119
+ timestamp: new Date().toISOString(),
120
+ success: true
121
+ }
122
+
123
+ await fs.mkdir(path.dirname(filePath), { recursive: true })
124
+ await fs.writeFile(filePath, JSON.stringify(data, null, 2), 'utf-8')
125
+ }
126
+
127
+ /**
128
+ * Get last sync info
129
+ */
130
+ async getLastSync(projectId: string): Promise<{ timestamp: string; success: boolean } | null> {
131
+ const filePath = path.join(os.homedir(), '.prjct-cli/projects', projectId, 'sync/last-sync.json')
132
+
133
+ try {
134
+ const content = await fs.readFile(filePath, 'utf-8')
135
+ return JSON.parse(content)
136
+ } catch {
137
+ return null
138
+ }
139
+ }
140
+ }
141
+
142
+ export const eventBus = new EventBus()
143
+ export default eventBus
package/core/index.ts CHANGED
@@ -39,7 +39,7 @@ async function main(): Promise<void> {
39
39
  process.exit(0)
40
40
  }
41
41
 
42
- if (['-h', '--help', 'help', undefined].includes(commandName)) {
42
+ if (['-h', '--help', undefined].includes(commandName)) {
43
43
  displayHelp()
44
44
  process.exit(0)
45
45
  }
@@ -100,29 +100,32 @@ async function main(): Promise<void> {
100
100
  result = await commands.setup(options)
101
101
  } else if (commandName === 'migrate-all') {
102
102
  result = await commands.migrateAll(options)
103
- } else if (commandName === 'progress') {
104
- const period = parsedArgs[0] || 'week'
105
- result = await commands.progress(period)
106
- } else if (commandName === 'build') {
107
- const taskOrNumber = parsedArgs.join(' ')
108
- result = await commands.build(taskOrNumber)
109
103
  } else {
110
104
  // Standard commands - type-safe invocation
111
105
  const param = parsedArgs.join(' ') || null
112
106
  const standardCommands: Record<string, (p: string | null) => Promise<CommandResult>> = {
113
- now: (p) => commands.now(p),
107
+ // Core workflow
108
+ work: (p) => commands.work(p),
114
109
  done: () => commands.done(),
115
110
  next: () => commands.next(),
111
+ pause: (p) => commands.pause(p || ''),
112
+ resume: (p) => commands.resume(p),
113
+ // Planning
116
114
  init: (p) => commands.init(p),
117
115
  feature: (p) => commands.feature(p || ''),
118
116
  bug: (p) => commands.bug(p || ''),
119
- architect: (p) => commands.architect(p || 'execute'),
117
+ idea: (p) => commands.idea(p || ''),
118
+ spec: (p) => commands.spec(p),
120
119
  ship: (p) => commands.ship(p),
121
- context: () => commands.context(),
122
- recap: () => commands.recap(),
123
- stuck: (p) => commands.stuck(p || ''),
124
- roadmap: () => commands.roadmap(),
125
- status: () => commands.status(),
120
+ // Analytics
121
+ dash: (p) => commands.dash(p || 'default'),
122
+ help: (p) => commands.help(p || ''),
123
+ // Maintenance
124
+ recover: () => commands.recover(),
125
+ undo: () => commands.undo(),
126
+ redo: () => commands.redo(),
127
+ history: () => commands.history(),
128
+ // Setup
126
129
  sync: () => commands.sync(),
127
130
  start: () => commands.start(),
128
131
  }
@@ -1,13 +1,15 @@
1
1
  /**
2
2
  * Capability Installer
3
- * Handles installation of missing tools and tracks them as workflow tasks
3
+ *
4
+ * AGENTIC: This module provides TOOLS, not DECISIONS.
5
+ * Claude reads project context and decides what to install/configure.
6
+ *
7
+ * NO hardcoded configurations for specific frameworks.
8
+ * Configuration should be template-driven, not code-driven.
4
9
  */
5
10
 
6
11
  import { exec } from 'child_process'
7
12
  import { promisify } from 'util'
8
- import fs from 'fs/promises'
9
- import path from 'path'
10
- import projectCapabilities from '../utils/project-capabilities'
11
13
 
12
14
  const execAsync = promisify(exec)
13
15
 
@@ -25,20 +27,12 @@ interface InstallResult {
25
27
  error?: string
26
28
  }
27
29
 
28
- interface ConfigureResult {
29
- configured: boolean
30
- framework?: string
31
- tool?: string
32
- }
33
-
34
- interface PackageJson {
35
- devDependencies?: Record<string, string>
36
- scripts?: Record<string, string>
37
- }
38
-
39
30
  class CapabilityInstaller {
40
31
  /**
41
- * Install capability and create tracking task
32
+ * Install capability using provided command
33
+ *
34
+ * AGENTIC: Claude provides the install command based on analysis.
35
+ * This is just the executor - no hardcoded logic.
42
36
  */
43
37
  async install(
44
38
  capability: string,
@@ -49,7 +43,6 @@ class CapabilityInstaller {
49
43
  const startTime = Date.now()
50
44
 
51
45
  try {
52
- // Execute installation command
53
46
  const { stdout, stderr } = await execAsync(command)
54
47
 
55
48
  const duration = Date.now() - startTime
@@ -73,138 +66,9 @@ class CapabilityInstaller {
73
66
  }
74
67
  }
75
68
 
76
- /**
77
- * Create configuration for installed tool
78
- */
79
- async configure(capability: string, projectPath: string): Promise<ConfigureResult> {
80
- const configs: Record<string, () => Promise<ConfigureResult>> = {
81
- test: () => this.configureTest(projectPath),
82
- design: () => this.configureDesign(projectPath),
83
- docs: () => this.configureDocs(projectPath),
84
- }
85
-
86
- if (configs[capability]) {
87
- return await configs[capability]()
88
- }
89
-
90
- return { configured: false }
91
- }
92
-
93
- /**
94
- * Configure test framework
95
- */
96
- async configureTest(projectPath: string): Promise<ConfigureResult> {
97
- // Check if package.json exists
98
- const pkgPath = path.join(projectPath, 'package.json')
99
- const pkg: PackageJson = JSON.parse(await fs.readFile(pkgPath, 'utf8'))
100
-
101
- const hasVitest = pkg.devDependencies?.vitest
102
- const hasJest = pkg.devDependencies?.jest
103
-
104
- if (hasVitest) {
105
- // Create vitest.config.js
106
- const config = `import { defineConfig } from 'vitest/config'
107
-
108
- export default defineConfig({
109
- test: {
110
- globals: true,
111
- environment: 'jsdom',
112
- setupFiles: './test/setup.js'
113
- }
114
- })
115
- `
116
- await fs.writeFile(path.join(projectPath, 'vitest.config.js'), config)
117
-
118
- // Add test script
119
- pkg.scripts = pkg.scripts || {}
120
- pkg.scripts.test = 'vitest'
121
- pkg.scripts['test:ui'] = 'vitest --ui'
122
- await fs.writeFile(pkgPath, JSON.stringify(pkg, null, 2))
123
-
124
- return { configured: true, framework: 'vitest' }
125
- }
126
-
127
- if (hasJest) {
128
- // Create jest.config.js
129
- const config = `module.exports = {
130
- testEnvironment: 'jsdom',
131
- setupFilesAfterEnv: ['<rootDir>/test/setup.js'],
132
- moduleNameMapper: {
133
- '\\\\.(css|less|scss|sass)$': 'identity-obj-proxy'
134
- }
135
- }
136
- `
137
- await fs.writeFile(path.join(projectPath, 'jest.config.js'), config)
138
-
139
- // Add test script
140
- pkg.scripts = pkg.scripts || {}
141
- pkg.scripts.test = 'jest'
142
- pkg.scripts['test:watch'] = 'jest --watch'
143
- await fs.writeFile(pkgPath, JSON.stringify(pkg, null, 2))
144
-
145
- return { configured: true, framework: 'jest' }
146
- }
147
-
148
- return { configured: false }
149
- }
150
-
151
- /**
152
- * Configure design system
153
- */
154
- async configureDesign(projectPath: string): Promise<ConfigureResult> {
155
- const pkgPath = path.join(projectPath, 'package.json')
156
- const pkg: PackageJson = JSON.parse(await fs.readFile(pkgPath, 'utf8'))
157
-
158
- if (pkg.devDependencies?.storybook) {
159
- // Storybook auto-configures itself during init
160
- return { configured: true, tool: 'storybook' }
161
- }
162
-
163
- return { configured: false }
164
- }
165
-
166
- /**
167
- * Configure documentation
168
- */
169
- async configureDocs(projectPath: string): Promise<ConfigureResult> {
170
- const pkgPath = path.join(projectPath, 'package.json')
171
- const pkg: PackageJson = JSON.parse(await fs.readFile(pkgPath, 'utf8'))
172
-
173
- if (pkg.devDependencies?.jsdoc) {
174
- // Create jsdoc.json
175
- const config = {
176
- source: {
177
- include: ['src'],
178
- includePattern: '.+\\.js(doc|x)?$',
179
- excludePattern: '(node_modules|docs)',
180
- },
181
- opts: {
182
- destination: './docs',
183
- recurse: true,
184
- },
185
- }
186
-
187
- await fs.writeFile(path.join(projectPath, 'jsdoc.json'), JSON.stringify(config, null, 2))
188
-
189
- // Add docs script
190
- pkg.scripts = pkg.scripts || {}
191
- pkg.scripts.docs = 'jsdoc -c jsdoc.json'
192
- await fs.writeFile(pkgPath, JSON.stringify(pkg, null, 2))
193
-
194
- return { configured: true, tool: 'jsdoc' }
195
- }
196
-
197
- return { configured: false }
198
- }
199
-
200
- /**
201
- * Verify installation succeeded
202
- */
203
- async verify(capability: string, projectPath: string): Promise<boolean> {
204
- const detected = await projectCapabilities.detect(projectPath)
205
-
206
- return detected[capability as keyof typeof detected] === true
207
- }
69
+ // NOTE: configure() and hardcoded framework configs REMOVED.
70
+ // Configuration is AGENTIC - Claude reads templates and generates configs.
71
+ // See templates/workflows/ for configuration guidance.
208
72
  }
209
73
 
210
74
  const capabilityInstaller = new CapabilityInstaller()
@@ -4,6 +4,7 @@
4
4
  */
5
5
 
6
6
  import fs from 'fs/promises'
7
+ import { accessSync } from 'fs'
7
8
  import path from 'path'
8
9
  import os from 'os'
9
10
  import type { FindProjectsOptions } from './types'
@@ -34,7 +35,7 @@ export async function findAllProjects(options: FindProjectsOptions = {}): Promis
34
35
  .map((dir) => path.join(os.homedir(), dir))
35
36
  .filter((dirPath) => {
36
37
  try {
37
- require('fs').accessSync(dirPath)
38
+ accessSync(dirPath)
38
39
  return true
39
40
  } catch {
40
41
  return false
@@ -36,13 +36,11 @@ class PathManager {
36
36
  }
37
37
 
38
38
  /**
39
- * Generate a unique project ID from the absolute project path
40
- * Uses SHA-256 hash of the absolute path for consistency
39
+ * Generate a unique project ID using UUID.
40
+ * Standard UUID format for PostgreSQL consistency.
41
41
  */
42
- generateProjectId(projectPath: string): string {
43
- const absolutePath = path.resolve(projectPath)
44
- const hash = crypto.createHash('sha256').update(absolutePath).digest('hex')
45
- return hash.substring(0, 12) // Use first 12 chars for readability
42
+ generateProjectId(_projectPath: string): string {
43
+ return crypto.randomUUID()
46
44
  }
47
45
 
48
46
  /**
@@ -145,6 +145,9 @@ export async function run(): Promise<SetupResults> {
145
145
  return results
146
146
  }
147
147
 
148
+ // Default export for CommonJS require
149
+ export default { run }
150
+
148
151
  /**
149
152
  * Show setup results
150
153
  */