@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 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
+ }
@@ -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