convoke-agents 3.2.0 → 3.3.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.
Files changed (38) hide show
  1. package/CHANGELOG.md +68 -0
  2. package/README.md +40 -11
  3. package/_bmad/bme/README.md +36 -0
  4. package/_bmad/bme/_enhance/workflows/initiatives-backlog/SKILL.md +1 -1
  5. package/_bmad/bme/_enhance/workflows/initiatives-backlog/steps-c/step-c-01-init.md +55 -32
  6. package/_bmad/bme/_enhance/workflows/initiatives-backlog/steps-c/step-c-02-gather.md +62 -59
  7. package/_bmad/bme/_enhance/workflows/initiatives-backlog/steps-c/step-c-03-qualify.md +176 -0
  8. package/_bmad/bme/_enhance/workflows/initiatives-backlog/steps-c/step-c-04-generate.md +259 -0
  9. package/_bmad/bme/_enhance/workflows/initiatives-backlog/steps-r/step-r-01-load.md +65 -35
  10. package/_bmad/bme/_enhance/workflows/initiatives-backlog/steps-r/step-r-02-rescore.md +60 -30
  11. package/_bmad/bme/_enhance/workflows/initiatives-backlog/steps-r/step-r-03-update.md +67 -71
  12. package/_bmad/bme/_enhance/workflows/initiatives-backlog/steps-t/step-t-01-ingest.md +12 -12
  13. package/_bmad/bme/_enhance/workflows/initiatives-backlog/steps-t/step-t-02-extract.md +49 -44
  14. package/_bmad/bme/_enhance/workflows/initiatives-backlog/steps-t/step-t-03-qualify.md +192 -0
  15. package/_bmad/bme/_enhance/workflows/initiatives-backlog/steps-t/step-t-04-update.md +72 -67
  16. package/_bmad/bme/_enhance/workflows/initiatives-backlog/templates/backlog-format-spec.md +223 -112
  17. package/_bmad/bme/_enhance/workflows/initiatives-backlog/templates/lifecycle-process-spec.md +188 -0
  18. package/_bmad/bme/_enhance/workflows/initiatives-backlog/workflow.md +44 -31
  19. package/_bmad/bme/_gyre/config.yaml +3 -0
  20. package/_bmad/bme/_vortex/config.yaml +4 -1
  21. package/package.json +2 -1
  22. package/scripts/convoke-doctor.js +56 -2
  23. package/scripts/lib/artifact-utils.js +16 -3
  24. package/scripts/migrate-artifacts.js +3 -2
  25. package/scripts/portability/catalog-generator.js +1 -1
  26. package/scripts/portability/convoke-export.js +26 -5
  27. package/scripts/portability/export-engine.js +34 -9
  28. package/scripts/portability/seed-catalog-repo.js +6 -6
  29. package/scripts/portability/validate-exports.js +25 -14
  30. package/scripts/update/convoke-update.js +27 -2
  31. package/scripts/update/lib/changelog-reader.js +90 -0
  32. package/scripts/update/lib/config-merger.js +52 -1
  33. package/scripts/update/lib/refresh-installation.js +48 -9
  34. package/scripts/update/lib/taxonomy-merger.js +2 -1
  35. package/scripts/update/lib/validator.js +9 -1
  36. package/_bmad/bme/_enhance/workflows/initiatives-backlog/steps-c/step-c-03-score.md +0 -146
  37. package/_bmad/bme/_enhance/workflows/initiatives-backlog/steps-c/step-c-04-prioritize.md +0 -181
  38. package/_bmad/bme/_enhance/workflows/initiatives-backlog/steps-t/step-t-03-score.md +0 -147
@@ -6,6 +6,7 @@ const versionDetector = require('./lib/version-detector');
6
6
  const migrationRunner = require('./lib/migration-runner');
7
7
  const registry = require('./migrations/registry');
8
8
  const { findProjectRoot } = require('./lib/utils');
9
+ const { readChangelogEntries } = require('./lib/changelog-reader');
9
10
 
10
11
  /**
11
12
  * Convoke Update CLI
@@ -158,6 +159,8 @@ async function main() {
158
159
  console.log(chalk.cyan('No migration deltas needed — refreshing installation files.'));
159
160
  console.log('');
160
161
 
162
+ printChangelog(assessment.currentVersion, assessment.targetVersion);
163
+
161
164
  if (dryRun) {
162
165
  console.log(chalk.yellow.bold('DRY RUN — no changes will be made'));
163
166
  console.log('');
@@ -220,6 +223,8 @@ async function main() {
220
223
  console.log('');
221
224
  }
222
225
 
226
+ printChangelog(assessment.currentVersion, assessment.targetVersion);
227
+
223
228
  // Dry run - preview only
224
229
  if (dryRun) {
225
230
  console.log(chalk.yellow.bold('DRY RUN - Previewing changes'));
@@ -279,6 +284,26 @@ async function main() {
279
284
  }
280
285
  }
281
286
 
287
+ /**
288
+ * Render CHANGELOG sections for versions in (fromVersion, toVersion] to stdout.
289
+ * Silently skips if there are no matching entries — this is a decorative surface.
290
+ */
291
+ function printChangelog(fromVersion, toVersion) {
292
+ const entries = readChangelogEntries(fromVersion, toVersion);
293
+ if (entries.length === 0) return;
294
+
295
+ console.log(chalk.cyan.bold("What's New:"));
296
+ console.log('');
297
+ for (const entry of entries) {
298
+ const header = entry.date
299
+ ? `${entry.version} — ${entry.date}`
300
+ : entry.version;
301
+ console.log(chalk.bold.green(header));
302
+ console.log(entry.body);
303
+ console.log('');
304
+ }
305
+ }
306
+
282
307
  /**
283
308
  * Confirm action with user
284
309
  * @param {string} message - Confirmation message
@@ -298,8 +323,8 @@ async function confirm(message) {
298
323
  });
299
324
  }
300
325
 
301
- // Export assessUpdate for testing
302
- module.exports = { assessUpdate };
326
+ // Export for testing
327
+ module.exports = { assessUpdate, printChangelog };
303
328
 
304
329
  // Run main when executed directly
305
330
  if (require.main === module) {
@@ -0,0 +1,90 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const { compareVersions } = require('./utils');
6
+
7
+ const DEFAULT_CHANGELOG_PATH = path.join(__dirname, '..', '..', '..', 'CHANGELOG.md');
8
+
9
+ // Broad bracket-capture so pre-release headers ([1.0.4-alpha]) don't get skipped —
10
+ // a skipped header would leak its body into the preceding entry. Non-semver labels
11
+ // (Unreleased, TBD) are dropped later by SEMVER_RE. Date may use ASCII or en/em dash.
12
+ const HEADER_RE = /^##\s+\[([^\]]+)\](?:\s*[-–—]\s*(.+?))?\s*$/;
13
+ const SEMVER_RE = /^\d+\.\d+\.\d+(?:[-+][\w.-]+)?$/;
14
+ const FENCE_RE = /^(?:```|~~~)/;
15
+
16
+ /**
17
+ * Parse a Keep-a-Changelog-formatted file and return entries for versions
18
+ * strictly greater than `fromVersion` and less than or equal to `toVersion`.
19
+ *
20
+ * Returns entries newest-first. Unreleased sections, malformed headers, and
21
+ * missing files are skipped silently — this is a decorative surface and must
22
+ * never break the update flow.
23
+ *
24
+ * @param {string|null|undefined} fromVersion - Installed version (exclusive lower bound). Null treats all entries <= toVersion as new.
25
+ * @param {string} toVersion - Target version (inclusive upper bound).
26
+ * @param {string} [changelogPath] - Absolute path to CHANGELOG.md.
27
+ * @returns {Array<{version: string, date: string|null, body: string}>}
28
+ */
29
+ function readChangelogEntries(fromVersion, toVersion, changelogPath = DEFAULT_CHANGELOG_PATH) {
30
+ if (!toVersion) return [];
31
+
32
+ let raw;
33
+ try {
34
+ raw = fs.readFileSync(changelogPath, 'utf8');
35
+ } catch {
36
+ return [];
37
+ }
38
+
39
+ const entries = [];
40
+ const lines = raw.split('\n');
41
+ let current = null;
42
+ let inFence = false;
43
+
44
+ const flush = () => {
45
+ if (!current) return;
46
+ current.body = current.bodyLines.join('\n').replace(/\s+$/, '');
47
+ delete current.bodyLines;
48
+ entries.push(current);
49
+ current = null;
50
+ };
51
+
52
+ for (const line of lines) {
53
+ if (FENCE_RE.test(line)) {
54
+ inFence = !inFence;
55
+ if (current) current.bodyLines.push(line);
56
+ continue;
57
+ }
58
+ if (!inFence) {
59
+ const match = HEADER_RE.exec(line);
60
+ if (match) {
61
+ flush();
62
+ current = {
63
+ version: match[1].trim(),
64
+ date: match[2] ? match[2].trim() : null,
65
+ bodyLines: [],
66
+ };
67
+ continue;
68
+ }
69
+ }
70
+ if (current) {
71
+ if (!inFence && /^---\s*$/.test(line)) continue;
72
+ current.bodyLines.push(line);
73
+ }
74
+ }
75
+ flush();
76
+
77
+ return entries
78
+ .filter((e) => {
79
+ if (!SEMVER_RE.test(e.version)) return false;
80
+ const aboveFloor = fromVersion ? compareVersions(e.version, fromVersion) > 0 : true;
81
+ const atOrBelowCeiling = compareVersions(e.version, toVersion) <= 0;
82
+ return aboveFloor && atOrBelowCeiling;
83
+ })
84
+ .sort((a, b) => compareVersions(b.version, a.version));
85
+ }
86
+
87
+ module.exports = {
88
+ readChangelogEntries,
89
+ DEFAULT_CHANGELOG_PATH,
90
+ };
@@ -19,6 +19,40 @@ const { assertVersion } = require('./utils');
19
19
 
20
20
  const MERGED_DOC_SENTINEL = Symbol.for('convoke.config-merger.docMerged');
21
21
 
22
+ /**
23
+ * Read `excluded_agents` from a module's config.yaml without going through the
24
+ * full merge path. Used by refresh-installation and validator to skip copying
25
+ * / checking agents the operator has opted out of. U8: permanent agent
26
+ * exclusions that survive upgrades.
27
+ *
28
+ * @param {string} configPath - Absolute path to module config.yaml
29
+ * @returns {string[]} Array of excluded agent IDs (empty if missing, malformed, or not an array)
30
+ */
31
+ function readExcludedAgents(configPath) {
32
+ let content;
33
+ try {
34
+ content = fs.readFileSync(configPath, 'utf8');
35
+ } catch (err) {
36
+ // ENOENT is expected on fresh installs (config hasn't been written yet).
37
+ // Other IO errors (EACCES, EISDIR, EMFILE, ...) indicate a real misconfiguration —
38
+ // warn so the operator knows their exclusions won't be applied this run. Never throw:
39
+ // this reader must not break the install flow.
40
+ if (err && err.code !== 'ENOENT') {
41
+ console.warn(`Warning: could not read ${configPath} for excluded_agents (${err.code || err.message}). Proceeding without exclusions.`);
42
+ }
43
+ return [];
44
+ }
45
+ try {
46
+ const parsed = yaml.load(content);
47
+ if (parsed && Array.isArray(parsed.excluded_agents)) {
48
+ return parsed.excluded_agents.filter(a => typeof a === 'string');
49
+ }
50
+ } catch (err) {
51
+ console.warn(`Warning: could not parse ${configPath} for excluded_agents (${err.message}). Proceeding without exclusions.`);
52
+ }
53
+ return [];
54
+ }
55
+
22
56
  /**
23
57
  * Merge current config with new template while preserving user preferences.
24
58
  * Agents and workflows use smart-merge: canonical entries in registry order
@@ -78,13 +112,29 @@ async function mergeConfig(currentConfigPath, newVersion, updates = {}) {
78
112
 
79
113
  // Smart-merge agents: canonical agents in order, then unique user-added agents appended.
80
114
  // Core agents are always restored to canonical order. User-added agents (not in AGENT_IDS)
81
- // are preserved and deduplicated. Deliberately removed core agents are restored on upgrade.
115
+ // are preserved and deduplicated.
116
+ //
117
+ // U8: respect `excluded_agents` — an operator-maintained opt-out list. Agents named in that
118
+ // list are filtered out of the active `agents` array so deliberate removals survive upgrades.
119
+ // Re-inclusion works by removing the agent from `excluded_agents` — the next merge restores
120
+ // it via the canonical spread above.
121
+ const excludedAgents = Array.isArray(current.excluded_agents)
122
+ ? current.excluded_agents.filter(a => typeof a === 'string')
123
+ : [];
82
124
  if (updates.agents) {
83
125
  const userAgents = Array.isArray(current.agents)
84
126
  ? [...new Set(current.agents.filter(a => !AGENT_IDS.includes(a)))]
85
127
  : [];
86
128
  merged.agents = [...updates.agents, ...userAgents];
87
129
  }
130
+ // Apply exclusions to merged.agents regardless of whether `updates.agents` was provided —
131
+ // otherwise callers that pass empty updates (e.g., a workflows-only migration delta) would
132
+ // leak excluded agents back via the `defaults` spread at line 96.
133
+ if (excludedAgents.length > 0 && Array.isArray(merged.agents)) {
134
+ merged.agents = merged.agents.filter(a => !excludedAgents.includes(a));
135
+ }
136
+ // Preserve the exclusion list as a first-class field (empty stays empty — the schema default).
137
+ merged.excluded_agents = excludedAgents;
88
138
 
89
139
  // Smart-merge workflows: canonical workflows in order, then unique user-added appended
90
140
  if (updates.workflows) {
@@ -365,6 +415,7 @@ function addMigrationHistory(config, fromVersion, toVersion, migrationsApplied)
365
415
  module.exports = {
366
416
  CONFIG_SCHEMA,
367
417
  mergeConfig,
418
+ readExcludedAgents,
368
419
  extractUserPreferences,
369
420
  validateConfig,
370
421
  writeConfig,
@@ -6,7 +6,7 @@ const yaml = require('js-yaml');
6
6
  const YAML = require('yaml'); // Comment-preserving YAML library (ag-7-1: I29). Use for WRITE sites that need to preserve comments. js-yaml stays for read-only consumers.
7
7
  const { getPackageVersion, assertVersion } = require('./utils');
8
8
  const configMerger = require('./config-merger');
9
- const { AGENTS, AGENT_FILES, AGENT_IDS, WORKFLOW_NAMES, USER_GUIDES, GYRE_AGENTS, GYRE_AGENT_FILES, GYRE_AGENT_IDS, GYRE_WORKFLOW_NAMES, EXTRA_BME_AGENTS } = require('./agent-registry');
9
+ const { AGENTS, AGENT_FILES, AGENT_IDS, WORKFLOW_NAMES, GYRE_AGENTS, GYRE_AGENT_FILES, GYRE_AGENT_IDS, GYRE_WORKFLOW_NAMES, EXTRA_BME_AGENTS } = require('./agent-registry');
10
10
 
11
11
  /**
12
12
  * Refresh Installation for Convoke
@@ -37,6 +37,15 @@ async function refreshInstallation(projectRoot, options = {}) {
37
37
  // source and destination are identical — skip file copies.
38
38
  const isSameRoot = path.resolve(packageRoot) === path.resolve(projectRoot);
39
39
 
40
+ // U8: read per-module `excluded_agents` from target configs BEFORE copy.
41
+ // These are opt-out lists the operator maintains; excluded agents don't get
42
+ // their agent file copied, don't get a skill wrapper generated, and don't
43
+ // fail presence checks downstream.
44
+ const vortexExcluded = configMerger.readExcludedAgents(path.join(targetVortex, 'config.yaml'));
45
+ const gyreExcluded = configMerger.readExcludedAgents(
46
+ path.join(projectRoot, '_bmad', 'bme', '_gyre', 'config.yaml')
47
+ );
48
+
40
49
  // 1. Copy agent files
41
50
  const agentsSource = path.join(packageVortex, 'agents');
42
51
  const agentsTarget = path.join(targetVortex, 'agents');
@@ -44,6 +53,12 @@ async function refreshInstallation(projectRoot, options = {}) {
44
53
 
45
54
  if (!isSameRoot) {
46
55
  for (const file of AGENT_FILES) {
56
+ const agentId = file.replace(/\.md$/, '');
57
+ if (vortexExcluded.includes(agentId)) {
58
+ changes.push(`Skipped excluded Vortex agent: ${file}`);
59
+ if (verbose) console.log(` Skipped excluded Vortex agent: ${file}`);
60
+ continue;
61
+ }
47
62
  const src = path.join(agentsSource, file);
48
63
  if (fs.existsSync(src)) {
49
64
  await fs.copy(src, path.join(agentsTarget, file), { overwrite: true });
@@ -290,6 +305,12 @@ async function refreshInstallation(projectRoot, options = {}) {
290
305
 
291
306
  if (!isSameRoot) {
292
307
  for (const file of GYRE_AGENT_FILES) {
308
+ const agentId = file.replace(/\.md$/, '');
309
+ if (gyreExcluded.includes(agentId)) {
310
+ changes.push(`Skipped excluded Gyre agent: ${file}`);
311
+ if (verbose) console.log(` Skipped excluded Gyre agent: ${file}`);
312
+ continue;
313
+ }
293
314
  const src = path.join(gyreAgentsSource, file);
294
315
  if (fs.existsSync(src)) {
295
316
  await fs.copy(src, path.join(gyreAgentsTarget, file), { overwrite: true });
@@ -502,17 +523,21 @@ async function refreshInstallation(projectRoot, options = {}) {
502
523
  ].map(csvEscape).join(',');
503
524
  }
504
525
 
526
+ // U8: filter out excluded agents so manifest rows don't point at wrappers the
527
+ // stale-cleanup loop (§6) just removed. Left in, rows become dangling pointers.
528
+ const activeVortexAgents = AGENTS.filter(a => !vortexExcluded.includes(a.id));
529
+ const activeGyreAgents = GYRE_AGENTS.filter(a => !gyreExcluded.includes(a.id));
505
530
  let bmeRows;
506
531
  if (isV610) {
507
532
  bmeRows = [
508
- ...AGENTS.map(a => buildAgentRow610(a, '_vortex')),
509
- ...GYRE_AGENTS.map(a => buildAgentRow610(a, '_gyre')),
533
+ ...activeVortexAgents.map(a => buildAgentRow610(a, '_vortex')),
534
+ ...activeGyreAgents.map(a => buildAgentRow610(a, '_gyre')),
510
535
  ...EXTRA_BME_AGENTS.map(buildExtraBmeAgentRow610),
511
536
  ];
512
537
  } else {
513
538
  bmeRows = [
514
- ...AGENTS.map(a => buildAgentRowLegacy(a, '_vortex')),
515
- ...GYRE_AGENTS.map(a => buildAgentRowLegacy(a, '_gyre')),
539
+ ...activeVortexAgents.map(a => buildAgentRowLegacy(a, '_vortex')),
540
+ ...activeGyreAgents.map(a => buildAgentRowLegacy(a, '_gyre')),
516
541
  ...EXTRA_BME_AGENTS.map(buildExtraBmeAgentRowLegacy),
517
542
  ];
518
543
  }
@@ -528,7 +553,16 @@ async function refreshInstallation(projectRoot, options = {}) {
528
553
  await fs.ensureDir(guidesTarget);
529
554
 
530
555
  if (!isSameRoot) {
531
- for (const guide of USER_GUIDES) {
556
+ // U8: user guides are named after each agent (e.g., NOAH-USER-GUIDE.md). Iterate
557
+ // AGENTS so we can match guides to agent IDs and skip excluded ones — a guide
558
+ // without its agent is dead docs.
559
+ for (const agent of AGENTS) {
560
+ const guide = `${agent.name.toUpperCase()}-USER-GUIDE.md`;
561
+ if (vortexExcluded.includes(agent.id)) {
562
+ changes.push(`Skipped excluded guide: ${guide}`);
563
+ if (verbose) console.log(` Skipped excluded guide: ${guide}`);
564
+ continue;
565
+ }
532
566
  const src = path.join(guidesSource, guide);
533
567
  const dest = path.join(guidesTarget, guide);
534
568
 
@@ -563,10 +597,13 @@ async function refreshInstallation(projectRoot, options = {}) {
563
597
 
564
598
  const skillsDir = path.join(projectRoot, '.claude', 'skills');
565
599
 
566
- // Remove stale skill directories (agents no longer in registry)
600
+ // Remove stale skill directories (agents no longer in registry OR excluded by operator).
601
+ // U8: excluded agents are intentionally omitted from the valid set so the stale-removal
602
+ // loop below deletes their wrappers on the next refresh. Re-inclusion (removing from
603
+ // excluded_agents) regenerates the wrapper here.
567
604
  const currentSkillDirs = new Set([
568
- ...AGENTS.map(a => `bmad-agent-bme-${a.id}`),
569
- ...GYRE_AGENTS.map(a => `bmad-agent-bme-${a.id}`),
605
+ ...AGENTS.filter(a => !vortexExcluded.includes(a.id)).map(a => `bmad-agent-bme-${a.id}`),
606
+ ...GYRE_AGENTS.filter(a => !gyreExcluded.includes(a.id)).map(a => `bmad-agent-bme-${a.id}`),
570
607
  ...EXTRA_BME_AGENTS.map(a => `bmad-agent-bme-${a.id}`),
571
608
  ]);
572
609
  if (fs.existsSync(skillsDir)) {
@@ -581,6 +618,7 @@ async function refreshInstallation(projectRoot, options = {}) {
581
618
  }
582
619
 
583
620
  for (const agent of AGENTS) {
621
+ if (vortexExcluded.includes(agent.id)) continue;
584
622
  const skillDir = path.join(skillsDir, `bmad-agent-bme-${agent.id}`);
585
623
  await fs.ensureDir(skillDir);
586
624
  const content = `---
@@ -606,6 +644,7 @@ You must fully embody this agent's persona and follow all activation instruction
606
644
 
607
645
  // 6b. Generate .claude/skills/ for Gyre agents
608
646
  for (const agent of GYRE_AGENTS) {
647
+ if (gyreExcluded.includes(agent.id)) continue;
609
648
  const skillDir = path.join(skillsDir, `bmad-agent-bme-${agent.id}`);
610
649
  await fs.ensureDir(skillDir);
611
650
  const content = `---
@@ -11,7 +11,8 @@ const PLATFORM_INITIATIVES = ['vortex', 'gyre', 'bmm', 'forge', 'helm', 'enhance
11
11
  const DEFAULT_ARTIFACT_TYPES = [
12
12
  'prd', 'epic', 'arch', 'adr', 'persona', 'lean-persona', 'empathy-map',
13
13
  'problem-def', 'hypothesis', 'experiment', 'signal', 'decision', 'scope',
14
- 'pre-reg', 'sprint', 'brief', 'vision', 'report', 'research', 'story', 'spec'
14
+ 'pre-reg', 'sprint', 'brief', 'vision', 'report', 'research', 'story', 'spec',
15
+ 'covenant'
15
16
  ];
16
17
 
17
18
  const DEFAULT_ALIASES = {
@@ -111,7 +111,15 @@ async function validateAgentFiles(projectRoot) {
111
111
 
112
112
  try {
113
113
  const agentsDir = path.join(projectRoot, '_bmad/bme/_vortex/agents');
114
- const requiredAgents = AGENT_FILES;
114
+ // U8: honor operator agent exclusions from the target config.yaml, so a
115
+ // deliberately-removed agent doesn't fail post-upgrade validation.
116
+ const configMerger = require('./config-merger');
117
+ const excluded = configMerger.readExcludedAgents(
118
+ path.join(projectRoot, '_bmad/bme/_vortex/config.yaml')
119
+ );
120
+ const requiredAgents = AGENT_FILES.filter(
121
+ f => !excluded.includes(f.replace(/\.md$/, ''))
122
+ );
115
123
 
116
124
  if (!fs.existsSync(agentsDir)) {
117
125
  check.error = 'agents/ directory not found';
@@ -1,146 +0,0 @@
1
- ---
2
- name: 'step-c-03-score'
3
- description: 'Propose RICE scores for gathered initiatives in batch, validate with user, assign track labels'
4
- nextStepFile: '{project-root}/_bmad/bme/_enhance/workflows/initiatives-backlog/steps-c/step-c-04-prioritize.md'
5
- outputFile: '{planning_artifacts}/initiatives-backlog.md'
6
- templateFile: '{project-root}/_bmad/bme/_enhance/workflows/initiatives-backlog/templates/rice-scoring-guide.md'
7
- advancedElicitationTask: '{project-root}/_bmad/core/workflows/advanced-elicitation/workflow.md'
8
- partyModeWorkflow: '{project-root}/_bmad/core/workflows/bmad-party-mode/workflow.md'
9
- ---
10
-
11
- # Step 3: Batch RICE Scoring
12
-
13
- ## STEP GOAL:
14
-
15
- Propose RICE scores and Track assignments for all gathered initiatives, present the scored batch for user validation, and finalize scores before backlog generation.
16
-
17
- ## MANDATORY EXECUTION RULES (READ FIRST):
18
-
19
- ### Universal Rules:
20
- - 🛑 NEVER generate content without user input at the scoring menu
21
- - 📖 CRITICAL: Read this complete step file before taking action
22
- - 🔄 CRITICAL: When loading next step with 'C', read the entire file
23
- - 📋 YOU ARE A SCORING ANALYST proposing calibrated RICE scores
24
-
25
- ### Role Reinforcement:
26
- - ✅ You are a **RICE scoring analyst** — systematic, calibrated, evidence-based
27
- - ✅ Propose scores grounded in the scoring guide's definitions and calibration examples
28
- - ✅ The user validates and adjusts your proposals — you propose, they decide
29
- - ✅ Assign Track labels based on initiative nature: maintenance/stability = "Keep the lights on", growth/new capability = "Move the needle"
30
-
31
- ### Step-Specific Rules:
32
- - 🎯 Focus on scoring, rationale, Track assignment, and composite calculation
33
- - 🚫 FORBIDDEN to write to the backlog file (that is step-c-04's job)
34
- - 🚫 FORBIDDEN to re-gather or add new initiatives (that was step-c-02's job)
35
- - 💬 Approach: propose entire batch at once so user sees relative positioning, then collaborative refinement
36
-
37
- ## EXECUTION PROTOCOLS:
38
- - 🎯 Follow the MANDATORY SEQUENCE exactly
39
- - 📖 Load `{templateFile}` for RICE factor definitions, scales, and calibration examples
40
- - 💾 Recalculate and re-sort after every score adjustment
41
-
42
- ## CONTEXT BOUNDARIES:
43
- - Available context: Gathered initiatives from step-c-02, RICE scoring guide template
44
- - Focus: Scoring and user validation only
45
- - Limits: Do NOT write to backlog or modify gathered initiative descriptions
46
- - Dependencies: step-c-02-gather.md (gathered initiatives list)
47
-
48
- ## MANDATORY SEQUENCE
49
-
50
- **CRITICAL:** Follow this sequence exactly. Do not skip, reorder, or improvise.
51
-
52
- ### 1. Load RICE Scoring Guide
53
-
54
- Load `{templateFile}` (rice-scoring-guide.md) and internalize:
55
- - **Factor definitions:** Reach (1-10), Impact (0.25-3), Confidence (20-100%), Effort (1-10)
56
- - **Guided questions** for each factor
57
- - **Calibration examples** (study the reasoning, not just the numbers)
58
- - **Composite formula:** Score = (R x I x C) / E, where C is decimal (e.g., 70% = 0.7)
59
- - **Score rounding:** One decimal place for display
60
-
61
- ### 2. Propose RICE Scores and Track for All Initiatives
62
-
63
- For each gathered initiative, propose RICE scores using the guided questions:
64
-
65
- - **Reach (1-10):** "How many users per quarter will this affect?"
66
- - **Impact (0.25-3):** "What's the per-user impact?"
67
- - **Confidence (20-100%):** "How confident are we in these estimates?" Default to 50% when no direct evidence exists.
68
- - **Effort (1-10):** "Relative effort in story points?"
69
- - **Track:** "Keep the lights on" (maintenance, stability, bug fixes) or "Move the needle" (growth, new capability, strategic)
70
-
71
- For each score, write a **one-line rationale** explaining the scoring basis. Example:
72
-
73
- > **#1: Add output examples for Noah agent** — R:5 I:1 C:70% E:2 = 1.8 | Move the needle
74
- > *Reach 5: affects users checking agent outputs. Impact 1: helpful but workarounds exist. Confidence 70%: pattern validated with other agents. Effort 2: single file addition.*
75
-
76
- ### 3. Calculate Composite Scores and Sort
77
-
78
- For each initiative:
79
- 1. Calculate composite: Score = (Reach x Impact x Confidence) / Effort
80
- 2. Round to one decimal place
81
- 3. Verify score falls within expected range (~0.0 to ~30.0)
82
-
83
- Sort the batch:
84
- 1. **Primary:** Descending by composite score
85
- 2. **Tiebreak 1:** Higher Confidence first
86
- 3. **Tiebreak 2:** Newer insertion order first
87
-
88
- ### 4. Present Scoring Batch
89
-
90
- Display the scored results:
91
-
92
- > **RICE Scoring — Review Proposed Scores**
93
- >
94
- > **Initiatives scored: [N]**
95
- >
96
- > | # | Initiative | R | I | C | E | Score | Track | Rationale |
97
- > |---|-----------|---|---|---|---|-------|-------|-----------|
98
- > | 1 | [title] | 5 | 2 | 80% | 3 | 2.7 | Move the needle | [one-line rationale] |
99
- > | 2 | [title] | 3 | 1 | 60% | 2 | 0.9 | Keep the lights on | [one-line rationale] |
100
- >
101
- > *Sorted by composite score (descending). Formula: (R x I x C) / E*
102
-
103
- ### 5. Present SCORING MENU OPTIONS
104
-
105
- Display:
106
-
107
- > **Adjust scores or finalize:**
108
- >
109
- > **Score adjustments** (by item number):
110
- > - `#N R [value]` — Change Reach (1-10)
111
- > - `#N I [value]` — Change Impact (0.25, 0.5, 1, 2, or 3)
112
- > - `#N CF [value]` — Change Confidence (20-100%)
113
- > - `#N E [value]` — Change Effort (1-10)
114
- > - `#N T [value]` — Change Track ("keep" or "move")
115
- >
116
- > **Batch editing:**
117
- > - `D #N` — Drop item #N from the batch (will not be added to backlog)
118
- >
119
- > **[A] Advanced Elicitation** — Deeper analysis of scoring rationale
120
- > **[P] Party Mode** — Multi-perspective scoring discussion
121
- > **[C] Continue** — Finalize scores and proceed to backlog generation
122
-
123
- #### Menu Handling Logic:
124
- - IF `#N R [value]`: Update Reach for item #N. Recalculate composite. Re-sort batch. Redisplay table and menu.
125
- - IF `#N I [value]`: Update Impact for item #N. Recalculate composite. Re-sort batch. Redisplay table and menu.
126
- - IF `#N CF [value]`: Update Confidence for item #N. Recalculate composite. Re-sort batch. Redisplay table and menu.
127
- - IF `#N E [value]`: Update Effort for item #N. Recalculate composite. Re-sort batch. Redisplay table and menu.
128
- - IF `#N T [value]`: Update Track for item #N ("keep" = "Keep the lights on", "move" = "Move the needle"). Redisplay table and menu.
129
- - IF `D #N`: Remove item #N from the scoring batch. Redisplay table and menu.
130
- - IF A: Execute `{advancedElicitationTask}` for deeper scoring analysis, and when finished redisplay the menu.
131
- - IF P: Execute `{partyModeWorkflow}` for multi-perspective scoring discussion, and when finished redisplay the menu.
132
- - IF C: Finalize the scored batch. Load, read the entire file, and execute `{project-root}/_bmad/bme/_enhance/workflows/initiatives-backlog/steps-c/step-c-04-prioritize.md`
133
- - IF any other input: Display "Unknown command. Use `#N R/I/CF/E/T [value]`, `D #N`, **A**, **P**, or **C** to continue." then redisplay menu.
134
-
135
- #### EXECUTION RULES:
136
- - ALWAYS halt and wait for user input after presenting the menu
137
- - After EVERY score or Track adjustment, recalculate composite, re-sort, and redisplay the full table AND the menu
138
- - The user may make multiple adjustments before pressing C
139
- - ONLY proceed to step-c-04 when user selects 'C'
140
- - After A or P execution, return to this menu
141
- - Do NOT auto-continue — the user must explicitly approve the scores
142
-
143
- ## 🚨 SYSTEM SUCCESS/FAILURE METRICS:
144
- ### ✅ SUCCESS: All initiatives scored with calibrated RICE components, rationale, and Track assignment, composites calculated correctly, batch sorted by score, user validated scores, finalized batch passed to step-c-04
145
- ### ❌ SYSTEM FAILURE: Scores proposed without rationale, composite formula wrong, Track not assigned, scores outside valid ranges, user not given validation opportunity, items written to backlog prematurely
146
- **Master Rule:** Skipping steps is FORBIDDEN.