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.
- package/CHANGELOG.md +68 -0
- package/README.md +3 -1
- package/_bmad/bme/README.md +36 -0
- package/_bmad/bme/_enhance/workflows/initiatives-backlog/SKILL.md +1 -1
- package/_bmad/bme/_enhance/workflows/initiatives-backlog/steps-c/step-c-01-init.md +55 -32
- package/_bmad/bme/_enhance/workflows/initiatives-backlog/steps-c/step-c-02-gather.md +62 -59
- package/_bmad/bme/_enhance/workflows/initiatives-backlog/steps-c/step-c-03-qualify.md +176 -0
- package/_bmad/bme/_enhance/workflows/initiatives-backlog/steps-c/step-c-04-generate.md +259 -0
- package/_bmad/bme/_enhance/workflows/initiatives-backlog/steps-r/step-r-01-load.md +65 -35
- package/_bmad/bme/_enhance/workflows/initiatives-backlog/steps-r/step-r-02-rescore.md +60 -30
- package/_bmad/bme/_enhance/workflows/initiatives-backlog/steps-r/step-r-03-update.md +67 -71
- package/_bmad/bme/_enhance/workflows/initiatives-backlog/steps-t/step-t-01-ingest.md +12 -12
- package/_bmad/bme/_enhance/workflows/initiatives-backlog/steps-t/step-t-02-extract.md +49 -44
- package/_bmad/bme/_enhance/workflows/initiatives-backlog/steps-t/step-t-03-qualify.md +192 -0
- package/_bmad/bme/_enhance/workflows/initiatives-backlog/steps-t/step-t-04-update.md +72 -67
- package/_bmad/bme/_enhance/workflows/initiatives-backlog/templates/backlog-format-spec.md +223 -112
- package/_bmad/bme/_enhance/workflows/initiatives-backlog/templates/lifecycle-process-spec.md +188 -0
- package/_bmad/bme/_enhance/workflows/initiatives-backlog/workflow.md +44 -31
- package/_bmad/bme/_gyre/config.yaml +3 -0
- package/_bmad/bme/_vortex/config.yaml +4 -1
- package/package.json +1 -1
- package/scripts/convoke-doctor.js +56 -2
- package/scripts/lib/artifact-utils.js +16 -3
- package/scripts/migrate-artifacts.js +3 -2
- package/scripts/portability/convoke-export.js +26 -5
- package/scripts/portability/export-engine.js +10 -8
- package/scripts/portability/seed-catalog-repo.js +6 -6
- package/scripts/portability/validate-exports.js +25 -14
- package/scripts/update/convoke-update.js +27 -2
- package/scripts/update/lib/changelog-reader.js +90 -0
- package/scripts/update/lib/config-merger.js +52 -1
- package/scripts/update/lib/refresh-installation.js +48 -9
- package/scripts/update/lib/taxonomy-merger.js +2 -1
- package/scripts/update/lib/validator.js +9 -1
- package/_bmad/bme/_enhance/workflows/initiatives-backlog/steps-c/step-c-03-score.md +0 -146
- package/_bmad/bme/_enhance/workflows/initiatives-backlog/steps-c/step-c-04-prioritize.md +0 -181
- 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:
|
|
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:
|
|
6
|
+
version: 2.0.0
|
|
7
7
|
---
|
|
8
8
|
|
|
9
|
-
#
|
|
9
|
+
# Initiative Lifecycle Backlog Workflow
|
|
10
10
|
|
|
11
|
-
Manage a
|
|
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
|
-
-
|
|
19
|
+
- Qualifying gate sub-flow in Triage + Create modes
|
|
20
20
|
|
|
21
21
|
## Modes Overview
|
|
22
22
|
|
|
23
|
-
### [T] Triage — Ingest
|
|
24
|
-
Accepts text input (review transcripts,
|
|
25
|
-
- **Steps:** Ingest > Extract &
|
|
26
|
-
- **When to use:** After
|
|
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
|
|
30
|
-
- **Steps:** Load
|
|
31
|
-
- **When to use:** Periodically (monthly
|
|
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 —
|
|
34
|
-
|
|
35
|
-
- **Steps:** Initialize > Gather
|
|
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}/
|
|
41
|
-
**Templates:**
|
|
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
|
-
**
|
|
58
|
+
**Initiative Lifecycle Backlog — Select a Mode:**
|
|
56
59
|
|
|
57
|
-
- **[T] Triage** — Ingest
|
|
58
|
-
- **[R] Review** —
|
|
59
|
-
- **[C] Create** — Bootstrap a new
|
|
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
|
|
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
|
|
78
|
-
- Modes run independently — do NOT switch modes mid-execution
|
|
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
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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.
|
|
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
|
@@ -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.
|
|
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
|
-
|
|
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 (
|
|
2044
|
+
**Platform initiatives (${platformIds.length}):** ${platformIds.join(', ')}
|
|
2032
2045
|
|
|
2033
|
-
**Artifact types (
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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 (
|
|
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[
|
|
99
|
-
|
|
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 (
|
|
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
|
-
//
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
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 (
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
const
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
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
|
|
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) {
|