prjct-cli 0.20.0 → 0.20.1

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 (226) 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/outcomes/analyzer.ts +7 -41
  58. package/core/outcomes/index.ts +1 -1
  59. package/core/outcomes/recorder.ts +1 -1
  60. package/core/{plugins → plugin/builtin}/webhook.ts +6 -22
  61. package/core/plugin/loader.ts +5 -5
  62. package/core/plugin/registry.ts +2 -2
  63. package/core/schemas/ideas.ts +85 -54
  64. package/core/schemas/index.ts +14 -33
  65. package/core/schemas/permissions.ts +177 -0
  66. package/core/schemas/project.ts +39 -12
  67. package/core/schemas/roadmap.ts +94 -59
  68. package/core/schemas/schemas.ts +39 -0
  69. package/core/schemas/shipped.ts +87 -60
  70. package/core/schemas/state.ts +110 -70
  71. package/core/server/index.ts +21 -0
  72. package/core/server/routes.ts +165 -0
  73. package/core/server/server.ts +136 -0
  74. package/core/server/sse.ts +135 -0
  75. package/core/services/agent-service.ts +170 -0
  76. package/core/services/breakdown-service.ts +126 -0
  77. package/core/services/index.ts +21 -0
  78. package/core/services/memory-service.ts +108 -0
  79. package/core/services/project-service.ts +146 -0
  80. package/core/services/skill-service.ts +253 -0
  81. package/core/session/compaction.ts +257 -0
  82. package/core/session/index.ts +20 -8
  83. package/core/{infrastructure/session-manager/migration.ts → session/log-migration.ts} +9 -9
  84. package/core/{infrastructure/session-manager/session-manager.ts → session/session-log-manager.ts} +27 -26
  85. package/core/session/{session-manager.ts → task-session-manager.ts} +7 -4
  86. package/core/session/utils.ts +1 -1
  87. package/core/storage/ideas-storage.ts +10 -26
  88. package/core/storage/index.ts +14 -162
  89. package/core/storage/queue-storage.ts +13 -11
  90. package/core/storage/shipped-storage.ts +4 -17
  91. package/core/storage/state-storage.ts +35 -43
  92. package/core/storage/storage-manager.ts +42 -52
  93. package/core/storage/storage.ts +160 -0
  94. package/core/sync/auth-config.ts +1 -8
  95. package/core/sync/index.ts +17 -10
  96. package/core/sync/oauth-handler.ts +1 -6
  97. package/core/sync/sync-client.ts +6 -34
  98. package/core/sync/sync-manager.ts +11 -40
  99. package/core/types/agentic.ts +577 -0
  100. package/core/types/agents.ts +145 -0
  101. package/core/types/bus.ts +82 -0
  102. package/core/types/commands.ts +366 -0
  103. package/core/types/config.ts +66 -0
  104. package/core/types/core.ts +96 -0
  105. package/core/types/domain.ts +71 -0
  106. package/core/types/events.ts +42 -0
  107. package/core/types/fs.ts +56 -0
  108. package/core/types/index.ts +387 -500
  109. package/core/types/infrastructure.ts +196 -0
  110. package/core/{agentic/memory-system/types.ts → types/memory.ts} +33 -8
  111. package/core/{outcomes/types.ts → types/outcomes.ts} +53 -8
  112. package/core/types/plugin.ts +25 -0
  113. package/core/types/server.ts +54 -0
  114. package/core/types/services.ts +65 -0
  115. package/core/types/session.ts +135 -0
  116. package/core/types/storage.ts +148 -0
  117. package/core/types/sync.ts +121 -0
  118. package/core/types/task.ts +72 -0
  119. package/core/types/template.ts +24 -0
  120. package/core/types/utils.ts +90 -0
  121. package/core/utils/cache.ts +195 -0
  122. package/core/utils/collection-filters.ts +245 -0
  123. package/core/utils/date-helper.ts +1 -5
  124. package/core/utils/file-helper.ts +20 -10
  125. package/core/utils/jsonl-helper.ts +5 -8
  126. package/core/utils/markdown-builder.ts +277 -0
  127. package/core/utils/project-commands.ts +132 -0
  128. package/core/utils/runtime.ts +119 -0
  129. package/dist/bin/prjct.mjs +12568 -0
  130. package/package.json +13 -8
  131. package/scripts/build.js +106 -0
  132. package/scripts/postinstall.js +50 -8
  133. package/templates/agentic/subagent-generation.md +1 -1
  134. package/templates/commands/serve.md +118 -0
  135. package/templates/commands/ship.md +13 -2
  136. package/templates/commands/skill.md +110 -0
  137. package/templates/commands/sync.md +1 -1
  138. package/templates/commands/test.md +23 -4
  139. package/templates/permissions/default.jsonc +60 -0
  140. package/templates/permissions/permissive.jsonc +49 -0
  141. package/templates/permissions/strict.jsonc +62 -0
  142. package/templates/skills/code-review.md +47 -0
  143. package/templates/skills/debug.md +61 -0
  144. package/templates/skills/refactor.md +47 -0
  145. package/templates/subagents/domain/devops.md +1 -1
  146. package/templates/subagents/domain/testing.md +6 -10
  147. package/templates/subagents/workflow/prjct-shipper.md +16 -7
  148. package/templates/tools/bash.txt +22 -0
  149. package/templates/tools/edit.txt +18 -0
  150. package/templates/tools/glob.txt +19 -0
  151. package/templates/tools/grep.txt +21 -0
  152. package/templates/tools/read.txt +14 -0
  153. package/templates/tools/task.txt +20 -0
  154. package/templates/tools/webfetch.txt +16 -0
  155. package/templates/tools/websearch.txt +18 -0
  156. package/templates/tools/write.txt +17 -0
  157. package/core/agentic/command-executor/command-executor.ts +0 -312
  158. package/core/agentic/command-executor/index.ts +0 -16
  159. package/core/agentic/command-executor/status-signal.ts +0 -38
  160. package/core/agentic/command-executor/types.ts +0 -79
  161. package/core/agentic/ground-truth/index.ts +0 -76
  162. package/core/agentic/ground-truth/types.ts +0 -33
  163. package/core/agentic/ground-truth/utils.ts +0 -48
  164. package/core/agentic/ground-truth/verifiers/analyze.ts +0 -54
  165. package/core/agentic/ground-truth/verifiers/done.ts +0 -75
  166. package/core/agentic/ground-truth/verifiers/feature.ts +0 -70
  167. package/core/agentic/ground-truth/verifiers/index.ts +0 -37
  168. package/core/agentic/ground-truth/verifiers/init.ts +0 -52
  169. package/core/agentic/ground-truth/verifiers/now.ts +0 -57
  170. package/core/agentic/ground-truth/verifiers/ship.ts +0 -85
  171. package/core/agentic/ground-truth/verifiers/spec.ts +0 -45
  172. package/core/agentic/ground-truth/verifiers/sync.ts +0 -47
  173. package/core/agentic/ground-truth/verifiers.ts +0 -6
  174. package/core/agentic/loop-detector/error-analysis.ts +0 -97
  175. package/core/agentic/loop-detector/hallucination.ts +0 -71
  176. package/core/agentic/loop-detector/index.ts +0 -41
  177. package/core/agentic/loop-detector/loop-detector.ts +0 -222
  178. package/core/agentic/loop-detector/types.ts +0 -66
  179. package/core/agentic/memory-system/history.ts +0 -53
  180. package/core/agentic/memory-system/index.ts +0 -192
  181. package/core/agentic/memory-system/patterns.ts +0 -156
  182. package/core/agentic/memory-system/semantic-memories.ts +0 -278
  183. package/core/agentic/memory-system/session.ts +0 -21
  184. package/core/agentic/plan-mode/approval.ts +0 -57
  185. package/core/agentic/plan-mode/constants.ts +0 -44
  186. package/core/agentic/plan-mode/index.ts +0 -28
  187. package/core/agentic/plan-mode/plan-mode.ts +0 -407
  188. package/core/agentic/plan-mode/types.ts +0 -193
  189. package/core/agents/types.ts +0 -126
  190. package/core/command-registry/categories.ts +0 -23
  191. package/core/command-registry/commands.ts +0 -15
  192. package/core/command-registry/core-commands.ts +0 -344
  193. package/core/command-registry/index.ts +0 -158
  194. package/core/command-registry/optional-commands.ts +0 -163
  195. package/core/command-registry/setup-commands.ts +0 -83
  196. package/core/command-registry/types.ts +0 -59
  197. package/core/command-registry.ts +0 -9
  198. package/core/commands/types.ts +0 -185
  199. package/core/commands.ts +0 -11
  200. package/core/constants/formats.ts +0 -187
  201. package/core/context-sync.ts +0 -18
  202. package/core/data/index.ts +0 -27
  203. package/core/data/md-base-manager.ts +0 -203
  204. package/core/data/md-ideas-manager.ts +0 -155
  205. package/core/data/md-queue-manager.ts +0 -180
  206. package/core/data/md-shipped-manager.ts +0 -90
  207. package/core/data/md-state-manager.ts +0 -137
  208. package/core/domain/task-stack/index.ts +0 -19
  209. package/core/domain/task-stack/parser.ts +0 -86
  210. package/core/domain/task-stack/storage.ts +0 -123
  211. package/core/domain/task-stack/task-stack.ts +0 -340
  212. package/core/domain/task-stack/types.ts +0 -51
  213. package/core/infrastructure/command-installer/command-installer.ts +0 -327
  214. package/core/infrastructure/command-installer/global-config.ts +0 -136
  215. package/core/infrastructure/command-installer/index.ts +0 -25
  216. package/core/infrastructure/command-installer/types.ts +0 -41
  217. package/core/infrastructure/session-manager/index.ts +0 -23
  218. package/core/infrastructure/session-manager/types.ts +0 -45
  219. package/core/infrastructure/session-manager.ts +0 -8
  220. package/core/serializers/ideas-serializer.ts +0 -187
  221. package/core/serializers/index.ts +0 -36
  222. package/core/serializers/queue-serializer.ts +0 -210
  223. package/core/serializers/shipped-serializer.ts +0 -108
  224. package/core/serializers/state-serializer.ts +0 -136
  225. package/core/session/types.ts +0 -29
  226. /package/core/infrastructure/{agents/claude-agent.ts → claude-agent.ts} +0 -0
@@ -1,203 +0,0 @@
1
- /**
2
- * MD Base Manager
3
- *
4
- * Abstract base class for MD file managers.
5
- * MD-First Architecture: MD is the source of truth.
6
- *
7
- * Each concrete manager must implement:
8
- * - parse(content: string): T - Convert MD to schema
9
- * - serialize(data: T): string - Convert schema to MD
10
- * - getDefault(projectId: string): T - Default value when file doesn't exist
11
- */
12
-
13
- import path from 'path'
14
- import fs from 'fs/promises'
15
- import * as fileHelper from '../utils/file-helper'
16
- import pathManager from '../infrastructure/path-manager'
17
-
18
- export abstract class MdBaseManager<T> {
19
- protected filename: string
20
- protected cache: Map<string, T> = new Map()
21
- protected cacheTimeout = 5000 // 5 seconds
22
- protected lastRead: Map<string, number> = new Map()
23
-
24
- constructor(filename: string) {
25
- this.filename = filename
26
- }
27
-
28
- /**
29
- * Get file path for a project.
30
- */
31
- protected getFilePath(projectId: string): string {
32
- const globalPath = pathManager.getGlobalProjectPath(projectId)
33
- return path.join(globalPath, this.filename)
34
- }
35
-
36
- /**
37
- * Get default value for this data type.
38
- */
39
- protected abstract getDefault(projectId: string): T
40
-
41
- /**
42
- * Parse MD content to schema.
43
- */
44
- protected abstract parse(content: string): T
45
-
46
- /**
47
- * Serialize schema to MD content.
48
- */
49
- protected abstract serialize(data: T): string
50
-
51
- /**
52
- * Read data from MD file.
53
- */
54
- async read(projectId: string): Promise<T> {
55
- const now = Date.now()
56
- const lastReadTime = this.lastRead.get(projectId) || 0
57
-
58
- // Return cached if fresh
59
- if (now - lastReadTime < this.cacheTimeout && this.cache.has(projectId)) {
60
- return this.cache.get(projectId)!
61
- }
62
-
63
- const filePath = this.getFilePath(projectId)
64
- const content = await fileHelper.readFile(filePath, '')
65
-
66
- const data = content.trim() ? this.parse(content) : this.getDefault(projectId)
67
-
68
- // Update cache
69
- this.cache.set(projectId, data)
70
- this.lastRead.set(projectId, now)
71
-
72
- return data
73
- }
74
-
75
- /**
76
- * Write data to MD file using atomic write (prevents partial writes).
77
- */
78
- async write(projectId: string, data: T): Promise<void> {
79
- const filePath = this.getFilePath(projectId)
80
- const content = this.serialize(data)
81
-
82
- // Ensure directory exists
83
- await fileHelper.ensureDir(path.dirname(filePath))
84
-
85
- // Atomic write: write to temp file, then rename
86
- const tempPath = `${filePath}.${Date.now()}.tmp`
87
- await fs.writeFile(tempPath, content, 'utf-8')
88
- await fs.rename(tempPath, filePath)
89
-
90
- // Update cache
91
- this.cache.set(projectId, data)
92
- this.lastRead.set(projectId, Date.now())
93
- }
94
-
95
- /**
96
- * Check if file exists.
97
- */
98
- async exists(projectId: string): Promise<boolean> {
99
- const filePath = this.getFilePath(projectId)
100
- return fileHelper.fileExists(filePath)
101
- }
102
-
103
- /**
104
- * Initialize with default data.
105
- */
106
- async initialize(projectId: string): Promise<T> {
107
- const data = this.getDefault(projectId)
108
- await this.write(projectId, data)
109
- return data
110
- }
111
-
112
- /**
113
- * Clear cache.
114
- */
115
- clearCache(projectId?: string): void {
116
- if (projectId) {
117
- this.cache.delete(projectId)
118
- this.lastRead.delete(projectId)
119
- } else {
120
- this.cache.clear()
121
- this.lastRead.clear()
122
- }
123
- }
124
-
125
- /**
126
- * Update data with an updater function (read-modify-write).
127
- */
128
- async update(projectId: string, updater: (data: T) => T): Promise<T> {
129
- const current = await this.read(projectId)
130
- const updated = updater(current)
131
- await this.write(projectId, updated)
132
- return updated
133
- }
134
-
135
- /**
136
- * Get raw MD content without parsing.
137
- */
138
- async readRaw(projectId: string): Promise<string> {
139
- const filePath = this.getFilePath(projectId)
140
- return fileHelper.readFile(filePath, '')
141
- }
142
-
143
- /**
144
- * Write raw MD content without serialization.
145
- */
146
- async writeRaw(projectId: string, content: string): Promise<void> {
147
- const filePath = this.getFilePath(projectId)
148
- await fileHelper.ensureDir(path.dirname(filePath))
149
- await fileHelper.atomicWrite(filePath, content)
150
- this.clearCache(projectId)
151
- }
152
- }
153
-
154
- /**
155
- * Base manager for array-based MD files (like shipped, ideas).
156
- */
157
- export abstract class MdArrayManager<T> extends MdBaseManager<T[]> {
158
- protected getDefault(): T[] {
159
- return []
160
- }
161
-
162
- /**
163
- * Add item to array.
164
- */
165
- async add(projectId: string, item: T): Promise<T[]> {
166
- return this.update(projectId, (data) => [...data, item])
167
- }
168
-
169
- /**
170
- * Prepend item to array (add at beginning).
171
- */
172
- async prepend(projectId: string, item: T): Promise<T[]> {
173
- return this.update(projectId, (data) => [item, ...data])
174
- }
175
-
176
- /**
177
- * Remove item by predicate.
178
- */
179
- async remove(projectId: string, predicate: (item: T) => boolean): Promise<T[]> {
180
- return this.update(projectId, (data) => data.filter((item) => !predicate(item)))
181
- }
182
-
183
- /**
184
- * Find item by predicate.
185
- */
186
- async find(projectId: string, predicate: (item: T) => boolean): Promise<T | undefined> {
187
- const data = await this.read(projectId)
188
- return data.find(predicate)
189
- }
190
-
191
- /**
192
- * Update item by predicate.
193
- */
194
- async updateItem(
195
- projectId: string,
196
- predicate: (item: T) => boolean,
197
- updater: (item: T) => T
198
- ): Promise<T[]> {
199
- return this.update(projectId, (data) =>
200
- data.map((item) => (predicate(item) ? updater(item) : item))
201
- )
202
- }
203
- }
@@ -1,155 +0,0 @@
1
- /**
2
- * MD Ideas Manager
3
- *
4
- * MD-First Architecture: Manages ideas via ideas.md.
5
- * Source of truth is the markdown file, not JSON.
6
- */
7
-
8
- import { MdArrayManager } from './md-base-manager'
9
- import { parseIdeas, serializeIdeas, type Idea, type IdeaStatus, type IdeaPriority } from '../serializers/ideas-serializer'
10
- import { generateUUID } from '../schemas'
11
-
12
- class MdIdeasManager extends MdArrayManager<Idea> {
13
- constructor() {
14
- super('planning/ideas.md')
15
- }
16
-
17
- protected parse(content: string): Idea[] {
18
- return parseIdeas(content)
19
- }
20
-
21
- protected serialize(data: Idea[]): string {
22
- return serializeIdeas(data)
23
- }
24
-
25
- // =========== Ideas Operations ===========
26
-
27
- /**
28
- * Add a new idea
29
- */
30
- async addIdea(
31
- projectId: string,
32
- text: string,
33
- options?: { tags?: string[]; priority?: IdeaPriority }
34
- ): Promise<Idea[]> {
35
- const idea: Idea = {
36
- id: generateUUID(),
37
- text,
38
- status: 'pending',
39
- priority: options?.priority || 'medium',
40
- tags: options?.tags || [],
41
- addedAt: new Date().toISOString()
42
- }
43
-
44
- return this.add(projectId, idea)
45
- }
46
-
47
- /**
48
- * Get all ideas
49
- */
50
- async getAll(projectId: string): Promise<Idea[]> {
51
- return this.read(projectId)
52
- }
53
-
54
- /**
55
- * Get pending ideas
56
- */
57
- async getPending(projectId: string): Promise<Idea[]> {
58
- const all = await this.read(projectId)
59
- return all.filter(i => i.status === 'pending')
60
- }
61
-
62
- /**
63
- * Get idea by ID
64
- */
65
- async getById(projectId: string, id: string): Promise<Idea | undefined> {
66
- return this.find(projectId, i => i.id === id)
67
- }
68
-
69
- /**
70
- * Convert idea to feature
71
- */
72
- async convertToFeature(projectId: string, id: string, featureId: string): Promise<Idea[]> {
73
- return this.updateItem(
74
- projectId,
75
- i => i.id === id,
76
- i => ({ ...i, status: 'converted' as IdeaStatus, convertedTo: featureId })
77
- )
78
- }
79
-
80
- /**
81
- * Archive an idea
82
- */
83
- async archive(projectId: string, id: string): Promise<Idea[]> {
84
- return this.updateItem(
85
- projectId,
86
- i => i.id === id,
87
- i => ({ ...i, status: 'archived' as IdeaStatus })
88
- )
89
- }
90
-
91
- /**
92
- * Update idea priority
93
- */
94
- async setPriority(projectId: string, id: string, priority: IdeaPriority): Promise<Idea[]> {
95
- return this.updateItem(
96
- projectId,
97
- i => i.id === id,
98
- i => ({ ...i, priority })
99
- )
100
- }
101
-
102
- /**
103
- * Add tags to an idea
104
- */
105
- async addTags(projectId: string, id: string, tags: string[]): Promise<Idea[]> {
106
- return this.updateItem(
107
- projectId,
108
- i => i.id === id,
109
- i => ({ ...i, tags: [...new Set([...i.tags, ...tags])] })
110
- )
111
- }
112
-
113
- /**
114
- * Remove an idea
115
- */
116
- async removeIdea(projectId: string, id: string): Promise<Idea[]> {
117
- return this.remove(projectId, i => i.id === id)
118
- }
119
-
120
- /**
121
- * Get ideas count by status
122
- */
123
- async getCounts(projectId: string): Promise<{ pending: number; converted: number; archived: number }> {
124
- const all = await this.read(projectId)
125
- return {
126
- pending: all.filter(i => i.status === 'pending').length,
127
- converted: all.filter(i => i.status === 'converted').length,
128
- archived: all.filter(i => i.status === 'archived').length
129
- }
130
- }
131
-
132
- /**
133
- * Clean up empty sections (remove archived ideas older than 30 days)
134
- */
135
- async cleanup(projectId: string): Promise<{ removed: number }> {
136
- const thirtyDaysAgo = new Date()
137
- thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30)
138
-
139
- const all = await this.read(projectId)
140
- const toKeep = all.filter(i => {
141
- if (i.status !== 'archived') return true
142
- return new Date(i.addedAt) > thirtyDaysAgo
143
- })
144
-
145
- const removed = all.length - toKeep.length
146
- if (removed > 0) {
147
- await this.write(projectId, toKeep)
148
- }
149
-
150
- return { removed }
151
- }
152
- }
153
-
154
- export const mdIdeasManager = new MdIdeasManager()
155
- export default mdIdeasManager
@@ -1,180 +0,0 @@
1
- /**
2
- * MD Queue Manager
3
- *
4
- * MD-First Architecture: Manages queue via next.md.
5
- * Source of truth is the markdown file, not JSON.
6
- */
7
-
8
- import { MdBaseManager } from './md-base-manager'
9
- import { parseQueue, serializeQueue } from '../serializers'
10
- import { generateUUID } from '../schemas'
11
- import type { QueueJson, QueueTask, Priority, TaskType, TaskSection } from '../schemas/state'
12
-
13
- class MdQueueManager extends MdBaseManager<QueueJson> {
14
- constructor() {
15
- super('core/next.md')
16
- }
17
-
18
- protected getDefault(): QueueJson {
19
- return {
20
- tasks: [],
21
- lastUpdated: new Date().toISOString().split('T')[0]
22
- }
23
- }
24
-
25
- protected parse(content: string): QueueJson {
26
- return parseQueue(content)
27
- }
28
-
29
- protected serialize(data: QueueJson): string {
30
- return serializeQueue(data)
31
- }
32
-
33
- // =========== Queue Operations ===========
34
-
35
- async getTasks(projectId: string): Promise<QueueTask[]> {
36
- const queue = await this.read(projectId)
37
- return queue.tasks
38
- }
39
-
40
- async getActiveTasks(projectId: string): Promise<QueueTask[]> {
41
- const queue = await this.read(projectId)
42
- return queue.tasks.filter(t => t.section === 'active' && !t.completed)
43
- }
44
-
45
- async getBacklog(projectId: string): Promise<QueueTask[]> {
46
- const queue = await this.read(projectId)
47
- return queue.tasks.filter(t => t.section === 'backlog' && !t.completed)
48
- }
49
-
50
- async addTask(
51
- projectId: string,
52
- task: Omit<QueueTask, 'id' | 'createdAt' | 'completed'>
53
- ): Promise<QueueJson> {
54
- const newTask: QueueTask = {
55
- ...task,
56
- id: generateUUID(),
57
- createdAt: new Date().toISOString(),
58
- completed: false
59
- }
60
-
61
- return this.update(projectId, (queue) => ({
62
- tasks: this.sortTasks([...queue.tasks, newTask]),
63
- lastUpdated: new Date().toISOString().split('T')[0]
64
- }))
65
- }
66
-
67
- async removeTask(projectId: string, taskId: string): Promise<QueueJson> {
68
- return this.update(projectId, (queue) => ({
69
- tasks: queue.tasks.filter(t => t.id !== taskId),
70
- lastUpdated: new Date().toISOString().split('T')[0]
71
- }))
72
- }
73
-
74
- async completeTask(projectId: string, taskId: string): Promise<QueueJson> {
75
- return this.update(projectId, (queue) => ({
76
- tasks: queue.tasks.map(t =>
77
- t.id === taskId
78
- ? { ...t, completed: true, completedAt: new Date().toISOString() }
79
- : t
80
- ),
81
- lastUpdated: new Date().toISOString().split('T')[0]
82
- }))
83
- }
84
-
85
- async getNextTask(projectId: string): Promise<QueueTask | null> {
86
- const queue = await this.read(projectId)
87
- return queue.tasks.find(t => t.section === 'active' && !t.completed) || null
88
- }
89
-
90
- async moveToSection(
91
- projectId: string,
92
- taskId: string,
93
- section: TaskSection
94
- ): Promise<QueueJson> {
95
- return this.update(projectId, (queue) => ({
96
- tasks: queue.tasks.map(t =>
97
- t.id === taskId ? { ...t, section } : t
98
- ),
99
- lastUpdated: new Date().toISOString().split('T')[0]
100
- }))
101
- }
102
-
103
- async setPriority(
104
- projectId: string,
105
- taskId: string,
106
- priority: Priority
107
- ): Promise<QueueJson> {
108
- return this.update(projectId, (queue) => ({
109
- tasks: this.sortTasks(
110
- queue.tasks.map(t => t.id === taskId ? { ...t, priority } : t)
111
- ),
112
- lastUpdated: new Date().toISOString().split('T')[0]
113
- }))
114
- }
115
-
116
- /**
117
- * Add multiple tasks at once (e.g., from a feature breakdown)
118
- */
119
- async addTasks(
120
- projectId: string,
121
- tasks: Array<Omit<QueueTask, 'id' | 'createdAt' | 'completed'>>
122
- ): Promise<QueueJson> {
123
- const newTasks: QueueTask[] = tasks.map((task) => ({
124
- ...task,
125
- id: generateUUID(),
126
- createdAt: new Date().toISOString(),
127
- completed: false
128
- }))
129
-
130
- return this.update(projectId, (queue) => ({
131
- tasks: this.sortTasks([...queue.tasks, ...newTasks]),
132
- lastUpdated: new Date().toISOString().split('T')[0]
133
- }))
134
- }
135
-
136
- /**
137
- * Clear completed tasks
138
- */
139
- async clearCompleted(projectId: string): Promise<QueueJson> {
140
- return this.update(projectId, (queue) => ({
141
- tasks: queue.tasks.filter(t => !t.completed),
142
- lastUpdated: new Date().toISOString().split('T')[0]
143
- }))
144
- }
145
-
146
- /**
147
- * Sort tasks by priority then by creation date
148
- */
149
- private sortTasks(tasks: QueueTask[]): QueueTask[] {
150
- const priorityOrder: Record<Priority, number> = {
151
- critical: 0,
152
- high: 1,
153
- medium: 2,
154
- low: 3
155
- }
156
-
157
- return tasks.sort((a, b) => {
158
- // First by section (active first)
159
- const sectionOrder: Record<TaskSection, number> = {
160
- active: 0,
161
- previously_active: 1,
162
- backlog: 2
163
- }
164
- if (sectionOrder[a.section] !== sectionOrder[b.section]) {
165
- return sectionOrder[a.section] - sectionOrder[b.section]
166
- }
167
-
168
- // Then by priority
169
- if (priorityOrder[a.priority] !== priorityOrder[b.priority]) {
170
- return priorityOrder[a.priority] - priorityOrder[b.priority]
171
- }
172
-
173
- // Finally by creation date
174
- return new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime()
175
- })
176
- }
177
- }
178
-
179
- export const mdQueueManager = new MdQueueManager()
180
- export default mdQueueManager
@@ -1,90 +0,0 @@
1
- /**
2
- * MD Shipped Manager
3
- *
4
- * MD-First Architecture: Manages shipped features via shipped.md.
5
- * Source of truth is the markdown file, not JSON.
6
- */
7
-
8
- import { MdArrayManager } from './md-base-manager'
9
- import { parseShipped, serializeShipped, type ShippedFeature } from '../serializers/shipped-serializer'
10
- import { generateUUID } from '../schemas'
11
-
12
- class MdShippedManager extends MdArrayManager<ShippedFeature> {
13
- constructor() {
14
- super('progress/shipped.md')
15
- }
16
-
17
- protected parse(content: string): ShippedFeature[] {
18
- return parseShipped(content)
19
- }
20
-
21
- protected serialize(data: ShippedFeature[]): string {
22
- return serializeShipped(data)
23
- }
24
-
25
- // =========== Shipped Features ===========
26
-
27
- /**
28
- * Add a shipped feature
29
- */
30
- async addShipped(
31
- projectId: string,
32
- feature: Omit<ShippedFeature, 'id' | 'shippedAt'>
33
- ): Promise<ShippedFeature[]> {
34
- const shippedFeature: ShippedFeature = {
35
- ...feature,
36
- id: generateUUID(),
37
- shippedAt: new Date().toISOString()
38
- }
39
-
40
- return this.prepend(projectId, shippedFeature)
41
- }
42
-
43
- /**
44
- * Get all shipped features
45
- */
46
- async getAll(projectId: string): Promise<ShippedFeature[]> {
47
- return this.read(projectId)
48
- }
49
-
50
- /**
51
- * Get recent shipped features (last N)
52
- */
53
- async getRecent(projectId: string, limit: number = 5): Promise<ShippedFeature[]> {
54
- const all = await this.read(projectId)
55
- return all.slice(0, limit)
56
- }
57
-
58
- /**
59
- * Get shipped features by version
60
- */
61
- async getByVersion(projectId: string, version: string): Promise<ShippedFeature | undefined> {
62
- return this.find(projectId, (f) => f.version === version)
63
- }
64
-
65
- /**
66
- * Get shipped features count
67
- */
68
- async getCount(projectId: string): Promise<number> {
69
- const all = await this.read(projectId)
70
- return all.length
71
- }
72
-
73
- /**
74
- * Get shipped features for a date range
75
- */
76
- async getByDateRange(
77
- projectId: string,
78
- startDate: Date,
79
- endDate: Date
80
- ): Promise<ShippedFeature[]> {
81
- const all = await this.read(projectId)
82
- return all.filter((f) => {
83
- const date = new Date(f.shippedAt)
84
- return date >= startDate && date <= endDate
85
- })
86
- }
87
- }
88
-
89
- export const mdShippedManager = new MdShippedManager()
90
- export default mdShippedManager