bmad-method 6.7.1 → 6.8.1-next.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/.claude-plugin/marketplace.json +1 -1
- package/README.md +10 -0
- package/package.json +3 -2
- package/removals.txt +8 -0
- package/src/bmm-skills/1-analysis/bmad-agent-analyst/SKILL.md +2 -0
- package/src/bmm-skills/1-analysis/bmad-agent-tech-writer/SKILL.md +2 -0
- package/src/bmm-skills/1-analysis/bmad-document-project/SKILL.md +1 -1
- package/src/bmm-skills/1-analysis/bmad-prfaq/SKILL.md +1 -1
- package/src/bmm-skills/1-analysis/bmad-product-brief/SKILL.md +5 -2
- package/src/bmm-skills/1-analysis/research/bmad-domain-research/SKILL.md +1 -1
- package/src/bmm-skills/1-analysis/research/bmad-market-research/SKILL.md +1 -1
- package/src/bmm-skills/1-analysis/research/bmad-technical-research/SKILL.md +1 -1
- package/src/bmm-skills/2-plan-workflows/bmad-agent-pm/SKILL.md +2 -0
- package/src/bmm-skills/2-plan-workflows/bmad-agent-ux-designer/SKILL.md +2 -0
- package/src/bmm-skills/2-plan-workflows/bmad-agent-ux-designer/customize.toml +1 -1
- package/src/bmm-skills/2-plan-workflows/bmad-prd/SKILL.md +9 -4
- package/src/bmm-skills/2-plan-workflows/bmad-prd/assets/prd-template.md +4 -7
- package/src/bmm-skills/2-plan-workflows/bmad-prd/assets/prd-validation-checklist.md +4 -4
- package/src/bmm-skills/2-plan-workflows/bmad-prd/references/headless.md +2 -2
- package/src/bmm-skills/2-plan-workflows/bmad-ux/SKILL.md +90 -0
- package/src/bmm-skills/2-plan-workflows/bmad-ux/assets/color-themes.md +9 -0
- package/src/bmm-skills/2-plan-workflows/bmad-ux/assets/design-directions.md +9 -0
- package/src/bmm-skills/2-plan-workflows/bmad-ux/assets/design-example-editorial.md +158 -0
- package/src/bmm-skills/2-plan-workflows/bmad-ux/assets/design-example-mobile.md +93 -0
- package/src/bmm-skills/2-plan-workflows/bmad-ux/assets/design-example-shadcn.md +109 -0
- package/src/bmm-skills/2-plan-workflows/bmad-ux/assets/excalidraw-wireframe.md +19 -0
- package/src/bmm-skills/2-plan-workflows/bmad-ux/assets/experience-example-mobile.md +112 -0
- package/src/bmm-skills/2-plan-workflows/bmad-ux/assets/experience-example-shadcn.md +133 -0
- package/src/bmm-skills/2-plan-workflows/bmad-ux/assets/headless-schemas.md +84 -0
- package/src/bmm-skills/2-plan-workflows/bmad-ux/assets/key-screens.md +29 -0
- package/src/bmm-skills/2-plan-workflows/bmad-ux/assets/validation-report-template.html +319 -0
- package/src/bmm-skills/2-plan-workflows/bmad-ux/customize.toml +100 -0
- package/src/bmm-skills/2-plan-workflows/bmad-ux/references/creative-tools.md +19 -0
- package/src/bmm-skills/2-plan-workflows/bmad-ux/references/design-md-spec.md +50 -0
- package/src/bmm-skills/2-plan-workflows/bmad-ux/references/headless.md +37 -0
- package/src/bmm-skills/2-plan-workflows/bmad-ux/references/validate.md +115 -0
- package/src/bmm-skills/3-solutioning/bmad-agent-architect/SKILL.md +2 -0
- package/src/bmm-skills/3-solutioning/bmad-check-implementation-readiness/SKILL.md +1 -1
- package/src/bmm-skills/3-solutioning/bmad-create-architecture/SKILL.md +1 -1
- package/src/bmm-skills/3-solutioning/bmad-create-epics-and-stories/SKILL.md +1 -1
- package/src/bmm-skills/3-solutioning/bmad-generate-project-context/SKILL.md +1 -1
- package/src/bmm-skills/4-implementation/bmad-agent-dev/SKILL.md +2 -0
- package/src/bmm-skills/4-implementation/bmad-checkpoint-preview/SKILL.md +1 -1
- package/src/bmm-skills/4-implementation/bmad-code-review/SKILL.md +1 -1
- package/src/bmm-skills/4-implementation/bmad-correct-course/SKILL.md +1 -1
- package/src/bmm-skills/4-implementation/bmad-create-story/SKILL.md +1 -1
- package/src/bmm-skills/4-implementation/bmad-dev-story/SKILL.md +23 -8
- package/src/bmm-skills/4-implementation/bmad-investigate/SKILL.md +2 -0
- package/src/bmm-skills/4-implementation/bmad-qa-generate-e2e-tests/SKILL.md +1 -1
- package/src/bmm-skills/4-implementation/bmad-quick-dev/SKILL.md +1 -1
- package/src/bmm-skills/4-implementation/bmad-retrospective/SKILL.md +1 -1
- package/src/bmm-skills/4-implementation/bmad-sprint-planning/SKILL.md +2 -1
- package/src/bmm-skills/4-implementation/bmad-sprint-status/SKILL.md +2 -1
- package/src/bmm-skills/module-help.csv +1 -1
- package/src/core-skills/bmad-advanced-elicitation/methods.csv +69 -50
- package/src/core-skills/bmad-brainstorming/steps/step-03-technique-execution.md +6 -4
- package/src/core-skills/bmad-brainstorming/workflow.md +1 -1
- package/src/core-skills/bmad-party-mode/SKILL.md +44 -97
- package/src/core-skills/bmad-spec/SKILL.md +129 -0
- package/src/core-skills/bmad-spec/assets/headless-schemas.md +33 -0
- package/src/core-skills/bmad-spec/assets/spec-template.md +49 -0
- package/src/core-skills/bmad-spec/customize.toml +53 -0
- package/src/core-skills/module-help.csv +1 -1
- package/src/scripts/resolve_customization.py +9 -1
- package/src/scripts/tests/test_resolve_customization.py +50 -0
- package/tools/bundle-web-bundles.js +117 -0
- package/tools/installer/modules/custom-module-manager.js +113 -4
- package/tools/installer/modules/official-modules.js +83 -3
- package/tools/skill-validator.md +1 -19
- package/tools/validate-sidebar-order.js +388 -0
- package/tools/validate-skills.js +1 -40
- package/web-bundles/README.md +46 -0
- package/web-bundles/brainstorming-coach/INSTRUCTIONS.md +86 -0
- package/web-bundles/brainstorming-coach/SKILL.md +83 -0
- package/web-bundles/brainstorming-coach/brain-methods.csv +62 -0
- package/web-bundles/bundles.json +139 -0
- package/web-bundles/market-and-industry-research/INSTRUCTIONS.md +88 -0
- package/web-bundles/market-and-industry-research/SKILL.md +59 -0
- package/web-bundles/prd-coach/INSTRUCTIONS.md +86 -0
- package/web-bundles/prd-coach/SKILL.md +101 -0
- package/web-bundles/prd-coach/prd-template.md +165 -0
- package/web-bundles/prd-coach/prd-validation-checklist.md +135 -0
- package/web-bundles/prfaq-coach/INSTRUCTIONS.md +86 -0
- package/web-bundles/prfaq-coach/SKILL.md +139 -0
- package/web-bundles/product-brief-coach/INSTRUCTIONS.md +86 -0
- package/web-bundles/product-brief-coach/SKILL.md +113 -0
- package/web-bundles/ux-coach/INSTRUCTIONS.md +92 -0
- package/web-bundles/ux-coach/SKILL.md +187 -0
- package/web-bundles/ux-coach/ux-validation.md +100 -0
- package/src/bmm-skills/2-plan-workflows/bmad-create-ux-design/SKILL.md +0 -75
- package/src/bmm-skills/2-plan-workflows/bmad-create-ux-design/customize.toml +0 -41
- package/src/bmm-skills/2-plan-workflows/bmad-create-ux-design/steps/step-01-init.md +0 -135
- package/src/bmm-skills/2-plan-workflows/bmad-create-ux-design/steps/step-01b-continue.md +0 -127
- package/src/bmm-skills/2-plan-workflows/bmad-create-ux-design/steps/step-02-discovery.md +0 -190
- package/src/bmm-skills/2-plan-workflows/bmad-create-ux-design/steps/step-03-core-experience.md +0 -217
- package/src/bmm-skills/2-plan-workflows/bmad-create-ux-design/steps/step-04-emotional-response.md +0 -220
- package/src/bmm-skills/2-plan-workflows/bmad-create-ux-design/steps/step-05-inspiration.md +0 -235
- package/src/bmm-skills/2-plan-workflows/bmad-create-ux-design/steps/step-06-design-system.md +0 -253
- package/src/bmm-skills/2-plan-workflows/bmad-create-ux-design/steps/step-07-defining-experience.md +0 -255
- package/src/bmm-skills/2-plan-workflows/bmad-create-ux-design/steps/step-08-visual-foundation.md +0 -225
- package/src/bmm-skills/2-plan-workflows/bmad-create-ux-design/steps/step-09-design-directions.md +0 -225
- package/src/bmm-skills/2-plan-workflows/bmad-create-ux-design/steps/step-10-user-journeys.md +0 -242
- package/src/bmm-skills/2-plan-workflows/bmad-create-ux-design/steps/step-11-component-strategy.md +0 -249
- package/src/bmm-skills/2-plan-workflows/bmad-create-ux-design/steps/step-12-ux-patterns.md +0 -238
- package/src/bmm-skills/2-plan-workflows/bmad-create-ux-design/steps/step-13-responsive-accessibility.md +0 -265
- package/src/bmm-skills/2-plan-workflows/bmad-create-ux-design/steps/step-14-complete.md +0 -177
- package/src/bmm-skills/2-plan-workflows/bmad-create-ux-design/ux-design-template.md +0 -13
- package/src/core-skills/bmad-distillator/SKILL.md +0 -177
- package/src/core-skills/bmad-distillator/agents/distillate-compressor.md +0 -116
- package/src/core-skills/bmad-distillator/agents/round-trip-reconstructor.md +0 -68
- package/src/core-skills/bmad-distillator/resources/compression-rules.md +0 -51
- package/src/core-skills/bmad-distillator/resources/distillate-format-reference.md +0 -227
- package/src/core-skills/bmad-distillator/resources/splitting-strategy.md +0 -78
- package/src/core-skills/bmad-distillator/scripts/analyze_sources.py +0 -300
- package/src/core-skills/bmad-distillator/scripts/tests/test_analyze_sources.py +0 -204
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Web Bundle Release Packager
|
|
3
|
+
*
|
|
4
|
+
* Zips each bundle under web-bundles/ into dist/web-bundles/{slug}.zip
|
|
5
|
+
* for attachment to a GitHub Release.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* node tools/bundle-web-bundles.js
|
|
9
|
+
*
|
|
10
|
+
* After running, the script prints the exact `gh release create` command
|
|
11
|
+
* (with the correct tag from bundles.json) for you to copy.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const fs = require('node:fs');
|
|
15
|
+
const path = require('node:path');
|
|
16
|
+
const { execSync, execFileSync } = require('node:child_process');
|
|
17
|
+
|
|
18
|
+
const REPO_ROOT = path.resolve(__dirname, '..');
|
|
19
|
+
const BUNDLES_DIR = path.join(REPO_ROOT, 'web-bundles');
|
|
20
|
+
const DIST_DIR = path.join(REPO_ROOT, 'dist', 'web-bundles');
|
|
21
|
+
const MANIFEST = path.join(BUNDLES_DIR, 'bundles.json');
|
|
22
|
+
const SLUG_RE = /^[a-z0-9][a-z0-9-]*$/;
|
|
23
|
+
|
|
24
|
+
function fail(msg) {
|
|
25
|
+
console.error(`[ERROR] ${msg}`);
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function requireZipCli() {
|
|
30
|
+
try {
|
|
31
|
+
execSync('zip -v', { stdio: 'ignore' });
|
|
32
|
+
} catch {
|
|
33
|
+
fail("'zip' CLI not found on PATH. Install zip (macOS: preinstalled; Debian/Ubuntu: apt install zip; Alpine: apk add zip) and re-run.");
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function loadManifest() {
|
|
38
|
+
if (!fs.existsSync(MANIFEST)) {
|
|
39
|
+
fail(`bundles.json not found at ${MANIFEST}`);
|
|
40
|
+
}
|
|
41
|
+
let manifest;
|
|
42
|
+
try {
|
|
43
|
+
manifest = JSON.parse(fs.readFileSync(MANIFEST, 'utf-8'));
|
|
44
|
+
} catch (error) {
|
|
45
|
+
fail(`bundles.json is not valid JSON: ${error.message}`);
|
|
46
|
+
}
|
|
47
|
+
if (!Array.isArray(manifest.bundles) || manifest.bundles.length === 0) {
|
|
48
|
+
fail('bundles.json is missing a non-empty "bundles" array.');
|
|
49
|
+
}
|
|
50
|
+
if (typeof manifest.releaseTag !== 'string' || !manifest.releaseTag) {
|
|
51
|
+
fail('bundles.json is missing "releaseTag".');
|
|
52
|
+
}
|
|
53
|
+
return manifest;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function main() {
|
|
57
|
+
requireZipCli();
|
|
58
|
+
const manifest = loadManifest();
|
|
59
|
+
const releaseTag = manifest.releaseTag;
|
|
60
|
+
|
|
61
|
+
fs.mkdirSync(DIST_DIR, { recursive: true });
|
|
62
|
+
|
|
63
|
+
console.log(`Packaging ${manifest.bundles.length} bundles for release ${releaseTag}\n`);
|
|
64
|
+
|
|
65
|
+
const zipped = [];
|
|
66
|
+
const missing = [];
|
|
67
|
+
const invalid = [];
|
|
68
|
+
for (const bundle of manifest.bundles) {
|
|
69
|
+
if (!bundle.slug || !SLUG_RE.test(bundle.slug)) {
|
|
70
|
+
invalid.push(bundle.slug || '(no slug)');
|
|
71
|
+
console.error(` [INVALID] slug must match ${SLUG_RE} — got: ${bundle.slug}`);
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
const src = path.join(BUNDLES_DIR, bundle.slug);
|
|
75
|
+
if (!fs.existsSync(src)) {
|
|
76
|
+
missing.push(bundle.slug);
|
|
77
|
+
console.error(` [MISSING] ${bundle.slug} — directory not found`);
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const out = path.join(DIST_DIR, `${bundle.slug}.zip`);
|
|
82
|
+
if (fs.existsSync(out)) fs.unlinkSync(out);
|
|
83
|
+
|
|
84
|
+
try {
|
|
85
|
+
execFileSync('zip', ['-r', '-X', '-q', out, bundle.slug, '-x', '*.DS_Store'], {
|
|
86
|
+
cwd: BUNDLES_DIR,
|
|
87
|
+
stdio: 'inherit',
|
|
88
|
+
});
|
|
89
|
+
} catch (error) {
|
|
90
|
+
fail(`zip failed for ${bundle.slug}: ${error.message}`);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const size = (fs.statSync(out).size / 1024).toFixed(1);
|
|
94
|
+
console.log(` [OK] ${bundle.slug}.zip (${size} KB)`);
|
|
95
|
+
zipped.push(bundle.slug);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (invalid.length > 0) {
|
|
99
|
+
fail(`Refusing to publish: ${invalid.length} bundle(s) have invalid slugs: ${invalid.join(', ')}`);
|
|
100
|
+
}
|
|
101
|
+
if (missing.length > 0) {
|
|
102
|
+
fail(`Refusing to publish an incomplete release: missing directories for ${missing.join(', ')}`);
|
|
103
|
+
}
|
|
104
|
+
if (zipped.length === 0) {
|
|
105
|
+
fail('No bundles were packaged. Check bundles.json against web-bundles/ subdirectories.');
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
console.log(`\nWrote ${zipped.length} bundles to ${path.relative(REPO_ROOT, DIST_DIR)}/`);
|
|
109
|
+
console.log('\nNext step — create or update the GitHub Release:\n');
|
|
110
|
+
console.log(` gh release create ${releaseTag} dist/web-bundles/*.zip \\`);
|
|
111
|
+
console.log(` --title "${releaseTag}" \\`);
|
|
112
|
+
console.log(` --notes "BMad web bundles for Gemini Gems and ChatGPT Custom GPTs. See https://bmadcode.com/web-bundles/"\n`);
|
|
113
|
+
console.log('Or, to refresh an existing release:\n');
|
|
114
|
+
console.log(` gh release upload ${releaseTag} dist/web-bundles/*.zip --clobber\n`);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
main();
|
|
@@ -19,6 +19,10 @@ function quoteCustomRef(ref) {
|
|
|
19
19
|
class CustomModuleManager {
|
|
20
20
|
/** @type {Map<string, Object>} Shared across all instances: module code -> ResolvedModule */
|
|
21
21
|
static _resolutionCache = new Map();
|
|
22
|
+
/** @type {Set<string>} Repo roots refreshed in the current process (dedupe quick-update fetches). */
|
|
23
|
+
static _refreshedRepoPaths = new Set();
|
|
24
|
+
/** @type {Map<string, Promise<void>>} In-flight refresh operations keyed by repo path. */
|
|
25
|
+
static _refreshInFlight = new Map();
|
|
22
26
|
|
|
23
27
|
// ─── Source Parsing ───────────────────────────────────────────────────────
|
|
24
28
|
|
|
@@ -111,7 +115,7 @@ class CustomModuleManager {
|
|
|
111
115
|
}
|
|
112
116
|
|
|
113
117
|
// SSH URL: git@host:owner/repo.git
|
|
114
|
-
const sshMatch = trimmed.match(/^git@([^:]+):(
|
|
118
|
+
const sshMatch = trimmed.match(/^git@([^:]+):(.+?)\/([^/.]+?)(?:\.git)?$/);
|
|
115
119
|
if (sshMatch) {
|
|
116
120
|
const [, host, owner, repo] = sshMatch;
|
|
117
121
|
return {
|
|
@@ -424,15 +428,39 @@ class CustomModuleManager {
|
|
|
424
428
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
425
429
|
});
|
|
426
430
|
} else {
|
|
427
|
-
|
|
431
|
+
// Resolve the default branch (origin/HEAD) and fetch it explicitly.
|
|
432
|
+
// With shallow clones, `origin/HEAD` is stale and `git reset --hard
|
|
433
|
+
// origin/HEAD` never picks up new commits on the default branch.
|
|
434
|
+
let defaultBranch = 'main';
|
|
435
|
+
try {
|
|
436
|
+
defaultBranch = execSync('git symbolic-ref refs/remotes/origin/HEAD --short', {
|
|
437
|
+
cwd: repoCacheDir,
|
|
438
|
+
stdio: 'pipe',
|
|
439
|
+
})
|
|
440
|
+
.toString()
|
|
441
|
+
.trim()
|
|
442
|
+
.replace('origin/', '');
|
|
443
|
+
} catch {
|
|
444
|
+
// Fallback if origin/HEAD is not set
|
|
445
|
+
}
|
|
446
|
+
execSync(`git fetch --depth 1 origin ${quoteCustomRef(defaultBranch)}`, {
|
|
447
|
+
cwd: repoCacheDir,
|
|
448
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
449
|
+
env: { ...process.env, GIT_TERMINAL_PROMPT: '0' },
|
|
450
|
+
});
|
|
451
|
+
execSync(`git reset --hard origin/${quoteCustomRef(defaultBranch)}`, {
|
|
428
452
|
cwd: repoCacheDir,
|
|
429
453
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
430
454
|
});
|
|
431
455
|
}
|
|
432
456
|
fetchSpinner.stop(`Updated ${displayName}`);
|
|
433
457
|
} catch {
|
|
434
|
-
|
|
435
|
-
|
|
458
|
+
// Fetch failed against an existing cache — most often the remote is
|
|
459
|
+
// unreachable (network down, repo deleted/moved, auth revoked).
|
|
460
|
+
// Preserve the previous clone so re-deploy still works from cached
|
|
461
|
+
// content; surface a warning so the user knows the cache is stale.
|
|
462
|
+
fetchSpinner.error(`Could not refresh ${displayName} — keeping cached copy`);
|
|
463
|
+
await prompts.log.warn(`Custom module ${displayName} was not refreshed (remote unreachable). Using cached copy.`);
|
|
436
464
|
}
|
|
437
465
|
}
|
|
438
466
|
|
|
@@ -466,6 +494,32 @@ class CustomModuleManager {
|
|
|
466
494
|
} catch {
|
|
467
495
|
// swallow — a non-git repo (local path) wouldn't reach here anyway
|
|
468
496
|
}
|
|
497
|
+
// Best-effort: capture the remote default branch name so channel marker
|
|
498
|
+
// metadata for "next" reflects the actual tracked ref (not always "main").
|
|
499
|
+
let defaultRef = 'main';
|
|
500
|
+
if (!effectiveVersion) {
|
|
501
|
+
try {
|
|
502
|
+
const symbolic = execSync('git symbolic-ref --short refs/remotes/origin/HEAD', {
|
|
503
|
+
cwd: repoCacheDir,
|
|
504
|
+
stdio: 'pipe',
|
|
505
|
+
})
|
|
506
|
+
.toString()
|
|
507
|
+
.trim();
|
|
508
|
+
if (symbolic.startsWith('origin/')) {
|
|
509
|
+
defaultRef = symbolic.slice('origin/'.length) || defaultRef;
|
|
510
|
+
}
|
|
511
|
+
} catch {
|
|
512
|
+
// Fallback to previous marker value when symbolic ref is unavailable.
|
|
513
|
+
try {
|
|
514
|
+
const existingMarker = await fs.readJson(path.join(repoCacheDir, '.bmad-channel.json'));
|
|
515
|
+
if (existingMarker?.channel === 'next' && typeof existingMarker.version === 'string' && existingMarker.version.trim()) {
|
|
516
|
+
defaultRef = existingMarker.version.trim();
|
|
517
|
+
}
|
|
518
|
+
} catch {
|
|
519
|
+
// Keep default fallback.
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
}
|
|
469
523
|
|
|
470
524
|
// Write source metadata for later URL reconstruction
|
|
471
525
|
const metadataPath = path.join(repoCacheDir, '.bmad-source.json');
|
|
@@ -478,6 +532,15 @@ class CustomModuleManager {
|
|
|
478
532
|
sha: resolvedSha,
|
|
479
533
|
clonedAt: new Date().toISOString(),
|
|
480
534
|
});
|
|
535
|
+
// Keep a channel marker in custom cache too so update paths that rely on
|
|
536
|
+
// channel metadata (same as official-module cache) can treat this clone as
|
|
537
|
+
// refreshable. URL + no explicit ref => next, explicit ref => pinned.
|
|
538
|
+
await fs.writeJson(path.join(repoCacheDir, '.bmad-channel.json'), {
|
|
539
|
+
channel: effectiveVersion ? 'pinned' : 'next',
|
|
540
|
+
version: effectiveVersion || defaultRef,
|
|
541
|
+
sha: resolvedSha,
|
|
542
|
+
writtenAt: new Date().toISOString(),
|
|
543
|
+
});
|
|
481
544
|
|
|
482
545
|
// Install dependencies if package.json exists (skip during browsing/analysis)
|
|
483
546
|
const packageJsonPath = path.join(repoCacheDir, 'package.json');
|
|
@@ -642,6 +705,13 @@ class CustomModuleManager {
|
|
|
642
705
|
const repoRoots = await this._findCacheRepoRoots(cacheDir);
|
|
643
706
|
|
|
644
707
|
for (const { repoPath, metadata } of repoRoots) {
|
|
708
|
+
// Quick-update path: refresh URL-backed cached repos before reading
|
|
709
|
+
// files from them so re-deploy uses latest commits for `next` and
|
|
710
|
+
// the pinned ref for `pinned`.
|
|
711
|
+
if (options.bmadDir && metadata?.rawInput) {
|
|
712
|
+
await this._refreshRepoCacheOnce(repoPath, metadata);
|
|
713
|
+
}
|
|
714
|
+
|
|
645
715
|
// Check marketplace.json for matching module code
|
|
646
716
|
const marketplacePath = path.join(repoPath, '.claude-plugin', 'marketplace.json');
|
|
647
717
|
if (!(await fs.pathExists(marketplacePath))) continue;
|
|
@@ -692,6 +762,45 @@ class CustomModuleManager {
|
|
|
692
762
|
return this._findLocalSourceFromManifest(moduleCode, options);
|
|
693
763
|
}
|
|
694
764
|
|
|
765
|
+
/**
|
|
766
|
+
* Refresh one cached repo at most once per process with in-flight dedupe.
|
|
767
|
+
* Prevents concurrent quick-update callers from racing the same cache path.
|
|
768
|
+
* @param {string} repoPath - Absolute cache repo path
|
|
769
|
+
* @param {Object} metadata - Parsed .bmad-source.json metadata
|
|
770
|
+
*/
|
|
771
|
+
async _refreshRepoCacheOnce(repoPath, metadata) {
|
|
772
|
+
if (CustomModuleManager._refreshedRepoPaths.has(repoPath)) return;
|
|
773
|
+
|
|
774
|
+
const existing = CustomModuleManager._refreshInFlight.get(repoPath);
|
|
775
|
+
if (existing) {
|
|
776
|
+
await existing;
|
|
777
|
+
return;
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
const refreshPromise = (async () => {
|
|
781
|
+
try {
|
|
782
|
+
await this.cloneRepo(metadata.rawInput, {
|
|
783
|
+
silent: true,
|
|
784
|
+
pinOverride: metadata.version || undefined,
|
|
785
|
+
});
|
|
786
|
+
CustomModuleManager._refreshedRepoPaths.add(repoPath);
|
|
787
|
+
} catch (error_) {
|
|
788
|
+
// cloneRepo only throws here for unrecoverable cases (no cache present
|
|
789
|
+
// and a fresh clone failed, or an unexpected internal error). The
|
|
790
|
+
// common "remote unreachable but cache exists" case is handled inside
|
|
791
|
+
// cloneRepo, which preserves the clone and returns normally. Reaching
|
|
792
|
+
// this catch means we have no usable cache — surface a warning so the
|
|
793
|
+
// failure isn't silent.
|
|
794
|
+
await prompts.log.warn(`Refresh of cached custom module at ${path.basename(repoPath)} failed: ${error_?.message || error_}`);
|
|
795
|
+
} finally {
|
|
796
|
+
CustomModuleManager._refreshInFlight.delete(repoPath);
|
|
797
|
+
}
|
|
798
|
+
})();
|
|
799
|
+
|
|
800
|
+
CustomModuleManager._refreshInFlight.set(repoPath, refreshPromise);
|
|
801
|
+
await refreshPromise;
|
|
802
|
+
}
|
|
803
|
+
|
|
695
804
|
/**
|
|
696
805
|
* Check the installation manifest for a localPath entry for this module.
|
|
697
806
|
* Used as fallback when the module was installed from a local source (no cache entry).
|
|
@@ -846,11 +846,35 @@ class OfficialModules {
|
|
|
846
846
|
return false;
|
|
847
847
|
}
|
|
848
848
|
|
|
849
|
-
//
|
|
850
|
-
//
|
|
849
|
+
// Primary source: installer-written config.toml + config.user.toml (v6+).
|
|
850
|
+
// Both files together hold all install answers; config.user.toml carries
|
|
851
|
+
// user-scoped keys like user_name that would otherwise be re-prompted on
|
|
852
|
+
// every reinstall.
|
|
851
853
|
let foundAny = false;
|
|
852
|
-
const
|
|
854
|
+
for (const fileName of ['config.toml', 'config.user.toml']) {
|
|
855
|
+
const tomlPath = path.join(bmadDir, fileName);
|
|
856
|
+
if (!(await fs.pathExists(tomlPath))) continue;
|
|
857
|
+
try {
|
|
858
|
+
const content = await fs.readFile(tomlPath, 'utf8');
|
|
859
|
+
const parsed = parseCentralToml(content);
|
|
860
|
+
for (const [section, values] of Object.entries(parsed)) {
|
|
861
|
+
if (values && typeof values === 'object' && !Array.isArray(values)) {
|
|
862
|
+
if (!this._existingConfig[section]) this._existingConfig[section] = {};
|
|
863
|
+
Object.assign(this._existingConfig[section], values);
|
|
864
|
+
foundAny = true;
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
} catch {
|
|
868
|
+
// Ignore parse errors
|
|
869
|
+
}
|
|
870
|
+
}
|
|
853
871
|
|
|
872
|
+
if (foundAny) {
|
|
873
|
+
return true;
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
// Fallback: legacy per-module config.yaml files (pre-v6 installations).
|
|
877
|
+
const entries = await fs.readdir(bmadDir, { withFileTypes: true });
|
|
854
878
|
const nonModuleDirs = new Set(['_config', '_memory', 'memory', 'docs', 'scripts', 'custom']);
|
|
855
879
|
for (const entry of entries) {
|
|
856
880
|
if (entry.isDirectory()) {
|
|
@@ -2127,4 +2151,60 @@ class OfficialModules {
|
|
|
2127
2151
|
}
|
|
2128
2152
|
}
|
|
2129
2153
|
|
|
2154
|
+
/**
|
|
2155
|
+
* Parse a config.toml or config.user.toml written by writeCentralConfig.
|
|
2156
|
+
* Only handles the subset of TOML the installer produces: [core],
|
|
2157
|
+
* [modules.<code>], string/bool/number scalar values. [agents.*] and other
|
|
2158
|
+
* sections are ignored. Returns a plain object keyed by section name where
|
|
2159
|
+
* module sections use the bare code (e.g. "bmm"), not the full "modules.bmm".
|
|
2160
|
+
*/
|
|
2161
|
+
function parseCentralToml(content) {
|
|
2162
|
+
const result = {};
|
|
2163
|
+
let currentSection = null;
|
|
2164
|
+
|
|
2165
|
+
for (const rawLine of content.split('\n')) {
|
|
2166
|
+
const line = rawLine.trim();
|
|
2167
|
+
if (!line || line.startsWith('#')) continue;
|
|
2168
|
+
|
|
2169
|
+
const sectionMatch = line.match(/^\[([^\]]+)\]\s*$/);
|
|
2170
|
+
if (sectionMatch) {
|
|
2171
|
+
const name = sectionMatch[1];
|
|
2172
|
+
if (name === 'core') {
|
|
2173
|
+
currentSection = 'core';
|
|
2174
|
+
} else if (name.startsWith('modules.')) {
|
|
2175
|
+
currentSection = name.slice('modules.'.length);
|
|
2176
|
+
} else {
|
|
2177
|
+
currentSection = null;
|
|
2178
|
+
}
|
|
2179
|
+
if (currentSection && !result[currentSection]) {
|
|
2180
|
+
result[currentSection] = {};
|
|
2181
|
+
}
|
|
2182
|
+
continue;
|
|
2183
|
+
}
|
|
2184
|
+
|
|
2185
|
+
if (!currentSection) continue;
|
|
2186
|
+
|
|
2187
|
+
const kvMatch = line.match(/^([a-zA-Z_][a-zA-Z0-9_]*)\s*=\s*(.+)$/);
|
|
2188
|
+
if (!kvMatch) continue;
|
|
2189
|
+
|
|
2190
|
+
const key = kvMatch[1];
|
|
2191
|
+
const raw = kvMatch[2].trim();
|
|
2192
|
+
let value;
|
|
2193
|
+
if (raw.startsWith('"') && raw.endsWith('"')) {
|
|
2194
|
+
value = raw.slice(1, -1).replaceAll(/\\(["\\nrbt])/g, (_, c) => ({ '"': '"', '\\': '\\', n: '\n', r: '\r', b: '\b', t: '\t' })[c]);
|
|
2195
|
+
} else if (raw === 'true') {
|
|
2196
|
+
value = true;
|
|
2197
|
+
} else if (raw === 'false') {
|
|
2198
|
+
value = false;
|
|
2199
|
+
} else if (raw !== '' && !isNaN(raw)) {
|
|
2200
|
+
value = Number(raw);
|
|
2201
|
+
} else {
|
|
2202
|
+
value = raw;
|
|
2203
|
+
}
|
|
2204
|
+
result[currentSection][key] = value;
|
|
2205
|
+
}
|
|
2206
|
+
|
|
2207
|
+
return result;
|
|
2208
|
+
}
|
|
2209
|
+
|
|
2130
2210
|
module.exports = { OfficialModules };
|
package/tools/skill-validator.md
CHANGED
|
@@ -10,7 +10,7 @@ Before running inference-based validation, run the deterministic validator:
|
|
|
10
10
|
node tools/validate-skills.js --json path/to/skill-dir
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
-
This checks
|
|
13
|
+
This checks 12 rules deterministically: SKILL-01, SKILL-02, SKILL-03, SKILL-04, SKILL-05, SKILL-06, SKILL-07, PATH-02, STEP-01, STEP-06, STEP-07, SEQ-02.
|
|
14
14
|
|
|
15
15
|
Review its JSON output. For any rule that produced **zero findings** in the first pass, **skip it** during inference-based validation below — it has already been verified. If a rule produced any findings, the inference validator should still review that rule (some rules like SKILL-04 and SKILL-06 have sub-checks that benefit from judgment). Focus your inference effort on the remaining rules that require judgment (PATH-01, PATH-03, PATH-04, PATH-05, WF-03, STEP-02, STEP-03, STEP-04, STEP-05, SEQ-01, REF-01, REF-02, REF-03).
|
|
16
16
|
|
|
@@ -98,24 +98,6 @@ If no findings are generated (from either pass), the skill passes validation.
|
|
|
98
98
|
|
|
99
99
|
---
|
|
100
100
|
|
|
101
|
-
### WF-01 — Only SKILL.md May Have `name` in Frontmatter
|
|
102
|
-
|
|
103
|
-
- **Severity:** HIGH
|
|
104
|
-
- **Applies to:** all `.md` files except `SKILL.md`
|
|
105
|
-
- **Rule:** The `name` field belongs only in `SKILL.md`. No other markdown file in the skill directory may have `name:` in its frontmatter.
|
|
106
|
-
- **Detection:** Parse frontmatter of every non-SKILL.md markdown file and check for `name:` key.
|
|
107
|
-
- **Fix:** Remove the `name:` line from the file's frontmatter.
|
|
108
|
-
- **Exception:** `bmad-agent-tech-writer` — has sub-skill files with intentional `name` fields (to be revisited).
|
|
109
|
-
|
|
110
|
-
### WF-02 — Only SKILL.md May Have `description` in Frontmatter
|
|
111
|
-
|
|
112
|
-
- **Severity:** HIGH
|
|
113
|
-
- **Applies to:** all `.md` files except `SKILL.md`
|
|
114
|
-
- **Rule:** The `description` field belongs only in `SKILL.md`. No other markdown file in the skill directory may have `description:` in its frontmatter.
|
|
115
|
-
- **Detection:** Parse frontmatter of every non-SKILL.md markdown file and check for `description:` key.
|
|
116
|
-
- **Fix:** Remove the `description:` line from the file's frontmatter.
|
|
117
|
-
- **Exception:** `bmad-agent-tech-writer` — has sub-skill files with intentional `description` fields (to be revisited).
|
|
118
|
-
|
|
119
101
|
### WF-03 — workflow.md Frontmatter Variables Must Be Config or Runtime Only
|
|
120
102
|
|
|
121
103
|
- **Severity:** HIGH
|