prjct-cli 0.20.0 → 0.20.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +24 -6
- package/CLAUDE.md +56 -15
- package/README.md +5 -6
- package/bin/prjct +59 -42
- package/bin/prjct.ts +60 -0
- package/core/__tests__/agentic/memory-system.test.ts +18 -3
- package/core/__tests__/agentic/plan-mode.test.ts +55 -26
- package/core/__tests__/agentic/prompt-builder.test.ts +6 -6
- package/core/__tests__/utils/project-commands.test.ts +72 -0
- package/core/agentic/agent-router.ts +3 -12
- package/core/agentic/command-executor.ts +372 -3
- package/core/agentic/context-builder.ts +7 -27
- package/core/agentic/ground-truth.ts +604 -5
- package/core/agentic/index.ts +180 -0
- package/core/agentic/loop-detector.ts +418 -4
- package/core/agentic/memory-system.ts +857 -3
- package/core/agentic/plan-mode.ts +491 -4
- package/core/agentic/prompt-builder.ts +44 -65
- package/core/agentic/services.ts +13 -5
- package/core/agentic/skill-loader.ts +112 -0
- package/core/agentic/smart-context.ts +37 -122
- package/core/agentic/template-loader.ts +79 -122
- package/core/agentic/tool-registry.ts +5 -11
- package/core/agents/index.ts +1 -1
- package/core/agents/performance.ts +4 -2
- package/core/bus/bus.ts +262 -0
- package/core/bus/index.ts +3 -313
- package/core/commands/analysis.ts +5 -5
- package/core/commands/analytics.ts +11 -11
- package/core/commands/base.ts +33 -209
- package/core/commands/cleanup.ts +148 -0
- package/core/commands/command-data.ts +346 -0
- package/core/commands/commands.ts +216 -0
- package/core/commands/design.ts +83 -0
- package/core/commands/index.ts +13 -207
- package/core/commands/maintenance.ts +52 -473
- package/core/commands/planning.ts +3 -3
- package/core/commands/register.ts +104 -0
- package/core/commands/registry.ts +441 -0
- package/core/commands/setup.ts +25 -9
- package/core/commands/shipping.ts +48 -11
- package/core/commands/snapshots.ts +299 -0
- package/core/commands/workflow.ts +2 -2
- package/core/constants/index.ts +254 -4
- package/core/domain/agent-loader.ts +5 -6
- package/core/domain/task-stack.ts +555 -4
- package/core/errors.ts +127 -1
- package/core/events/events.ts +87 -0
- package/core/events/index.ts +4 -138
- package/core/index.ts +15 -23
- package/core/infrastructure/agent-detector.ts +126 -201
- package/core/infrastructure/author-detector.ts +99 -171
- package/core/infrastructure/command-installer.ts +476 -4
- package/core/infrastructure/config-manager.ts +41 -37
- package/core/infrastructure/path-manager.ts +59 -9
- package/core/infrastructure/permission-manager.ts +286 -0
- package/core/outcomes/analyzer.ts +7 -41
- package/core/outcomes/index.ts +1 -1
- package/core/outcomes/recorder.ts +1 -1
- package/core/{plugins → plugin/builtin}/webhook.ts +6 -22
- package/core/plugin/loader.ts +5 -5
- package/core/plugin/registry.ts +2 -2
- package/core/schemas/ideas.ts +85 -54
- package/core/schemas/index.ts +14 -33
- package/core/schemas/permissions.ts +177 -0
- package/core/schemas/project.ts +39 -12
- package/core/schemas/roadmap.ts +94 -59
- package/core/schemas/schemas.ts +39 -0
- package/core/schemas/shipped.ts +87 -60
- package/core/schemas/state.ts +110 -70
- package/core/server/index.ts +21 -0
- package/core/server/routes.ts +165 -0
- package/core/server/server.ts +136 -0
- package/core/server/sse.ts +135 -0
- package/core/services/agent-service.ts +170 -0
- package/core/services/breakdown-service.ts +126 -0
- package/core/services/index.ts +21 -0
- package/core/services/memory-service.ts +108 -0
- package/core/services/project-service.ts +146 -0
- package/core/services/skill-service.ts +253 -0
- package/core/session/compaction.ts +257 -0
- package/core/session/index.ts +20 -8
- package/core/{infrastructure/session-manager/migration.ts → session/log-migration.ts} +9 -9
- package/core/{infrastructure/session-manager/session-manager.ts → session/session-log-manager.ts} +27 -26
- package/core/session/{session-manager.ts → task-session-manager.ts} +7 -4
- package/core/session/utils.ts +1 -1
- package/core/storage/ideas-storage.ts +10 -26
- package/core/storage/index.ts +14 -162
- package/core/storage/queue-storage.ts +13 -11
- package/core/storage/shipped-storage.ts +4 -17
- package/core/storage/state-storage.ts +35 -43
- package/core/storage/storage-manager.ts +42 -52
- package/core/storage/storage.ts +160 -0
- package/core/sync/auth-config.ts +1 -8
- package/core/sync/index.ts +17 -10
- package/core/sync/oauth-handler.ts +1 -6
- package/core/sync/sync-client.ts +6 -34
- package/core/sync/sync-manager.ts +11 -40
- package/core/types/agentic.ts +577 -0
- package/core/types/agents.ts +145 -0
- package/core/types/bus.ts +82 -0
- package/core/types/commands.ts +366 -0
- package/core/types/config.ts +66 -0
- package/core/types/core.ts +96 -0
- package/core/types/domain.ts +71 -0
- package/core/types/events.ts +42 -0
- package/core/types/fs.ts +56 -0
- package/core/types/index.ts +387 -500
- package/core/types/infrastructure.ts +196 -0
- package/core/{agentic/memory-system/types.ts → types/memory.ts} +33 -8
- package/core/{outcomes/types.ts → types/outcomes.ts} +53 -8
- package/core/types/plugin.ts +25 -0
- package/core/types/server.ts +54 -0
- package/core/types/services.ts +65 -0
- package/core/types/session.ts +135 -0
- package/core/types/storage.ts +148 -0
- package/core/types/sync.ts +121 -0
- package/core/types/task.ts +72 -0
- package/core/types/template.ts +24 -0
- package/core/types/utils.ts +90 -0
- package/core/utils/cache.ts +195 -0
- package/core/utils/collection-filters.ts +245 -0
- package/core/utils/date-helper.ts +1 -5
- package/core/utils/file-helper.ts +20 -10
- package/core/utils/jsonl-helper.ts +5 -8
- package/core/utils/markdown-builder.ts +277 -0
- package/core/utils/project-commands.ts +132 -0
- package/core/utils/runtime.ts +119 -0
- package/dist/bin/prjct.mjs +12568 -0
- package/package.json +13 -8
- package/scripts/build.js +106 -0
- package/scripts/postinstall.js +50 -8
- package/templates/agentic/subagent-generation.md +1 -1
- package/templates/commands/serve.md +118 -0
- package/templates/commands/ship.md +13 -2
- package/templates/commands/skill.md +110 -0
- package/templates/commands/sync.md +1 -1
- package/templates/commands/test.md +23 -4
- package/templates/permissions/default.jsonc +60 -0
- package/templates/permissions/permissive.jsonc +49 -0
- package/templates/permissions/strict.jsonc +62 -0
- package/templates/skills/code-review.md +47 -0
- package/templates/skills/debug.md +61 -0
- package/templates/skills/refactor.md +47 -0
- package/templates/subagents/domain/devops.md +1 -1
- package/templates/subagents/domain/testing.md +6 -10
- package/templates/subagents/workflow/prjct-shipper.md +16 -7
- package/templates/tools/bash.txt +22 -0
- package/templates/tools/edit.txt +18 -0
- package/templates/tools/glob.txt +19 -0
- package/templates/tools/grep.txt +21 -0
- package/templates/tools/read.txt +14 -0
- package/templates/tools/task.txt +20 -0
- package/templates/tools/webfetch.txt +16 -0
- package/templates/tools/websearch.txt +18 -0
- package/templates/tools/write.txt +17 -0
- package/core/agentic/command-executor/command-executor.ts +0 -312
- package/core/agentic/command-executor/index.ts +0 -16
- package/core/agentic/command-executor/status-signal.ts +0 -38
- package/core/agentic/command-executor/types.ts +0 -79
- package/core/agentic/ground-truth/index.ts +0 -76
- package/core/agentic/ground-truth/types.ts +0 -33
- package/core/agentic/ground-truth/utils.ts +0 -48
- package/core/agentic/ground-truth/verifiers/analyze.ts +0 -54
- package/core/agentic/ground-truth/verifiers/done.ts +0 -75
- package/core/agentic/ground-truth/verifiers/feature.ts +0 -70
- package/core/agentic/ground-truth/verifiers/index.ts +0 -37
- package/core/agentic/ground-truth/verifiers/init.ts +0 -52
- package/core/agentic/ground-truth/verifiers/now.ts +0 -57
- package/core/agentic/ground-truth/verifiers/ship.ts +0 -85
- package/core/agentic/ground-truth/verifiers/spec.ts +0 -45
- package/core/agentic/ground-truth/verifiers/sync.ts +0 -47
- package/core/agentic/ground-truth/verifiers.ts +0 -6
- package/core/agentic/loop-detector/error-analysis.ts +0 -97
- package/core/agentic/loop-detector/hallucination.ts +0 -71
- package/core/agentic/loop-detector/index.ts +0 -41
- package/core/agentic/loop-detector/loop-detector.ts +0 -222
- package/core/agentic/loop-detector/types.ts +0 -66
- package/core/agentic/memory-system/history.ts +0 -53
- package/core/agentic/memory-system/index.ts +0 -192
- package/core/agentic/memory-system/patterns.ts +0 -156
- package/core/agentic/memory-system/semantic-memories.ts +0 -278
- package/core/agentic/memory-system/session.ts +0 -21
- package/core/agentic/plan-mode/approval.ts +0 -57
- package/core/agentic/plan-mode/constants.ts +0 -44
- package/core/agentic/plan-mode/index.ts +0 -28
- package/core/agentic/plan-mode/plan-mode.ts +0 -407
- package/core/agentic/plan-mode/types.ts +0 -193
- package/core/agents/types.ts +0 -126
- package/core/command-registry/categories.ts +0 -23
- package/core/command-registry/commands.ts +0 -15
- package/core/command-registry/core-commands.ts +0 -344
- package/core/command-registry/index.ts +0 -158
- package/core/command-registry/optional-commands.ts +0 -163
- package/core/command-registry/setup-commands.ts +0 -83
- package/core/command-registry/types.ts +0 -59
- package/core/command-registry.ts +0 -9
- package/core/commands/types.ts +0 -185
- package/core/commands.ts +0 -11
- package/core/constants/formats.ts +0 -187
- package/core/context-sync.ts +0 -18
- package/core/data/index.ts +0 -27
- package/core/data/md-base-manager.ts +0 -203
- package/core/data/md-ideas-manager.ts +0 -155
- package/core/data/md-queue-manager.ts +0 -180
- package/core/data/md-shipped-manager.ts +0 -90
- package/core/data/md-state-manager.ts +0 -137
- package/core/domain/task-stack/index.ts +0 -19
- package/core/domain/task-stack/parser.ts +0 -86
- package/core/domain/task-stack/storage.ts +0 -123
- package/core/domain/task-stack/task-stack.ts +0 -340
- package/core/domain/task-stack/types.ts +0 -51
- package/core/infrastructure/command-installer/command-installer.ts +0 -327
- package/core/infrastructure/command-installer/global-config.ts +0 -136
- package/core/infrastructure/command-installer/index.ts +0 -25
- package/core/infrastructure/command-installer/types.ts +0 -41
- package/core/infrastructure/session-manager/index.ts +0 -23
- package/core/infrastructure/session-manager/types.ts +0 -45
- package/core/infrastructure/session-manager.ts +0 -8
- package/core/serializers/ideas-serializer.ts +0 -187
- package/core/serializers/index.ts +0 -36
- package/core/serializers/queue-serializer.ts +0 -210
- package/core/serializers/shipped-serializer.ts +0 -108
- package/core/serializers/state-serializer.ts +0 -136
- package/core/session/types.ts +0 -29
- /package/core/infrastructure/{agents/claude-agent.ts → claude-agent.ts} +0 -0
package/core/bus/bus.ts
ADDED
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* EventBus - Lightweight Pub/Sub System for prjct-cli
|
|
3
|
+
*
|
|
4
|
+
* Simple event bus for decoupled communication between components.
|
|
5
|
+
* Supports sync/async listeners, wildcards, and one-time subscriptions.
|
|
6
|
+
*
|
|
7
|
+
* @version 1.0.0
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import fs from 'fs/promises'
|
|
11
|
+
import path from 'path'
|
|
12
|
+
import pathManager from '../infrastructure/path-manager'
|
|
13
|
+
import { EventTypes, type EventData, type EventCallback } from '../types'
|
|
14
|
+
|
|
15
|
+
class EventBus {
|
|
16
|
+
private listeners: Map<string, Set<EventCallback>>
|
|
17
|
+
private onceListeners: Map<string, Set<EventCallback>>
|
|
18
|
+
private history: EventData[]
|
|
19
|
+
private historyLimit: number
|
|
20
|
+
projectId: string | null
|
|
21
|
+
|
|
22
|
+
constructor() {
|
|
23
|
+
this.listeners = new Map()
|
|
24
|
+
this.onceListeners = new Map()
|
|
25
|
+
this.history = []
|
|
26
|
+
this.historyLimit = 100
|
|
27
|
+
this.projectId = null
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Initialize event bus for a project
|
|
32
|
+
*/
|
|
33
|
+
async initialize(projectId: string): Promise<void> {
|
|
34
|
+
this.projectId = projectId
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Subscribe to an event
|
|
39
|
+
*/
|
|
40
|
+
on(event: string, callback: EventCallback): () => void {
|
|
41
|
+
if (!this.listeners.has(event)) {
|
|
42
|
+
this.listeners.set(event, new Set())
|
|
43
|
+
}
|
|
44
|
+
this.listeners.get(event)!.add(callback)
|
|
45
|
+
|
|
46
|
+
// Return unsubscribe function
|
|
47
|
+
return () => this.off(event, callback)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Subscribe to an event once
|
|
52
|
+
*/
|
|
53
|
+
once(event: string, callback: EventCallback): () => void {
|
|
54
|
+
if (!this.onceListeners.has(event)) {
|
|
55
|
+
this.onceListeners.set(event, new Set())
|
|
56
|
+
}
|
|
57
|
+
this.onceListeners.get(event)!.add(callback)
|
|
58
|
+
|
|
59
|
+
return () => {
|
|
60
|
+
const listeners = this.onceListeners.get(event)
|
|
61
|
+
if (listeners) {
|
|
62
|
+
listeners.delete(callback)
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Unsubscribe from an event
|
|
69
|
+
*/
|
|
70
|
+
off(event: string, callback: EventCallback): void {
|
|
71
|
+
const listeners = this.listeners.get(event)
|
|
72
|
+
if (listeners) {
|
|
73
|
+
listeners.delete(callback)
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Emit an event
|
|
79
|
+
*/
|
|
80
|
+
async emit(event: string, data: Record<string, unknown> = {}): Promise<void> {
|
|
81
|
+
const timestamp = new Date().toISOString()
|
|
82
|
+
const eventData: EventData = {
|
|
83
|
+
type: event,
|
|
84
|
+
timestamp,
|
|
85
|
+
projectId: this.projectId,
|
|
86
|
+
...data
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Store in history
|
|
90
|
+
this.history.push(eventData)
|
|
91
|
+
if (this.history.length > this.historyLimit) {
|
|
92
|
+
this.history.shift()
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Log event if project initialized
|
|
96
|
+
if (this.projectId) {
|
|
97
|
+
await this.logEvent(eventData)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Get all matching listeners
|
|
101
|
+
const callbacks = this.getMatchingListeners(event)
|
|
102
|
+
|
|
103
|
+
// Execute all callbacks
|
|
104
|
+
const results = await Promise.allSettled(
|
|
105
|
+
callbacks.map(cb => this.executeCallback(cb, eventData))
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
// Log any errors
|
|
109
|
+
results.forEach((result) => {
|
|
110
|
+
if (result.status === 'rejected') {
|
|
111
|
+
console.error(`Event listener error for ${event}:`, result.reason)
|
|
112
|
+
}
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
// Handle once listeners
|
|
116
|
+
const onceCallbacks = this.onceListeners.get(event)
|
|
117
|
+
if (onceCallbacks) {
|
|
118
|
+
for (const cb of onceCallbacks) {
|
|
119
|
+
await this.executeCallback(cb, eventData)
|
|
120
|
+
}
|
|
121
|
+
this.onceListeners.delete(event)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Also trigger wildcard once listeners
|
|
125
|
+
const wildcardOnce = this.onceListeners.get(EventTypes.ALL)
|
|
126
|
+
if (wildcardOnce) {
|
|
127
|
+
for (const cb of wildcardOnce) {
|
|
128
|
+
await this.executeCallback(cb, eventData)
|
|
129
|
+
}
|
|
130
|
+
// Don't delete wildcard once - only for specific events
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Get all listeners that match an event (including wildcards)
|
|
136
|
+
*/
|
|
137
|
+
getMatchingListeners(event: string): EventCallback[] {
|
|
138
|
+
const callbacks: EventCallback[] = []
|
|
139
|
+
|
|
140
|
+
// Exact match
|
|
141
|
+
const exact = this.listeners.get(event)
|
|
142
|
+
if (exact) {
|
|
143
|
+
callbacks.push(...exact)
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Wildcard match (*)
|
|
147
|
+
const wildcard = this.listeners.get(EventTypes.ALL)
|
|
148
|
+
if (wildcard) {
|
|
149
|
+
callbacks.push(...wildcard)
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Namespace wildcard (e.g., 'session.*' matches 'session.started')
|
|
153
|
+
const namespace = event.split('.')[0]
|
|
154
|
+
const namespaceWildcard = this.listeners.get(`${namespace}.*`)
|
|
155
|
+
if (namespaceWildcard) {
|
|
156
|
+
callbacks.push(...namespaceWildcard)
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return callbacks
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Execute a callback safely (handles sync and async)
|
|
164
|
+
*/
|
|
165
|
+
async executeCallback(callback: EventCallback, data: EventData): Promise<void> {
|
|
166
|
+
try {
|
|
167
|
+
const result = callback(data)
|
|
168
|
+
if (result instanceof Promise) {
|
|
169
|
+
await result
|
|
170
|
+
}
|
|
171
|
+
} catch (error) {
|
|
172
|
+
console.error('Event callback error:', error)
|
|
173
|
+
throw error
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Log event to persistent storage
|
|
179
|
+
*/
|
|
180
|
+
async logEvent(eventData: EventData): Promise<void> {
|
|
181
|
+
try {
|
|
182
|
+
const globalPath = pathManager.getGlobalProjectPath(this.projectId!)
|
|
183
|
+
const eventsPath = path.join(globalPath, 'memory', 'events.jsonl')
|
|
184
|
+
|
|
185
|
+
// Ensure directory exists
|
|
186
|
+
await fs.mkdir(path.dirname(eventsPath), { recursive: true })
|
|
187
|
+
|
|
188
|
+
// Append event
|
|
189
|
+
const line = JSON.stringify(eventData) + '\n'
|
|
190
|
+
await fs.appendFile(eventsPath, line)
|
|
191
|
+
} catch {
|
|
192
|
+
// Silently fail - logging should not break functionality
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Get recent events from history
|
|
198
|
+
*/
|
|
199
|
+
getHistory(limit: number = 10, type: string | null = null): EventData[] {
|
|
200
|
+
let events = this.history
|
|
201
|
+
if (type) {
|
|
202
|
+
events = events.filter(e => e.type === type || e.type.startsWith(type))
|
|
203
|
+
}
|
|
204
|
+
return events.slice(-limit)
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Clear all listeners
|
|
209
|
+
*/
|
|
210
|
+
clear(): void {
|
|
211
|
+
this.listeners.clear()
|
|
212
|
+
this.onceListeners.clear()
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Get count of listeners for an event
|
|
217
|
+
*/
|
|
218
|
+
listenerCount(event: string): number {
|
|
219
|
+
const listeners = this.listeners.get(event)
|
|
220
|
+
return listeners ? listeners.size : 0
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Get all registered event types
|
|
225
|
+
*/
|
|
226
|
+
getRegisteredEvents(): string[] {
|
|
227
|
+
return Array.from(this.listeners.keys())
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Singleton instance
|
|
232
|
+
const eventBus = new EventBus()
|
|
233
|
+
|
|
234
|
+
// Convenience methods for common events
|
|
235
|
+
const emit = {
|
|
236
|
+
sessionStarted: (data: Record<string, unknown>) => eventBus.emit(EventTypes.SESSION_STARTED, data),
|
|
237
|
+
sessionPaused: (data: Record<string, unknown>) => eventBus.emit(EventTypes.SESSION_PAUSED, data),
|
|
238
|
+
sessionResumed: (data: Record<string, unknown>) => eventBus.emit(EventTypes.SESSION_RESUMED, data),
|
|
239
|
+
sessionCompleted: (data: Record<string, unknown>) => eventBus.emit(EventTypes.SESSION_COMPLETED, data),
|
|
240
|
+
|
|
241
|
+
taskCreated: (data: Record<string, unknown>) => eventBus.emit(EventTypes.TASK_CREATED, data),
|
|
242
|
+
taskCompleted: (data: Record<string, unknown>) => eventBus.emit(EventTypes.TASK_COMPLETED, data),
|
|
243
|
+
|
|
244
|
+
featureAdded: (data: Record<string, unknown>) => eventBus.emit(EventTypes.FEATURE_ADDED, data),
|
|
245
|
+
featureShipped: (data: Record<string, unknown>) => eventBus.emit(EventTypes.FEATURE_SHIPPED, data),
|
|
246
|
+
|
|
247
|
+
ideaCaptured: (data: Record<string, unknown>) => eventBus.emit(EventTypes.IDEA_CAPTURED, data),
|
|
248
|
+
|
|
249
|
+
snapshotCreated: (data: Record<string, unknown>) => eventBus.emit(EventTypes.SNAPSHOT_CREATED, data),
|
|
250
|
+
snapshotRestored: (data: Record<string, unknown>) => eventBus.emit(EventTypes.SNAPSHOT_RESTORED, data),
|
|
251
|
+
|
|
252
|
+
commitCreated: (data: Record<string, unknown>) => eventBus.emit(EventTypes.COMMIT_CREATED, data),
|
|
253
|
+
pushCompleted: (data: Record<string, unknown>) => eventBus.emit(EventTypes.PUSH_COMPLETED, data),
|
|
254
|
+
|
|
255
|
+
projectInitialized: (data: Record<string, unknown>) => eventBus.emit(EventTypes.PROJECT_INITIALIZED, data),
|
|
256
|
+
projectSynced: (data: Record<string, unknown>) => eventBus.emit(EventTypes.PROJECT_SYNCED, data),
|
|
257
|
+
analysisCompleted: (data: Record<string, unknown>) => eventBus.emit(EventTypes.ANALYSIS_COMPLETED, data)
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
export { EventBus, eventBus, emit }
|
|
261
|
+
export default { EventBus, EventTypes, eventBus, emit }
|
|
262
|
+
|
package/core/bus/index.ts
CHANGED
|
@@ -1,318 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* EventBus - Lightweight Pub/Sub System for prjct-cli
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* Supports sync/async listeners, wildcards, and one-time subscriptions.
|
|
6
|
-
*
|
|
7
|
-
* @version 1.0.0
|
|
4
|
+
* Barrel export for bus module
|
|
8
5
|
*/
|
|
9
6
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
import pathManager from '../infrastructure/path-manager'
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Event Types - All events that can be emitted
|
|
16
|
-
*/
|
|
17
|
-
const EventTypes = {
|
|
18
|
-
// Session events
|
|
19
|
-
SESSION_STARTED: 'session.started',
|
|
20
|
-
SESSION_PAUSED: 'session.paused',
|
|
21
|
-
SESSION_RESUMED: 'session.resumed',
|
|
22
|
-
SESSION_COMPLETED: 'session.completed',
|
|
23
|
-
|
|
24
|
-
// Task events
|
|
25
|
-
TASK_CREATED: 'task.created',
|
|
26
|
-
TASK_COMPLETED: 'task.completed',
|
|
27
|
-
TASK_UPDATED: 'task.updated',
|
|
28
|
-
|
|
29
|
-
// Feature events
|
|
30
|
-
FEATURE_ADDED: 'feature.added',
|
|
31
|
-
FEATURE_SHIPPED: 'feature.shipped',
|
|
32
|
-
FEATURE_UPDATED: 'feature.updated',
|
|
33
|
-
|
|
34
|
-
// Idea events
|
|
35
|
-
IDEA_CAPTURED: 'idea.captured',
|
|
36
|
-
IDEA_PROMOTED: 'idea.promoted',
|
|
37
|
-
|
|
38
|
-
// Snapshot events
|
|
39
|
-
SNAPSHOT_CREATED: 'snapshot.created',
|
|
40
|
-
SNAPSHOT_RESTORED: 'snapshot.restored',
|
|
41
|
-
|
|
42
|
-
// Git events
|
|
43
|
-
COMMIT_CREATED: 'git.commit',
|
|
44
|
-
PUSH_COMPLETED: 'git.push',
|
|
45
|
-
|
|
46
|
-
// System events
|
|
47
|
-
PROJECT_INITIALIZED: 'project.init',
|
|
48
|
-
PROJECT_SYNCED: 'project.sync',
|
|
49
|
-
ANALYSIS_COMPLETED: 'analysis.completed',
|
|
50
|
-
|
|
51
|
-
// Wildcard
|
|
52
|
-
ALL: '*'
|
|
53
|
-
} as const
|
|
54
|
-
|
|
55
|
-
type EventType = typeof EventTypes[keyof typeof EventTypes]
|
|
56
|
-
|
|
57
|
-
interface EventData {
|
|
58
|
-
type: string
|
|
59
|
-
timestamp: string
|
|
60
|
-
projectId: string | null
|
|
61
|
-
[key: string]: unknown
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
type EventCallback = (data: EventData) => void | Promise<void>
|
|
65
|
-
|
|
66
|
-
class EventBus {
|
|
67
|
-
private listeners: Map<string, Set<EventCallback>>
|
|
68
|
-
private onceListeners: Map<string, Set<EventCallback>>
|
|
69
|
-
private history: EventData[]
|
|
70
|
-
private historyLimit: number
|
|
71
|
-
projectId: string | null
|
|
72
|
-
|
|
73
|
-
constructor() {
|
|
74
|
-
this.listeners = new Map()
|
|
75
|
-
this.onceListeners = new Map()
|
|
76
|
-
this.history = []
|
|
77
|
-
this.historyLimit = 100
|
|
78
|
-
this.projectId = null
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* Initialize event bus for a project
|
|
83
|
-
*/
|
|
84
|
-
async initialize(projectId: string): Promise<void> {
|
|
85
|
-
this.projectId = projectId
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
* Subscribe to an event
|
|
90
|
-
*/
|
|
91
|
-
on(event: string, callback: EventCallback): () => void {
|
|
92
|
-
if (!this.listeners.has(event)) {
|
|
93
|
-
this.listeners.set(event, new Set())
|
|
94
|
-
}
|
|
95
|
-
this.listeners.get(event)!.add(callback)
|
|
96
|
-
|
|
97
|
-
// Return unsubscribe function
|
|
98
|
-
return () => this.off(event, callback)
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
/**
|
|
102
|
-
* Subscribe to an event once
|
|
103
|
-
*/
|
|
104
|
-
once(event: string, callback: EventCallback): () => void {
|
|
105
|
-
if (!this.onceListeners.has(event)) {
|
|
106
|
-
this.onceListeners.set(event, new Set())
|
|
107
|
-
}
|
|
108
|
-
this.onceListeners.get(event)!.add(callback)
|
|
109
|
-
|
|
110
|
-
return () => {
|
|
111
|
-
const listeners = this.onceListeners.get(event)
|
|
112
|
-
if (listeners) {
|
|
113
|
-
listeners.delete(callback)
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
/**
|
|
119
|
-
* Unsubscribe from an event
|
|
120
|
-
*/
|
|
121
|
-
off(event: string, callback: EventCallback): void {
|
|
122
|
-
const listeners = this.listeners.get(event)
|
|
123
|
-
if (listeners) {
|
|
124
|
-
listeners.delete(callback)
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
/**
|
|
129
|
-
* Emit an event
|
|
130
|
-
*/
|
|
131
|
-
async emit(event: string, data: Record<string, unknown> = {}): Promise<void> {
|
|
132
|
-
const timestamp = new Date().toISOString()
|
|
133
|
-
const eventData: EventData = {
|
|
134
|
-
type: event,
|
|
135
|
-
timestamp,
|
|
136
|
-
projectId: this.projectId,
|
|
137
|
-
...data
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
// Store in history
|
|
141
|
-
this.history.push(eventData)
|
|
142
|
-
if (this.history.length > this.historyLimit) {
|
|
143
|
-
this.history.shift()
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
// Log event if project initialized
|
|
147
|
-
if (this.projectId) {
|
|
148
|
-
await this.logEvent(eventData)
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
// Get all matching listeners
|
|
152
|
-
const callbacks = this.getMatchingListeners(event)
|
|
153
|
-
|
|
154
|
-
// Execute all callbacks
|
|
155
|
-
const results = await Promise.allSettled(
|
|
156
|
-
callbacks.map(cb => this.executeCallback(cb, eventData))
|
|
157
|
-
)
|
|
158
|
-
|
|
159
|
-
// Log any errors
|
|
160
|
-
results.forEach((result) => {
|
|
161
|
-
if (result.status === 'rejected') {
|
|
162
|
-
console.error(`Event listener error for ${event}:`, result.reason)
|
|
163
|
-
}
|
|
164
|
-
})
|
|
165
|
-
|
|
166
|
-
// Handle once listeners
|
|
167
|
-
const onceCallbacks = this.onceListeners.get(event)
|
|
168
|
-
if (onceCallbacks) {
|
|
169
|
-
for (const cb of onceCallbacks) {
|
|
170
|
-
await this.executeCallback(cb, eventData)
|
|
171
|
-
}
|
|
172
|
-
this.onceListeners.delete(event)
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
// Also trigger wildcard once listeners
|
|
176
|
-
const wildcardOnce = this.onceListeners.get(EventTypes.ALL)
|
|
177
|
-
if (wildcardOnce) {
|
|
178
|
-
for (const cb of wildcardOnce) {
|
|
179
|
-
await this.executeCallback(cb, eventData)
|
|
180
|
-
}
|
|
181
|
-
// Don't delete wildcard once - only for specific events
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
/**
|
|
186
|
-
* Get all listeners that match an event (including wildcards)
|
|
187
|
-
*/
|
|
188
|
-
getMatchingListeners(event: string): EventCallback[] {
|
|
189
|
-
const callbacks: EventCallback[] = []
|
|
190
|
-
|
|
191
|
-
// Exact match
|
|
192
|
-
const exact = this.listeners.get(event)
|
|
193
|
-
if (exact) {
|
|
194
|
-
callbacks.push(...exact)
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
// Wildcard match (*)
|
|
198
|
-
const wildcard = this.listeners.get(EventTypes.ALL)
|
|
199
|
-
if (wildcard) {
|
|
200
|
-
callbacks.push(...wildcard)
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
// Namespace wildcard (e.g., 'session.*' matches 'session.started')
|
|
204
|
-
const namespace = event.split('.')[0]
|
|
205
|
-
const namespaceWildcard = this.listeners.get(`${namespace}.*`)
|
|
206
|
-
if (namespaceWildcard) {
|
|
207
|
-
callbacks.push(...namespaceWildcard)
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
return callbacks
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
/**
|
|
214
|
-
* Execute a callback safely (handles sync and async)
|
|
215
|
-
*/
|
|
216
|
-
async executeCallback(callback: EventCallback, data: EventData): Promise<void> {
|
|
217
|
-
try {
|
|
218
|
-
const result = callback(data)
|
|
219
|
-
if (result instanceof Promise) {
|
|
220
|
-
await result
|
|
221
|
-
}
|
|
222
|
-
} catch (error) {
|
|
223
|
-
console.error('Event callback error:', error)
|
|
224
|
-
throw error
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
/**
|
|
229
|
-
* Log event to persistent storage
|
|
230
|
-
*/
|
|
231
|
-
async logEvent(eventData: EventData): Promise<void> {
|
|
232
|
-
try {
|
|
233
|
-
const globalPath = pathManager.getGlobalProjectPath(this.projectId!)
|
|
234
|
-
const eventsPath = path.join(globalPath, 'memory', 'events.jsonl')
|
|
235
|
-
|
|
236
|
-
// Ensure directory exists
|
|
237
|
-
await fs.mkdir(path.dirname(eventsPath), { recursive: true })
|
|
238
|
-
|
|
239
|
-
// Append event
|
|
240
|
-
const line = JSON.stringify(eventData) + '\n'
|
|
241
|
-
await fs.appendFile(eventsPath, line)
|
|
242
|
-
} catch {
|
|
243
|
-
// Silently fail - logging should not break functionality
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
/**
|
|
248
|
-
* Get recent events from history
|
|
249
|
-
*/
|
|
250
|
-
getHistory(limit: number = 10, type: string | null = null): EventData[] {
|
|
251
|
-
let events = this.history
|
|
252
|
-
if (type) {
|
|
253
|
-
events = events.filter(e => e.type === type || e.type.startsWith(type))
|
|
254
|
-
}
|
|
255
|
-
return events.slice(-limit)
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
/**
|
|
259
|
-
* Clear all listeners
|
|
260
|
-
*/
|
|
261
|
-
clear(): void {
|
|
262
|
-
this.listeners.clear()
|
|
263
|
-
this.onceListeners.clear()
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
/**
|
|
267
|
-
* Get count of listeners for an event
|
|
268
|
-
*/
|
|
269
|
-
listenerCount(event: string): number {
|
|
270
|
-
const listeners = this.listeners.get(event)
|
|
271
|
-
return listeners ? listeners.size : 0
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
/**
|
|
275
|
-
* Get all registered event types
|
|
276
|
-
*/
|
|
277
|
-
getRegisteredEvents(): string[] {
|
|
278
|
-
return Array.from(this.listeners.keys())
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
// Singleton instance
|
|
283
|
-
const eventBus = new EventBus()
|
|
284
|
-
|
|
285
|
-
// Convenience methods for common events
|
|
286
|
-
const emit = {
|
|
287
|
-
sessionStarted: (data: Record<string, unknown>) => eventBus.emit(EventTypes.SESSION_STARTED, data),
|
|
288
|
-
sessionPaused: (data: Record<string, unknown>) => eventBus.emit(EventTypes.SESSION_PAUSED, data),
|
|
289
|
-
sessionResumed: (data: Record<string, unknown>) => eventBus.emit(EventTypes.SESSION_RESUMED, data),
|
|
290
|
-
sessionCompleted: (data: Record<string, unknown>) => eventBus.emit(EventTypes.SESSION_COMPLETED, data),
|
|
291
|
-
|
|
292
|
-
taskCreated: (data: Record<string, unknown>) => eventBus.emit(EventTypes.TASK_CREATED, data),
|
|
293
|
-
taskCompleted: (data: Record<string, unknown>) => eventBus.emit(EventTypes.TASK_COMPLETED, data),
|
|
294
|
-
|
|
295
|
-
featureAdded: (data: Record<string, unknown>) => eventBus.emit(EventTypes.FEATURE_ADDED, data),
|
|
296
|
-
featureShipped: (data: Record<string, unknown>) => eventBus.emit(EventTypes.FEATURE_SHIPPED, data),
|
|
297
|
-
|
|
298
|
-
ideaCaptured: (data: Record<string, unknown>) => eventBus.emit(EventTypes.IDEA_CAPTURED, data),
|
|
299
|
-
|
|
300
|
-
snapshotCreated: (data: Record<string, unknown>) => eventBus.emit(EventTypes.SNAPSHOT_CREATED, data),
|
|
301
|
-
snapshotRestored: (data: Record<string, unknown>) => eventBus.emit(EventTypes.SNAPSHOT_RESTORED, data),
|
|
302
|
-
|
|
303
|
-
commitCreated: (data: Record<string, unknown>) => eventBus.emit(EventTypes.COMMIT_CREATED, data),
|
|
304
|
-
pushCompleted: (data: Record<string, unknown>) => eventBus.emit(EventTypes.PUSH_COMPLETED, data),
|
|
305
|
-
|
|
306
|
-
projectInitialized: (data: Record<string, unknown>) => eventBus.emit(EventTypes.PROJECT_INITIALIZED, data),
|
|
307
|
-
projectSynced: (data: Record<string, unknown>) => eventBus.emit(EventTypes.PROJECT_SYNCED, data),
|
|
308
|
-
analysisCompleted: (data: Record<string, unknown>) => eventBus.emit(EventTypes.ANALYSIS_COMPLETED, data)
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
export {
|
|
312
|
-
EventBus,
|
|
313
|
-
EventTypes,
|
|
314
|
-
eventBus,
|
|
315
|
-
emit
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
export default { EventBus, EventTypes, eventBus, emit }
|
|
7
|
+
export { EventBus, eventBus, emit, default } from './bus'
|
|
8
|
+
export { EventTypes, type EventData, type EventCallback, type BusEventType } from '../types'
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import path from 'path'
|
|
6
6
|
|
|
7
|
-
import type { CommandResult, AnalyzeOptions,
|
|
7
|
+
import type { CommandResult, AnalyzeOptions, ProjectContext } from '../types'
|
|
8
8
|
import {
|
|
9
9
|
PrjctCommandsBase,
|
|
10
10
|
contextBuilder,
|
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
dateHelper
|
|
15
15
|
} from './base'
|
|
16
16
|
import analyzer from '../domain/analyzer'
|
|
17
|
-
import
|
|
17
|
+
import { generateContext } from '../context/generator'
|
|
18
18
|
import commandInstaller from '../infrastructure/command-installer'
|
|
19
19
|
|
|
20
20
|
export class AnalysisCommands extends PrjctCommandsBase {
|
|
@@ -29,7 +29,7 @@ export class AnalysisCommands extends PrjctCommandsBase {
|
|
|
29
29
|
|
|
30
30
|
analyzer.init(projectPath)
|
|
31
31
|
|
|
32
|
-
const context = await contextBuilder.build(projectPath, options) as
|
|
32
|
+
const context = await contextBuilder.build(projectPath, options) as ProjectContext
|
|
33
33
|
|
|
34
34
|
const analysisData = {
|
|
35
35
|
packageJson: await analyzer.readPackageJson(),
|
|
@@ -71,7 +71,7 @@ export class AnalysisCommands extends PrjctCommandsBase {
|
|
|
71
71
|
gitCommits: analysisData.gitStats.totalCommits,
|
|
72
72
|
})
|
|
73
73
|
|
|
74
|
-
await
|
|
74
|
+
await generateContext(projectId!, projectPath)
|
|
75
75
|
|
|
76
76
|
const globalConfigResult = await commandInstaller.installGlobalConfig()
|
|
77
77
|
if (globalConfigResult.success) {
|
|
@@ -217,7 +217,7 @@ export class AnalysisCommands extends PrjctCommandsBase {
|
|
|
217
217
|
|
|
218
218
|
// 2. Generate CLAUDE.md with RAW DATA (no processing)
|
|
219
219
|
// Claude will read this and decide what to do
|
|
220
|
-
await
|
|
220
|
+
await generateContext(projectId!, projectPath)
|
|
221
221
|
|
|
222
222
|
// 3. Update global config
|
|
223
223
|
const globalConfigResult = await commandInstaller.installGlobalConfig()
|
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import path from 'path'
|
|
7
|
-
import registry from '../command-registry'
|
|
8
7
|
|
|
9
|
-
import
|
|
8
|
+
import { commandRegistry } from './registry'
|
|
9
|
+
import type { CommandResult, ProjectContext } from '../types'
|
|
10
10
|
import {
|
|
11
11
|
PrjctCommandsBase,
|
|
12
12
|
contextBuilder,
|
|
@@ -98,7 +98,7 @@ export class AnalyticsCommands extends PrjctCommandsBase {
|
|
|
98
98
|
|
|
99
99
|
if (view === 'roadmap') {
|
|
100
100
|
// Roadmap view
|
|
101
|
-
const context = await contextBuilder.build(projectPath) as
|
|
101
|
+
const context = await contextBuilder.build(projectPath) as ProjectContext
|
|
102
102
|
const roadmapContent = (await toolRegistry.get('Read')!(context.paths.roadmap)) as string | null
|
|
103
103
|
|
|
104
104
|
console.log(`\n🗺️ ROADMAP - ${projectName}\n`)
|
|
@@ -199,22 +199,22 @@ export class AnalyticsCommands extends PrjctCommandsBase {
|
|
|
199
199
|
try {
|
|
200
200
|
if (!topic) {
|
|
201
201
|
// Show command overview
|
|
202
|
-
console.log('\n
|
|
203
|
-
console.log('
|
|
202
|
+
console.log('\n PRJCT COMMANDS\n')
|
|
203
|
+
console.log('='.repeat(50))
|
|
204
204
|
|
|
205
|
-
const categories =
|
|
206
|
-
const commands =
|
|
205
|
+
const categories = commandRegistry.getAllCategories()
|
|
206
|
+
const commands = commandRegistry.getAll()
|
|
207
207
|
|
|
208
208
|
// Group by category
|
|
209
209
|
const byCategory: Record<string, typeof commands> = {}
|
|
210
210
|
commands.forEach(cmd => {
|
|
211
211
|
if (cmd.deprecated) return
|
|
212
|
-
if (!byCategory[cmd.
|
|
213
|
-
byCategory[cmd.
|
|
212
|
+
if (!byCategory[cmd.group]) byCategory[cmd.group] = []
|
|
213
|
+
byCategory[cmd.group].push(cmd)
|
|
214
214
|
})
|
|
215
215
|
|
|
216
216
|
Object.entries(byCategory).forEach(([cat, cmds]) => {
|
|
217
|
-
const catInfo = categories
|
|
217
|
+
const catInfo = categories.get(cat)
|
|
218
218
|
console.log(`\n${catInfo?.title || cat}:`)
|
|
219
219
|
cmds.forEach(cmd => {
|
|
220
220
|
const params = cmd.params ? ` ${cmd.params}` : ''
|
|
@@ -230,7 +230,7 @@ export class AnalyticsCommands extends PrjctCommandsBase {
|
|
|
230
230
|
}
|
|
231
231
|
|
|
232
232
|
// Topic-specific help
|
|
233
|
-
const command =
|
|
233
|
+
const command = commandRegistry.getByName(topic)
|
|
234
234
|
if (command) {
|
|
235
235
|
console.log(`\n📚 HELP: /p:${command.name}\n`)
|
|
236
236
|
console.log('═'.repeat(50))
|