bmad-plus 0.17.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 +90 -29
- package/README.md +21 -21
- 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 +30 -8
- package/readme-international/README.de.md +134 -542
- package/readme-international/README.es.md +136 -561
- package/readme-international/README.fr.md +135 -558
- 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/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 +62 -22
- 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/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 +120 -68
- 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/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 -904
- package/tools/release/supply-chain.js +0 -282
package/tools/cli/lib/packs.js
CHANGED
|
@@ -25,8 +25,6 @@ const PACKS = {
|
|
|
25
25
|
"data": [
|
|
26
26
|
"role-triggers.yaml"
|
|
27
27
|
],
|
|
28
|
-
"packDir": "pack-core",
|
|
29
|
-
"packSrcDir": "packs",
|
|
30
28
|
"required": true,
|
|
31
29
|
"desc": "Core agents & skills"
|
|
32
30
|
},
|
|
@@ -38,8 +36,6 @@ const PACKS = {
|
|
|
38
36
|
],
|
|
39
37
|
"skills": [],
|
|
40
38
|
"externalPackage": "osint-agent-package",
|
|
41
|
-
"packDir": "pack-osint",
|
|
42
|
-
"packSrcDir": "packs",
|
|
43
39
|
"desc": "OSINT & investigation"
|
|
44
40
|
},
|
|
45
41
|
"maker": {
|
|
@@ -50,16 +46,12 @@ const PACKS = {
|
|
|
50
46
|
],
|
|
51
47
|
"skills": [],
|
|
52
48
|
"data": [],
|
|
53
|
-
"packDir": "pack-maker",
|
|
54
|
-
"packSrcDir": "packs",
|
|
55
49
|
"desc": "Agent creation toolkit"
|
|
56
50
|
},
|
|
57
51
|
"shield": {
|
|
58
52
|
"name": "Shield",
|
|
59
53
|
"icon": "m",
|
|
60
|
-
"agents": [
|
|
61
|
-
"shield-orchestrator"
|
|
62
|
-
],
|
|
54
|
+
"agents": [],
|
|
63
55
|
"skills": [],
|
|
64
56
|
"packDir": "pack-shield",
|
|
65
57
|
"packSrcDir": "packs",
|
|
@@ -68,11 +60,7 @@ const PACKS = {
|
|
|
68
60
|
"seo": {
|
|
69
61
|
"name": "SEO",
|
|
70
62
|
"icon": "k",
|
|
71
|
-
"agents": [
|
|
72
|
-
"seo-scout",
|
|
73
|
-
"seo-chief",
|
|
74
|
-
"seo-judge"
|
|
75
|
-
],
|
|
63
|
+
"agents": [],
|
|
76
64
|
"skills": [],
|
|
77
65
|
"packDir": "pack-seo",
|
|
78
66
|
"packSrcDir": "packs",
|
|
@@ -81,9 +69,7 @@ const PACKS = {
|
|
|
81
69
|
"memory": {
|
|
82
70
|
"name": "Memory",
|
|
83
71
|
"icon": "x",
|
|
84
|
-
"agents": [
|
|
85
|
-
"zecher"
|
|
86
|
-
],
|
|
72
|
+
"agents": [],
|
|
87
73
|
"skills": [],
|
|
88
74
|
"packDir": "pack-memory",
|
|
89
75
|
"packSrcDir": "packs",
|
|
@@ -92,9 +78,7 @@ const PACKS = {
|
|
|
92
78
|
"dev-studio": {
|
|
93
79
|
"name": "Dev Studio",
|
|
94
80
|
"icon": "v",
|
|
95
|
-
"agents": [
|
|
96
|
-
"dev-studio-orchestrator"
|
|
97
|
-
],
|
|
81
|
+
"agents": [],
|
|
98
82
|
"skills": [
|
|
99
83
|
"dev-studio"
|
|
100
84
|
],
|
|
@@ -105,9 +89,7 @@ const PACKS = {
|
|
|
105
89
|
"backup": {
|
|
106
90
|
"name": "Backup",
|
|
107
91
|
"icon": "y",
|
|
108
|
-
"agents": [
|
|
109
|
-
"backup-agent"
|
|
110
|
-
],
|
|
92
|
+
"agents": [],
|
|
111
93
|
"skills": [],
|
|
112
94
|
"packDir": "pack-backup",
|
|
113
95
|
"packSrcDir": "packs",
|
|
@@ -116,9 +98,7 @@ const PACKS = {
|
|
|
116
98
|
"animated": {
|
|
117
99
|
"name": "Animated",
|
|
118
100
|
"icon": "z",
|
|
119
|
-
"agents": [
|
|
120
|
-
"animated-website-agent"
|
|
121
|
-
],
|
|
101
|
+
"agents": [],
|
|
122
102
|
"skills": [],
|
|
123
103
|
"packDir": "pack-animated",
|
|
124
104
|
"packSrcDir": "packs",
|
|
@@ -211,7 +191,7 @@ const DERIVED = {
|
|
|
211
191
|
"product": {
|
|
212
192
|
"code": "bmad-plus",
|
|
213
193
|
"displayName": "BMAD+",
|
|
214
|
-
"version": "0.17.
|
|
194
|
+
"version": "0.17.1",
|
|
215
195
|
"derivedFrom": "BMAD-METHOD v6.6.0"
|
|
216
196
|
},
|
|
217
197
|
"packOrder": [
|
|
@@ -246,6 +226,7 @@ const DERIVED = {
|
|
|
246
226
|
"order": 0,
|
|
247
227
|
"name": "Core",
|
|
248
228
|
"displayName": "Core Development",
|
|
229
|
+
"iconEmoji": "⚙️",
|
|
249
230
|
"required": true,
|
|
250
231
|
"installerAgentCount": 4,
|
|
251
232
|
"categoryAgentCount": 0,
|
|
@@ -306,6 +287,7 @@ const DERIVED = {
|
|
|
306
287
|
"order": 1,
|
|
307
288
|
"name": "OSINT",
|
|
308
289
|
"displayName": "OSINT Intelligence",
|
|
290
|
+
"iconEmoji": "🔍",
|
|
309
291
|
"required": false,
|
|
310
292
|
"installerAgentCount": 1,
|
|
311
293
|
"categoryAgentCount": 0,
|
|
@@ -328,7 +310,7 @@ const DERIVED = {
|
|
|
328
310
|
"id": "agent-shadow",
|
|
329
311
|
"name": "Shadow",
|
|
330
312
|
"role": "OSINT",
|
|
331
|
-
"description": "Investigation +
|
|
313
|
+
"description": "Investigation + Public-source research (governed, legal basis required)"
|
|
332
314
|
}
|
|
333
315
|
],
|
|
334
316
|
"summary": "Investigation & scraping (55+ Apify actors, 7 APIs).",
|
|
@@ -340,6 +322,7 @@ const DERIVED = {
|
|
|
340
322
|
"order": 2,
|
|
341
323
|
"name": "Maker",
|
|
342
324
|
"displayName": "Agent Creator",
|
|
325
|
+
"iconEmoji": "🧬",
|
|
343
326
|
"required": false,
|
|
344
327
|
"installerAgentCount": 1,
|
|
345
328
|
"categoryAgentCount": 0,
|
|
@@ -374,6 +357,7 @@ const DERIVED = {
|
|
|
374
357
|
"order": 3,
|
|
375
358
|
"name": "Shield",
|
|
376
359
|
"displayName": "Shield — GRC Compliance",
|
|
360
|
+
"iconEmoji": "🛡️",
|
|
377
361
|
"required": false,
|
|
378
362
|
"installerAgentCount": 1,
|
|
379
363
|
"categoryAgentCount": 27,
|
|
@@ -416,6 +400,7 @@ const DERIVED = {
|
|
|
416
400
|
"order": 4,
|
|
417
401
|
"name": "SEO",
|
|
418
402
|
"displayName": "SEO Audit 360",
|
|
403
|
+
"iconEmoji": "🔍",
|
|
419
404
|
"required": false,
|
|
420
405
|
"installerAgentCount": 3,
|
|
421
406
|
"categoryAgentCount": 0,
|
|
@@ -467,6 +452,7 @@ const DERIVED = {
|
|
|
467
452
|
"order": 5,
|
|
468
453
|
"name": "Memory",
|
|
469
454
|
"displayName": "Memory — Persistent Brain",
|
|
455
|
+
"iconEmoji": "🧠",
|
|
470
456
|
"required": false,
|
|
471
457
|
"installerAgentCount": 1,
|
|
472
458
|
"categoryAgentCount": 0,
|
|
@@ -503,6 +489,7 @@ const DERIVED = {
|
|
|
503
489
|
"order": 6,
|
|
504
490
|
"name": "Dev Studio",
|
|
505
491
|
"displayName": "Dev Studio — Full SDLC",
|
|
492
|
+
"iconEmoji": "🏗️",
|
|
506
493
|
"required": false,
|
|
507
494
|
"installerAgentCount": 1,
|
|
508
495
|
"categoryAgentCount": 0,
|
|
@@ -585,6 +572,7 @@ const DERIVED = {
|
|
|
585
572
|
"order": 7,
|
|
586
573
|
"name": "Backup",
|
|
587
574
|
"displayName": "Universal Backup",
|
|
575
|
+
"iconEmoji": "🗂️",
|
|
588
576
|
"required": false,
|
|
589
577
|
"installerAgentCount": 1,
|
|
590
578
|
"categoryAgentCount": 0,
|
|
@@ -619,6 +607,7 @@ const DERIVED = {
|
|
|
619
607
|
"order": 8,
|
|
620
608
|
"name": "Animated",
|
|
621
609
|
"displayName": "Animated Website",
|
|
610
|
+
"iconEmoji": "🎬",
|
|
622
611
|
"required": false,
|
|
623
612
|
"installerAgentCount": 1,
|
|
624
613
|
"categoryAgentCount": 0,
|
|
@@ -33,7 +33,11 @@ function normalizePathForHash(p) {
|
|
|
33
33
|
* @returns {string} 8-character hex hash
|
|
34
34
|
*/
|
|
35
35
|
function projectHash(projectDir) {
|
|
36
|
-
return crypto
|
|
36
|
+
return crypto
|
|
37
|
+
.createHash('sha256')
|
|
38
|
+
.update(normalizePathForHash(projectDir))
|
|
39
|
+
.digest('hex')
|
|
40
|
+
.slice(0, 8);
|
|
37
41
|
}
|
|
38
42
|
|
|
39
43
|
module.exports = { normalizePathForHash, projectHash };
|
|
@@ -156,7 +156,13 @@ function venvPython(envDir, platform = process.platform) {
|
|
|
156
156
|
* @returns {{ create: Array<{cmd:string,args:string[],label:string}>,
|
|
157
157
|
* install: Array<{cmd:string,args:string[],label:string}> }}
|
|
158
158
|
*/
|
|
159
|
-
function buildEnvCommands({
|
|
159
|
+
function buildEnvCommands({
|
|
160
|
+
tool,
|
|
161
|
+
python,
|
|
162
|
+
envDir,
|
|
163
|
+
requirementsPath = null,
|
|
164
|
+
platform = process.platform,
|
|
165
|
+
}) {
|
|
160
166
|
const envPy = venvPython(envDir, platform);
|
|
161
167
|
const create = [];
|
|
162
168
|
const install = [];
|
|
@@ -223,13 +229,21 @@ function missingPythonGuidance(platform = process.platform) {
|
|
|
223
229
|
const min = `${MIN_PYTHON.major}.${MIN_PYTHON.minor}`;
|
|
224
230
|
const lines = [`Python >= ${min} was not found on PATH.`];
|
|
225
231
|
if (platform === 'win32') {
|
|
226
|
-
lines.push(
|
|
232
|
+
lines.push(
|
|
233
|
+
`Install it with: winget install Python.Python.3.12 (or from https://www.python.org/downloads/)`
|
|
234
|
+
);
|
|
227
235
|
} else if (platform === 'darwin') {
|
|
228
|
-
lines.push(
|
|
236
|
+
lines.push(
|
|
237
|
+
`Install it with: brew install python@3.12 (or from https://www.python.org/downloads/)`
|
|
238
|
+
);
|
|
229
239
|
} else {
|
|
230
|
-
lines.push(
|
|
240
|
+
lines.push(
|
|
241
|
+
`Install it with your package manager (e.g. apt install python3.12) or from https://www.python.org/downloads/`
|
|
242
|
+
);
|
|
231
243
|
}
|
|
232
|
-
lines.push(
|
|
244
|
+
lines.push(
|
|
245
|
+
`Alternatively install uv (https://docs.astral.sh/uv/) — BMAD+ will use it to fetch Python ${min} automatically.`
|
|
246
|
+
);
|
|
233
247
|
return lines;
|
|
234
248
|
}
|
|
235
249
|
|
|
@@ -248,7 +262,11 @@ function missingPythonGuidance(platform = process.platform) {
|
|
|
248
262
|
* @returns {{ ok: boolean, tool: 'python'|null, command: {cmd:string,baseArgs:string[]}|null,
|
|
249
263
|
* version: object|null, messages: string[] }}
|
|
250
264
|
*/
|
|
251
|
-
function detectPython({
|
|
265
|
+
function detectPython({
|
|
266
|
+
runner = defaultRunner,
|
|
267
|
+
platform = process.platform,
|
|
268
|
+
minVersion = MIN_PYTHON,
|
|
269
|
+
} = {}) {
|
|
252
270
|
const messages = [];
|
|
253
271
|
let tooOld = null;
|
|
254
272
|
|
|
@@ -259,7 +277,9 @@ function detectPython({ runner = defaultRunner, platform = process.platform, min
|
|
|
259
277
|
const version = parsePythonVersion(`${res.stdout}\n${res.stderr}`);
|
|
260
278
|
if (!version) continue;
|
|
261
279
|
if (meetsMinVersion(version, minVersion)) {
|
|
262
|
-
messages.push(
|
|
280
|
+
messages.push(
|
|
281
|
+
`Found ${version.raw} via \`${[candidate.cmd, ...candidate.baseArgs].join(' ')}\`.`
|
|
282
|
+
);
|
|
263
283
|
return { ok: true, tool: 'python', command: candidate, version, messages };
|
|
264
284
|
}
|
|
265
285
|
tooOld = { candidate, version };
|
|
@@ -370,14 +390,22 @@ function verifyImports({ envDir, modules, runner = defaultRunner, platform = pro
|
|
|
370
390
|
* Verify that the pack's entry script runs inside the env (default: --help).
|
|
371
391
|
* @returns {{ ok: boolean, messages: string[] }}
|
|
372
392
|
*/
|
|
373
|
-
function verifyEntry({
|
|
393
|
+
function verifyEntry({
|
|
394
|
+
envDir,
|
|
395
|
+
entryPath,
|
|
396
|
+
entryArgs = ['--help'],
|
|
397
|
+
runner = defaultRunner,
|
|
398
|
+
platform = process.platform,
|
|
399
|
+
}) {
|
|
374
400
|
if (!entryPath) return { ok: true, messages: [] };
|
|
375
401
|
const envPy = venvPython(envDir, platform);
|
|
376
402
|
const res = runner(envPy, [entryPath, ...entryArgs]);
|
|
377
403
|
if (!ranOk(res)) {
|
|
378
404
|
return {
|
|
379
405
|
ok: false,
|
|
380
|
-
messages: [
|
|
406
|
+
messages: [
|
|
407
|
+
`Entry check failed (${path.basename(entryPath)} ${entryArgs.join(' ')}): ${firstErrorLine(res)}`,
|
|
408
|
+
],
|
|
381
409
|
};
|
|
382
410
|
}
|
|
383
411
|
return { ok: true, messages: [`Verified entry: ${path.basename(entryPath)} runs.`] };
|
|
@@ -418,7 +446,13 @@ function provisionPack({
|
|
|
418
446
|
const messages = [];
|
|
419
447
|
|
|
420
448
|
if (!envDir) {
|
|
421
|
-
return {
|
|
449
|
+
return {
|
|
450
|
+
ok: false,
|
|
451
|
+
tool: null,
|
|
452
|
+
envDir: null,
|
|
453
|
+
python: null,
|
|
454
|
+
messages: ['provisionPack: envDir is required.'],
|
|
455
|
+
};
|
|
422
456
|
}
|
|
423
457
|
|
|
424
458
|
// 1) provisioner first — uv can operate without a system Python.
|
|
@@ -432,7 +466,9 @@ function provisionPack({
|
|
|
432
466
|
return { ok: false, tool: prov.tool, envDir, python: null, messages };
|
|
433
467
|
}
|
|
434
468
|
if (!py.ok && prov.tool === 'uv') {
|
|
435
|
-
messages.push(
|
|
469
|
+
messages.push(
|
|
470
|
+
`No system Python >= ${minVersion.major}.${minVersion.minor} — uv will download a managed interpreter.`
|
|
471
|
+
);
|
|
436
472
|
}
|
|
437
473
|
|
|
438
474
|
// 3) create isolated env
|
|
@@ -482,7 +518,10 @@ function provisionPack({
|
|
|
482
518
|
function firstErrorLine(res) {
|
|
483
519
|
if (!res) return 'no result';
|
|
484
520
|
if (res.error) return res.error;
|
|
485
|
-
const text = `${res.stderr}\n${res.stdout}
|
|
521
|
+
const text = `${res.stderr}\n${res.stdout}`
|
|
522
|
+
.split(/\r?\n/)
|
|
523
|
+
.map((l) => l.trim())
|
|
524
|
+
.filter(Boolean);
|
|
486
525
|
return text.length > 0 ? text[text.length - 1] : `exit code ${res.status}`;
|
|
487
526
|
}
|
|
488
527
|
|
|
@@ -10,24 +10,28 @@ const fs = require('node:fs');
|
|
|
10
10
|
// ── Project Detection Markers (for scan.js) ──
|
|
11
11
|
|
|
12
12
|
const PROJECT_MARKERS = [
|
|
13
|
-
{
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
13
|
+
{
|
|
14
|
+
file: 'package.json',
|
|
15
|
+
stack: 'Node.js',
|
|
16
|
+
detect: (dir) => {
|
|
17
|
+
try {
|
|
18
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(dir, 'package.json'), 'utf8'));
|
|
19
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
20
|
+
if (deps['next']) return 'Next.js';
|
|
21
|
+
if (deps['nuxt']) return 'Nuxt';
|
|
22
|
+
if (deps['react']) return 'React';
|
|
23
|
+
if (deps['vue']) return 'Vue.js';
|
|
24
|
+
if (deps['svelte']) return 'Svelte';
|
|
25
|
+
if (deps['express']) return 'Express';
|
|
26
|
+
if (deps['fastify']) return 'Fastify';
|
|
27
|
+
if (deps['electron']) return 'Electron';
|
|
28
|
+
if (deps['tauri']) return 'Tauri';
|
|
29
|
+
return 'Node.js';
|
|
30
|
+
} catch {
|
|
31
|
+
return 'Node.js';
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
},
|
|
31
35
|
{ file: 'Cargo.toml', stack: 'Rust' },
|
|
32
36
|
{ file: 'pyproject.toml', stack: 'Python' },
|
|
33
37
|
{ file: 'requirements.txt', stack: 'Python' },
|
|
@@ -56,7 +60,10 @@ function detectStack(dir) {
|
|
|
56
60
|
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
|
57
61
|
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
58
62
|
result.runtime = 'Node.js';
|
|
59
|
-
result.language =
|
|
63
|
+
result.language =
|
|
64
|
+
deps['typescript'] || fs.existsSync(path.join(dir, 'tsconfig.json'))
|
|
65
|
+
? 'TypeScript'
|
|
66
|
+
: 'JavaScript';
|
|
60
67
|
result.hasTypeScript = result.language === 'TypeScript';
|
|
61
68
|
|
|
62
69
|
// Framework detection
|
|
@@ -85,12 +92,31 @@ function detectStack(dir) {
|
|
|
85
92
|
|
|
86
93
|
// Other languages
|
|
87
94
|
if (!result.runtime) {
|
|
88
|
-
if (fs.existsSync(path.join(dir, 'Cargo.toml'))) {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
else if (
|
|
92
|
-
|
|
93
|
-
|
|
95
|
+
if (fs.existsSync(path.join(dir, 'Cargo.toml'))) {
|
|
96
|
+
result.language = 'Rust';
|
|
97
|
+
result.runtime = 'Rust';
|
|
98
|
+
} else if (
|
|
99
|
+
fs.existsSync(path.join(dir, 'pyproject.toml')) ||
|
|
100
|
+
fs.existsSync(path.join(dir, 'requirements.txt'))
|
|
101
|
+
) {
|
|
102
|
+
result.language = 'Python';
|
|
103
|
+
result.runtime = 'Python';
|
|
104
|
+
} else if (fs.existsSync(path.join(dir, 'go.mod'))) {
|
|
105
|
+
result.language = 'Go';
|
|
106
|
+
result.runtime = 'Go';
|
|
107
|
+
} else if (fs.existsSync(path.join(dir, 'composer.json'))) {
|
|
108
|
+
result.language = 'PHP';
|
|
109
|
+
result.runtime = 'PHP';
|
|
110
|
+
} else if (fs.existsSync(path.join(dir, 'Gemfile'))) {
|
|
111
|
+
result.language = 'Ruby';
|
|
112
|
+
result.runtime = 'Ruby';
|
|
113
|
+
} else if (
|
|
114
|
+
fs.existsSync(path.join(dir, 'pom.xml')) ||
|
|
115
|
+
fs.existsSync(path.join(dir, 'build.gradle'))
|
|
116
|
+
) {
|
|
117
|
+
result.language = 'Java';
|
|
118
|
+
result.runtime = 'JVM';
|
|
119
|
+
}
|
|
94
120
|
}
|
|
95
121
|
|
|
96
122
|
return result;
|
|
@@ -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 });
|