prjct-cli 0.55.1 → 0.55.3
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 +35 -3
- package/core/__tests__/services/project-index.test.ts +3 -1
- package/core/infrastructure/setup.ts +55 -0
- package/core/integrations/jira/index.ts +0 -15
- package/core/services/sync-service.ts +6 -0
- package/dist/bin/prjct.mjs +38 -2
- package/dist/core/infrastructure/setup.js +33 -0
- package/package.json +2 -2
- package/templates/commands/bug.md +143 -18
- package/templates/commands/dash.md +74 -18
- package/templates/commands/done.md +125 -26
- package/templates/commands/idea.md +64 -9
- package/templates/commands/jira.md +3 -1
- package/templates/commands/linear.md +16 -24
- package/templates/commands/next.md +49 -11
- package/templates/commands/p.md +30 -183
- package/templates/commands/pause.md +115 -18
- package/templates/commands/resume.md +151 -14
- package/templates/commands/sync.md +0 -10
- package/templates/commands/task.md +86 -38
- package/templates/commands/test.md +60 -17
- package/templates/commands/workflow.md +70 -70
- package/templates/mcp-config.json +4 -32
- package/core/integrations/jira/mcp-adapter.ts +0 -446
- package/templates/_bases/tracker-base.md +0 -321
- package/templates/commands/github.md +0 -298
- package/templates/commands/monday.md +0 -243
|
@@ -4,36 +4,133 @@ allowed-tools: [Read, Write, Bash, AskUserQuestion]
|
|
|
4
4
|
|
|
5
5
|
# p. pause "$ARGUMENTS"
|
|
6
6
|
|
|
7
|
+
## Step 1: Resolve Project Paths
|
|
8
|
+
|
|
7
9
|
```bash
|
|
8
|
-
|
|
10
|
+
# Get projectId from local config
|
|
11
|
+
cat .prjct/prjct.config.json | grep -o '"projectId"[[:space:]]*:[[:space:]]*"[^"]*"' | cut -d'"' -f4
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Set `globalPath = ~/.prjct-cli/projects/{projectId}`
|
|
15
|
+
|
|
16
|
+
## Step 2: Read Current State
|
|
17
|
+
|
|
18
|
+
READ `{globalPath}/storage/state.json`
|
|
19
|
+
|
|
9
20
|
```
|
|
21
|
+
IF no currentTask OR currentTask is null:
|
|
22
|
+
OUTPUT: "No active task to pause."
|
|
23
|
+
STOP
|
|
10
24
|
|
|
11
|
-
IF
|
|
12
|
-
|
|
25
|
+
IF currentTask.status == "paused":
|
|
26
|
+
OUTPUT:
|
|
27
|
+
"""
|
|
28
|
+
⏸️ Already paused: {currentTask.description}
|
|
13
29
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
- Researching
|
|
30
|
+
To resume: `p. resume`
|
|
31
|
+
"""
|
|
32
|
+
STOP
|
|
33
|
+
```
|
|
19
34
|
|
|
20
|
-
|
|
35
|
+
## Step 3: Get Pause Reason
|
|
21
36
|
|
|
22
|
-
|
|
37
|
+
```
|
|
38
|
+
IF $ARGUMENTS is empty:
|
|
39
|
+
AskUserQuestion:
|
|
40
|
+
question: "Why are you pausing?"
|
|
41
|
+
header: "Pause"
|
|
42
|
+
options:
|
|
43
|
+
- label: "Blocked (waiting on external)"
|
|
44
|
+
description: "Waiting for review, dependency, or other team"
|
|
45
|
+
- label: "Switching task"
|
|
46
|
+
description: "Need to work on something else"
|
|
47
|
+
- label: "Taking a break"
|
|
48
|
+
description: "Stepping away temporarily"
|
|
49
|
+
- label: "Researching"
|
|
50
|
+
description: "Need to investigate before continuing"
|
|
51
|
+
|
|
52
|
+
SET pauseReason = selected option
|
|
53
|
+
|
|
54
|
+
IF pauseReason == "Blocked":
|
|
55
|
+
ASK: "What's blocking you?"
|
|
56
|
+
SET blockingReason = response
|
|
57
|
+
ELSE:
|
|
58
|
+
SET pauseReason = $ARGUMENTS
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Step 4: Calculate Duration
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
# Get current timestamp
|
|
65
|
+
node -e "console.log(new Date().toISOString())"
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Calculate time elapsed since `currentTask.startedAt` (or `resumedAt` if it exists).
|
|
69
|
+
|
|
70
|
+
## Step 5: Update State
|
|
71
|
+
|
|
72
|
+
READ current state, then update:
|
|
73
|
+
|
|
74
|
+
```
|
|
75
|
+
pausedTask = currentTask
|
|
76
|
+
pausedTask.status = "paused"
|
|
77
|
+
pausedTask.pausedAt = "{timestamp}"
|
|
78
|
+
pausedTask.pauseReason = "{pauseReason}"
|
|
79
|
+
pausedTask.blockingReason = "{blockingReason if set}"
|
|
80
|
+
pausedTask.activeTime = "{duration worked so far}"
|
|
81
|
+
|
|
82
|
+
state.pausedTasks = state.pausedTasks || []
|
|
83
|
+
state.pausedTasks.unshift(pausedTask) # Add to front
|
|
84
|
+
state.currentTask = null
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
WRITE `{globalPath}/storage/state.json`:
|
|
88
|
+
```json
|
|
89
|
+
{
|
|
90
|
+
"currentTask": null,
|
|
91
|
+
"pausedTasks": [
|
|
92
|
+
{
|
|
93
|
+
"id": "{task.id}",
|
|
94
|
+
"description": "{task.description}",
|
|
95
|
+
"type": "{task.type}",
|
|
96
|
+
"status": "paused",
|
|
97
|
+
"startedAt": "{task.startedAt}",
|
|
98
|
+
"pausedAt": "{timestamp}",
|
|
99
|
+
"pauseReason": "{pauseReason}",
|
|
100
|
+
"blockingReason": "{blockingReason or null}",
|
|
101
|
+
"activeTime": "{duration}",
|
|
102
|
+
"subtasks": [...],
|
|
103
|
+
"currentSubtaskIndex": {task.currentSubtaskIndex},
|
|
104
|
+
"parentDescription": "{task.parentDescription}",
|
|
105
|
+
"branch": "{task.branch}",
|
|
106
|
+
"linearId": "{task.linearId or null}"
|
|
107
|
+
},
|
|
108
|
+
...existing paused tasks
|
|
109
|
+
],
|
|
110
|
+
"previousTask": {...}
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## Step 6: Log Event
|
|
115
|
+
|
|
116
|
+
APPEND to `{globalPath}/memory/events.jsonl`:
|
|
117
|
+
```json
|
|
118
|
+
{"type":"task_paused","taskId":"{id}","description":"{description}","reason":"{pauseReason}","timestamp":"{timestamp}","activeTime":"{duration}"}
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
---
|
|
23
122
|
|
|
24
|
-
|
|
25
|
-
- `pausedTask` = currentTask with `status: "paused"`, `pausedAt`, `pauseReason`
|
|
26
|
-
- `currentTask` = null
|
|
123
|
+
## Output
|
|
27
124
|
|
|
28
|
-
**Output**:
|
|
29
125
|
```
|
|
30
|
-
⏸️ Paused: {task}
|
|
126
|
+
⏸️ Paused: {task description}
|
|
31
127
|
|
|
32
|
-
Duration: {time}
|
|
33
|
-
Reason: {
|
|
128
|
+
Duration: {time worked}
|
|
129
|
+
Reason: {pauseReason}
|
|
130
|
+
{IF blockingReason: "Blocked by: {blockingReason}"}
|
|
34
131
|
|
|
35
132
|
Next:
|
|
36
133
|
- Resume → `p. resume`
|
|
37
134
|
- New task → `p. task "description"`
|
|
38
|
-
-
|
|
135
|
+
- Fix bug → `p. bug "description"`
|
|
39
136
|
```
|
|
@@ -4,31 +4,168 @@ allowed-tools: [Read, Write, Bash, Glob, AskUserQuestion]
|
|
|
4
4
|
|
|
5
5
|
# p. resume
|
|
6
6
|
|
|
7
|
+
## Step 1: Resolve Project Paths
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
# Get projectId from local config
|
|
11
|
+
cat .prjct/prjct.config.json | grep -o '"projectId"[[:space:]]*:[[:space:]]*"[^"]*"' | cut -d'"' -f4
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Set `globalPath = ~/.prjct-cli/projects/{projectId}`
|
|
15
|
+
|
|
16
|
+
## Step 2: Read Current State
|
|
17
|
+
|
|
18
|
+
READ `{globalPath}/storage/state.json`
|
|
19
|
+
|
|
20
|
+
```
|
|
21
|
+
IF currentTask exists AND currentTask.status == "active":
|
|
22
|
+
OUTPUT:
|
|
23
|
+
"""
|
|
24
|
+
Already active: {currentTask.description}
|
|
25
|
+
|
|
26
|
+
To pause: `p. pause`
|
|
27
|
+
To complete: `p. done`
|
|
28
|
+
"""
|
|
29
|
+
STOP
|
|
30
|
+
|
|
31
|
+
IF (no pausedTasks OR pausedTasks is empty):
|
|
32
|
+
OUTPUT: "Nothing to resume. Use `p. task` to start a new task."
|
|
33
|
+
STOP
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Step 3: Select Task to Resume
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
IF pausedTasks.length == 1:
|
|
40
|
+
taskToResume = pausedTasks[0]
|
|
41
|
+
ELSE:
|
|
42
|
+
# Multiple paused tasks - let user choose
|
|
43
|
+
Show list:
|
|
44
|
+
"""
|
|
45
|
+
Multiple paused tasks:
|
|
46
|
+
|
|
47
|
+
1. {pausedTasks[0].description} (paused {time ago})
|
|
48
|
+
Reason: {pauseReason}
|
|
49
|
+
2. {pausedTasks[1].description} (paused {time ago})
|
|
50
|
+
Reason: {pauseReason}
|
|
51
|
+
...
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
AskUserQuestion:
|
|
55
|
+
question: "Which task do you want to resume?"
|
|
56
|
+
header: "Resume"
|
|
57
|
+
options:
|
|
58
|
+
- label: "{pausedTasks[0].description}"
|
|
59
|
+
description: "Paused {time ago} - {pauseReason}"
|
|
60
|
+
- label: "{pausedTasks[1].description}"
|
|
61
|
+
description: "Paused {time ago} - {pauseReason}"
|
|
62
|
+
... (up to 4 options)
|
|
63
|
+
|
|
64
|
+
taskToResume = selected task
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Step 4: Calculate Away Duration
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
# Get current timestamp
|
|
71
|
+
node -e "console.log(new Date().toISOString())"
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Calculate time since `taskToResume.pausedAt`.
|
|
75
|
+
|
|
76
|
+
## Step 5: Switch to Task Branch (if needed)
|
|
77
|
+
|
|
7
78
|
```bash
|
|
8
|
-
|
|
79
|
+
git branch --show-current
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
```
|
|
83
|
+
IF taskToResume.branch exists AND currentBranch != taskToResume.branch:
|
|
84
|
+
# Check for uncommitted changes first
|
|
85
|
+
git status --porcelain
|
|
86
|
+
|
|
87
|
+
IF uncommitted changes:
|
|
88
|
+
AskUserQuestion:
|
|
89
|
+
question: "You have uncommitted changes. What should we do?"
|
|
90
|
+
header: "Git"
|
|
91
|
+
options:
|
|
92
|
+
- label: "Stash changes"
|
|
93
|
+
description: "Save changes and switch branches"
|
|
94
|
+
- label: "Commit changes"
|
|
95
|
+
description: "Commit before switching"
|
|
96
|
+
- label: "Cancel resume"
|
|
97
|
+
description: "Stay on current branch"
|
|
98
|
+
|
|
99
|
+
Handle response appropriately
|
|
100
|
+
|
|
101
|
+
git checkout {taskToResume.branch}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## Step 6: Update State
|
|
105
|
+
|
|
106
|
+
```
|
|
107
|
+
resumedTask = taskToResume
|
|
108
|
+
resumedTask.status = "active"
|
|
109
|
+
resumedTask.resumedAt = "{timestamp}"
|
|
110
|
+
resumedTask.pausedDuration = "{away duration}"
|
|
111
|
+
|
|
112
|
+
# Remove from pausedTasks
|
|
113
|
+
state.pausedTasks = state.pausedTasks.filter(t => t.id != resumedTask.id)
|
|
114
|
+
state.currentTask = resumedTask
|
|
9
115
|
```
|
|
10
116
|
|
|
11
|
-
|
|
12
|
-
|
|
117
|
+
WRITE `{globalPath}/storage/state.json`:
|
|
118
|
+
```json
|
|
119
|
+
{
|
|
120
|
+
"currentTask": {
|
|
121
|
+
"id": "{task.id}",
|
|
122
|
+
"description": "{task.description}",
|
|
123
|
+
"type": "{task.type}",
|
|
124
|
+
"status": "active",
|
|
125
|
+
"startedAt": "{task.startedAt}",
|
|
126
|
+
"resumedAt": "{timestamp}",
|
|
127
|
+
"pausedDuration": "{away duration}",
|
|
128
|
+
"subtasks": [...],
|
|
129
|
+
"currentSubtaskIndex": {task.currentSubtaskIndex},
|
|
130
|
+
"parentDescription": "{task.parentDescription}",
|
|
131
|
+
"branch": "{task.branch}",
|
|
132
|
+
"linearId": "{task.linearId or null}"
|
|
133
|
+
},
|
|
134
|
+
"pausedTasks": [...remaining paused tasks],
|
|
135
|
+
"previousTask": {...}
|
|
136
|
+
}
|
|
137
|
+
```
|
|
13
138
|
|
|
14
|
-
|
|
139
|
+
## Step 7: Load Context
|
|
15
140
|
|
|
16
|
-
|
|
141
|
+
Load agents for context:
|
|
142
|
+
```
|
|
143
|
+
GLOB: {globalPath}/agents/*.md
|
|
17
144
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
145
|
+
FOR each agent file:
|
|
146
|
+
READ agent content for domain patterns
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
## Step 8: Log Event
|
|
150
|
+
|
|
151
|
+
APPEND to `{globalPath}/memory/events.jsonl`:
|
|
152
|
+
```json
|
|
153
|
+
{"type":"task_resumed","taskId":"{id}","description":"{description}","timestamp":"{timestamp}","pausedDuration":"{away duration}"}
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
---
|
|
21
157
|
|
|
22
|
-
|
|
158
|
+
## Output
|
|
23
159
|
|
|
24
|
-
**Output**:
|
|
25
160
|
```
|
|
26
|
-
▶️ Resumed: {task}
|
|
161
|
+
▶️ Resumed: {task.parentDescription}
|
|
27
162
|
|
|
28
|
-
Was paused: {
|
|
29
|
-
{IF
|
|
163
|
+
Was paused: {pausedDuration}
|
|
164
|
+
{IF task.subtasks: "Current subtask: {current subtask}"}
|
|
165
|
+
Branch: {task.branch}
|
|
30
166
|
|
|
31
167
|
Next:
|
|
32
|
-
-
|
|
168
|
+
- Continue work → make changes
|
|
169
|
+
- Finish subtask → `p. done`
|
|
33
170
|
- Pause again → `p. pause`
|
|
34
171
|
```
|
|
@@ -89,15 +89,6 @@ IF integrations.linear.enabled:
|
|
|
89
89
|
OUTPUT: "Linear: {fetched} issues synced"
|
|
90
90
|
```
|
|
91
91
|
|
|
92
|
-
## Cursor Router Regeneration
|
|
93
|
-
|
|
94
|
-
If `.cursor/` exists but `.cursor/rules/prjct.mdc` is missing:
|
|
95
|
-
1. Get npm root: `npm root -g`
|
|
96
|
-
2. Create `.cursor/rules/` directory if needed
|
|
97
|
-
3. Copy router: `{npmRoot}/prjct-cli/templates/cursor/router.mdc` → `.cursor/rules/prjct.mdc`
|
|
98
|
-
4. Copy commands: `{npmRoot}/prjct-cli/templates/cursor/p.md` → `.cursor/commands/p.md`
|
|
99
|
-
5. Report: "Cursor routers regenerated"
|
|
100
|
-
|
|
101
92
|
## Output
|
|
102
93
|
|
|
103
94
|
```
|
|
@@ -106,7 +97,6 @@ If `.cursor/` exists but `.cursor/rules/prjct.mdc` is missing:
|
|
|
106
97
|
Ecosystem: {ecosystem}
|
|
107
98
|
Agents: {count} generated
|
|
108
99
|
Linear: {issueCount} issues synced (or "not enabled")
|
|
109
|
-
Cursor: {regenerated/ready/not detected}
|
|
110
100
|
|
|
111
101
|
Next:
|
|
112
102
|
- Start work → `p. task "description"`
|
|
@@ -17,7 +17,16 @@ IF $ARGUMENTS is empty:
|
|
|
17
17
|
DO NOT proceed with empty task
|
|
18
18
|
```
|
|
19
19
|
|
|
20
|
-
### Check 2:
|
|
20
|
+
### Check 2: Resolve Project Paths
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
# Get projectId from local config
|
|
24
|
+
cat .prjct/prjct.config.json | grep -o '"projectId"[[:space:]]*:[[:space:]]*"[^"]*"' | cut -d'"' -f4
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Set `globalPath = ~/.prjct-cli/projects/{projectId}`
|
|
28
|
+
|
|
29
|
+
### Check 3: Check for Active Task
|
|
21
30
|
|
|
22
31
|
```
|
|
23
32
|
READ: {globalPath}/storage/state.json
|
|
@@ -38,7 +47,7 @@ IF currentTask exists AND currentTask.status == "active":
|
|
|
38
47
|
DO NOT automatically switch tasks
|
|
39
48
|
```
|
|
40
49
|
|
|
41
|
-
### Check
|
|
50
|
+
### Check 4: Validate Git State
|
|
42
51
|
|
|
43
52
|
```bash
|
|
44
53
|
git status --porcelain
|
|
@@ -77,9 +86,9 @@ IF integrations.linear.enabled:
|
|
|
77
86
|
IF issue found in local cache:
|
|
78
87
|
USE cached issue data (no API call needed)
|
|
79
88
|
ELSE:
|
|
80
|
-
# Fallback to API
|
|
81
|
-
|
|
82
|
-
|
|
89
|
+
# Fallback to API - sync and retry
|
|
90
|
+
RUN: bun $PRJCT_CLI/core/cli/linear.ts --project {projectId} sync
|
|
91
|
+
RUN: bun $PRJCT_CLI/core/cli/linear.ts --project {projectId} get-local "$ARGUMENTS"
|
|
83
92
|
|
|
84
93
|
IF issue found:
|
|
85
94
|
SET: task.linearId = issue.identifier # "PRJ-123"
|
|
@@ -87,17 +96,15 @@ IF integrations.linear.enabled:
|
|
|
87
96
|
SET: task.description = issue.title
|
|
88
97
|
SET: $ARGUMENTS = issue.title # Use title for task
|
|
89
98
|
|
|
90
|
-
# Mark issue as In Progress in Linear
|
|
91
|
-
|
|
92
|
-
CALL: linearSync.pushStatus(projectId, issue.identifier, 'in_progress')
|
|
99
|
+
# Mark issue as In Progress in Linear
|
|
100
|
+
RUN: bun $PRJCT_CLI/core/cli/linear.ts --project {projectId} status "$ARGUMENTS" "in_progress"
|
|
93
101
|
|
|
94
102
|
OUTPUT: "Linked to Linear: {issue.identifier} - {issue.title}"
|
|
95
103
|
OUTPUT: "Linear: In Progress"
|
|
96
104
|
|
|
97
105
|
ELSE IF integrations.jira.enabled:
|
|
98
106
|
# JIRA issue detected
|
|
99
|
-
|
|
100
|
-
CALL: jiraService.fetchIssue("$ARGUMENTS")
|
|
107
|
+
RUN: bun $PRJCT_CLI/core/cli/jira.ts --project {projectId} get "$ARGUMENTS"
|
|
101
108
|
|
|
102
109
|
IF issue found:
|
|
103
110
|
SET: task.externalId = issue.externalId
|
|
@@ -106,7 +113,7 @@ ELSE IF integrations.jira.enabled:
|
|
|
106
113
|
SET: $ARGUMENTS = issue.title # Use title for task
|
|
107
114
|
|
|
108
115
|
# Mark issue as In Progress in JIRA
|
|
109
|
-
|
|
116
|
+
RUN: bun $PRJCT_CLI/core/cli/jira.ts --project {projectId} status "$ARGUMENTS" "in_progress"
|
|
110
117
|
|
|
111
118
|
OUTPUT: "Linked to JIRA: {issue.externalId} - {issue.title}"
|
|
112
119
|
OUTPUT: "JIRA: In Progress"
|
|
@@ -119,7 +126,29 @@ ELSE:
|
|
|
119
126
|
|
|
120
127
|
## Main Flow
|
|
121
128
|
|
|
122
|
-
### Step A:
|
|
129
|
+
### Step A: Explore Codebase (for context)
|
|
130
|
+
|
|
131
|
+
```
|
|
132
|
+
USE Task(Explore) → find similar code, affected files
|
|
133
|
+
READ {globalPath}/agents/*.md → get domain patterns (if they exist)
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### Step B: Classify Task
|
|
137
|
+
|
|
138
|
+
Determine type based on keywords:
|
|
139
|
+
- `add`, `create`, `implement`, `new` → **feature**
|
|
140
|
+
- `fix`, `repair`, `broken`, `error` → **bug**
|
|
141
|
+
- `improve`, `enhance`, `optimize` → **improvement**
|
|
142
|
+
- `refactor`, `clean`, `reorganize` → **refactor**
|
|
143
|
+
- `update`, `upgrade`, `migrate` → **chore**
|
|
144
|
+
|
|
145
|
+
Default: **feature**
|
|
146
|
+
|
|
147
|
+
### Step C: Break Down into Subtasks
|
|
148
|
+
|
|
149
|
+
Create 2-5 actionable subtasks based on the task description.
|
|
150
|
+
|
|
151
|
+
### Step D: Show Plan and Get Approval (BLOCKING)
|
|
123
152
|
|
|
124
153
|
**⛔ DO NOT create branches or modify state without user approval.**
|
|
125
154
|
|
|
@@ -131,11 +160,16 @@ Description: $ARGUMENTS
|
|
|
131
160
|
Type: {classified type}
|
|
132
161
|
Branch: {type}/{slug}
|
|
133
162
|
|
|
163
|
+
Subtasks:
|
|
164
|
+
1. {subtask 1}
|
|
165
|
+
2. {subtask 2}
|
|
166
|
+
...
|
|
167
|
+
|
|
134
168
|
Will do:
|
|
135
169
|
1. Create feature branch from current branch
|
|
136
170
|
2. Initialize task tracking in state.json
|
|
137
|
-
3.
|
|
138
|
-
|
|
171
|
+
3. Begin work on first subtask
|
|
172
|
+
{If Linear: 4. Update issue status to In Progress}
|
|
139
173
|
```
|
|
140
174
|
|
|
141
175
|
Then ask for confirmation:
|
|
@@ -167,27 +201,16 @@ STOP - Do not continue
|
|
|
167
201
|
```
|
|
168
202
|
|
|
169
203
|
**If "Yes, start task":**
|
|
170
|
-
CONTINUE to Step
|
|
204
|
+
CONTINUE to Step E
|
|
171
205
|
|
|
172
|
-
### Step
|
|
173
|
-
|
|
174
|
-
```
|
|
175
|
-
USE Task(Explore) → find similar code, affected files
|
|
176
|
-
READ agents/*.md → get domain patterns
|
|
177
|
-
```
|
|
178
|
-
|
|
179
|
-
### Step C: Classify Task
|
|
180
|
-
|
|
181
|
-
Determine type: feature | bug | improvement | refactor | chore
|
|
182
|
-
|
|
183
|
-
### Step D: Create Branch (if needed)
|
|
206
|
+
### Step E: Create Branch (if needed)
|
|
184
207
|
|
|
185
208
|
```bash
|
|
186
|
-
|
|
209
|
+
git branch --show-current
|
|
187
210
|
```
|
|
188
211
|
|
|
189
212
|
```
|
|
190
|
-
IF
|
|
213
|
+
IF current branch == "main" OR "master":
|
|
191
214
|
OUTPUT: "Creating feature branch: {type}/{slug}"
|
|
192
215
|
|
|
193
216
|
git checkout -b {type}/{slug}
|
|
@@ -197,8 +220,15 @@ IF CURRENT_BRANCH == "main" OR CURRENT_BRANCH == "master":
|
|
|
197
220
|
STOP
|
|
198
221
|
```
|
|
199
222
|
|
|
223
|
+
### Step F: Write State
|
|
224
|
+
|
|
225
|
+
Generate UUID and timestamp:
|
|
200
226
|
```bash
|
|
201
|
-
|
|
227
|
+
# UUID
|
|
228
|
+
node -e "console.log(require('crypto').randomUUID())"
|
|
229
|
+
|
|
230
|
+
# Timestamp
|
|
231
|
+
node -e "console.log(new Date().toISOString())"
|
|
202
232
|
```
|
|
203
233
|
|
|
204
234
|
WRITE `{globalPath}/storage/state.json`:
|
|
@@ -206,25 +236,43 @@ WRITE `{globalPath}/storage/state.json`:
|
|
|
206
236
|
{
|
|
207
237
|
"currentTask": {
|
|
208
238
|
"id": "{uuid}",
|
|
209
|
-
"description": "{subtask}",
|
|
239
|
+
"description": "{first subtask description}",
|
|
210
240
|
"type": "{type}",
|
|
211
241
|
"status": "active",
|
|
212
|
-
"startedAt": "{
|
|
213
|
-
"subtasks": [
|
|
242
|
+
"startedAt": "{timestamp}",
|
|
243
|
+
"subtasks": [
|
|
244
|
+
{"description": "{subtask 1}", "status": "active"},
|
|
245
|
+
{"description": "{subtask 2}", "status": "pending"},
|
|
246
|
+
{"description": "{subtask 3}", "status": "pending"}
|
|
247
|
+
],
|
|
214
248
|
"currentSubtaskIndex": 0,
|
|
215
249
|
"parentDescription": "$ARGUMENTS",
|
|
216
|
-
"
|
|
217
|
-
"
|
|
218
|
-
|
|
250
|
+
"branch": "{type}/{slug}",
|
|
251
|
+
"linearId": "{identifier or null}",
|
|
252
|
+
"linearUuid": "{uuid or null}"
|
|
253
|
+
},
|
|
254
|
+
"pausedTasks": []
|
|
219
255
|
}
|
|
220
256
|
```
|
|
221
257
|
|
|
222
|
-
|
|
258
|
+
### Step G: Log Event
|
|
259
|
+
|
|
260
|
+
APPEND to `{globalPath}/memory/events.jsonl`:
|
|
261
|
+
```json
|
|
262
|
+
{"type":"task_started","taskId":"{uuid}","description":"$ARGUMENTS","timestamp":"{timestamp}","branch":"{branch}"}
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
---
|
|
266
|
+
|
|
267
|
+
## Output
|
|
268
|
+
|
|
223
269
|
```
|
|
224
|
-
{type}: $ARGUMENTS
|
|
270
|
+
✅ {type}: $ARGUMENTS
|
|
225
271
|
|
|
226
272
|
Branch: {branch} | Subtasks: {count}
|
|
227
273
|
{linearId ? "Linear: {linearId} → In Progress" : ""}
|
|
228
274
|
|
|
275
|
+
Current: {first subtask}
|
|
276
|
+
|
|
229
277
|
Next: Work on subtask, then `p. done`
|
|
230
278
|
```
|
|
@@ -4,47 +4,90 @@ allowed-tools: [Bash, Read, Write]
|
|
|
4
4
|
|
|
5
5
|
# p. test
|
|
6
6
|
|
|
7
|
+
## Step 1: Detect Test Runner
|
|
8
|
+
|
|
9
|
+
Check project files to determine the test runner:
|
|
10
|
+
|
|
7
11
|
```bash
|
|
8
|
-
|
|
12
|
+
# Check for package.json with test script
|
|
13
|
+
if [ -f package.json ]; then
|
|
14
|
+
cat package.json | grep -o '"test"[[:space:]]*:' && echo "node"
|
|
15
|
+
fi
|
|
16
|
+
|
|
17
|
+
# Check for pytest
|
|
18
|
+
if [ -f pytest.ini ] || [ -f pyproject.toml ]; then
|
|
19
|
+
echo "pytest"
|
|
20
|
+
fi
|
|
21
|
+
|
|
22
|
+
# Check for Cargo (Rust)
|
|
23
|
+
if [ -f Cargo.toml ]; then
|
|
24
|
+
echo "cargo"
|
|
25
|
+
fi
|
|
26
|
+
|
|
27
|
+
# Check for Go
|
|
28
|
+
if [ -f go.mod ]; then
|
|
29
|
+
echo "go"
|
|
30
|
+
fi
|
|
31
|
+
|
|
32
|
+
# Check for .NET
|
|
33
|
+
if ls *.sln *.csproj 2>/dev/null | head -1; then
|
|
34
|
+
echo "dotnet"
|
|
35
|
+
fi
|
|
9
36
|
```
|
|
10
37
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
38
|
+
| Detected | Runner Command |
|
|
39
|
+
|----------|----------------|
|
|
40
|
+
| package.json with scripts.test | `npm test` or `bun test` or `pnpm test` |
|
|
41
|
+
| pytest.ini / pyproject.toml | `pytest` |
|
|
42
|
+
| Cargo.toml | `cargo test` |
|
|
43
|
+
| go.mod | `go test ./...` |
|
|
44
|
+
| *.sln / *.csproj | `dotnet test` |
|
|
45
|
+
|
|
46
|
+
## Step 2: Run Tests
|
|
17
47
|
|
|
18
|
-
Run tests:
|
|
19
48
|
```bash
|
|
20
49
|
{runnerCmd} 2>&1
|
|
21
50
|
```
|
|
22
51
|
|
|
23
|
-
Parse results
|
|
52
|
+
Parse results to count passed/failed tests.
|
|
24
53
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
54
|
+
## Step 3: Handle Results
|
|
55
|
+
|
|
56
|
+
**IF all tests pass:**
|
|
28
57
|
|
|
29
|
-
**Output (pass)**:
|
|
30
58
|
```
|
|
31
59
|
✅ Tests passing
|
|
32
60
|
|
|
33
61
|
Passed: {count}
|
|
34
|
-
Coverage: {%}
|
|
62
|
+
Coverage: {%} (if available)
|
|
35
63
|
|
|
36
64
|
Next:
|
|
37
65
|
- Code review → `p. review`
|
|
38
66
|
- Ship → `p. ship`
|
|
39
67
|
```
|
|
40
68
|
|
|
41
|
-
**
|
|
69
|
+
**IF tests fail:**
|
|
70
|
+
|
|
42
71
|
```
|
|
43
72
|
❌ {failed} tests failing
|
|
44
73
|
|
|
45
|
-
{test output}
|
|
74
|
+
{test output - last 50 lines}
|
|
46
75
|
|
|
47
76
|
Next:
|
|
48
|
-
- Auto-fix → `p. test fix`
|
|
77
|
+
- Auto-fix snapshots → `p. test fix`
|
|
49
78
|
- Fix manually and re-run
|
|
50
79
|
```
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
## Fix Mode (`p. test fix`)
|
|
84
|
+
|
|
85
|
+
IF mode == "fix":
|
|
86
|
+
Try updating snapshots:
|
|
87
|
+
```bash
|
|
88
|
+
{runnerCmd} -- -u
|
|
89
|
+
# or for jest: npm test -- -u
|
|
90
|
+
# or for vitest: npx vitest --update
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Re-run tests to verify fix.
|