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
@@ -2,80 +2,107 @@
2
2
  * Shipped Schema
3
3
  *
4
4
  * Defines the structure for shipped.json - completed/shipped items.
5
+ * Uses Zod for runtime validation and TypeScript type inference.
5
6
  * ZERO DATA LOSS - captures ALL fields from MD files.
7
+ *
8
+ * @version 2.0.0
6
9
  */
7
10
 
8
- export type ShipType = 'feature' | 'fix' | 'improvement' | 'refactor'
9
- export type CheckStatus = 'pass' | 'warning' | 'fail' | 'skipped'
10
- export type AgentType = 'fe' | 'be' | 'fe+be' | 'devops' | 'ai' | string
11
+ import { z } from 'zod'
11
12
 
12
- // Duration object for parsed time strings like "13h 38m"
13
- export interface Duration {
14
- hours: number
15
- minutes: number
16
- totalMinutes: number
17
- }
13
+ // =============================================================================
14
+ // Zod Schemas - Source of Truth
15
+ // =============================================================================
18
16
 
19
- // Code metrics from "Files: 4 | +160/-31 | Commits: 0"
20
- export interface CodeMetrics {
21
- filesChanged?: number | null
22
- linesAdded?: number | null
23
- linesRemoved?: number | null
24
- commits?: number | null
25
- }
17
+ export const ShipTypeSchema = z.enum(['feature', 'fix', 'improvement', 'refactor'])
18
+ export const CheckStatusSchema = z.enum(['pass', 'warning', 'fail', 'skipped'])
19
+ export const ChangeTypeSchema = z.enum(['added', 'changed', 'fixed', 'removed'])
26
20
 
27
- export interface ShipChange {
28
- description: string
29
- type?: 'added' | 'changed' | 'fixed' | 'removed'
30
- }
21
+ export const DurationSchema = z.object({
22
+ hours: z.number(),
23
+ minutes: z.number(),
24
+ totalMinutes: z.number(),
25
+ })
31
26
 
32
- export interface QualityMetrics {
33
- lintStatus?: CheckStatus | null
34
- lintDetails?: string
35
- testStatus?: CheckStatus | null
36
- testDetails?: string
37
- }
27
+ export const CodeMetricsSchema = z.object({
28
+ filesChanged: z.number().nullable().optional(),
29
+ linesAdded: z.number().nullable().optional(),
30
+ linesRemoved: z.number().nullable().optional(),
31
+ commits: z.number().nullable().optional(),
32
+ })
38
33
 
39
- // Git commit information
40
- export interface CommitInfo {
41
- hash?: string // "0a7bbea"
42
- message?: string // "feat(security): Multi-tenant..."
43
- branch?: string // "main"
44
- }
34
+ export const ShipChangeSchema = z.object({
35
+ description: z.string(),
36
+ type: ChangeTypeSchema.optional(),
37
+ })
45
38
 
46
- export interface ShippedItemSchema {
47
- id: string // ship_xxxxxxxx
48
- name: string
49
- version?: string | null // "0.11.6" extracted from MD
50
- type: ShipType
51
- // Agent who worked on this
52
- agent?: AgentType // "fe+be", "be", "fe"
53
- // Full description (narrative text, not just bullet points)
54
- description?: string // "CRITICAL: Multi-tenant isolation hardening..."
55
- // Changelog from bullet points
56
- changes: ShipChange[]
57
- // Code snippets if any
58
- codeSnippets?: string[] // TypeScript/code examples from MD
59
- // Git commit info
60
- commit?: CommitInfo
61
- // Enriched fields from MD
62
- codeMetrics?: CodeMetrics
63
- qualityMetrics?: QualityMetrics
64
- quantitativeImpact?: string // "81% (1,079 → 204 lines)"
65
- duration?: Duration // parsed from "13h 38m"
66
- tasksCompleted?: number | null
67
- shippedAt: string // ISO8601
68
- featureId?: string
69
- }
39
+ export const QualityMetricsSchema = z.object({
40
+ lintStatus: CheckStatusSchema.nullable().optional(),
41
+ lintDetails: z.string().optional(),
42
+ testStatus: CheckStatusSchema.nullable().optional(),
43
+ testDetails: z.string().optional(),
44
+ })
70
45
 
71
- export interface ShippedJson {
72
- items: ShippedItemSchema[]
73
- lastUpdated: string
74
- }
46
+ export const CommitInfoSchema = z.object({
47
+ hash: z.string().optional(),
48
+ message: z.string().optional(),
49
+ branch: z.string().optional(),
50
+ })
51
+
52
+ export const ShippedItemSchema = z.object({
53
+ id: z.string(), // ship_xxxxxxxx
54
+ name: z.string(),
55
+ version: z.string().nullable().optional(),
56
+ type: ShipTypeSchema,
57
+ agent: z.string().optional(), // "fe+be", "be", "fe"
58
+ description: z.string().optional(),
59
+ changes: z.array(ShipChangeSchema),
60
+ codeSnippets: z.array(z.string()).optional(),
61
+ commit: CommitInfoSchema.optional(),
62
+ codeMetrics: CodeMetricsSchema.optional(),
63
+ qualityMetrics: QualityMetricsSchema.optional(),
64
+ quantitativeImpact: z.string().optional(),
65
+ duration: DurationSchema.optional(),
66
+ tasksCompleted: z.number().nullable().optional(),
67
+ shippedAt: z.string(), // ISO8601
68
+ featureId: z.string().optional(),
69
+ })
70
+
71
+ export const ShippedJsonSchema = z.object({
72
+ items: z.array(ShippedItemSchema),
73
+ lastUpdated: z.string(),
74
+ })
75
+
76
+ // =============================================================================
77
+ // Inferred Types - Backward Compatible
78
+ // =============================================================================
79
+
80
+ export type ShipType = z.infer<typeof ShipTypeSchema>
81
+ export type CheckStatus = z.infer<typeof CheckStatusSchema>
82
+ export type AgentType = 'fe' | 'be' | 'fe+be' | 'devops' | 'ai' | string
83
+ export type Duration = z.infer<typeof DurationSchema>
84
+ export type CodeMetrics = z.infer<typeof CodeMetricsSchema>
85
+ export type ShipChange = z.infer<typeof ShipChangeSchema>
86
+ export type QualityMetrics = z.infer<typeof QualityMetricsSchema>
87
+ export type CommitInfo = z.infer<typeof CommitInfoSchema>
88
+ export type ShippedItemSchema = z.infer<typeof ShippedItemSchema>
89
+ export type ShippedJson = z.infer<typeof ShippedJsonSchema>
75
90
 
76
91
  // Legacy type for backwards compatibility
77
92
  export type ShippedSchema = ShippedItemSchema[]
78
93
 
94
+ // =============================================================================
95
+ // Validation Helpers
96
+ // =============================================================================
97
+
98
+ /** Parse and validate shipped.json content */
99
+ export const parseShipped = (data: unknown): ShippedJson => ShippedJsonSchema.parse(data)
100
+ export const safeParseShipped = (data: unknown) => ShippedJsonSchema.safeParse(data)
101
+
102
+ // =============================================================================
103
+ // Defaults
104
+ // =============================================================================
105
+
79
106
  export const DEFAULT_SHIPPED: ShippedJson = {
80
107
  items: [],
81
108
  lastUpdated: ''
@@ -4,91 +4,131 @@
4
4
  * Defines the structure for state.json - current task state.
5
5
  * Queue is now separate in queue.json.
6
6
  *
7
- * Matches json-loader.ts types exactly.
7
+ * Uses Zod for runtime validation and TypeScript type inference.
8
+ * @version 2.0.0
8
9
  */
9
10
 
10
- export type Priority = 'low' | 'medium' | 'high' | 'critical'
11
- export type TaskType = 'feature' | 'bug' | 'improvement' | 'chore'
12
- export type TaskSection = 'active' | 'backlog' | 'previously_active'
11
+ import { z } from 'zod'
13
12
 
14
- export interface CurrentTask {
15
- id: string // task_xxxxxxxx
16
- description: string
17
- startedAt: string // ISO8601
18
- sessionId: string // sess_xxxxxxxx
19
- featureId?: string // feat_xxxxxxxx
20
- }
13
+ // =============================================================================
14
+ // Zod Schemas - Source of Truth
15
+ // =============================================================================
21
16
 
22
- export interface PreviousTask {
23
- id: string
24
- description: string
25
- status: 'paused'
26
- startedAt: string // ISO8601
27
- pausedAt: string // ISO8601
28
- pauseReason?: string // Optional reason for pausing
29
- }
17
+ export const PrioritySchema = z.enum(['low', 'medium', 'high', 'critical'])
18
+ export const TaskTypeSchema = z.enum(['feature', 'bug', 'improvement', 'chore'])
19
+ export const TaskSectionSchema = z.enum(['active', 'backlog', 'previously_active'])
20
+ export const TaskStatusSchema = z.enum(['pending', 'in_progress', 'completed', 'blocked', 'paused'])
21
+ export const ActivityTypeSchema = z.enum(['task_completed', 'feature_shipped', 'idea_captured', 'session_started'])
30
22
 
31
- // StateJson is the wrapper for state.json file
32
- export interface StateJson {
33
- currentTask: CurrentTask | null
34
- previousTask?: PreviousTask | null
35
- lastUpdated: string
36
- }
23
+ export const CurrentTaskSchema = z.object({
24
+ id: z.string(), // task_xxxxxxxx
25
+ description: z.string(),
26
+ startedAt: z.string(), // ISO8601
27
+ sessionId: z.string(), // sess_xxxxxxxx
28
+ featureId: z.string().optional(), // feat_xxxxxxxx
29
+ })
30
+
31
+ export const PreviousTaskSchema = z.object({
32
+ id: z.string(),
33
+ description: z.string(),
34
+ status: z.literal('paused'),
35
+ startedAt: z.string(), // ISO8601
36
+ pausedAt: z.string(), // ISO8601
37
+ pauseReason: z.string().optional(),
38
+ })
39
+
40
+ export const StateJsonSchema = z.object({
41
+ currentTask: CurrentTaskSchema.nullable(),
42
+ previousTask: PreviousTaskSchema.nullable().optional(),
43
+ lastUpdated: z.string(),
44
+ })
37
45
 
38
- // QueueJson is the wrapper for queue.json file
39
- export interface QueueTask {
40
- id: string // task_xxxxxxxx
41
- description: string
42
- priority: Priority
43
- type: TaskType // detect from emoji 🐛=bug
44
- featureId?: string
45
- originFeature?: string // from "(from: Feature Name)" pattern
46
- completed: boolean
47
- completedAt?: string // ISO8601
48
- createdAt: string // ISO8601
49
- section: TaskSection // based on MD section
46
+ export const QueueTaskSchema = z.object({
47
+ id: z.string(), // task_xxxxxxxx
48
+ description: z.string(),
49
+ priority: PrioritySchema,
50
+ type: TaskTypeSchema, // detect from emoji 🐛=bug
51
+ featureId: z.string().optional(),
52
+ originFeature: z.string().optional(),
53
+ completed: z.boolean(),
54
+ completedAt: z.string().optional(),
55
+ createdAt: z.string(), // ISO8601
56
+ section: TaskSectionSchema,
50
57
  // Additional fields for ZERO DATA LOSS
51
- agent?: string // "fe", "be", "fe + be"
52
- groupName?: string // "Sales Reports", "Stock Audits"
53
- groupId?: string // For grouping related tasks
54
- }
58
+ agent: z.string().optional(), // "fe", "be", "fe + be"
59
+ groupName: z.string().optional(), // "Sales Reports", "Stock Audits"
60
+ groupId: z.string().optional(), // For grouping related tasks
61
+ })
55
62
 
56
- export interface QueueJson {
57
- tasks: QueueTask[]
58
- lastUpdated: string
59
- }
63
+ export const QueueJsonSchema = z.object({
64
+ tasks: z.array(QueueTaskSchema),
65
+ lastUpdated: z.string(),
66
+ })
60
67
 
61
- // Legacy types for backwards compatibility
62
- export type TaskStatus = 'pending' | 'in_progress' | 'completed' | 'blocked' | 'paused'
63
- export type ActivityType = 'task_completed' | 'feature_shipped' | 'idea_captured' | 'session_started'
68
+ export const StatsSchema = z.object({
69
+ tasksToday: z.number(),
70
+ tasksThisWeek: z.number(),
71
+ streak: z.number(),
72
+ velocity: z.string(),
73
+ avgDuration: z.string(),
74
+ })
64
75
 
65
- export interface QueuedTask extends QueueTask {} // Alias
76
+ export const RecentActivitySchema = z.object({
77
+ type: ActivityTypeSchema,
78
+ description: z.string(),
79
+ timestamp: z.string(), // ISO8601
80
+ duration: z.string().optional(),
81
+ })
66
82
 
67
- export interface Stats {
68
- tasksToday: number
69
- tasksThisWeek: number
70
- streak: number
71
- velocity: string
72
- avgDuration: string
73
- }
83
+ export const StateSchemaFull = z.object({
84
+ projectId: z.string(),
85
+ currentTask: CurrentTaskSchema.nullable(),
86
+ queue: z.array(QueueTaskSchema),
87
+ stats: StatsSchema,
88
+ recentActivity: z.array(RecentActivitySchema),
89
+ lastSync: z.string(), // ISO8601
90
+ })
74
91
 
75
- export interface RecentActivity {
76
- type: ActivityType
77
- description: string
78
- timestamp: string // ISO8601
79
- duration?: string
80
- }
92
+ // =============================================================================
93
+ // Inferred Types - Backward Compatible
94
+ // =============================================================================
81
95
 
82
- export interface StateSchema {
83
- projectId: string
84
- currentTask: CurrentTask | null
85
- queue: QueuedTask[]
86
- stats: Stats
87
- recentActivity: RecentActivity[]
88
- lastSync: string // ISO8601
89
- }
96
+ export type Priority = z.infer<typeof PrioritySchema>
97
+ export type TaskType = z.infer<typeof TaskTypeSchema>
98
+ export type TaskSection = z.infer<typeof TaskSectionSchema>
99
+ export type TaskStatus = z.infer<typeof TaskStatusSchema>
100
+ export type ActivityType = z.infer<typeof ActivityTypeSchema>
101
+
102
+ export type CurrentTask = z.infer<typeof CurrentTaskSchema>
103
+ export type PreviousTask = z.infer<typeof PreviousTaskSchema>
104
+ export type StateJson = z.infer<typeof StateJsonSchema>
105
+ export type QueueTask = z.infer<typeof QueueTaskSchema>
106
+ export type QueueJson = z.infer<typeof QueueJsonSchema>
107
+ export type Stats = z.infer<typeof StatsSchema>
108
+ export type RecentActivity = z.infer<typeof RecentActivitySchema>
109
+ export type StateSchema = z.infer<typeof StateSchemaFull>
110
+
111
+ // Legacy alias
112
+ export type QueuedTask = QueueTask
113
+
114
+ // =============================================================================
115
+ // Validation Helpers
116
+ // =============================================================================
90
117
 
118
+ /** Parse and validate state.json content */
119
+ export const parseState = (data: unknown): StateJson => StateJsonSchema.parse(data)
120
+
121
+ /** Parse and validate queue.json content */
122
+ export const parseQueue = (data: unknown): QueueJson => QueueJsonSchema.parse(data)
123
+
124
+ /** Safe parse with error result */
125
+ export const safeParseState = (data: unknown) => StateJsonSchema.safeParse(data)
126
+ export const safeParseQueue = (data: unknown) => QueueJsonSchema.safeParse(data)
127
+
128
+ // =============================================================================
91
129
  // Defaults
130
+ // =============================================================================
131
+
92
132
  export const DEFAULT_STATE: StateJson = {
93
133
  currentTask: null,
94
134
  lastUpdated: ''
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Server Module
3
+ *
4
+ * HTTP server for prjct-cli web dashboard and API.
5
+ *
6
+ * @module core/server
7
+ * @version 1.0.0
8
+ */
9
+
10
+ export { createServer, startServer, DEFAULT_PORT } from './server'
11
+ export { createRoutes } from './routes'
12
+ export { createSSEManager, SSE_EVENTS } from './sse'
13
+
14
+ // Re-export types from canonical location
15
+ export type {
16
+ ServerConfig,
17
+ ServerInstance,
18
+ SSEClient,
19
+ SSEManager,
20
+ SSEEventType,
21
+ } from '../types'
@@ -0,0 +1,165 @@
1
+ /**
2
+ * REST API Routes for prjct-cli
3
+ *
4
+ * Provides endpoints for reading and managing project state.
5
+ *
6
+ * @version 1.0.0
7
+ */
8
+
9
+ import { Hono } from 'hono'
10
+ import fs from 'fs/promises'
11
+ import path from 'path'
12
+ import * as jsonc from 'jsonc-parser'
13
+
14
+ // Storage paths relative to project data directory
15
+ const STORAGE_PATHS = {
16
+ state: 'storage/state.json',
17
+ queue: 'storage/queue.json',
18
+ ideas: 'storage/ideas.json',
19
+ shipped: 'storage/shipped.json',
20
+ roadmap: 'planning/roadmap.json',
21
+ }
22
+
23
+ /**
24
+ * Read JSON file with JSONC support
25
+ */
26
+ async function readJsonFile<T>(filePath: string): Promise<T | null> {
27
+ try {
28
+ const content = await fs.readFile(filePath, 'utf-8')
29
+ const errors: jsonc.ParseError[] = []
30
+ const result = jsonc.parse(content, errors)
31
+ return errors.length > 0 ? null : result
32
+ } catch {
33
+ return null
34
+ }
35
+ }
36
+
37
+ /**
38
+ * Write JSON file
39
+ */
40
+ async function writeJsonFile(filePath: string, data: unknown): Promise<boolean> {
41
+ try {
42
+ await fs.mkdir(path.dirname(filePath), { recursive: true })
43
+ await fs.writeFile(filePath, JSON.stringify(data, null, 2) + '\n', 'utf-8')
44
+ return true
45
+ } catch {
46
+ return false
47
+ }
48
+ }
49
+
50
+ /**
51
+ * Get global project path
52
+ */
53
+ function getProjectDataPath(projectId: string): string {
54
+ const homeDir = process.env.HOME || process.env.USERPROFILE || '~'
55
+ return path.join(homeDir, '.prjct-cli', 'projects', projectId)
56
+ }
57
+
58
+ /**
59
+ * Create API routes for a project
60
+ */
61
+ export function createRoutes(projectId: string, _projectPath: string): Hono {
62
+ const api = new Hono()
63
+ const dataPath = getProjectDataPath(projectId)
64
+
65
+ // GET /state - Current task state
66
+ api.get('/state', async (c) => {
67
+ const data = await readJsonFile(path.join(dataPath, STORAGE_PATHS.state))
68
+ if (!data) {
69
+ return c.json({ currentTask: null, lastUpdated: '' })
70
+ }
71
+ return c.json(data)
72
+ })
73
+
74
+ // GET /queue - Task queue
75
+ api.get('/queue', async (c) => {
76
+ const data = await readJsonFile(path.join(dataPath, STORAGE_PATHS.queue))
77
+ if (!data) {
78
+ return c.json({ tasks: [], lastUpdated: '' })
79
+ }
80
+ return c.json(data)
81
+ })
82
+
83
+ // GET /ideas - Ideas backlog
84
+ api.get('/ideas', async (c) => {
85
+ const data = await readJsonFile(path.join(dataPath, STORAGE_PATHS.ideas))
86
+ if (!data) {
87
+ return c.json({ ideas: [], lastUpdated: '' })
88
+ }
89
+ return c.json(data)
90
+ })
91
+
92
+ // GET /roadmap - Feature roadmap
93
+ api.get('/roadmap', async (c) => {
94
+ const data = await readJsonFile(path.join(dataPath, STORAGE_PATHS.roadmap))
95
+ if (!data) {
96
+ return c.json({ features: [], backlog: [], lastUpdated: '' })
97
+ }
98
+ return c.json(data)
99
+ })
100
+
101
+ // GET /shipped - Shipped items
102
+ api.get('/shipped', async (c) => {
103
+ const data = await readJsonFile(path.join(dataPath, STORAGE_PATHS.shipped))
104
+ if (!data) {
105
+ return c.json({ items: [], lastUpdated: '' })
106
+ }
107
+ return c.json(data)
108
+ })
109
+
110
+ // GET /dashboard - Combined dashboard data
111
+ api.get('/dashboard', async (c) => {
112
+ const [state, queue, ideas, roadmap, shipped] = await Promise.all([
113
+ readJsonFile(path.join(dataPath, STORAGE_PATHS.state)),
114
+ readJsonFile(path.join(dataPath, STORAGE_PATHS.queue)),
115
+ readJsonFile(path.join(dataPath, STORAGE_PATHS.ideas)),
116
+ readJsonFile(path.join(dataPath, STORAGE_PATHS.roadmap)),
117
+ readJsonFile(path.join(dataPath, STORAGE_PATHS.shipped)),
118
+ ])
119
+
120
+ return c.json({
121
+ projectId,
122
+ state: state || { currentTask: null, lastUpdated: '' },
123
+ queue: queue || { tasks: [], lastUpdated: '' },
124
+ ideas: ideas || { ideas: [], lastUpdated: '' },
125
+ roadmap: roadmap || { features: [], backlog: [], lastUpdated: '' },
126
+ shipped: shipped || { items: [], lastUpdated: '' },
127
+ timestamp: new Date().toISOString(),
128
+ })
129
+ })
130
+
131
+ // POST /state - Update state (for future use)
132
+ api.post('/state', async (c) => {
133
+ try {
134
+ const body = await c.req.json()
135
+ const filePath = path.join(dataPath, STORAGE_PATHS.state)
136
+ const success = await writeJsonFile(filePath, body)
137
+ if (success) {
138
+ return c.json({ success: true })
139
+ }
140
+ return c.json({ success: false, error: 'Failed to write' }, 500)
141
+ } catch (e) {
142
+ return c.json({ success: false, error: String(e) }, 400)
143
+ }
144
+ })
145
+
146
+ // GET /context - Read context markdown files
147
+ api.get('/context/:name', async (c) => {
148
+ const name = c.req.param('name')
149
+ const allowedFiles = ['now', 'next', 'ideas', 'shipped']
150
+
151
+ if (!allowedFiles.includes(name)) {
152
+ return c.json({ error: 'Invalid context file' }, 400)
153
+ }
154
+
155
+ try {
156
+ const filePath = path.join(dataPath, 'context', `${name}.md`)
157
+ const content = await fs.readFile(filePath, 'utf-8')
158
+ return c.text(content, 200, { 'Content-Type': 'text/markdown' })
159
+ } catch {
160
+ return c.text('', 200, { 'Content-Type': 'text/markdown' })
161
+ }
162
+ })
163
+
164
+ return api
165
+ }