bmad-plus 0.16.0 → 0.17.1

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 (66) hide show
  1. package/CHANGELOG.md +114 -29
  2. package/README.md +45 -27
  3. package/SECURITY.md +9 -1
  4. package/osint-agent-package/README.md +13 -0
  5. package/osint-agent-package/SETUP_KEYS.md +13 -0
  6. package/package.json +32 -8
  7. package/readme-international/README.de.md +134 -537
  8. package/readme-international/README.es.md +136 -556
  9. package/readme-international/README.fr.md +135 -553
  10. package/src/bmad-plus/agents/agent-shadow/SKILL.md +6 -0
  11. package/src/bmad-plus/packs/pack-memory/README.md +1 -1
  12. package/src/bmad-plus/packs/pack-memory/memory-orchestrator.md +1 -1
  13. package/src/bmad-plus/packs/pack-memory/shared/memory-protocol.md +1 -1
  14. package/src/bmad-plus/packs/pack-shield/README.md +2 -0
  15. package/src/bmad-plus/packs/pack-shield/SKILL.md +2 -0
  16. package/src/bmad-plus/packs/pack-shield/shared/audit-report-template.md +6 -0
  17. package/src/bmad-plus/packs/pack-shield/shield-orchestrator.md +4 -0
  18. package/src/bmad-plus/skills/bmad-plus-uat/SKILL.md +18 -4
  19. package/src/bmad-plus/skills/bmad-plus-uat/template/page.html +95 -19
  20. package/src/bmad-plus/skills/bmad-plus-uat/template/strings.json +90 -10
  21. package/src/bmad-plus/skills/bmad-plus-uat/templates/README.md +63 -0
  22. package/src/bmad-plus/skills/bmad-plus-uat/templates/example-uat-spec.json +95 -0
  23. package/src/bmad-plus/skills/bmad-plus-uat/templates/uat-release-gate.mjs +127 -0
  24. package/src/bmad-plus/skills/bmad-plus-uat/templates/uat-spec-present.mjs +77 -0
  25. package/tools/build/adapters.config.js +1 -1
  26. package/tools/build/generate-adapters.js +186 -60
  27. package/tools/build/generate.js +49 -13
  28. package/tools/cli/bmad-plus-cli.js +3 -6
  29. package/tools/cli/commands/autoconfig.js +66 -24
  30. package/tools/cli/commands/install.js +620 -356
  31. package/tools/cli/commands/memory.js +40 -13
  32. package/tools/cli/commands/scan.js +94 -30
  33. package/tools/cli/commands/uat.js +21 -5
  34. package/tools/cli/commands/uninstall.js +65 -16
  35. package/tools/cli/commands/update-check.js +8 -3
  36. package/tools/cli/commands/update-policy.js +14 -5
  37. package/tools/cli/commands/update.js +89 -20
  38. package/tools/cli/i18n.js +128 -66
  39. package/tools/cli/lib/ide-config.js +3 -1
  40. package/tools/cli/lib/install-manifest.js +13 -6
  41. package/tools/cli/lib/installation-health.js +33 -1
  42. package/tools/cli/lib/installed-adapters.js +29 -6
  43. package/tools/cli/lib/memory-init.js +19 -4
  44. package/tools/cli/lib/packs.js +17 -28
  45. package/tools/cli/lib/path-hash.js +5 -1
  46. package/tools/cli/lib/python-provision.js +51 -12
  47. package/tools/cli/lib/stack-detect.js +51 -25
  48. package/tools/cli/lib/uat.js +18 -0
  49. package/tools/cli/lib/update-check.js +132 -34
  50. package/tools/cli/lib/update-dispatch.js +2 -2
  51. package/tools/cli/lib/update-policy.js +57 -19
  52. package/tools/cli/lib/update-transaction.js +439 -85
  53. package/tools/build/README.md +0 -78
  54. package/tools/build/check-counts.js +0 -626
  55. package/tools/build/check-install-contract.js +0 -648
  56. package/tools/build/generated-adapters/.codex/AGENTS.md +0 -134
  57. package/tools/build/generated-adapters/.cursor/rules/bmad-plus.mdc +0 -139
  58. package/tools/build/generated-adapters/.opencode/AGENTS.md +0 -134
  59. package/tools/build/generated-adapters/AGENTS.md +0 -132
  60. package/tools/build/generated-adapters/CLAUDE.md +0 -135
  61. package/tools/build/generated-adapters/CONVENTIONS.md +0 -134
  62. package/tools/build/generated-adapters/GEMINI.md +0 -139
  63. package/tools/build/generated-adapters/README.md +0 -79
  64. package/tools/maintain/upstream-candidate.js +0 -456
  65. package/tools/release/publication-content.js +0 -903
  66. package/tools/release/supply-chain.js +0 -282
@@ -3,20 +3,37 @@ const fs = require('node:fs');
3
3
  const semver = require('semver');
4
4
  const { safeAdapterPath } = require('./installed-adapters');
5
5
  const { readInstallManifest } = require('./install-manifest');
6
- const { readUpdatePolicy, validatePolicy, authorizeTarget, isExactVersion, writeProjectJson } = require('./update-policy');
6
+ const {
7
+ readUpdatePolicy,
8
+ validatePolicy,
9
+ authorizeTarget,
10
+ isExactVersion,
11
+ writeProjectJson,
12
+ } = require('./update-policy');
7
13
 
8
14
  const CACHE_FILE = '.bmad/update-check.json';
9
15
  const CACHE_TTL_MS = 24 * 60 * 60 * 1000;
10
16
  const FAILURE_BACKOFF_MS = 60 * 60 * 1000;
11
17
 
12
18
  function validateRelease(value) {
13
- if (!value || typeof value !== 'object' || Array.isArray(value) || !isExactVersion(value.version)) {
19
+ if (
20
+ !value ||
21
+ typeof value !== 'object' ||
22
+ Array.isArray(value) ||
23
+ !isExactVersion(value.version)
24
+ ) {
14
25
  throw new Error('Registry returned an invalid exact version.');
15
26
  }
16
27
  const engines = value.engines === undefined ? {} : value.engines;
17
- if (!engines || typeof engines !== 'object' || Array.isArray(engines) ||
18
- (engines.node !== undefined && (typeof engines.node !== 'string' ||
19
- !engines.node.trim() || !semver.validRange(engines.node)))) {
28
+ if (
29
+ !engines ||
30
+ typeof engines !== 'object' ||
31
+ Array.isArray(engines) ||
32
+ (engines.node !== undefined &&
33
+ (typeof engines.node !== 'string' ||
34
+ !engines.node.trim() ||
35
+ !semver.validRange(engines.node)))
36
+ ) {
20
37
  throw new Error('Registry returned an invalid Node engine requirement.');
21
38
  }
22
39
  return { version: value.version, engines: engines.node ? { node: engines.node } : {} };
@@ -25,10 +42,20 @@ function validateRelease(value) {
25
42
  async function queryNpmRelease({ projectDir, channel, registry }) {
26
43
  validatePolicy({ channel, registry });
27
44
  const { runNpm } = require('./npm-runner');
28
- const stdout = await runNpm([
29
- 'view', `bmad-plus@${channel}`, 'version', 'engines', '--json', `--registry=${registry}`,
30
- '--prefer-online', '--fetch-timeout=4000', '--fetch-retries=0',
31
- ], { cwd: projectDir, timeout: 6000, maxBuffer: 64 * 1024 });
45
+ const stdout = await runNpm(
46
+ [
47
+ 'view',
48
+ `bmad-plus@${channel}`,
49
+ 'version',
50
+ 'engines',
51
+ '--json',
52
+ `--registry=${registry}`,
53
+ '--prefer-online',
54
+ '--fetch-timeout=4000',
55
+ '--fetch-retries=0',
56
+ ],
57
+ { cwd: projectDir, timeout: 6000, maxBuffer: 64 * 1024 }
58
+ );
32
59
  return validateRelease(JSON.parse(stdout));
33
60
  }
34
61
 
@@ -40,45 +67,88 @@ function timestamp(value, now) {
40
67
  function readCache(projectDir, policy, now) {
41
68
  try {
42
69
  const cache = JSON.parse(fs.readFileSync(safeAdapterPath(projectDir, CACHE_FILE), 'utf8'));
43
- if (!cache || cache.schemaVersion !== 1 || cache.package !== 'bmad-plus' ||
44
- cache.registry !== policy.registry || cache.channel !== policy.channel) return null;
70
+ if (
71
+ !cache ||
72
+ cache.schemaVersion !== 1 ||
73
+ cache.package !== 'bmad-plus' ||
74
+ cache.registry !== policy.registry ||
75
+ cache.channel !== policy.channel
76
+ )
77
+ return null;
45
78
  if (cache.release) {
46
79
  cache.release = validateRelease(cache.release);
47
80
  if (timestamp(cache.checkedAt, now) === null) return null;
48
81
  }
49
82
  if (cache.failedAt !== undefined && timestamp(cache.failedAt, now) === null) return null;
50
83
  return cache;
51
- } catch { return null; }
84
+ } catch {
85
+ return null;
86
+ }
52
87
  }
53
88
 
54
89
  function saveCache(projectDir, cache) {
55
- try { writeProjectJson(projectDir, CACHE_FILE, cache); } catch { /* A read-only cache is nonfatal. */ }
90
+ try {
91
+ writeProjectJson(projectDir, CACHE_FILE, cache);
92
+ } catch {
93
+ /* A read-only cache is nonfatal. */
94
+ }
56
95
  }
57
96
 
58
97
  async function checkForUpdate({
59
- projectDir, runningVersion = require('../../../package.json').version, refresh = false, offline = false,
60
- now = Date.now, queryRelease = queryNpmRelease, checkReadiness, nodeVersion = process.version,
98
+ projectDir,
99
+ runningVersion = require('../../../package.json').version,
100
+ refresh = false,
101
+ offline = false,
102
+ now = Date.now,
103
+ queryRelease = queryNpmRelease,
104
+ checkReadiness,
105
+ nodeVersion = process.version,
61
106
  }) {
62
107
  const time = typeof now === 'function' ? now() : now;
63
108
  const result = {
64
- schemaVersion: 1, installedVersion: null, runningVersion, latestVersion: null,
65
- targetVersion: null, channel: null, status: 'unknown', checkedAt: null,
66
- source: 'none', stale: false, updateAvailable: null, releaseEligible: false,
67
- versionEligible: false, canAutoApply: false, engineCompatible: null, engines: {}, reason: null,
109
+ schemaVersion: 1,
110
+ installedVersion: null,
111
+ runningVersion,
112
+ latestVersion: null,
113
+ targetVersion: null,
114
+ channel: null,
115
+ status: 'unknown',
116
+ checkedAt: null,
117
+ source: 'none',
118
+ stale: false,
119
+ updateAvailable: null,
120
+ releaseEligible: false,
121
+ versionEligible: false,
122
+ canAutoApply: false,
123
+ engineCompatible: null,
124
+ engines: {},
125
+ reason: null,
68
126
  };
69
- if (!isExactVersion(runningVersion) || !Number.isFinite(time) ||
70
- !Number.isFinite(new Date(time).getTime()) || !semver.valid(nodeVersion)) {
127
+ if (
128
+ !isExactVersion(runningVersion) ||
129
+ !Number.isFinite(time) ||
130
+ !Number.isFinite(new Date(time).getTime()) ||
131
+ !semver.valid(nodeVersion)
132
+ ) {
71
133
  return { ...result, status: 'invalid-runtime', reason: 'invalid-running-version-or-clock' };
72
134
  }
73
135
  try {
74
- const manifest = readInstallManifest(safeAdapterPath(projectDir, '_bmad/.bmad-plus-install.json'));
136
+ const manifest = readInstallManifest(
137
+ safeAdapterPath(projectDir, '_bmad/.bmad-plus-install.json')
138
+ );
75
139
  if (!isExactVersion(manifest.version)) throw new Error('Invalid installed version');
76
140
  result.installedVersion = manifest.version;
77
141
  } catch {
78
- return { ...result, status: 'invalid-installation', reason: 'missing-or-invalid-installation-manifest' };
142
+ return {
143
+ ...result,
144
+ status: 'invalid-installation',
145
+ reason: 'missing-or-invalid-installation-manifest',
146
+ };
79
147
  }
80
148
  let policy;
81
- try { policy = readUpdatePolicy(projectDir); } catch {
149
+ try {
150
+ policy = readUpdatePolicy(projectDir);
151
+ } catch {
82
152
  return { ...result, status: 'invalid-policy', reason: 'unreadable-or-invalid-update-policy' };
83
153
  }
84
154
  result.channel = policy.channel;
@@ -98,16 +168,30 @@ async function checkForUpdate({
98
168
  result.checkedAt = cache.checkedAt;
99
169
  } else {
100
170
  try {
101
- release = validateRelease(await queryRelease({ projectDir, channel: policy.channel, registry: policy.registry }));
102
- cache = { schemaVersion: 1, package: 'bmad-plus', registry: policy.registry, channel: policy.channel,
103
- release, checkedAt: new Date(time).toISOString() };
171
+ release = validateRelease(
172
+ await queryRelease({ projectDir, channel: policy.channel, registry: policy.registry })
173
+ );
174
+ cache = {
175
+ schemaVersion: 1,
176
+ package: 'bmad-plus',
177
+ registry: policy.registry,
178
+ channel: policy.channel,
179
+ release,
180
+ checkedAt: new Date(time).toISOString(),
181
+ };
104
182
  saveCache(projectDir, cache);
105
183
  result.source = 'registry';
106
184
  result.checkedAt = cache.checkedAt;
107
185
  } catch {
108
186
  result.reason = 'registry-unavailable';
109
- saveCache(projectDir, { ...cache, schemaVersion: 1, package: 'bmad-plus',
110
- registry: policy.registry, channel: policy.channel, failedAt: new Date(time).toISOString() });
187
+ saveCache(projectDir, {
188
+ ...cache,
189
+ schemaVersion: 1,
190
+ package: 'bmad-plus',
191
+ registry: policy.registry,
192
+ channel: policy.channel,
193
+ failedAt: new Date(time).toISOString(),
194
+ });
111
195
  }
112
196
  }
113
197
  if (!release) {
@@ -125,11 +209,17 @@ async function checkForUpdate({
125
209
  result.targetVersion = release.version;
126
210
  result.latestVersion = policy.channel === 'latest' ? release.version : null;
127
211
  result.engines = release.engines;
128
- result.engineCompatible = !release.engines.node || semver.satisfies(nodeVersion, release.engines.node);
212
+ result.engineCompatible =
213
+ !release.engines.node || semver.satisfies(nodeVersion, release.engines.node);
129
214
  const comparison = semver.compare(release.version, result.installedVersion);
130
215
  result.updateAvailable = comparison > 0;
131
216
  result.status = comparison > 0 ? 'update-available' : comparison === 0 ? 'current' : 'ahead';
132
- result.reason = comparison > 0 ? null : comparison === 0 ? 'installed-matches-channel' : 'installed-ahead-of-channel';
217
+ result.reason =
218
+ comparison > 0
219
+ ? null
220
+ : comparison === 0
221
+ ? 'installed-matches-channel'
222
+ : 'installed-ahead-of-channel';
133
223
  if (comparison <= 0) return result;
134
224
  if (!result.engineCompatible) return { ...result, reason: 'incompatible-node-version' };
135
225
  if (!policy.allowPrerelease && semver.prerelease(release.version)) {
@@ -142,10 +232,18 @@ async function checkForUpdate({
142
232
  if (authorization.allowed) {
143
233
  if (!checkReadiness) return { ...result, reason: 'ownership-readiness-not-checked' };
144
234
  try {
145
- const readiness = await checkReadiness({ projectDir, targetVersion: release.version, policy });
235
+ const readiness = await checkReadiness({
236
+ projectDir,
237
+ targetVersion: release.version,
238
+ policy,
239
+ });
146
240
  result.canAutoApply = readiness?.ready === true;
147
- result.reason = result.canAutoApply ? 'ready-for-authorized-update' : readiness?.reason || 'ownership-not-ready';
148
- } catch { result.reason = 'ownership-readiness-failed'; }
241
+ result.reason = result.canAutoApply
242
+ ? 'ready-for-authorized-update'
243
+ : readiness?.reason || 'ownership-not-ready';
244
+ } catch {
245
+ result.reason = 'ownership-readiness-failed';
246
+ }
149
247
  }
150
248
  return result;
151
249
  }
@@ -46,8 +46,8 @@ async function withExecutionDirectory(action) {
46
46
  const actual = fs.realpathSync(directory);
47
47
  assert(
48
48
  path.dirname(actual) === parent &&
49
- path.basename(actual).startsWith('bmad-update-exec-') &&
50
- !fs.lstatSync(directory).isSymbolicLink(),
49
+ path.basename(actual).startsWith('bmad-update-exec-') &&
50
+ !fs.lstatSync(directory).isSymbolicLink(),
51
51
  'Refusing to remove an unexpected updater temporary directory.'
52
52
  );
53
53
  fs.rmSync(actual, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
@@ -8,14 +8,19 @@ const { safeAdapterPath } = require('./installed-adapters');
8
8
  const POLICY_FILE = '_bmad/update-policy.json';
9
9
  const OFFICIAL_REGISTRY = 'https://registry.npmjs.org/';
10
10
  const DEFAULT_POLICY = Object.freeze({
11
- mode: 'notify', channel: 'latest', allowedRange: null,
12
- allowPrerelease: false, registry: OFFICIAL_REGISTRY,
11
+ mode: 'notify',
12
+ channel: 'latest',
13
+ allowedRange: null,
14
+ allowPrerelease: false,
15
+ registry: OFFICIAL_REGISTRY,
13
16
  });
14
17
 
15
18
  function isExactVersion(version) {
16
- return typeof version === 'string' &&
19
+ return (
20
+ typeof version === 'string' &&
17
21
  /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/.test(version) &&
18
- semver.valid(version) !== null;
22
+ semver.valid(version) !== null
23
+ );
19
24
  }
20
25
 
21
26
  function validatePolicy(value) {
@@ -27,13 +32,21 @@ function validatePolicy(value) {
27
32
  }
28
33
  const policy = { ...DEFAULT_POLICY, ...value };
29
34
  delete policy.schemaVersion;
30
- if (Object.keys(policy).some(key => !Object.hasOwn(DEFAULT_POLICY, key)) ||
31
- !['off', 'notify', 'auto'].includes(policy.mode) ||
32
- typeof policy.channel !== 'string' || !/^[a-z][a-z0-9_-]{0,31}$/.test(policy.channel) ||
33
- typeof policy.allowPrerelease !== 'boolean' || policy.registry !== OFFICIAL_REGISTRY ||
34
- (policy.allowedRange !== null && (typeof policy.allowedRange !== 'string' ||
35
- !policy.allowedRange.trim() || !semver.validRange(policy.allowedRange)))) {
36
- throw new Error('Invalid update policy: check mode, channel, range, prerelease flag and official registry.');
35
+ if (
36
+ Object.keys(policy).some((key) => !Object.hasOwn(DEFAULT_POLICY, key)) ||
37
+ !['off', 'notify', 'auto'].includes(policy.mode) ||
38
+ typeof policy.channel !== 'string' ||
39
+ !/^[a-z][a-z0-9_-]{0,31}$/.test(policy.channel) ||
40
+ typeof policy.allowPrerelease !== 'boolean' ||
41
+ policy.registry !== OFFICIAL_REGISTRY ||
42
+ (policy.allowedRange !== null &&
43
+ (typeof policy.allowedRange !== 'string' ||
44
+ !policy.allowedRange.trim() ||
45
+ !semver.validRange(policy.allowedRange)))
46
+ ) {
47
+ throw new Error(
48
+ 'Invalid update policy: check mode, channel, range, prerelease flag and official registry.'
49
+ );
37
50
  }
38
51
  if (policy.mode === 'auto' && policy.allowedRange === null) {
39
52
  throw new Error('Automatic updates require an explicit allowedRange.');
@@ -58,11 +71,18 @@ function writeProjectJson(projectDir, relativeFile, data) {
58
71
  const temporaryName = `${relativeFile}.${process.pid}.${crypto.randomUUID()}.tmp`;
59
72
  const temporary = safeAdapterPath(projectDir, temporaryName);
60
73
  try {
61
- fs.writeFileSync(temporary, JSON.stringify(data, null, 2) + '\n', { encoding: 'utf8', flag: 'wx' });
74
+ fs.writeFileSync(temporary, JSON.stringify(data, null, 2) + '\n', {
75
+ encoding: 'utf8',
76
+ flag: 'wx',
77
+ });
62
78
  safeAdapterPath(projectDir, relativeFile);
63
79
  fs.renameSync(temporary, file);
64
80
  } finally {
65
- try { fs.unlinkSync(temporary); } catch { /* Preserve the original write error if cleanup fails. */ }
81
+ try {
82
+ fs.unlinkSync(temporary);
83
+ } catch {
84
+ /* Preserve the original write error if cleanup fails. */
85
+ }
66
86
  }
67
87
  }
68
88
 
@@ -74,17 +94,35 @@ function writeUpdatePolicy(projectDir, value) {
74
94
 
75
95
  function authorizeTarget(value, version) {
76
96
  let policy;
77
- try { policy = validatePolicy(value); } catch { return { allowed: false, reason: 'invalid-policy' }; }
97
+ try {
98
+ policy = validatePolicy(value);
99
+ } catch {
100
+ return { allowed: false, reason: 'invalid-policy' };
101
+ }
78
102
  if (!isExactVersion(version)) return { allowed: false, reason: 'invalid-target-version' };
79
- if (policy.mode !== 'auto') return { allowed: false, reason: policy.mode === 'off' ? 'updates-disabled' : 'approval-required' };
80
- if (!policy.allowPrerelease && semver.prerelease(version)) return { allowed: false, reason: 'prerelease-not-allowed' };
81
- if (!semver.satisfies(version, policy.allowedRange, { includePrerelease: policy.allowPrerelease })) {
103
+ if (policy.mode !== 'auto')
104
+ return {
105
+ allowed: false,
106
+ reason: policy.mode === 'off' ? 'updates-disabled' : 'approval-required',
107
+ };
108
+ if (!policy.allowPrerelease && semver.prerelease(version))
109
+ return { allowed: false, reason: 'prerelease-not-allowed' };
110
+ if (
111
+ !semver.satisfies(version, policy.allowedRange, { includePrerelease: policy.allowPrerelease })
112
+ ) {
82
113
  return { allowed: false, reason: 'outside-allowed-range' };
83
114
  }
84
115
  return { allowed: true, reason: 'authorized-policy' };
85
116
  }
86
117
 
87
118
  module.exports = {
88
- DEFAULT_POLICY, POLICY_FILE, OFFICIAL_REGISTRY, isExactVersion,
89
- validatePolicy, readUpdatePolicy, writeUpdatePolicy, authorizeTarget, writeProjectJson,
119
+ DEFAULT_POLICY,
120
+ POLICY_FILE,
121
+ OFFICIAL_REGISTRY,
122
+ isExactVersion,
123
+ validatePolicy,
124
+ readUpdatePolicy,
125
+ writeUpdatePolicy,
126
+ authorizeTarget,
127
+ writeProjectJson,
90
128
  };