prjct-cli 0.20.0 → 0.21.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 (236) hide show
  1. package/CHANGELOG.md +24 -6
  2. package/CLAUDE.md +56 -15
  3. package/README.md +5 -6
  4. package/bin/prjct +59 -42
  5. package/bin/prjct.ts +60 -0
  6. package/core/__tests__/agentic/memory-system.test.ts +18 -3
  7. package/core/__tests__/agentic/plan-mode.test.ts +55 -26
  8. package/core/__tests__/agentic/prompt-builder.test.ts +6 -6
  9. package/core/__tests__/utils/project-commands.test.ts +72 -0
  10. package/core/agentic/agent-router.ts +3 -12
  11. package/core/agentic/command-executor.ts +372 -3
  12. package/core/agentic/context-builder.ts +7 -27
  13. package/core/agentic/ground-truth.ts +604 -5
  14. package/core/agentic/index.ts +180 -0
  15. package/core/agentic/loop-detector.ts +418 -4
  16. package/core/agentic/memory-system.ts +857 -3
  17. package/core/agentic/plan-mode.ts +491 -4
  18. package/core/agentic/prompt-builder.ts +44 -65
  19. package/core/agentic/services.ts +13 -5
  20. package/core/agentic/skill-loader.ts +112 -0
  21. package/core/agentic/smart-context.ts +37 -122
  22. package/core/agentic/template-loader.ts +79 -122
  23. package/core/agentic/tool-registry.ts +5 -11
  24. package/core/agents/index.ts +1 -1
  25. package/core/agents/performance.ts +4 -2
  26. package/core/bus/bus.ts +262 -0
  27. package/core/bus/index.ts +3 -313
  28. package/core/commands/analysis.ts +5 -5
  29. package/core/commands/analytics.ts +11 -11
  30. package/core/commands/base.ts +33 -209
  31. package/core/commands/cleanup.ts +148 -0
  32. package/core/commands/command-data.ts +346 -0
  33. package/core/commands/commands.ts +216 -0
  34. package/core/commands/design.ts +83 -0
  35. package/core/commands/index.ts +13 -207
  36. package/core/commands/maintenance.ts +52 -473
  37. package/core/commands/planning.ts +3 -3
  38. package/core/commands/register.ts +104 -0
  39. package/core/commands/registry.ts +441 -0
  40. package/core/commands/setup.ts +25 -9
  41. package/core/commands/shipping.ts +48 -11
  42. package/core/commands/snapshots.ts +299 -0
  43. package/core/commands/workflow.ts +2 -2
  44. package/core/constants/index.ts +254 -4
  45. package/core/domain/agent-loader.ts +5 -6
  46. package/core/domain/task-stack.ts +555 -4
  47. package/core/errors.ts +127 -1
  48. package/core/events/events.ts +87 -0
  49. package/core/events/index.ts +4 -138
  50. package/core/index.ts +15 -23
  51. package/core/infrastructure/agent-detector.ts +126 -201
  52. package/core/infrastructure/author-detector.ts +99 -171
  53. package/core/infrastructure/command-installer.ts +476 -4
  54. package/core/infrastructure/config-manager.ts +41 -37
  55. package/core/infrastructure/path-manager.ts +59 -9
  56. package/core/infrastructure/permission-manager.ts +286 -0
  57. package/core/integrations/notion/client.ts +323 -0
  58. package/core/integrations/notion/index.ts +43 -0
  59. package/core/integrations/notion/setup.ts +230 -0
  60. package/core/integrations/notion/sync.ts +311 -0
  61. package/core/integrations/notion/templates.ts +234 -0
  62. package/core/outcomes/analyzer.ts +7 -41
  63. package/core/outcomes/index.ts +1 -1
  64. package/core/outcomes/recorder.ts +1 -1
  65. package/core/plugin/builtin/notion.ts +178 -0
  66. package/core/{plugins → plugin/builtin}/webhook.ts +6 -22
  67. package/core/plugin/loader.ts +5 -5
  68. package/core/plugin/registry.ts +2 -2
  69. package/core/schemas/ideas.ts +85 -54
  70. package/core/schemas/index.ts +14 -33
  71. package/core/schemas/permissions.ts +177 -0
  72. package/core/schemas/project.ts +39 -12
  73. package/core/schemas/roadmap.ts +94 -59
  74. package/core/schemas/schemas.ts +39 -0
  75. package/core/schemas/shipped.ts +87 -60
  76. package/core/schemas/state.ts +110 -70
  77. package/core/server/index.ts +21 -0
  78. package/core/server/routes.ts +165 -0
  79. package/core/server/server.ts +136 -0
  80. package/core/server/sse.ts +135 -0
  81. package/core/services/agent-service.ts +170 -0
  82. package/core/services/breakdown-service.ts +126 -0
  83. package/core/services/index.ts +21 -0
  84. package/core/services/memory-service.ts +108 -0
  85. package/core/services/project-service.ts +146 -0
  86. package/core/services/skill-service.ts +253 -0
  87. package/core/session/compaction.ts +257 -0
  88. package/core/session/index.ts +20 -8
  89. package/core/{infrastructure/session-manager/migration.ts → session/log-migration.ts} +9 -9
  90. package/core/{infrastructure/session-manager/session-manager.ts → session/session-log-manager.ts} +27 -26
  91. package/core/session/{session-manager.ts → task-session-manager.ts} +7 -4
  92. package/core/session/utils.ts +1 -1
  93. package/core/storage/ideas-storage.ts +10 -26
  94. package/core/storage/index.ts +14 -162
  95. package/core/storage/queue-storage.ts +13 -11
  96. package/core/storage/shipped-storage.ts +4 -17
  97. package/core/storage/state-storage.ts +35 -43
  98. package/core/storage/storage-manager.ts +42 -52
  99. package/core/storage/storage.ts +160 -0
  100. package/core/sync/auth-config.ts +1 -8
  101. package/core/sync/index.ts +17 -10
  102. package/core/sync/oauth-handler.ts +1 -6
  103. package/core/sync/sync-client.ts +6 -34
  104. package/core/sync/sync-manager.ts +11 -40
  105. package/core/types/agentic.ts +577 -0
  106. package/core/types/agents.ts +145 -0
  107. package/core/types/bus.ts +82 -0
  108. package/core/types/commands.ts +366 -0
  109. package/core/types/config.ts +70 -0
  110. package/core/types/core.ts +96 -0
  111. package/core/types/domain.ts +71 -0
  112. package/core/types/events.ts +42 -0
  113. package/core/types/fs.ts +56 -0
  114. package/core/types/index.ts +396 -500
  115. package/core/types/infrastructure.ts +196 -0
  116. package/core/types/integrations.ts +57 -0
  117. package/core/{agentic/memory-system/types.ts → types/memory.ts} +33 -8
  118. package/core/{outcomes/types.ts → types/outcomes.ts} +53 -8
  119. package/core/types/plugin.ts +25 -0
  120. package/core/types/server.ts +54 -0
  121. package/core/types/services.ts +65 -0
  122. package/core/types/session.ts +135 -0
  123. package/core/types/storage.ts +148 -0
  124. package/core/types/sync.ts +121 -0
  125. package/core/types/task.ts +72 -0
  126. package/core/types/template.ts +24 -0
  127. package/core/types/utils.ts +90 -0
  128. package/core/utils/cache.ts +195 -0
  129. package/core/utils/collection-filters.ts +245 -0
  130. package/core/utils/date-helper.ts +1 -5
  131. package/core/utils/file-helper.ts +20 -10
  132. package/core/utils/jsonl-helper.ts +5 -8
  133. package/core/utils/markdown-builder.ts +277 -0
  134. package/core/utils/project-commands.ts +132 -0
  135. package/core/utils/runtime.ts +119 -0
  136. package/dist/bin/prjct.mjs +12568 -0
  137. package/package.json +13 -8
  138. package/scripts/build.js +106 -0
  139. package/scripts/postinstall.js +50 -8
  140. package/templates/agentic/subagent-generation.md +1 -1
  141. package/templates/commands/init.md +43 -0
  142. package/templates/commands/notion-setup.md +191 -0
  143. package/templates/commands/serve.md +118 -0
  144. package/templates/commands/ship.md +13 -2
  145. package/templates/commands/skill.md +110 -0
  146. package/templates/commands/sync.md +1 -1
  147. package/templates/commands/test.md +23 -4
  148. package/templates/mcp-config.json +28 -0
  149. package/templates/permissions/default.jsonc +60 -0
  150. package/templates/permissions/permissive.jsonc +49 -0
  151. package/templates/permissions/strict.jsonc +62 -0
  152. package/templates/skills/code-review.md +47 -0
  153. package/templates/skills/debug.md +61 -0
  154. package/templates/skills/refactor.md +47 -0
  155. package/templates/subagents/domain/devops.md +1 -1
  156. package/templates/subagents/domain/testing.md +6 -10
  157. package/templates/subagents/workflow/prjct-shipper.md +16 -7
  158. package/templates/tools/bash.txt +22 -0
  159. package/templates/tools/edit.txt +18 -0
  160. package/templates/tools/glob.txt +19 -0
  161. package/templates/tools/grep.txt +21 -0
  162. package/templates/tools/read.txt +14 -0
  163. package/templates/tools/task.txt +20 -0
  164. package/templates/tools/webfetch.txt +16 -0
  165. package/templates/tools/websearch.txt +18 -0
  166. package/templates/tools/write.txt +17 -0
  167. package/core/agentic/command-executor/command-executor.ts +0 -312
  168. package/core/agentic/command-executor/index.ts +0 -16
  169. package/core/agentic/command-executor/status-signal.ts +0 -38
  170. package/core/agentic/command-executor/types.ts +0 -79
  171. package/core/agentic/ground-truth/index.ts +0 -76
  172. package/core/agentic/ground-truth/types.ts +0 -33
  173. package/core/agentic/ground-truth/utils.ts +0 -48
  174. package/core/agentic/ground-truth/verifiers/analyze.ts +0 -54
  175. package/core/agentic/ground-truth/verifiers/done.ts +0 -75
  176. package/core/agentic/ground-truth/verifiers/feature.ts +0 -70
  177. package/core/agentic/ground-truth/verifiers/index.ts +0 -37
  178. package/core/agentic/ground-truth/verifiers/init.ts +0 -52
  179. package/core/agentic/ground-truth/verifiers/now.ts +0 -57
  180. package/core/agentic/ground-truth/verifiers/ship.ts +0 -85
  181. package/core/agentic/ground-truth/verifiers/spec.ts +0 -45
  182. package/core/agentic/ground-truth/verifiers/sync.ts +0 -47
  183. package/core/agentic/ground-truth/verifiers.ts +0 -6
  184. package/core/agentic/loop-detector/error-analysis.ts +0 -97
  185. package/core/agentic/loop-detector/hallucination.ts +0 -71
  186. package/core/agentic/loop-detector/index.ts +0 -41
  187. package/core/agentic/loop-detector/loop-detector.ts +0 -222
  188. package/core/agentic/loop-detector/types.ts +0 -66
  189. package/core/agentic/memory-system/history.ts +0 -53
  190. package/core/agentic/memory-system/index.ts +0 -192
  191. package/core/agentic/memory-system/patterns.ts +0 -156
  192. package/core/agentic/memory-system/semantic-memories.ts +0 -278
  193. package/core/agentic/memory-system/session.ts +0 -21
  194. package/core/agentic/plan-mode/approval.ts +0 -57
  195. package/core/agentic/plan-mode/constants.ts +0 -44
  196. package/core/agentic/plan-mode/index.ts +0 -28
  197. package/core/agentic/plan-mode/plan-mode.ts +0 -407
  198. package/core/agentic/plan-mode/types.ts +0 -193
  199. package/core/agents/types.ts +0 -126
  200. package/core/command-registry/categories.ts +0 -23
  201. package/core/command-registry/commands.ts +0 -15
  202. package/core/command-registry/core-commands.ts +0 -344
  203. package/core/command-registry/index.ts +0 -158
  204. package/core/command-registry/optional-commands.ts +0 -163
  205. package/core/command-registry/setup-commands.ts +0 -83
  206. package/core/command-registry/types.ts +0 -59
  207. package/core/command-registry.ts +0 -9
  208. package/core/commands/types.ts +0 -185
  209. package/core/commands.ts +0 -11
  210. package/core/constants/formats.ts +0 -187
  211. package/core/context-sync.ts +0 -18
  212. package/core/data/index.ts +0 -27
  213. package/core/data/md-base-manager.ts +0 -203
  214. package/core/data/md-ideas-manager.ts +0 -155
  215. package/core/data/md-queue-manager.ts +0 -180
  216. package/core/data/md-shipped-manager.ts +0 -90
  217. package/core/data/md-state-manager.ts +0 -137
  218. package/core/domain/task-stack/index.ts +0 -19
  219. package/core/domain/task-stack/parser.ts +0 -86
  220. package/core/domain/task-stack/storage.ts +0 -123
  221. package/core/domain/task-stack/task-stack.ts +0 -340
  222. package/core/domain/task-stack/types.ts +0 -51
  223. package/core/infrastructure/command-installer/command-installer.ts +0 -327
  224. package/core/infrastructure/command-installer/global-config.ts +0 -136
  225. package/core/infrastructure/command-installer/index.ts +0 -25
  226. package/core/infrastructure/command-installer/types.ts +0 -41
  227. package/core/infrastructure/session-manager/index.ts +0 -23
  228. package/core/infrastructure/session-manager/types.ts +0 -45
  229. package/core/infrastructure/session-manager.ts +0 -8
  230. package/core/serializers/ideas-serializer.ts +0 -187
  231. package/core/serializers/index.ts +0 -36
  232. package/core/serializers/queue-serializer.ts +0 -210
  233. package/core/serializers/shipped-serializer.ts +0 -108
  234. package/core/serializers/state-serializer.ts +0 -136
  235. package/core/session/types.ts +0 -29
  236. /package/core/infrastructure/{agents/claude-agent.ts → claude-agent.ts} +0 -0
@@ -1,281 +1,105 @@
1
1
  /**
2
2
  * Base class and helpers for PrjctCommands
3
+ *
4
+ * Delegates to service modules for business logic.
5
+ * This class maintains backward compatibility while services handle implementation.
3
6
  */
4
7
 
5
8
  import commandExecutor from '../agentic/command-executor'
6
9
  import contextBuilder from '../agentic/context-builder'
7
10
  import toolRegistry from '../agentic/tool-registry'
8
- import AgentRouter from '../agentic/agent-router'
9
11
  import pathManager from '../infrastructure/path-manager'
10
12
  import configManager from '../infrastructure/config-manager'
11
- import authorDetector from '../infrastructure/author-detector'
12
- import agentDetector from '../infrastructure/agent-detector'
13
13
  import UpdateChecker from '../infrastructure/update-checker'
14
14
  import dateHelper from '../utils/date-helper'
15
15
  import jsonlHelper from '../utils/jsonl-helper'
16
16
  import * as fileHelper from '../utils/file-helper'
17
17
  import out from '../utils/output'
18
18
 
19
+ // Services
20
+ import { agentService, projectService, memoryService, breakdownService } from '../services'
21
+
19
22
  import type {
20
23
  CommandResult,
21
24
  AgentInfo,
22
25
  Author,
23
26
  AgentAssignmentResult,
24
- Context
25
- } from './types'
26
- import { ProjectError, AgentError } from '../errors'
27
-
28
- // Valid agent types - whitelist for security (prevents path traversal)
29
- const VALID_AGENT_TYPES = ['claude'] as const
30
- type ValidAgentType = typeof VALID_AGENT_TYPES[number]
31
-
32
- // Lazy-loaded to avoid circular dependencies
33
- let _planningCommands: import('./planning').PlanningCommands | null = null
34
- async function getPlanningCommands(): Promise<import('./planning').PlanningCommands> {
35
- if (!_planningCommands) {
36
- const { PlanningCommands } = await import('./planning')
37
- _planningCommands = new PlanningCommands()
38
- }
39
- return _planningCommands
40
- }
27
+ ProjectContext
28
+ } from '../types'
41
29
 
42
30
  /**
43
31
  * Base class with shared state and utilities
32
+ * Delegates to service modules for implementation
44
33
  */
45
34
  export class PrjctCommandsBase {
46
- agent: unknown
47
- agentInfo: AgentInfo | null
48
- currentAuthor: Author | null
49
35
  prjctDir: string
50
36
  updateChecker: UpdateChecker
51
37
  updateNotificationShown: boolean
52
38
  commandExecutor: typeof commandExecutor
53
- agentRouter: AgentRouter
54
39
 
55
40
  constructor() {
56
- this.agent = null
57
- this.agentInfo = null
58
- this.currentAuthor = null
59
41
  this.prjctDir = '.prjct'
60
42
  this.updateChecker = new UpdateChecker()
61
43
  this.updateNotificationShown = false
62
44
  this.commandExecutor = commandExecutor
63
- this.agentRouter = new AgentRouter()
64
45
  }
65
46
 
66
- /**
67
- * Initialize agent (Claude Code, Desktop, or Terminal)
68
- */
69
- async initializeAgent(): Promise<unknown> {
70
- if (this.agent) return this.agent
71
-
72
- this.agentInfo = await agentDetector.detect()
73
-
74
- if (!this.agentInfo.isSupported) {
75
- throw AgentError.notSupported(this.agentInfo.type)
76
- }
47
+ // Agent accessors (delegate to agentService)
48
+ get agent(): unknown {
49
+ return agentService.getAgent()
50
+ }
77
51
 
78
- // Security: validate agent type against whitelist to prevent path traversal
79
- const agentType = this.agentInfo.type as ValidAgentType
80
- if (!VALID_AGENT_TYPES.includes(agentType)) {
81
- throw AgentError.notSupported(this.agentInfo.type)
82
- }
52
+ get agentInfo(): AgentInfo | null {
53
+ return agentService.getInfo()
54
+ }
83
55
 
84
- const { default: Agent } = await import(`../infrastructure/agents/${agentType}-agent`)
85
- this.agent = new Agent()
56
+ get currentAuthor(): Author | null {
57
+ return projectService.getCurrentAuthor()
58
+ }
86
59
 
87
- return this.agent
60
+ async initializeAgent(): Promise<unknown> {
61
+ return agentService.initialize()
88
62
  }
89
63
 
90
- /**
91
- * Ensure project is initialized
92
- */
93
64
  async ensureProjectInit(projectPath: string): Promise<CommandResult> {
94
- if (await configManager.isConfigured(projectPath)) {
95
- return { success: true }
96
- }
97
-
98
- out.spin('initializing project...')
99
- const planning = await getPlanningCommands()
100
- const initResult = await planning.init(null, projectPath)
101
- if (!initResult.success) {
102
- return initResult
103
- }
104
- return { success: true }
65
+ return projectService.ensureInit(projectPath)
105
66
  }
106
67
 
107
- /**
108
- * Ensure author information is loaded
109
- */
110
68
  async ensureAuthor(): Promise<Author> {
111
- if (this.currentAuthor) return this.currentAuthor
112
- // detectAuthorForLogs returns a string (username), detect() returns full Author
113
- const authorObj = await authorDetector.detect()
114
- this.currentAuthor = {
115
- name: authorObj.name,
116
- email: authorObj.email,
117
- github: authorObj.github
118
- }
119
- return this.currentAuthor
69
+ return projectService.ensureAuthor()
120
70
  }
121
71
 
122
- /**
123
- * Get global project path
124
- */
125
72
  async getGlobalProjectPath(projectPath: string): Promise<string> {
126
- const projectId = await configManager.getProjectId(projectPath)
127
- if (!projectId) {
128
- throw ProjectError.notInitialized()
129
- }
130
- await pathManager.ensureProjectStructure(projectId)
131
- return pathManager.getGlobalProjectPath(projectId)
73
+ return projectService.getGlobalPath(projectPath)
132
74
  }
133
75
 
134
- /**
135
- * Log to memory
136
- */
137
76
  async logToMemory(projectPath: string, action: string, data: Record<string, unknown>): Promise<void> {
138
- try {
139
- const author = await this.ensureAuthor()
140
- const projectId = await configManager.getProjectId(projectPath)
141
- const memoryPath = pathManager.getFilePath(projectId!, 'memory', 'context.jsonl')
142
-
143
- const entry = {
144
- timestamp: dateHelper.getTimestamp(),
145
- action,
146
- data,
147
- author: author.name,
148
- }
149
-
150
- await jsonlHelper.appendJsonLine(memoryPath, entry)
151
- } catch {
152
- // Non-critical - don't fail the command
153
- }
77
+ const author = await this.ensureAuthor()
78
+ return memoryService.log(projectPath, action, data, author.name)
154
79
  }
155
80
 
156
- /**
157
- * Detect if directory is empty (excluding common files)
158
- */
159
81
  async _detectEmptyDirectory(projectPath: string): Promise<boolean> {
160
- try {
161
- const entries = await fileHelper.listFiles(projectPath)
162
- const meaningfulFiles = entries.filter(
163
- (name) =>
164
- !name.startsWith('.') &&
165
- name !== 'node_modules' &&
166
- name !== 'package.json' &&
167
- name !== 'package-lock.json' &&
168
- name !== 'README.md'
169
- )
170
- return meaningfulFiles.length === 0
171
- } catch {
172
- return true
173
- }
82
+ return projectService.isEmptyDirectory(projectPath)
174
83
  }
175
84
 
176
- /**
177
- * Detect if directory has existing code
178
- */
179
85
  async _detectExistingCode(projectPath: string): Promise<boolean> {
180
- try {
181
- const codePatterns = [
182
- 'src', 'lib', 'app', 'components', 'pages', 'api',
183
- 'main.go', 'main.rs', 'main.py',
184
- ]
185
- const entries = await fileHelper.listFiles(projectPath)
186
- return entries.some((name) => codePatterns.includes(name))
187
- } catch {
188
- return false
189
- }
86
+ return projectService.hasExistingCode(projectPath)
190
87
  }
191
88
 
192
- /**
193
- * Breakdown feature into tasks
194
- */
195
89
  _breakdownFeatureTasks(description: string): string[] {
196
- return [
197
- `Analyze and plan: ${description}`,
198
- 'Implement core functionality',
199
- 'Test and validate',
200
- 'Document changes',
201
- ]
90
+ return breakdownService.breakdownFeature(description)
202
91
  }
203
92
 
204
- /**
205
- * Detect bug severity from description
206
- */
207
- _detectBugSeverity(_description: string): string {
208
- return 'medium'
93
+ _detectBugSeverity(description: string): string {
94
+ return breakdownService.detectBugSeverity(description)
209
95
  }
210
96
 
211
- /**
212
- * Assign agent for a task using AgentRouter
213
- * Returns agent info for Claude to delegate work
214
- */
215
97
  async _assignAgentForTask(
216
98
  task: string,
217
99
  projectPath: string,
218
- _context: Context
100
+ context: ProjectContext
219
101
  ): Promise<AgentAssignmentResult> {
220
- try {
221
- await this.agentRouter.initialize(projectPath)
222
- const agents = await this.agentRouter.getAgentNames()
223
-
224
- if (agents.length === 0) {
225
- return {
226
- agent: { name: 'generalist' },
227
- routing: {
228
- confidence: 1.0,
229
- reason: 'No specialized agents available',
230
- availableAgents: [],
231
- },
232
- }
233
- }
234
-
235
- // Simple keyword matching for agent assignment
236
- // Claude will make the final decision via templates
237
- const taskLower = task.toLowerCase()
238
- let bestMatch = 'generalist'
239
-
240
- for (const agentName of agents) {
241
- const nameLower = agentName.toLowerCase()
242
- if (taskLower.includes(nameLower) || nameLower.includes('general')) {
243
- bestMatch = agentName
244
- break
245
- }
246
- // Common domain keywords
247
- if ((nameLower.includes('fe') || nameLower.includes('frontend')) &&
248
- (taskLower.includes('ui') || taskLower.includes('component') || taskLower.includes('react'))) {
249
- bestMatch = agentName
250
- break
251
- }
252
- if ((nameLower.includes('be') || nameLower.includes('backend')) &&
253
- (taskLower.includes('api') || taskLower.includes('server') || taskLower.includes('database'))) {
254
- bestMatch = agentName
255
- break
256
- }
257
- }
258
-
259
- await this.agentRouter.logUsage(task, bestMatch, projectPath)
260
-
261
- return {
262
- agent: { name: bestMatch },
263
- routing: {
264
- confidence: 0.7,
265
- reason: 'Keyword-based agent matching',
266
- availableAgents: agents,
267
- },
268
- _agenticNote: 'Claude should verify this assignment using agent context',
269
- }
270
- } catch {
271
- return {
272
- agent: { name: 'generalist' },
273
- routing: {
274
- confidence: 1.0,
275
- reason: 'Agent routing unavailable',
276
- },
277
- }
278
- }
102
+ return agentService.assignForTask(task, projectPath, context)
279
103
  }
280
104
  }
281
105
 
@@ -0,0 +1,148 @@
1
+ /**
2
+ * Cleanup Commands
3
+ *
4
+ * Memory and project file cleanup operations.
5
+ */
6
+
7
+ import path from 'path'
8
+
9
+ import type { CommandResult, CleanupOptions } from '../types'
10
+ import {
11
+ pathManager,
12
+ configManager,
13
+ jsonlHelper,
14
+ dateHelper,
15
+ out
16
+ } from './base'
17
+ import { ideasStorage, queueStorage } from '../storage'
18
+ import { memoryService } from '../services'
19
+
20
+ /**
21
+ * Memory cleanup helper
22
+ */
23
+ export async function cleanupMemory(projectPath: string): Promise<{
24
+ success: boolean
25
+ results: { rotated: string[]; totalSize: number; freedSpace: number }
26
+ }> {
27
+ const projectId = await configManager.getProjectId(projectPath)
28
+
29
+ const results = { rotated: [] as string[], totalSize: 0, freedSpace: 0 }
30
+ const jsonlFiles = [
31
+ pathManager.getFilePath(projectId!, 'memory', 'context.jsonl'),
32
+ pathManager.getFilePath(projectId!, 'progress', 'shipped.md'),
33
+ pathManager.getFilePath(projectId!, 'planning', 'ideas.md'),
34
+ ]
35
+
36
+ for (const filePath of jsonlFiles) {
37
+ try {
38
+ const sizeMB = await jsonlHelper.getFileSizeMB(filePath)
39
+ if (sizeMB > 0) {
40
+ results.totalSize += sizeMB
41
+ const rotated = await jsonlHelper.rotateJsonLinesIfNeeded(filePath, 10)
42
+ if (rotated) {
43
+ results.rotated.push(path.basename(filePath))
44
+ results.freedSpace += sizeMB
45
+ }
46
+ }
47
+ } catch {
48
+ // skip
49
+ }
50
+ }
51
+
52
+ return { success: true, results }
53
+ }
54
+
55
+ /**
56
+ * Internal cleanup helper for memory during normal cleanup
57
+ */
58
+ export async function cleanupMemoryInternal(projectPath: string): Promise<void> {
59
+ const projectId = await configManager.getProjectId(projectPath)
60
+ const memoryPath = pathManager.getFilePath(projectId!, 'memory', 'context.jsonl')
61
+ await jsonlHelper.rotateJsonLinesIfNeeded(memoryPath, 10)
62
+ }
63
+
64
+ /**
65
+ * /p:cleanup - Clean temp files and old entries
66
+ */
67
+ export async function cleanup(
68
+ options: CleanupOptions = {},
69
+ projectPath: string = process.cwd()
70
+ ): Promise<CommandResult> {
71
+ try {
72
+ const isMemoryMode = options.memory === true || options.type === 'memory'
73
+
74
+ if (isMemoryMode) {
75
+ out.spin('cleaning memory...')
76
+ const result = await cleanupMemory(projectPath)
77
+ out.done('memory cleaned')
78
+ return result
79
+ }
80
+
81
+ out.spin('cleaning up...')
82
+
83
+ const projectId = await configManager.getProjectId(projectPath)
84
+ if (!projectId) {
85
+ out.fail('no project ID')
86
+ return { success: false, error: 'No project ID found' }
87
+ }
88
+
89
+ const cleaned: string[] = []
90
+
91
+ // Clean memory (keep last 100 entries)
92
+ const memoryPath = pathManager.getFilePath(projectId, 'memory', 'context.jsonl')
93
+ try {
94
+ const entries = await jsonlHelper.readJsonLines(memoryPath)
95
+
96
+ if (entries.length > 100) {
97
+ const kept = entries.slice(-100)
98
+ await jsonlHelper.writeJsonLines(memoryPath, kept)
99
+ cleaned.push(`Memory: ${entries.length - 100} old entries removed`)
100
+ } else {
101
+ cleaned.push('Memory: No cleanup needed')
102
+ }
103
+ } catch {
104
+ cleaned.push('Memory: No file found')
105
+ }
106
+
107
+ // Clean ideas using ideasStorage
108
+ try {
109
+ const result = await ideasStorage.cleanup(projectId)
110
+ if (result.removed > 0) {
111
+ cleaned.push(`Ideas: ${result.removed} old archived ideas removed`)
112
+ } else {
113
+ cleaned.push('Ideas: No cleanup needed')
114
+ }
115
+ } catch {
116
+ cleaned.push('Ideas: No file found')
117
+ }
118
+
119
+ // Check queue for completed tasks using queueStorage
120
+ try {
121
+ const tasks = await queueStorage.getActiveTasks(projectId)
122
+ const completedTasks = tasks.filter(t => t.completed).length
123
+
124
+ if (completedTasks > 0) {
125
+ cleaned.push(
126
+ `Queue: ${completedTasks} completed tasks found (not removed - use /p:done to clear)`
127
+ )
128
+ } else {
129
+ cleaned.push('Queue: No completed tasks')
130
+ }
131
+ } catch {
132
+ cleaned.push('Queue: No file found')
133
+ }
134
+
135
+ await cleanupMemoryInternal(projectPath)
136
+
137
+ await memoryService.log(projectPath, 'cleanup_performed', {
138
+ items: cleaned.length,
139
+ timestamp: dateHelper.getTimestamp(),
140
+ })
141
+
142
+ out.done(`${cleaned.length} items cleaned`)
143
+ return { success: true, cleaned }
144
+ } catch (error) {
145
+ out.fail((error as Error).message)
146
+ return { success: false, error: (error as Error).message }
147
+ }
148
+ }