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