convoke-agents 3.2.1 → 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 (37) hide show
  1. package/CHANGELOG.md +68 -0
  2. package/README.md +3 -1
  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 +1 -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/convoke-export.js +26 -5
  26. package/scripts/portability/export-engine.js +10 -8
  27. package/scripts/portability/seed-catalog-repo.js +6 -6
  28. package/scripts/portability/validate-exports.js +25 -14
  29. package/scripts/update/convoke-update.js +27 -2
  30. package/scripts/update/lib/changelog-reader.js +90 -0
  31. package/scripts/update/lib/config-merger.js +52 -1
  32. package/scripts/update/lib/refresh-installation.js +48 -9
  33. package/scripts/update/lib/taxonomy-merger.js +2 -1
  34. package/scripts/update/lib/validator.js +9 -1
  35. package/_bmad/bme/_enhance/workflows/initiatives-backlog/steps-c/step-c-03-score.md +0 -146
  36. package/_bmad/bme/_enhance/workflows/initiatives-backlog/steps-c/step-c-04-prioritize.md +0 -181
  37. package/_bmad/bme/_enhance/workflows/initiatives-backlog/steps-t/step-t-03-score.md +0 -147
@@ -1,14 +1,14 @@
1
1
  ---
2
2
  workflow: initiatives-backlog
3
3
  type: step-file
4
- description: Tri-modal RICE initiatives backlog management — Triage review findings, Review existing items, or Create a new backlog from scratch
4
+ description: Lane-aware initiative lifecycle backlog management — Triage intakes through the qualifying gate, review lane items, or bootstrap a new lifecycle backlog
5
5
  author: John PM (pm.md)
6
- version: 1.0.0
6
+ version: 2.0.0
7
7
  ---
8
8
 
9
- # Initiatives Backlog Workflow
9
+ # Initiative Lifecycle Backlog Workflow
10
10
 
11
- Manage a RICE-scored initiatives backlog through three complementary modes. This workflow transforms unstructured review findings, audit outputs, and team discussions into prioritized, scored backlog items and keeps them calibrated over time.
11
+ Manage the **Convoke Initiative Lifecycle & Backlog** — a three-lane (Bug / Fast / Initiative) model with a qualifying gate (Vortex, John, or Winston). Transforms unstructured findings into logged intakes, routes them through qualification, and keeps RICE scores calibrated across lanes over time.
12
12
 
13
13
  ## Workflow Structure
14
14
 
@@ -16,29 +16,32 @@ Manage a RICE-scored initiatives backlog through three complementary modes. This
16
16
  - Just-in-time loading (each step loads only when needed)
17
17
  - Sequential enforcement within each mode
18
18
  - State tracking in frontmatter (progress preserved across sessions)
19
- - Two-gate validation in Triage mode (extraction review, then scoring review)
19
+ - Qualifying gate sub-flow in Triage + Create modes
20
20
 
21
21
  ## Modes Overview
22
22
 
23
- ### [T] Triage — Ingest Review Findings
24
- Accepts text input (review transcripts, meeting notes, audit outputs), extracts actionable findings, proposes RICE scores with two-gate validation, and appends scored items to the existing backlog.
25
- - **Steps:** Ingest > Extract & Gate 1 > Score & Gate 2 > Update backlog
26
- - **When to use:** After a party-mode review, code review, retrospective, or any session that produces findings
23
+ ### [T] Triage — Ingest Intake → Qualify
24
+ Accepts text input (review transcripts, party mode outputs, audit findings, retro notes), extracts actionable findings, logs every finding as an **Intake** (§2.1), then offers the qualifying gate: for each intake, assign a **Lane** (Bug / Fast / Initiative), **Portfolio**, and **RICE** score.
25
+ - **Steps:** Ingest > Extract & log to Intakes > Qualify into lanes > Update backlog
26
+ - **When to use:** After any session that produces findings — code review, retrospective, party mode, user report
27
27
 
28
28
  ### [R] Review — Rescore Existing Items
29
- Loads the current backlog and walks through items for rescoring. Prevents score drift by prompting reassessment of Reach, Impact, Confidence, and Effort as project context evolves.
30
- - **Steps:** Load backlog > Walkthrough & rescore > Update backlog
31
- - **When to use:** Periodically (monthly or after major project milestones) to keep priorities calibrated
29
+ Loads the current backlog, lets you pick which lane(s) to walk (Bug / Fast / Initiative / All), then walks items one at a time for RICE rescoring. Prevents score drift as project context evolves.
30
+ - **Steps:** Load & choose lanes > Walkthrough & rescore > Update backlog
31
+ - **When to use:** Periodically (monthly, after major milestones, or when an epic completes and changes adjacent effort)
32
32
 
33
- ### [C] Create — Build New Backlog
34
- Bootstraps a new RICE-scored backlog from scratch through guided interactive gathering and scoring.
35
- - **Steps:** Initialize > Gather initiatives > Score > Generate prioritized view
36
- - **When to use:** Starting a new project or creating a fresh backlog for a new domain
33
+ ### [C] Create — Bootstrap New Lifecycle Backlog
34
+ Generates a new lifecycle backlog file from scratch: Part 1 (canonical lifecycle process) verbatim from template + empty Part 2 lanes. Optionally gather initial intakes and run the qualifying gate on them.
35
+ - **Steps:** Initialize & guard overwrite > Gather intakes (optional) > Qualify (optional) > Generate file
36
+ - **When to use:** Starting a new project or creating a fresh backlog for a new domain. Existing Convoke backlog was bootstrapped on 2026-04-12.
37
37
 
38
38
  ## Output
39
39
 
40
- **Artifact:** `{planning_artifacts}/initiatives-backlog.md`
41
- **Templates:** `templates/rice-scoring-guide.md`, `templates/backlog-format-spec.md`
40
+ **Artifact:** `{planning_artifacts}/convoke-note-initiative-lifecycle-backlog.md`
41
+ **Templates:**
42
+ - `templates/backlog-format-spec.md` — canonical file structure and table formats
43
+ - `templates/lifecycle-process-spec.md` — canonical Part 1 content (Create mode only)
44
+ - `templates/rice-scoring-guide.md` — RICE factor definitions and calibration examples
42
45
 
43
46
  ---
44
47
 
@@ -52,11 +55,11 @@ Display the following menu to the user:
52
55
 
53
56
  ---
54
57
 
55
- **Initiatives Backlog — Select a Mode:**
58
+ **Initiative Lifecycle Backlog — Select a Mode:**
56
59
 
57
- - **[T] Triage** — Ingest review findings into scored backlog items
58
- - **[R] Review** — Rescore existing backlog items to keep priorities calibrated
59
- - **[C] Create** — Bootstrap a new RICE-scored backlog from scratch
60
+ - **[T] Triage** — Ingest findings log as intakes → route through qualifying gate into lanes
61
+ - **[R] Review** — Walk a lane and rescore items to keep RICE calibrated
62
+ - **[C] Create** — Bootstrap a new lifecycle backlog from scratch
60
63
  - **[X] Exit** — Return to John PM agent menu
61
64
 
62
65
  ---
@@ -68,21 +71,31 @@ Display the following menu to the user:
68
71
  - **IF T:** Load, read the entire file, and execute `{project-root}/_bmad/bme/_enhance/workflows/initiatives-backlog/steps-t/step-t-01-ingest.md`
69
72
  - **IF R:** Load, read the entire file, and execute `{project-root}/_bmad/bme/_enhance/workflows/initiatives-backlog/steps-r/step-r-01-load.md`
70
73
  - **IF C:** Load, read the entire file, and execute `{project-root}/_bmad/bme/_enhance/workflows/initiatives-backlog/steps-c/step-c-01-init.md`
71
- - **IF X:** Display "Exiting Initiatives Backlog workflow." and end the workflow — return control to the John PM agent menu
74
+ - **IF X:** Display "Exiting Initiative Lifecycle Backlog workflow." and end the workflow — return control to the John PM agent menu
72
75
  - **IF any other input:** Display "Unknown option. Please select **T**, **R**, **C**, or **X**." then redisplay the Mode Selection menu above
73
76
 
74
77
  ### EXECUTION RULES:
75
78
 
76
79
  - ALWAYS halt and wait for user input after presenting the menu
77
- - Do NOT auto-select a mode — the user must explicitly choose (ADR-3)
78
- - Modes run independently — do NOT switch modes mid-execution (ADR-4)
80
+ - Do NOT auto-select a mode — the user must explicitly choose
81
+ - Modes run independently — do NOT switch modes mid-execution
79
82
  - After X, end the workflow completely
80
83
 
81
84
  ---
82
85
 
83
- <!-- RETURN-TO-MENU CONVENTION (for step file authors):
84
- When the final step of any mode completes (e.g., step-t-04-update.md for Triage),
85
- it must instruct the LLM to re-load this entire workflow file:
86
- {project-root}/_bmad/bme/_enhance/workflows/initiatives-backlog/workflow.md
87
- This re-presents the INITIALIZATION section and Mode Selection menu,
88
- allowing the user to run another mode or exit (FR28). -->
86
+ ## Qualifying Gate Reference
87
+
88
+ The **qualifying gate** is the decision point in Triage and Create modes where intakes become lane items. Only three qualifiers can invoke it:
89
+
90
+ 1. **Vortex team** through discovery (full 7-stream or partial)
91
+ 2. **John (PM)** product framing shortcut (this agent)
92
+ 3. **Winston (Architect)** — technical framing shortcut
93
+
94
+ The qualifier assigns:
95
+ - **Lane:** Bug / Fast / Initiative (per §1.3 of lifecycle-process-spec.md)
96
+ - **Portfolio:** convoke, vortex, gyre, forge, bmm, enhance, loom, helm — or new (John+Winston decision)
97
+ - **RICE:** Reach × Impact × Confidence / Effort (per rice-scoring-guide.md)
98
+
99
+ **Default lane when uncertain:** Fast Lane. Can be promoted later if scope grows.
100
+
101
+ **Intakes stay logged even after qualification.** §2.1 is the audit trail — never deleted.
@@ -7,6 +7,9 @@ agents:
7
7
  - model-curator
8
8
  - readiness-analyst
9
9
  - review-coach
10
+ # Agents you have deliberately removed — they will NOT be restored on `convoke-update`.
11
+ # Add an agent ID here to opt out permanently; remove to re-enable on the next update.
12
+ excluded_agents: []
10
13
  workflows:
11
14
  - full-analysis
12
15
  - stack-detection
@@ -10,6 +10,9 @@ agents:
10
10
  - lean-experiments-specialist
11
11
  - production-intelligence-specialist
12
12
  - learning-decision-expert
13
+ # Agents you have deliberately removed — they will NOT be restored on `convoke-update`.
14
+ # Add an agent ID here to opt out permanently; remove to re-enable on the next update.
15
+ excluded_agents: []
13
16
  workflows:
14
17
  - lean-persona
15
18
  - product-vision
@@ -33,7 +36,7 @@ workflows:
33
36
  - learning-card
34
37
  - pivot-patch-persevere
35
38
  - vortex-navigation
36
- version: 3.2.0
39
+ version: 3.3.0
37
40
  user_name: "{user}"
38
41
  communication_language: en
39
42
  party_mode_enabled: true
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "convoke-agents",
3
- "version": "3.2.1",
3
+ "version": "3.3.0",
4
4
  "description": "Agent teams for complex systems, compatible with BMad Method",
5
5
  "main": "index.js",
6
6
  "files": [
@@ -5,6 +5,7 @@ const path = require('path');
5
5
  const chalk = require('chalk');
6
6
  const yaml = require('js-yaml');
7
7
  const { findProjectRoot, getPackageVersion } = require('./update/lib/utils');
8
+ const { AGENTS, GYRE_AGENTS, EXTRA_BME_AGENTS } = require('./update/lib/agent-registry');
8
9
  // Note: parseCsvRow is loaded LAZILY inside loadSkillManifest() (ag-7-2 review patch).
9
10
  // Top-level require would crash the doctor on installs missing _team-factory/ — exactly
10
11
  // the broken-install case the doctor exists to diagnose. The lazy require is wrapped
@@ -82,7 +83,10 @@ async function main() {
82
83
  }
83
84
  }
84
85
 
85
- // 4. Global checks (module-agnostic)
86
+ // 4. Agent skill wrapper check (I43: spans all bme modules)
87
+ checks.push(checkAgentSkillWrappers(projectRoot, modules));
88
+
89
+ // 5. Global checks (module-agnostic)
86
90
  checks.push(await checkOutputDir(projectRoot));
87
91
  checks.push(checkMigrationLock(projectRoot));
88
92
  checks.push(checkVersionConsistency(projectRoot, modules));
@@ -196,6 +200,12 @@ function checkModuleAgents(mod) {
196
200
  const label = `${mod.name} agents`;
197
201
  const agentsDir = path.join(mod.dir, 'agents');
198
202
  const agentIds = mod.config.agents;
203
+ // U8: `agents` in config.yaml already has exclusions filtered out (mergeConfig does this
204
+ // at upgrade time). We read `excluded_agents` purely to surface the opt-out list in the
205
+ // info line — so operators can see what's excluded without cross-referencing files.
206
+ const excluded = Array.isArray(mod.config.excluded_agents)
207
+ ? mod.config.excluded_agents.filter(a => typeof a === 'string')
208
+ : [];
199
209
 
200
210
  if (!fs.existsSync(agentsDir)) {
201
211
  return {
@@ -232,7 +242,11 @@ function checkModuleAgents(mod) {
232
242
  };
233
243
  }
234
244
 
235
- return { name: label, passed: true, info: `${agentIds.length} agents present` };
245
+ let info = `${agentIds.length} agents present`;
246
+ if (excluded.length > 0) {
247
+ info += ` (${excluded.length} excluded: ${excluded.join(', ')})`;
248
+ }
249
+ return { name: label, passed: true, info };
236
250
  }
237
251
 
238
252
  function checkModuleWorkflows(mod) {
@@ -421,6 +435,46 @@ function checkModuleSkillWrappers(mod, projectRoot, manifestMap) {
421
435
  };
422
436
  }
423
437
 
438
+ function checkAgentSkillWrappers(projectRoot, modules = []) {
439
+ const label = 'BME agent skill wrappers';
440
+ // U8: gather excluded agent IDs across all discovered modules so their wrappers
441
+ // don't get flagged as missing (they are intentionally absent).
442
+ const excludedIds = new Set();
443
+ for (const mod of modules) {
444
+ if (mod.config && Array.isArray(mod.config.excluded_agents)) {
445
+ for (const id of mod.config.excluded_agents) {
446
+ if (typeof id === 'string') excludedIds.add(id);
447
+ }
448
+ }
449
+ }
450
+
451
+ const allAgents = [...AGENTS, ...GYRE_AGENTS, ...EXTRA_BME_AGENTS].filter(
452
+ a => !excludedIds.has(a.id)
453
+ );
454
+ const failures = [];
455
+
456
+ for (const agent of allAgents) {
457
+ const wrapperPath = path.join(projectRoot, '.claude', 'skills', `bmad-agent-bme-${agent.id}`, 'SKILL.md');
458
+ if (!fs.existsSync(wrapperPath)) {
459
+ failures.push(`Missing: .claude/skills/bmad-agent-bme-${agent.id}/SKILL.md`);
460
+ }
461
+ }
462
+
463
+ if (failures.length > 0) {
464
+ return {
465
+ name: label,
466
+ passed: false,
467
+ error: failures.join('; '),
468
+ fix: 'Run convoke-update to regenerate agent skill wrappers'
469
+ };
470
+ }
471
+
472
+ const info = excludedIds.size > 0
473
+ ? `${allAgents.length} agent skill wrappers verified (${excludedIds.size} excluded)`
474
+ : `${allAgents.length} agent skill wrappers verified`;
475
+ return { name: label, passed: true, info };
476
+ }
477
+
424
478
  async function checkOutputDir(projectRoot) {
425
479
  const outputDir = path.join(projectRoot, '_bmad-output');
426
480
 
@@ -1996,10 +1996,23 @@ function generateRenameMap(renamedEntries) {
1996
1996
  *
1997
1997
  * @param {string} date - ISO date string (YYYY-MM-DD)
1998
1998
  * @param {{renamedCount: number, injectedCount: number, linksUpdated: number, scopeDirs: string[]}} migrationStats
1999
+ * @param {import('./types').TaxonomyConfig} taxonomy - Loaded taxonomy (source of truth for platform/type lists and counts)
1999
2000
  * @returns {string} Markdown content for the ADR file
2000
2001
  */
2001
- function generateGovernanceADR(date, migrationStats = {}) {
2002
+ function generateGovernanceADR(date, migrationStats = {}, taxonomy) {
2003
+ if (
2004
+ !taxonomy ||
2005
+ !taxonomy.initiatives ||
2006
+ !Array.isArray(taxonomy.initiatives.platform) ||
2007
+ !Array.isArray(taxonomy.artifact_types)
2008
+ ) {
2009
+ throw new TypeError(
2010
+ 'generateGovernanceADR: taxonomy arg is required and must expose initiatives.platform and artifact_types as arrays (pass the object returned by readTaxonomy)'
2011
+ );
2012
+ }
2002
2013
  const { renamedCount = 0, injectedCount = 0, linksUpdated = 0, scopeDirs = [] } = migrationStats;
2014
+ const platformIds = taxonomy.initiatives.platform;
2015
+ const artifactTypes = taxonomy.artifact_types;
2003
2016
  return `# Architecture Decision Record: Artifact Governance Convention
2004
2017
 
2005
2018
  **Status:** ACCEPTED
@@ -2028,9 +2041,9 @@ All artifacts within \`_bmad-output/\` follow the governance naming convention:
2028
2041
 
2029
2042
  ## Taxonomy
2030
2043
 
2031
- **Platform initiatives (8):** vortex, gyre, bmm, forge, helm, enhance, loom, convoke
2044
+ **Platform initiatives (${platformIds.length}):** ${platformIds.join(', ')}
2032
2045
 
2033
- **Artifact types (21):** prd, epic, arch, adr, persona, lean-persona, empathy-map, problem-def, hypothesis, experiment, signal, decision, scope, pre-reg, sprint, brief, vision, report, research, story, spec
2046
+ **Artifact types (${artifactTypes.length}):** ${artifactTypes.join(', ')}
2034
2047
 
2035
2048
  **Aliases (migration-specific):** Historical name variants mapped to canonical initiative IDs during migration (e.g., strategy-perimeter -> helm, team-factory -> loom).
2036
2049
 
@@ -135,7 +135,8 @@ const PLATFORM_INITIATIVES = ['vortex', 'gyre', 'bmm', 'forge', 'helm', 'enhance
135
135
  const DEFAULT_ARTIFACT_TYPES = [
136
136
  'prd', 'epic', 'arch', 'adr', 'persona', 'lean-persona', 'empathy-map',
137
137
  'problem-def', 'hypothesis', 'experiment', 'signal', 'decision', 'scope',
138
- 'pre-reg', 'sprint', 'brief', 'vision', 'report', 'research', 'story', 'spec'
138
+ 'pre-reg', 'sprint', 'brief', 'vision', 'report', 'research', 'story', 'spec',
139
+ 'covenant'
139
140
  ];
140
141
 
141
142
  /**
@@ -385,7 +386,7 @@ async function main() {
385
386
  injectedCount: injResult.injectedCount,
386
387
  linksUpdated: injResult.linkUpdates.updatedLinks,
387
388
  scopeDirs: filteredIncludeDirs
388
- });
389
+ }, taxonomy);
389
390
 
390
391
  const adrDir = path.join(projectRoot, '_bmad-output', 'planning-artifacts');
391
392
  fs.ensureDirSync(adrDir);
@@ -67,9 +67,14 @@ function parseArgs(argv) {
67
67
  opts.dryRun = true;
68
68
  } else if (a === '--all') {
69
69
  opts.all = true;
70
+ } else if (a === '--quiet' || a === '-q') {
71
+ opts.quiet = true;
70
72
  } else if (a === '--output') {
71
73
  const next = argv[i + 1];
72
- if (!next || next.startsWith('--')) {
74
+ // Reject any `-`-prefixed token as a value — covers both long (`--flag`)
75
+ // and short (`-q`, `-h`) neighbors. Previously accepted `-q` as a literal
76
+ // output path; I50's short-alias introduction made that exploitable.
77
+ if (!next || next.startsWith('-')) {
73
78
  opts.unknown = '--output (missing value)';
74
79
  return opts;
75
80
  }
@@ -80,7 +85,8 @@ function parseArgs(argv) {
80
85
  opts.output = val;
81
86
  } else if (a === '--tier') {
82
87
  const next = argv[i + 1];
83
- if (!next || next.startsWith('--')) {
88
+ // See note on `--output` above: reject any `-`-prefixed token as a value.
89
+ if (!next || next.startsWith('-')) {
84
90
  opts.unknown = '--tier (missing value)';
85
91
  return opts;
86
92
  }
@@ -126,6 +132,9 @@ function printHelp() {
126
132
  ' --all Export all exportable tiers (standalone + light-deps).',
127
133
  ' --dry-run Run the engine in-memory; print would-be paths; write',
128
134
  ' nothing. Combinable with all other flags.',
135
+ ' --quiet, -q Suppress per-skill success and skip lines. Failures',
136
+ ' (stderr) and the final summary line are still emitted.',
137
+ ' Useful in CI / scripted pipelines.',
129
138
  ' --help, -h Print this message and exit 0.',
130
139
  '',
131
140
  ' Conflicts:',
@@ -161,25 +170,30 @@ function printHelp() {
161
170
  // REPORTER
162
171
  // =============================================================================
163
172
 
164
- function makeReporter() {
173
+ function makeReporter(opts = {}) {
174
+ const { quiet = false } = opts;
165
175
  const results = { success: 0, failed: 0, skipped: 0, warnings: 0 };
166
176
  return {
167
177
  success(skill, relPath, warnings) {
168
178
  results.success++;
169
179
  results.warnings += warnings;
180
+ if (quiet) return;
170
181
  const suffix = warnings > 0 ? ` (${warnings} warnings)` : '';
171
182
  process.stdout.write(`✅ ${skill} → ${relPath}${suffix}\n`);
172
183
  },
173
184
  failure(skill, error) {
185
+ // Failures always emit — `--quiet` suppresses success noise, not errors.
174
186
  results.failed++;
175
187
  const msg = (error && error.message ? error.message : String(error)).split('\n')[0];
176
188
  process.stderr.write(`❌ ${skill} — ${msg}\n`);
177
189
  },
178
190
  skip(skill, reason) {
179
191
  results.skipped++;
192
+ if (quiet) return;
180
193
  process.stdout.write(`⏭️ ${skill} — ${reason}\n`);
181
194
  },
182
195
  summary(dryRun) {
196
+ // Summary always emits — it's a single line that tells CI the batch outcome.
183
197
  const prefix = dryRun ? '[DRY RUN] ' : '';
184
198
  const total = results.success + results.failed + results.skipped;
185
199
  process.stdout.write(
@@ -189,6 +203,9 @@ function makeReporter() {
189
203
  counts() {
190
204
  return results;
191
205
  },
206
+ isQuiet() {
207
+ return quiet;
208
+ },
192
209
  };
193
210
  }
194
211
 
@@ -410,7 +427,11 @@ function runBatch(tierValue, outputBase, dryRun, projectRoot, reporter) {
410
427
  ].sort();
411
428
 
412
429
  if (matchingSkills.length === 0) {
413
- process.stdout.write('Nothing to export manifest matches found 0 skills\n');
430
+ // Suppress the friendly message in --quiet mode so the caller only sees
431
+ // the single-line summary ("Exported 0 skills (0/0/0) — 0 warnings total").
432
+ if (!reporter.isQuiet()) {
433
+ process.stdout.write('Nothing to export — manifest matches found 0 skills\n');
434
+ }
414
435
  return EXIT_SUCCESS;
415
436
  }
416
437
 
@@ -484,7 +505,7 @@ function main() {
484
505
  outputBase = path.join(projectRoot, 'exported-skills');
485
506
  }
486
507
 
487
- const reporter = makeReporter();
508
+ const reporter = makeReporter({ quiet: !!opts.quiet });
488
509
 
489
510
  let exitCode;
490
511
  if (hasPositional) {
@@ -269,7 +269,7 @@ function loadPersona(skillName, skillContent, workflowContent, projectRoot) {
269
269
 
270
270
  // Convert agent row to persona object
271
271
  return {
272
- name: agent.displayName,
272
+ name: agent.displayName || agent.name,
273
273
  icon: agent.icon || '',
274
274
  title: agent.title || '',
275
275
  role: agent.role || '',
@@ -1008,13 +1008,8 @@ function buildDependencyNotes(skillRefs, sidecars, manifestSkillNames) {
1008
1008
  function exportSkill(skillName, projectRoot, options = {}) {
1009
1009
  const warnings = [];
1010
1010
 
1011
- // 1. Load skill row + tier check (standalone + light-deps allowed; pipeline rejected)
1011
+ // 1. Load skill row (all tiers exportable; pipeline skills get a framework-only notice)
1012
1012
  const skillRow = loadSkillRow(skillName, projectRoot);
1013
- if (skillRow.tier === 'pipeline') {
1014
- throw new Error(
1015
- `${skillName} is tier "pipeline" — pipeline skills are not exported per the portability schema.`
1016
- );
1017
- }
1018
1013
 
1019
1014
  // 2. Load source files
1020
1015
  const { skillContent, workflowContent, stepContents, skillDir } = loadSkillSource(skillRow, projectRoot, warnings);
@@ -1079,6 +1074,13 @@ function exportSkill(skillName, projectRoot, options = {}) {
1079
1074
  const parts = [
1080
1075
  transformedSections.title,
1081
1076
  '',
1077
+ ];
1078
+ // Framework-only notice for pipeline skills (not fully portable)
1079
+ if (skillRow.tier === 'pipeline') {
1080
+ parts.push('> **⚠️ Framework-only skill.** This skill depends on the full Convoke installation and cannot run standalone. It\'s included in the catalog for discoverability. Install Convoke from [convoke-agents](https://github.com/amalik/convoke-agents) to use it.');
1081
+ parts.push('');
1082
+ }
1083
+ parts.push(
1082
1084
  transformedSections.persona,
1083
1085
  '',
1084
1086
  transformedSections.whenToUse,
@@ -1088,7 +1090,7 @@ function exportSkill(skillName, projectRoot, options = {}) {
1088
1090
  transformedSections.howToProceed,
1089
1091
  '',
1090
1092
  transformedSections.whatYouProduce,
1091
- ];
1093
+ );
1092
1094
  if (transformedSections.qualityChecks) {
1093
1095
  parts.push('');
1094
1096
  parts.push(transformedSections.qualityChecks);
@@ -86,22 +86,22 @@ function generate(outputDir, projectRoot) {
86
86
  const manifestPath = path.join(projectRoot, '_bmad', '_config', 'skill-manifest.csv');
87
87
  const { header, rows } = readManifest(manifestPath);
88
88
  const nameIdx = header.indexOf('name');
89
- const tierIdx = header.indexOf('tier');
90
89
 
91
- // Get unique exportable skill names (standalone + light-deps)
90
+ // Get unique exportable skill names (all tiers — pipeline gets a framework-only notice)
91
+ // Exclude meta-platform skills (framework internals, not user-facing)
92
+ const intentIdx = header.indexOf('intent');
92
93
  const seen = new Set();
93
94
  const exportableNames = [];
94
95
  for (const row of rows) {
95
96
  const name = row[nameIdx];
96
97
  if (seen.has(name)) continue;
97
98
  seen.add(name);
98
- if (row[tierIdx] === 'standalone' || row[tierIdx] === 'light-deps') {
99
- exportableNames.push(name);
100
- }
99
+ if (row[intentIdx] === 'meta-platform') continue;
100
+ exportableNames.push(name);
101
101
  }
102
102
  exportableNames.sort();
103
103
 
104
- console.log(`Exporting ${exportableNames.length} skills (standalone + light-deps)...`);
104
+ console.log(`Exporting ${exportableNames.length} skills (all tiers)...`);
105
105
 
106
106
  // Create output directory
107
107
  fs.mkdirSync(outputDir, { recursive: true });
@@ -84,25 +84,36 @@ function validateInstructions(filePath, skillName) {
84
84
  issues.push({ skill: skillName, file: 'instructions.md', issue: 'contains frontmatter block' });
85
85
  }
86
86
 
87
- // Balanced code fences
88
- const fenceCount = (content.match(/^```/gm) || []).length;
89
- if (fenceCount % 2 !== 0) {
90
- issues.push({ skill: skillName, file: 'instructions.md', issue: `unbalanced code fences (${fenceCount} found)` });
87
+ // Framework-only (pipeline) skills skip strict structural checks since they
88
+ // intentionally reference framework-internal paths and may have unbalanced
89
+ // fences after transformations strip framework scaffolding
90
+ const isFrameworkOnly = content.includes('⚠️ Framework-only skill.');
91
+
92
+ // Balanced code fences (skipped for framework-only)
93
+ if (!isFrameworkOnly) {
94
+ const fenceCount = (content.match(/^```/gm) || []).length;
95
+ if (fenceCount % 2 !== 0) {
96
+ issues.push({ skill: skillName, file: 'instructions.md', issue: `unbalanced code fences (${fenceCount} found)` });
97
+ }
91
98
  }
92
99
 
93
- // Broken markdown links (links to local files that aren't URLs or anchors)
94
- const localLinks = [...content.matchAll(/\[([^\]]*)\]\((?!https?:\/\/|#|mailto:)([^)]+)\)/g)];
95
- for (const match of localLinks) {
96
- const linkTarget = match[2];
97
- // Skip links containing template placeholders or non-path content
98
- if (linkTarget.includes('[') || linkTarget.includes('{')) continue;
99
- if (linkTarget.includes(' ')) continue; // URLs/paths don't have spaces; this is prose like "(none found)"
100
- const resolved = path.resolve(path.dirname(filePath), linkTarget);
101
- if (!fs.existsSync(resolved)) {
102
- issues.push({ skill: skillName, file: 'instructions.md', issue: `broken link: [${match[1]}](${linkTarget})` });
100
+ // Broken markdown links (skipped for framework-only)
101
+ if (!isFrameworkOnly) {
102
+ // Broken markdown links (links to local files that aren't URLs or anchors)
103
+ const localLinks = [...content.matchAll(/\[([^\]]*)\]\((?!https?:\/\/|#|mailto:)([^)]+)\)/g)];
104
+ for (const match of localLinks) {
105
+ const linkTarget = match[2];
106
+ // Skip links containing template placeholders or non-path content
107
+ if (linkTarget.includes('[') || linkTarget.includes('{')) continue;
108
+ if (linkTarget.includes(' ')) continue; // URLs/paths don't have spaces; this is prose like "(none found)"
109
+ const resolved = path.resolve(path.dirname(filePath), linkTarget);
110
+ if (!fs.existsSync(resolved)) {
111
+ issues.push({ skill: skillName, file: 'instructions.md', issue: `broken link: [${match[1]}](${linkTarget})` });
112
+ }
103
113
  }
104
114
  }
105
115
 
116
+
106
117
  return issues;
107
118
  }
108
119
 
@@ -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) {