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
|
@@ -1,282 +0,0 @@
|
|
|
1
|
-
/** Build one npm archive and a checked production SBOM; publish these exact bytes. */
|
|
2
|
-
const fs = require('node:fs');
|
|
3
|
-
const path = require('node:path');
|
|
4
|
-
const crypto = require('node:crypto');
|
|
5
|
-
const { parseArgs } = require('node:util');
|
|
6
|
-
const { runNpm } = require('../cli/lib/npm-runner');
|
|
7
|
-
|
|
8
|
-
const REQUIRED_FILES = ['package.json', 'README.md', 'LICENSE', 'THIRD-PARTY-LICENSES.md'];
|
|
9
|
-
const digest = (bytes, algorithm = 'sha256', encoding = 'hex') =>
|
|
10
|
-
crypto.createHash(algorithm).update(bytes).digest(encoding);
|
|
11
|
-
const readJson = (file) => JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
12
|
-
const requireThat = (condition, message) => {
|
|
13
|
-
if (!condition) throw new Error(message);
|
|
14
|
-
};
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* `npm pack --json` returns a list up to npm 11 and an object keyed by package
|
|
18
|
-
* name from npm 12 on. The release job installs npm@latest, so both shapes reach
|
|
19
|
-
* this code; the identity checks below stay the same for either.
|
|
20
|
-
*/
|
|
21
|
-
function packedEntries(output) {
|
|
22
|
-
const value = JSON.parse(output);
|
|
23
|
-
const entries = Array.isArray(value) ? value : Object.values(value);
|
|
24
|
-
requireThat(
|
|
25
|
-
entries.every(
|
|
26
|
-
(entry) => entry && typeof entry === 'object' && typeof entry.filename === 'string'
|
|
27
|
-
),
|
|
28
|
-
'npm pack did not report packaged archives.'
|
|
29
|
-
);
|
|
30
|
-
return entries;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
function productionGraph(pkg, lock) {
|
|
34
|
-
requireThat(
|
|
35
|
-
lock.lockfileVersion >= 2 && lock.packages?.[''],
|
|
36
|
-
'A modern npm lockfile is required.'
|
|
37
|
-
);
|
|
38
|
-
requireThat(
|
|
39
|
-
lock.name === pkg.name && lock.version === pkg.version,
|
|
40
|
-
'Package/lock identity mismatch.'
|
|
41
|
-
);
|
|
42
|
-
requireThat(
|
|
43
|
-
JSON.stringify(lock.packages[''].dependencies) === JSON.stringify(pkg.dependencies),
|
|
44
|
-
'Package/lock production dependency mismatch.'
|
|
45
|
-
);
|
|
46
|
-
const graph = new Map();
|
|
47
|
-
const pending = [''];
|
|
48
|
-
const visited = new Set();
|
|
49
|
-
const identity = (location) => {
|
|
50
|
-
if (!location) return `${pkg.name}@${pkg.version}`;
|
|
51
|
-
const item = lock.packages[location];
|
|
52
|
-
requireThat(item && !item.link && item.version, `Unsupported lock entry: ${location}`);
|
|
53
|
-
return `${item.name || location.split('node_modules/').pop()}@${item.version}`;
|
|
54
|
-
};
|
|
55
|
-
const resolve = (location, name) => {
|
|
56
|
-
let ancestor = location;
|
|
57
|
-
for (;;) {
|
|
58
|
-
const candidate = `${ancestor ? ancestor + '/' : ''}node_modules/${name}`;
|
|
59
|
-
if (lock.packages[candidate]) return candidate;
|
|
60
|
-
if (!ancestor) return null;
|
|
61
|
-
const index = ancestor.lastIndexOf('/node_modules/');
|
|
62
|
-
ancestor = index < 0 ? '' : ancestor.slice(0, index);
|
|
63
|
-
}
|
|
64
|
-
};
|
|
65
|
-
while (pending.length) {
|
|
66
|
-
const location = pending.shift();
|
|
67
|
-
if (visited.has(location)) continue;
|
|
68
|
-
visited.add(location);
|
|
69
|
-
const item = lock.packages[location];
|
|
70
|
-
const ref = identity(location);
|
|
71
|
-
const edges = graph.get(ref) || new Set();
|
|
72
|
-
graph.set(ref, edges);
|
|
73
|
-
const dependencies = {
|
|
74
|
-
...item.dependencies,
|
|
75
|
-
...item.optionalDependencies,
|
|
76
|
-
...(location ? item.peerDependencies : {}),
|
|
77
|
-
};
|
|
78
|
-
for (const name of Object.keys(dependencies)) {
|
|
79
|
-
const target = resolve(location, name);
|
|
80
|
-
const optional =
|
|
81
|
-
Object.hasOwn(item.optionalDependencies || {}, name) ||
|
|
82
|
-
item.peerDependenciesMeta?.[name]?.optional;
|
|
83
|
-
if (!target && optional) continue;
|
|
84
|
-
requireThat(target, `Missing production dependency ${name} of ${ref}.`);
|
|
85
|
-
edges.add(identity(target));
|
|
86
|
-
pending.push(target);
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
return graph;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
function productionSbom(full, pkg, lock) {
|
|
93
|
-
const graph = productionGraph(pkg, lock);
|
|
94
|
-
const root = `${pkg.name}@${pkg.version}`;
|
|
95
|
-
requireThat(
|
|
96
|
-
full.bomFormat === 'CycloneDX' && full.metadata?.component?.['bom-ref'] === root,
|
|
97
|
-
'SBOM package identity mismatch.'
|
|
98
|
-
);
|
|
99
|
-
requireThat(
|
|
100
|
-
full.metadata.component.purl === `pkg:npm/${pkg.name}@${pkg.version}`,
|
|
101
|
-
'SBOM package URL mismatch.'
|
|
102
|
-
);
|
|
103
|
-
const components = (full.components || []).filter((item) => graph.has(item['bom-ref']));
|
|
104
|
-
const found = new Set(components.map((item) => item['bom-ref']));
|
|
105
|
-
requireThat(
|
|
106
|
-
found.size === graph.size - 1 && found.size === components.length,
|
|
107
|
-
'SBOM production component coverage mismatch.'
|
|
108
|
-
);
|
|
109
|
-
for (const component of components) {
|
|
110
|
-
requireThat(
|
|
111
|
-
component['bom-ref'] === `${component.name}@${component.version}`,
|
|
112
|
-
'SBOM component identity mismatch.'
|
|
113
|
-
);
|
|
114
|
-
}
|
|
115
|
-
const dependencies = (full.dependencies || [])
|
|
116
|
-
.filter((item) => graph.has(item.ref))
|
|
117
|
-
.map((item) => ({
|
|
118
|
-
ref: item.ref,
|
|
119
|
-
dependsOn: (item.dependsOn || []).filter((ref) => graph.has(ref)).sort(),
|
|
120
|
-
}));
|
|
121
|
-
requireThat(
|
|
122
|
-
new Set(dependencies.map((item) => item.ref)).size === graph.size &&
|
|
123
|
-
dependencies.length === graph.size,
|
|
124
|
-
'SBOM dependency graph is incomplete.'
|
|
125
|
-
);
|
|
126
|
-
for (const item of dependencies) {
|
|
127
|
-
requireThat(
|
|
128
|
-
JSON.stringify(item.dependsOn) === JSON.stringify([...graph.get(item.ref)].sort()),
|
|
129
|
-
`SBOM production edges mismatch: ${item.ref}`
|
|
130
|
-
);
|
|
131
|
-
}
|
|
132
|
-
return { ...full, components, dependencies };
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
async function build({ cwd, out, npm = runNpm }) {
|
|
136
|
-
cwd = path.resolve(cwd);
|
|
137
|
-
out = path.resolve(out);
|
|
138
|
-
const relative = path.relative(cwd, out);
|
|
139
|
-
requireThat(
|
|
140
|
-
relative.startsWith('..' + path.sep) || path.isAbsolute(relative),
|
|
141
|
-
'Release artifacts must be outside the package worktree.'
|
|
142
|
-
);
|
|
143
|
-
fs.mkdirSync(out, { recursive: true });
|
|
144
|
-
requireThat(fs.readdirSync(out).length === 0, 'Release output directory must be empty.');
|
|
145
|
-
const pkg = readJson(path.join(cwd, 'package.json'));
|
|
146
|
-
const lock = readJson(path.join(cwd, 'package-lock.json'));
|
|
147
|
-
requireThat(
|
|
148
|
-
pkg.name === 'bmad-plus' && /^\d+\.\d+\.\d+$/.test(pkg.version),
|
|
149
|
-
'A stable bmad-plus package is required.'
|
|
150
|
-
);
|
|
151
|
-
// npm --omit=dev can omit shared production dependencies. Generate the full
|
|
152
|
-
// locked graph, select its production closure, and check every node and edge.
|
|
153
|
-
const full = JSON.parse(
|
|
154
|
-
await npm(['sbom', '--package-lock-only', '--sbom-format=cyclonedx', '--sbom-type=framework'], {
|
|
155
|
-
cwd,
|
|
156
|
-
timeout: 60000,
|
|
157
|
-
maxBuffer: 8 * 1024 * 1024,
|
|
158
|
-
})
|
|
159
|
-
);
|
|
160
|
-
const sbom = productionSbom(full, pkg, lock);
|
|
161
|
-
const packed = packedEntries(
|
|
162
|
-
await npm(['pack', '--json', '--ignore-scripts', '--pack-destination', out], {
|
|
163
|
-
cwd,
|
|
164
|
-
timeout: 60000,
|
|
165
|
-
maxBuffer: 8 * 1024 * 1024,
|
|
166
|
-
})
|
|
167
|
-
);
|
|
168
|
-
const filename = `bmad-plus-${pkg.version}.tgz`;
|
|
169
|
-
requireThat(
|
|
170
|
-
packed.length === 1 &&
|
|
171
|
-
packed[0].filename === filename &&
|
|
172
|
-
packed[0].name === pkg.name &&
|
|
173
|
-
packed[0].version === pkg.version,
|
|
174
|
-
'npm archive identity mismatch.'
|
|
175
|
-
);
|
|
176
|
-
const shipped = new Set(packed[0].files.map((file) => file.path));
|
|
177
|
-
requireThat(
|
|
178
|
-
![...shipped].some(
|
|
179
|
-
(file) => file.split('/').includes('__pycache__') || /\.py[co]$/i.test(file)
|
|
180
|
-
),
|
|
181
|
-
'Interpreter cache files must not be published.'
|
|
182
|
-
);
|
|
183
|
-
requireThat(
|
|
184
|
-
REQUIRED_FILES.every((file) => shipped.has(file)),
|
|
185
|
-
'Required license/package files are missing.'
|
|
186
|
-
);
|
|
187
|
-
const archive = fs.readFileSync(path.join(out, filename));
|
|
188
|
-
const integrity = `sha512-${digest(archive, 'sha512', 'base64')}`;
|
|
189
|
-
requireThat(packed[0].integrity === integrity, 'npm archive integrity mismatch.');
|
|
190
|
-
const sbomBytes = Buffer.from(JSON.stringify(sbom, null, 2) + '\n');
|
|
191
|
-
const manifest = {
|
|
192
|
-
schemaVersion: 1,
|
|
193
|
-
name: pkg.name,
|
|
194
|
-
version: pkg.version,
|
|
195
|
-
archive: { filename, bytes: archive.length, sha256: digest(archive), integrity },
|
|
196
|
-
sbom: {
|
|
197
|
-
filename: 'sbom.cdx.json',
|
|
198
|
-
sha256: digest(sbomBytes),
|
|
199
|
-
components: sbom.components.length,
|
|
200
|
-
},
|
|
201
|
-
packageLockSha256: digest(fs.readFileSync(path.join(cwd, 'package-lock.json'))),
|
|
202
|
-
scope: 'Locked npm production dependencies; bundled source notices are shipped separately.',
|
|
203
|
-
attestation: 'Maintainer-generated inventory and hashes; not an npm provenance attestation.',
|
|
204
|
-
};
|
|
205
|
-
fs.writeFileSync(path.join(out, 'sbom.cdx.json'), sbomBytes, { flag: 'wx' });
|
|
206
|
-
fs.writeFileSync(
|
|
207
|
-
path.join(out, 'release-manifest.json'),
|
|
208
|
-
JSON.stringify(manifest, null, 2) + '\n',
|
|
209
|
-
{ flag: 'wx' }
|
|
210
|
-
);
|
|
211
|
-
return verify({ cwd, out });
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
function verify({ cwd, out }) {
|
|
215
|
-
const pkg = readJson(path.join(cwd, 'package.json'));
|
|
216
|
-
const manifest = readJson(path.join(out, 'release-manifest.json'));
|
|
217
|
-
requireThat(
|
|
218
|
-
manifest.schemaVersion === 1 &&
|
|
219
|
-
manifest.name === pkg.name &&
|
|
220
|
-
manifest.version === pkg.version &&
|
|
221
|
-
manifest.archive?.filename === `bmad-plus-${pkg.version}.tgz` &&
|
|
222
|
-
manifest.sbom?.filename === 'sbom.cdx.json',
|
|
223
|
-
'Release manifest identity mismatch.'
|
|
224
|
-
);
|
|
225
|
-
const archive = fs.readFileSync(path.join(out, manifest.archive.filename));
|
|
226
|
-
requireThat(
|
|
227
|
-
archive.length === manifest.archive.bytes &&
|
|
228
|
-
digest(archive) === manifest.archive.sha256 &&
|
|
229
|
-
`sha512-${digest(archive, 'sha512', 'base64')}` === manifest.archive.integrity,
|
|
230
|
-
'Release archive was changed.'
|
|
231
|
-
);
|
|
232
|
-
const sbomBytes = fs.readFileSync(path.join(out, 'sbom.cdx.json'));
|
|
233
|
-
requireThat(digest(sbomBytes) === manifest.sbom.sha256, 'Release SBOM was changed.');
|
|
234
|
-
const lockBytes = fs.readFileSync(path.join(cwd, 'package-lock.json'));
|
|
235
|
-
requireThat(digest(lockBytes) === manifest.packageLockSha256, 'Release lockfile was changed.');
|
|
236
|
-
const sbom = productionSbom(JSON.parse(sbomBytes), pkg, JSON.parse(lockBytes));
|
|
237
|
-
requireThat(sbom.components.length === manifest.sbom.components, 'Release SBOM count mismatch.');
|
|
238
|
-
return manifest;
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
async function verifyRegistry({ cwd, out, npm = runNpm }) {
|
|
242
|
-
const manifest = verify({ cwd, out });
|
|
243
|
-
const integrity = JSON.parse(
|
|
244
|
-
await npm(
|
|
245
|
-
[
|
|
246
|
-
'view',
|
|
247
|
-
`bmad-plus@${manifest.version}`,
|
|
248
|
-
'dist.integrity',
|
|
249
|
-
'--json',
|
|
250
|
-
'--prefer-online',
|
|
251
|
-
'--registry=https://registry.npmjs.org',
|
|
252
|
-
],
|
|
253
|
-
{ cwd, timeout: 20000 }
|
|
254
|
-
)
|
|
255
|
-
);
|
|
256
|
-
requireThat(
|
|
257
|
-
integrity === manifest.archive.integrity,
|
|
258
|
-
'Published npm integrity does not match the checked release archive.'
|
|
259
|
-
);
|
|
260
|
-
return manifest;
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
if (require.main === module) {
|
|
264
|
-
(async () => {
|
|
265
|
-
const { positionals, values } = parseArgs({
|
|
266
|
-
allowPositionals: true,
|
|
267
|
-
options: { out: { type: 'string' } },
|
|
268
|
-
});
|
|
269
|
-
const commands = { build, verify, 'verify-registry': verifyRegistry };
|
|
270
|
-
requireThat(
|
|
271
|
-
positionals.length === 1 && commands[positionals[0]] && values.out,
|
|
272
|
-
'Usage: node tools/release/supply-chain.js build|verify|verify-registry --out DIRECTORY'
|
|
273
|
-
);
|
|
274
|
-
const result = await commands[positionals[0]]({ cwd: process.cwd(), out: values.out });
|
|
275
|
-
process.stdout.write(JSON.stringify(result, null, 2) + '\n');
|
|
276
|
-
})().catch((error) => {
|
|
277
|
-
process.stderr.write(error.message + '\n');
|
|
278
|
-
process.exitCode = 1;
|
|
279
|
-
});
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
module.exports = { productionGraph, productionSbom, build, verify, verifyRegistry };
|