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
@@ -4,16 +4,16 @@
4
4
  * Tracks metrics, timeline, and archives completed sessions.
5
5
  */
6
6
 
7
- import fs from 'fs/promises'
8
- import path from 'path'
9
- import { exec } from 'child_process'
10
- import { promisify } from 'util'
11
- import pathManager from '../infrastructure/path-manager'
12
- import configManager from '../infrastructure/config-manager'
7
+ import { exec } from 'node:child_process'
8
+ import fs from 'node:fs/promises'
9
+ import path from 'node:path'
10
+ import { promisify } from 'node:util'
13
11
  import { emit } from '../bus'
14
- import { isNotFoundError } from '../types/fs'
12
+ import configManager from '../infrastructure/config-manager'
13
+ import pathManager from '../infrastructure/path-manager'
15
14
  import type { Session, SessionMetrics } from '../types'
16
- import { generateId, calculateDuration, formatDuration } from './utils'
15
+ import { isNotFoundError } from '../types/fs'
16
+ import { calculateDuration, formatDuration, generateId } from './utils'
17
17
 
18
18
  const execAsync = promisify(exec)
19
19
 
@@ -100,11 +100,9 @@ export class TaskSessionManager {
100
100
  linesAdded: 0,
101
101
  linesRemoved: 0,
102
102
  commits: 0,
103
- snapshots: []
103
+ snapshots: [],
104
104
  },
105
- timeline: [
106
- { type: 'start', at: now }
107
- ]
105
+ timeline: [{ type: 'start', at: now }],
108
106
  }
109
107
 
110
108
  // Save as current session
@@ -117,7 +115,7 @@ export class TaskSessionManager {
117
115
  await emit.sessionStarted({
118
116
  sessionId: session.id,
119
117
  task,
120
- projectId: this.projectId
118
+ projectId: this.projectId,
121
119
  })
122
120
 
123
121
  return session
@@ -161,7 +159,7 @@ export class TaskSessionManager {
161
159
  await emit.sessionResumed({
162
160
  sessionId: current.id,
163
161
  task: current.task,
164
- projectId: this.projectId
162
+ projectId: this.projectId,
165
163
  })
166
164
 
167
165
  return current
@@ -196,7 +194,7 @@ export class TaskSessionManager {
196
194
  sessionId: current.id,
197
195
  task: current.task,
198
196
  duration: current.duration,
199
- projectId: this.projectId
197
+ projectId: this.projectId,
200
198
  })
201
199
 
202
200
  return current
@@ -231,7 +229,7 @@ export class TaskSessionManager {
231
229
  sessionId: current.id,
232
230
  task: current.task,
233
231
  duration: current.duration,
234
- metrics: current.metrics
232
+ metrics: current.metrics,
235
233
  })
236
234
 
237
235
  // Emit event for plugins
@@ -240,7 +238,7 @@ export class TaskSessionManager {
240
238
  task: current.task,
241
239
  duration: current.duration,
242
240
  metrics: current.metrics,
243
- projectId: this.projectId
241
+ projectId: this.projectId,
244
242
  })
245
243
 
246
244
  return current
@@ -268,7 +266,7 @@ export class TaskSessionManager {
268
266
  `git rev-list --count --since="${since}" HEAD 2>/dev/null || echo "0"`,
269
267
  { cwd: this.projectPath }
270
268
  )
271
- metrics.commits = parseInt(commitCount.trim()) || 0
269
+ metrics.commits = parseInt(commitCount.trim(), 10) || 0
272
270
 
273
271
  // Get diff stats
274
272
  const { stdout: diffStat } = await execAsync(
@@ -279,12 +277,14 @@ export class TaskSessionManager {
279
277
  // Parse diff stats
280
278
  const lines = diffStat.split('\n')
281
279
  const summaryLine = lines[lines.length - 2] || ''
282
- const match = summaryLine.match(/(\d+) files? changed(?:, (\d+) insertions?)?(?:, (\d+) deletions?)?/)
280
+ const match = summaryLine.match(
281
+ /(\d+) files? changed(?:, (\d+) insertions?)?(?:, (\d+) deletions?)?/
282
+ )
283
283
 
284
284
  if (match) {
285
- metrics.filesChanged = parseInt(match[1]) || 0
286
- metrics.linesAdded = parseInt(match[2]) || 0
287
- metrics.linesRemoved = parseInt(match[3]) || 0
285
+ metrics.filesChanged = parseInt(match[1], 10) || 0
286
+ metrics.linesAdded = parseInt(match[2], 10) || 0
287
+ metrics.linesRemoved = parseInt(match[3], 10) || 0
288
288
  }
289
289
  } catch (error) {
290
290
  // Keep existing metrics if git fails (not a repo, git not installed, etc.)
@@ -379,11 +379,11 @@ export class TaskSessionManager {
379
379
  const globalPath = pathManager.getGlobalProjectPath(this.projectId!)
380
380
  const memoryPath = path.join(globalPath, 'memory', 'context.jsonl')
381
381
 
382
- const entry = JSON.stringify({
382
+ const entry = `${JSON.stringify({
383
383
  timestamp: new Date().toISOString(),
384
384
  action,
385
- ...data
386
- }) + '\n'
385
+ ...data,
386
+ })}\n`
387
387
 
388
388
  try {
389
389
  await fs.appendFile(memoryPath, entry)
@@ -2,8 +2,8 @@
2
2
  * Session Utilities
3
3
  */
4
4
 
5
- import type { Session, TimelineEvent } from '../types'
6
5
  import { generateUUID } from '../schemas'
6
+ import type { Session } from '../types'
7
7
 
8
8
  /**
9
9
  * Generate unique session ID (re-export from schemas)
@@ -5,10 +5,10 @@
5
5
  * Generates context/ideas.md for Claude
6
6
  */
7
7
 
8
- import { StorageManager } from './storage-manager'
9
8
  import { generateUUID } from '../schemas'
9
+ import type { Idea, IdeaPriority, IdeaStatus, IdeasJson } from '../types'
10
10
  import { getTimestamp } from '../utils/date-helper'
11
- import type { Idea, IdeasJson, IdeaStatus, IdeaPriority } from '../types'
11
+ import { StorageManager } from './storage-manager'
12
12
 
13
13
  class IdeasStorage extends StorageManager<IdeasJson> {
14
14
  constructor() {
@@ -18,7 +18,7 @@ class IdeasStorage extends StorageManager<IdeasJson> {
18
18
  protected getDefault(): IdeasJson {
19
19
  return {
20
20
  ideas: [],
21
- lastUpdated: ''
21
+ lastUpdated: '',
22
22
  }
23
23
  }
24
24
 
@@ -37,16 +37,16 @@ class IdeasStorage extends StorageManager<IdeasJson> {
37
37
  protected toMarkdown(data: IdeasJson): string {
38
38
  const lines = ['# IDEAS \u{1F4A1}', '']
39
39
 
40
- const pending = data.ideas.filter(i => i.status === 'pending')
41
- const converted = data.ideas.filter(i => i.status === 'converted')
42
- const archived = data.ideas.filter(i => i.status === 'archived')
40
+ const pending = data.ideas.filter((i) => i.status === 'pending')
41
+ const converted = data.ideas.filter((i) => i.status === 'converted')
42
+ const archived = data.ideas.filter((i) => i.status === 'archived')
43
43
 
44
44
  // Brain Dump (pending)
45
45
  lines.push('## Brain Dump')
46
46
  if (pending.length > 0) {
47
- pending.forEach(idea => {
47
+ pending.forEach((idea) => {
48
48
  const date = idea.addedAt.split('T')[0]
49
- const tags = idea.tags.length > 0 ? ' ' + idea.tags.map(t => `#${t}`).join(' ') : ''
49
+ const tags = idea.tags.length > 0 ? ` ${idea.tags.map((t) => `#${t}`).join(' ')}` : ''
50
50
  const priority = idea.priority !== 'medium' ? ` [${idea.priority.toUpperCase()}]` : ''
51
51
  lines.push(`- ${idea.text}${priority} _(${date})_${tags}`)
52
52
  })
@@ -58,7 +58,7 @@ class IdeasStorage extends StorageManager<IdeasJson> {
58
58
  // Converted
59
59
  if (converted.length > 0) {
60
60
  lines.push('## Converted')
61
- converted.forEach(idea => {
61
+ converted.forEach((idea) => {
62
62
  const date = idea.addedAt.split('T')[0]
63
63
  const feat = idea.convertedTo ? ` \u2192 ${idea.convertedTo}` : ''
64
64
  lines.push(`- \u2713 ${idea.text}${feat} _(${date})_`)
@@ -69,7 +69,7 @@ class IdeasStorage extends StorageManager<IdeasJson> {
69
69
  // Archived
70
70
  if (archived.length > 0) {
71
71
  lines.push('## Archived')
72
- archived.forEach(idea => {
72
+ archived.forEach((idea) => {
73
73
  const date = idea.addedAt.split('T')[0]
74
74
  lines.push(`- ${idea.text} _(${date})_`)
75
75
  })
@@ -94,7 +94,7 @@ class IdeasStorage extends StorageManager<IdeasJson> {
94
94
  */
95
95
  async getPending(projectId: string): Promise<Idea[]> {
96
96
  const data = await this.read(projectId)
97
- return data.ideas.filter(i => i.status === 'pending')
97
+ return data.ideas.filter((i) => i.status === 'pending')
98
98
  }
99
99
 
100
100
  /**
@@ -111,19 +111,19 @@ class IdeasStorage extends StorageManager<IdeasJson> {
111
111
  status: 'pending',
112
112
  priority: options.priority || 'medium',
113
113
  tags: options.tags || [],
114
- addedAt: getTimestamp()
114
+ addedAt: getTimestamp(),
115
115
  }
116
116
 
117
117
  await this.update(projectId, (data) => ({
118
118
  ideas: [idea, ...data.ideas], // Prepend new ideas
119
- lastUpdated: getTimestamp()
119
+ lastUpdated: getTimestamp(),
120
120
  }))
121
121
 
122
122
  // Publish event
123
123
  await this.publishEvent(projectId, 'idea.created', {
124
124
  ideaId: idea.id,
125
125
  text: idea.text,
126
- priority: idea.priority
126
+ priority: idea.priority,
127
127
  })
128
128
 
129
129
  return idea
@@ -134,29 +134,23 @@ class IdeasStorage extends StorageManager<IdeasJson> {
134
134
  */
135
135
  async getById(projectId: string, id: string): Promise<Idea | undefined> {
136
136
  const data = await this.read(projectId)
137
- return data.ideas.find(i => i.id === id)
137
+ return data.ideas.find((i) => i.id === id)
138
138
  }
139
139
 
140
140
  /**
141
141
  * Convert idea to feature
142
142
  */
143
- async convertToFeature(
144
- projectId: string,
145
- ideaId: string,
146
- featureId: string
147
- ): Promise<void> {
143
+ async convertToFeature(projectId: string, ideaId: string, featureId: string): Promise<void> {
148
144
  await this.update(projectId, (data) => ({
149
- ideas: data.ideas.map(i =>
150
- i.id === ideaId
151
- ? { ...i, status: 'converted' as IdeaStatus, convertedTo: featureId }
152
- : i
145
+ ideas: data.ideas.map((i) =>
146
+ i.id === ideaId ? { ...i, status: 'converted' as IdeaStatus, convertedTo: featureId } : i
153
147
  ),
154
- lastUpdated: getTimestamp()
148
+ lastUpdated: getTimestamp(),
155
149
  }))
156
150
 
157
151
  await this.publishEvent(projectId, 'idea.converted', {
158
152
  ideaId,
159
- featureId
153
+ featureId,
160
154
  })
161
155
  }
162
156
 
@@ -165,10 +159,10 @@ class IdeasStorage extends StorageManager<IdeasJson> {
165
159
  */
166
160
  async archive(projectId: string, ideaId: string): Promise<void> {
167
161
  await this.update(projectId, (data) => ({
168
- ideas: data.ideas.map(i =>
162
+ ideas: data.ideas.map((i) =>
169
163
  i.id === ideaId ? { ...i, status: 'archived' as IdeaStatus } : i
170
164
  ),
171
- lastUpdated: getTimestamp()
165
+ lastUpdated: getTimestamp(),
172
166
  }))
173
167
 
174
168
  await this.publishEvent(projectId, 'idea.archived', { ideaId })
@@ -177,34 +171,22 @@ class IdeasStorage extends StorageManager<IdeasJson> {
177
171
  /**
178
172
  * Set priority
179
173
  */
180
- async setPriority(
181
- projectId: string,
182
- ideaId: string,
183
- priority: IdeaPriority
184
- ): Promise<void> {
174
+ async setPriority(projectId: string, ideaId: string, priority: IdeaPriority): Promise<void> {
185
175
  await this.update(projectId, (data) => ({
186
- ideas: data.ideas.map(i =>
187
- i.id === ideaId ? { ...i, priority } : i
188
- ),
189
- lastUpdated: getTimestamp()
176
+ ideas: data.ideas.map((i) => (i.id === ideaId ? { ...i, priority } : i)),
177
+ lastUpdated: getTimestamp(),
190
178
  }))
191
179
  }
192
180
 
193
181
  /**
194
182
  * Add tags to an idea
195
183
  */
196
- async addTags(
197
- projectId: string,
198
- ideaId: string,
199
- tags: string[]
200
- ): Promise<void> {
184
+ async addTags(projectId: string, ideaId: string, tags: string[]): Promise<void> {
201
185
  await this.update(projectId, (data) => ({
202
- ideas: data.ideas.map(i =>
203
- i.id === ideaId
204
- ? { ...i, tags: [...new Set([...i.tags, ...tags])] }
205
- : i
186
+ ideas: data.ideas.map((i) =>
187
+ i.id === ideaId ? { ...i, tags: [...new Set([...i.tags, ...tags])] } : i
206
188
  ),
207
- lastUpdated: getTimestamp()
189
+ lastUpdated: getTimestamp(),
208
190
  }))
209
191
  }
210
192
 
@@ -213,20 +195,22 @@ class IdeasStorage extends StorageManager<IdeasJson> {
213
195
  */
214
196
  async removeIdea(projectId: string, ideaId: string): Promise<void> {
215
197
  await this.update(projectId, (data) => ({
216
- ideas: data.ideas.filter(i => i.id !== ideaId),
217
- lastUpdated: getTimestamp()
198
+ ideas: data.ideas.filter((i) => i.id !== ideaId),
199
+ lastUpdated: getTimestamp(),
218
200
  }))
219
201
  }
220
202
 
221
203
  /**
222
204
  * Get counts by status
223
205
  */
224
- async getCounts(projectId: string): Promise<{ pending: number; converted: number; archived: number }> {
206
+ async getCounts(
207
+ projectId: string
208
+ ): Promise<{ pending: number; converted: number; archived: number }> {
225
209
  const data = await this.read(projectId)
226
210
  return {
227
- pending: data.ideas.filter(i => i.status === 'pending').length,
228
- converted: data.ideas.filter(i => i.status === 'converted').length,
229
- archived: data.ideas.filter(i => i.status === 'archived').length
211
+ pending: data.ideas.filter((i) => i.status === 'pending').length,
212
+ converted: data.ideas.filter((i) => i.status === 'converted').length,
213
+ archived: data.ideas.filter((i) => i.status === 'archived').length,
230
214
  }
231
215
  }
232
216
 
@@ -235,7 +219,7 @@ class IdeasStorage extends StorageManager<IdeasJson> {
235
219
  */
236
220
  async cleanup(projectId: string): Promise<{ removed: number }> {
237
221
  const data = await this.read(projectId)
238
- const archived = data.ideas.filter(i => i.status === 'archived')
222
+ const archived = data.ideas.filter((i) => i.status === 'archived')
239
223
 
240
224
  if (archived.length <= 50) {
241
225
  return { removed: 0 }
@@ -245,12 +229,12 @@ class IdeasStorage extends StorageManager<IdeasJson> {
245
229
  const sortedArchived = archived.sort(
246
230
  (a, b) => new Date(b.addedAt).getTime() - new Date(a.addedAt).getTime()
247
231
  )
248
- const toRemove = new Set(sortedArchived.slice(50).map(i => i.id))
232
+ const toRemove = new Set(sortedArchived.slice(50).map((i) => i.id))
249
233
  const removed = toRemove.size
250
234
 
251
235
  await this.update(projectId, (d) => ({
252
- ideas: d.ideas.filter(i => !toRemove.has(i.id)),
253
- lastUpdated: getTimestamp()
236
+ ideas: d.ideas.filter((i) => !toRemove.has(i.id)),
237
+ lastUpdated: getTimestamp(),
254
238
  }))
255
239
 
256
240
  return { removed }