prjct-cli 0.10.13 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +58 -0
- package/CLAUDE.md +47 -2
- package/bin/dev.js +217 -0
- package/bin/prjct +10 -0
- package/bin/serve.js +78 -0
- package/core/agentic/command-executor.js +38 -112
- package/core/agentic/prompt-builder.js +72 -0
- package/core/bus/index.js +322 -0
- package/core/command-registry.js +65 -0
- package/core/domain/snapshot-manager.js +375 -0
- package/core/plugin/hooks.js +313 -0
- package/core/plugin/index.js +52 -0
- package/core/plugin/loader.js +331 -0
- package/core/plugin/registry.js +325 -0
- package/core/plugins/webhook.js +143 -0
- package/core/session/index.js +449 -0
- package/core/session/metrics.js +293 -0
- package/package.json +18 -4
- package/templates/agentic/agent-routing.md +42 -9
- package/templates/agentic/checklist-routing.md +98 -0
- package/templates/checklists/accessibility.md +33 -0
- package/templates/checklists/architecture.md +28 -0
- package/templates/checklists/code-quality.md +28 -0
- package/templates/checklists/data.md +33 -0
- package/templates/checklists/documentation.md +33 -0
- package/templates/checklists/infrastructure.md +33 -0
- package/templates/checklists/performance.md +33 -0
- package/templates/checklists/security.md +33 -0
- package/templates/checklists/testing.md +33 -0
- package/templates/checklists/ux-ui.md +37 -0
- package/templates/commands/bug.md +27 -1
- package/templates/commands/done.md +176 -54
- package/templates/commands/feature.md +38 -1
- package/templates/commands/history.md +176 -0
- package/templates/commands/init.md +28 -1
- package/templates/commands/now.md +191 -9
- package/templates/commands/pause.md +176 -12
- package/templates/commands/redo.md +142 -0
- package/templates/commands/resume.md +166 -62
- package/templates/commands/serve.md +121 -0
- package/templates/commands/ship.md +45 -1
- package/templates/commands/sync.md +34 -1
- package/templates/commands/task.md +27 -1
- package/templates/commands/undo.md +152 -0
|
@@ -7,9 +7,65 @@
|
|
|
7
7
|
* P3.1: Includes think blocks for anti-hallucination
|
|
8
8
|
* P3.3: Includes relevant memories from semantic database
|
|
9
9
|
* P3.4: Includes plan mode instructions
|
|
10
|
+
* P4.1: Includes quality checklists (Claude decides which to apply)
|
|
10
11
|
*/
|
|
11
12
|
|
|
13
|
+
const fs = require('fs')
|
|
14
|
+
const path = require('path')
|
|
15
|
+
|
|
12
16
|
class PromptBuilder {
|
|
17
|
+
constructor() {
|
|
18
|
+
this._checklistsCache = null
|
|
19
|
+
this._checklistRoutingCache = null
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Load quality checklists from templates/checklists/
|
|
24
|
+
* Returns checklist content - Claude decides which to apply
|
|
25
|
+
* NO if/else logic here - just load and provide
|
|
26
|
+
*/
|
|
27
|
+
loadChecklists() {
|
|
28
|
+
if (this._checklistsCache) return this._checklistsCache
|
|
29
|
+
|
|
30
|
+
const checklistsDir = path.join(__dirname, '..', '..', 'templates', 'checklists')
|
|
31
|
+
const checklists = {}
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
if (fs.existsSync(checklistsDir)) {
|
|
35
|
+
const files = fs.readdirSync(checklistsDir).filter(f => f.endsWith('.md'))
|
|
36
|
+
for (const file of files) {
|
|
37
|
+
const name = file.replace('.md', '')
|
|
38
|
+
const content = fs.readFileSync(path.join(checklistsDir, file), 'utf-8')
|
|
39
|
+
checklists[name] = content
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
} catch (err) {
|
|
43
|
+
// Silent fail - checklists are optional enhancement
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
this._checklistsCache = checklists
|
|
47
|
+
return checklists
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Load checklist routing template
|
|
52
|
+
* Claude reads this to decide which checklists to apply
|
|
53
|
+
*/
|
|
54
|
+
loadChecklistRouting() {
|
|
55
|
+
if (this._checklistRoutingCache) return this._checklistRoutingCache
|
|
56
|
+
|
|
57
|
+
const routingPath = path.join(__dirname, '..', '..', 'templates', 'agentic', 'checklist-routing.md')
|
|
58
|
+
|
|
59
|
+
try {
|
|
60
|
+
if (fs.existsSync(routingPath)) {
|
|
61
|
+
this._checklistRoutingCache = fs.readFileSync(routingPath, 'utf-8')
|
|
62
|
+
}
|
|
63
|
+
} catch (err) {
|
|
64
|
+
// Silent fail
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return this._checklistRoutingCache || null
|
|
68
|
+
}
|
|
13
69
|
/**
|
|
14
70
|
* Build concise prompt - only essentials
|
|
15
71
|
* CRITICAL: Includes full agent content if agent is provided
|
|
@@ -154,6 +210,22 @@ class PromptBuilder {
|
|
|
154
210
|
parts.push(`\n## APPROVAL REQUIRED\nShow changes, list affected files, ask for confirmation.\n`)
|
|
155
211
|
}
|
|
156
212
|
|
|
213
|
+
// P4.1: Quality Checklists (Claude decides which to apply)
|
|
214
|
+
// Only for code-modifying commands that benefit from quality gates
|
|
215
|
+
const checklistCommands = ['now', 'build', 'feature', 'design', 'fix', 'bug', 'cleanup', 'spec', 'work']
|
|
216
|
+
if (checklistCommands.includes(commandName)) {
|
|
217
|
+
const routing = this.loadChecklistRouting()
|
|
218
|
+
const checklists = this.loadChecklists()
|
|
219
|
+
|
|
220
|
+
if (routing && Object.keys(checklists).length > 0) {
|
|
221
|
+
parts.push('\n## QUALITY CHECKLISTS\n')
|
|
222
|
+
parts.push('Apply relevant checklists based on task. Read checklist-routing.md for guidance.\n')
|
|
223
|
+
parts.push(`Available: ${Object.keys(checklists).join(', ')}\n`)
|
|
224
|
+
parts.push('Path: templates/checklists/{name}.md\n')
|
|
225
|
+
parts.push('Use Read tool to load checklists you determine are relevant.\n')
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
157
229
|
// Simple execution directive
|
|
158
230
|
parts.push('\nEXECUTE: Follow flow. Use tools. Decide.\n')
|
|
159
231
|
|
|
@@ -0,0 +1,322 @@
|
|
|
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
|
+
const fs = require('fs').promises
|
|
11
|
+
const path = require('path')
|
|
12
|
+
const pathManager = require('../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
|
+
}
|
|
54
|
+
|
|
55
|
+
class EventBus {
|
|
56
|
+
constructor() {
|
|
57
|
+
this.listeners = new Map()
|
|
58
|
+
this.onceListeners = new Map()
|
|
59
|
+
this.history = []
|
|
60
|
+
this.historyLimit = 100
|
|
61
|
+
this.projectId = null
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Initialize event bus for a project
|
|
66
|
+
* @param {string} projectId
|
|
67
|
+
*/
|
|
68
|
+
async initialize(projectId) {
|
|
69
|
+
this.projectId = projectId
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Subscribe to an event
|
|
74
|
+
* @param {string} event - Event type or wildcard pattern
|
|
75
|
+
* @param {Function} callback - Handler function
|
|
76
|
+
* @returns {Function} Unsubscribe function
|
|
77
|
+
*/
|
|
78
|
+
on(event, callback) {
|
|
79
|
+
if (!this.listeners.has(event)) {
|
|
80
|
+
this.listeners.set(event, new Set())
|
|
81
|
+
}
|
|
82
|
+
this.listeners.get(event).add(callback)
|
|
83
|
+
|
|
84
|
+
// Return unsubscribe function
|
|
85
|
+
return () => this.off(event, callback)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Subscribe to an event once
|
|
90
|
+
* @param {string} event
|
|
91
|
+
* @param {Function} callback
|
|
92
|
+
* @returns {Function} Unsubscribe function
|
|
93
|
+
*/
|
|
94
|
+
once(event, callback) {
|
|
95
|
+
if (!this.onceListeners.has(event)) {
|
|
96
|
+
this.onceListeners.set(event, new Set())
|
|
97
|
+
}
|
|
98
|
+
this.onceListeners.get(event).add(callback)
|
|
99
|
+
|
|
100
|
+
return () => {
|
|
101
|
+
const listeners = this.onceListeners.get(event)
|
|
102
|
+
if (listeners) {
|
|
103
|
+
listeners.delete(callback)
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Unsubscribe from an event
|
|
110
|
+
* @param {string} event
|
|
111
|
+
* @param {Function} callback
|
|
112
|
+
*/
|
|
113
|
+
off(event, callback) {
|
|
114
|
+
const listeners = this.listeners.get(event)
|
|
115
|
+
if (listeners) {
|
|
116
|
+
listeners.delete(callback)
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Emit an event
|
|
122
|
+
* @param {string} event - Event type
|
|
123
|
+
* @param {Object} data - Event payload
|
|
124
|
+
* @returns {Promise<void>}
|
|
125
|
+
*/
|
|
126
|
+
async emit(event, data = {}) {
|
|
127
|
+
const timestamp = new Date().toISOString()
|
|
128
|
+
const eventData = {
|
|
129
|
+
type: event,
|
|
130
|
+
timestamp,
|
|
131
|
+
projectId: this.projectId,
|
|
132
|
+
...data
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Store in history
|
|
136
|
+
this.history.push(eventData)
|
|
137
|
+
if (this.history.length > this.historyLimit) {
|
|
138
|
+
this.history.shift()
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Log event if project initialized
|
|
142
|
+
if (this.projectId) {
|
|
143
|
+
await this.logEvent(eventData)
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Get all matching listeners
|
|
147
|
+
const callbacks = this.getMatchingListeners(event)
|
|
148
|
+
|
|
149
|
+
// Execute all callbacks
|
|
150
|
+
const results = await Promise.allSettled(
|
|
151
|
+
callbacks.map(cb => this.executeCallback(cb, eventData))
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
// Log any errors
|
|
155
|
+
results.forEach((result, index) => {
|
|
156
|
+
if (result.status === 'rejected') {
|
|
157
|
+
console.error(`Event listener error for ${event}:`, result.reason)
|
|
158
|
+
}
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
// Handle once listeners
|
|
162
|
+
const onceCallbacks = this.onceListeners.get(event)
|
|
163
|
+
if (onceCallbacks) {
|
|
164
|
+
for (const cb of onceCallbacks) {
|
|
165
|
+
await this.executeCallback(cb, eventData)
|
|
166
|
+
}
|
|
167
|
+
this.onceListeners.delete(event)
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Also trigger wildcard once listeners
|
|
171
|
+
const wildcardOnce = this.onceListeners.get(EventTypes.ALL)
|
|
172
|
+
if (wildcardOnce) {
|
|
173
|
+
for (const cb of wildcardOnce) {
|
|
174
|
+
await this.executeCallback(cb, eventData)
|
|
175
|
+
}
|
|
176
|
+
// Don't delete wildcard once - only for specific events
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Get all listeners that match an event (including wildcards)
|
|
182
|
+
* @param {string} event
|
|
183
|
+
* @returns {Function[]}
|
|
184
|
+
*/
|
|
185
|
+
getMatchingListeners(event) {
|
|
186
|
+
const callbacks = []
|
|
187
|
+
|
|
188
|
+
// Exact match
|
|
189
|
+
const exact = this.listeners.get(event)
|
|
190
|
+
if (exact) {
|
|
191
|
+
callbacks.push(...exact)
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Wildcard match (*)
|
|
195
|
+
const wildcard = this.listeners.get(EventTypes.ALL)
|
|
196
|
+
if (wildcard) {
|
|
197
|
+
callbacks.push(...wildcard)
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Namespace wildcard (e.g., 'session.*' matches 'session.started')
|
|
201
|
+
const namespace = event.split('.')[0]
|
|
202
|
+
const namespaceWildcard = this.listeners.get(`${namespace}.*`)
|
|
203
|
+
if (namespaceWildcard) {
|
|
204
|
+
callbacks.push(...namespaceWildcard)
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return callbacks
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Execute a callback safely (handles sync and async)
|
|
212
|
+
* @param {Function} callback
|
|
213
|
+
* @param {Object} data
|
|
214
|
+
*/
|
|
215
|
+
async executeCallback(callback, data) {
|
|
216
|
+
try {
|
|
217
|
+
const result = callback(data)
|
|
218
|
+
if (result instanceof Promise) {
|
|
219
|
+
await result
|
|
220
|
+
}
|
|
221
|
+
} catch (error) {
|
|
222
|
+
console.error('Event callback error:', error)
|
|
223
|
+
throw error
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Log event to persistent storage
|
|
229
|
+
* @param {Object} eventData
|
|
230
|
+
*/
|
|
231
|
+
async logEvent(eventData) {
|
|
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
|
+
* @param {number} limit
|
|
250
|
+
* @param {string} [type] - Optional filter by event type
|
|
251
|
+
* @returns {Object[]}
|
|
252
|
+
*/
|
|
253
|
+
getHistory(limit = 10, type = null) {
|
|
254
|
+
let events = this.history
|
|
255
|
+
if (type) {
|
|
256
|
+
events = events.filter(e => e.type === type || e.type.startsWith(type))
|
|
257
|
+
}
|
|
258
|
+
return events.slice(-limit)
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Clear all listeners
|
|
263
|
+
*/
|
|
264
|
+
clear() {
|
|
265
|
+
this.listeners.clear()
|
|
266
|
+
this.onceListeners.clear()
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Get count of listeners for an event
|
|
271
|
+
* @param {string} event
|
|
272
|
+
* @returns {number}
|
|
273
|
+
*/
|
|
274
|
+
listenerCount(event) {
|
|
275
|
+
const listeners = this.listeners.get(event)
|
|
276
|
+
return listeners ? listeners.size : 0
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Get all registered event types
|
|
281
|
+
* @returns {string[]}
|
|
282
|
+
*/
|
|
283
|
+
getRegisteredEvents() {
|
|
284
|
+
return Array.from(this.listeners.keys())
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// Singleton instance
|
|
289
|
+
const eventBus = new EventBus()
|
|
290
|
+
|
|
291
|
+
// Convenience methods for common events
|
|
292
|
+
const emit = {
|
|
293
|
+
sessionStarted: (data) => eventBus.emit(EventTypes.SESSION_STARTED, data),
|
|
294
|
+
sessionPaused: (data) => eventBus.emit(EventTypes.SESSION_PAUSED, data),
|
|
295
|
+
sessionResumed: (data) => eventBus.emit(EventTypes.SESSION_RESUMED, data),
|
|
296
|
+
sessionCompleted: (data) => eventBus.emit(EventTypes.SESSION_COMPLETED, data),
|
|
297
|
+
|
|
298
|
+
taskCreated: (data) => eventBus.emit(EventTypes.TASK_CREATED, data),
|
|
299
|
+
taskCompleted: (data) => eventBus.emit(EventTypes.TASK_COMPLETED, data),
|
|
300
|
+
|
|
301
|
+
featureAdded: (data) => eventBus.emit(EventTypes.FEATURE_ADDED, data),
|
|
302
|
+
featureShipped: (data) => eventBus.emit(EventTypes.FEATURE_SHIPPED, data),
|
|
303
|
+
|
|
304
|
+
ideaCaptured: (data) => eventBus.emit(EventTypes.IDEA_CAPTURED, data),
|
|
305
|
+
|
|
306
|
+
snapshotCreated: (data) => eventBus.emit(EventTypes.SNAPSHOT_CREATED, data),
|
|
307
|
+
snapshotRestored: (data) => eventBus.emit(EventTypes.SNAPSHOT_RESTORED, data),
|
|
308
|
+
|
|
309
|
+
commitCreated: (data) => eventBus.emit(EventTypes.COMMIT_CREATED, data),
|
|
310
|
+
pushCompleted: (data) => eventBus.emit(EventTypes.PUSH_COMPLETED, data),
|
|
311
|
+
|
|
312
|
+
projectInitialized: (data) => eventBus.emit(EventTypes.PROJECT_INITIALIZED, data),
|
|
313
|
+
projectSynced: (data) => eventBus.emit(EventTypes.PROJECT_SYNCED, data),
|
|
314
|
+
analysisCompleted: (data) => eventBus.emit(EventTypes.ANALYSIS_COMPLETED, data)
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
module.exports = {
|
|
318
|
+
EventBus,
|
|
319
|
+
EventTypes,
|
|
320
|
+
eventBus,
|
|
321
|
+
emit
|
|
322
|
+
}
|
package/core/command-registry.js
CHANGED
|
@@ -427,6 +427,71 @@ const COMMANDS = [
|
|
|
427
427
|
isOptional: true,
|
|
428
428
|
},
|
|
429
429
|
|
|
430
|
+
// ===== SNAPSHOT COMMANDS (Undo/Redo Support) =====
|
|
431
|
+
{
|
|
432
|
+
name: 'undo',
|
|
433
|
+
category: 'optional',
|
|
434
|
+
description: 'Revert to previous snapshot',
|
|
435
|
+
usage: {
|
|
436
|
+
claude: '/p:undo',
|
|
437
|
+
terminal: 'prjct undo',
|
|
438
|
+
},
|
|
439
|
+
params: null,
|
|
440
|
+
implemented: true,
|
|
441
|
+
hasTemplate: true,
|
|
442
|
+
icon: 'RotateCcw',
|
|
443
|
+
requiresInit: true,
|
|
444
|
+
blockingRules: null,
|
|
445
|
+
isOptional: true,
|
|
446
|
+
features: [
|
|
447
|
+
'Git-based snapshots',
|
|
448
|
+
'Preserves redo history',
|
|
449
|
+
'Non-destructive',
|
|
450
|
+
],
|
|
451
|
+
},
|
|
452
|
+
{
|
|
453
|
+
name: 'redo',
|
|
454
|
+
category: 'optional',
|
|
455
|
+
description: 'Redo previously undone changes',
|
|
456
|
+
usage: {
|
|
457
|
+
claude: '/p:redo',
|
|
458
|
+
terminal: 'prjct redo',
|
|
459
|
+
},
|
|
460
|
+
params: null,
|
|
461
|
+
implemented: true,
|
|
462
|
+
hasTemplate: true,
|
|
463
|
+
icon: 'RotateCw',
|
|
464
|
+
requiresInit: true,
|
|
465
|
+
blockingRules: null,
|
|
466
|
+
isOptional: true,
|
|
467
|
+
features: [
|
|
468
|
+
'Only available after undo',
|
|
469
|
+
'Restores undone state',
|
|
470
|
+
'Clears on new snapshot',
|
|
471
|
+
],
|
|
472
|
+
},
|
|
473
|
+
{
|
|
474
|
+
name: 'history',
|
|
475
|
+
category: 'optional',
|
|
476
|
+
description: 'View snapshot history',
|
|
477
|
+
usage: {
|
|
478
|
+
claude: '/p:history',
|
|
479
|
+
terminal: 'prjct history',
|
|
480
|
+
},
|
|
481
|
+
params: null,
|
|
482
|
+
implemented: true,
|
|
483
|
+
hasTemplate: true,
|
|
484
|
+
icon: 'Clock',
|
|
485
|
+
requiresInit: true,
|
|
486
|
+
blockingRules: null,
|
|
487
|
+
isOptional: true,
|
|
488
|
+
features: [
|
|
489
|
+
'Shows all snapshots',
|
|
490
|
+
'Current position indicator',
|
|
491
|
+
'Redo availability count',
|
|
492
|
+
],
|
|
493
|
+
},
|
|
494
|
+
|
|
430
495
|
// ===== SETUP COMMANDS (Not part of daily workflow) =====
|
|
431
496
|
{
|
|
432
497
|
name: 'start',
|