@tcanaud/product-manager 1.0.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/bin/cli.js +49 -0
- package/package.json +31 -0
- package/src/detect.js +16 -0
- package/src/installer.js +143 -0
- package/src/updater.js +63 -0
- package/templates/commands/product.backlog.md +134 -0
- package/templates/commands/product.check.md +192 -0
- package/templates/commands/product.dashboard.md +188 -0
- package/templates/commands/product.intake.md +125 -0
- package/templates/commands/product.promote.md +159 -0
- package/templates/commands/product.triage.md +205 -0
- package/templates/core/backlog.tpl.md +21 -0
- package/templates/core/feedback.tpl.md +23 -0
- package/templates/core/index.yaml +31 -0
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: View product health dashboard — status counts, category distribution, conversion metrics, and warnings.
|
|
3
|
+
handoffs:
|
|
4
|
+
- label: Triage feedbacks
|
|
5
|
+
agent: product.triage
|
|
6
|
+
prompt: Triage new feedbacks into backlogs
|
|
7
|
+
send: true
|
|
8
|
+
- label: Check health
|
|
9
|
+
agent: product.check
|
|
10
|
+
prompt: Run product health check
|
|
11
|
+
send: true
|
|
12
|
+
- label: Browse backlogs
|
|
13
|
+
agent: product.backlog
|
|
14
|
+
prompt: List all backlogs
|
|
15
|
+
send: true
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## User Input
|
|
19
|
+
|
|
20
|
+
```text
|
|
21
|
+
$ARGUMENTS
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
You **MUST** consider the user input before proceeding (if not empty).
|
|
25
|
+
|
|
26
|
+
- If `$ARGUMENTS` contains `--json`: output structured JSON instead of Markdown
|
|
27
|
+
- Otherwise: output Markdown dashboard
|
|
28
|
+
|
|
29
|
+
## Purpose
|
|
30
|
+
|
|
31
|
+
Read-only command that displays a complete product health overview: feedback counts by status, backlog counts by status, category distribution, conversion metrics (feedback-to-backlog rate, backlog-to-feature rate, resolution rate), and warnings for stale feedbacks or critical bugs.
|
|
32
|
+
|
|
33
|
+
## Execution Flow
|
|
34
|
+
|
|
35
|
+
### 1. Validate environment
|
|
36
|
+
|
|
37
|
+
- Check that `.product/` directory exists. If not → **ERROR**: "Product directory not initialized."
|
|
38
|
+
|
|
39
|
+
### 2. Gather data
|
|
40
|
+
|
|
41
|
+
Scan the filesystem directly for accurate counts:
|
|
42
|
+
|
|
43
|
+
**Feedbacks:**
|
|
44
|
+
- Count files in `.product/feedbacks/new/`
|
|
45
|
+
- Count files in `.product/feedbacks/triaged/`
|
|
46
|
+
- Count files in `.product/feedbacks/excluded/`
|
|
47
|
+
- Count files in `.product/feedbacks/resolved/`
|
|
48
|
+
|
|
49
|
+
**Backlogs:**
|
|
50
|
+
- Count files in `.product/backlogs/open/`
|
|
51
|
+
- Count files in `.product/backlogs/in-progress/`
|
|
52
|
+
- Count files in `.product/backlogs/done/`
|
|
53
|
+
- Count files in `.product/backlogs/promoted/`
|
|
54
|
+
- Count files in `.product/backlogs/cancelled/`
|
|
55
|
+
|
|
56
|
+
**Categories:**
|
|
57
|
+
- For each feedback file across all status directories, read the `category` field from frontmatter
|
|
58
|
+
- Count occurrences of each category: `critical-bug`, `bug`, `optimization`, `evolution`, `new-feature`
|
|
59
|
+
|
|
60
|
+
**Backlog priorities:**
|
|
61
|
+
- For each backlog file across all status directories, read the `priority` field
|
|
62
|
+
|
|
63
|
+
### 3. Compute metrics
|
|
64
|
+
|
|
65
|
+
- **Feedback-to-backlog rate**: count of feedbacks with non-empty `linked_to.backlog[]` / total non-excluded feedbacks. If total non-excluded is 0, rate = 0.
|
|
66
|
+
- **Backlog-to-feature rate**: count of promoted backlogs / total backlogs. If total backlogs is 0, rate = 0.
|
|
67
|
+
- **Resolution rate**: count of resolved feedbacks / total feedbacks. If total feedbacks is 0, rate = 0.
|
|
68
|
+
|
|
69
|
+
### 4. Identify warnings
|
|
70
|
+
|
|
71
|
+
- **Stale feedbacks**: For each feedback in `feedbacks/new/`, read the `created` date. If today minus created > 14 days → add stale warning with count and oldest age.
|
|
72
|
+
- **Critical bugs**: For each backlog with `priority: critical` → add critical bug alert with ID and title.
|
|
73
|
+
|
|
74
|
+
### 5. Output — Markdown (default)
|
|
75
|
+
|
|
76
|
+
```markdown
|
|
77
|
+
## Product Dashboard
|
|
78
|
+
|
|
79
|
+
**Last updated**: {today's date}
|
|
80
|
+
|
|
81
|
+
### Feedbacks
|
|
82
|
+
|
|
83
|
+
| Status | Count |
|
|
84
|
+
|--------|-------|
|
|
85
|
+
| New | {count} |
|
|
86
|
+
| Triaged | {count} |
|
|
87
|
+
| Excluded | {count} |
|
|
88
|
+
| Resolved | {count} |
|
|
89
|
+
| **Total** | **{total}** |
|
|
90
|
+
|
|
91
|
+
### Backlogs
|
|
92
|
+
|
|
93
|
+
| Status | Count |
|
|
94
|
+
|--------|-------|
|
|
95
|
+
| Open | {count} |
|
|
96
|
+
| In Progress | {count} |
|
|
97
|
+
| Done | {count} |
|
|
98
|
+
| Promoted | {count} |
|
|
99
|
+
| Cancelled | {count} |
|
|
100
|
+
| **Total** | **{total}** |
|
|
101
|
+
|
|
102
|
+
### Categories
|
|
103
|
+
|
|
104
|
+
| Category | Count | % |
|
|
105
|
+
|----------|-------|---|
|
|
106
|
+
| critical-bug | {count} | {pct}% |
|
|
107
|
+
| bug | {count} | {pct}% |
|
|
108
|
+
| optimization | {count} | {pct}% |
|
|
109
|
+
| evolution | {count} | {pct}% |
|
|
110
|
+
| new-feature | {count} | {pct}% |
|
|
111
|
+
|
|
112
|
+
### Conversion Metrics
|
|
113
|
+
|
|
114
|
+
| Metric | Value |
|
|
115
|
+
|--------|-------|
|
|
116
|
+
| Feedback → Backlog | {rate}% |
|
|
117
|
+
| Backlog → Feature | {rate}% |
|
|
118
|
+
| Resolution rate | {rate}% |
|
|
119
|
+
|
|
120
|
+
### Warnings
|
|
121
|
+
|
|
122
|
+
{If stale feedbacks:}
|
|
123
|
+
- **{count} stale feedback(s)** in `new/` (oldest: {days} days) — run `/product.triage`
|
|
124
|
+
|
|
125
|
+
{If critical bugs:}
|
|
126
|
+
- **{count} critical bug(s)** in backlogs — {id} "{title}"
|
|
127
|
+
|
|
128
|
+
{If no warnings:}
|
|
129
|
+
No warnings.
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
If the product directory is empty (no feedbacks, no backlogs), display the dashboard with all zeros and add:
|
|
133
|
+
> No feedbacks or backlogs yet. Start with `/product.intake`.
|
|
134
|
+
|
|
135
|
+
### 6. Output — JSON (`--json` flag)
|
|
136
|
+
|
|
137
|
+
Output a structured JSON object:
|
|
138
|
+
|
|
139
|
+
```json
|
|
140
|
+
{
|
|
141
|
+
"updated": "{ISO timestamp}",
|
|
142
|
+
"feedbacks": {
|
|
143
|
+
"total": {n},
|
|
144
|
+
"new": {n},
|
|
145
|
+
"triaged": {n},
|
|
146
|
+
"excluded": {n},
|
|
147
|
+
"resolved": {n}
|
|
148
|
+
},
|
|
149
|
+
"backlogs": {
|
|
150
|
+
"total": {n},
|
|
151
|
+
"open": {n},
|
|
152
|
+
"in_progress": {n},
|
|
153
|
+
"done": {n},
|
|
154
|
+
"promoted": {n},
|
|
155
|
+
"cancelled": {n}
|
|
156
|
+
},
|
|
157
|
+
"categories": {
|
|
158
|
+
"critical-bug": {n},
|
|
159
|
+
"bug": {n},
|
|
160
|
+
"optimization": {n},
|
|
161
|
+
"evolution": {n},
|
|
162
|
+
"new-feature": {n}
|
|
163
|
+
},
|
|
164
|
+
"metrics": {
|
|
165
|
+
"feedback_to_backlog_rate": {float},
|
|
166
|
+
"backlog_to_feature_rate": {float},
|
|
167
|
+
"resolution_rate": {float}
|
|
168
|
+
},
|
|
169
|
+
"warnings": [
|
|
170
|
+
{ "type": "stale_feedbacks", "count": {n}, "oldest_days": {n} },
|
|
171
|
+
{ "type": "critical_bug", "id": "{BL-xxx}", "title": "{title}" }
|
|
172
|
+
]
|
|
173
|
+
}
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
## Error Handling
|
|
177
|
+
|
|
178
|
+
- `.product/` missing → ERROR with init instructions
|
|
179
|
+
- Empty `.product/` → display dashboard with all zeros and a note
|
|
180
|
+
|
|
181
|
+
## Rules
|
|
182
|
+
|
|
183
|
+
- This command is **READ-ONLY** — no files are modified
|
|
184
|
+
- ALWAYS scan the filesystem directly for accurate counts — do not rely solely on index.yaml
|
|
185
|
+
- ALWAYS compute percentages as integers (rounded)
|
|
186
|
+
- ALWAYS show all categories even if count is 0
|
|
187
|
+
- ALWAYS show all status groups even if count is 0
|
|
188
|
+
- NEVER modify any files
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Capture feedback from free text or inbox files into structured .product/feedbacks/new/ entries.
|
|
3
|
+
handoffs:
|
|
4
|
+
- label: Triage feedbacks
|
|
5
|
+
agent: product.triage
|
|
6
|
+
prompt: Triage new feedbacks into backlogs
|
|
7
|
+
send: true
|
|
8
|
+
- label: View dashboard
|
|
9
|
+
agent: product.dashboard
|
|
10
|
+
prompt: View product health dashboard
|
|
11
|
+
send: true
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## User Input
|
|
15
|
+
|
|
16
|
+
```text
|
|
17
|
+
$ARGUMENTS
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
You **MUST** consider the user input before proceeding (if not empty).
|
|
21
|
+
|
|
22
|
+
## Purpose
|
|
23
|
+
|
|
24
|
+
Capture user feedback — either from free-text arguments or from files dropped in `.product/inbox/` — and create structured feedback entries in `.product/feedbacks/new/` with proper YAML frontmatter. This is the entry point for all product feedback.
|
|
25
|
+
|
|
26
|
+
## Execution Flow
|
|
27
|
+
|
|
28
|
+
### 1. Validate environment
|
|
29
|
+
|
|
30
|
+
- Check that `.product/` directory exists. If not → **ERROR**: "Product directory not initialized. Run `npx @tcanaud/product-manager init` first."
|
|
31
|
+
- Check that `.product/_templates/feedback.tpl.md` exists. If not → **ERROR**: "Feedback template missing. Run `npx @tcanaud/product-manager update` to restore templates."
|
|
32
|
+
|
|
33
|
+
### 2. Determine intake mode
|
|
34
|
+
|
|
35
|
+
- **If `$ARGUMENTS` is non-empty**: Free-text mode (Mode 1) is active
|
|
36
|
+
- **Check `.product/inbox/`**: If inbox has files, inbox mode (Mode 2) is active
|
|
37
|
+
- **If both**: Combined mode — process both
|
|
38
|
+
- **If neither**: **INFO**: "No feedback to process. Provide a description or drop files in `.product/inbox/`." → STOP
|
|
39
|
+
|
|
40
|
+
### 3. Assign next ID
|
|
41
|
+
|
|
42
|
+
Scan ALL `.product/feedbacks/` subdirectories (`new/`, `triaged/`, `excluded/`, `resolved/`) for existing feedback files. Extract the numeric portion from filenames matching `FB-xxx.md`. Find the highest number and increment by 1. Zero-pad to 3 digits.
|
|
43
|
+
|
|
44
|
+
Example: If highest existing is `FB-012.md` → next ID is `FB-013`.
|
|
45
|
+
If no feedbacks exist → start at `FB-001`.
|
|
46
|
+
|
|
47
|
+
### 4. Mode 1 — Free-text intake (if `$ARGUMENTS` provided)
|
|
48
|
+
|
|
49
|
+
1. Read `.product/_templates/feedback.tpl.md` for the schema
|
|
50
|
+
2. Analyze the free-text content to:
|
|
51
|
+
- Extract a concise **title** (max 80 chars) summarizing the feedback
|
|
52
|
+
- Propose a **category** from: `critical-bug`, `bug`, `optimization`, `evolution`, `new-feature`
|
|
53
|
+
- Determine **source**: default to `user`
|
|
54
|
+
3. Create the feedback file at `.product/feedbacks/new/FB-{NNN}.md`:
|
|
55
|
+
- Replace `{{id}}` with `FB-{NNN}`
|
|
56
|
+
- Replace `{{title}}` with the extracted title
|
|
57
|
+
- Replace `{{category}}` with the proposed category
|
|
58
|
+
- Replace `{{source}}` with `user`
|
|
59
|
+
- Replace `{{reporter}}` with the git user name (from `git config user.name`) or `unknown`
|
|
60
|
+
- Replace `{{created}}` and `{{updated}}` with today's date (YYYY-MM-DD)
|
|
61
|
+
- Replace `{{body}}` with the full free-text description
|
|
62
|
+
4. Record this feedback for the summary report
|
|
63
|
+
|
|
64
|
+
### 5. Mode 2 — Inbox processing (if `.product/inbox/` has files)
|
|
65
|
+
|
|
66
|
+
1. List all files in `.product/inbox/`
|
|
67
|
+
2. For each file:
|
|
68
|
+
a. Read the file content
|
|
69
|
+
b. If the file appears to be binary or empty → **WARN**: "Skipped {filename}: unrecognizable content. Left in inbox for manual review." → skip
|
|
70
|
+
c. Check for YAML frontmatter (between `---` markers):
|
|
71
|
+
- If present: extract `source`, `reporter`, `timestamp` fields
|
|
72
|
+
- If absent: infer metadata from content
|
|
73
|
+
d. Assign next sequential ID (continuing from Mode 1 if both modes active)
|
|
74
|
+
e. Analyze content to propose category and extract title
|
|
75
|
+
f. Create structured feedback in `.product/feedbacks/new/FB-{NNN}.md`:
|
|
76
|
+
- Use extracted/inferred metadata for frontmatter fields
|
|
77
|
+
- Use the file content (minus frontmatter) as the body
|
|
78
|
+
g. Delete the processed inbox file
|
|
79
|
+
3. Record each created feedback for the summary report
|
|
80
|
+
|
|
81
|
+
### 6. Update index
|
|
82
|
+
|
|
83
|
+
Read `.product/index.yaml`. For each new feedback created:
|
|
84
|
+
- Increment `feedbacks.total` by 1
|
|
85
|
+
- Increment `feedbacks.by_status.new` by 1
|
|
86
|
+
- Increment the appropriate `feedbacks.by_category.{category}` by 1
|
|
87
|
+
- Add an entry to `feedbacks.items[]` with: id, title, status, category, priority, created
|
|
88
|
+
- Update the `updated` timestamp
|
|
89
|
+
|
|
90
|
+
Write the updated index back to `.product/index.yaml`.
|
|
91
|
+
|
|
92
|
+
### 7. Output report
|
|
93
|
+
|
|
94
|
+
Display a Markdown summary:
|
|
95
|
+
|
|
96
|
+
```markdown
|
|
97
|
+
## Intake Complete
|
|
98
|
+
|
|
99
|
+
**Created**: {count} new feedback(s)
|
|
100
|
+
|
|
101
|
+
| ID | Title | Category | Source |
|
|
102
|
+
|----|-------|----------|--------|
|
|
103
|
+
| FB-{NNN} | {title} | {category} | {source} |
|
|
104
|
+
| ... | ... | ... | ... |
|
|
105
|
+
|
|
106
|
+
**Next**: Run `/product.triage` when ready to process new feedbacks.
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## Error Handling
|
|
110
|
+
|
|
111
|
+
- `.product/` does not exist → ERROR with initialization instructions
|
|
112
|
+
- No arguments AND empty inbox → INFO message, no action taken
|
|
113
|
+
- Inbox file is empty or binary → WARN, skip that file, continue with others
|
|
114
|
+
- Unreadable inbox file → WARN, leave file in inbox for manual review
|
|
115
|
+
|
|
116
|
+
## Rules
|
|
117
|
+
|
|
118
|
+
- ALWAYS assign IDs by scanning ALL feedbacks/ subdirectories — never reuse IDs
|
|
119
|
+
- ALWAYS use today's date for `created` and `updated`
|
|
120
|
+
- ALWAYS set `status: "new"` for newly created feedbacks
|
|
121
|
+
- ALWAYS update `index.yaml` after creating feedbacks
|
|
122
|
+
- ALWAYS delete successfully processed inbox files
|
|
123
|
+
- NEVER modify existing feedback files
|
|
124
|
+
- NEVER modify backlog files
|
|
125
|
+
- Category MUST be one of: `critical-bug`, `bug`, `optimization`, `evolution`, `new-feature`
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Promote a backlog item to a full kai feature with traceability links.
|
|
3
|
+
handoffs:
|
|
4
|
+
- label: Start feature workflow
|
|
5
|
+
agent: feature.workflow
|
|
6
|
+
prompt: Start the feature workflow for the newly created feature
|
|
7
|
+
send: true
|
|
8
|
+
- label: Browse backlogs
|
|
9
|
+
agent: product.backlog
|
|
10
|
+
prompt: List all backlogs
|
|
11
|
+
send: true
|
|
12
|
+
- label: View dashboard
|
|
13
|
+
agent: product.dashboard
|
|
14
|
+
prompt: View product health dashboard
|
|
15
|
+
send: true
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## User Input
|
|
19
|
+
|
|
20
|
+
```text
|
|
21
|
+
$ARGUMENTS
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
You **MUST** consider the user input before proceeding (if not empty).
|
|
25
|
+
|
|
26
|
+
`$ARGUMENTS` must contain a backlog ID (e.g., `BL-001`).
|
|
27
|
+
|
|
28
|
+
## Purpose
|
|
29
|
+
|
|
30
|
+
Convert a backlog item into a kai feature — creating the `.features/{NNN}-{name}.yaml` file, updating `.features/index.yaml`, moving the backlog to `promoted/`, and updating all traceability links through the feedback → backlog → feature chain.
|
|
31
|
+
|
|
32
|
+
## Execution Flow
|
|
33
|
+
|
|
34
|
+
### 1. Validate arguments
|
|
35
|
+
|
|
36
|
+
- If `$ARGUMENTS` is empty → **ERROR**: "Backlog ID required. Usage: `/product.promote BL-xxx`"
|
|
37
|
+
- Extract the backlog ID from arguments (should match pattern `BL-\d{3}`)
|
|
38
|
+
- If invalid format → **ERROR**: "Invalid backlog ID format. Expected BL-xxx (e.g., BL-001)."
|
|
39
|
+
|
|
40
|
+
### 2. Find the backlog
|
|
41
|
+
|
|
42
|
+
Search for the backlog file across these directories (in order):
|
|
43
|
+
1. `.product/backlogs/open/`
|
|
44
|
+
2. `.product/backlogs/in-progress/`
|
|
45
|
+
|
|
46
|
+
If found in `promoted/` → **ERROR**: "Backlog {id} is already promoted (feature: {feature_id})."
|
|
47
|
+
If found in `done/` or `cancelled/` → **ERROR**: "Cannot promote a {status} backlog. Only open or in-progress backlogs can be promoted."
|
|
48
|
+
If not found anywhere → **ERROR**: "Backlog {id} not found."
|
|
49
|
+
|
|
50
|
+
Read the backlog file: parse YAML frontmatter and body content.
|
|
51
|
+
|
|
52
|
+
### 3. Determine feature identity
|
|
53
|
+
|
|
54
|
+
1. Read `.features/index.yaml` to list all existing features
|
|
55
|
+
2. Scan `.features/` for all `{NNN}-*.yaml` files
|
|
56
|
+
3. Find the highest feature number (NNN)
|
|
57
|
+
4. Next feature number = highest + 1, zero-padded to 3 digits
|
|
58
|
+
5. Derive the feature name from the backlog title:
|
|
59
|
+
- Convert to lowercase
|
|
60
|
+
- Replace spaces and special characters with hyphens
|
|
61
|
+
- Remove consecutive hyphens
|
|
62
|
+
- Trim hyphens from start/end
|
|
63
|
+
- Example: "Search Performance on Large Repos" → `search-performance-on-large-repos`
|
|
64
|
+
6. Feature ID = `{NNN}-{name}` (e.g., `009-search-performance-on-large-repos`)
|
|
65
|
+
|
|
66
|
+
### 4. Create feature YAML
|
|
67
|
+
|
|
68
|
+
1. Read `.features/_templates/feature.tpl.yaml`
|
|
69
|
+
2. Copy to `.features/{feature_id}.yaml`
|
|
70
|
+
3. Replace template placeholders:
|
|
71
|
+
- `{{feature_id}}` → the feature ID
|
|
72
|
+
- `{{title}}` → the backlog title (title case)
|
|
73
|
+
- `{{owner}}` → the backlog `owner` field, or `default_owner` from `.features/config.yaml`
|
|
74
|
+
- `{{date}}` → today's date (YYYY-MM-DD)
|
|
75
|
+
- `{{timestamp}}` → current ISO timestamp
|
|
76
|
+
4. Set `workflow_path: "full"` (promoted features use the full method)
|
|
77
|
+
|
|
78
|
+
### 5. Update feature index
|
|
79
|
+
|
|
80
|
+
Read `.features/index.yaml` and add the new feature entry:
|
|
81
|
+
- `id`: the feature ID
|
|
82
|
+
- `title`: the backlog title
|
|
83
|
+
- `status`: `active`
|
|
84
|
+
- `stage`: `ideation`
|
|
85
|
+
- `progress`: `0.0`
|
|
86
|
+
- `health`: `HEALTHY`
|
|
87
|
+
|
|
88
|
+
Write the updated index back.
|
|
89
|
+
|
|
90
|
+
### 6. Update backlog
|
|
91
|
+
|
|
92
|
+
1. Move the backlog file from its current directory to `.product/backlogs/promoted/`
|
|
93
|
+
2. Update the backlog frontmatter:
|
|
94
|
+
- `status: "promoted"`
|
|
95
|
+
- `updated: today's date`
|
|
96
|
+
- `promotion.promoted_date: today's date`
|
|
97
|
+
- `promotion.feature_id: "{feature_id}"`
|
|
98
|
+
- Add the feature ID to `features[]` array
|
|
99
|
+
3. Write the updated file
|
|
100
|
+
|
|
101
|
+
### 7. Update linked feedbacks
|
|
102
|
+
|
|
103
|
+
For each feedback ID in the backlog's `feedbacks[]` array:
|
|
104
|
+
1. Find the feedback file across all `feedbacks/` subdirectories
|
|
105
|
+
2. Read its frontmatter
|
|
106
|
+
3. Add the feature ID to `linked_to.features[]`
|
|
107
|
+
4. Update `updated` to today's date
|
|
108
|
+
5. Write the updated file
|
|
109
|
+
|
|
110
|
+
### 8. Update product index
|
|
111
|
+
|
|
112
|
+
Read `.product/index.yaml` and update:
|
|
113
|
+
- Decrement the appropriate `backlogs.by_status.{old_status}` count
|
|
114
|
+
- Increment `backlogs.by_status.promoted`
|
|
115
|
+
- Update the backlog entry in `backlogs.items[]`
|
|
116
|
+
- Recalculate `metrics.backlog_to_feature_rate`
|
|
117
|
+
- Update `updated` timestamp
|
|
118
|
+
|
|
119
|
+
Write the updated index back.
|
|
120
|
+
|
|
121
|
+
### 9. Output report
|
|
122
|
+
|
|
123
|
+
```markdown
|
|
124
|
+
## Promotion Complete
|
|
125
|
+
|
|
126
|
+
**Backlog**: {backlog_id} → promoted
|
|
127
|
+
**Feature**: {feature_id} created
|
|
128
|
+
|
|
129
|
+
### Traceability Chain
|
|
130
|
+
|
|
131
|
+
{For each linked feedback:}
|
|
132
|
+
FB-xxx ──→ {backlog_id} ──→ {feature_id}
|
|
133
|
+
|
|
134
|
+
### Next Steps
|
|
135
|
+
|
|
136
|
+
1. Run `/feature.workflow {feature_id}` to start the feature pipeline
|
|
137
|
+
2. The feature begins at **ideation** stage — proceed through Brief → PRD → Spec → Tasks → Code
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## Error Handling
|
|
141
|
+
|
|
142
|
+
- `$ARGUMENTS` is empty → ERROR with usage instructions
|
|
143
|
+
- Backlog not found → ERROR with ID
|
|
144
|
+
- Backlog already promoted → ERROR with existing feature ID
|
|
145
|
+
- Backlog in wrong status (done/cancelled) → ERROR with explanation
|
|
146
|
+
- `.features/` missing → ERROR: "Feature lifecycle system not initialized. Run `npx feature-lifecycle init` first."
|
|
147
|
+
- `.features/_templates/feature.tpl.yaml` missing → ERROR: "Feature template missing."
|
|
148
|
+
- Feature index update fails → ERROR but backlog move is already done — instruct to run `/product.check`
|
|
149
|
+
|
|
150
|
+
## Rules
|
|
151
|
+
|
|
152
|
+
- ALWAYS validate the backlog exists and is in a promotable status before any mutations
|
|
153
|
+
- ALWAYS create the feature YAML before moving the backlog (fail-safe ordering)
|
|
154
|
+
- ALWAYS update bidirectional links: backlog → feature AND feedback → feature
|
|
155
|
+
- ALWAYS use the next sequential feature number from `.features/`
|
|
156
|
+
- NEVER modify feedbacks beyond adding to `linked_to.features[]` and updating `updated`
|
|
157
|
+
- NEVER promote backlogs in `done/` or `cancelled/` status
|
|
158
|
+
- NEVER overwrite an existing feature YAML
|
|
159
|
+
- Feature name derivation MUST produce valid kebab-case (lowercase, hyphens only)
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Triage new feedbacks — semantic clustering, duplicate/regression detection, backlog creation.
|
|
3
|
+
handoffs:
|
|
4
|
+
- label: Browse backlogs
|
|
5
|
+
agent: product.backlog
|
|
6
|
+
prompt: List all backlogs
|
|
7
|
+
send: true
|
|
8
|
+
- label: Promote backlog
|
|
9
|
+
agent: product.promote
|
|
10
|
+
prompt: Promote a backlog to feature
|
|
11
|
+
send: true
|
|
12
|
+
- label: View dashboard
|
|
13
|
+
agent: product.dashboard
|
|
14
|
+
prompt: View product health dashboard
|
|
15
|
+
send: true
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## User Input
|
|
19
|
+
|
|
20
|
+
```text
|
|
21
|
+
$ARGUMENTS
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
You **MUST** consider the user input before proceeding (if not empty).
|
|
25
|
+
|
|
26
|
+
- If `$ARGUMENTS` contains `--supervised`: use supervised mode (confirm each action)
|
|
27
|
+
- Otherwise: use autonomous mode (execute all actions immediately)
|
|
28
|
+
|
|
29
|
+
## Purpose
|
|
30
|
+
|
|
31
|
+
Read all new feedbacks, perform AI semantic clustering, detect duplicates and regressions against resolved feedbacks, propose backlog items, and execute triage actions — moving feedbacks to `triaged/` or `excluded/` and creating backlog items in `backlogs/open/`.
|
|
32
|
+
|
|
33
|
+
## Execution Flow
|
|
34
|
+
|
|
35
|
+
### 1. Validate environment
|
|
36
|
+
|
|
37
|
+
- Check that `.product/` directory exists. If not → **ERROR**: "Product directory not initialized."
|
|
38
|
+
- List files in `.product/feedbacks/new/`. If empty → **INFO**: "No new feedbacks to triage." → STOP
|
|
39
|
+
- If more than 30 feedbacks in `new/` → **WARN**: "Large batch ({count} feedbacks). Processing first 30; re-run for remainder." Process only the first 30 (sorted by filename).
|
|
40
|
+
|
|
41
|
+
### 2. Read all feedbacks for context
|
|
42
|
+
|
|
43
|
+
Read **all** feedback files from:
|
|
44
|
+
- `.product/feedbacks/new/` — the feedbacks to triage (primary input)
|
|
45
|
+
- `.product/feedbacks/resolved/` — for regression/duplicate detection
|
|
46
|
+
- `.product/feedbacks/triaged/` — for existing context and deduplication
|
|
47
|
+
|
|
48
|
+
For each file, parse the YAML frontmatter and body content.
|
|
49
|
+
|
|
50
|
+
### 3. Read existing backlogs for context
|
|
51
|
+
|
|
52
|
+
Read all backlog files from all `.product/backlogs/` subdirectories to understand what already exists and avoid creating duplicate backlogs.
|
|
53
|
+
|
|
54
|
+
### 4. Semantic analysis
|
|
55
|
+
|
|
56
|
+
For each new feedback, perform the following analysis:
|
|
57
|
+
|
|
58
|
+
#### 4a. Clustering
|
|
59
|
+
|
|
60
|
+
Group new feedbacks that describe the **same problem or request**, even with different phrasings. Use semantic understanding — NOT keyword matching.
|
|
61
|
+
|
|
62
|
+
Example: "Search takes 40 seconds" and "Search is unusable with 10k files" → same cluster (search performance).
|
|
63
|
+
|
|
64
|
+
#### 4b. Regression / duplicate detection
|
|
65
|
+
|
|
66
|
+
Compare each new feedback against **resolved** feedbacks:
|
|
67
|
+
|
|
68
|
+
1. If semantically similar to a resolved feedback:
|
|
69
|
+
- Find the resolution chain: read the resolved feedback's `linked_to.backlog[]` → read that backlog's `features[]` → find the feature
|
|
70
|
+
- Read the feature's `.features/{feature_id}.yaml` to get `lifecycle.stage` and `lifecycle.stage_since`
|
|
71
|
+
- **If** feedback `created` date > feature `stage_since` date (feature was released BEFORE the new feedback was created) → classify as **REGRESSION**
|
|
72
|
+
- **If** feedback `created` date <= feature `stage_since` date → classify as **DUPLICATE-RESOLVED**
|
|
73
|
+
2. If no resolution chain can be traced, treat as a new standalone feedback
|
|
74
|
+
|
|
75
|
+
#### 4c. Category assignment
|
|
76
|
+
|
|
77
|
+
For each feedback, verify or reassign its category from the predefined set: `critical-bug`, `bug`, `optimization`, `evolution`, `new-feature`.
|
|
78
|
+
|
|
79
|
+
#### 4d. Priority proposal
|
|
80
|
+
|
|
81
|
+
For each cluster/standalone feedback, propose a priority: `low`, `medium`, `high`, `critical`.
|
|
82
|
+
Consider: user impact, frequency of reports, severity of the issue.
|
|
83
|
+
|
|
84
|
+
### 5. Present triage proposal
|
|
85
|
+
|
|
86
|
+
Build a structured proposal showing all planned actions:
|
|
87
|
+
|
|
88
|
+
```markdown
|
|
89
|
+
## Triage Proposal
|
|
90
|
+
|
|
91
|
+
### Group 1: {cluster title} ({count} feedbacks)
|
|
92
|
+
- FB-xxx: "{title}"
|
|
93
|
+
- FB-yyy: "{title}"
|
|
94
|
+
**Action**: Create backlog BL-{NNN} "{proposed title}" ({category}, {priority})
|
|
95
|
+
|
|
96
|
+
### Group 2: Standalone
|
|
97
|
+
- FB-zzz: "{title}"
|
|
98
|
+
**Action**: Create backlog BL-{NNN} "{proposed title}" ({category}, {priority})
|
|
99
|
+
|
|
100
|
+
### Excluded
|
|
101
|
+
- FB-aaa: "{title}"
|
|
102
|
+
**Action**: Move to excluded/ (reason: {noise | out-of-scope | duplicate-resolved | ...})
|
|
103
|
+
|
|
104
|
+
### Regression Detected
|
|
105
|
+
- FB-bbb: Similar to resolved FB-ccc (resolved by feature {feature_id}, released {date})
|
|
106
|
+
FB-bbb created: {date} — AFTER release
|
|
107
|
+
**Action**: Create backlog BL-{NNN} "{title}" (critical-bug, critical, tag: regression)
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### 6. Execute actions
|
|
111
|
+
|
|
112
|
+
#### Autonomous mode (default)
|
|
113
|
+
|
|
114
|
+
Execute all proposed actions immediately:
|
|
115
|
+
|
|
116
|
+
#### Supervised mode (`--supervised`)
|
|
117
|
+
|
|
118
|
+
Present each action individually and wait for user confirmation:
|
|
119
|
+
- **Accept**: execute the action
|
|
120
|
+
- **Reject**: skip the action, leave the feedback in `new/`
|
|
121
|
+
- **Modify**: let the user change the proposed action before executing
|
|
122
|
+
|
|
123
|
+
#### For each triage action:
|
|
124
|
+
|
|
125
|
+
**Creating a backlog item:**
|
|
126
|
+
1. Scan ALL `.product/backlogs/` subdirectories for highest existing BL-xxx number
|
|
127
|
+
2. Assign next sequential ID (zero-padded to 3 digits)
|
|
128
|
+
3. Read `.product/_templates/backlog.tpl.md` for the schema
|
|
129
|
+
4. Create backlog file at `.product/backlogs/open/BL-{NNN}.md`:
|
|
130
|
+
- Replace `{{id}}` with `BL-{NNN}`
|
|
131
|
+
- Replace `{{title}}` with the synthesized title from the cluster
|
|
132
|
+
- Replace `{{category}}` with the determined category
|
|
133
|
+
- Replace `{{priority}}` with the proposed priority
|
|
134
|
+
- Replace `{{created}}` and `{{updated}}` with today's date
|
|
135
|
+
- Replace `{{owner}}` with git user name or `unknown`
|
|
136
|
+
- Set `feedbacks:` array with all feedback IDs in the cluster
|
|
137
|
+
- Replace `{{body}}` with a synthesized description of the problem from the cluster
|
|
138
|
+
5. For each feedback in the cluster:
|
|
139
|
+
- Move file from `feedbacks/new/` to `feedbacks/triaged/`
|
|
140
|
+
- Update frontmatter: `status: "triaged"`, `updated: today`
|
|
141
|
+
- Add the backlog ID to `linked_to.backlog[]`
|
|
142
|
+
|
|
143
|
+
**Excluding a feedback:**
|
|
144
|
+
1. Move file from `feedbacks/new/` to `feedbacks/excluded/`
|
|
145
|
+
2. Update frontmatter: `status: "excluded"`, `updated: today`
|
|
146
|
+
3. Set `exclusion_reason` to the specific reason (e.g., "duplicate-resolved", "noise", "out-of-scope")
|
|
147
|
+
|
|
148
|
+
**Handling a regression:**
|
|
149
|
+
1. Create a backlog item (same as above) with:
|
|
150
|
+
- `category: "critical-bug"`
|
|
151
|
+
- `priority: "critical"`
|
|
152
|
+
- `tags: ["regression"]`
|
|
153
|
+
2. Move the feedback to `triaged/` with the backlog link
|
|
154
|
+
|
|
155
|
+
### 7. Update index
|
|
156
|
+
|
|
157
|
+
Read `.product/index.yaml` and update:
|
|
158
|
+
- Feedback counts by status (decrement `new`, increment `triaged`/`excluded` as appropriate)
|
|
159
|
+
- Backlog counts (increment `open` for each new backlog)
|
|
160
|
+
- Category distributions
|
|
161
|
+
- Recalculate `metrics.feedback_to_backlog_rate`
|
|
162
|
+
- Update `updated` timestamp
|
|
163
|
+
|
|
164
|
+
Write the updated index back.
|
|
165
|
+
|
|
166
|
+
### 8. Output report
|
|
167
|
+
|
|
168
|
+
```markdown
|
|
169
|
+
## Triage Complete
|
|
170
|
+
|
|
171
|
+
**Processed**: {count} feedbacks
|
|
172
|
+
**Created**: {count} backlog item(s)
|
|
173
|
+
**Excluded**: {count} feedback(s)
|
|
174
|
+
**Regressions**: {count} detected
|
|
175
|
+
|
|
176
|
+
| Feedback | Action | Result |
|
|
177
|
+
|----------|--------|--------|
|
|
178
|
+
| FB-xxx | Grouped → BL-{NNN} | triaged/ |
|
|
179
|
+
| FB-yyy | Grouped → BL-{NNN} | triaged/ |
|
|
180
|
+
| FB-zzz | Standalone → BL-{NNN} | triaged/ |
|
|
181
|
+
| FB-aaa | Excluded ({reason}) | excluded/ |
|
|
182
|
+
|
|
183
|
+
**Next**: Review backlogs with `/product.backlog` or promote with `/product.promote BL-xxx`.
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
## Error Handling
|
|
187
|
+
|
|
188
|
+
- No feedbacks in `new/` → INFO, stop gracefully
|
|
189
|
+
- `.product/` missing → ERROR with init instructions
|
|
190
|
+
- Batch > 30 items → WARN, process first 30, instruct to re-run
|
|
191
|
+
- Feature file not found during regression check → skip regression detection for that feedback, note in report
|
|
192
|
+
- Feedback file cannot be parsed → WARN, skip, leave in `new/`
|
|
193
|
+
|
|
194
|
+
## Rules
|
|
195
|
+
|
|
196
|
+
- ALWAYS use semantic similarity for clustering — NEVER keyword matching
|
|
197
|
+
- ALWAYS scan ALL backlogs/ and feedbacks/ subdirs for ID assignment — NEVER reuse IDs
|
|
198
|
+
- ALWAYS update bidirectional links (feedback → backlog AND backlog → feedbacks)
|
|
199
|
+
- ALWAYS move files to new directories as state transitions — NEVER just update frontmatter status without moving
|
|
200
|
+
- ALWAYS update `index.yaml` after all actions
|
|
201
|
+
- NEVER modify resolved feedbacks (read-only context)
|
|
202
|
+
- NEVER modify existing backlog items (read-only context)
|
|
203
|
+
- Category MUST be one of: `critical-bug`, `bug`, `optimization`, `evolution`, `new-feature`
|
|
204
|
+
- Priority MUST be one of: `low`, `medium`, `high`, `critical`
|
|
205
|
+
- In supervised mode, respect user decisions without argument
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: "{{id}}"
|
|
3
|
+
title: "{{title}}"
|
|
4
|
+
status: "open"
|
|
5
|
+
category: "{{category}}"
|
|
6
|
+
priority: "{{priority}}"
|
|
7
|
+
created: "{{created}}"
|
|
8
|
+
updated: "{{updated}}"
|
|
9
|
+
owner: "{{owner}}"
|
|
10
|
+
feedbacks: []
|
|
11
|
+
features: []
|
|
12
|
+
tags: []
|
|
13
|
+
promotion:
|
|
14
|
+
promoted_date: ""
|
|
15
|
+
feature_id: ""
|
|
16
|
+
cancellation:
|
|
17
|
+
cancelled_date: ""
|
|
18
|
+
reason: ""
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
{{body}}
|