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.
Files changed (82) hide show
  1. package/_bmad-output/implementation-artifacts/21-1-install-time-profile-prompt.md +181 -0
  2. package/_bmad-output/implementation-artifacts/21-10-profile-reconfigure.md +137 -0
  3. package/_bmad-output/implementation-artifacts/21-11-profile-uninstall.md +149 -0
  4. package/_bmad-output/implementation-artifacts/21-2-universal-instruction-block-expansion.md +98 -0
  5. package/_bmad-output/implementation-artifacts/21-3-roomodes-template-bmad-modes.md +106 -0
  6. package/_bmad-output/implementation-artifacts/21-4-agents-md-template-opencode.md +86 -0
  7. package/_bmad-output/implementation-artifacts/21-5-clinerules-template-extension.md +82 -0
  8. package/_bmad-output/implementation-artifacts/21-6-onprem-layered-guardrails.md +112 -0
  9. package/_bmad-output/implementation-artifacts/21-7-bmad-persona-phase-prefix.md +126 -0
  10. package/_bmad-output/implementation-artifacts/21-8-vllm-reference-doc-readme.md +100 -0
  11. package/_bmad-output/implementation-artifacts/21-9-tests-validation.md +97 -0
  12. package/_bmad-output/implementation-artifacts/bug-experimentalwarning-about-commonjs-loading-es-module-during-install.md +57 -0
  13. package/_bmad-output/implementation-artifacts/sprint-status.yaml +43 -1
  14. package/_bmad-output/methodology/BMAD_AI_Development_Training.pptx +0 -0
  15. package/_bmad-output/methodology/version.json +1 -1
  16. package/_bmad-output/planning-artifacts/architecture.md +52 -0
  17. package/_bmad-output/planning-artifacts/epics.md +397 -0
  18. package/_bmad-output/planning-artifacts/prd.md +46 -1
  19. package/bin/cli.js +109 -1
  20. package/docs/BMAD_AI_Development_Training.pptx +0 -0
  21. package/lib/bmad-cache/bmb/_git_preserved/index +0 -0
  22. package/lib/bmad-cache/bmb/_git_preserved/logs/HEAD +1 -1
  23. package/lib/bmad-cache/bmb/_git_preserved/logs/refs/heads/main +1 -1
  24. package/lib/bmad-cache/bmb/_git_preserved/logs/refs/remotes/origin/HEAD +1 -1
  25. package/lib/bmad-cache/bmb/_git_preserved/objects/pack/pack-554778ad4e7254827618ebd2497c3f4bce9054a4.idx +0 -0
  26. package/lib/bmad-cache/bmb/_git_preserved/objects/pack/{pack-4b395d030ca386fc5748f1b670dcf8c0ef41c94c.pack → pack-554778ad4e7254827618ebd2497c3f4bce9054a4.pack} +0 -0
  27. package/lib/bmad-cache/bmb/_git_preserved/objects/pack/pack-554778ad4e7254827618ebd2497c3f4bce9054a4.rev +0 -0
  28. package/lib/bmad-cache/bmb/_git_preserved/packed-refs +1 -1
  29. package/lib/bmad-cache/bmb/_git_preserved/refs/heads/main +1 -1
  30. package/lib/bmad-cache/bmb/_git_preserved/shallow +1 -1
  31. package/lib/bmad-cache/bmb/skills/bmad-module-builder/scripts/tests/test-scaffold-setup-skill.py +7 -0
  32. package/lib/bmad-cache/cache-manifest.json +5 -5
  33. package/lib/bmad-cache/tea/_git_preserved/index +0 -0
  34. package/lib/bmad-cache/tea/_git_preserved/objects/pack/pack-e75385cd52b693dbb8a3b2afb50058952543b3a2.idx +0 -0
  35. package/lib/bmad-cache/tea/_git_preserved/objects/pack/{pack-c79805bb3fee27fa6d8c612a971af7fc86fc80e1.pack → pack-e75385cd52b693dbb8a3b2afb50058952543b3a2.pack} +0 -0
  36. package/lib/bmad-cache/tea/_git_preserved/objects/pack/pack-e75385cd52b693dbb8a3b2afb50058952543b3a2.rev +0 -0
  37. package/lib/bmad-cache/tea/_git_preserved/packed-refs +1 -1
  38. package/lib/bmad-cache/tea/_git_preserved/refs/heads/main +1 -1
  39. package/lib/bmad-cache/tea/_git_preserved/refs/tags/v1.10.0 +1 -0
  40. package/lib/bmad-cache/tea/_git_preserved/shallow +1 -1
  41. package/lib/bmad-cache/tea/docs/explanation/tea-overview.md +2 -2
  42. package/lib/bmad-cache/tea/docs/how-to/workflows/run-atdd.md +28 -30
  43. package/lib/bmad-cache/tea/docs/reference/commands.md +4 -4
  44. package/lib/bmad-cache/tea/docs/reference/configuration.md +1 -1
  45. package/lib/bmad-cache/tea/package-lock.json +2 -2
  46. package/lib/bmad-cache/tea/package.json +1 -1
  47. package/lib/bmad-cache/tea/src/module-help.csv +1 -1
  48. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/SKILL.md +1 -1
  49. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/atdd-checklist-template.md +50 -27
  50. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/checklist.md +18 -17
  51. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/instructions.md +1 -1
  52. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/steps-c/step-01-preflight-and-context.md +21 -3
  53. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/steps-c/step-01b-resume.md +1 -1
  54. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/steps-c/step-02-generation-mode.md +1 -1
  55. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/steps-c/step-03-test-strategy.md +1 -1
  56. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/steps-c/step-04-generate-tests.md +20 -19
  57. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/steps-c/step-04a-subagent-api-failing.md +13 -13
  58. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/steps-c/step-04b-subagent-e2e-failing.md +13 -13
  59. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/steps-c/step-04c-aggregate.md +42 -18
  60. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/steps-c/step-05-validate-and-complete.md +12 -3
  61. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/workflow-plan.md +2 -2
  62. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/workflow.md +2 -2
  63. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/workflow.yaml +2 -2
  64. package/lib/bmad.js +25 -4
  65. package/lib/installer.js +2 -1
  66. package/lib/methodology/BMAD_AI_Development_Training.pptx +0 -0
  67. package/lib/methodology/version.json +1 -1
  68. package/lib/profile.js +107 -0
  69. package/lib/warning-filter.js +245 -0
  70. package/package.json +2 -2
  71. package/test/experimental-warning.test.js +314 -0
  72. package/test/fixtures/README.md +74 -0
  73. package/test/fixtures/empty-project/README.md +5 -0
  74. package/test/fixtures/empty-project/package.json +5 -0
  75. package/test/fixtures/onprem-profile-baseline/.gitkeep +2 -0
  76. package/test/fixtures/standard-profile-baseline/.gitkeep +2 -0
  77. package/test/onprem-injection.test.js +48 -0
  78. package/test/profile.test.js +301 -0
  79. package/lib/bmad-cache/bmb/_git_preserved/objects/pack/pack-4b395d030ca386fc5748f1b670dcf8c0ef41c94c.idx +0 -0
  80. package/lib/bmad-cache/bmb/_git_preserved/objects/pack/pack-4b395d030ca386fc5748f1b670dcf8c0ef41c94c.rev +0 -0
  81. package/lib/bmad-cache/tea/_git_preserved/objects/pack/pack-c79805bb3fee27fa6d8c612a971af7fc86fc80e1.idx +0 -0
  82. package/lib/bmad-cache/tea/_git_preserved/objects/pack/pack-c79805bb3fee27fa6d8c612a971af7fc86fc80e1.rev +0 -0
@@ -0,0 +1,48 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Story 21.9 — End-to-end installer harness (scaffolding; PENDING implementation).
4
+ *
5
+ * When Story 21.9 is picked up, this file becomes the byte-for-byte fixture-diff
6
+ * harness described in ACs #5 and #6 of the story. Today it is a no-op that exits
7
+ * 0 so `npm test` stays green.
8
+ *
9
+ * Scaffolding committed in corrective-plan step 7 (PR #40 approximate).
10
+ * Fixtures live under `test/fixtures/` — see `test/fixtures/README.md`.
11
+ */
12
+ 'use strict';
13
+
14
+ const fs = require('fs');
15
+ const path = require('path');
16
+
17
+ const FIXTURES_DIR = path.join(__dirname, 'fixtures');
18
+ const STANDARD_BASELINE = path.join(FIXTURES_DIR, 'standard-profile-baseline');
19
+ const ONPREM_BASELINE = path.join(FIXTURES_DIR, 'onprem-profile-baseline');
20
+
21
+ function baselineIsPopulated(dir) {
22
+ if (!fs.existsSync(dir)) return false;
23
+ const entries = fs.readdirSync(dir).filter(f => f !== '.gitkeep');
24
+ return entries.length > 0;
25
+ }
26
+
27
+ const standardReady = baselineIsPopulated(STANDARD_BASELINE);
28
+ const onPremReady = baselineIsPopulated(ONPREM_BASELINE);
29
+
30
+ if (!standardReady && !onPremReady) {
31
+ console.log('[onprem-injection.test] PENDING — baseline fixtures not yet captured.');
32
+ console.log(' See test/fixtures/README.md for the Story 21.9 regeneration procedure.');
33
+ console.log(' Exiting 0 (scaffold only).');
34
+ process.exit(0);
35
+ }
36
+
37
+ // Story 21.9 implementation will replace everything below this line with the real harness.
38
+ // Until then, refuse to silently pass if only one baseline is populated — that's a red flag.
39
+ if (standardReady !== onPremReady) {
40
+ console.error('[onprem-injection.test] FAIL — inconsistent fixture state: one profile baseline is populated and the other is not.');
41
+ console.error(' Either finish regenerating both, or remove the populated one. See test/fixtures/README.md.');
42
+ process.exit(1);
43
+ }
44
+
45
+ // Both populated but the real harness is not yet wired — flag clearly.
46
+ console.log('[onprem-injection.test] PENDING — fixtures populated but harness implementation not yet landed (Story 21.9).');
47
+ console.log(' Run `grep "Story 21.9" _bmad-output/implementation-artifacts/21-9-tests-validation.md` for scope.');
48
+ process.exit(0);
@@ -0,0 +1,301 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Story 21.1 — Unit tests for lib/profile.js
4
+ *
5
+ * AC coverage:
6
+ * 6.1 getProfile undefined when file absent (AC #2)
7
+ * 6.2 getProfile undefined when field absent (AC #2, #14 back-compat)
8
+ * 6.3 getProfile returns persisted value (AC #2)
9
+ * 6.4 setProfile creates file with standard schema (AC #3)
10
+ * 6.5 setProfile preserves existing fields (AC #3)
11
+ * 6.6 setProfile throws on invalid value (AC #4)
12
+ * 6.7 resolveProfile: persisted > yes-default (AC #5)
13
+ * 6.8 resolveProfile: yes-default only when no persisted (AC #5)
14
+ * 6.9 resolveProfile: null when nothing set, yesMode=false (AC #5)
15
+ * 6.10 resolveProfile does no I/O (AC #5)
16
+ * 6.11 round-trip setProfile -> getProfile (AC #2/#3)
17
+ * 6.12 setProfile idempotent (profile field stable) (AC #3)
18
+ * 6.13 setProfile('on-prem') bumps manifestVersion (AC #12 — new files at 1.2.0)
19
+ * 6.14 1.1.0 manifest reads + setProfile migrates to 1.2.0 with pinned log line (AC #12)
20
+ * 6.15 setProfile on 1.2.0 manifest does not emit migration log (AC #12)
21
+ */
22
+ 'use strict';
23
+
24
+ const assert = require('assert');
25
+ const path = require('path');
26
+ const fs = require('fs');
27
+ const os = require('os');
28
+
29
+ let passed = 0;
30
+ let failed = 0;
31
+ const errors = [];
32
+
33
+ async function test(name, fn) {
34
+ try {
35
+ await fn();
36
+ console.log(` ✓ ${name}`);
37
+ passed++;
38
+ } catch (err) {
39
+ console.error(` ✗ ${name}: ${err.message}`);
40
+ failed++;
41
+ errors.push({ name, error: err.message });
42
+ }
43
+ }
44
+
45
+ const { getProfile, setProfile, resolveProfile } = require('../lib/profile');
46
+
47
+ function mktemp() {
48
+ return fs.mkdtempSync(path.join(os.tmpdir(), 'ma-agents-profile-test-'));
49
+ }
50
+
51
+ function cleanup(dir) {
52
+ try { fs.rmSync(dir, { recursive: true, force: true }); } catch {}
53
+ }
54
+
55
+ console.log('\n profile unit tests\n');
56
+
57
+ async function runAll() {
58
+ // 6.1 — getProfile returns undefined when file absent
59
+ await test('6.1 getProfile returns undefined when .ma-agents.json does not exist', () => {
60
+ const dir = mktemp();
61
+ try {
62
+ assert.strictEqual(getProfile(dir), undefined);
63
+ } finally { cleanup(dir); }
64
+ });
65
+
66
+ // 6.2 — getProfile returns undefined when field absent (1.1.0 back-compat)
67
+ await test('6.2 getProfile returns undefined when profile field absent (1.1.0 back-compat)', () => {
68
+ const dir = mktemp();
69
+ try {
70
+ fs.writeFileSync(
71
+ path.join(dir, '.ma-agents.json'),
72
+ JSON.stringify({ manifestVersion: '1.1.0', agent: 'claude-code', agents: ['claude-code'], scope: 'project', skills: {} }, null, 2),
73
+ 'utf-8'
74
+ );
75
+ assert.strictEqual(getProfile(dir), undefined);
76
+ } finally { cleanup(dir); }
77
+ });
78
+
79
+ // 6.3 — getProfile returns persisted value
80
+ await test('6.3 getProfile returns persisted value when present', () => {
81
+ const dir = mktemp();
82
+ try {
83
+ fs.writeFileSync(
84
+ path.join(dir, '.ma-agents.json'),
85
+ JSON.stringify({ manifestVersion: '1.2.0', profile: 'on-prem', skills: {} }, null, 2),
86
+ 'utf-8'
87
+ );
88
+ assert.strictEqual(getProfile(dir), 'on-prem');
89
+ } finally { cleanup(dir); }
90
+ });
91
+
92
+ // 6.4 — setProfile creates file with standard schema when absent
93
+ await test('6.4 setProfile creates .ma-agents.json with standard schema when absent', () => {
94
+ const dir = mktemp();
95
+ try {
96
+ setProfile(dir, 'standard');
97
+ const manifestPath = path.join(dir, '.ma-agents.json');
98
+ assert.ok(fs.existsSync(manifestPath), 'manifest file should exist');
99
+ const parsed = JSON.parse(fs.readFileSync(manifestPath, 'utf-8'));
100
+ assert.strictEqual(parsed.profile, 'standard');
101
+ assert.strictEqual(parsed.manifestVersion, '1.2.0', 'new file should be bumped to 1.2.0');
102
+ assert.ok('agents' in parsed, 'agents array should be initialized');
103
+ assert.ok('skills' in parsed, 'skills object should be initialized');
104
+ assert.strictEqual(parsed.scope, 'project');
105
+ } finally { cleanup(dir); }
106
+ });
107
+
108
+ // 6.5 — setProfile preserves existing fields and migrates manifestVersion 1.1.0 → 1.2.0
109
+ await test('6.5 setProfile preserves existing fields and migrates manifestVersion 1.1.0 → 1.2.0', () => {
110
+ const dir = mktemp();
111
+ try {
112
+ const original = {
113
+ manifestVersion: '1.1.0',
114
+ agent: 'claude-code',
115
+ agents: ['claude-code', 'roo-code'],
116
+ scope: 'project',
117
+ skills: { foo: { version: '1.0.0', installedAt: '2026-01-01T00:00:00Z' } },
118
+ customField: 'preserved'
119
+ };
120
+ fs.writeFileSync(path.join(dir, '.ma-agents.json'), JSON.stringify(original, null, 2), 'utf-8');
121
+ setProfile(dir, 'on-prem');
122
+ const parsed = JSON.parse(fs.readFileSync(path.join(dir, '.ma-agents.json'), 'utf-8'));
123
+ assert.strictEqual(parsed.profile, 'on-prem');
124
+ assert.strictEqual(parsed.agent, 'claude-code');
125
+ assert.deepStrictEqual(parsed.agents, ['claude-code', 'roo-code']);
126
+ assert.strictEqual(parsed.scope, 'project');
127
+ assert.deepStrictEqual(parsed.skills, original.skills);
128
+ assert.strictEqual(parsed.customField, 'preserved', 'unknown fields must also be preserved');
129
+ // Per AC #12 (clarified): the "profile" field is 1.2.0-only, so setProfile must
130
+ // migrate a 1.1.0 manifest to 1.2.0 on write. Writing "profile" onto a 1.1.0
131
+ // manifest would be schema drift.
132
+ assert.strictEqual(parsed.manifestVersion, '1.2.0', 'setProfile must migrate manifestVersion 1.1.0 → 1.2.0');
133
+ } finally { cleanup(dir); }
134
+ });
135
+
136
+ // 6.6 — setProfile throws on invalid value; error names the bad value
137
+ await test('6.6 setProfile throws on invalid value and error message names the bad value', () => {
138
+ const dir = mktemp();
139
+ try {
140
+ assert.throws(
141
+ () => setProfile(dir, 'hybrid'),
142
+ (err) => err instanceof Error && err.message.includes('hybrid'),
143
+ 'should throw naming the bad value'
144
+ );
145
+ assert.throws(() => setProfile(dir, ''), /Invalid profile value/);
146
+ assert.throws(() => setProfile(dir, null), /Invalid profile value/);
147
+ assert.throws(() => setProfile(dir, undefined), /Invalid profile value/);
148
+ } finally { cleanup(dir); }
149
+ });
150
+
151
+ // 6.7 — resolveProfile: persisted wins over yes-default
152
+ await test('6.7 resolveProfile: persisted wins over --yes default', () => {
153
+ assert.strictEqual(
154
+ resolveProfile({ persisted: 'on-prem', yesMode: true }),
155
+ 'on-prem'
156
+ );
157
+ });
158
+
159
+ // 6.8 — resolveProfile: yes-default returns standard only when no persisted
160
+ await test('6.8 resolveProfile: yesMode=true with no persisted returns "standard"', () => {
161
+ assert.strictEqual(
162
+ resolveProfile({ persisted: undefined, yesMode: true }),
163
+ 'standard'
164
+ );
165
+ });
166
+
167
+ // 6.9 — resolveProfile: null when nothing set and yesMode is false
168
+ await test('6.9 resolveProfile: returns null when nothing set and yesMode=false', () => {
169
+ assert.strictEqual(
170
+ resolveProfile({ persisted: undefined, yesMode: false }),
171
+ null
172
+ );
173
+ assert.strictEqual(resolveProfile({}), null, 'empty options → null');
174
+ });
175
+
176
+ // 6.10 — resolveProfile performs no I/O
177
+ await test('6.10 resolveProfile does not perform I/O (fs spies not triggered)', () => {
178
+ const calls = [];
179
+ const originals = {};
180
+ for (const name of ['readFileSync', 'writeFileSync', 'existsSync', 'readdirSync', 'statSync']) {
181
+ originals[name] = fs[name];
182
+ fs[name] = function spy(...args) {
183
+ calls.push({ name, args });
184
+ return originals[name].apply(fs, args);
185
+ };
186
+ }
187
+ try {
188
+ resolveProfile({ persisted: null, yesMode: false });
189
+ resolveProfile({ persisted: 'standard', yesMode: true });
190
+ resolveProfile({ persisted: null, yesMode: true });
191
+ resolveProfile({ persisted: null, yesMode: false });
192
+ assert.strictEqual(calls.length, 0, `resolveProfile triggered fs calls: ${calls.map(c => c.name).join(',')}`);
193
+ } finally {
194
+ for (const name of Object.keys(originals)) fs[name] = originals[name];
195
+ }
196
+ });
197
+
198
+ // 6.11 — Round-trip: setProfile then getProfile
199
+ await test('6.11 round-trip: setProfile("on-prem") then getProfile() returns "on-prem"', () => {
200
+ const dir = mktemp();
201
+ try {
202
+ setProfile(dir, 'on-prem');
203
+ assert.strictEqual(getProfile(dir), 'on-prem');
204
+ setProfile(dir, 'standard');
205
+ assert.strictEqual(getProfile(dir), 'standard');
206
+ } finally { cleanup(dir); }
207
+ });
208
+
209
+ // 6.12 — Idempotence: re-running setProfile with same value keeps the profile field stable
210
+ await test('6.12 setProfile is idempotent — profile field remains stable on re-apply', () => {
211
+ const dir = mktemp();
212
+ try {
213
+ setProfile(dir, 'on-prem');
214
+ const first = fs.readFileSync(path.join(dir, '.ma-agents.json'), 'utf-8');
215
+ setProfile(dir, 'on-prem');
216
+ const second = fs.readFileSync(path.join(dir, '.ma-agents.json'), 'utf-8');
217
+ const p1 = JSON.parse(first);
218
+ const p2 = JSON.parse(second);
219
+ assert.strictEqual(p1.profile, p2.profile, 'profile field must be stable');
220
+ assert.strictEqual(p1.profile, 'on-prem');
221
+ } finally { cleanup(dir); }
222
+ });
223
+
224
+ // 6.13 — Fresh setProfile writes manifestVersion 1.2.0
225
+ await test('6.13 setProfile on fresh project bootstraps manifestVersion 1.2.0', () => {
226
+ const dir = mktemp();
227
+ try {
228
+ setProfile(dir, 'standard');
229
+ const parsed = JSON.parse(fs.readFileSync(path.join(dir, '.ma-agents.json'), 'utf-8'));
230
+ assert.strictEqual(parsed.manifestVersion, '1.2.0');
231
+ } finally { cleanup(dir); }
232
+ });
233
+
234
+ // 6.14 — 1.1.0 back-compat read + setProfile migration to 1.2.0 with pinned log line
235
+ await test('6.14 1.1.0 manifest reads without profile; setProfile migrates to 1.2.0 and logs (AC #12)', () => {
236
+ const dir = mktemp();
237
+ const captured = [];
238
+ const originalLog = console.log;
239
+ try {
240
+ fs.writeFileSync(
241
+ path.join(dir, '.ma-agents.json'),
242
+ JSON.stringify({ manifestVersion: '1.1.0', skills: {} }, null, 2),
243
+ 'utf-8'
244
+ );
245
+ assert.strictEqual(getProfile(dir), undefined);
246
+ // setProfile must migrate the 1.1.0 manifest to 1.2.0, since the "profile" field
247
+ // is 1.2.0-only. It must also emit the pinned migration log line to stdout.
248
+ console.log = (...args) => { captured.push(args.join(' ')); };
249
+ setProfile(dir, 'on-prem');
250
+ console.log = originalLog;
251
+ const after = JSON.parse(fs.readFileSync(path.join(dir, '.ma-agents.json'), 'utf-8'));
252
+ assert.strictEqual(after.profile, 'on-prem');
253
+ assert.strictEqual(after.manifestVersion, '1.2.0', 'setProfile must migrate manifestVersion 1.1.0 → 1.2.0');
254
+ const output = captured.join('\n');
255
+ assert.ok(
256
+ output.includes('Migrated .ma-agents.json manifestVersion 1.1.0 → 1.2.0'),
257
+ `expected pinned migration log line; got: ${JSON.stringify(output)}`
258
+ );
259
+ } finally {
260
+ console.log = originalLog;
261
+ cleanup(dir);
262
+ }
263
+ });
264
+
265
+ // 6.15 — no migration log when manifest is already at 1.2.0
266
+ await test('6.15 setProfile on 1.2.0 manifest does not emit migration log (AC #12)', () => {
267
+ const dir = mktemp();
268
+ const captured = [];
269
+ const originalLog = console.log;
270
+ try {
271
+ fs.writeFileSync(
272
+ path.join(dir, '.ma-agents.json'),
273
+ JSON.stringify({ manifestVersion: '1.2.0', skills: {} }, null, 2),
274
+ 'utf-8'
275
+ );
276
+ console.log = (...args) => { captured.push(args.join(' ')); };
277
+ setProfile(dir, 'on-prem');
278
+ console.log = originalLog;
279
+ const after = JSON.parse(fs.readFileSync(path.join(dir, '.ma-agents.json'), 'utf-8'));
280
+ assert.strictEqual(after.profile, 'on-prem');
281
+ assert.strictEqual(after.manifestVersion, '1.2.0');
282
+ const output = captured.join('\n');
283
+ assert.ok(
284
+ !output.includes('Migrated .ma-agents.json manifestVersion'),
285
+ `expected no migration log; got: ${JSON.stringify(output)}`
286
+ );
287
+ } finally {
288
+ console.log = originalLog;
289
+ cleanup(dir);
290
+ }
291
+ });
292
+
293
+ // ─── Report ───
294
+ console.log(`\n ${passed} passed, ${failed} failed\n`);
295
+ if (failed > 0) {
296
+ errors.forEach(e => console.error(` ✗ ${e.name}: ${e.error}`));
297
+ process.exit(1);
298
+ }
299
+ }
300
+
301
+ runAll();