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.
- package/CHANGELOG.md +82 -0
- package/bin/prjct.ts +117 -10
- package/core/__tests__/agentic/memory-system.test.ts +39 -26
- package/core/__tests__/agentic/plan-mode.test.ts +64 -46
- package/core/__tests__/agentic/prompt-builder.test.ts +14 -14
- package/core/__tests__/services/project-index.test.ts +353 -0
- package/core/__tests__/types/fs.test.ts +3 -3
- package/core/__tests__/utils/date-helper.test.ts +10 -10
- package/core/__tests__/utils/output.test.ts +9 -6
- package/core/__tests__/utils/project-commands.test.ts +5 -6
- package/core/agentic/agent-router.ts +9 -10
- package/core/agentic/chain-of-thought.ts +16 -4
- package/core/agentic/command-executor.ts +66 -40
- package/core/agentic/context-builder.ts +8 -5
- package/core/agentic/ground-truth.ts +15 -9
- package/core/agentic/index.ts +145 -152
- package/core/agentic/loop-detector.ts +40 -11
- package/core/agentic/memory-system.ts +98 -35
- package/core/agentic/orchestrator-executor.ts +135 -71
- package/core/agentic/plan-mode.ts +46 -16
- package/core/agentic/prompt-builder.ts +108 -42
- package/core/agentic/services.ts +10 -9
- package/core/agentic/skill-loader.ts +9 -15
- package/core/agentic/smart-context.ts +129 -79
- package/core/agentic/template-executor.ts +13 -12
- package/core/agentic/template-loader.ts +7 -4
- package/core/agentic/tool-registry.ts +16 -13
- package/core/agents/index.ts +1 -1
- package/core/agents/performance.ts +10 -27
- package/core/ai-tools/formatters.ts +8 -6
- package/core/ai-tools/generator.ts +4 -4
- package/core/ai-tools/index.ts +1 -1
- package/core/ai-tools/registry.ts +21 -11
- package/core/bus/bus.ts +23 -16
- package/core/bus/index.ts +2 -2
- package/core/cli/linear.ts +3 -5
- package/core/cli/start.ts +28 -25
- package/core/commands/analysis.ts +58 -39
- package/core/commands/analytics.ts +52 -44
- package/core/commands/base.ts +15 -13
- package/core/commands/cleanup.ts +6 -13
- package/core/commands/command-data.ts +28 -4
- package/core/commands/commands.ts +57 -24
- package/core/commands/context.ts +4 -4
- package/core/commands/design.ts +3 -10
- package/core/commands/index.ts +5 -8
- package/core/commands/maintenance.ts +7 -4
- package/core/commands/planning.ts +179 -56
- package/core/commands/register.ts +13 -9
- package/core/commands/registry.ts +15 -14
- package/core/commands/setup.ts +26 -14
- package/core/commands/shipping.ts +11 -16
- package/core/commands/snapshots.ts +16 -32
- package/core/commands/uninstall.ts +541 -0
- package/core/commands/workflow.ts +24 -28
- package/core/constants/index.ts +10 -22
- package/core/context/generator.ts +82 -33
- package/core/context-tools/files-tool.ts +18 -19
- package/core/context-tools/imports-tool.ts +13 -33
- package/core/context-tools/index.ts +29 -54
- package/core/context-tools/recent-tool.ts +16 -22
- package/core/context-tools/signatures-tool.ts +17 -26
- package/core/context-tools/summary-tool.ts +20 -22
- package/core/context-tools/token-counter.ts +25 -20
- package/core/context-tools/types.ts +5 -5
- package/core/domain/agent-generator.ts +7 -5
- package/core/domain/agent-loader.ts +2 -2
- package/core/domain/analyzer.ts +19 -16
- package/core/domain/architecture-generator.ts +6 -3
- package/core/domain/context-estimator.ts +3 -4
- package/core/domain/snapshot-manager.ts +25 -22
- package/core/domain/task-stack.ts +24 -14
- package/core/errors.ts +1 -1
- package/core/events/events.ts +2 -4
- package/core/events/index.ts +1 -2
- package/core/index.ts +28 -16
- package/core/infrastructure/agent-detector.ts +3 -3
- package/core/infrastructure/ai-provider.ts +23 -20
- package/core/infrastructure/author-detector.ts +16 -10
- package/core/infrastructure/capability-installer.ts +2 -2
- package/core/infrastructure/claude-agent.ts +6 -6
- package/core/infrastructure/command-installer.ts +22 -17
- package/core/infrastructure/config-manager.ts +18 -14
- package/core/infrastructure/editors-config.ts +8 -4
- package/core/infrastructure/path-manager.ts +8 -6
- package/core/infrastructure/permission-manager.ts +20 -17
- package/core/infrastructure/setup.ts +42 -38
- package/core/infrastructure/update-checker.ts +5 -5
- package/core/integrations/issue-tracker/enricher.ts +8 -19
- package/core/integrations/issue-tracker/index.ts +2 -2
- package/core/integrations/issue-tracker/manager.ts +15 -15
- package/core/integrations/issue-tracker/types.ts +5 -22
- package/core/integrations/jira/client.ts +67 -59
- package/core/integrations/jira/index.ts +11 -14
- package/core/integrations/jira/mcp-adapter.ts +5 -10
- package/core/integrations/jira/service.ts +10 -10
- package/core/integrations/linear/client.ts +27 -18
- package/core/integrations/linear/index.ts +9 -12
- package/core/integrations/linear/service.ts +11 -11
- package/core/integrations/linear/sync.ts +8 -8
- package/core/outcomes/analyzer.ts +5 -18
- package/core/outcomes/index.ts +2 -2
- package/core/outcomes/recorder.ts +3 -3
- package/core/plugin/builtin/webhook.ts +19 -15
- package/core/plugin/hooks.ts +29 -21
- package/core/plugin/index.ts +7 -7
- package/core/plugin/loader.ts +19 -19
- package/core/plugin/registry.ts +12 -23
- package/core/schemas/agents.ts +1 -1
- package/core/schemas/analysis.ts +1 -1
- package/core/schemas/enriched-task.ts +62 -49
- package/core/schemas/ideas.ts +13 -13
- package/core/schemas/index.ts +17 -27
- package/core/schemas/issues.ts +40 -25
- package/core/schemas/metrics.ts +25 -25
- package/core/schemas/outcomes.ts +70 -62
- package/core/schemas/permissions.ts +15 -12
- package/core/schemas/prd.ts +27 -14
- package/core/schemas/project.ts +3 -3
- package/core/schemas/roadmap.ts +47 -34
- package/core/schemas/schemas.ts +3 -4
- package/core/schemas/shipped.ts +3 -3
- package/core/schemas/state.ts +43 -29
- package/core/server/index.ts +5 -6
- package/core/server/routes-extended.ts +68 -72
- package/core/server/routes.ts +3 -3
- package/core/server/server.ts +31 -26
- package/core/services/agent-generator.ts +237 -0
- package/core/services/agent-service.ts +2 -2
- package/core/services/breakdown-service.ts +2 -4
- package/core/services/context-generator.ts +299 -0
- package/core/services/context-selector.ts +420 -0
- package/core/services/doctor-service.ts +426 -0
- package/core/services/file-categorizer.ts +448 -0
- package/core/services/file-scorer.ts +270 -0
- package/core/services/git-analyzer.ts +267 -0
- package/core/services/index.ts +27 -10
- package/core/services/memory-service.ts +3 -4
- package/core/services/project-index.ts +911 -0
- package/core/services/project-service.ts +4 -4
- package/core/services/skill-installer.ts +14 -17
- package/core/services/skill-lock.ts +3 -3
- package/core/services/skill-service.ts +12 -6
- package/core/services/stack-detector.ts +245 -0
- package/core/services/sync-service.ts +87 -345
- package/core/services/watch-service.ts +294 -0
- package/core/session/compaction.ts +23 -31
- package/core/session/index.ts +11 -5
- package/core/session/log-migration.ts +3 -3
- package/core/session/metrics.ts +19 -14
- package/core/session/session-log-manager.ts +12 -17
- package/core/session/task-session-manager.ts +25 -25
- package/core/session/utils.ts +1 -1
- package/core/storage/ideas-storage.ts +41 -57
- package/core/storage/index-storage.ts +514 -0
- package/core/storage/index.ts +41 -17
- package/core/storage/metrics-storage.ts +39 -34
- package/core/storage/queue-storage.ts +35 -45
- package/core/storage/shipped-storage.ts +17 -20
- package/core/storage/state-storage.ts +50 -30
- package/core/storage/storage-manager.ts +6 -6
- package/core/storage/storage.ts +18 -15
- package/core/sync/auth-config.ts +3 -3
- package/core/sync/index.ts +13 -19
- package/core/sync/oauth-handler.ts +3 -3
- package/core/sync/sync-client.ts +4 -9
- package/core/sync/sync-manager.ts +12 -14
- package/core/types/commands.ts +42 -7
- package/core/types/index.ts +284 -305
- package/core/types/integrations.ts +3 -3
- package/core/types/storage.ts +14 -14
- package/core/types/utils.ts +3 -3
- package/core/utils/agent-stream.ts +3 -1
- package/core/utils/animations.ts +14 -11
- package/core/utils/branding.ts +7 -7
- package/core/utils/cache.ts +1 -3
- package/core/utils/collection-filters.ts +3 -15
- package/core/utils/date-helper.ts +2 -7
- package/core/utils/file-helper.ts +13 -8
- package/core/utils/jsonl-helper.ts +13 -10
- package/core/utils/keychain.ts +4 -8
- package/core/utils/logger.ts +1 -1
- package/core/utils/next-steps.ts +3 -3
- package/core/utils/output.ts +58 -11
- package/core/utils/project-commands.ts +6 -6
- package/core/utils/project-credentials.ts +5 -12
- package/core/utils/runtime.ts +2 -2
- package/core/utils/session-helper.ts +3 -4
- package/core/utils/version.ts +3 -3
- package/core/wizard/index.ts +13 -0
- package/core/wizard/onboarding.ts +633 -0
- package/core/workflow/state-machine.ts +7 -7
- package/dist/bin/prjct.mjs +18755 -15574
- package/dist/core/infrastructure/command-installer.js +86 -79
- package/dist/core/infrastructure/editors-config.js +6 -6
- package/dist/core/infrastructure/setup.js +246 -225
- package/dist/core/utils/version.js +9 -9
- package/package.json +11 -12
- package/scripts/build.js +3 -3
- package/scripts/postinstall.js +2 -2
- package/templates/mcp-config.json +6 -1
- package/templates/permissions/permissive.jsonc +1 -1
- package/templates/permissions/strict.jsonc +5 -9
- package/templates/global/docs/agents.md +0 -88
- package/templates/global/docs/architecture.md +0 -103
- package/templates/global/docs/commands.md +0 -96
- package/templates/global/docs/validation.md +0 -95
package/core/schemas/state.ts
CHANGED
|
@@ -17,17 +17,31 @@ import { z } from 'zod'
|
|
|
17
17
|
export const PrioritySchema = z.enum(['low', 'medium', 'high', 'critical'])
|
|
18
18
|
export const TaskTypeSchema = z.enum(['feature', 'bug', 'improvement', 'chore'])
|
|
19
19
|
export const TaskSectionSchema = z.enum(['active', 'backlog', 'previously_active'])
|
|
20
|
-
export const TaskStatusSchema = z.enum([
|
|
21
|
-
|
|
20
|
+
export const TaskStatusSchema = z.enum([
|
|
21
|
+
'pending',
|
|
22
|
+
'in_progress',
|
|
23
|
+
'completed',
|
|
24
|
+
'blocked',
|
|
25
|
+
'paused',
|
|
26
|
+
'failed',
|
|
27
|
+
])
|
|
28
|
+
export const ActivityTypeSchema = z.enum([
|
|
29
|
+
'task_completed',
|
|
30
|
+
'feature_shipped',
|
|
31
|
+
'idea_captured',
|
|
32
|
+
'session_started',
|
|
33
|
+
])
|
|
22
34
|
|
|
23
35
|
// Subtask summary for context handoff between agents
|
|
24
36
|
export const SubtaskSummarySchema = z.object({
|
|
25
37
|
title: z.string(),
|
|
26
38
|
description: z.string(),
|
|
27
|
-
filesChanged: z.array(
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
39
|
+
filesChanged: z.array(
|
|
40
|
+
z.object({
|
|
41
|
+
path: z.string(),
|
|
42
|
+
action: z.enum(['created', 'modified', 'deleted']),
|
|
43
|
+
})
|
|
44
|
+
),
|
|
31
45
|
whatWasDone: z.array(z.string()),
|
|
32
46
|
outputForNextAgent: z.string().optional(),
|
|
33
47
|
notes: z.string().optional(),
|
|
@@ -35,15 +49,15 @@ export const SubtaskSummarySchema = z.object({
|
|
|
35
49
|
|
|
36
50
|
// Subtask schema for task fragmentation
|
|
37
51
|
export const SubtaskSchema = z.object({
|
|
38
|
-
id: z.string(),
|
|
52
|
+
id: z.string(), // subtask-xxx
|
|
39
53
|
description: z.string(),
|
|
40
|
-
domain: z.string(),
|
|
41
|
-
agent: z.string(),
|
|
54
|
+
domain: z.string(), // frontend, backend, database, testing, etc.
|
|
55
|
+
agent: z.string(), // agent file name (e.g., "frontend.md")
|
|
42
56
|
status: TaskStatusSchema,
|
|
43
|
-
dependsOn: z.array(z.string()),
|
|
44
|
-
startedAt: z.string().optional(),
|
|
57
|
+
dependsOn: z.array(z.string()), // IDs of dependent subtasks
|
|
58
|
+
startedAt: z.string().optional(), // ISO8601
|
|
45
59
|
completedAt: z.string().optional(), // ISO8601
|
|
46
|
-
output: z.string().optional(),
|
|
60
|
+
output: z.string().optional(), // Brief output description
|
|
47
61
|
summary: SubtaskSummarySchema.optional(), // Full summary for context handoff
|
|
48
62
|
})
|
|
49
63
|
|
|
@@ -55,26 +69,26 @@ export const SubtaskProgressSchema = z.object({
|
|
|
55
69
|
})
|
|
56
70
|
|
|
57
71
|
export const CurrentTaskSchema = z.object({
|
|
58
|
-
id: z.string(),
|
|
72
|
+
id: z.string(), // task_xxxxxxxx
|
|
59
73
|
description: z.string(),
|
|
60
|
-
startedAt: z.string(),
|
|
61
|
-
sessionId: z.string(),
|
|
74
|
+
startedAt: z.string(), // ISO8601
|
|
75
|
+
sessionId: z.string(), // sess_xxxxxxxx
|
|
62
76
|
featureId: z.string().optional(), // feat_xxxxxxxx
|
|
63
77
|
// Subtask tracking for fragmented tasks
|
|
64
78
|
subtasks: z.array(SubtaskSchema).optional(),
|
|
65
79
|
currentSubtaskIndex: z.number().optional(),
|
|
66
80
|
subtaskProgress: SubtaskProgressSchema.optional(),
|
|
67
81
|
// Linear integration - bidirectional sync
|
|
68
|
-
linearId: z.string().optional(),
|
|
69
|
-
linearUuid: z.string().optional(),
|
|
82
|
+
linearId: z.string().optional(), // "PRJ-123" - Linear identifier
|
|
83
|
+
linearUuid: z.string().optional(), // Linear internal UUID for API calls
|
|
70
84
|
})
|
|
71
85
|
|
|
72
86
|
export const PreviousTaskSchema = z.object({
|
|
73
87
|
id: z.string(),
|
|
74
88
|
description: z.string(),
|
|
75
89
|
status: z.literal('paused'),
|
|
76
|
-
startedAt: z.string(),
|
|
77
|
-
pausedAt: z.string(),
|
|
90
|
+
startedAt: z.string(), // ISO8601
|
|
91
|
+
pausedAt: z.string(), // ISO8601
|
|
78
92
|
pauseReason: z.string().optional(),
|
|
79
93
|
})
|
|
80
94
|
|
|
@@ -85,20 +99,20 @@ export const StateJsonSchema = z.object({
|
|
|
85
99
|
})
|
|
86
100
|
|
|
87
101
|
export const QueueTaskSchema = z.object({
|
|
88
|
-
id: z.string(),
|
|
102
|
+
id: z.string(), // task_xxxxxxxx
|
|
89
103
|
description: z.string(),
|
|
90
104
|
priority: PrioritySchema,
|
|
91
|
-
type: TaskTypeSchema,
|
|
105
|
+
type: TaskTypeSchema, // detect from emoji 🐛=bug
|
|
92
106
|
featureId: z.string().optional(),
|
|
93
107
|
originFeature: z.string().optional(),
|
|
94
108
|
completed: z.boolean(),
|
|
95
109
|
completedAt: z.string().optional(),
|
|
96
|
-
createdAt: z.string(),
|
|
110
|
+
createdAt: z.string(), // ISO8601
|
|
97
111
|
section: TaskSectionSchema,
|
|
98
112
|
// Additional fields for ZERO DATA LOSS
|
|
99
|
-
agent: z.string().optional(),
|
|
113
|
+
agent: z.string().optional(), // "fe", "be", "fe + be"
|
|
100
114
|
groupName: z.string().optional(), // "Sales Reports", "Stock Audits"
|
|
101
|
-
groupId: z.string().optional(),
|
|
115
|
+
groupId: z.string().optional(), // For grouping related tasks
|
|
102
116
|
})
|
|
103
117
|
|
|
104
118
|
export const QueueJsonSchema = z.object({
|
|
@@ -117,7 +131,7 @@ export const StatsSchema = z.object({
|
|
|
117
131
|
export const RecentActivitySchema = z.object({
|
|
118
132
|
type: ActivityTypeSchema,
|
|
119
133
|
description: z.string(),
|
|
120
|
-
timestamp: z.string(),
|
|
134
|
+
timestamp: z.string(), // ISO8601
|
|
121
135
|
duration: z.string().optional(),
|
|
122
136
|
})
|
|
123
137
|
|
|
@@ -127,7 +141,7 @@ export const StateSchemaFull = z.object({
|
|
|
127
141
|
queue: z.array(QueueTaskSchema),
|
|
128
142
|
stats: StatsSchema,
|
|
129
143
|
recentActivity: z.array(RecentActivitySchema),
|
|
130
|
-
lastSync: z.string(),
|
|
144
|
+
lastSync: z.string(), // ISO8601
|
|
131
145
|
})
|
|
132
146
|
|
|
133
147
|
// =============================================================================
|
|
@@ -176,12 +190,12 @@ export const safeParseQueue = (data: unknown) => QueueJsonSchema.safeParse(data)
|
|
|
176
190
|
|
|
177
191
|
export const DEFAULT_STATE: StateJson = {
|
|
178
192
|
currentTask: null,
|
|
179
|
-
lastUpdated: ''
|
|
193
|
+
lastUpdated: '',
|
|
180
194
|
}
|
|
181
195
|
|
|
182
196
|
export const DEFAULT_QUEUE: QueueJson = {
|
|
183
197
|
tasks: [],
|
|
184
|
-
lastUpdated: ''
|
|
198
|
+
lastUpdated: '',
|
|
185
199
|
}
|
|
186
200
|
|
|
187
201
|
export const DEFAULT_STATS: Stats = {
|
|
@@ -189,5 +203,5 @@ export const DEFAULT_STATS: Stats = {
|
|
|
189
203
|
tasksThisWeek: 0,
|
|
190
204
|
streak: 0,
|
|
191
205
|
velocity: '0/day',
|
|
192
|
-
avgDuration: '0m'
|
|
206
|
+
avgDuration: '0m',
|
|
193
207
|
}
|
package/core/server/index.ts
CHANGED
|
@@ -7,16 +7,15 @@
|
|
|
7
7
|
* @version 1.0.0
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
export { createServer, startServer, DEFAULT_PORT } from './server'
|
|
11
|
-
export { createRoutes } from './routes'
|
|
12
|
-
export { createExtendedRoutes } from './routes-extended'
|
|
13
|
-
export { createSSEManager, SSE_EVENTS } from './sse'
|
|
14
|
-
|
|
15
10
|
// Re-export types from canonical location
|
|
16
11
|
export type {
|
|
17
12
|
ServerConfig,
|
|
18
13
|
ServerInstance,
|
|
19
14
|
SSEClient,
|
|
20
|
-
SSEManager,
|
|
21
15
|
SSEEventType,
|
|
16
|
+
SSEManager,
|
|
22
17
|
} from '../types'
|
|
18
|
+
export { createRoutes } from './routes'
|
|
19
|
+
export { createExtendedRoutes } from './routes-extended'
|
|
20
|
+
export { createServer, DEFAULT_PORT, startServer } from './server'
|
|
21
|
+
export { createSSEManager, SSE_EVENTS } from './sse'
|
|
@@ -7,9 +7,9 @@
|
|
|
7
7
|
* @version 2.0.0
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
+
import fs from 'node:fs/promises'
|
|
11
|
+
import path from 'node:path'
|
|
10
12
|
import { Hono } from 'hono'
|
|
11
|
-
import fs from 'fs/promises'
|
|
12
|
-
import path from 'path'
|
|
13
13
|
import * as jsonc from 'jsonc-parser'
|
|
14
14
|
import pathManager from '../infrastructure/path-manager'
|
|
15
15
|
import { isNotFoundError } from '../types/fs'
|
|
@@ -38,7 +38,7 @@ async function readJsonFile<T>(filePath: string): Promise<T | null> {
|
|
|
38
38
|
async function writeJsonFile(filePath: string, data: unknown): Promise<boolean> {
|
|
39
39
|
try {
|
|
40
40
|
await fs.mkdir(path.dirname(filePath), { recursive: true })
|
|
41
|
-
await fs.writeFile(filePath, JSON.stringify(data, null, 2)
|
|
41
|
+
await fs.writeFile(filePath, `${JSON.stringify(data, null, 2)}\n`, 'utf-8')
|
|
42
42
|
return true
|
|
43
43
|
} catch (error) {
|
|
44
44
|
if (isNotFoundError(error)) {
|
|
@@ -59,14 +59,14 @@ async function getProjectConfig(projectId: string): Promise<any> {
|
|
|
59
59
|
|
|
60
60
|
async function calculateDuration(startedAt: string | undefined): Promise<string> {
|
|
61
61
|
if (!startedAt) return ''
|
|
62
|
-
|
|
62
|
+
|
|
63
63
|
const start = new Date(startedAt)
|
|
64
64
|
const now = new Date()
|
|
65
65
|
const elapsed = now.getTime() - start.getTime()
|
|
66
|
-
|
|
66
|
+
|
|
67
67
|
const hours = Math.floor(elapsed / (1000 * 60 * 60))
|
|
68
68
|
const minutes = Math.floor((elapsed % (1000 * 60 * 60)) / (1000 * 60))
|
|
69
|
-
|
|
69
|
+
|
|
70
70
|
if (hours > 0) {
|
|
71
71
|
return `${hours}h ${minutes}m`
|
|
72
72
|
}
|
|
@@ -87,34 +87,26 @@ export function createExtendedRoutes(): Hono {
|
|
|
87
87
|
try {
|
|
88
88
|
await fs.mkdir(PROJECTS_DIR, { recursive: true })
|
|
89
89
|
const entries = await fs.readdir(PROJECTS_DIR, { withFileTypes: true })
|
|
90
|
-
const projectIds = entries.filter(e => e.isDirectory()).map(e => e.name)
|
|
90
|
+
const projectIds = entries.filter((e) => e.isDirectory()).map((e) => e.name)
|
|
91
91
|
|
|
92
92
|
const projects = await Promise.all(
|
|
93
93
|
projectIds.map(async (id) => {
|
|
94
94
|
const projectPath = getProjectPath(id)
|
|
95
|
-
|
|
95
|
+
|
|
96
96
|
// Read config
|
|
97
97
|
const config = await getProjectConfig(id)
|
|
98
|
-
|
|
98
|
+
|
|
99
99
|
// Read state
|
|
100
|
-
const state = await readJsonFile<any>(
|
|
101
|
-
|
|
102
|
-
)
|
|
103
|
-
|
|
100
|
+
const state = await readJsonFile<any>(path.join(projectPath, 'storage/state.json'))
|
|
101
|
+
|
|
104
102
|
// Read queue for count
|
|
105
|
-
const queue = await readJsonFile<any>(
|
|
106
|
-
|
|
107
|
-
)
|
|
108
|
-
|
|
103
|
+
const queue = await readJsonFile<any>(path.join(projectPath, 'storage/queue.json'))
|
|
104
|
+
|
|
109
105
|
// Read ideas for count
|
|
110
|
-
const ideas = await readJsonFile<any>(
|
|
111
|
-
|
|
112
|
-
)
|
|
113
|
-
|
|
106
|
+
const ideas = await readJsonFile<any>(path.join(projectPath, 'storage/ideas.json'))
|
|
107
|
+
|
|
114
108
|
// Read shipped for count
|
|
115
|
-
const shipped = await readJsonFile<any>(
|
|
116
|
-
path.join(projectPath, 'storage/shipped.json')
|
|
117
|
-
)
|
|
109
|
+
const shipped = await readJsonFile<any>(path.join(projectPath, 'storage/shipped.json'))
|
|
118
110
|
|
|
119
111
|
const currentTask = state?.currentTask
|
|
120
112
|
const duration = await calculateDuration(currentTask?.startedAt)
|
|
@@ -123,16 +115,18 @@ export function createExtendedRoutes(): Hono {
|
|
|
123
115
|
id,
|
|
124
116
|
name: config?.name || id.slice(0, 8),
|
|
125
117
|
path: config?.path || null,
|
|
126
|
-
currentTask: currentTask
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
118
|
+
currentTask: currentTask
|
|
119
|
+
? {
|
|
120
|
+
...currentTask,
|
|
121
|
+
duration,
|
|
122
|
+
}
|
|
123
|
+
: null,
|
|
130
124
|
pausedTask: state?.previousTask || null,
|
|
131
125
|
stats: {
|
|
132
126
|
queueCount: queue?.tasks?.filter((t: any) => !t.completed)?.length || 0,
|
|
133
127
|
ideasCount: ideas?.ideas?.filter((i: any) => i.status === 'pending')?.length || 0,
|
|
134
128
|
shippedCount: shipped?.shipped?.length || 0,
|
|
135
|
-
}
|
|
129
|
+
},
|
|
136
130
|
}
|
|
137
131
|
})
|
|
138
132
|
)
|
|
@@ -178,15 +172,17 @@ export function createExtendedRoutes(): Hono {
|
|
|
178
172
|
const weekStart = new Date(todayStart)
|
|
179
173
|
weekStart.setDate(weekStart.getDate() - weekStart.getDay())
|
|
180
174
|
|
|
181
|
-
const completedToday =
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
175
|
+
const completedToday =
|
|
176
|
+
queue?.tasks?.filter((t: any) => {
|
|
177
|
+
if (!t.completed || !t.completedAt) return false
|
|
178
|
+
return new Date(t.completedAt) >= todayStart
|
|
179
|
+
})?.length || 0
|
|
185
180
|
|
|
186
|
-
const completedThisWeek =
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
181
|
+
const completedThisWeek =
|
|
182
|
+
queue?.tasks?.filter((t: any) => {
|
|
183
|
+
if (!t.completed || !t.completedAt) return false
|
|
184
|
+
return new Date(t.completedAt) >= weekStart
|
|
185
|
+
})?.length || 0
|
|
190
186
|
|
|
191
187
|
return c.json({
|
|
192
188
|
id: projectId,
|
|
@@ -204,7 +200,7 @@ export function createExtendedRoutes(): Hono {
|
|
|
204
200
|
ideasCount: ideas?.ideas?.filter((i: any) => i.status === 'pending')?.length || 0,
|
|
205
201
|
shippedCount: shipped?.shipped?.length || 0,
|
|
206
202
|
},
|
|
207
|
-
timestamp: new Date().toISOString()
|
|
203
|
+
timestamp: new Date().toISOString(),
|
|
208
204
|
})
|
|
209
205
|
} catch (error) {
|
|
210
206
|
return c.json({ error: String(error) }, 500)
|
|
@@ -221,7 +217,7 @@ export function createExtendedRoutes(): Hono {
|
|
|
221
217
|
|
|
222
218
|
try {
|
|
223
219
|
const state = await readJsonFile<any>(statePath)
|
|
224
|
-
|
|
220
|
+
|
|
225
221
|
if (!state?.currentTask) {
|
|
226
222
|
return c.json({ success: false, error: 'No active task' }, 400)
|
|
227
223
|
}
|
|
@@ -232,15 +228,15 @@ export function createExtendedRoutes(): Hono {
|
|
|
232
228
|
const newState = {
|
|
233
229
|
currentTask: null,
|
|
234
230
|
previousTask: null,
|
|
235
|
-
lastUpdated: new Date().toISOString()
|
|
231
|
+
lastUpdated: new Date().toISOString(),
|
|
236
232
|
}
|
|
237
233
|
|
|
238
234
|
await writeJsonFile(statePath, newState)
|
|
239
235
|
|
|
240
|
-
return c.json({
|
|
241
|
-
success: true,
|
|
236
|
+
return c.json({
|
|
237
|
+
success: true,
|
|
242
238
|
completedTask,
|
|
243
|
-
message: `Completed: ${completedTask.description}
|
|
239
|
+
message: `Completed: ${completedTask.description}`,
|
|
244
240
|
})
|
|
245
241
|
} catch (error) {
|
|
246
242
|
return c.json({ success: false, error: String(error) }, 500)
|
|
@@ -260,7 +256,7 @@ export function createExtendedRoutes(): Hono {
|
|
|
260
256
|
const reason = body.reason
|
|
261
257
|
|
|
262
258
|
const state = await readJsonFile<any>(statePath)
|
|
263
|
-
|
|
259
|
+
|
|
264
260
|
if (!state?.currentTask) {
|
|
265
261
|
return c.json({ success: false, error: 'No active task' }, 400)
|
|
266
262
|
}
|
|
@@ -271,21 +267,21 @@ export function createExtendedRoutes(): Hono {
|
|
|
271
267
|
status: 'paused',
|
|
272
268
|
startedAt: state.currentTask.startedAt,
|
|
273
269
|
pausedAt: new Date().toISOString(),
|
|
274
|
-
pauseReason: reason
|
|
270
|
+
pauseReason: reason,
|
|
275
271
|
}
|
|
276
272
|
|
|
277
273
|
const newState = {
|
|
278
274
|
currentTask: null,
|
|
279
275
|
previousTask: pausedTask,
|
|
280
|
-
lastUpdated: new Date().toISOString()
|
|
276
|
+
lastUpdated: new Date().toISOString(),
|
|
281
277
|
}
|
|
282
278
|
|
|
283
279
|
await writeJsonFile(statePath, newState)
|
|
284
280
|
|
|
285
|
-
return c.json({
|
|
286
|
-
success: true,
|
|
281
|
+
return c.json({
|
|
282
|
+
success: true,
|
|
287
283
|
pausedTask,
|
|
288
|
-
message: `Paused: ${pausedTask.description}
|
|
284
|
+
message: `Paused: ${pausedTask.description}`,
|
|
289
285
|
})
|
|
290
286
|
} catch (error) {
|
|
291
287
|
return c.json({ success: false, error: String(error) }, 500)
|
|
@@ -302,7 +298,7 @@ export function createExtendedRoutes(): Hono {
|
|
|
302
298
|
|
|
303
299
|
try {
|
|
304
300
|
const state = await readJsonFile<any>(statePath)
|
|
305
|
-
|
|
301
|
+
|
|
306
302
|
if (!state?.previousTask) {
|
|
307
303
|
return c.json({ success: false, error: 'No paused task' }, 400)
|
|
308
304
|
}
|
|
@@ -311,21 +307,21 @@ export function createExtendedRoutes(): Hono {
|
|
|
311
307
|
id: state.previousTask.id,
|
|
312
308
|
description: state.previousTask.description,
|
|
313
309
|
startedAt: new Date().toISOString(),
|
|
314
|
-
sessionId: `sess_${Date.now().toString(36)}
|
|
310
|
+
sessionId: `sess_${Date.now().toString(36)}`,
|
|
315
311
|
}
|
|
316
312
|
|
|
317
313
|
const newState = {
|
|
318
314
|
currentTask: resumedTask,
|
|
319
315
|
previousTask: null,
|
|
320
|
-
lastUpdated: new Date().toISOString()
|
|
316
|
+
lastUpdated: new Date().toISOString(),
|
|
321
317
|
}
|
|
322
318
|
|
|
323
319
|
await writeJsonFile(statePath, newState)
|
|
324
320
|
|
|
325
|
-
return c.json({
|
|
326
|
-
success: true,
|
|
321
|
+
return c.json({
|
|
322
|
+
success: true,
|
|
327
323
|
resumedTask,
|
|
328
|
-
message: `Resumed: ${resumedTask.description}
|
|
324
|
+
message: `Resumed: ${resumedTask.description}`,
|
|
329
325
|
})
|
|
330
326
|
} catch (error) {
|
|
331
327
|
return c.json({ success: false, error: String(error) }, 500)
|
|
@@ -369,22 +365,22 @@ export function createExtendedRoutes(): Hono {
|
|
|
369
365
|
description: task.description,
|
|
370
366
|
startedAt: new Date().toISOString(),
|
|
371
367
|
sessionId: `sess_${Date.now().toString(36)}`,
|
|
372
|
-
featureId: task.featureId
|
|
368
|
+
featureId: task.featureId,
|
|
373
369
|
}
|
|
374
370
|
|
|
375
371
|
// Update state
|
|
376
372
|
const newState = {
|
|
377
373
|
currentTask: newTask,
|
|
378
374
|
previousTask: null,
|
|
379
|
-
lastUpdated: new Date().toISOString()
|
|
375
|
+
lastUpdated: new Date().toISOString(),
|
|
380
376
|
}
|
|
381
377
|
|
|
382
378
|
await writeJsonFile(statePath, newState)
|
|
383
379
|
|
|
384
|
-
return c.json({
|
|
385
|
-
success: true,
|
|
380
|
+
return c.json({
|
|
381
|
+
success: true,
|
|
386
382
|
task: newTask,
|
|
387
|
-
message: `Started: ${newTask.description}
|
|
383
|
+
message: `Started: ${newTask.description}`,
|
|
388
384
|
})
|
|
389
385
|
} catch (error) {
|
|
390
386
|
return c.json({ success: false, error: String(error) }, 500)
|
|
@@ -407,7 +403,7 @@ export function createExtendedRoutes(): Hono {
|
|
|
407
403
|
return c.json({ success: false, error: 'text required' }, 400)
|
|
408
404
|
}
|
|
409
405
|
|
|
410
|
-
const ideas = await readJsonFile<any>(ideasPath) || { ideas: [], lastUpdated: '' }
|
|
406
|
+
const ideas = (await readJsonFile<any>(ideasPath)) || { ideas: [], lastUpdated: '' }
|
|
411
407
|
|
|
412
408
|
const newIdea = {
|
|
413
409
|
id: `idea_${Date.now().toString(36)}`,
|
|
@@ -415,7 +411,7 @@ export function createExtendedRoutes(): Hono {
|
|
|
415
411
|
status: 'pending',
|
|
416
412
|
priority,
|
|
417
413
|
tags,
|
|
418
|
-
addedAt: new Date().toISOString()
|
|
414
|
+
addedAt: new Date().toISOString(),
|
|
419
415
|
}
|
|
420
416
|
|
|
421
417
|
ideas.ideas.unshift(newIdea) // Prepend
|
|
@@ -423,10 +419,10 @@ export function createExtendedRoutes(): Hono {
|
|
|
423
419
|
|
|
424
420
|
await writeJsonFile(ideasPath, ideas)
|
|
425
421
|
|
|
426
|
-
return c.json({
|
|
427
|
-
success: true,
|
|
422
|
+
return c.json({
|
|
423
|
+
success: true,
|
|
428
424
|
idea: newIdea,
|
|
429
|
-
message: `Captured: ${text.slice(0, 50)}
|
|
425
|
+
message: `Captured: ${text.slice(0, 50)}...`,
|
|
430
426
|
})
|
|
431
427
|
} catch (error) {
|
|
432
428
|
return c.json({ success: false, error: String(error) }, 500)
|
|
@@ -440,7 +436,7 @@ export function createExtendedRoutes(): Hono {
|
|
|
440
436
|
try {
|
|
441
437
|
await fs.mkdir(PROJECTS_DIR, { recursive: true })
|
|
442
438
|
const entries = await fs.readdir(PROJECTS_DIR, { withFileTypes: true })
|
|
443
|
-
const projectIds = entries.filter(e => e.isDirectory()).map(e => e.name)
|
|
439
|
+
const projectIds = entries.filter((e) => e.isDirectory()).map((e) => e.name)
|
|
444
440
|
|
|
445
441
|
let totalTasks = 0
|
|
446
442
|
let totalIdeas = 0
|
|
@@ -449,14 +445,14 @@ export function createExtendedRoutes(): Hono {
|
|
|
449
445
|
|
|
450
446
|
for (const id of projectIds) {
|
|
451
447
|
const projectPath = getProjectPath(id)
|
|
452
|
-
|
|
448
|
+
|
|
453
449
|
const state = await readJsonFile<any>(path.join(projectPath, 'storage/state.json'))
|
|
454
450
|
const queue = await readJsonFile<any>(path.join(projectPath, 'storage/queue.json'))
|
|
455
451
|
const ideas = await readJsonFile<any>(path.join(projectPath, 'storage/ideas.json'))
|
|
456
452
|
const shipped = await readJsonFile<any>(path.join(projectPath, 'storage/shipped.json'))
|
|
457
453
|
|
|
458
454
|
if (state?.currentTask) activeProjects++
|
|
459
|
-
|
|
455
|
+
|
|
460
456
|
totalTasks += queue?.tasks?.filter((t: any) => !t.completed)?.length || 0
|
|
461
457
|
totalIdeas += ideas?.ideas?.filter((i: any) => i.status === 'pending')?.length || 0
|
|
462
458
|
totalShipped += shipped?.shipped?.length || 0
|
|
@@ -468,7 +464,7 @@ export function createExtendedRoutes(): Hono {
|
|
|
468
464
|
totalTasks,
|
|
469
465
|
totalIdeas,
|
|
470
466
|
totalShipped,
|
|
471
|
-
timestamp: new Date().toISOString()
|
|
467
|
+
timestamp: new Date().toISOString(),
|
|
472
468
|
})
|
|
473
469
|
} catch (error) {
|
|
474
470
|
return c.json({ error: String(error) }, 500)
|
|
@@ -485,7 +481,7 @@ export function createExtendedRoutes(): Hono {
|
|
|
485
481
|
|
|
486
482
|
await fs.mkdir(PROJECTS_DIR, { recursive: true })
|
|
487
483
|
const entries = await fs.readdir(PROJECTS_DIR, { withFileTypes: true })
|
|
488
|
-
const projectIds = entries.filter(e => e.isDirectory()).map(e => e.name)
|
|
484
|
+
const projectIds = entries.filter((e) => e.isDirectory()).map((e) => e.name)
|
|
489
485
|
|
|
490
486
|
// If cwd provided, find matching project by path
|
|
491
487
|
let targetProjectId: string | null = null
|
|
@@ -519,7 +515,7 @@ export function createExtendedRoutes(): Hono {
|
|
|
519
515
|
activeProject = { id, name: config?.name || id, path: config?.repoPath || config?.path }
|
|
520
516
|
activeTask = {
|
|
521
517
|
...state.currentTask,
|
|
522
|
-
duration: await calculateDuration(state.currentTask.startedAt)
|
|
518
|
+
duration: await calculateDuration(state.currentTask.startedAt),
|
|
523
519
|
}
|
|
524
520
|
break
|
|
525
521
|
}
|
|
@@ -540,7 +536,7 @@ export function createExtendedRoutes(): Hono {
|
|
|
540
536
|
// Include whether we filtered by cwd
|
|
541
537
|
filtered: !!targetProjectId,
|
|
542
538
|
cwd: cwd || null,
|
|
543
|
-
timestamp: new Date().toISOString()
|
|
539
|
+
timestamp: new Date().toISOString(),
|
|
544
540
|
})
|
|
545
541
|
} catch (error) {
|
|
546
542
|
return c.json({ error: String(error) }, 500)
|
package/core/server/routes.ts
CHANGED
|
@@ -6,9 +6,9 @@
|
|
|
6
6
|
* @version 1.0.0
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
+
import fs from 'node:fs/promises'
|
|
10
|
+
import path from 'node:path'
|
|
9
11
|
import { Hono } from 'hono'
|
|
10
|
-
import fs from 'fs/promises'
|
|
11
|
-
import path from 'path'
|
|
12
12
|
import * as jsonc from 'jsonc-parser'
|
|
13
13
|
import pathManager from '../infrastructure/path-manager'
|
|
14
14
|
import { isNotFoundError } from '../types/fs'
|
|
@@ -46,7 +46,7 @@ async function readJsonFile<T>(filePath: string): Promise<T | null> {
|
|
|
46
46
|
async function writeJsonFile(filePath: string, data: unknown): Promise<boolean> {
|
|
47
47
|
try {
|
|
48
48
|
await fs.mkdir(path.dirname(filePath), { recursive: true })
|
|
49
|
-
await fs.writeFile(filePath, JSON.stringify(data, null, 2)
|
|
49
|
+
await fs.writeFile(filePath, `${JSON.stringify(data, null, 2)}\n`, 'utf-8')
|
|
50
50
|
return true
|
|
51
51
|
} catch (error) {
|
|
52
52
|
console.error(`JSON write error: ${(error as Error).message}`)
|
package/core/server/server.ts
CHANGED
|
@@ -11,11 +11,11 @@
|
|
|
11
11
|
import { Hono } from 'hono'
|
|
12
12
|
import { cors } from 'hono/cors'
|
|
13
13
|
import { logger } from 'hono/logger'
|
|
14
|
+
import type { ServerConfig, ServerInstance } from '../types'
|
|
15
|
+
import { isBun } from '../utils/runtime'
|
|
14
16
|
import { createRoutes } from './routes'
|
|
15
17
|
import { createExtendedRoutes } from './routes-extended'
|
|
16
18
|
import { createSSEManager } from './sse'
|
|
17
|
-
import { isBun } from '../utils/runtime'
|
|
18
|
-
import type { ServerConfig, ServerInstance } from '../types'
|
|
19
19
|
|
|
20
20
|
// Server handle type that works for both runtimes
|
|
21
21
|
type ServerHandle = { stop: () => void } | null
|
|
@@ -29,11 +29,14 @@ export function createServer(config: ServerConfig): ServerInstance {
|
|
|
29
29
|
|
|
30
30
|
// Middleware
|
|
31
31
|
if (config.enableCors !== false) {
|
|
32
|
-
app.use(
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
32
|
+
app.use(
|
|
33
|
+
'*',
|
|
34
|
+
cors({
|
|
35
|
+
origin: '*',
|
|
36
|
+
allowMethods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
|
|
37
|
+
allowHeaders: ['Content-Type', 'Authorization'],
|
|
38
|
+
})
|
|
39
|
+
)
|
|
37
40
|
}
|
|
38
41
|
|
|
39
42
|
if (config.enableLogging !== false) {
|
|
@@ -44,25 +47,27 @@ export function createServer(config: ServerConfig): ServerInstance {
|
|
|
44
47
|
app.get('/health', (c) => c.json({ status: 'ok', timestamp: new Date().toISOString() }))
|
|
45
48
|
|
|
46
49
|
// API info
|
|
47
|
-
app.get('/', (c) =>
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
50
|
+
app.get('/', (c) =>
|
|
51
|
+
c.json({
|
|
52
|
+
name: 'prjct-cli',
|
|
53
|
+
version: '0.20.0',
|
|
54
|
+
projectId: config.projectId,
|
|
55
|
+
endpoints: {
|
|
56
|
+
health: '/health',
|
|
57
|
+
state: '/api/state',
|
|
58
|
+
queue: '/api/queue',
|
|
59
|
+
ideas: '/api/ideas',
|
|
60
|
+
roadmap: '/api/roadmap',
|
|
61
|
+
shipped: '/api/shipped',
|
|
62
|
+
events: '/api/events',
|
|
63
|
+
// Extended endpoints for status-bar
|
|
64
|
+
projects: '/api/projects',
|
|
65
|
+
projectFull: '/api/projects/:id/full',
|
|
66
|
+
statusBarCompact: '/api/status-bar/compact',
|
|
67
|
+
globalStats: '/api/stats/global',
|
|
68
|
+
},
|
|
69
|
+
})
|
|
70
|
+
)
|
|
66
71
|
|
|
67
72
|
// Mount API routes
|
|
68
73
|
const routes = createRoutes(config.projectId, config.projectPath)
|