bmad-method 6.6.1-next.8 → 6.7.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/README.md +3 -3
- package/{tools/installer/modules/registry-fallback.yaml → bmad-modules.yaml} +29 -15
- package/package.json +4 -4
- package/src/bmm-skills/1-analysis/bmad-product-brief/SKILL.md +18 -11
- package/src/bmm-skills/1-analysis/bmad-product-brief/customize.toml +13 -8
- package/src/bmm-skills/2-plan-workflows/bmad-prd/SKILL.md +54 -57
- package/src/bmm-skills/2-plan-workflows/bmad-prd/assets/headless-schemas.md +2 -2
- package/src/bmm-skills/2-plan-workflows/bmad-prd/assets/prd-template.md +40 -30
- package/src/bmm-skills/2-plan-workflows/bmad-prd/assets/prd-validation-checklist.md +126 -21
- package/src/bmm-skills/2-plan-workflows/bmad-prd/assets/validation-report-template.html +193 -58
- package/src/bmm-skills/2-plan-workflows/bmad-prd/customize.toml +47 -13
- package/src/bmm-skills/2-plan-workflows/bmad-prd/references/headless.md +27 -12
- package/src/bmm-skills/2-plan-workflows/bmad-prd/references/validate.md +97 -0
- package/src/bmm-skills/module.yaml +2 -2
- package/src/core-skills/module.yaml +1 -1
- package/tools/installer/core/installer.js +1 -22
- package/tools/installer/core/manifest.js +0 -22
- package/tools/installer/modules/channel-plan.js +1 -1
- package/tools/installer/modules/external-manager.js +9 -27
- package/tools/installer/modules/official-modules.js +9 -48
- package/tools/installer/prompts.js +149 -0
- package/tools/installer/ui.js +13 -197
- package/src/bmm-skills/2-plan-workflows/bmad-prd/references/facilitation-guide.md +0 -79
- package/src/bmm-skills/2-plan-workflows/bmad-prd/references/validation-render.md +0 -58
- package/src/bmm-skills/2-plan-workflows/bmad-prd/scripts/render-validation-html.py +0 -290
- package/tools/installer/modules/community-manager.js +0 -704
- package/tools/installer/modules/registry-client.js +0 -187
package/tools/installer/ui.js
CHANGED
|
@@ -818,32 +818,18 @@ class UI {
|
|
|
818
818
|
// Phase 1: Official modules
|
|
819
819
|
const officialSelected = await this._selectOfficialModules(installedModuleIds, installedModuleVersions, channelOptions);
|
|
820
820
|
|
|
821
|
-
//
|
|
822
|
-
//
|
|
823
|
-
|
|
821
|
+
// Identify installed modules that aren't official (previously installed
|
|
822
|
+
// community modules or custom-source modules). Preserve them on update;
|
|
823
|
+
// they can be managed via --custom-source, uninstall, or a dedicated installer.
|
|
824
824
|
const externalManager = new ExternalModuleManager();
|
|
825
825
|
const registryModules = await externalManager.listAvailable();
|
|
826
826
|
const officialRegistryCodes = new Set(['core', 'bmm', ...registryModules.map((m) => m.code)]);
|
|
827
827
|
const installedNonOfficial = [...installedModuleIds].filter((id) => !officialRegistryCodes.has(id));
|
|
828
828
|
|
|
829
|
-
// Phase 2:
|
|
830
|
-
// Returns { codes, didBrowse } so we know if the user entered the flow
|
|
831
|
-
const communityResult = await this._browseCommunityModules(installedModuleIds);
|
|
832
|
-
|
|
833
|
-
// Phase 3: Custom URL modules
|
|
829
|
+
// Phase 2: Custom URL modules
|
|
834
830
|
const customSelected = await this._addCustomUrlModules(installedModuleIds);
|
|
835
831
|
|
|
836
|
-
|
|
837
|
-
const allSelected = new Set([...officialSelected, ...communityResult.codes, ...customSelected]);
|
|
838
|
-
|
|
839
|
-
// Auto-include installed non-official modules that the user didn't get
|
|
840
|
-
// a chance to manage (they declined to browse). If they did browse,
|
|
841
|
-
// trust their selections - they could have deselected intentionally.
|
|
842
|
-
if (!communityResult.didBrowse) {
|
|
843
|
-
for (const code of installedNonOfficial) {
|
|
844
|
-
allSelected.add(code);
|
|
845
|
-
}
|
|
846
|
-
}
|
|
832
|
+
const allSelected = new Set([...officialSelected, ...customSelected, ...installedNonOfficial]);
|
|
847
833
|
|
|
848
834
|
return [...allSelected];
|
|
849
835
|
}
|
|
@@ -954,166 +940,6 @@ class UI {
|
|
|
954
940
|
return result;
|
|
955
941
|
}
|
|
956
942
|
|
|
957
|
-
/**
|
|
958
|
-
* Browse and select community modules using category drill-down.
|
|
959
|
-
* Featured/promoted modules appear at the top.
|
|
960
|
-
* @param {Set} installedModuleIds - Currently installed module IDs
|
|
961
|
-
* @returns {Object} { codes: string[], didBrowse: boolean }
|
|
962
|
-
*/
|
|
963
|
-
async _browseCommunityModules(installedModuleIds = new Set()) {
|
|
964
|
-
const browseCommunity = await prompts.confirm({
|
|
965
|
-
message: 'Would you like to browse community modules?',
|
|
966
|
-
default: false,
|
|
967
|
-
});
|
|
968
|
-
if (!browseCommunity) return { codes: [], didBrowse: false };
|
|
969
|
-
|
|
970
|
-
const { CommunityModuleManager } = require('./modules/community-manager');
|
|
971
|
-
const communityMgr = new CommunityModuleManager();
|
|
972
|
-
|
|
973
|
-
const s = await prompts.spinner();
|
|
974
|
-
s.start('Loading community module catalog...');
|
|
975
|
-
|
|
976
|
-
let categories, featured, allCommunity;
|
|
977
|
-
try {
|
|
978
|
-
[categories, featured, allCommunity] = await Promise.all([
|
|
979
|
-
communityMgr.getCategoryList(),
|
|
980
|
-
communityMgr.listFeatured(),
|
|
981
|
-
communityMgr.listAll(),
|
|
982
|
-
]);
|
|
983
|
-
s.stop(`Community catalog loaded (${allCommunity.length} modules)`);
|
|
984
|
-
} catch (error) {
|
|
985
|
-
s.error('Failed to load community catalog');
|
|
986
|
-
await prompts.log.warn(` ${error.message}`);
|
|
987
|
-
return { codes: [], didBrowse: false };
|
|
988
|
-
}
|
|
989
|
-
|
|
990
|
-
if (allCommunity.length === 0) {
|
|
991
|
-
await prompts.log.info('No community modules are currently available.');
|
|
992
|
-
return { codes: [], didBrowse: false };
|
|
993
|
-
}
|
|
994
|
-
|
|
995
|
-
const selectedCodes = new Set();
|
|
996
|
-
let browsing = true;
|
|
997
|
-
|
|
998
|
-
while (browsing) {
|
|
999
|
-
const categoryChoices = [];
|
|
1000
|
-
|
|
1001
|
-
// Featured section at top
|
|
1002
|
-
if (featured.length > 0) {
|
|
1003
|
-
categoryChoices.push({
|
|
1004
|
-
value: '__featured__',
|
|
1005
|
-
label: `\u2605 Featured (${featured.length} module${featured.length === 1 ? '' : 's'})`,
|
|
1006
|
-
});
|
|
1007
|
-
}
|
|
1008
|
-
|
|
1009
|
-
// Categories with module counts
|
|
1010
|
-
for (const cat of categories) {
|
|
1011
|
-
categoryChoices.push({
|
|
1012
|
-
value: cat.slug,
|
|
1013
|
-
label: `${cat.name} (${cat.moduleCount} module${cat.moduleCount === 1 ? '' : 's'})`,
|
|
1014
|
-
});
|
|
1015
|
-
}
|
|
1016
|
-
|
|
1017
|
-
// Special actions at bottom
|
|
1018
|
-
categoryChoices.push(
|
|
1019
|
-
{ value: '__all__', label: '\u25CE View all community modules' },
|
|
1020
|
-
{ value: '__search__', label: '\u25CE Search by keyword' },
|
|
1021
|
-
{ value: '__done__', label: '\u2713 Done browsing' },
|
|
1022
|
-
);
|
|
1023
|
-
|
|
1024
|
-
const selectedCount = selectedCodes.size;
|
|
1025
|
-
const categoryChoice = await prompts.select({
|
|
1026
|
-
message: `Browse community modules${selectedCount > 0 ? ` (${selectedCount} selected)` : ''}:`,
|
|
1027
|
-
choices: categoryChoices,
|
|
1028
|
-
});
|
|
1029
|
-
|
|
1030
|
-
if (categoryChoice === '__done__') {
|
|
1031
|
-
browsing = false;
|
|
1032
|
-
continue;
|
|
1033
|
-
}
|
|
1034
|
-
|
|
1035
|
-
let modulesToShow;
|
|
1036
|
-
switch (categoryChoice) {
|
|
1037
|
-
case '__featured__': {
|
|
1038
|
-
modulesToShow = featured;
|
|
1039
|
-
|
|
1040
|
-
break;
|
|
1041
|
-
}
|
|
1042
|
-
case '__all__': {
|
|
1043
|
-
modulesToShow = allCommunity;
|
|
1044
|
-
|
|
1045
|
-
break;
|
|
1046
|
-
}
|
|
1047
|
-
case '__search__': {
|
|
1048
|
-
const query = await prompts.text({
|
|
1049
|
-
message: 'Search community modules:',
|
|
1050
|
-
placeholder: 'e.g., design, testing, game',
|
|
1051
|
-
});
|
|
1052
|
-
if (!query || query.trim() === '') continue;
|
|
1053
|
-
modulesToShow = await communityMgr.searchByKeyword(query.trim());
|
|
1054
|
-
if (modulesToShow.length === 0) {
|
|
1055
|
-
await prompts.log.warn('No matching modules found.');
|
|
1056
|
-
continue;
|
|
1057
|
-
}
|
|
1058
|
-
|
|
1059
|
-
break;
|
|
1060
|
-
}
|
|
1061
|
-
default: {
|
|
1062
|
-
modulesToShow = await communityMgr.listByCategory(categoryChoice);
|
|
1063
|
-
}
|
|
1064
|
-
}
|
|
1065
|
-
|
|
1066
|
-
// Build options for autocompleteMultiselect
|
|
1067
|
-
const trustBadge = (tier) => {
|
|
1068
|
-
if (tier === 'bmad-certified') return '\u2713';
|
|
1069
|
-
if (tier === 'community-reviewed') return '\u25CB';
|
|
1070
|
-
return '\u26A0';
|
|
1071
|
-
};
|
|
1072
|
-
|
|
1073
|
-
const options = modulesToShow.map((mod) => {
|
|
1074
|
-
const versionStr = mod.version ? ` (v${mod.version})` : '';
|
|
1075
|
-
const badge = trustBadge(mod.trustTier);
|
|
1076
|
-
return {
|
|
1077
|
-
label: `${mod.displayName}${versionStr} [${badge}]`,
|
|
1078
|
-
value: mod.code,
|
|
1079
|
-
hint: mod.description,
|
|
1080
|
-
};
|
|
1081
|
-
});
|
|
1082
|
-
|
|
1083
|
-
// Pre-check modules that are already selected or installed
|
|
1084
|
-
const initialValues = modulesToShow.filter((m) => selectedCodes.has(m.code) || installedModuleIds.has(m.code)).map((m) => m.code);
|
|
1085
|
-
|
|
1086
|
-
const selected = await prompts.autocompleteMultiselect({
|
|
1087
|
-
message: 'Select community modules:',
|
|
1088
|
-
options,
|
|
1089
|
-
initialValues: initialValues.length > 0 ? initialValues : undefined,
|
|
1090
|
-
required: false,
|
|
1091
|
-
maxItems: Math.min(options.length, 10),
|
|
1092
|
-
});
|
|
1093
|
-
|
|
1094
|
-
// Update accumulated selections: sync with what user selected in this view
|
|
1095
|
-
const shownCodes = new Set(modulesToShow.map((m) => m.code));
|
|
1096
|
-
for (const code of shownCodes) {
|
|
1097
|
-
if (selected && selected.includes(code)) {
|
|
1098
|
-
selectedCodes.add(code);
|
|
1099
|
-
} else {
|
|
1100
|
-
selectedCodes.delete(code);
|
|
1101
|
-
}
|
|
1102
|
-
}
|
|
1103
|
-
}
|
|
1104
|
-
|
|
1105
|
-
if (selectedCodes.size > 0) {
|
|
1106
|
-
const moduleLines = [];
|
|
1107
|
-
for (const code of selectedCodes) {
|
|
1108
|
-
const mod = await communityMgr.getModuleByCode(code);
|
|
1109
|
-
moduleLines.push(` \u2022 ${mod?.displayName || code}`);
|
|
1110
|
-
}
|
|
1111
|
-
await prompts.log.message('Selected community modules:\n' + moduleLines.join('\n'));
|
|
1112
|
-
}
|
|
1113
|
-
|
|
1114
|
-
return { codes: [...selectedCodes], didBrowse: true };
|
|
1115
|
-
}
|
|
1116
|
-
|
|
1117
943
|
/**
|
|
1118
944
|
* Prompt user to install modules from custom sources (Git URLs or local paths).
|
|
1119
945
|
* @param {Set} installedModuleIds - Currently installed module IDs
|
|
@@ -1121,7 +947,7 @@ class UI {
|
|
|
1121
947
|
*/
|
|
1122
948
|
async _addCustomUrlModules(installedModuleIds = new Set()) {
|
|
1123
949
|
const addCustom = await prompts.confirm({
|
|
1124
|
-
message: '
|
|
950
|
+
message: 'Do you want to install custom or community modules (Git URL or local path)?',
|
|
1125
951
|
default: false,
|
|
1126
952
|
});
|
|
1127
953
|
if (!addCustom) return [];
|
|
@@ -1436,7 +1262,7 @@ class UI {
|
|
|
1436
1262
|
*/
|
|
1437
1263
|
async promptForDirectory() {
|
|
1438
1264
|
// Use sync validation because @clack/prompts doesn't support async validate
|
|
1439
|
-
const directory = await prompts.
|
|
1265
|
+
const directory = await prompts.directory({
|
|
1440
1266
|
message: 'Installation directory:',
|
|
1441
1267
|
default: process.cwd(),
|
|
1442
1268
|
placeholder: process.cwd(),
|
|
@@ -1885,19 +1711,14 @@ class UI {
|
|
|
1885
1711
|
const haveFlagIntent = channelOptions.global || channelOptions.nextSet.size > 0 || channelOptions.pins.size > 0;
|
|
1886
1712
|
if (haveFlagIntent) return;
|
|
1887
1713
|
|
|
1888
|
-
// Figure out which selected modules actually get a channel (externals
|
|
1889
|
-
//
|
|
1714
|
+
// Figure out which selected modules actually get a channel (externals only).
|
|
1715
|
+
// Bundled core/bmm and custom modules skip the picker.
|
|
1890
1716
|
const externalManager = new ExternalModuleManager();
|
|
1891
1717
|
const externals = await externalManager.listAvailable();
|
|
1892
1718
|
const externalByCode = new Map(externals.map((m) => [m.code, m]));
|
|
1893
1719
|
|
|
1894
|
-
const { CommunityModuleManager } = require('./modules/community-manager');
|
|
1895
|
-
const communityMgr = new CommunityModuleManager();
|
|
1896
|
-
const community = await communityMgr.listAll();
|
|
1897
|
-
const communityByCode = new Map(community.map((m) => [m.code, m]));
|
|
1898
|
-
|
|
1899
1720
|
const channelSelectable = selectedModules.filter((code) => {
|
|
1900
|
-
const info = externalByCode.get(code)
|
|
1721
|
+
const info = externalByCode.get(code);
|
|
1901
1722
|
return info && !info.builtIn;
|
|
1902
1723
|
});
|
|
1903
1724
|
if (channelSelectable.length === 0) return;
|
|
@@ -1912,7 +1733,7 @@ class UI {
|
|
|
1912
1733
|
const { fetchStableTags, parseGitHubRepo } = require('./modules/channel-resolver');
|
|
1913
1734
|
|
|
1914
1735
|
for (const code of channelSelectable) {
|
|
1915
|
-
const info = externalByCode.get(code)
|
|
1736
|
+
const info = externalByCode.get(code);
|
|
1916
1737
|
const repoUrl = info.url;
|
|
1917
1738
|
|
|
1918
1739
|
// Try to pre-resolve the top stable tag so we can surface it in the picker.
|
|
@@ -1987,11 +1808,6 @@ class UI {
|
|
|
1987
1808
|
const externals = await externalManager.listAvailable();
|
|
1988
1809
|
const externalByCode = new Map(externals.map((m) => [m.code, m]));
|
|
1989
1810
|
|
|
1990
|
-
const { CommunityModuleManager } = require('./modules/community-manager');
|
|
1991
|
-
const communityMgr = new CommunityModuleManager();
|
|
1992
|
-
const community = await communityMgr.listAll();
|
|
1993
|
-
const communityByCode = new Map(community.map((m) => [m.code, m]));
|
|
1994
|
-
|
|
1995
1811
|
const { fetchStableTags, classifyUpgrade, releaseNotesUrl } = require('./modules/channel-resolver');
|
|
1996
1812
|
const { parseGitHubRepo } = require('./modules/channel-resolver');
|
|
1997
1813
|
|
|
@@ -2003,7 +1819,7 @@ class UI {
|
|
|
2003
1819
|
const existingWithChannel = selectedModules.filter((code) => {
|
|
2004
1820
|
const prev = existingByName.get(code);
|
|
2005
1821
|
if (!prev) return false;
|
|
2006
|
-
const info = externalByCode.get(code)
|
|
1822
|
+
const info = externalByCode.get(code);
|
|
2007
1823
|
return info && !info.builtIn;
|
|
2008
1824
|
});
|
|
2009
1825
|
if (existingWithChannel.length > 0) {
|
|
@@ -2018,7 +1834,7 @@ class UI {
|
|
|
2018
1834
|
const prev = existingByName.get(code);
|
|
2019
1835
|
if (!prev) continue;
|
|
2020
1836
|
|
|
2021
|
-
const info = externalByCode.get(code)
|
|
1837
|
+
const info = externalByCode.get(code);
|
|
2022
1838
|
if (!info) continue;
|
|
2023
1839
|
// Bundled modules (core/bmm) ship with the installer binary itself —
|
|
2024
1840
|
// their version is stapled to the CLI version, not a git tag. Skip
|
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
# PRD Facilitation Guide
|
|
2
|
-
|
|
3
|
-
Per-section conversation techniques for facilitative mode. Each entry names the coaching move that makes the section's conversation productive — not a checklist, a posture. Skip sections the PM has already resolved; spend more time where thinking is thin.
|
|
4
|
-
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
## Users and Personas
|
|
8
|
-
|
|
9
|
-
**The move:** Ground personas in real people, not archetypes.
|
|
10
|
-
|
|
11
|
-
Ask the PM to describe a specific person they have observed or talked to — not a type, an actual human. "Who is the clerk at your store? Tell me about them." Invented detail (name, age, backstory from nowhere) is persona theater — the team builds for a fiction. If the PM says "someone like..." push gently: "Is there a real person you're thinking of?"
|
|
12
|
-
|
|
13
|
-
Once grounded: what does that person want to accomplish in the time they interact with this product? What would make them say this is easier than what they do today? What would make them abandon it?
|
|
14
|
-
|
|
15
|
-
For the remote user or secondary persona: same grounding, different question — what question do they need answered in under ten seconds, and what do they do if they can't get it?
|
|
16
|
-
|
|
17
|
-
Mark anything the PM could not ground in observation as `[ILLUSTRATIVE]` — and note it's a hypothesis to validate, not a spec to build for.
|
|
18
|
-
|
|
19
|
-
---
|
|
20
|
-
|
|
21
|
-
## Core User Journeys
|
|
22
|
-
|
|
23
|
-
**The move:** Story structure, not use-case list.
|
|
24
|
-
|
|
25
|
-
For each primary journey, walk through four beats:
|
|
26
|
-
|
|
27
|
-
- **Opening scene** — where do we meet this person, what is their situation right now, what pain or need is present?
|
|
28
|
-
- **Rising action** — what steps do they take, what do they discover or decide along the way?
|
|
29
|
-
- **Climax** — the moment the product delivers real value; the thing they could not do before
|
|
30
|
-
- **Resolution** — what is their new reality; how is their situation different?
|
|
31
|
-
|
|
32
|
-
After each journey: what could go wrong at the climax? What is the recovery path? This is where edge cases that matter surface — not invented error states, but real failure modes for this person.
|
|
33
|
-
|
|
34
|
-
Explicitly name what capability each journey reveals. "This journey requires the operator to log an entry with no internet — which means we need a decision on whether that's in or out of MVP." Journeys produce capability requirements; make the link visible.
|
|
35
|
-
|
|
36
|
-
---
|
|
37
|
-
|
|
38
|
-
## Key Feature Decisions
|
|
39
|
-
|
|
40
|
-
**The move:** Surface the assumptions that would otherwise be silent.
|
|
41
|
-
|
|
42
|
-
Before the draft exists, there are decisions the agent would silently make and the PM would never know were made. These are the ones worth a thirty-second conversation:
|
|
43
|
-
|
|
44
|
-
- Decisions that drive the core UX model (e.g., one record per day vs. many; who can edit vs. view; what happens when the expected input doesn't exist)
|
|
45
|
-
- Decisions where the "obvious" choice has real consequences the PM may not have considered
|
|
46
|
-
- Decisions that, if wrong, require structural changes to fix later
|
|
47
|
-
|
|
48
|
-
For each: state what you inferred, name the alternative, ask which is right. Do not present options as a quiz — present your inference and invite correction. "I'm assuming one sales tally per day replaces rather than adds. Is that right, or should the operator be able to log multiple?" Resolve and move on. Only tag as `[ASSUMPTION]` when the answer requires external input or research the PM cannot provide now.
|
|
49
|
-
|
|
50
|
-
---
|
|
51
|
-
|
|
52
|
-
## Scope Boundary
|
|
53
|
-
|
|
54
|
-
**The move:** Establish MVP philosophy before listing features.
|
|
55
|
-
|
|
56
|
-
Before asking what is in or out, ask what kind of MVP this is:
|
|
57
|
-
|
|
58
|
-
- **Problem-solving MVP** — the minimum that proves the core problem is solved; rough edges acceptable
|
|
59
|
-
- **Experience MVP** — the minimum that proves the interaction model works; quality matters
|
|
60
|
-
- **Platform MVP** — the minimum infrastructure other things can build on; completeness of the base matters
|
|
61
|
-
- **Revenue MVP** — the minimum someone will pay for; business viability is the test
|
|
62
|
-
|
|
63
|
-
The answer changes what "minimum" means. A problem-solving MVP for a personal-use tool has different scope logic than an experience MVP aimed at non-tech-savvy users who will bounce at the first confusion.
|
|
64
|
-
|
|
65
|
-
Once the philosophy is named, non-goals do as much work as in-scope items. Probe for the things the PM is tempted to add. "What keeps almost making it onto the list?" For each: is it truly out of MVP, or does it need to be in because the MVP fails without it?
|
|
66
|
-
|
|
67
|
-
---
|
|
68
|
-
|
|
69
|
-
## Success Metrics
|
|
70
|
-
|
|
71
|
-
**The move:** Push every adjective to a measurement.
|
|
72
|
-
|
|
73
|
-
"Users will love it" — what does that mean in behavior? "It'll be fast" — fast at what, for whom, measured how? "Good adoption" — what percentage, by when, doing what? Every quality claim needs a measurement or it is not a success criterion, it is a wish.
|
|
74
|
-
|
|
75
|
-
For each metric surfaced: connect it back to the product's differentiator. If the differentiator is simplicity for non-tech users, the primary metric should measure whether non-tech users successfully complete the core action without help — not session count or feature usage breadth.
|
|
76
|
-
|
|
77
|
-
Name counter-metrics explicitly — what this product should *not* optimize for. These prevent the wrong thing being built: more entries per day is not better if the goal is accurate daily records; longer dashboard sessions may indicate a broken IA, not high engagement. Counter-metrics are as load-bearing as primary metrics for downstream readers.
|
|
78
|
-
|
|
79
|
-
For low-stakes or personal-use products: one sentence is enough. "Success: I use this daily and it replaces the notebook within a month." Do not impose metric rigor where the stakes do not warrant it.
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
# Validation Rendering
|
|
2
|
-
|
|
3
|
-
How the validator subagent's findings become a validation report. Loaded only when the user has explicitly asked for analysis — either Validate intent or a mid-session report request. The Finalize discipline pass during Create/Update does NOT render a report; its findings stay in-conversation.
|
|
4
|
-
|
|
5
|
-
## Validator subagent output contract
|
|
6
|
-
|
|
7
|
-
The subagent walks `{workflow.validation_checklist}` against `prd.md` (and `addendum.md` if present) and writes `{doc_workspace}/validation-findings.json`:
|
|
8
|
-
|
|
9
|
-
```json
|
|
10
|
-
{
|
|
11
|
-
"prd_name": "Plantsona",
|
|
12
|
-
"prd_path": "{doc_workspace}/prd.md",
|
|
13
|
-
"checklist_path": "{workflow.validation_checklist}",
|
|
14
|
-
"timestamp": "2026-05-11T09:14:00",
|
|
15
|
-
"overall_synthesis": "2-3 sentences of judgment about the PRD's overall state — what holds up, what's at risk. Written by the subagent, not the parent.",
|
|
16
|
-
"findings": [
|
|
17
|
-
{
|
|
18
|
-
"id": "Q-2",
|
|
19
|
-
"category": "Quality",
|
|
20
|
-
"title": "Measurability",
|
|
21
|
-
"status": "warn",
|
|
22
|
-
"severity": "medium",
|
|
23
|
-
"location": "§16 Success Metrics, lines 408-422",
|
|
24
|
-
"note": "Success Metrics list is measurable but counter-metrics are named only for premium conversion. Other metrics lack paired counter-metrics.",
|
|
25
|
-
"suggested_fix": "Add counter-metrics for engagement (e.g., DAU/MAU) and seasonal cadence."
|
|
26
|
-
}
|
|
27
|
-
]
|
|
28
|
-
}
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
Per-finding fields:
|
|
32
|
-
|
|
33
|
-
- `id` (required) — checklist item ID (e.g., `Q-1`, `D-2`, `STK-1`, or org-custom prefixes).
|
|
34
|
-
- `category` (optional) — explicit category name; if omitted, the renderer maps from the ID prefix.
|
|
35
|
-
- `title` (optional but recommended) — the checklist item's short name.
|
|
36
|
-
- `status` — `pass` | `warn` | `fail` | `n/a`.
|
|
37
|
-
- `severity` — `low` | `medium` | `high` | `critical`.
|
|
38
|
-
- `location` (optional) — section/line/range in the PRD where the finding lives. Cite specifics, never abstract criticism.
|
|
39
|
-
- `note` (optional) — the finding itself, in one or two sentences.
|
|
40
|
-
- `suggested_fix` (optional) — concrete next action.
|
|
41
|
-
|
|
42
|
-
## Rendering invocation
|
|
43
|
-
|
|
44
|
-
After the subagent writes findings:
|
|
45
|
-
|
|
46
|
-
```bash
|
|
47
|
-
python3 {skill-root}/scripts/render-validation-html.py \
|
|
48
|
-
--findings {doc_workspace}/validation-findings.json \
|
|
49
|
-
--template {workflow.validation_report_template} \
|
|
50
|
-
--output {doc_workspace}/validation-report.html \
|
|
51
|
-
--open
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
Include `--open` for interactive runs (auto-opens in default browser). Omit `--open` in headless runs.
|
|
55
|
-
|
|
56
|
-
The script writes two artifacts side-by-side: the HTML report at `--output`, and a markdown companion at the same path with `.md` extension (e.g. `validation-report.md`). Both are always produced when the script runs — trigger gating happens upstream (the script is only invoked when the user has asked for analysis). It computes pass/warn/fail/na counts, derives a grade (Excellent / Good / Fair / Poor) from critical-fail and total-fail counts, renders an inline SVG score bar in the HTML, groups findings by category, and returns a one-line JSON summary on stdout: `{"output": "...", "markdown": "...", "grade": "...", "stats": {...}}`.
|
|
57
|
-
|
|
58
|
-
Re-running validation overwrites the existing report files in place. Markdown form is what Update mode reads when rolling findings into a revision.
|