@memnexus-ai/mx-agent-cli 0.1.207 → 0.1.208

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.
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Integration test: driving runStart to a successful hand-off writes ONE durable
3
+ * StartupReport JSONL line capturing the start-path output (platform v2.58 Phase
4
+ * 1, #4350).
5
+ *
6
+ * Every boundary is stubbed (git, network, filesystem sync, locking, launcher)
7
+ * so runStart reaches the `finalize('ok')` immediately before session launch.
8
+ * We then read the JSONL file from a temp HOME and assert the documented schema
9
+ * plus a non-empty entries[] captured from the start command's own output.
10
+ */
11
+ export {};
12
+ //# sourceMappingURL=start-report-integration.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"start-report-integration.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/start-report-integration.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG"}
@@ -0,0 +1,123 @@
1
+ /**
2
+ * Integration test: driving runStart to a successful hand-off writes ONE durable
3
+ * StartupReport JSONL line capturing the start-path output (platform v2.58 Phase
4
+ * 1, #4350).
5
+ *
6
+ * Every boundary is stubbed (git, network, filesystem sync, locking, launcher)
7
+ * so runStart reaches the `finalize('ok')` immediately before session launch.
8
+ * We then read the JSONL file from a temp HOME and assert the documented schema
9
+ * plus a non-empty entries[] captured from the start command's own output.
10
+ */
11
+ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
12
+ import { mkdtempSync, readFileSync, rmSync } from 'fs';
13
+ import { tmpdir } from 'os';
14
+ import { join } from 'path';
15
+ // hasOrigin=false (git remote probe returns non-zero) → skip the fetch/rebase
16
+ // block and land on the config-sync + launch tail deterministically.
17
+ vi.mock('child_process', () => ({
18
+ spawnSync: vi.fn(() => ({ status: 1, stdout: '', stderr: '' })),
19
+ }));
20
+ vi.mock('../lib/worktree.js', () => ({
21
+ findProjectRoot: vi.fn(() => '/fake/project'),
22
+ findWorktreePath: vi.fn(() => '/fake/project/.worktrees/demo-wt'),
23
+ getSlot: vi.fn(() => 0),
24
+ getCurrentBranch: vi.fn(() => 'worktree/demo'),
25
+ }));
26
+ // claude.js is real for the log helpers but we don't want real config sync or a
27
+ // real session launch, so stub the heavy operations to no-ops. The direct
28
+ // console.log calls inside runStart itself still run and get captured by the tee.
29
+ vi.mock('../lib/claude.js', () => ({
30
+ syncClaudeConfig: vi.fn(),
31
+ deployBaseHooks: vi.fn(),
32
+ deployClaudeMd: vi.fn(),
33
+ writeClaudeLocal: vi.fn(),
34
+ launchClaudeSession: vi.fn(async () => { }),
35
+ logSuccess: vi.fn(),
36
+ logDim: vi.fn(),
37
+ logWarning: vi.fn(),
38
+ }));
39
+ vi.mock('../lib/agent-session.js', () => ({
40
+ launchAgentSession: vi.fn(async () => { }),
41
+ }));
42
+ vi.mock('../lib/catalog.js', () => ({
43
+ getTeam: vi.fn(() => undefined),
44
+ }));
45
+ vi.mock('../lib/project-config.js', () => ({
46
+ loadProjectConfig: vi.fn(() => ({})),
47
+ resolveProjectConfig: vi.fn(() => ({
48
+ baseBranch: 'main',
49
+ worktreeDir: '.worktrees',
50
+ teamsDir: 'mx-agent-system/teams',
51
+ roleguidesDir: 'mx-agent-system/roleguides',
52
+ configSource: 'mx-agent-system/agent-config',
53
+ coordinationMode: 'off',
54
+ })),
55
+ }));
56
+ vi.mock('../lib/config-generation.js', () => ({
57
+ checkConfigGeneration: vi.fn(),
58
+ }));
59
+ vi.mock('../lib/config.js', () => ({
60
+ getConfigValue: vi.fn(() => undefined),
61
+ setConfigValue: vi.fn(),
62
+ }));
63
+ vi.mock('../lib/leader-lock.js', () => ({
64
+ claimLock: vi.fn(() => ({ ok: true })),
65
+ checkLiveness: vi.fn(() => ({ state: 'stale', lockAgeSeconds: null })),
66
+ formatLockEvidence: vi.fn(() => ''),
67
+ lockPath: vi.fn(() => '/fake/project/.worktrees/demo-wt/.leader.lock'),
68
+ readLock: vi.fn(() => ({ lock: null, parseError: null })),
69
+ removeLock: vi.fn(),
70
+ writeLock: vi.fn(),
71
+ updateChildPid: vi.fn(),
72
+ }));
73
+ vi.mock('../lib/coordination-token.js', () => ({
74
+ resolveCoordinationToken: vi.fn(() => ({ status: 'not-on-roster' })),
75
+ }));
76
+ import { runStart } from '../commands/start.js';
77
+ import { startupReport } from '../lib/startup-report.js';
78
+ let home;
79
+ const OLD_HOME = process.env.HOME;
80
+ let logSpy;
81
+ beforeEach(() => {
82
+ home = mkdtempSync(join(tmpdir(), 'mx-startlog-int-'));
83
+ // Set HOME as a property so Node's env proxy stays intact (os.homedir()
84
+ // reads it); a wholesale `process.env = {...}` would break that.
85
+ process.env.HOME = home;
86
+ startupReport._reset();
87
+ vi.clearAllMocks();
88
+ // Neutralize the npm version check network call.
89
+ vi.stubGlobal('fetch', vi.fn().mockRejectedValue(new Error('no network in test')));
90
+ logSpy = vi.spyOn(console, 'log').mockImplementation(() => { });
91
+ });
92
+ afterEach(() => {
93
+ logSpy.mockRestore();
94
+ vi.unstubAllGlobals();
95
+ startupReport._reset();
96
+ rmSync(home, { recursive: true, force: true });
97
+ if (OLD_HOME === undefined)
98
+ delete process.env.HOME;
99
+ else
100
+ process.env.HOME = OLD_HOME;
101
+ });
102
+ describe('runStart → durable StartupReport', () => {
103
+ it('writes one JSONL report with schema_version, outcome ok, and entries[]', async () => {
104
+ await runStart({ name: 'demo' });
105
+ const file = join(home, '.mx-agent', 'logs', 'startup', 'demo-wt.jsonl');
106
+ const lines = readFileSync(file, 'utf-8').trim().split('\n');
107
+ expect(lines).toHaveLength(1);
108
+ const report = JSON.parse(lines[0]);
109
+ expect(report.schema_version).toBe(1);
110
+ expect(report.outcome).toBe('ok');
111
+ expect(report.team).toBe('demo');
112
+ expect(report.worktree).toBe('demo-wt');
113
+ expect(report.project).toBe('project'); // basename('/fake/project')
114
+ expect(typeof report.cli_version).toBe('string');
115
+ expect(Array.isArray(report.entries)).toBe(true);
116
+ expect(report.entries.length).toBeGreaterThan(0);
117
+ // The banner line the command prints is captured verbatim (ANSI stripped).
118
+ const messages = report.entries.map((e) => e.message).join('\n');
119
+ expect(messages).toContain('Starting Agent Team');
120
+ expect(messages).toContain('Team:');
121
+ });
122
+ });
123
+ //# sourceMappingURL=start-report-integration.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"start-report-integration.test.js","sourceRoot":"","sources":["../../src/__tests__/start-report-integration.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Tests for the durable startup-log collector (platform v2.58 Phase 1, #4350).
3
+ *
4
+ * Covers the module's contract:
5
+ * - entry recording (level + optional code)
6
+ * - finalize writes one JSON line with the documented schema; idempotent
7
+ * - two-generation rotation at the 5 MB threshold
8
+ * - finalize NEVER throws, even when the log directory is unwritable
9
+ * - level inference from glyphs and ANSI colors
10
+ * - the console-capture tee records every print without altering output
11
+ */
12
+ export {};
13
+ //# sourceMappingURL=startup-report.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"startup-report.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/startup-report.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG"}
@@ -0,0 +1,245 @@
1
+ /**
2
+ * Tests for the durable startup-log collector (platform v2.58 Phase 1, #4350).
3
+ *
4
+ * Covers the module's contract:
5
+ * - entry recording (level + optional code)
6
+ * - finalize writes one JSON line with the documented schema; idempotent
7
+ * - two-generation rotation at the 5 MB threshold
8
+ * - finalize NEVER throws, even when the log directory is unwritable
9
+ * - level inference from glyphs and ANSI colors
10
+ * - the console-capture tee records every print without altering output
11
+ */
12
+ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
13
+ import { mkdtempSync, mkdirSync, writeFileSync, readFileSync, existsSync, rmSync, chmodSync } from 'fs';
14
+ import { tmpdir } from 'os';
15
+ import { join } from 'path';
16
+ import { startupReport, inferLevel, stripAnsi, redactUrlCredentials } from '../lib/startup-report.js';
17
+ let home;
18
+ const OLD_HOME = process.env.HOME;
19
+ /** Path where finalize writes for a given worktree/team name under the temp HOME. */
20
+ function logPath(name) {
21
+ return join(home, '.mx-agent', 'logs', 'startup', `${name}.jsonl`);
22
+ }
23
+ beforeEach(() => {
24
+ home = mkdtempSync(join(tmpdir(), 'mx-startlog-home-'));
25
+ // Set HOME as a property (keeps Node's env proxy → os.homedir() sees it).
26
+ // A wholesale `process.env = {...}` would drop the proxy and break homedir().
27
+ process.env.HOME = home; // os.homedir() honors $HOME on POSIX
28
+ startupReport._reset();
29
+ });
30
+ afterEach(() => {
31
+ startupReport._reset();
32
+ rmSync(home, { recursive: true, force: true });
33
+ if (OLD_HOME === undefined)
34
+ delete process.env.HOME;
35
+ else
36
+ process.env.HOME = OLD_HOME;
37
+ });
38
+ describe('stripAnsi', () => {
39
+ it('removes SGR color/style escapes', () => {
40
+ expect(stripAnsi('\x1b[32m✓ ok\x1b[39m')).toBe('✓ ok');
41
+ expect(stripAnsi('plain')).toBe('plain');
42
+ });
43
+ });
44
+ describe('inferLevel', () => {
45
+ it('reads glyph markers first (survives colors off)', () => {
46
+ expect(inferLevel(' ✓ Deployed AGENTS.md', 'out')).toBe('success');
47
+ expect(inferLevel(' ⚠ Could not fetch', 'out')).toBe('warning');
48
+ expect(inferLevel(' ✗ Refusing to start', 'out')).toBe('error');
49
+ });
50
+ it('falls back to ANSI color when no glyph', () => {
51
+ expect(inferLevel('\x1b[31mError: boom\x1b[39m', 'out')).toBe('error');
52
+ expect(inferLevel('\x1b[33mheads up\x1b[39m', 'out')).toBe('warning');
53
+ expect(inferLevel('\x1b[32mgreen\x1b[39m', 'out')).toBe('success');
54
+ expect(inferLevel('\x1b[2mdim line\x1b[22m', 'out')).toBe('dim');
55
+ expect(inferLevel('\x1b[90mgray\x1b[39m', 'out')).toBe('dim');
56
+ });
57
+ it('biases by stream when plain text has no marker', () => {
58
+ expect(inferLevel('Team: demo', 'out')).toBe('info');
59
+ expect(inferLevel('[mx-agent] skipping behavioral content', 'err')).toBe('warning');
60
+ expect(inferLevel('Error: not found', 'error-console')).toBe('error');
61
+ });
62
+ });
63
+ describe('URL credential redaction', () => {
64
+ it('strips userinfo from a credentialed https remote', () => {
65
+ expect(redactUrlCredentials('https://x-access-token:SECRET@github.com/org/repo.git'))
66
+ .toBe('https://***@github.com/org/repo.git');
67
+ });
68
+ it('leaves a plain https URL unchanged', () => {
69
+ expect(redactUrlCredentials('https://github.com/org/repo.git'))
70
+ .toBe('https://github.com/org/repo.git');
71
+ });
72
+ it('does NOT mangle an ssh-style git@github.com: remote (no scheme://)', () => {
73
+ expect(redactUrlCredentials('git@github.com:org/repo.git'))
74
+ .toBe('git@github.com:org/repo.git');
75
+ });
76
+ it('record() persists the scrubbed message', () => {
77
+ startupReport.record('warning', 'fatal: could not read from https://user:pw@example.com/x.git');
78
+ const entries = startupReport._entries();
79
+ expect(entries[0].message).toBe('fatal: could not read from https://***@example.com/x.git');
80
+ expect(entries[0].message).not.toContain('pw');
81
+ });
82
+ });
83
+ describe('record + finalize', () => {
84
+ it('buffers entries with level and optional code', () => {
85
+ startupReport.record('info', 'starting');
86
+ startupReport.record('warning', 'heads up', 'obs-token-missing');
87
+ const entries = startupReport._entries();
88
+ expect(entries).toHaveLength(2);
89
+ expect(entries[0]).toMatchObject({ level: 'info', message: 'starting' });
90
+ expect(entries[0].code).toBeUndefined();
91
+ expect(entries[1]).toMatchObject({ level: 'warning', message: 'heads up', code: 'obs-token-missing' });
92
+ expect(typeof entries[0].ts).toBe('string');
93
+ });
94
+ it('writes one JSON line with the documented schema', () => {
95
+ startupReport.setContext({
96
+ project: 'memnexus',
97
+ team: 'demo',
98
+ worktree: 'demo-20260721',
99
+ cliVersion: '9.9.9',
100
+ machine: 'test-host',
101
+ });
102
+ startupReport.record('info', 'line one');
103
+ startupReport.record('success', 'line two');
104
+ startupReport.finalize('ok');
105
+ const raw = readFileSync(logPath('demo-20260721'), 'utf-8').trim().split('\n');
106
+ expect(raw).toHaveLength(1);
107
+ const report = JSON.parse(raw[0]);
108
+ expect(report.schema_version).toBe(1);
109
+ expect(report.project).toBe('memnexus');
110
+ expect(report.team).toBe('demo');
111
+ expect(report.worktree).toBe('demo-20260721');
112
+ expect(report.cli_version).toBe('9.9.9');
113
+ expect(report.machine).toBe('test-host');
114
+ expect(report.outcome).toBe('ok');
115
+ expect(typeof report.started_at).toBe('string');
116
+ expect(typeof report.finished_at).toBe('string');
117
+ expect(report.entries).toHaveLength(2);
118
+ expect(report.entries[1]).toMatchObject({ level: 'success', message: 'line two' });
119
+ });
120
+ it('is idempotent — a second finalize is a no-op (single line total)', () => {
121
+ startupReport.setContext({ team: 'demo' });
122
+ startupReport.record('info', 'once');
123
+ startupReport.finalize('ok');
124
+ startupReport.finalize('error'); // must be ignored
125
+ const raw = readFileSync(logPath('demo'), 'utf-8').trim().split('\n');
126
+ expect(raw).toHaveLength(1);
127
+ expect(JSON.parse(raw[0]).outcome).toBe('ok');
128
+ });
129
+ it('falls back to team, then "unknown", for the file name', () => {
130
+ startupReport.setContext({ team: 'teamx' }); // no worktree
131
+ startupReport.record('info', 'x');
132
+ startupReport.finalize('refused');
133
+ expect(existsSync(logPath('teamx'))).toBe(true);
134
+ });
135
+ it('appends across invocations (multiple JSON lines accumulate)', () => {
136
+ startupReport.setContext({ worktree: 'wt' });
137
+ startupReport.record('info', 'first');
138
+ startupReport.finalize('ok');
139
+ startupReport._reset();
140
+ startupReport.setContext({ worktree: 'wt' });
141
+ startupReport.record('info', 'second');
142
+ startupReport.finalize('refused');
143
+ const lines = readFileSync(logPath('wt'), 'utf-8').trim().split('\n');
144
+ expect(lines).toHaveLength(2);
145
+ expect(JSON.parse(lines[0]).outcome).toBe('ok');
146
+ expect(JSON.parse(lines[1]).outcome).toBe('refused');
147
+ });
148
+ });
149
+ describe('rotation at the 5 MB threshold', () => {
150
+ it('renames the current file to .1 before appending when over 5 MB', () => {
151
+ const dir = join(home, '.mx-agent', 'logs', 'startup');
152
+ mkdirSync(dir, { recursive: true });
153
+ const target = logPath('big');
154
+ // Pre-seed a file just over 5 MB.
155
+ writeFileSync(target, 'x'.repeat(5 * 1024 * 1024 + 10));
156
+ startupReport.setContext({ worktree: 'big' });
157
+ startupReport.record('info', 'after rotation');
158
+ startupReport.finalize('ok');
159
+ expect(existsSync(`${target}.1`)).toBe(true);
160
+ // The new primary file holds exactly the one fresh report line.
161
+ const lines = readFileSync(target, 'utf-8').trim().split('\n');
162
+ expect(lines).toHaveLength(1);
163
+ expect(JSON.parse(lines[0]).entries[0].message).toBe('after rotation');
164
+ });
165
+ it('does not rotate a small file', () => {
166
+ startupReport.setContext({ worktree: 'small' });
167
+ startupReport.record('info', 'a');
168
+ startupReport.finalize('ok');
169
+ expect(existsSync(`${logPath('small')}.1`)).toBe(false);
170
+ });
171
+ });
172
+ describe('never throws on unwritable log dir', () => {
173
+ it('finalize swallows I/O errors when the home dir is read-only', () => {
174
+ // Make HOME read-only so mkdirSync(~/.mx-agent/...) fails with EACCES.
175
+ // (Skipped implicitly when running as root, where perms are ignored — the
176
+ // assertion below still holds because finalize never throws regardless.)
177
+ chmodSync(home, 0o555);
178
+ startupReport.setContext({ worktree: 'ro' });
179
+ startupReport.record('error', 'boom');
180
+ expect(() => startupReport.finalize('error')).not.toThrow();
181
+ // Restore perms so afterEach cleanup can remove the dir.
182
+ chmodSync(home, 0o755);
183
+ });
184
+ it('record never throws', () => {
185
+ expect(() => startupReport.record('info', 'ok')).not.toThrow();
186
+ });
187
+ });
188
+ describe('console-capture tee', () => {
189
+ it('records every print with an inferred level and still emits output', () => {
190
+ startupReport.setContext({ worktree: 'tee' });
191
+ // Spy the REAL console before installing the tee so we can prove output
192
+ // still reaches the terminal exactly once per call.
193
+ const logSpy = vi.spyOn(console, 'log').mockImplementation(() => { });
194
+ const errSpy = vi.spyOn(console, 'error').mockImplementation(() => { });
195
+ startupReport.installConsoleCapture();
196
+ console.log('\x1b[32m ✓ Deployed\x1b[39m');
197
+ console.log(' Team: demo'); // plain info
198
+ console.log('\x1b[33m ⚠ heads up\x1b[39m');
199
+ console.log(' '); // whitespace-only — must NOT be recorded
200
+ console.error('\x1b[31mError: nope\x1b[39m');
201
+ process.stderr.write('[mx-agent] skipping behavioral content\n');
202
+ startupReport.removeConsoleCapture();
203
+ const entries = startupReport._entries();
204
+ const levels = entries.map((e) => e.level);
205
+ expect(levels).toEqual(['success', 'info', 'warning', 'error', 'warning']);
206
+ expect(entries[0].message).toBe(' ✓ Deployed');
207
+ expect(entries.find((e) => e.message.includes('skipping behavioral'))).toBeTruthy();
208
+ // Output still went out: 4 console.log calls (incl. the whitespace one).
209
+ expect(logSpy).toHaveBeenCalledTimes(4);
210
+ expect(errSpy).toHaveBeenCalledTimes(1);
211
+ logSpy.mockRestore();
212
+ errSpy.mockRestore();
213
+ });
214
+ it('records a REAL console.error exactly ONCE (no double via stderr chain)', () => {
215
+ // Do NOT mock console.error — the native impl dispatches through the teed
216
+ // process.stderr.write, which is exactly the double-count path under test.
217
+ // Redirect the raw stderr fd write to a sink so the line does not clutter
218
+ // test output, but leave console.error / process.stderr.write real so the
219
+ // tee sees the genuine dispatch chain.
220
+ startupReport.setContext({ worktree: 'realerr' });
221
+ const realStderrWrite = process.stderr.write.bind(process.stderr);
222
+ const sink = vi.fn(() => true);
223
+ process.stderr.write = sink;
224
+ try {
225
+ startupReport.installConsoleCapture();
226
+ console.error('\x1b[31mTeam \'bogus\' not found.\x1b[39m');
227
+ startupReport.removeConsoleCapture();
228
+ }
229
+ finally {
230
+ process.stderr.write = realStderrWrite;
231
+ }
232
+ const entries = startupReport._entries();
233
+ expect(entries).toHaveLength(1);
234
+ expect(entries[0].level).toBe('error');
235
+ expect(entries[0].message).toBe("Team 'bogus' not found.");
236
+ });
237
+ it('removeConsoleCapture restores the original console.log', () => {
238
+ const before = console.log;
239
+ startupReport.installConsoleCapture();
240
+ expect(console.log).not.toBe(before);
241
+ startupReport.removeConsoleCapture();
242
+ expect(console.log).toBe(before);
243
+ });
244
+ });
245
+ //# sourceMappingURL=startup-report.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"startup-report.test.js","sourceRoot":"","sources":["../../src/__tests__/startup-report.test.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"start.d.ts","sourceRoot":"","sources":["../../src/commands/start.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAyBH;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,8BAA8B,UAG1C,CAAC;AACF,eAAO,MAAM,2BAA2B,aAEtC,CAAC;AAEH;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAK3D;AA2BD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,uBAAuB,CAAC,YAAY,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAgFjG;AA4FD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,eAAe,CAAC,YAAY,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAsC5E;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,gEAAgE;IAChE,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,6FAA6F;IAC7F,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,kGAAkG;IAClG,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAID,wBAAsB,QAAQ,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAukBnE"}
1
+ {"version":3,"file":"start.d.ts","sourceRoot":"","sources":["../../src/commands/start.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AA0BH;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,8BAA8B,UAG1C,CAAC;AACF,eAAO,MAAM,2BAA2B,aAEtC,CAAC;AAEH;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAK3D;AA4BD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,uBAAuB,CAAC,YAAY,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAgFjG;AA4FD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,eAAe,CAAC,YAAY,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAsC5E;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,gEAAgE;IAChE,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,6FAA6F;IAC7F,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,kGAAkG;IAClG,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAID,wBAAsB,QAAQ,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CA0mBnE"}
@@ -18,6 +18,7 @@ import { checkConfigGeneration } from '../lib/config-generation.js';
18
18
  import { getConfigValue, setConfigValue } from '../lib/config.js';
19
19
  import { checkLiveness, claimLock, formatLockEvidence, lockPath, readLock, removeLock, writeLock } from '../lib/leader-lock.js';
20
20
  import { resolveCoordinationToken } from '../lib/coordination-token.js';
21
+ import { startupReport } from '../lib/startup-report.js';
21
22
  const PACKAGE_NAME = '@memnexus-ai/mx-agent-cli';
22
23
  /**
23
24
  * Agent-config / hook paths whose rebase conflict must FAIL CLOSED
@@ -69,6 +70,7 @@ function refuseLiveLeader(worktreePath, team, lock, result) {
69
70
  console.log(chalk.yellow(' Two leaders in one worktree interleave writes and corrupt team state (#3946).'));
70
71
  console.log(chalk.yellow(` If you are certain no other session is running: mx-agent unlock ${team}`));
71
72
  console.log();
73
+ startupReport.finalize('refused');
72
74
  process.exit(1);
73
75
  }
74
76
  /** Register a process-exit backstop that removes OUR lock on any exit path
@@ -322,6 +324,18 @@ export function restoreOwnStash(worktreePath, stashSha) {
322
324
  const MAX_DIRECTIVE_LENGTH = 2000;
323
325
  export async function runStart(options) {
324
326
  const { name, experimental = false, directive, testInject = false } = options;
327
+ // ── Startup log capture (#4350) ────────────────────────────────────
328
+ // Tee every line this command prints into ONE durable StartupReport, and
329
+ // register a process-exit safety net so any unhandled exit path still writes
330
+ // a terminal record. Seed the context we already know; worktree/project are
331
+ // filled in once resolved (finalize reads them then). Capture is installed
332
+ // FIRST so even the earliest checks below are recorded.
333
+ startupReport.setContext({
334
+ team: name,
335
+ cliVersion: getInstalledVersion(),
336
+ });
337
+ startupReport.registerExitSafetyNet();
338
+ startupReport.installConsoleCapture();
325
339
  // SECURITY: The directive is injected verbatim into the agent prompt.
326
340
  // This is safe only because the operator (the person running mx-agent start)
327
341
  // is the trust principal. If directive values ever come from untrusted
@@ -329,6 +343,7 @@ export async function runStart(options) {
329
343
  // structured prompting (e.g., XML-delimited user input blocks) is required.
330
344
  if (directive && directive.length > MAX_DIRECTIVE_LENGTH) {
331
345
  console.error(chalk.red(`Error: --directive exceeds ${MAX_DIRECTIVE_LENGTH} characters (got ${directive.length}).`));
346
+ startupReport.finalize('refused');
332
347
  process.exit(1);
333
348
  }
334
349
  let projectRoot;
@@ -337,8 +352,11 @@ export async function runStart(options) {
337
352
  }
338
353
  catch (err) {
339
354
  console.error(chalk.red(`Error: ${err instanceof Error ? err.message : String(err)}`));
355
+ startupReport.finalize('refused');
340
356
  process.exit(1);
341
357
  }
358
+ const { basename: basenamePath } = await import('path');
359
+ startupReport.setContext({ project: basenamePath(projectRoot) });
342
360
  const cfg = resolveProjectConfig(loadProjectConfig(projectRoot));
343
361
  // ── 0. Version staleness check (non-blocking) ──────────────────────
344
362
  await warnIfOutdated();
@@ -353,6 +371,7 @@ export async function runStart(options) {
353
371
  console.error(chalk.red(`Team '${name}' not found.`));
354
372
  console.error(chalk.dim(` No worktree found in ${projectRoot}/.worktrees/ matching '${name}' or '${name}-*'`));
355
373
  console.error(chalk.dim(` Run: agent create --roleguide <path> --name ${name}`));
374
+ startupReport.finalize('refused');
356
375
  process.exit(1);
357
376
  }
358
377
  // ── 2. Resolve slot ────────────────────────────────────────────────
@@ -360,6 +379,8 @@ export async function runStart(options) {
360
379
  // or just the team slug. Try both.
361
380
  const { basename } = await import('path');
362
381
  const worktreeDirName = basename(worktreePath);
382
+ // Worktree resolved — record it so finalize writes to the right log file.
383
+ startupReport.setContext({ worktree: worktreeDirName });
363
384
  let slot = getSlot(projectRoot, worktreeDirName, cfg.worktreeDir);
364
385
  if (slot === null) {
365
386
  // Try the short name
@@ -647,6 +668,7 @@ export async function runStart(options) {
647
668
  },
648
669
  });
649
670
  removeLock(worktreePath);
671
+ startupReport.finalize('refused');
650
672
  process.exit(1);
651
673
  }
652
674
  // ── Tier 2: ordinary source conflict ────────────────────────
@@ -812,6 +834,18 @@ export async function runStart(options) {
812
834
  : '') +
813
835
  `After startup${directive ? ' and the directive' : ''}, act only within the authority, evidence tier, scope, and stop conditions defined by the roleguide.`;
814
836
  // ── 9. Launch session ──────────────────────────────────────────────
837
+ // Everything the start command does BEFORE handing off to the session has
838
+ // now succeeded. Finalize the durable startup log with 'ok' here (#4350):
839
+ // all config sync, warnings, and refusal-relevant output happen above this
840
+ // point, and finalizing before launch means a correct 'ok' outcome even
841
+ // though the launched session later drives process.exit with its own code
842
+ // (the exit safety net is a no-op once finalized). finalize also removes the
843
+ // console tee, so the session's own output is left completely untouched.
844
+ // KNOWN LIMITATION (#4350 Phase 2): finalize('ok') runs BEFORE the claude child
845
+ // is confirmed to spawn, so a post-finalize launch failure is not reflected in
846
+ // the recorded outcome. Phase 2 (central shipping + launch-result signal) will
847
+ // revisit whether the outcome should be deferred until spawn is confirmed.
848
+ startupReport.finalize('ok');
815
849
  if (experimental) {
816
850
  // Experimental: Agent SDK runtime with async Slack communication support.
817
851
  // Same setup (steps 1-8) — only the session launcher differs.