@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
package/bin/cli.js
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { argv, exit } from "node:process";
|
|
4
|
+
|
|
5
|
+
const command = argv[2];
|
|
6
|
+
const flags = argv.slice(3);
|
|
7
|
+
|
|
8
|
+
const HELP = `
|
|
9
|
+
product-manager — File-based product feedback & backlog system for the kai governance stack.
|
|
10
|
+
|
|
11
|
+
Usage:
|
|
12
|
+
npx @tcanaud/product-manager init Scaffold .product/ directory and install slash commands
|
|
13
|
+
npx @tcanaud/product-manager update Update commands and templates without touching user data
|
|
14
|
+
npx @tcanaud/product-manager help Show this help message
|
|
15
|
+
|
|
16
|
+
Options (init):
|
|
17
|
+
--yes Skip confirmation prompts
|
|
18
|
+
|
|
19
|
+
Claude Code commands (after init):
|
|
20
|
+
/product.intake [text] Capture feedback from text or inbox files
|
|
21
|
+
/product.triage [--supervised] Triage new feedbacks into backlogs
|
|
22
|
+
/product.backlog [BL-xxx] List or inspect backlog items
|
|
23
|
+
/product.promote BL-xxx Promote backlog to a kai feature
|
|
24
|
+
/product.check Detect drift and integrity issues
|
|
25
|
+
/product.dashboard [--json] View product health dashboard
|
|
26
|
+
`;
|
|
27
|
+
|
|
28
|
+
switch (command) {
|
|
29
|
+
case "init": {
|
|
30
|
+
const { install } = await import("../src/installer.js");
|
|
31
|
+
install(flags);
|
|
32
|
+
break;
|
|
33
|
+
}
|
|
34
|
+
case "update": {
|
|
35
|
+
const { update } = await import("../src/updater.js");
|
|
36
|
+
update(flags);
|
|
37
|
+
break;
|
|
38
|
+
}
|
|
39
|
+
case "help":
|
|
40
|
+
case "--help":
|
|
41
|
+
case "-h":
|
|
42
|
+
case undefined:
|
|
43
|
+
console.log(HELP);
|
|
44
|
+
break;
|
|
45
|
+
default:
|
|
46
|
+
console.error(`Unknown command: ${command}`);
|
|
47
|
+
console.log(HELP);
|
|
48
|
+
exit(1);
|
|
49
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@tcanaud/product-manager",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "File-based product feedback intake, AI semantic triage, backlog management, and feature promotion pipeline for the kai governance stack.",
|
|
6
|
+
"bin": {
|
|
7
|
+
"product-manager": "./bin/cli.js"
|
|
8
|
+
},
|
|
9
|
+
"engines": {
|
|
10
|
+
"node": ">=18.0.0"
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"bin/",
|
|
14
|
+
"src/",
|
|
15
|
+
"templates/"
|
|
16
|
+
],
|
|
17
|
+
"keywords": [
|
|
18
|
+
"kai",
|
|
19
|
+
"product-management",
|
|
20
|
+
"feedback",
|
|
21
|
+
"triage",
|
|
22
|
+
"backlog",
|
|
23
|
+
"claude-code"
|
|
24
|
+
],
|
|
25
|
+
"author": "Thibaud Canaud",
|
|
26
|
+
"license": "MIT",
|
|
27
|
+
"repository": {
|
|
28
|
+
"type": "git",
|
|
29
|
+
"url": "https://github.com/tcanaud/product-manager.git"
|
|
30
|
+
}
|
|
31
|
+
}
|
package/src/detect.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
|
|
4
|
+
export function detect(projectRoot) {
|
|
5
|
+
const productDir = join(projectRoot, ".product");
|
|
6
|
+
const hasProduct = existsSync(productDir);
|
|
7
|
+
const hasClaudeCommands = existsSync(join(projectRoot, ".claude", "commands"));
|
|
8
|
+
const hasFeatures = existsSync(join(projectRoot, ".features"));
|
|
9
|
+
|
|
10
|
+
return {
|
|
11
|
+
hasProduct,
|
|
12
|
+
productDir,
|
|
13
|
+
hasClaudeCommands,
|
|
14
|
+
hasFeatures,
|
|
15
|
+
};
|
|
16
|
+
}
|
package/src/installer.js
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, copyFileSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { join, dirname } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { createInterface } from "node:readline";
|
|
5
|
+
import { detect } from "./detect.js";
|
|
6
|
+
|
|
7
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
8
|
+
const TEMPLATES = join(__dirname, "..", "templates");
|
|
9
|
+
|
|
10
|
+
function copyTemplate(src, dest) {
|
|
11
|
+
const destDir = dirname(dest);
|
|
12
|
+
if (!existsSync(destDir)) {
|
|
13
|
+
mkdirSync(destDir, { recursive: true });
|
|
14
|
+
}
|
|
15
|
+
copyFileSync(src, dest);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function ask(question) {
|
|
19
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
20
|
+
return new Promise((resolve) => {
|
|
21
|
+
rl.question(question, (answer) => {
|
|
22
|
+
rl.close();
|
|
23
|
+
resolve(answer.trim().toLowerCase());
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export async function install(flags = []) {
|
|
29
|
+
const projectRoot = process.cwd();
|
|
30
|
+
const autoYes = flags.includes("--yes");
|
|
31
|
+
|
|
32
|
+
console.log("\n @tcanaud/product-manager v1.0.0\n");
|
|
33
|
+
|
|
34
|
+
// ── Detect environment ──────────────────────────────
|
|
35
|
+
const env = detect(projectRoot);
|
|
36
|
+
|
|
37
|
+
console.log(" Environment detected:");
|
|
38
|
+
console.log(` .product/: ${env.hasProduct ? "yes" : "no"}`);
|
|
39
|
+
console.log(` .features/: ${env.hasFeatures ? "yes" : "no"}`);
|
|
40
|
+
console.log(` Claude commands: ${env.hasClaudeCommands ? "yes" : "no"}`);
|
|
41
|
+
console.log();
|
|
42
|
+
|
|
43
|
+
const productDir = join(projectRoot, ".product");
|
|
44
|
+
|
|
45
|
+
if (existsSync(productDir) && !autoYes) {
|
|
46
|
+
const answer = await ask(" .product/ already exists. Overwrite templates? (y/N) ");
|
|
47
|
+
if (answer !== "y" && answer !== "yes") {
|
|
48
|
+
console.log(" Skipping. Use '@tcanaud/product-manager update' to update commands only.\n");
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// ── Phase 1/3: Create .product/ directory structure ─
|
|
54
|
+
console.log(" [1/3] Creating .product/ directory structure...");
|
|
55
|
+
|
|
56
|
+
const dirs = [
|
|
57
|
+
".product/inbox",
|
|
58
|
+
".product/feedbacks/new",
|
|
59
|
+
".product/feedbacks/triaged",
|
|
60
|
+
".product/feedbacks/excluded",
|
|
61
|
+
".product/feedbacks/resolved",
|
|
62
|
+
".product/backlogs/open",
|
|
63
|
+
".product/backlogs/in-progress",
|
|
64
|
+
".product/backlogs/done",
|
|
65
|
+
".product/backlogs/promoted",
|
|
66
|
+
".product/backlogs/cancelled",
|
|
67
|
+
".product/_templates",
|
|
68
|
+
];
|
|
69
|
+
|
|
70
|
+
for (const dir of dirs) {
|
|
71
|
+
const fullPath = join(projectRoot, dir);
|
|
72
|
+
if (!existsSync(fullPath)) {
|
|
73
|
+
mkdirSync(fullPath, { recursive: true });
|
|
74
|
+
console.log(` create ${dir}/`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Copy artifact templates
|
|
79
|
+
copyTemplate(
|
|
80
|
+
join(TEMPLATES, "core", "feedback.tpl.md"),
|
|
81
|
+
join(productDir, "_templates", "feedback.tpl.md")
|
|
82
|
+
);
|
|
83
|
+
console.log(" write .product/_templates/feedback.tpl.md");
|
|
84
|
+
|
|
85
|
+
copyTemplate(
|
|
86
|
+
join(TEMPLATES, "core", "backlog.tpl.md"),
|
|
87
|
+
join(productDir, "_templates", "backlog.tpl.md")
|
|
88
|
+
);
|
|
89
|
+
console.log(" write .product/_templates/backlog.tpl.md");
|
|
90
|
+
|
|
91
|
+
// Index
|
|
92
|
+
const indexPath = join(productDir, "index.yaml");
|
|
93
|
+
if (existsSync(indexPath)) {
|
|
94
|
+
console.log(" skip .product/index.yaml (already exists)");
|
|
95
|
+
} else {
|
|
96
|
+
const template = readFileSync(join(TEMPLATES, "core", "index.yaml"), "utf-8");
|
|
97
|
+
const content = template.replace("{{generated}}", new Date().toISOString());
|
|
98
|
+
writeFileSync(indexPath, content);
|
|
99
|
+
console.log(" write .product/index.yaml");
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// ── Phase 2/3: Install Claude Code commands ─────────
|
|
103
|
+
console.log(" [2/3] Installing Claude Code commands...");
|
|
104
|
+
|
|
105
|
+
if (!env.hasClaudeCommands) {
|
|
106
|
+
mkdirSync(join(projectRoot, ".claude", "commands"), { recursive: true });
|
|
107
|
+
console.log(" create .claude/commands/");
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const commandMappings = [
|
|
111
|
+
["commands/product.intake.md", ".claude/commands/product.intake.md"],
|
|
112
|
+
["commands/product.triage.md", ".claude/commands/product.triage.md"],
|
|
113
|
+
["commands/product.backlog.md", ".claude/commands/product.backlog.md"],
|
|
114
|
+
["commands/product.promote.md", ".claude/commands/product.promote.md"],
|
|
115
|
+
["commands/product.check.md", ".claude/commands/product.check.md"],
|
|
116
|
+
["commands/product.dashboard.md", ".claude/commands/product.dashboard.md"],
|
|
117
|
+
];
|
|
118
|
+
|
|
119
|
+
for (const [src, dest] of commandMappings) {
|
|
120
|
+
const srcPath = join(TEMPLATES, src);
|
|
121
|
+
if (existsSync(srcPath)) {
|
|
122
|
+
copyTemplate(srcPath, join(projectRoot, dest));
|
|
123
|
+
console.log(` write ${dest}`);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// ── Phase 3/3: Summary ─────────────────────────────
|
|
128
|
+
console.log(" [3/3] Verifying installation...");
|
|
129
|
+
|
|
130
|
+
if (!env.hasFeatures) {
|
|
131
|
+
console.log(" note: .features/ not found — /product.promote requires the feature lifecycle system");
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// ── Done ────────────────────────────────────────────
|
|
135
|
+
console.log();
|
|
136
|
+
console.log(" Done! Product Manager installed.");
|
|
137
|
+
console.log();
|
|
138
|
+
console.log(" Next steps:");
|
|
139
|
+
console.log(" 1. Run /product.intake \"your feedback text\" to capture feedback");
|
|
140
|
+
console.log(" 2. Run /product.triage to process new feedbacks into backlogs");
|
|
141
|
+
console.log(" 3. Run /product.dashboard for a health overview");
|
|
142
|
+
console.log();
|
|
143
|
+
}
|
package/src/updater.js
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { existsSync, copyFileSync, mkdirSync } from "node:fs";
|
|
2
|
+
import { join, dirname } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
|
|
5
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
6
|
+
const TEMPLATES = join(__dirname, "..", "templates");
|
|
7
|
+
|
|
8
|
+
function copyTemplate(src, dest) {
|
|
9
|
+
const destDir = dirname(dest);
|
|
10
|
+
if (!existsSync(destDir)) {
|
|
11
|
+
mkdirSync(destDir, { recursive: true });
|
|
12
|
+
}
|
|
13
|
+
copyFileSync(src, dest);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function update(flags = []) {
|
|
17
|
+
const projectRoot = process.cwd();
|
|
18
|
+
|
|
19
|
+
console.log("\n product-manager update\n");
|
|
20
|
+
|
|
21
|
+
if (!existsSync(join(projectRoot, ".product"))) {
|
|
22
|
+
console.error(" Error: .product/ not found. Run 'product-manager init' first.");
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Update artifact templates
|
|
27
|
+
console.log(" Updating templates...");
|
|
28
|
+
copyTemplate(
|
|
29
|
+
join(TEMPLATES, "core", "feedback.tpl.md"),
|
|
30
|
+
join(projectRoot, ".product", "_templates", "feedback.tpl.md")
|
|
31
|
+
);
|
|
32
|
+
console.log(" update .product/_templates/feedback.tpl.md");
|
|
33
|
+
|
|
34
|
+
copyTemplate(
|
|
35
|
+
join(TEMPLATES, "core", "backlog.tpl.md"),
|
|
36
|
+
join(projectRoot, ".product", "_templates", "backlog.tpl.md")
|
|
37
|
+
);
|
|
38
|
+
console.log(" update .product/_templates/backlog.tpl.md");
|
|
39
|
+
|
|
40
|
+
// Update Claude Code commands
|
|
41
|
+
console.log(" Updating Claude Code commands...");
|
|
42
|
+
|
|
43
|
+
const commandMappings = [
|
|
44
|
+
["commands/product.intake.md", ".claude/commands/product.intake.md"],
|
|
45
|
+
["commands/product.triage.md", ".claude/commands/product.triage.md"],
|
|
46
|
+
["commands/product.backlog.md", ".claude/commands/product.backlog.md"],
|
|
47
|
+
["commands/product.promote.md", ".claude/commands/product.promote.md"],
|
|
48
|
+
["commands/product.check.md", ".claude/commands/product.check.md"],
|
|
49
|
+
["commands/product.dashboard.md", ".claude/commands/product.dashboard.md"],
|
|
50
|
+
];
|
|
51
|
+
|
|
52
|
+
for (const [src, dest] of commandMappings) {
|
|
53
|
+
const srcPath = join(TEMPLATES, src);
|
|
54
|
+
if (existsSync(srcPath)) {
|
|
55
|
+
copyTemplate(srcPath, join(projectRoot, dest));
|
|
56
|
+
console.log(` update ${dest}`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
console.log();
|
|
61
|
+
console.log(" Done! Commands and templates updated.");
|
|
62
|
+
console.log(" Your feedbacks, backlogs, inbox, and index.yaml are untouched.\n");
|
|
63
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Browse and manage backlogs — list all by status or inspect a specific backlog with linked feedbacks.
|
|
3
|
+
handoffs:
|
|
4
|
+
- label: Promote backlog
|
|
5
|
+
agent: product.promote
|
|
6
|
+
prompt: Promote a backlog to feature
|
|
7
|
+
send: true
|
|
8
|
+
- label: Triage feedbacks
|
|
9
|
+
agent: product.triage
|
|
10
|
+
prompt: Triage new feedbacks into 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
|
+
- If `$ARGUMENTS` contains a backlog ID (e.g., `BL-001`): show detail view for that backlog
|
|
27
|
+
- If `$ARGUMENTS` is empty: show list of all backlogs grouped by status
|
|
28
|
+
|
|
29
|
+
## Purpose
|
|
30
|
+
|
|
31
|
+
Read-only command to browse and inspect backlog items. List all backlogs grouped by status, or view detailed information about a specific backlog including its linked feedbacks.
|
|
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. Determine mode
|
|
40
|
+
|
|
41
|
+
- **If `$ARGUMENTS` is empty**: List mode (Mode 1)
|
|
42
|
+
- **If `$ARGUMENTS` contains `BL-xxx`**: Detail mode (Mode 2)
|
|
43
|
+
|
|
44
|
+
### 3. Mode 1 — List all backlogs
|
|
45
|
+
|
|
46
|
+
1. Scan all `.product/backlogs/` subdirectories: `open/`, `in-progress/`, `done/`, `promoted/`, `cancelled/`
|
|
47
|
+
2. For each backlog file found, parse YAML frontmatter to extract: id, title, priority, feedbacks count, created date, owner, features
|
|
48
|
+
3. Group by status directory
|
|
49
|
+
4. Display grouped summary:
|
|
50
|
+
|
|
51
|
+
```markdown
|
|
52
|
+
## Product Backlog
|
|
53
|
+
|
|
54
|
+
**Total**: {count} items
|
|
55
|
+
|
|
56
|
+
### Open ({count})
|
|
57
|
+
|
|
58
|
+
| ID | Title | Priority | Feedbacks | Created |
|
|
59
|
+
|----|-------|----------|-----------|---------|
|
|
60
|
+
| BL-xxx | {title} | {priority} | {feedbacks.length} | {created} |
|
|
61
|
+
|
|
62
|
+
### In Progress ({count})
|
|
63
|
+
|
|
64
|
+
| ID | Title | Priority | Owner | Created |
|
|
65
|
+
|----|-------|----------|-------|---------|
|
|
66
|
+
| BL-xxx | {title} | {priority} | {owner} | {created} |
|
|
67
|
+
|
|
68
|
+
### Promoted ({count})
|
|
69
|
+
|
|
70
|
+
| ID | Title | Feature | Promoted |
|
|
71
|
+
|----|-------|---------|----------|
|
|
72
|
+
| BL-xxx | {title} | {promotion.feature_id} | {promotion.promoted_date} |
|
|
73
|
+
|
|
74
|
+
### Done ({count})
|
|
75
|
+
|
|
76
|
+
| ID | Title | Completed |
|
|
77
|
+
|----|-------|-----------|
|
|
78
|
+
(list or "(none)")
|
|
79
|
+
|
|
80
|
+
### Cancelled ({count})
|
|
81
|
+
|
|
82
|
+
| ID | Title | Reason |
|
|
83
|
+
|----|-------|--------|
|
|
84
|
+
(list or "(none)")
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
If no backlogs exist in any directory → **INFO**: "No backlog items. Run `/product.triage` to create backlogs from feedbacks."
|
|
88
|
+
|
|
89
|
+
### 4. Mode 2 — Backlog detail
|
|
90
|
+
|
|
91
|
+
1. Search for the backlog file matching the given ID across ALL status directories: `open/`, `in-progress/`, `done/`, `promoted/`, `cancelled/`
|
|
92
|
+
2. If not found → **ERROR**: "Backlog item {id} not found."
|
|
93
|
+
3. Read the full file: parse YAML frontmatter and body content
|
|
94
|
+
4. For each feedback ID in the `feedbacks[]` array:
|
|
95
|
+
- Search for the feedback file across all `feedbacks/` subdirectories
|
|
96
|
+
- Read its title and current status
|
|
97
|
+
5. Display detail view:
|
|
98
|
+
|
|
99
|
+
```markdown
|
|
100
|
+
## {id}: {title}
|
|
101
|
+
|
|
102
|
+
**Status**: {status} | **Priority**: {priority} | **Category**: {category}
|
|
103
|
+
**Created**: {created} | **Owner**: {owner}
|
|
104
|
+
**Tags**: {tags joined by ", "}
|
|
105
|
+
|
|
106
|
+
### Linked Feedbacks
|
|
107
|
+
|
|
108
|
+
| ID | Title | Status | Created |
|
|
109
|
+
|----|-------|--------|---------|
|
|
110
|
+
| FB-xxx | {title} | {status} | {created} |
|
|
111
|
+
|
|
112
|
+
### Promotion
|
|
113
|
+
|
|
114
|
+
(if promoted: show feature_id and promoted_date)
|
|
115
|
+
(if not promoted: "Not yet promoted. Run `/product.promote {id}` when ready.")
|
|
116
|
+
|
|
117
|
+
### Description
|
|
118
|
+
|
|
119
|
+
{backlog body content}
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
## Error Handling
|
|
123
|
+
|
|
124
|
+
- `.product/` missing → ERROR with init instructions
|
|
125
|
+
- BL-xxx not found → ERROR with ID
|
|
126
|
+
- No backlogs exist → INFO suggesting `/product.triage`
|
|
127
|
+
- Linked feedback file missing → show feedback ID with "(not found)" status
|
|
128
|
+
|
|
129
|
+
## Rules
|
|
130
|
+
|
|
131
|
+
- This command is **READ-ONLY** — no files are modified
|
|
132
|
+
- ALWAYS search across ALL status directories when looking for a specific backlog or feedback
|
|
133
|
+
- ALWAYS display backlogs sorted by ID within each status group
|
|
134
|
+
- NEVER modify any files
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Detect drift and integrity issues — status desync, stale feedbacks, orphaned backlogs, broken chains, index desync, duplicate IDs.
|
|
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
|
+
Detect inconsistencies in the `.product/` directory: status/directory desync, stale feedbacks, orphaned backlogs, broken traceability chains, index desync, and duplicate IDs. This is the self-testing mechanism for the product management system.
|
|
25
|
+
|
|
26
|
+
## Execution Flow
|
|
27
|
+
|
|
28
|
+
### 1. Validate environment
|
|
29
|
+
|
|
30
|
+
- Check that `.product/` directory exists. If not → **ERROR**: "Product directory not initialized."
|
|
31
|
+
- Check if `.product/` is empty (no feedbacks, no backlogs) → **INFO**: "Product directory is empty. No checks to perform." → STOP
|
|
32
|
+
|
|
33
|
+
### 2. Scan all files
|
|
34
|
+
|
|
35
|
+
Read all files from:
|
|
36
|
+
- `.product/feedbacks/new/`, `triaged/`, `excluded/`, `resolved/`
|
|
37
|
+
- `.product/backlogs/open/`, `in-progress/`, `done/`, `promoted/`, `cancelled/`
|
|
38
|
+
|
|
39
|
+
For each file, parse the YAML frontmatter. Track all findings.
|
|
40
|
+
|
|
41
|
+
### 3. Check 1 — Status/directory desync
|
|
42
|
+
|
|
43
|
+
For each feedback file in all `feedbacks/` subdirectories:
|
|
44
|
+
- Read the `status` field from frontmatter
|
|
45
|
+
- Compare against the directory name the file resides in
|
|
46
|
+
- The directory name IS the canonical status:
|
|
47
|
+
- `new/` → status should be `"new"`
|
|
48
|
+
- `triaged/` → status should be `"triaged"`
|
|
49
|
+
- `excluded/` → status should be `"excluded"`
|
|
50
|
+
- `resolved/` → status should be `"resolved"`
|
|
51
|
+
- If mismatch → finding: **STATUS_DESYNC** (severity: WARNING)
|
|
52
|
+
- File path, expected status (from directory), actual status (from frontmatter)
|
|
53
|
+
- Fix: "Update frontmatter `status` to \"{expected}\""
|
|
54
|
+
|
|
55
|
+
For each backlog file in all `backlogs/` subdirectories:
|
|
56
|
+
- Same check: directory name must match `status` field
|
|
57
|
+
- `open/` → `"open"`, `in-progress/` → `"in-progress"`, `done/` → `"done"`, `promoted/` → `"promoted"`, `cancelled/` → `"cancelled"`
|
|
58
|
+
|
|
59
|
+
### 4. Check 2 — Stale feedbacks
|
|
60
|
+
|
|
61
|
+
For each feedback in `feedbacks/new/`:
|
|
62
|
+
- Read the `created` date
|
|
63
|
+
- Calculate days since creation: today - created
|
|
64
|
+
- If > 14 days → finding: **STALE_FEEDBACK** (severity: WARNING)
|
|
65
|
+
- File path, created date, days since creation
|
|
66
|
+
- Fix: "Run `/product.triage` to process stale feedbacks"
|
|
67
|
+
|
|
68
|
+
### 5. Check 3 — Orphaned backlogs
|
|
69
|
+
|
|
70
|
+
For each backlog file in any status directory:
|
|
71
|
+
- Read the `feedbacks[]` array from frontmatter
|
|
72
|
+
- For each listed feedback ID, search for the file across ALL `feedbacks/` subdirectories
|
|
73
|
+
- If **ALL** listed feedbacks are missing → finding: **ORPHANED_BACKLOG** (severity: WARNING)
|
|
74
|
+
- Backlog file path, missing feedback IDs
|
|
75
|
+
- Fix: "Remove broken references or recreate the missing feedbacks"
|
|
76
|
+
- If **SOME** listed feedbacks are missing → finding: **PARTIAL_ORPHAN** (severity: INFO)
|
|
77
|
+
- Backlog file path, missing feedback IDs, existing feedback IDs
|
|
78
|
+
- Fix: "Remove broken reference(s) from feedbacks[] array"
|
|
79
|
+
|
|
80
|
+
### 6. Check 4 — Broken traceability chains
|
|
81
|
+
|
|
82
|
+
**Feedback → Backlog:**
|
|
83
|
+
For each feedback with non-empty `linked_to.backlog[]`:
|
|
84
|
+
- For each referenced backlog ID, search across ALL `backlogs/` subdirectories
|
|
85
|
+
- If not found → finding: **BROKEN_CHAIN_FB_TO_BL** (severity: ERROR)
|
|
86
|
+
- Feedback file path, missing backlog ID
|
|
87
|
+
- Fix: "Remove broken reference or create the missing backlog"
|
|
88
|
+
|
|
89
|
+
**Backlog → Feature:**
|
|
90
|
+
For each backlog with non-empty `features[]`:
|
|
91
|
+
- For each referenced feature ID, check if `.features/{feature_id}.yaml` exists
|
|
92
|
+
- If not found → finding: **BROKEN_CHAIN_BL_TO_FEAT** (severity: ERROR)
|
|
93
|
+
- Backlog file path, missing feature ID
|
|
94
|
+
- Fix: "Remove broken reference or create the missing feature"
|
|
95
|
+
|
|
96
|
+
### 7. Check 5 — Index consistency
|
|
97
|
+
|
|
98
|
+
Read `.product/index.yaml` and compare against actual filesystem state:
|
|
99
|
+
|
|
100
|
+
- Compare `feedbacks.total` against actual file count
|
|
101
|
+
- Compare `feedbacks.by_status.*` against actual directory counts
|
|
102
|
+
- Compare `backlogs.total` against actual file count
|
|
103
|
+
- Compare `backlogs.by_status.*` against actual directory counts
|
|
104
|
+
- Compare `feedbacks.items[]` list against actual files
|
|
105
|
+
|
|
106
|
+
If any mismatch → finding: **INDEX_DESYNC** (severity: WARNING)
|
|
107
|
+
- What's different (expected vs actual)
|
|
108
|
+
- Fix: Auto-rebuild the index from filesystem state
|
|
109
|
+
|
|
110
|
+
If index desync is detected, rebuild `index.yaml` from the filesystem:
|
|
111
|
+
1. Count all feedbacks by status directory
|
|
112
|
+
2. Count all backlogs by status directory
|
|
113
|
+
3. Read category from each feedback for category distribution
|
|
114
|
+
4. Build items arrays from all files
|
|
115
|
+
5. Recalculate metrics
|
|
116
|
+
6. Write the rebuilt index
|
|
117
|
+
|
|
118
|
+
### 8. Check 6 — ID uniqueness
|
|
119
|
+
|
|
120
|
+
Scan all feedback files and collect all `id` fields:
|
|
121
|
+
- If any duplicate `id` values found → finding: **DUPLICATE_ID** (severity: ERROR)
|
|
122
|
+
- The duplicate ID, all file paths containing it
|
|
123
|
+
- Fix: "Rename one of the duplicate files and update the id field"
|
|
124
|
+
|
|
125
|
+
Same for backlog files.
|
|
126
|
+
|
|
127
|
+
### 9. Compile and classify findings
|
|
128
|
+
|
|
129
|
+
Group findings by severity:
|
|
130
|
+
- **ERROR**: Issues that break traceability or data integrity (must fix)
|
|
131
|
+
- **WARNING**: Issues that indicate drift or neglect (should fix)
|
|
132
|
+
- **INFO**: Minor issues or informational notes (nice to fix)
|
|
133
|
+
|
|
134
|
+
### 10. Output report
|
|
135
|
+
|
|
136
|
+
```markdown
|
|
137
|
+
## Product Health Check
|
|
138
|
+
|
|
139
|
+
**Date**: {today's date}
|
|
140
|
+
**Scanned**: {feedback_count} feedbacks, {backlog_count} backlogs
|
|
141
|
+
|
|
142
|
+
### Summary
|
|
143
|
+
|
|
144
|
+
| Severity | Count |
|
|
145
|
+
|----------|-------|
|
|
146
|
+
| ERROR | {count} |
|
|
147
|
+
| WARNING | {count} |
|
|
148
|
+
| INFO | {count} |
|
|
149
|
+
|
|
150
|
+
### Findings
|
|
151
|
+
|
|
152
|
+
{For each finding, numbered sequentially:}
|
|
153
|
+
|
|
154
|
+
#### FINDING-{NNN} [{severity}] {type}
|
|
155
|
+
- **File**: {file path}
|
|
156
|
+
- **{detail key}**: {detail value}
|
|
157
|
+
- **Fix**: {suggested remediation}
|
|
158
|
+
|
|
159
|
+
### Verdict
|
|
160
|
+
|
|
161
|
+
**PASS** — No errors found. {warning_count} warning(s) to review.
|
|
162
|
+
or
|
|
163
|
+
**FAIL** — {error_count} error(s) require action.
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
If no findings at all:
|
|
167
|
+
```markdown
|
|
168
|
+
## Product Health Check
|
|
169
|
+
|
|
170
|
+
**Date**: {today's date}
|
|
171
|
+
**Scanned**: {feedback_count} feedbacks, {backlog_count} backlogs
|
|
172
|
+
|
|
173
|
+
**PASS** — Zero findings. Product data is consistent.
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
## Error Handling
|
|
177
|
+
|
|
178
|
+
- `.product/` missing → ERROR with init instructions
|
|
179
|
+
- Empty `.product/` → INFO, no checks to perform
|
|
180
|
+
- Unparseable file (malformed frontmatter) → WARN, skip file, report as finding
|
|
181
|
+
- `.features/` missing when checking backlog→feature links → skip those checks, note in report
|
|
182
|
+
|
|
183
|
+
## Rules
|
|
184
|
+
|
|
185
|
+
- This command is **READ-ONLY** except for `index.yaml` rebuild when desync is detected
|
|
186
|
+
- ALWAYS check ALL six categories of issues
|
|
187
|
+
- ALWAYS number findings sequentially (FINDING-001, FINDING-002, ...)
|
|
188
|
+
- ALWAYS provide a suggested fix for each finding
|
|
189
|
+
- Verdict is **FAIL** if ANY error-severity findings exist
|
|
190
|
+
- Verdict is **PASS** if only warnings or info findings (or none)
|
|
191
|
+
- NEVER modify feedback or backlog files — only report findings
|
|
192
|
+
- NEVER skip checks — run all 6 checks every time
|