prjct-cli 0.45.0 → 0.45.4

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 (207) hide show
  1. package/CHANGELOG.md +82 -0
  2. package/bin/prjct.ts +117 -10
  3. package/core/__tests__/agentic/memory-system.test.ts +39 -26
  4. package/core/__tests__/agentic/plan-mode.test.ts +64 -46
  5. package/core/__tests__/agentic/prompt-builder.test.ts +14 -14
  6. package/core/__tests__/services/project-index.test.ts +353 -0
  7. package/core/__tests__/types/fs.test.ts +3 -3
  8. package/core/__tests__/utils/date-helper.test.ts +10 -10
  9. package/core/__tests__/utils/output.test.ts +9 -6
  10. package/core/__tests__/utils/project-commands.test.ts +5 -6
  11. package/core/agentic/agent-router.ts +9 -10
  12. package/core/agentic/chain-of-thought.ts +16 -4
  13. package/core/agentic/command-executor.ts +66 -40
  14. package/core/agentic/context-builder.ts +8 -5
  15. package/core/agentic/ground-truth.ts +15 -9
  16. package/core/agentic/index.ts +145 -152
  17. package/core/agentic/loop-detector.ts +40 -11
  18. package/core/agentic/memory-system.ts +98 -35
  19. package/core/agentic/orchestrator-executor.ts +135 -71
  20. package/core/agentic/plan-mode.ts +46 -16
  21. package/core/agentic/prompt-builder.ts +108 -42
  22. package/core/agentic/services.ts +10 -9
  23. package/core/agentic/skill-loader.ts +9 -15
  24. package/core/agentic/smart-context.ts +129 -79
  25. package/core/agentic/template-executor.ts +13 -12
  26. package/core/agentic/template-loader.ts +7 -4
  27. package/core/agentic/tool-registry.ts +16 -13
  28. package/core/agents/index.ts +1 -1
  29. package/core/agents/performance.ts +10 -27
  30. package/core/ai-tools/formatters.ts +8 -6
  31. package/core/ai-tools/generator.ts +4 -4
  32. package/core/ai-tools/index.ts +1 -1
  33. package/core/ai-tools/registry.ts +21 -11
  34. package/core/bus/bus.ts +23 -16
  35. package/core/bus/index.ts +2 -2
  36. package/core/cli/linear.ts +3 -5
  37. package/core/cli/start.ts +28 -25
  38. package/core/commands/analysis.ts +58 -39
  39. package/core/commands/analytics.ts +52 -44
  40. package/core/commands/base.ts +15 -13
  41. package/core/commands/cleanup.ts +6 -13
  42. package/core/commands/command-data.ts +28 -4
  43. package/core/commands/commands.ts +57 -24
  44. package/core/commands/context.ts +4 -4
  45. package/core/commands/design.ts +3 -10
  46. package/core/commands/index.ts +5 -8
  47. package/core/commands/maintenance.ts +7 -4
  48. package/core/commands/planning.ts +179 -56
  49. package/core/commands/register.ts +13 -9
  50. package/core/commands/registry.ts +15 -14
  51. package/core/commands/setup.ts +26 -14
  52. package/core/commands/shipping.ts +11 -16
  53. package/core/commands/snapshots.ts +16 -32
  54. package/core/commands/uninstall.ts +541 -0
  55. package/core/commands/workflow.ts +24 -28
  56. package/core/constants/index.ts +10 -22
  57. package/core/context/generator.ts +82 -33
  58. package/core/context-tools/files-tool.ts +18 -19
  59. package/core/context-tools/imports-tool.ts +13 -33
  60. package/core/context-tools/index.ts +29 -54
  61. package/core/context-tools/recent-tool.ts +16 -22
  62. package/core/context-tools/signatures-tool.ts +17 -26
  63. package/core/context-tools/summary-tool.ts +20 -22
  64. package/core/context-tools/token-counter.ts +25 -20
  65. package/core/context-tools/types.ts +5 -5
  66. package/core/domain/agent-generator.ts +7 -5
  67. package/core/domain/agent-loader.ts +2 -2
  68. package/core/domain/analyzer.ts +19 -16
  69. package/core/domain/architecture-generator.ts +6 -3
  70. package/core/domain/context-estimator.ts +3 -4
  71. package/core/domain/snapshot-manager.ts +25 -22
  72. package/core/domain/task-stack.ts +24 -14
  73. package/core/errors.ts +1 -1
  74. package/core/events/events.ts +2 -4
  75. package/core/events/index.ts +1 -2
  76. package/core/index.ts +28 -16
  77. package/core/infrastructure/agent-detector.ts +3 -3
  78. package/core/infrastructure/ai-provider.ts +23 -20
  79. package/core/infrastructure/author-detector.ts +16 -10
  80. package/core/infrastructure/capability-installer.ts +2 -2
  81. package/core/infrastructure/claude-agent.ts +6 -6
  82. package/core/infrastructure/command-installer.ts +22 -17
  83. package/core/infrastructure/config-manager.ts +18 -14
  84. package/core/infrastructure/editors-config.ts +8 -4
  85. package/core/infrastructure/path-manager.ts +8 -6
  86. package/core/infrastructure/permission-manager.ts +20 -17
  87. package/core/infrastructure/setup.ts +42 -38
  88. package/core/infrastructure/update-checker.ts +5 -5
  89. package/core/integrations/issue-tracker/enricher.ts +8 -19
  90. package/core/integrations/issue-tracker/index.ts +2 -2
  91. package/core/integrations/issue-tracker/manager.ts +15 -15
  92. package/core/integrations/issue-tracker/types.ts +5 -22
  93. package/core/integrations/jira/client.ts +67 -59
  94. package/core/integrations/jira/index.ts +11 -14
  95. package/core/integrations/jira/mcp-adapter.ts +5 -10
  96. package/core/integrations/jira/service.ts +10 -10
  97. package/core/integrations/linear/client.ts +27 -18
  98. package/core/integrations/linear/index.ts +9 -12
  99. package/core/integrations/linear/service.ts +11 -11
  100. package/core/integrations/linear/sync.ts +8 -8
  101. package/core/outcomes/analyzer.ts +5 -18
  102. package/core/outcomes/index.ts +2 -2
  103. package/core/outcomes/recorder.ts +3 -3
  104. package/core/plugin/builtin/webhook.ts +19 -15
  105. package/core/plugin/hooks.ts +29 -21
  106. package/core/plugin/index.ts +7 -7
  107. package/core/plugin/loader.ts +19 -19
  108. package/core/plugin/registry.ts +12 -23
  109. package/core/schemas/agents.ts +1 -1
  110. package/core/schemas/analysis.ts +1 -1
  111. package/core/schemas/enriched-task.ts +62 -49
  112. package/core/schemas/ideas.ts +13 -13
  113. package/core/schemas/index.ts +17 -27
  114. package/core/schemas/issues.ts +40 -25
  115. package/core/schemas/metrics.ts +25 -25
  116. package/core/schemas/outcomes.ts +70 -62
  117. package/core/schemas/permissions.ts +15 -12
  118. package/core/schemas/prd.ts +27 -14
  119. package/core/schemas/project.ts +3 -3
  120. package/core/schemas/roadmap.ts +47 -34
  121. package/core/schemas/schemas.ts +3 -4
  122. package/core/schemas/shipped.ts +3 -3
  123. package/core/schemas/state.ts +43 -29
  124. package/core/server/index.ts +5 -6
  125. package/core/server/routes-extended.ts +68 -72
  126. package/core/server/routes.ts +3 -3
  127. package/core/server/server.ts +31 -26
  128. package/core/services/agent-generator.ts +237 -0
  129. package/core/services/agent-service.ts +2 -2
  130. package/core/services/breakdown-service.ts +2 -4
  131. package/core/services/context-generator.ts +299 -0
  132. package/core/services/context-selector.ts +420 -0
  133. package/core/services/doctor-service.ts +426 -0
  134. package/core/services/file-categorizer.ts +448 -0
  135. package/core/services/file-scorer.ts +270 -0
  136. package/core/services/git-analyzer.ts +267 -0
  137. package/core/services/index.ts +27 -10
  138. package/core/services/memory-service.ts +3 -4
  139. package/core/services/project-index.ts +911 -0
  140. package/core/services/project-service.ts +4 -4
  141. package/core/services/skill-installer.ts +14 -17
  142. package/core/services/skill-lock.ts +3 -3
  143. package/core/services/skill-service.ts +12 -6
  144. package/core/services/stack-detector.ts +245 -0
  145. package/core/services/sync-service.ts +87 -345
  146. package/core/services/watch-service.ts +294 -0
  147. package/core/session/compaction.ts +23 -31
  148. package/core/session/index.ts +11 -5
  149. package/core/session/log-migration.ts +3 -3
  150. package/core/session/metrics.ts +19 -14
  151. package/core/session/session-log-manager.ts +12 -17
  152. package/core/session/task-session-manager.ts +25 -25
  153. package/core/session/utils.ts +1 -1
  154. package/core/storage/ideas-storage.ts +41 -57
  155. package/core/storage/index-storage.ts +514 -0
  156. package/core/storage/index.ts +41 -17
  157. package/core/storage/metrics-storage.ts +39 -34
  158. package/core/storage/queue-storage.ts +35 -45
  159. package/core/storage/shipped-storage.ts +17 -20
  160. package/core/storage/state-storage.ts +50 -30
  161. package/core/storage/storage-manager.ts +6 -6
  162. package/core/storage/storage.ts +18 -15
  163. package/core/sync/auth-config.ts +3 -3
  164. package/core/sync/index.ts +13 -19
  165. package/core/sync/oauth-handler.ts +3 -3
  166. package/core/sync/sync-client.ts +4 -9
  167. package/core/sync/sync-manager.ts +12 -14
  168. package/core/types/commands.ts +42 -7
  169. package/core/types/index.ts +284 -305
  170. package/core/types/integrations.ts +3 -3
  171. package/core/types/storage.ts +14 -14
  172. package/core/types/utils.ts +3 -3
  173. package/core/utils/agent-stream.ts +3 -1
  174. package/core/utils/animations.ts +14 -11
  175. package/core/utils/branding.ts +7 -7
  176. package/core/utils/cache.ts +1 -3
  177. package/core/utils/collection-filters.ts +3 -15
  178. package/core/utils/date-helper.ts +2 -7
  179. package/core/utils/file-helper.ts +13 -8
  180. package/core/utils/jsonl-helper.ts +13 -10
  181. package/core/utils/keychain.ts +4 -8
  182. package/core/utils/logger.ts +1 -1
  183. package/core/utils/next-steps.ts +3 -3
  184. package/core/utils/output.ts +58 -11
  185. package/core/utils/project-commands.ts +6 -6
  186. package/core/utils/project-credentials.ts +5 -12
  187. package/core/utils/runtime.ts +2 -2
  188. package/core/utils/session-helper.ts +3 -4
  189. package/core/utils/version.ts +3 -3
  190. package/core/wizard/index.ts +13 -0
  191. package/core/wizard/onboarding.ts +633 -0
  192. package/core/workflow/state-machine.ts +7 -7
  193. package/dist/bin/prjct.mjs +18755 -15574
  194. package/dist/core/infrastructure/command-installer.js +86 -79
  195. package/dist/core/infrastructure/editors-config.js +6 -6
  196. package/dist/core/infrastructure/setup.js +246 -225
  197. package/dist/core/utils/version.js +9 -9
  198. package/package.json +11 -12
  199. package/scripts/build.js +3 -3
  200. package/scripts/postinstall.js +2 -2
  201. package/templates/mcp-config.json +6 -1
  202. package/templates/permissions/permissive.jsonc +1 -1
  203. package/templates/permissions/strict.jsonc +5 -9
  204. package/templates/global/docs/agents.md +0 -88
  205. package/templates/global/docs/architecture.md +0 -103
  206. package/templates/global/docs/commands.md +0 -96
  207. package/templates/global/docs/validation.md +0 -95
@@ -5,11 +5,17 @@
5
5
  * Generates context/now.md for Claude
6
6
  */
7
7
 
8
- import { StorageManager } from './storage-manager'
9
8
  import { generateUUID } from '../schemas'
10
- import { md } from '../utils/markdown-builder'
9
+ import type {
10
+ CurrentTask,
11
+ PreviousTask,
12
+ StateJson,
13
+ Subtask,
14
+ SubtaskSummary,
15
+ } from '../schemas/state'
11
16
  import { getTimestamp } from '../utils/date-helper'
12
- import type { StateJson, CurrentTask, PreviousTask, Subtask, SubtaskSummary } from '../schemas/state'
17
+ import { md } from '../utils/markdown-builder'
18
+ import { StorageManager } from './storage-manager'
13
19
 
14
20
  class StateStorage extends StorageManager<StateJson> {
15
21
  constructor() {
@@ -20,7 +26,7 @@ class StateStorage extends StorageManager<StateJson> {
20
26
  return {
21
27
  currentTask: null,
22
28
  previousTask: null,
23
- lastUpdated: ''
29
+ lastUpdated: '',
24
30
  }
25
31
  }
26
32
 
@@ -51,17 +57,26 @@ class StateStorage extends StorageManager<StateJson> {
51
57
  if (task.subtasks && task.subtasks.length > 0) {
52
58
  m.blank()
53
59
  .h2('Subtasks Progress')
54
- .raw(`**Progress**: ${task.subtaskProgress?.completed || 0}/${task.subtaskProgress?.total || 0} (${task.subtaskProgress?.percentage || 0}%)`)
60
+ .raw(
61
+ `**Progress**: ${task.subtaskProgress?.completed || 0}/${task.subtaskProgress?.total || 0} (${task.subtaskProgress?.percentage || 0}%)`
62
+ )
55
63
  .blank()
56
64
  .raw('| # | Domain | Description | Status | Agent |')
57
65
  .raw('|---|--------|-------------|--------|-------|')
58
66
 
59
67
  task.subtasks.forEach((subtask, index) => {
60
- const statusIcon = subtask.status === 'completed' ? '✅' :
61
- subtask.status === 'in_progress' ? '▶️' :
62
- subtask.status === 'failed' ? '' : '⏳'
68
+ const statusIcon =
69
+ subtask.status === 'completed'
70
+ ? ''
71
+ : subtask.status === 'in_progress'
72
+ ? '▶️'
73
+ : subtask.status === 'failed'
74
+ ? '❌'
75
+ : '⏳'
63
76
  const isActive = index === task.currentSubtaskIndex ? ' **← Active**' : ''
64
- m.raw(`| ${index + 1} | ${subtask.domain} | ${subtask.description} | ${statusIcon} ${subtask.status}${isActive} | ${subtask.agent} |`)
77
+ m.raw(
78
+ `| ${index + 1} | ${subtask.domain} | ${subtask.description} | ${statusIcon} ${subtask.status}${isActive} | ${subtask.agent} |`
79
+ )
65
80
  })
66
81
 
67
82
  // Current subtask details
@@ -80,7 +95,9 @@ class StateStorage extends StorageManager<StateJson> {
80
95
  }
81
96
 
82
97
  // Show last completed subtask summary if available
83
- const completedSubtasks = task.subtasks.filter(s => s.status === 'completed' && s.summary)
98
+ const completedSubtasks = task.subtasks.filter(
99
+ (s) => s.status === 'completed' && s.summary
100
+ )
84
101
  if (completedSubtasks.length > 0) {
85
102
  const lastCompleted = completedSubtasks[completedSubtasks.length - 1]
86
103
  if (lastCompleted.summary) {
@@ -124,19 +141,16 @@ class StateStorage extends StorageManager<StateJson> {
124
141
  /**
125
142
  * Start a new task
126
143
  */
127
- async startTask(
128
- projectId: string,
129
- task: Omit<CurrentTask, 'startedAt'>
130
- ): Promise<CurrentTask> {
144
+ async startTask(projectId: string, task: Omit<CurrentTask, 'startedAt'>): Promise<CurrentTask> {
131
145
  const currentTask: CurrentTask = {
132
146
  ...task,
133
- startedAt: getTimestamp()
147
+ startedAt: getTimestamp(),
134
148
  }
135
149
 
136
150
  await this.update(projectId, (state) => ({
137
151
  ...state,
138
152
  currentTask,
139
- lastUpdated: getTimestamp()
153
+ lastUpdated: getTimestamp(),
140
154
  }))
141
155
 
142
156
  // Publish incremental event
@@ -144,7 +158,7 @@ class StateStorage extends StorageManager<StateJson> {
144
158
  taskId: currentTask.id,
145
159
  description: currentTask.description,
146
160
  startedAt: currentTask.startedAt,
147
- sessionId: currentTask.sessionId
161
+ sessionId: currentTask.sessionId,
148
162
  })
149
163
 
150
164
  return currentTask
@@ -164,7 +178,7 @@ class StateStorage extends StorageManager<StateJson> {
164
178
  await this.update(projectId, () => ({
165
179
  currentTask: null,
166
180
  previousTask: null,
167
- lastUpdated: getTimestamp()
181
+ lastUpdated: getTimestamp(),
168
182
  }))
169
183
 
170
184
  // Publish incremental event
@@ -172,7 +186,7 @@ class StateStorage extends StorageManager<StateJson> {
172
186
  taskId: completedTask.id,
173
187
  description: completedTask.description,
174
188
  startedAt: completedTask.startedAt,
175
- completedAt: getTimestamp()
189
+ completedAt: getTimestamp(),
176
190
  })
177
191
 
178
192
  return completedTask
@@ -194,13 +208,13 @@ class StateStorage extends StorageManager<StateJson> {
194
208
  status: 'paused',
195
209
  startedAt: state.currentTask.startedAt,
196
210
  pausedAt: getTimestamp(),
197
- pauseReason: reason
211
+ pauseReason: reason,
198
212
  }
199
213
 
200
214
  await this.update(projectId, () => ({
201
215
  currentTask: null,
202
216
  previousTask,
203
- lastUpdated: getTimestamp()
217
+ lastUpdated: getTimestamp(),
204
218
  }))
205
219
 
206
220
  // Publish incremental event
@@ -208,7 +222,7 @@ class StateStorage extends StorageManager<StateJson> {
208
222
  taskId: previousTask.id,
209
223
  description: previousTask.description,
210
224
  pausedAt: previousTask.pausedAt,
211
- reason
225
+ reason,
212
226
  })
213
227
 
214
228
  return previousTask
@@ -228,20 +242,20 @@ class StateStorage extends StorageManager<StateJson> {
228
242
  id: state.previousTask.id,
229
243
  description: state.previousTask.description,
230
244
  startedAt: getTimestamp(),
231
- sessionId: generateUUID()
245
+ sessionId: generateUUID(),
232
246
  }
233
247
 
234
248
  await this.update(projectId, () => ({
235
249
  currentTask,
236
250
  previousTask: null,
237
- lastUpdated: getTimestamp()
251
+ lastUpdated: getTimestamp(),
238
252
  }))
239
253
 
240
254
  // Publish incremental event
241
255
  await this.publishEvent(projectId, 'task.resumed', {
242
256
  taskId: currentTask.id,
243
257
  description: currentTask.description,
244
- resumedAt: currentTask.startedAt
258
+ resumedAt: currentTask.startedAt,
245
259
  })
246
260
 
247
261
  return currentTask
@@ -254,7 +268,7 @@ class StateStorage extends StorageManager<StateJson> {
254
268
  await this.update(projectId, () => ({
255
269
  currentTask: null,
256
270
  previousTask: null,
257
- lastUpdated: getTimestamp()
271
+ lastUpdated: getTimestamp(),
258
272
  }))
259
273
  }
260
274
 
@@ -314,7 +328,11 @@ class StateStorage extends StorageManager<StateJson> {
314
328
  await this.publishEvent(projectId, 'subtasks.created', {
315
329
  taskId: state.currentTask.id,
316
330
  subtaskCount: fullSubtasks.length,
317
- subtasks: fullSubtasks.map(s => ({ id: s.id, description: s.description, domain: s.domain })),
331
+ subtasks: fullSubtasks.map((s) => ({
332
+ id: s.id,
333
+ description: s.description,
334
+ domain: s.domain,
335
+ })),
318
336
  })
319
337
  }
320
338
 
@@ -345,7 +363,7 @@ class StateStorage extends StorageManager<StateJson> {
345
363
  }
346
364
 
347
365
  // Calculate new progress
348
- const completed = updatedSubtasks.filter(s => s.status === 'completed').length
366
+ const completed = updatedSubtasks.filter((s) => s.status === 'completed').length
349
367
  const total = updatedSubtasks.length
350
368
  const percentage = Math.round((completed / total) * 100)
351
369
 
@@ -425,7 +443,9 @@ class StateStorage extends StorageManager<StateJson> {
425
443
  /**
426
444
  * Get subtask progress
427
445
  */
428
- async getSubtaskProgress(projectId: string): Promise<{ completed: number; total: number; percentage: number } | null> {
446
+ async getSubtaskProgress(
447
+ projectId: string
448
+ ): Promise<{ completed: number; total: number; percentage: number } | null> {
429
449
  const state = await this.read(projectId)
430
450
  return state.currentTask?.subtaskProgress || null
431
451
  }
@@ -444,7 +464,7 @@ class StateStorage extends StorageManager<StateJson> {
444
464
  async areAllSubtasksComplete(projectId: string): Promise<boolean> {
445
465
  const state = await this.read(projectId)
446
466
  if (!state.currentTask?.subtasks) return true
447
- return state.currentTask.subtasks.every(s => s.status === 'completed')
467
+ return state.currentTask.subtasks.every((s) => s.status === 'completed')
448
468
  }
449
469
 
450
470
  /**
@@ -9,13 +9,13 @@
9
9
  * Subclasses implement specific data types (state, queue, ideas, shipped).
10
10
  */
11
11
 
12
- import fs from 'fs/promises'
13
- import path from 'path'
12
+ import fs from 'node:fs/promises'
13
+ import path from 'node:path'
14
14
  import { eventBus, type SyncEvent } from '../events'
15
- import { getTimestamp } from '../utils/date-helper'
16
15
  import pathManager from '../infrastructure/path-manager'
17
- import { TTLCache } from '../utils/cache'
18
16
  import { isNotFoundError } from '../types/fs'
17
+ import { TTLCache } from '../utils/cache'
18
+ import { getTimestamp } from '../utils/date-helper'
19
19
 
20
20
  export abstract class StorageManager<T> {
21
21
  protected filename: string
@@ -143,7 +143,7 @@ export abstract class StorageManager<T> {
143
143
  path: [this.filename.replace('.json', '')],
144
144
  data: eventData,
145
145
  timestamp: getTimestamp(),
146
- projectId
146
+ projectId,
147
147
  }
148
148
 
149
149
  await eventBus.publish(event)
@@ -167,7 +167,7 @@ export abstract class StorageManager<T> {
167
167
  const eventType = `${entity}.${action}`
168
168
  const eventData = {
169
169
  ...payload,
170
- timestamp: getTimestamp()
170
+ timestamp: getTimestamp(),
171
171
  }
172
172
 
173
173
  await this.publishEvent(projectId, eventType, eventData)
@@ -5,12 +5,12 @@
5
5
  * For future per-entity storage: data/{entity}s/{id}.json
6
6
  */
7
7
 
8
- import fs from 'fs/promises'
9
- import path from 'path'
10
- import os from 'os'
8
+ import fs from 'node:fs/promises'
9
+ import os from 'node:os'
10
+ import path from 'node:path'
11
11
  import { eventBus, inferEventType } from '../events'
12
- import { isNotFoundError } from '../types/fs'
13
12
  import type { Storage } from '../types'
13
+ import { isNotFoundError } from '../types/fs'
14
14
 
15
15
  class FileStorage implements Storage {
16
16
  private projectId: string
@@ -32,9 +32,9 @@ class FileStorage implements Storage {
32
32
  }
33
33
 
34
34
  // Pluralize first segment for directory
35
- const dir = pathArray[0] + 's'
35
+ const dir = `${pathArray[0]}s`
36
36
  const rest = pathArray.slice(1)
37
- const filename = rest.join('/') + '.json'
37
+ const filename = `${rest.join('/')}.json`
38
38
 
39
39
  return path.join(this.basePath, dir, filename)
40
40
  }
@@ -54,7 +54,7 @@ class FileStorage implements Storage {
54
54
  path: pathArray,
55
55
  data,
56
56
  timestamp: new Date().toISOString(),
57
- projectId: this.projectId
57
+ projectId: this.projectId,
58
58
  })
59
59
 
60
60
  // Update index if it's a collection item
@@ -78,13 +78,13 @@ class FileStorage implements Storage {
78
78
  }
79
79
 
80
80
  async list(prefix: string[]): Promise<string[][]> {
81
- const dir = path.join(this.basePath, prefix[0] + 's')
81
+ const dir = path.join(this.basePath, `${prefix[0]}s`)
82
82
 
83
83
  try {
84
84
  const files = await fs.readdir(dir)
85
85
  return files
86
- .filter(f => f.endsWith('.json') && f !== 'index.json')
87
- .map(f => [...prefix, f.replace('.json', '')])
86
+ .filter((f) => f.endsWith('.json') && f !== 'index.json')
87
+ .map((f) => [...prefix, f.replace('.json', '')])
88
88
  } catch (error) {
89
89
  if (isNotFoundError(error)) {
90
90
  return []
@@ -105,7 +105,7 @@ class FileStorage implements Storage {
105
105
  path: pathArray,
106
106
  data: null,
107
107
  timestamp: new Date().toISOString(),
108
- projectId: this.projectId
108
+ projectId: this.projectId,
109
109
  })
110
110
 
111
111
  // Update index if it's a collection item
@@ -137,8 +137,12 @@ class FileStorage implements Storage {
137
137
  /**
138
138
  * Update collection index
139
139
  */
140
- private async updateIndex(collection: string, id: string, action: 'add' | 'remove'): Promise<void> {
141
- const indexPath = path.join(this.basePath, collection + 's', 'index.json')
140
+ private async updateIndex(
141
+ collection: string,
142
+ id: string,
143
+ action: 'add' | 'remove'
144
+ ): Promise<void> {
145
+ const indexPath = path.join(this.basePath, `${collection}s`, 'index.json')
142
146
 
143
147
  let index: { ids: string[]; updatedAt: string } = { ids: [], updatedAt: '' }
144
148
 
@@ -155,7 +159,7 @@ class FileStorage implements Storage {
155
159
  if (action === 'add' && !index.ids.includes(id)) {
156
160
  index.ids.push(id)
157
161
  } else if (action === 'remove') {
158
- index.ids = index.ids.filter(i => i !== id)
162
+ index.ids = index.ids.filter((i) => i !== id)
159
163
  }
160
164
 
161
165
  index.updatedAt = new Date().toISOString()
@@ -173,4 +177,3 @@ export function getStorage(projectId: string): Storage {
173
177
  }
174
178
 
175
179
  export default { getStorage }
176
-
@@ -5,10 +5,10 @@
5
5
  * Used by SyncClient to authenticate with prjct API
6
6
  */
7
7
 
8
- import path from 'path'
9
- import * as fileHelper from '../utils/file-helper'
8
+ import path from 'node:path'
10
9
  import pathManager from '../infrastructure/path-manager'
11
10
  import type { AuthConfig } from '../types'
11
+ import * as fileHelper from '../utils/file-helper'
12
12
 
13
13
  const DEFAULT_API_URL = 'https://api.prjct.app'
14
14
 
@@ -121,7 +121,7 @@ class AuthConfigManager {
121
121
  return {
122
122
  authenticated: config.apiKey !== null,
123
123
  email: config.email,
124
- apiKeyPrefix: config.apiKey ? config.apiKey.substring(0, 12) + '...' : null,
124
+ apiKeyPrefix: config.apiKey ? `${config.apiKey.substring(0, 12)}...` : null,
125
125
  lastAuth: config.lastAuth,
126
126
  }
127
127
  }
@@ -8,30 +8,24 @@
8
8
  * - OAuthHandler: Authentication flow management
9
9
  */
10
10
 
11
- // Auth
12
- export { authConfig } from './auth-config'
13
-
14
- // OAuth
15
- export { oauthHandler } from './oauth-handler'
16
-
17
- // Client
18
- export { syncClient } from './sync-client'
19
-
20
- // Manager
21
- export { syncManager } from './sync-manager'
22
-
23
- // Default export is the main sync manager
24
- export { syncManager as default } from './sync-manager'
25
-
26
11
  // Re-export types from canonical location
27
12
  export type {
28
13
  AuthConfig,
29
14
  AuthResult,
15
+ PullResult,
16
+ PushResult,
30
17
  SyncBatchResult,
31
- SyncPullResult,
32
- SyncStatus,
33
18
  SyncClientError,
34
19
  SyncManagerResult as SyncResult,
35
- PushResult,
36
- PullResult,
20
+ SyncPullResult,
21
+ SyncStatus,
37
22
  } from '../types'
23
+ // Auth
24
+ export { authConfig } from './auth-config'
25
+ // OAuth
26
+ export { oauthHandler } from './oauth-handler'
27
+ // Client
28
+ export { syncClient } from './sync-client'
29
+ // Manager
30
+ // Default export is the main sync manager
31
+ export { syncManager, syncManager as default } from './sync-manager'
@@ -6,9 +6,9 @@
6
6
  * 2. Browser (future): Full OAuth device flow
7
7
  */
8
8
 
9
+ import type { AuthResult } from '../types'
9
10
  import { authConfig } from './auth-config'
10
11
  import { syncClient } from './sync-client'
11
- import type { AuthResult } from '../types'
12
12
 
13
13
  class OAuthHandler {
14
14
  /**
@@ -123,8 +123,8 @@ class OAuthHandler {
123
123
  * Open URL in default browser
124
124
  */
125
125
  async openBrowser(url: string): Promise<void> {
126
- const { exec } = await import('child_process')
127
- const { promisify } = await import('util')
126
+ const { exec } = await import('node:child_process')
127
+ const { promisify } = await import('node:util')
128
128
  const execAsync = promisify(exec)
129
129
 
130
130
  const platform = process.platform
@@ -5,14 +5,9 @@
5
5
  * Uses native fetch API (available in Node 18+ and Bun).
6
6
  */
7
7
 
8
- import authConfig from './auth-config'
9
8
  import type { SyncEvent } from '../events'
10
- import type {
11
- SyncBatchResult,
12
- SyncPullResult,
13
- SyncStatus,
14
- SyncClientError,
15
- } from '../types'
9
+ import type { SyncBatchResult, SyncClientError, SyncPullResult, SyncStatus } from '../types'
10
+ import authConfig from './auth-config'
16
11
 
17
12
  // ============================================
18
13
  // Sync Client
@@ -167,7 +162,7 @@ class SyncClient {
167
162
  // Retry on server errors (5xx) but not client errors (4xx)
168
163
  if (response.status >= 500 && retryCount < this.retryConfig.maxRetries) {
169
164
  const delay = Math.min(
170
- this.retryConfig.baseDelayMs * Math.pow(2, retryCount),
165
+ this.retryConfig.baseDelayMs * 2 ** retryCount,
171
166
  this.retryConfig.maxDelayMs
172
167
  )
173
168
  await this.sleep(delay)
@@ -179,7 +174,7 @@ class SyncClient {
179
174
  // Retry on network errors
180
175
  if (retryCount < this.retryConfig.maxRetries) {
181
176
  const delay = Math.min(
182
- this.retryConfig.baseDelayMs * Math.pow(2, retryCount),
177
+ this.retryConfig.baseDelayMs * 2 ** retryCount,
183
178
  this.retryConfig.maxDelayMs
184
179
  )
185
180
  await this.sleep(delay)
@@ -5,23 +5,23 @@
5
5
  * Handles the coordination between local storage (EventBus) and remote API (SyncClient).
6
6
  */
7
7
 
8
- import { syncClient } from './sync-client'
9
- import authConfig from './auth-config'
10
- import eventBus, { type SyncEvent } from '../events'
11
- import { stateStorage } from '../storage/state-storage'
12
- import { queueStorage } from '../storage/queue-storage'
8
+ import eventBus from '../events'
9
+ import type { IdeaPriority } from '../schemas/ideas'
10
+ import type { Priority, TaskSection, TaskType } from '../schemas/state'
13
11
  import { ideasStorage } from '../storage/ideas-storage'
12
+ import { queueStorage } from '../storage/queue-storage'
14
13
  import { shippedStorage } from '../storage/shipped-storage'
15
- import type { TaskType, Priority, TaskSection } from '../schemas/state'
14
+ import { stateStorage } from '../storage/state-storage'
16
15
  import type {
17
- SyncManagerResult as SyncResult,
18
- PushResult,
19
16
  PullResult,
17
+ PushResult,
20
18
  SyncBatchResult,
21
19
  SyncPullResult,
20
+ SyncManagerResult as SyncResult,
22
21
  SyncStatus,
23
22
  } from '../types'
24
- import type { IdeaPriority } from '../schemas/ideas'
23
+ import authConfig from './auth-config'
24
+ import { syncClient } from './sync-client'
25
25
 
26
26
  // ============================================
27
27
  // Sync Manager
@@ -293,11 +293,9 @@ class SyncManager {
293
293
  ): Promise<void> {
294
294
  switch (action) {
295
295
  case 'created':
296
- await ideasStorage.addIdea(
297
- projectId,
298
- (data.title as string) || (data.text as string),
299
- { priority: (data.priority as IdeaPriority) || 'medium' }
300
- )
296
+ await ideasStorage.addIdea(projectId, (data.title as string) || (data.text as string), {
297
+ priority: (data.priority as IdeaPriority) || 'medium',
298
+ })
301
299
  break
302
300
  case 'archived':
303
301
  await ideasStorage.update(projectId, (ideas) => ({
@@ -131,6 +131,20 @@ export interface SetupOptions {
131
131
  nonInteractive?: boolean
132
132
  }
133
133
 
134
+ /**
135
+ * Options for the uninstall command.
136
+ */
137
+ export interface UninstallOptions {
138
+ /** Skip confirmation prompt */
139
+ force?: boolean
140
+ /** Create backup before deletion */
141
+ backup?: boolean
142
+ /** Show what would be deleted without actually deleting */
143
+ dryRun?: boolean
144
+ /** Keep the npm/homebrew package, only remove data */
145
+ keepPackage?: boolean
146
+ }
147
+
134
148
  /**
135
149
  * Options for the analyze command.
136
150
  */
@@ -213,18 +227,39 @@ export interface HealthResult {
213
227
  * Type-safe command method names (for dynamic invocation)
214
228
  */
215
229
  export type CommandMethodName =
216
- | 'work' | 'now' | 'done' | 'next' | 'pause' | 'resume'
217
- | 'init' | 'feature' | 'bug' | 'idea' | 'spec'
230
+ | 'work'
231
+ | 'now'
232
+ | 'done'
233
+ | 'next'
234
+ | 'pause'
235
+ | 'resume'
236
+ | 'init'
237
+ | 'feature'
238
+ | 'bug'
239
+ | 'idea'
240
+ | 'spec'
218
241
  | 'ship'
219
- | 'dash' | 'help'
220
- | 'cleanup' | 'design' | 'recover' | 'undo' | 'redo' | 'history'
221
- | 'analyze' | 'sync'
222
- | 'start' | 'setup' | 'migrateAll'
242
+ | 'dash'
243
+ | 'help'
244
+ | 'cleanup'
245
+ | 'design'
246
+ | 'recover'
247
+ | 'undo'
248
+ | 'redo'
249
+ | 'history'
250
+ | 'analyze'
251
+ | 'sync'
252
+ | 'start'
253
+ | 'setup'
254
+ | 'migrateAll'
223
255
 
224
256
  /**
225
257
  * Function signature for standard command methods
226
258
  */
227
- export type StandardCommandFn = (param: string | null, projectPath?: string) => Promise<CommandResult>
259
+ export type StandardCommandFn = (
260
+ param: string | null,
261
+ projectPath?: string
262
+ ) => Promise<CommandResult>
228
263
 
229
264
  // ============================================
230
265
  // Registry Types