ma-agents 3.5.3 → 3.5.5
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/_bmad-output/implementation-artifacts/21-1-install-time-profile-prompt.md +181 -0
- package/_bmad-output/implementation-artifacts/21-10-profile-reconfigure.md +137 -0
- package/_bmad-output/implementation-artifacts/21-11-profile-uninstall.md +149 -0
- package/_bmad-output/implementation-artifacts/21-2-universal-instruction-block-expansion.md +98 -0
- package/_bmad-output/implementation-artifacts/21-3-roomodes-template-bmad-modes.md +106 -0
- package/_bmad-output/implementation-artifacts/21-4-agents-md-template-opencode.md +86 -0
- package/_bmad-output/implementation-artifacts/21-5-clinerules-template-extension.md +82 -0
- package/_bmad-output/implementation-artifacts/21-6-onprem-layered-guardrails.md +112 -0
- package/_bmad-output/implementation-artifacts/21-7-bmad-persona-phase-prefix.md +126 -0
- package/_bmad-output/implementation-artifacts/21-8-vllm-reference-doc-readme.md +100 -0
- package/_bmad-output/implementation-artifacts/21-9-tests-validation.md +97 -0
- package/_bmad-output/implementation-artifacts/bug-experimentalwarning-about-commonjs-loading-es-module-during-install.md +57 -0
- package/_bmad-output/implementation-artifacts/sprint-status.yaml +43 -1
- package/_bmad-output/methodology/BMAD_AI_Development_Training.pptx +0 -0
- package/_bmad-output/methodology/version.json +1 -1
- package/_bmad-output/planning-artifacts/architecture.md +52 -0
- package/_bmad-output/planning-artifacts/epics.md +397 -0
- package/_bmad-output/planning-artifacts/prd.md +46 -1
- package/bin/cli.js +109 -1
- package/docs/BMAD_AI_Development_Training.pptx +0 -0
- package/lib/bmad-cache/bmb/_git_preserved/index +0 -0
- package/lib/bmad-cache/bmb/_git_preserved/logs/HEAD +1 -1
- package/lib/bmad-cache/bmb/_git_preserved/logs/refs/heads/main +1 -1
- package/lib/bmad-cache/bmb/_git_preserved/logs/refs/remotes/origin/HEAD +1 -1
- package/lib/bmad-cache/bmb/_git_preserved/objects/pack/pack-554778ad4e7254827618ebd2497c3f4bce9054a4.idx +0 -0
- package/lib/bmad-cache/bmb/_git_preserved/objects/pack/{pack-4b395d030ca386fc5748f1b670dcf8c0ef41c94c.pack → pack-554778ad4e7254827618ebd2497c3f4bce9054a4.pack} +0 -0
- package/lib/bmad-cache/bmb/_git_preserved/objects/pack/pack-554778ad4e7254827618ebd2497c3f4bce9054a4.rev +0 -0
- package/lib/bmad-cache/bmb/_git_preserved/packed-refs +1 -1
- package/lib/bmad-cache/bmb/_git_preserved/refs/heads/main +1 -1
- package/lib/bmad-cache/bmb/_git_preserved/shallow +1 -1
- package/lib/bmad-cache/bmb/skills/bmad-module-builder/scripts/tests/test-scaffold-setup-skill.py +7 -0
- package/lib/bmad-cache/cache-manifest.json +5 -5
- package/lib/bmad-cache/tea/_git_preserved/index +0 -0
- package/lib/bmad-cache/tea/_git_preserved/objects/pack/pack-e75385cd52b693dbb8a3b2afb50058952543b3a2.idx +0 -0
- package/lib/bmad-cache/tea/_git_preserved/objects/pack/{pack-c79805bb3fee27fa6d8c612a971af7fc86fc80e1.pack → pack-e75385cd52b693dbb8a3b2afb50058952543b3a2.pack} +0 -0
- package/lib/bmad-cache/tea/_git_preserved/objects/pack/pack-e75385cd52b693dbb8a3b2afb50058952543b3a2.rev +0 -0
- package/lib/bmad-cache/tea/_git_preserved/packed-refs +1 -1
- package/lib/bmad-cache/tea/_git_preserved/refs/heads/main +1 -1
- package/lib/bmad-cache/tea/_git_preserved/refs/tags/v1.10.0 +1 -0
- package/lib/bmad-cache/tea/_git_preserved/shallow +1 -1
- package/lib/bmad-cache/tea/docs/explanation/tea-overview.md +2 -2
- package/lib/bmad-cache/tea/docs/how-to/workflows/run-atdd.md +28 -30
- package/lib/bmad-cache/tea/docs/reference/commands.md +4 -4
- package/lib/bmad-cache/tea/docs/reference/configuration.md +1 -1
- package/lib/bmad-cache/tea/package-lock.json +2 -2
- package/lib/bmad-cache/tea/package.json +1 -1
- package/lib/bmad-cache/tea/src/module-help.csv +1 -1
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/SKILL.md +1 -1
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/atdd-checklist-template.md +50 -27
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/checklist.md +18 -17
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/instructions.md +1 -1
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/steps-c/step-01-preflight-and-context.md +21 -3
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/steps-c/step-01b-resume.md +1 -1
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/steps-c/step-02-generation-mode.md +1 -1
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/steps-c/step-03-test-strategy.md +1 -1
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/steps-c/step-04-generate-tests.md +20 -19
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/steps-c/step-04a-subagent-api-failing.md +13 -13
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/steps-c/step-04b-subagent-e2e-failing.md +13 -13
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/steps-c/step-04c-aggregate.md +42 -18
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/steps-c/step-05-validate-and-complete.md +12 -3
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/workflow-plan.md +2 -2
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/workflow.md +2 -2
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/workflow.yaml +2 -2
- package/lib/bmad.js +25 -4
- package/lib/installer.js +2 -1
- package/lib/methodology/BMAD_AI_Development_Training.pptx +0 -0
- package/lib/methodology/version.json +1 -1
- package/lib/profile.js +107 -0
- package/lib/warning-filter.js +245 -0
- package/package.json +2 -2
- package/test/experimental-warning.test.js +314 -0
- package/test/fixtures/README.md +74 -0
- package/test/fixtures/empty-project/README.md +5 -0
- package/test/fixtures/empty-project/package.json +5 -0
- package/test/fixtures/onprem-profile-baseline/.gitkeep +2 -0
- package/test/fixtures/standard-profile-baseline/.gitkeep +2 -0
- package/test/onprem-injection.test.js +48 -0
- package/test/profile.test.js +301 -0
- package/lib/bmad-cache/bmb/_git_preserved/objects/pack/pack-4b395d030ca386fc5748f1b670dcf8c0ef41c94c.idx +0 -0
- package/lib/bmad-cache/bmb/_git_preserved/objects/pack/pack-4b395d030ca386fc5748f1b670dcf8c0ef41c94c.rev +0 -0
- package/lib/bmad-cache/tea/_git_preserved/objects/pack/pack-c79805bb3fee27fa6d8c612a971af7fc86fc80e1.idx +0 -0
- package/lib/bmad-cache/tea/_git_preserved/objects/pack/pack-c79805bb3fee27fa6d8c612a971af7fc86fc80e1.rev +0 -0
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/warning-filter.js
|
|
3
|
+
*
|
|
4
|
+
* Suppresses the Node.js `ExperimentalWarning` that fires when a CommonJS
|
|
5
|
+
* module loads an ES Module via `require()`.
|
|
6
|
+
*
|
|
7
|
+
* Why this exists
|
|
8
|
+
* ---------------
|
|
9
|
+
* The ma-agents CLI (`bin/cli.js`) is CommonJS. It transitively loads
|
|
10
|
+
* code paths from `bmad-method`, which itself interacts with
|
|
11
|
+
* `@clack/prompts` / `@clack/core` (both `"type": "module"`).
|
|
12
|
+
* On Node runtimes where `require()` of ESM is permitted but still
|
|
13
|
+
* emits `ExperimentalWarning` (notably Node 20.17+ through early 22.x),
|
|
14
|
+
* every installer run prints a noisy warning before the banner.
|
|
15
|
+
*
|
|
16
|
+
* The warning is cosmetic — the installer completes successfully — but
|
|
17
|
+
* it frightens first-time users and pollutes CI logs.
|
|
18
|
+
*
|
|
19
|
+
* Why a `process.on('warning', ...)` listener alone is NOT enough
|
|
20
|
+
* ----------------------------------------------------------------
|
|
21
|
+
* In Node.js 20+, `ExperimentalWarning` is printed through an internal
|
|
22
|
+
* path that does not honor user-land `'warning'` listeners for
|
|
23
|
+
* suppression purposes — the listener is invoked, but Node ALSO prints
|
|
24
|
+
* the default formatted message to stderr. The only reliable way to
|
|
25
|
+
* silence it is a CLI flag at process start:
|
|
26
|
+
* NODE_OPTIONS="--no-warnings=ExperimentalWarning" (Node 16+)
|
|
27
|
+
*
|
|
28
|
+
* Strategy
|
|
29
|
+
* --------
|
|
30
|
+
* On first entry, we check whether the current process was started with
|
|
31
|
+
* the suppression flag. If not, we re-exec ourselves in a child
|
|
32
|
+
* process with `NODE_OPTIONS` augmented to include the flag, inherit
|
|
33
|
+
* its stdio, and exit with the child's status. A sentinel env var
|
|
34
|
+
* `MA_AGENTS_WARNINGS_FILTERED=1` prevents infinite re-spawn loops.
|
|
35
|
+
*
|
|
36
|
+
* We additionally attach a `process.on('warning')` listener that
|
|
37
|
+
* silently swallows the specific CJS-require-ESM ExperimentalWarning
|
|
38
|
+
* (if it somehow reaches user-land despite the flag) while re-emitting
|
|
39
|
+
* every other warning. This is belt-and-braces for Node versions where
|
|
40
|
+
* listener semantics differ.
|
|
41
|
+
*
|
|
42
|
+
* For the bmad-method child process spawn (separate Node invocation),
|
|
43
|
+
* `lib/bmad.js` extends `NODE_OPTIONS` with the same flag via
|
|
44
|
+
* `withExperimentalWarningSuppressed()` so the child is equally quiet.
|
|
45
|
+
*
|
|
46
|
+
* This module MUST stay CommonJS and MUST NOT import any ESM package.
|
|
47
|
+
*
|
|
48
|
+
* @module lib/warning-filter
|
|
49
|
+
*/
|
|
50
|
+
|
|
51
|
+
'use strict';
|
|
52
|
+
|
|
53
|
+
const SENTINEL_ENV = 'MA_AGENTS_WARNINGS_FILTERED';
|
|
54
|
+
const SUPPRESS_FLAG = '--no-warnings=ExperimentalWarning';
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Default Node.js warning formatter (mirrors the built-in output
|
|
58
|
+
* used when no `warning` listener is registered). Kept in sync with
|
|
59
|
+
* Node's internal `process/warning.js` formatting circa Node 20–22.
|
|
60
|
+
*
|
|
61
|
+
* @param {Error} warning
|
|
62
|
+
* @returns {string}
|
|
63
|
+
*/
|
|
64
|
+
function formatWarning(warning) {
|
|
65
|
+
const name = warning && warning.name ? warning.name : 'Warning';
|
|
66
|
+
const code = warning && warning.code ? ` [${warning.code}]` : '';
|
|
67
|
+
const message = warning && warning.message ? warning.message : String(warning);
|
|
68
|
+
const detail = warning && warning.detail ? `\n${warning.detail}` : '';
|
|
69
|
+
const stack = warning && warning.stack ? warning.stack.split('\n').slice(1).join('\n') : '';
|
|
70
|
+
const header = `(node:${process.pid}) ${name}${code}: ${message}`;
|
|
71
|
+
const body = stack ? `${header}\n${stack}` : header;
|
|
72
|
+
return detail ? `${body}${detail}\n` : `${body}\n`;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Classify a warning as the specific "CommonJS require() loading an ES Module"
|
|
77
|
+
* ExperimentalWarning we want to suppress.
|
|
78
|
+
*
|
|
79
|
+
* Matches two Node phrasings observed in v20.17+ / v22.x:
|
|
80
|
+
* 1. "CommonJS module <X> is loading ES Module <Y> using require()"
|
|
81
|
+
* 2. "Support for loading ES Module in require() is an experimental feature"
|
|
82
|
+
*
|
|
83
|
+
* Only matches when `warning.name === 'ExperimentalWarning'` — never
|
|
84
|
+
* silences a DeprecationWarning, custom warning, or anything else.
|
|
85
|
+
*
|
|
86
|
+
* @param {Error|{name?: string, message?: string}} warning
|
|
87
|
+
* @returns {boolean}
|
|
88
|
+
*/
|
|
89
|
+
function isRequireEsmWarning(warning) {
|
|
90
|
+
if (!warning || warning.name !== 'ExperimentalWarning') {
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
const msg = String(warning.message || '');
|
|
94
|
+
if (/CommonJS module\b.*\bloading ES Module\b.*\brequire\(\)/i.test(msg)) {
|
|
95
|
+
return true;
|
|
96
|
+
}
|
|
97
|
+
if (/Support for loading ES Module in require\(\)/i.test(msg)) {
|
|
98
|
+
return true;
|
|
99
|
+
}
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Build the NODE_OPTIONS value that should be passed to a child Node
|
|
105
|
+
* process (such as bmad-method) to silence the ExperimentalWarning.
|
|
106
|
+
* Preserves any existing NODE_OPTIONS the caller already set.
|
|
107
|
+
*
|
|
108
|
+
* @param {string|undefined} currentNodeOptions - value of NODE_OPTIONS in the parent env
|
|
109
|
+
* @returns {string} new NODE_OPTIONS value (may equal input if flag already present)
|
|
110
|
+
*/
|
|
111
|
+
function withExperimentalWarningSuppressed(currentNodeOptions) {
|
|
112
|
+
const current = (currentNodeOptions || '').trim();
|
|
113
|
+
if (!current) {
|
|
114
|
+
return SUPPRESS_FLAG;
|
|
115
|
+
}
|
|
116
|
+
// If any form of --no-warnings is already present, keep as-is.
|
|
117
|
+
if (/(^|\s)--no-warnings(\s|=|$)/.test(current)) {
|
|
118
|
+
return current;
|
|
119
|
+
}
|
|
120
|
+
return `${current} ${SUPPRESS_FLAG}`;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Returns true if the current Node process was started with a command-line
|
|
125
|
+
* option that already suppresses the target warning. Inspects
|
|
126
|
+
* `process.execArgv` (flags passed directly to node) and the environment's
|
|
127
|
+
* `NODE_OPTIONS` (flags Node consumed from env at startup).
|
|
128
|
+
*
|
|
129
|
+
* @returns {boolean}
|
|
130
|
+
*/
|
|
131
|
+
function isWarningAlreadySuppressed() {
|
|
132
|
+
const execArgv = process.execArgv || [];
|
|
133
|
+
for (const a of execArgv) {
|
|
134
|
+
if (a === '--no-warnings' || a.startsWith('--no-warnings=')) return true;
|
|
135
|
+
if (a.startsWith('--disable-warning=')) return true;
|
|
136
|
+
}
|
|
137
|
+
const opts = (process.env.NODE_OPTIONS || '').trim();
|
|
138
|
+
if (/(^|\s)--no-warnings(\s|=|$)/.test(opts)) return true;
|
|
139
|
+
if (/(^|\s)--disable-warning=/.test(opts)) return true;
|
|
140
|
+
return false;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Attach the user-land warning listener (belt-and-braces). Idempotent —
|
|
145
|
+
* tags its listener with `__maAgentsFilter` so repeated calls only
|
|
146
|
+
* register one.
|
|
147
|
+
*
|
|
148
|
+
* @returns {boolean} true if newly attached, false if already attached.
|
|
149
|
+
*/
|
|
150
|
+
function attachListener() {
|
|
151
|
+
const existing = process.listeners('warning').find(fn => fn.__maAgentsFilter === true);
|
|
152
|
+
if (existing) return false;
|
|
153
|
+
|
|
154
|
+
function filter(warning) {
|
|
155
|
+
if (isRequireEsmWarning(warning)) {
|
|
156
|
+
return; // swallow — cosmetic warning we fix
|
|
157
|
+
}
|
|
158
|
+
try {
|
|
159
|
+
process.stderr.write(formatWarning(warning));
|
|
160
|
+
} catch {
|
|
161
|
+
// never throw from a warning listener
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
filter.__maAgentsFilter = true;
|
|
165
|
+
process.on('warning', filter);
|
|
166
|
+
return true;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Install the warning filter. On first call from a non-suppressed
|
|
171
|
+
* process, re-execs the current script with
|
|
172
|
+
* `NODE_OPTIONS="--no-warnings=ExperimentalWarning"` and a sentinel env
|
|
173
|
+
* var to prevent re-spawn loops, then exits with the child's status.
|
|
174
|
+
*
|
|
175
|
+
* On subsequent calls — or when the process is already suppressed —
|
|
176
|
+
* it simply attaches the user-land listener and returns.
|
|
177
|
+
*
|
|
178
|
+
* Safe to call multiple times (idempotent).
|
|
179
|
+
*
|
|
180
|
+
* @param {Object} [opts]
|
|
181
|
+
* @param {boolean} [opts.reexec=true] - set to false to skip re-exec
|
|
182
|
+
* (used by tests that want to verify listener behaviour only).
|
|
183
|
+
* @returns {boolean} true if the filter was newly installed, false if
|
|
184
|
+
* a previous install was already active.
|
|
185
|
+
*/
|
|
186
|
+
function installExperimentalWarningFilter(opts = {}) {
|
|
187
|
+
const reexec = opts.reexec !== false;
|
|
188
|
+
|
|
189
|
+
// If already re-execed, or already suppressed via flags, just attach
|
|
190
|
+
// the listener and move on.
|
|
191
|
+
if (process.env[SENTINEL_ENV] === '1' || isWarningAlreadySuppressed()) {
|
|
192
|
+
return attachListener();
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (!reexec) {
|
|
196
|
+
return attachListener();
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Re-exec self with the suppression flag in NODE_OPTIONS.
|
|
200
|
+
// We use spawnSync to wait for the child synchronously and then
|
|
201
|
+
// propagate its exit code.
|
|
202
|
+
const { spawnSync } = require('child_process');
|
|
203
|
+
const newEnv = {
|
|
204
|
+
...process.env,
|
|
205
|
+
[SENTINEL_ENV]: '1',
|
|
206
|
+
NODE_OPTIONS: withExperimentalWarningSuppressed(process.env.NODE_OPTIONS),
|
|
207
|
+
};
|
|
208
|
+
// Forward most execArgv flags to the child, but drop any --inspect*
|
|
209
|
+
// variants — the parent has already bound the debugger port, so the
|
|
210
|
+
// child would fail with EADDRINUSE. The debugger is attached to the
|
|
211
|
+
// parent only; since the parent exits immediately after the child,
|
|
212
|
+
// this is acceptable.
|
|
213
|
+
const forwardedExecArgv = (process.execArgv || []).filter(a =>
|
|
214
|
+
!/^--inspect(-brk|-port)?(=|$)/.test(a)
|
|
215
|
+
);
|
|
216
|
+
const args = [...forwardedExecArgv, ...(process.argv.slice(1) || [])];
|
|
217
|
+
const child = spawnSync(process.execPath, args, {
|
|
218
|
+
stdio: 'inherit',
|
|
219
|
+
env: newEnv,
|
|
220
|
+
});
|
|
221
|
+
// If spawn failed (e.g., EACCES), fall back to listener-only mode.
|
|
222
|
+
if (child.error) {
|
|
223
|
+
return attachListener();
|
|
224
|
+
}
|
|
225
|
+
// Propagate signal or exit code. Node's process.exit is the last
|
|
226
|
+
// thing we do on this path.
|
|
227
|
+
if (child.signal) {
|
|
228
|
+
process.kill(process.pid, child.signal);
|
|
229
|
+
// In case the signal was ignored:
|
|
230
|
+
process.exit(1);
|
|
231
|
+
}
|
|
232
|
+
process.exit(typeof child.status === 'number' ? child.status : 0);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
module.exports = {
|
|
236
|
+
installExperimentalWarningFilter,
|
|
237
|
+
isRequireEsmWarning,
|
|
238
|
+
withExperimentalWarningSuppressed,
|
|
239
|
+
isWarningAlreadySuppressed,
|
|
240
|
+
// exposed for tests only
|
|
241
|
+
_formatWarning: formatWarning,
|
|
242
|
+
_attachListener: attachListener,
|
|
243
|
+
_SENTINEL_ENV: SENTINEL_ENV,
|
|
244
|
+
_SUPPRESS_FLAG: SUPPRESS_FLAG,
|
|
245
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ma-agents",
|
|
3
|
-
"version": "3.5.
|
|
3
|
+
"version": "3.5.5",
|
|
4
4
|
"description": "NPX tool to install skills for AI coding agents (Claude Code, Gemini, Copilot, Kilocode, Cline, Cursor, Roo Code)",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
},
|
|
9
9
|
"scripts": {
|
|
10
10
|
"start": "node bin/cli.js",
|
|
11
|
-
"test": "node test/yes-flag.test.js && node test/skill-authoring.test.js && node test/skill-validation.test.js && node test/skill-mandatory.test.js && node test/skill-customize-agent.test.js && node test/create-agent.test.js && node test/generate-project-context.test.js && node test/build-bmad-args.test.js && node test/bmad-version-bump.test.js && node test/extension-module-restructure.test.js && node test/convert-agents-to-skills.test.js && node test/migration.test.js && node test/migration-validation.test.js && node test/story-15-5-workflow-skills.test.js && node test/repo-layout.test.js && node test/config-storage.test.js && node test/cross-repo-validation.test.js && node test/config-lost-on-update.test.js && node test/portable-paths.test.js && node test/cicd-remote-mode.test.js && node test/config-layout.test.js && node test/roo-code-agent.test.js && node test/roo-code-injection.test.js",
|
|
11
|
+
"test": "node test/yes-flag.test.js && node test/skill-authoring.test.js && node test/skill-validation.test.js && node test/skill-mandatory.test.js && node test/skill-customize-agent.test.js && node test/create-agent.test.js && node test/generate-project-context.test.js && node test/build-bmad-args.test.js && node test/bmad-version-bump.test.js && node test/extension-module-restructure.test.js && node test/convert-agents-to-skills.test.js && node test/migration.test.js && node test/migration-validation.test.js && node test/story-15-5-workflow-skills.test.js && node test/repo-layout.test.js && node test/config-storage.test.js && node test/cross-repo-validation.test.js && node test/config-lost-on-update.test.js && node test/portable-paths.test.js && node test/cicd-remote-mode.test.js && node test/config-layout.test.js && node test/roo-code-agent.test.js && node test/roo-code-injection.test.js && node test/profile.test.js && node test/onprem-injection.test.js && node test/experimental-warning.test.js",
|
|
12
12
|
"build:bmad-cache": "node scripts/build-bmad-cache.js"
|
|
13
13
|
},
|
|
14
14
|
"keywords": [
|
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Tests for Bug A: ExperimentalWarning about CommonJS loading ES Module
|
|
4
|
+
* during installer startup.
|
|
5
|
+
*
|
|
6
|
+
* Contract:
|
|
7
|
+
* lib/warning-filter.js exports installExperimentalWarningFilter().
|
|
8
|
+
* When installed, the process-level 'warning' listener swallows
|
|
9
|
+
* ExperimentalWarning messages that describe CJS require() of an
|
|
10
|
+
* ES Module, but lets every other warning through unchanged.
|
|
11
|
+
*
|
|
12
|
+
* Additionally, bin/cli.js must install the filter at startup, and
|
|
13
|
+
* lib/bmad.js must pass NODE_OPTIONS="--no-warnings=ExperimentalWarning"
|
|
14
|
+
* (or equivalent) to the bmad-method child spawn env.
|
|
15
|
+
*/
|
|
16
|
+
'use strict';
|
|
17
|
+
|
|
18
|
+
const assert = require('assert');
|
|
19
|
+
const { spawnSync } = require('child_process');
|
|
20
|
+
const path = require('path');
|
|
21
|
+
const fs = require('fs');
|
|
22
|
+
|
|
23
|
+
const REPO_ROOT = path.join(__dirname, '..');
|
|
24
|
+
const FILTER_PATH = path.join(REPO_ROOT, 'lib', 'warning-filter.js');
|
|
25
|
+
const CLI_PATH = path.join(REPO_ROOT, 'bin', 'cli.js');
|
|
26
|
+
const BMAD_PATH = path.join(REPO_ROOT, 'lib', 'bmad.js');
|
|
27
|
+
|
|
28
|
+
let passed = 0;
|
|
29
|
+
let failed = 0;
|
|
30
|
+
|
|
31
|
+
function test(name, fn) {
|
|
32
|
+
try {
|
|
33
|
+
fn();
|
|
34
|
+
console.log(` ✓ ${name}`);
|
|
35
|
+
passed++;
|
|
36
|
+
} catch (err) {
|
|
37
|
+
console.error(` ✗ ${name}: ${err.message}`);
|
|
38
|
+
if (err.stack) console.error(err.stack.split('\n').slice(1, 4).join('\n'));
|
|
39
|
+
failed++;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
console.log('\n── Bug A: ExperimentalWarning filter tests ──────────────────────\n');
|
|
44
|
+
|
|
45
|
+
// ─── Test 1: filter module exists and exports the expected API ────────────────
|
|
46
|
+
test('lib/warning-filter.js exists', () => {
|
|
47
|
+
assert.ok(fs.existsSync(FILTER_PATH), 'expected lib/warning-filter.js to exist');
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test('filter module exports installExperimentalWarningFilter()', () => {
|
|
51
|
+
const mod = require(FILTER_PATH);
|
|
52
|
+
assert.strictEqual(typeof mod.installExperimentalWarningFilter, 'function');
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test('filter module exports isRequireEsmWarning() predicate', () => {
|
|
56
|
+
const mod = require(FILTER_PATH);
|
|
57
|
+
assert.strictEqual(typeof mod.isRequireEsmWarning, 'function');
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
// ─── Test 2: isRequireEsmWarning() correctly classifies warnings ──────────────
|
|
61
|
+
test('isRequireEsmWarning matches CJS-loading-ESM ExperimentalWarning', () => {
|
|
62
|
+
const { isRequireEsmWarning } = require(FILTER_PATH);
|
|
63
|
+
const w = new Error(
|
|
64
|
+
'CommonJS module /a/b.js is loading ES Module /c/d.js using require(). ' +
|
|
65
|
+
'Support for loading ES Module in require() is an experimental feature ' +
|
|
66
|
+
'and might change at any time.'
|
|
67
|
+
);
|
|
68
|
+
w.name = 'ExperimentalWarning';
|
|
69
|
+
assert.strictEqual(isRequireEsmWarning(w), true);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
test('isRequireEsmWarning matches require(ESM) variant phrasing', () => {
|
|
73
|
+
const { isRequireEsmWarning } = require(FILTER_PATH);
|
|
74
|
+
const w = new Error('Support for loading ES Module in require() is an experimental feature.');
|
|
75
|
+
w.name = 'ExperimentalWarning';
|
|
76
|
+
assert.strictEqual(isRequireEsmWarning(w), true);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
test('isRequireEsmWarning ignores unrelated ExperimentalWarning', () => {
|
|
80
|
+
const { isRequireEsmWarning } = require(FILTER_PATH);
|
|
81
|
+
const w = new Error('Fetch API is an experimental feature.');
|
|
82
|
+
w.name = 'ExperimentalWarning';
|
|
83
|
+
assert.strictEqual(isRequireEsmWarning(w), false);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
test('isRequireEsmWarning ignores DeprecationWarning with require() text', () => {
|
|
87
|
+
const { isRequireEsmWarning } = require(FILTER_PATH);
|
|
88
|
+
const w = new Error('Some deprecated require() usage in ES Module.');
|
|
89
|
+
w.name = 'DeprecationWarning';
|
|
90
|
+
assert.strictEqual(isRequireEsmWarning(w), false);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
// ─── Test 3: end-to-end — re-exec flow silences ExperimentalWarning ──────────
|
|
94
|
+
test('installed filter (with re-exec) silences CJS-require-ESM ExperimentalWarning', () => {
|
|
95
|
+
// Drive the full re-exec path: parent re-execs with NODE_OPTIONS set,
|
|
96
|
+
// child emits the warning, expects ZERO bytes of ExperimentalWarning.
|
|
97
|
+
const child = spawnSync(process.execPath, [
|
|
98
|
+
'-e',
|
|
99
|
+
`const { installExperimentalWarningFilter } = require(${JSON.stringify(FILTER_PATH)});
|
|
100
|
+
installExperimentalWarningFilter();
|
|
101
|
+
// After re-exec we land here in the child:
|
|
102
|
+
process.emitWarning('CommonJS module /a.js is loading ES Module /b.mjs using require().', 'ExperimentalWarning');`,
|
|
103
|
+
], { encoding: 'utf8' });
|
|
104
|
+
assert.strictEqual(child.status, 0, `child exited non-zero: ${child.stderr}`);
|
|
105
|
+
assert.ok(
|
|
106
|
+
!/ExperimentalWarning/.test(child.stderr),
|
|
107
|
+
`expected no ExperimentalWarning in stderr, got:\n${child.stderr}`
|
|
108
|
+
);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
// ─── Test 3b: NODE_OPTIONS-only test bypasses re-exec ─────────────────────────
|
|
112
|
+
test('listener-only mode swallows the matched warning at user-land', () => {
|
|
113
|
+
// reexec:false means we only attach the listener; target warning still
|
|
114
|
+
// hits Node's internal printer in some Node versions, but our listener
|
|
115
|
+
// returns silently so the listener path itself never re-emits it.
|
|
116
|
+
const child = spawnSync(process.execPath, [
|
|
117
|
+
'-e',
|
|
118
|
+
`const { installExperimentalWarningFilter, _attachListener } = require(${JSON.stringify(FILTER_PATH)});
|
|
119
|
+
installExperimentalWarningFilter({ reexec: false });
|
|
120
|
+
// Emit a custom-named warning identical in shape so we don't depend
|
|
121
|
+
// on Node's internal experimental-warning path:
|
|
122
|
+
const w = new Error('CommonJS module /a.js is loading ES Module /b.mjs using require().');
|
|
123
|
+
w.name = 'ExperimentalWarning';
|
|
124
|
+
// Drive the listener directly to assert it returns without writing.
|
|
125
|
+
const listener = process.listeners('warning').find(fn => fn.__maAgentsFilter);
|
|
126
|
+
if (!listener) { console.error('no filter listener'); process.exit(2); }
|
|
127
|
+
// capture stderr writes
|
|
128
|
+
let captured = '';
|
|
129
|
+
const orig = process.stderr.write.bind(process.stderr);
|
|
130
|
+
process.stderr.write = (c) => { captured += String(c); return true; };
|
|
131
|
+
listener(w);
|
|
132
|
+
process.stderr.write = orig;
|
|
133
|
+
if (captured.length !== 0) { console.error('LEAKED:', captured); process.exit(3); }
|
|
134
|
+
process.exit(0);`,
|
|
135
|
+
], { encoding: 'utf8', env: { ...process.env, [require(FILTER_PATH)._SENTINEL_ENV]: '1' } });
|
|
136
|
+
assert.strictEqual(child.status, 0, `child exited ${child.status}: ${child.stderr}`);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
// ─── Test 4: filter preserves unrelated warnings ──────────────────────────────
|
|
140
|
+
test('installed filter preserves DeprecationWarning', () => {
|
|
141
|
+
// Drive the listener directly to verify it re-emits non-matching warnings.
|
|
142
|
+
const child = spawnSync(process.execPath, [
|
|
143
|
+
'-e',
|
|
144
|
+
`const { installExperimentalWarningFilter } = require(${JSON.stringify(FILTER_PATH)});
|
|
145
|
+
installExperimentalWarningFilter({ reexec: false });
|
|
146
|
+
const listener = process.listeners('warning').find(fn => fn.__maAgentsFilter);
|
|
147
|
+
const w = new Error('legacy api — do not use');
|
|
148
|
+
w.name = 'DeprecationWarning';
|
|
149
|
+
let captured = '';
|
|
150
|
+
const orig = process.stderr.write.bind(process.stderr);
|
|
151
|
+
process.stderr.write = (c) => { captured += String(c); return true; };
|
|
152
|
+
listener(w);
|
|
153
|
+
process.stderr.write = orig;
|
|
154
|
+
process.stdout.write(captured);`,
|
|
155
|
+
], { encoding: 'utf8' });
|
|
156
|
+
assert.strictEqual(child.status, 0);
|
|
157
|
+
assert.ok(
|
|
158
|
+
/DeprecationWarning/.test(child.stdout),
|
|
159
|
+
`expected DeprecationWarning to be preserved, stdout=\n${child.stdout}\nstderr=\n${child.stderr}`
|
|
160
|
+
);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
test('installed filter preserves unrelated ExperimentalWarning', () => {
|
|
164
|
+
const child = spawnSync(process.execPath, [
|
|
165
|
+
'-e',
|
|
166
|
+
`const { installExperimentalWarningFilter } = require(${JSON.stringify(FILTER_PATH)});
|
|
167
|
+
installExperimentalWarningFilter({ reexec: false });
|
|
168
|
+
const listener = process.listeners('warning').find(fn => fn.__maAgentsFilter);
|
|
169
|
+
const w = new Error('Fetch API is an experimental feature.');
|
|
170
|
+
w.name = 'ExperimentalWarning';
|
|
171
|
+
let captured = '';
|
|
172
|
+
const orig = process.stderr.write.bind(process.stderr);
|
|
173
|
+
process.stderr.write = (c) => { captured += String(c); return true; };
|
|
174
|
+
listener(w);
|
|
175
|
+
process.stderr.write = orig;
|
|
176
|
+
process.stdout.write(captured);`,
|
|
177
|
+
], { encoding: 'utf8' });
|
|
178
|
+
assert.strictEqual(child.status, 0);
|
|
179
|
+
assert.ok(
|
|
180
|
+
/Fetch API/.test(child.stdout),
|
|
181
|
+
`expected unrelated ExperimentalWarning preserved, stdout=\n${child.stdout}`
|
|
182
|
+
);
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
// ─── Test 5: idempotent — installing twice doesn't double-suppress or leak ────
|
|
186
|
+
test('installExperimentalWarningFilter is idempotent', () => {
|
|
187
|
+
const child = spawnSync(process.execPath, [
|
|
188
|
+
'-e',
|
|
189
|
+
`const { installExperimentalWarningFilter } = require(${JSON.stringify(FILTER_PATH)});
|
|
190
|
+
installExperimentalWarningFilter({ reexec: false });
|
|
191
|
+
installExperimentalWarningFilter({ reexec: false });
|
|
192
|
+
const listeners = process.listeners('warning').filter(fn => fn.__maAgentsFilter === true);
|
|
193
|
+
if (listeners.length !== 1) {
|
|
194
|
+
console.error('EXPECTED 1 filter listener, got', listeners.length);
|
|
195
|
+
process.exit(2);
|
|
196
|
+
}
|
|
197
|
+
process.exit(0);`,
|
|
198
|
+
], { encoding: 'utf8' });
|
|
199
|
+
assert.strictEqual(child.status, 0, `child exited ${child.status}: ${child.stderr}`);
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
// ─── Test 6: bin/cli.js wires the filter at startup ───────────────────────────
|
|
203
|
+
test('bin/cli.js requires warning-filter and installs it', () => {
|
|
204
|
+
const src = fs.readFileSync(CLI_PATH, 'utf8');
|
|
205
|
+
assert.ok(
|
|
206
|
+
/require\(['"]\.\.\/lib\/warning-filter['"]\)/.test(src) ||
|
|
207
|
+
/require\(['"]\.\.\/lib\/warning-filter\.js['"]\)/.test(src),
|
|
208
|
+
'expected bin/cli.js to require ../lib/warning-filter'
|
|
209
|
+
);
|
|
210
|
+
assert.ok(
|
|
211
|
+
/installExperimentalWarningFilter\s*\(/.test(src),
|
|
212
|
+
'expected bin/cli.js to call installExperimentalWarningFilter(...)'
|
|
213
|
+
);
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
// ─── Test 7: lib/bmad.js propagates suppression to child spawn via NODE_OPTIONS
|
|
217
|
+
test('lib/bmad.js builds child env that suppresses ExperimentalWarning', () => {
|
|
218
|
+
const src = fs.readFileSync(BMAD_PATH, 'utf8');
|
|
219
|
+
// Either calls the helper directly or inlines the flag.
|
|
220
|
+
const usesHelper = /withExperimentalWarningSuppressed/.test(src) &&
|
|
221
|
+
/require\(['"]\.\/warning-filter['"]\)/.test(src);
|
|
222
|
+
const inlinesFlag = /--no-warnings=ExperimentalWarning/.test(src);
|
|
223
|
+
assert.ok(
|
|
224
|
+
usesHelper || inlinesFlag,
|
|
225
|
+
'expected lib/bmad.js to either import withExperimentalWarningSuppressed ' +
|
|
226
|
+
'from ./warning-filter, or inline --no-warnings=ExperimentalWarning'
|
|
227
|
+
);
|
|
228
|
+
assert.ok(
|
|
229
|
+
/NODE_OPTIONS/.test(src),
|
|
230
|
+
'expected lib/bmad.js to set NODE_OPTIONS for the child spawn'
|
|
231
|
+
);
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
// ─── Test 8: integration — running bin/cli.js --help is silent on stderr ──────
|
|
235
|
+
test('bin/cli.js --help produces no ExperimentalWarning on stderr', () => {
|
|
236
|
+
const child = spawnSync(process.execPath, [CLI_PATH, '--help'], {
|
|
237
|
+
encoding: 'utf8',
|
|
238
|
+
env: { ...process.env, [require(FILTER_PATH)._SENTINEL_ENV]: '' },
|
|
239
|
+
});
|
|
240
|
+
assert.strictEqual(child.status, 0, `cli --help exited non-zero: ${child.stderr}`);
|
|
241
|
+
assert.ok(
|
|
242
|
+
!/ExperimentalWarning/.test(child.stderr),
|
|
243
|
+
`expected no ExperimentalWarning from cli --help, got:\n${child.stderr}`
|
|
244
|
+
);
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
// ─── Test 9: withExperimentalWarningSuppressed helper is well-behaved ─────────
|
|
248
|
+
test('withExperimentalWarningSuppressed adds flag when none present', () => {
|
|
249
|
+
const { withExperimentalWarningSuppressed } = require(FILTER_PATH);
|
|
250
|
+
assert.strictEqual(withExperimentalWarningSuppressed(undefined), '--no-warnings=ExperimentalWarning');
|
|
251
|
+
assert.strictEqual(withExperimentalWarningSuppressed(''), '--no-warnings=ExperimentalWarning');
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
test('withExperimentalWarningSuppressed preserves existing --no-warnings', () => {
|
|
255
|
+
const { withExperimentalWarningSuppressed } = require(FILTER_PATH);
|
|
256
|
+
assert.strictEqual(withExperimentalWarningSuppressed('--no-warnings'), '--no-warnings');
|
|
257
|
+
assert.strictEqual(
|
|
258
|
+
withExperimentalWarningSuppressed('--no-warnings=DeprecationWarning'),
|
|
259
|
+
'--no-warnings=DeprecationWarning'
|
|
260
|
+
);
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
test('withExperimentalWarningSuppressed appends flag to other NODE_OPTIONS', () => {
|
|
264
|
+
const { withExperimentalWarningSuppressed } = require(FILTER_PATH);
|
|
265
|
+
assert.strictEqual(
|
|
266
|
+
withExperimentalWarningSuppressed('--max-old-space-size=4096'),
|
|
267
|
+
'--max-old-space-size=4096 --no-warnings=ExperimentalWarning'
|
|
268
|
+
);
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
// ─── Test 10: regression — require('bin/cli.js') must NOT re-exec ───────────
|
|
272
|
+
test('require("bin/cli.js") from another process does not re-exec', () => {
|
|
273
|
+
// If the CLI silently re-execs on require, the "before pid" line
|
|
274
|
+
// would print twice in the stderr/stdout stream.
|
|
275
|
+
const child = spawnSync(process.execPath, [
|
|
276
|
+
'-e',
|
|
277
|
+
`const before = process.pid;
|
|
278
|
+
console.log('MARK_BEFORE', before);
|
|
279
|
+
require(${JSON.stringify(CLI_PATH)});
|
|
280
|
+
console.log('MARK_AFTER', process.pid, 'same:', process.pid === before);`,
|
|
281
|
+
], { encoding: 'utf8' });
|
|
282
|
+
assert.strictEqual(child.status, 0, `child exited non-zero: ${child.stderr}`);
|
|
283
|
+
const beforeCount = (child.stdout.match(/MARK_BEFORE/g) || []).length;
|
|
284
|
+
assert.strictEqual(beforeCount, 1, `expected exactly one MARK_BEFORE, got ${beforeCount}\n${child.stdout}`);
|
|
285
|
+
assert.ok(/same: true/.test(child.stdout), 'expected PID to be unchanged after require');
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
// ─── Test 11: regression — fast commands must NOT re-exec ────────────────────
|
|
289
|
+
test('bin/cli.js --version runs in a single process (no re-exec)', () => {
|
|
290
|
+
// Spawn cli.js --version and assert PID stability via NODE_OPTIONS check.
|
|
291
|
+
// We approximate by running it with the sentinel unset and checking it
|
|
292
|
+
// still completes without looping. A process-tree check is overkill;
|
|
293
|
+
// we just verify exit code and output shape.
|
|
294
|
+
const child = spawnSync(process.execPath, [CLI_PATH, '--version'], {
|
|
295
|
+
encoding: 'utf8',
|
|
296
|
+
env: { ...process.env, [require(FILTER_PATH)._SENTINEL_ENV]: '' },
|
|
297
|
+
});
|
|
298
|
+
assert.strictEqual(child.status, 0);
|
|
299
|
+
assert.ok(/ma-agents v/.test(child.stdout), `expected version banner, got: ${child.stdout}`);
|
|
300
|
+
assert.ok(!/ExperimentalWarning/.test(child.stderr));
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
// ─── Test 12: --inspect is not forwarded to the re-exec child ────────────────
|
|
304
|
+
test('installExperimentalWarningFilter drops --inspect* from forwarded execArgv', () => {
|
|
305
|
+
const src = fs.readFileSync(FILTER_PATH, 'utf8');
|
|
306
|
+
assert.ok(
|
|
307
|
+
/--inspect/.test(src) && /filter/.test(src),
|
|
308
|
+
'expected warning-filter.js to filter --inspect out of forwarded execArgv'
|
|
309
|
+
);
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
// ─── Summary ──────────────────────────────────────────────────────────────────
|
|
313
|
+
console.log(`\n ${passed} passed, ${failed} failed\n`);
|
|
314
|
+
if (failed > 0) process.exit(1);
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# Test Fixtures — Epic 21 End-to-End Installer Harness
|
|
2
|
+
|
|
3
|
+
This directory holds the fixtures used by the Epic 21 Story 21.9 end-to-end installer test. They give the harness a stable, byte-for-byte reference point for what `npx ma-agents install` produces for each profile.
|
|
4
|
+
|
|
5
|
+
## Layout
|
|
6
|
+
|
|
7
|
+
- `empty-project/` — canonical seed project (README + minimal package.json). Copied to a scratch tmpdir by the harness before each install run. **Do not edit casually.** Changes here affect generated output and must be paired with baseline regeneration.
|
|
8
|
+
- `standard-profile-baseline/` — expected byte-for-byte output of `npx ma-agents install --yes` (standard profile) run against `empty-project/`. Populated by Story 21.9 via the regeneration procedure below.
|
|
9
|
+
- `onprem-profile-baseline/` — same, but with `"profile": "on-prem"` persisted in `.ma-agents.json` before install. Populated by Story 21.9.
|
|
10
|
+
|
|
11
|
+
## When to regenerate
|
|
12
|
+
|
|
13
|
+
Regenerate the baselines **only** when the installer or a template has intentionally changed. Examples:
|
|
14
|
+
- Story 21.2 landing changes the universal instruction block content → regenerate both baselines.
|
|
15
|
+
- Story 21.6 landing adds on-prem content → regenerate `onprem-profile-baseline/`.
|
|
16
|
+
- Story 21.3 landing adds `.roomodes` → both baselines.
|
|
17
|
+
- Story 21.11 landing does NOT require baseline changes (uninstall is a separate test).
|
|
18
|
+
|
|
19
|
+
**A regression is when the baselines diverge from installer output unintentionally.** Never regenerate to "make the test pass" — diagnose the diff first.
|
|
20
|
+
|
|
21
|
+
## Regeneration procedure
|
|
22
|
+
|
|
23
|
+
From a clean repo checkout:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
# 1. Copy the seed to a scratch tmpdir
|
|
27
|
+
rm -rf /tmp/ma-agents-fixture-regen
|
|
28
|
+
mkdir -p /tmp/ma-agents-fixture-regen
|
|
29
|
+
cp -R test/fixtures/empty-project/* /tmp/ma-agents-fixture-regen/
|
|
30
|
+
|
|
31
|
+
# 2. Standard profile install
|
|
32
|
+
cd /tmp/ma-agents-fixture-regen
|
|
33
|
+
node <repo>/bin/cli.js install --yes
|
|
34
|
+
# (no --profile flag exists per P3-3 Alternatives Considered; standard is the --yes default)
|
|
35
|
+
|
|
36
|
+
# 3. Copy generated output back to the repo
|
|
37
|
+
rm -rf <repo>/test/fixtures/standard-profile-baseline/*
|
|
38
|
+
cp -R /tmp/ma-agents-fixture-regen/* <repo>/test/fixtures/standard-profile-baseline/
|
|
39
|
+
|
|
40
|
+
# 4. Clean + reseed for on-prem
|
|
41
|
+
rm -rf /tmp/ma-agents-fixture-regen
|
|
42
|
+
mkdir -p /tmp/ma-agents-fixture-regen
|
|
43
|
+
cp -R <repo>/test/fixtures/empty-project/* /tmp/ma-agents-fixture-regen/
|
|
44
|
+
# Pre-seed .ma-agents.json with profile=on-prem so --yes honors it (no CLI flag)
|
|
45
|
+
echo '{"manifestVersion":"1.2.0","profile":"on-prem","skills":{}}' > /tmp/ma-agents-fixture-regen/.ma-agents.json
|
|
46
|
+
|
|
47
|
+
# 5. On-prem install
|
|
48
|
+
cd /tmp/ma-agents-fixture-regen
|
|
49
|
+
node <repo>/bin/cli.js install --yes
|
|
50
|
+
|
|
51
|
+
# 6. Copy back
|
|
52
|
+
rm -rf <repo>/test/fixtures/onprem-profile-baseline/*
|
|
53
|
+
cp -R /tmp/ma-agents-fixture-regen/* <repo>/test/fixtures/onprem-profile-baseline/
|
|
54
|
+
|
|
55
|
+
# 7. Review the diff
|
|
56
|
+
cd <repo>
|
|
57
|
+
git diff --stat test/fixtures/
|
|
58
|
+
git add test/fixtures/
|
|
59
|
+
# Commit only after human review — never blindly regenerate
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## What's excluded from baselines
|
|
63
|
+
|
|
64
|
+
The harness must ignore these when diffing (they're volatile or environment-specific):
|
|
65
|
+
- Timestamps in log files (if any are written — none expected today)
|
|
66
|
+
- The `backup-<ISO-timestamp>` files produced by Story 21.2 AC #8 upgrade-safety and Story 21.10/21.11 backup steps (not generated on clean install, but listed here for completeness)
|
|
67
|
+
- The `roomodesOverwriteLog` field's `date` values in `.ma-agents.json` (Story 21.3 AC #10 — the field is deterministic in shape but `date` will differ across runs; normalize to a fixed placeholder before diff)
|
|
68
|
+
- The `profileHistory` field's `date` values (Story 21.10 AC #11 — same treatment)
|
|
69
|
+
|
|
70
|
+
The harness implementation (Story 21.9 Task 5.2) is responsible for this normalization. This scaffolding PR does not implement it.
|
|
71
|
+
|
|
72
|
+
## Relationship to Story 21.9
|
|
73
|
+
|
|
74
|
+
See `_bmad-output/implementation-artifacts/21-9-tests-validation.md` ACs #5 and #6 (added 2026-04-14 in corrective-plan step 3). This scaffolding closes the "nothing concrete to build against" gap flagged in adversarial-review Finding #18.
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
# Empty Project Seed (Test Fixture)
|
|
2
|
+
|
|
3
|
+
This is the canonical empty-project fixture used by the Epic 21 Story 21.9 end-to-end installer test harness. It contains the minimum files an `npx ma-agents install` run expects at a project root: a `README.md` and a `package.json`. Nothing in this seed should affect what the installer generates.
|
|
4
|
+
|
|
5
|
+
**Do not edit this seed casually.** Any change to its contents will change the byte-for-byte output of the installer and break the baseline fixtures in sibling directories. If the seed genuinely needs to change, follow the regeneration procedure in `test/fixtures/README.md`.
|