prjct-cli 0.25.2 → 0.27.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.
@@ -1,6 +1,6 @@
1
1
  ---
2
- allowed-tools: [Read, Write, Bash]
3
- description: 'Resume paused session'
2
+ allowed-tools: [Read, Write, Bash, AskUserQuestion]
3
+ description: 'Resume paused or interrupted session'
4
4
  timestamp-rule: 'GetTimestamp() for all timestamps'
5
5
  architecture: 'Write-Through (JSON → MD → Events)'
6
6
  storage-layer: true
@@ -54,7 +54,7 @@ IF file not found:
54
54
  STOP
55
55
 
56
56
  PARSE JSON
57
- EXTRACT: {currentTask}, {pausedTask}
57
+ EXTRACT: {currentTask}, {pausedTask}, {interruptedTask}
58
58
 
59
59
  IF {currentTask} exists AND {currentTask.status} == "active":
60
60
  CALCULATE: {elapsed} = time since last start
@@ -65,47 +65,93 @@ IF {currentTask} exists AND {currentTask.status} == "active":
65
65
  Session: {currentTask.sessionId}
66
66
  Working for: {elapsed}
67
67
 
68
- /p:done to complete | /p:pause to pause
68
+ p. done to complete | p. pause to pause
69
69
  ```
70
70
  STOP
71
71
 
72
- IF {pausedTask} is null:
72
+ ### Handle interruptedTask (priority check)
73
+ IF {interruptedTask} exists AND {pausedTask} exists:
74
+ # Both exist - ask user which to resume
75
+ SET: {interruptedElapsed} = time since interruptedTask.interruptedAt
76
+ SET: {pausedElapsed} = time since pausedTask.pausedAt
77
+
78
+ USE AskUserQuestion:
79
+ ```
80
+ question: "Multiple tasks waiting. Which one to resume?"
81
+ header: "Resume Task"
82
+ options:
83
+ - label: "{interruptedTask.description}"
84
+ description: "Interrupted {interruptedElapsed} ago (by {interruptedTask.interruptReason})"
85
+ - label: "{pausedTask.description}"
86
+ description: "Paused {pausedElapsed} ago ({pausedTask.pauseReason})"
87
+ ```
88
+
89
+ IF choice == interruptedTask.description:
90
+ SET: {taskToResume} = {interruptedTask}
91
+ SET: {resumeType} = "interrupted"
92
+ SET: {clearField} = "interruptedTask"
93
+ ELSE:
94
+ SET: {taskToResume} = {pausedTask}
95
+ SET: {resumeType} = "paused"
96
+ SET: {clearField} = "pausedTask"
97
+
98
+ ELSE IF {interruptedTask} exists:
99
+ # Only interrupted task exists
100
+ SET: {taskToResume} = {interruptedTask}
101
+ SET: {resumeType} = "interrupted"
102
+ SET: {clearField} = "interruptedTask"
103
+
104
+ ELSE IF {pausedTask} exists:
105
+ # Only paused task exists
106
+ SET: {taskToResume} = {pausedTask}
107
+ SET: {resumeType} = "paused"
108
+ SET: {clearField} = "pausedTask"
109
+
110
+ ELSE:
73
111
  OUTPUT:
74
112
  ```
75
- ⚠️ No paused session to resume.
113
+ ⚠️ No paused or interrupted session to resume.
76
114
 
77
115
  Start a new task:
78
- /p:now <task>
116
+ • p. task <task>
79
117
  ```
80
118
  STOP
81
119
 
82
- ## Step 3: Calculate Pause Duration
120
+ ## Step 3: Calculate Away Duration
83
121
 
84
122
  SET: {now} = GetTimestamp()
85
- SET: {pauseDurationSeconds} = seconds between {pausedTask.pausedAt} and {now}
86
- SET: {pauseFormatted} = format as "Xh Ym" or "Xm"
123
+
124
+ IF {resumeType} == "interrupted":
125
+ SET: {awayDurationSeconds} = seconds between {taskToResume.interruptedAt} and {now}
126
+ ELSE:
127
+ SET: {awayDurationSeconds} = seconds between {taskToResume.pausedAt} and {now}
128
+
129
+ SET: {awayFormatted} = format as "Xh Ym" or "Xm"
87
130
 
88
131
  ## Step 4: Update Storage (SOURCE OF TRUTH)
89
132
 
90
133
  ### Prepare resumed task
91
134
  ```json
92
135
  {
93
- "id": "{pausedTask.id}",
94
- "description": "{pausedTask.description}",
136
+ "id": "{taskToResume.id}",
137
+ "description": "{taskToResume.description}",
95
138
  "status": "active",
96
- "startedAt": "{pausedTask.startedAt}",
139
+ "startedAt": "{taskToResume.startedAt}",
97
140
  "resumedAt": "{now}",
98
- "sessionId": "{pausedTask.sessionId}",
99
- "duration": {pausedTask.duration},
100
- "estimate": "{pausedTask.estimate}",
101
- "estimateSeconds": {pausedTask.estimateSeconds}
141
+ "sessionId": "{taskToResume.sessionId}",
142
+ "duration": {taskToResume.duration},
143
+ "estimate": "{taskToResume.estimate}",
144
+ "estimateSeconds": {taskToResume.estimateSeconds},
145
+ "subtasks": {taskToResume.subtasks},
146
+ "currentSubtaskIndex": {taskToResume.currentSubtaskIndex},
147
+ "parentDescription": "{taskToResume.parentDescription}"
102
148
  }
103
149
  ```
104
150
 
105
151
  ### Update state.json
106
152
  READ: `{statePath}`
107
153
  SET: state.currentTask = resumed task object
108
- SET: state.pausedTask = null
154
+ SET: state.{clearField} = null # Clear pausedTask or interruptedTask based on which was resumed
109
155
  SET: state.lastUpdated = {now}
110
156
  WRITE: `{statePath}`
111
157
 
@@ -116,12 +162,13 @@ WRITE: `{nowContextPath}`
116
162
  ```markdown
117
163
  # NOW
118
164
 
119
- **{pausedTask.description}**
165
+ **{taskToResume.description}**
120
166
 
121
- Started: {pausedTask.startedAt}
167
+ Started: {taskToResume.startedAt}
122
168
  Resumed: {now}
123
- Session: {pausedTask.sessionId}
124
- {IF pausedTask.estimate: Estimate: {pausedTask.estimate}}
169
+ Session: {taskToResume.sessionId}
170
+ {IF taskToResume.estimate: Estimate: {taskToResume.estimate}}
171
+ {IF taskToResume.subtasks: Subtask: {currentSubtask.description}}
125
172
  ```
126
173
 
127
174
  ## Step 6: Queue Sync Event (FOR BACKEND)
@@ -133,10 +180,11 @@ APPEND event:
133
180
  "type": "task.resumed",
134
181
  "path": ["state"],
135
182
  "data": {
136
- "taskId": "{pausedTask.id}",
137
- "description": "{pausedTask.description}",
183
+ "taskId": "{taskToResume.id}",
184
+ "description": "{taskToResume.description}",
138
185
  "resumedAt": "{now}",
139
- "pauseDuration": {pauseDurationSeconds}
186
+ "awayDuration": {awayDurationSeconds},
187
+ "resumeType": "{resumeType}"
140
188
  },
141
189
  "timestamp": "{now}",
142
190
  "projectId": "{projectId}"
@@ -148,22 +196,54 @@ WRITE: `{syncPath}`
148
196
 
149
197
  APPEND to: `{memoryPath}`
150
198
 
151
- Single line (JSONL):
199
+ IF {resumeType} == "interrupted":
200
+ ```json
201
+ {"timestamp":"{now}","action":"task_resumed_from_interrupt","taskId":"{taskToResume.id}","sessionId":"{taskToResume.sessionId}","task":"{taskToResume.description}","awayDuration":{awayDurationSeconds}}
202
+ ```
203
+ ELSE:
152
204
  ```json
153
- {"timestamp":"{now}","action":"task_resumed","taskId":"{pausedTask.id}","sessionId":"{pausedTask.sessionId}","task":"{pausedTask.description}","pauseDuration":{pauseDurationSeconds}}
205
+ {"timestamp":"{now}","action":"task_resumed","taskId":"{taskToResume.id}","sessionId":"{taskToResume.sessionId}","task":"{taskToResume.description}","pauseDuration":{awayDurationSeconds}}
154
206
  ```
155
207
 
156
208
  ## Output
157
209
 
158
- SUCCESS:
210
+ ### Resumed from pause (with workflow):
211
+ IF {taskToResume.workflow} exists:
212
+ ```
213
+ ▶️ Resumed: {taskToResume.description}
214
+
215
+ Session: {taskToResume.sessionId}
216
+ Was paused: {awayFormatted}
217
+ Phase: {taskToResume.workflow.phase} ({checkpointCount}/11 checkpoints)
218
+
219
+ Next step based on phase:
220
+ - implement: Continue coding, then p. test
221
+ - test: Run p. test
222
+ - review: Run p. review
223
+ - merge: Run p. merge
224
+ - register: Run p. verify
159
225
  ```
160
- ▶️ Resumed: {pausedTask.description}
161
226
 
162
- Session: {pausedTask.sessionId}
163
- Was paused: {pauseFormatted}
164
- Total active: {pausedTask.duration} (before this stretch)
227
+ ### Resumed from pause (legacy, no workflow):
228
+ ```
229
+ ▶️ Resumed: {taskToResume.description}
165
230
 
166
- /p:done when finished | /p:pause for another break
231
+ Session: {taskToResume.sessionId}
232
+ Was paused: {awayFormatted}
233
+ Total active: {taskToResume.duration} (before this stretch)
234
+
235
+ p. done when finished | p. pause for another break
236
+ ```
237
+
238
+ ### Resumed from interrupt (bug):
239
+ ```
240
+ ▶️ Resumed: {taskToResume.description}
241
+
242
+ Session: {taskToResume.sessionId}
243
+ Interrupted: {awayFormatted} ago (for bug fix)
244
+ Phase: {taskToResume.workflow.phase}
245
+
246
+ Continue where you left off.
167
247
  ```
168
248
 
169
249
  ## Error Handling
@@ -0,0 +1,276 @@
1
+ ---
2
+ allowed-tools: [Bash, Read, Write, Task, AskUserQuestion]
3
+ description: 'Code review with MCP agent and GitHub approvals'
4
+ timestamp-rule: 'GetTimestamp() for ALL timestamps'
5
+ architecture: 'Write-Through (JSON -> MD -> Events)'
6
+ storage-layer: true
7
+ source-of-truth: 'storage/state.json'
8
+ ---
9
+
10
+ # /p:review
11
+
12
+ Run MCP code review agent and wait for GitHub PR approvals.
13
+
14
+ ## Usage
15
+
16
+ ```
17
+ /p:review [--skip-mcp] # Skip MCP agent review
18
+ ```
19
+
20
+ ## Context Variables
21
+ - `{projectId}`: From `.prjct/prjct.config.json`
22
+ - `{globalPath}`: `~/.prjct-cli/projects/{projectId}`
23
+ - `{statePath}`: `{globalPath}/storage/state.json`
24
+ - `{memoryPath}`: `{globalPath}/memory/events.jsonl`
25
+ - `{syncPath}`: `{globalPath}/sync/pending.json`
26
+
27
+ ## Step 1: Validate Project
28
+
29
+ READ: `.prjct/prjct.config.json`
30
+ EXTRACT: `projectId`
31
+
32
+ IF file not found:
33
+ OUTPUT: "No prjct project. Run /p:init first."
34
+ STOP
35
+
36
+ ## Step 2: Validate Workflow Phase
37
+
38
+ READ: `{globalPath}/storage/state.json`
39
+
40
+ IF currentTask is null:
41
+ OUTPUT: "No active task. Use p. task to start one."
42
+ STOP
43
+
44
+ IF currentTask.workflow exists:
45
+ IF currentTask.workflow.phase != "test":
46
+ OUTPUT:
47
+ ```
48
+ Cannot start review. Current phase: {currentTask.workflow.phase}
49
+
50
+ Required phase: test
51
+
52
+ Workflow: analyze → branch → implement → test → review → merge → ship → verify
53
+
54
+ Run p. test first to advance to test phase.
55
+ ```
56
+ STOP
57
+
58
+ ## Step 3: Run MCP Code Review (unless --skip-mcp)
59
+
60
+ IF NOT --skip-mcp:
61
+ OUTPUT: "Running MCP code review..."
62
+
63
+ ### Get changed files
64
+ BASH: `git diff --name-only HEAD~1..HEAD 2>/dev/null || git diff --name-only`
65
+ SET: {changedFiles} = result
66
+
67
+ ### Analyze with MCP agent
68
+ FOR each file in {changedFiles}:
69
+ READ: file
70
+ ANALYZE for:
71
+ - Security issues (hardcoded secrets, injection vulnerabilities)
72
+ - Logic errors
73
+ - Missing error handling
74
+ - Performance issues
75
+ - Code style violations
76
+
77
+ ASSIGN confidence score (0-100):
78
+ - 90-100: Definite bug/security issue
79
+ - 70-89: Likely problem
80
+ - 50-69: Maybe a problem
81
+ - 0-49: Nitpick/style
82
+
83
+ SET: {issues} = issues with confidence >= 70
84
+ SET: {mcpScore} = 100 - (count of high-confidence issues * 10)
85
+
86
+ IF {issues}.length > 0:
87
+ OUTPUT:
88
+ ```
89
+ ## MCP Code Review Results
90
+
91
+ Found {issues.length} issues (confidence >= 70%):
92
+
93
+ {FOR each issue:}
94
+ - [{confidence}%] {description}
95
+ File: {file}:{line}
96
+ {END FOR}
97
+ ```
98
+
99
+ USE AskUserQuestion:
100
+ ```
101
+ question: "Code review found {issues.length} issues. How to proceed?"
102
+ header: "Review Issues"
103
+ options:
104
+ - label: "Fix issues first"
105
+ description: "Return to implement phase to fix"
106
+ - label: "Proceed anyway"
107
+ description: "Continue with PR creation"
108
+ ```
109
+
110
+ IF choice == "Fix issues first":
111
+ OUTPUT: "Returning to implement phase. Fix issues and run p. test again."
112
+ STOP
113
+ ELSE:
114
+ OUTPUT: "✓ MCP review passed. No high-confidence issues found."
115
+ SET: {mcpScore} = 100
116
+
117
+ ## Step 4: Create/Check PR
118
+
119
+ ### Check if PR exists
120
+ BASH: `gh pr view --json url,number,state 2>/dev/null`
121
+
122
+ IF PR exists:
123
+ SET: {prUrl} = result.url
124
+ SET: {prNumber} = result.number
125
+ SET: {prState} = result.state
126
+ OUTPUT: "PR exists: {prUrl}"
127
+ ELSE:
128
+ OUTPUT: "Creating PR..."
129
+
130
+ SET: {branchName} = currentTask.branch.name
131
+ SET: {baseBranch} = currentTask.branch.baseBranch OR "main"
132
+
133
+ BASH: `git push -u origin {branchName} 2>&1`
134
+
135
+ SET: {prTitle} = "{currentTask.type}: {currentTask.description}"
136
+ SET: {prBody} = """
137
+ ## Summary
138
+ {currentTask.description}
139
+
140
+ ## Workflow Phase
141
+ - [x] Analyze
142
+ - [x] Branch
143
+ - [x] Implement
144
+ - [x] Test
145
+ - [ ] Review ← current
146
+ - [ ] Merge
147
+ - [ ] Ship
148
+ - [ ] Verify
149
+
150
+ ## MCP Review Score
151
+ {mcpScore}/100
152
+
153
+ ---
154
+ 🤖 Generated with [p/](https://www.prjct.app/)
155
+ """
156
+
157
+ BASH: `gh pr create --title "{prTitle}" --base {baseBranch} --body "$(cat <<'PREOF'
158
+ {prBody}
159
+ PREOF
160
+ )"`
161
+
162
+ EXTRACT: {prUrl}, {prNumber} from output
163
+
164
+ ## Step 5: Check GitHub Approvals
165
+
166
+ OUTPUT: "Checking for approvals..."
167
+
168
+ BASH: `gh pr view {prNumber} --json reviews,reviewDecision`
169
+ SET: {reviews} = result.reviews
170
+ SET: {decision} = result.reviewDecision
171
+
172
+ IF {decision} == "APPROVED":
173
+ SET: {approved} = true
174
+ SET: {approvals} = reviews where state == "APPROVED"
175
+ OUTPUT: "✓ PR approved by {approvals.length} reviewer(s)"
176
+ ELSE IF {decision} == "CHANGES_REQUESTED":
177
+ OUTPUT:
178
+ ```
179
+ ⚠️ Changes requested
180
+
181
+ {FOR each review where state == "CHANGES_REQUESTED":}
182
+ - {review.author}: {review.body}
183
+ {END FOR}
184
+
185
+ Address feedback, push changes, and run p. review again.
186
+ ```
187
+ STOP
188
+ ELSE:
189
+ OUTPUT:
190
+ ```
191
+ ⏳ Waiting for approvals
192
+
193
+ PR: {prUrl}
194
+
195
+ Request review from team members, then run p. review again.
196
+ ```
197
+ STOP
198
+
199
+ ## Step 6: Update Workflow Phase
200
+
201
+ SET: {now} = GetTimestamp()
202
+
203
+ SET: currentTask.workflow.phase = "review"
204
+ SET: currentTask.workflow.checkpoints.review = {
205
+ "completedAt": "{now}",
206
+ "data": {
207
+ "mcpScore": {mcpScore},
208
+ "approvals": {approvals},
209
+ "prUrl": "{prUrl}",
210
+ "prNumber": {prNumber}
211
+ }
212
+ }
213
+ SET: currentTask.workflow.lastCheckpoint = "review"
214
+ SET: currentTask.branch.prUrl = "{prUrl}"
215
+ SET: currentTask.branch.prNumber = {prNumber}
216
+
217
+ WRITE: `{statePath}`
218
+
219
+ ## Step 7: Log Events
220
+
221
+ APPEND to `{memoryPath}`:
222
+ ```json
223
+ {"timestamp":"{now}","action":"phase_advanced","taskId":"{currentTask.id}","from":"test","to":"review"}
224
+ {"timestamp":"{now}","action":"checkpoint_completed","taskId":"{currentTask.id}","checkpoint":"review","data":{"mcpScore":{mcpScore},"approvals":{approvals.length}}}
225
+ ```
226
+
227
+ APPEND to `{syncPath}`:
228
+ ```json
229
+ {"type":"workflow.phase_advanced","data":{"taskId":"{currentTask.id}","from":"test","to":"review","prUrl":"{prUrl}"},"timestamp":"{now}"}
230
+ ```
231
+
232
+ ## Output
233
+
234
+ ```
235
+ ✓ Review Complete
236
+
237
+ Task: {currentTask.description}
238
+ MCP Score: {mcpScore}/100
239
+ Approvals: {approvals.length}
240
+ PR: {prUrl}
241
+
242
+ Phase: review (5/11 checkpoints)
243
+
244
+ Workflow:
245
+ 1. analyze ✓
246
+ 2. branch ✓
247
+ 3. implement ✓
248
+ 4. test ✓
249
+ 5. review ✓
250
+ 6. merge ← next
251
+
252
+ Next: p. merge to merge PR
253
+ ```
254
+
255
+ ## Error Handling
256
+
257
+ | Error | Response | Action |
258
+ |-------|----------|--------|
259
+ | No project | "No prjct project" | STOP |
260
+ | No active task | "No active task" | STOP |
261
+ | Wrong phase | Show required phase | STOP |
262
+ | MCP issues found | Ask user | WAIT |
263
+ | Changes requested | Show feedback | STOP |
264
+ | No approvals | Show PR URL | STOP |
265
+ | gh CLI missing | "Install gh CLI" | STOP |
266
+
267
+ ## Natural Language Triggers
268
+
269
+ - `p. review` -> /p:review
270
+ - `p. code review` -> /p:review
271
+ - `p. pr` -> /p:review
272
+
273
+ ## References
274
+
275
+ - Architecture: `~/.prjct-cli/docs/architecture.md`
276
+ - Workflow: `~/.prjct-cli/docs/workflow.md`