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.
- package/CHANGELOG.md +114 -29
- package/README.md +45 -27
- package/SECURITY.md +9 -1
- package/osint-agent-package/README.md +13 -0
- package/osint-agent-package/SETUP_KEYS.md +13 -0
- package/package.json +32 -8
- package/readme-international/README.de.md +134 -537
- package/readme-international/README.es.md +136 -556
- package/readme-international/README.fr.md +135 -553
- package/src/bmad-plus/agents/agent-shadow/SKILL.md +6 -0
- package/src/bmad-plus/packs/pack-memory/README.md +1 -1
- package/src/bmad-plus/packs/pack-memory/memory-orchestrator.md +1 -1
- package/src/bmad-plus/packs/pack-memory/shared/memory-protocol.md +1 -1
- package/src/bmad-plus/packs/pack-shield/README.md +2 -0
- package/src/bmad-plus/packs/pack-shield/SKILL.md +2 -0
- package/src/bmad-plus/packs/pack-shield/shared/audit-report-template.md +6 -0
- package/src/bmad-plus/packs/pack-shield/shield-orchestrator.md +4 -0
- package/src/bmad-plus/skills/bmad-plus-uat/SKILL.md +18 -4
- package/src/bmad-plus/skills/bmad-plus-uat/template/page.html +95 -19
- package/src/bmad-plus/skills/bmad-plus-uat/template/strings.json +90 -10
- package/src/bmad-plus/skills/bmad-plus-uat/templates/README.md +63 -0
- package/src/bmad-plus/skills/bmad-plus-uat/templates/example-uat-spec.json +95 -0
- package/src/bmad-plus/skills/bmad-plus-uat/templates/uat-release-gate.mjs +127 -0
- package/src/bmad-plus/skills/bmad-plus-uat/templates/uat-spec-present.mjs +77 -0
- package/tools/build/adapters.config.js +1 -1
- package/tools/build/generate-adapters.js +186 -60
- package/tools/build/generate.js +49 -13
- package/tools/cli/bmad-plus-cli.js +3 -6
- package/tools/cli/commands/autoconfig.js +66 -24
- package/tools/cli/commands/install.js +620 -356
- package/tools/cli/commands/memory.js +40 -13
- package/tools/cli/commands/scan.js +94 -30
- package/tools/cli/commands/uat.js +21 -5
- package/tools/cli/commands/uninstall.js +65 -16
- package/tools/cli/commands/update-check.js +8 -3
- package/tools/cli/commands/update-policy.js +14 -5
- package/tools/cli/commands/update.js +89 -20
- package/tools/cli/i18n.js +128 -66
- package/tools/cli/lib/ide-config.js +3 -1
- package/tools/cli/lib/install-manifest.js +13 -6
- package/tools/cli/lib/installation-health.js +33 -1
- package/tools/cli/lib/installed-adapters.js +29 -6
- package/tools/cli/lib/memory-init.js +19 -4
- package/tools/cli/lib/packs.js +17 -28
- package/tools/cli/lib/path-hash.js +5 -1
- package/tools/cli/lib/python-provision.js +51 -12
- package/tools/cli/lib/stack-detect.js +51 -25
- package/tools/cli/lib/uat.js +18 -0
- package/tools/cli/lib/update-check.js +132 -34
- package/tools/cli/lib/update-dispatch.js +2 -2
- package/tools/cli/lib/update-policy.js +57 -19
- package/tools/cli/lib/update-transaction.js +439 -85
- package/tools/build/README.md +0 -78
- package/tools/build/check-counts.js +0 -626
- package/tools/build/check-install-contract.js +0 -648
- package/tools/build/generated-adapters/.codex/AGENTS.md +0 -134
- package/tools/build/generated-adapters/.cursor/rules/bmad-plus.mdc +0 -139
- package/tools/build/generated-adapters/.opencode/AGENTS.md +0 -134
- package/tools/build/generated-adapters/AGENTS.md +0 -132
- package/tools/build/generated-adapters/CLAUDE.md +0 -135
- package/tools/build/generated-adapters/CONVENTIONS.md +0 -134
- package/tools/build/generated-adapters/GEMINI.md +0 -139
- package/tools/build/generated-adapters/README.md +0 -79
- package/tools/maintain/upstream-candidate.js +0 -456
- package/tools/release/publication-content.js +0 -903
- 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 {
|
|
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 (
|
|
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 (
|
|
18
|
-
|
|
19
|
-
|
|
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
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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 (
|
|
44
|
-
|
|
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 {
|
|
84
|
+
} catch {
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
52
87
|
}
|
|
53
88
|
|
|
54
89
|
function saveCache(projectDir, cache) {
|
|
55
|
-
try {
|
|
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,
|
|
60
|
-
|
|
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,
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
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 (
|
|
70
|
-
|
|
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(
|
|
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 {
|
|
142
|
+
return {
|
|
143
|
+
...result,
|
|
144
|
+
status: 'invalid-installation',
|
|
145
|
+
reason: 'missing-or-invalid-installation-manifest',
|
|
146
|
+
};
|
|
79
147
|
}
|
|
80
148
|
let policy;
|
|
81
|
-
try {
|
|
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(
|
|
102
|
-
|
|
103
|
-
|
|
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, {
|
|
110
|
-
|
|
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 =
|
|
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 =
|
|
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({
|
|
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
|
|
148
|
-
|
|
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
|
-
|
|
50
|
-
|
|
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',
|
|
12
|
-
|
|
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
|
|
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 (
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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', {
|
|
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 {
|
|
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 {
|
|
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')
|
|
80
|
-
|
|
81
|
-
|
|
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,
|
|
89
|
-
|
|
119
|
+
DEFAULT_POLICY,
|
|
120
|
+
POLICY_FILE,
|
|
121
|
+
OFFICIAL_REGISTRY,
|
|
122
|
+
isExactVersion,
|
|
123
|
+
validatePolicy,
|
|
124
|
+
readUpdatePolicy,
|
|
125
|
+
writeUpdatePolicy,
|
|
126
|
+
authorizeTarget,
|
|
127
|
+
writeProjectJson,
|
|
90
128
|
};
|