import * as os from 'os';
import * as path from 'path';
import { afterEach, describe, expect, it, vi } from 'vitest';

import * as fs from 'fs/promises';

import { setClaudeBasePathOverride } from '../../../../src/main/utils/pathDecoder';
import { TeamMemberLogsFinder } from '../../../../src/main/services/team/TeamMemberLogsFinder';

describe('TeamMemberLogsFinder', () => {
  let tmpDir: string | null = null;

  afterEach(async () => {
    setClaudeBasePathOverride(null);
    if (tmpDir) {
      await fs.rm(tmpDir, { recursive: true, force: true });
      tmpDir = null;
    }
  });

  it('builds live log source context without broad transcript discovery', async () => {
    tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'claude-team-logs-live-context-'));
    setClaudeBasePathOverride(tmpDir);

    const teamName = 'live-context-team';
    const projectPath = '/Users/test/live-context';
    const memberProjectPath = '/Users/test/member-cwd';
    const runtimeProjectPath = '/Users/test/runtime-bob-cwd';
    const projectRoot = path.join(tmpDir, 'projects', '-Users-test-live-context');
    const config = {
      name: teamName,
      projectPath,
      leadSessionId: 'lead-session',
      sessionHistory: ['old-session', 'recent-session'],
      members: [{ name: 'bob', cwd: memberProjectPath }],
    };
    await fs.mkdir(projectRoot, { recursive: true });

    const projectResolver = {
      getLiveBaseContext: vi.fn(() =>
        Promise.resolve({
          projectDir: projectRoot,
          projectId: '-Users-test-live-context',
          config,
        })
      ),
      getContext: vi.fn(() =>
        Promise.reject(new Error('broad context must not be used for live tracking'))
      ),
    };
    const launchStateStore = {
      read: vi.fn(() =>
        Promise.resolve({
          version: 2,
          teamName,
          updatedAt: '2026-05-03T12:00:00.000Z',
          leadSessionId: 'lead-session',
          launchPhase: 'active',
          expectedMembers: ['bob'],
          members: {
            bob: {
              name: 'bob',
              launchState: 'runtime_pending_bootstrap',
              agentToolAccepted: true,
              runtimeAlive: false,
              bootstrapConfirmed: false,
              hardFailure: false,
              runtimeSessionId: 'runtime-bob',
              cwd: runtimeProjectPath,
              updatedAt: '2026-05-03T12:00:00.000Z',
            },
          },
          summary: {},
          teamLaunchState: 'partial_pending',
        })
      ),
    };

    const finder = new TeamMemberLogsFinder(
      undefined,
      undefined,
      undefined,
      projectResolver as never,
      launchStateStore as never
    );

    const context = await finder.getLiveLogSourceWatchContext(teamName, { forceRefresh: true });

    expect(projectResolver.getLiveBaseContext).toHaveBeenCalledWith(
      teamName,
      expect.objectContaining({
        forceRefresh: true,
        extraProjectPathCandidates: [runtimeProjectPath],
      })
    );
    expect(projectResolver.getContext).not.toHaveBeenCalled();
    expect(context?.projectDir).toBe(projectRoot);
    expect(context?.watchSessionIds).toEqual([
      'lead-session',
      'runtime-bob',
      'recent-session',
      'old-session',
    ]);
    expect(context?.sessionIds).toEqual(context?.watchSessionIds);
    expect(context?.taskFreshnessRootDirs).toEqual([
      path.normalize(projectPath),
      path.normalize(memberProjectPath),
      path.normalize(runtimeProjectPath),
    ]);
  });

  it('dedupes concurrent log source discovery for the same team', async () => {
    const teamName = 'dedupe-context-team';
    let resolveContext!: (value: unknown) => void;
    const contextPromise = new Promise((resolve) => {
      resolveContext = resolve;
    });
    const projectResolver = {
      getContext: vi.fn(() => contextPromise),
      getLiveBaseContext: vi.fn(),
    };
    const inboxReader = { listInboxNames: vi.fn(async () => []) };
    const membersMetaStore = { getMembers: vi.fn(async () => []) };
    const finder = new TeamMemberLogsFinder(
      undefined,
      inboxReader as never,
      membersMetaStore as never,
      projectResolver as never
    );

    const first = finder.getLogSourceWatchContext(teamName);
    const second = finder.getLogSourceWatchContext(teamName);
    await Promise.resolve();

    expect(projectResolver.getContext).toHaveBeenCalledTimes(1);
    resolveContext({
      projectDir: '/tmp/project',
      projectId: 'project',
      sessionIds: ['session-1'],
      config: { name: teamName, projectPath: '/repo', members: [] },
    });

    await expect(Promise.all([first, second])).resolves.toEqual([
      {
        projectDir: '/tmp/project',
        projectPath: '/repo',
        leadSessionId: undefined,
        sessionIds: ['session-1'],
      },
      {
        projectDir: '/tmp/project',
        projectPath: '/repo',
        leadSessionId: undefined,
        sessionIds: ['session-1'],
      },
    ]);

    await finder.getLogSourceWatchContext(teamName);
    expect(projectResolver.getContext).toHaveBeenCalledTimes(1);
  });

  it('honors forceRefresh after cached log source discovery', async () => {
    const teamName = 'force-refresh-context-team';
    const contexts = [
      {
        projectDir: '/tmp/project-old',
        projectId: 'project-old',
        sessionIds: ['old-session'],
        config: { name: teamName, projectPath: '/repo-old', members: [] },
      },
      {
        projectDir: '/tmp/project-new',
        projectId: 'project-new',
        sessionIds: ['new-session'],
        config: { name: teamName, projectPath: '/repo-new', members: [] },
      },
    ];
    const projectResolver = {
      getContext: vi.fn(async () => contexts.shift() ?? contexts[0]),
      getLiveBaseContext: vi.fn(),
    };
    const inboxReader = { listInboxNames: vi.fn(async () => []) };
    const membersMetaStore = { getMembers: vi.fn(async () => []) };
    const finder = new TeamMemberLogsFinder(
      undefined,
      inboxReader as never,
      membersMetaStore as never,
      projectResolver as never
    );

    await expect(finder.getLogSourceWatchContext(teamName)).resolves.toMatchObject({
      projectDir: '/tmp/project-old',
      sessionIds: ['old-session'],
    });
    await finder.getLogSourceWatchContext(teamName);
    expect(projectResolver.getContext).toHaveBeenCalledTimes(1);

    await expect(
      finder.getLogSourceWatchContext(teamName, { forceRefresh: true })
    ).resolves.toMatchObject({
      projectDir: '/tmp/project-new',
      sessionIds: ['new-session'],
    });
    expect(projectResolver.getContext).toHaveBeenCalledTimes(2);

    await finder.getLogSourceWatchContext(teamName);
    expect(projectResolver.getContext).toHaveBeenCalledTimes(2);
  });

  it('dedupes concurrent forceRefresh log source discovery for the same team', async () => {
    const teamName = 'dedupe-force-refresh-context-team';
    let resolveContext!: (value: unknown) => void;
    const contextPromise = new Promise((resolve) => {
      resolveContext = resolve;
    });
    const projectResolver = {
      getContext: vi.fn(() => contextPromise),
      getLiveBaseContext: vi.fn(),
    };
    const inboxReader = { listInboxNames: vi.fn(async () => []) };
    const membersMetaStore = { getMembers: vi.fn(async () => []) };
    const finder = new TeamMemberLogsFinder(
      undefined,
      inboxReader as never,
      membersMetaStore as never,
      projectResolver as never
    );

    const first = finder.getLogSourceWatchContext(teamName, { forceRefresh: true });
    const second = finder.getLogSourceWatchContext(teamName, { forceRefresh: true });
    await Promise.resolve();

    expect(projectResolver.getContext).toHaveBeenCalledTimes(1);
    resolveContext({
      projectDir: '/tmp/project',
      projectId: 'project',
      sessionIds: ['session-1'],
      config: { name: teamName, projectPath: '/repo', members: [] },
    });

    await expect(Promise.all([first, second])).resolves.toEqual([
      {
        projectDir: '/tmp/project',
        projectPath: '/repo',
        leadSessionId: undefined,
        sessionIds: ['session-1'],
      },
      {
        projectDir: '/tmp/project',
        projectPath: '/repo',
        leadSessionId: undefined,
        sessionIds: ['session-1'],
      },
    ]);
  });

  it('returns subagent logs for a member and lead session for team-lead', async () => {
    tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'claude-team-logs-'));
    setClaudeBasePathOverride(tmpDir);

    const teamName = 't1';
    const projectPath = '/Users/test/my-proj';
    const projectId = '-Users-test-my-proj';
    const leadSessionId = 's1';

    await fs.mkdir(path.join(tmpDir, 'teams', teamName), { recursive: true });
    await fs.writeFile(
      path.join(tmpDir, 'teams', teamName, 'config.json'),
      JSON.stringify(
        {
          name: teamName,
          projectPath,
          leadSessionId,
          members: [
            { name: 'team-lead', agentType: 'team-lead' },
            { name: 'bob', agentType: 'general-purpose' },
          ],
        },
        null,
        2
      ),
      'utf8'
    );

    const projectRoot = path.join(tmpDir, 'projects', projectId);
    await fs.mkdir(path.join(projectRoot, leadSessionId, 'subagents'), { recursive: true });

    await fs.writeFile(
      path.join(projectRoot, `${leadSessionId}.jsonl`),
      [
        JSON.stringify({
          timestamp: '2026-01-01T00:00:00.000Z',
          type: 'user',
          message: { role: 'user', content: 'Lead start' },
        }),
      ].join('\n') + '\n',
      'utf8'
    );

    await fs.writeFile(
      path.join(projectRoot, leadSessionId, 'subagents', 'agent-abc1234.jsonl'),
      [
        JSON.stringify({
          timestamp: '2026-01-01T00:00:01.000Z',
          type: 'user',
          message: { role: 'user', content: 'You are bob, a developer on team "t1" (t1).' },
        }),
        JSON.stringify({
          timestamp: '2026-01-01T00:00:02.000Z',
          type: 'assistant',
          message: { role: 'assistant', content: [{ type: 'text', text: 'OK' }] },
        }),
      ].join('\n') + '\n',
      'utf8'
    );

    const finder = new TeamMemberLogsFinder();

    const bobLogs = await finder.findMemberLogs(teamName, 'bob');
    expect(bobLogs).toHaveLength(1);
    expect(bobLogs[0]?.kind).toBe('subagent');
    if (bobLogs[0]?.kind === 'subagent') {
      expect(bobLogs[0].subagentId).toBe('abc1234');
      expect(bobLogs[0].sessionId).toBe(leadSessionId);
      expect(bobLogs[0].projectId).toBe(projectId);
      expect(bobLogs[0].memberName?.toLowerCase()).toBe('bob');
    }

    const leadLogs = await finder.findMemberLogs(teamName, 'team-lead');
    expect(leadLogs.some((l) => l.kind === 'lead_session')).toBe(true);
    const lead = leadLogs.find((l) => l.kind === 'lead_session');
    expect(lead?.sessionId).toBe(leadSessionId);
    expect(lead?.projectId).toBe(projectId);
  });

  it('returns root member sessions when config.projectPath is missing but member cwd is present', async () => {
    tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'claude-team-logs-'));
    setClaudeBasePathOverride(tmpDir);

    const teamName = 'signal-ops-root';
    const projectPath = '/Users/test/signal-ops-root';
    const projectId = '-Users-test-signal-ops-root';
    const leadSessionId = 'lead-root';
    const memberSessionId = 'member-bob-root';

    await fs.mkdir(path.join(tmpDir, 'teams', teamName), { recursive: true });
    await fs.writeFile(
      path.join(tmpDir, 'teams', teamName, 'config.json'),
      JSON.stringify(
        {
          name: teamName,
          leadSessionId,
          members: [
            { name: 'team-lead', agentType: 'team-lead', cwd: projectPath },
            { name: 'bob', agentType: 'general-purpose', cwd: projectPath },
          ],
        },
        null,
        2
      ),
      'utf8'
    );

    const projectRoot = path.join(tmpDir, 'projects', projectId);
    await fs.mkdir(projectRoot, { recursive: true });

    await fs.writeFile(
      path.join(projectRoot, `${leadSessionId}.jsonl`),
      JSON.stringify({
        timestamp: '2026-04-15T14:02:00.000Z',
        type: 'user',
        teamName,
        agentName: 'team-lead',
        message: { role: 'user', content: `Lead for team "${teamName}" (${teamName})` },
      }) + '\n',
      'utf8'
    );

    await fs.writeFile(
      path.join(projectRoot, `${memberSessionId}.jsonl`),
      [
        JSON.stringify({
          timestamp: '2026-04-15T14:02:01.000Z',
          type: 'user',
          teamName,
          agentName: 'bob',
          message: {
            role: 'user',
            content: `You are bootstrapping into team "${teamName}" as member "bob".`,
          },
        }),
        JSON.stringify({
          timestamp: '2026-04-15T14:02:05.000Z',
          type: 'assistant',
          teamName,
          agentName: 'bob',
          message: {
            role: 'assistant',
            content: [
              {
                type: 'tool_use',
                id: 'call-task-start',
                name: 'mcp__agent-teams__task_start',
                input: {
                  teamName,
                  taskId: 'task-root-1',
                },
              },
            ],
          },
        }),
      ].join('\n') + '\n',
      'utf8'
    );

    const finder = new TeamMemberLogsFinder();
    const bobLogs = await finder.findMemberLogs(teamName, 'bob');
    const taskLogs = await finder.findLogsForTask(teamName, 'task-root-1');
    const attributedFiles = await finder.listAttributedMemberFiles(teamName);

    expect(bobLogs).toHaveLength(1);
    expect(bobLogs[0]?.kind).toBe('member_session');
    if (bobLogs[0]?.kind === 'member_session') {
      expect(bobLogs[0].sessionId).toBe(memberSessionId);
      expect(bobLogs[0].projectId).toBe(projectId);
      expect(bobLogs[0].memberName?.toLowerCase()).toBe('bob');
      expect(bobLogs[0].filePath).toBe(path.join(projectRoot, `${memberSessionId}.jsonl`));
    }

    expect(
      taskLogs.some(
        (log) =>
          log.kind === 'member_session' &&
          log.sessionId === memberSessionId &&
          log.memberName?.toLowerCase() === 'bob'
      )
    ).toBe(true);
    expect(attributedFiles).toEqual([
      {
        memberName: 'bob',
        sessionId: memberSessionId,
        filePath: path.join(projectRoot, `${memberSessionId}.jsonl`),
        mtimeMs: expect.any(Number),
      },
    ]);
  });

  it('returns recent attributed member log file refs in one batch for advisory scans', async () => {
    tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'claude-team-logs-'));
    setClaudeBasePathOverride(tmpDir);

    const teamName = 'runtime-advisory-batch';
    const projectPath = '/Users/test/runtime-advisory-batch';
    const projectId = '-Users-test-runtime-advisory-batch';
    const leadSessionId = 'lead-session';
    const bobSessionId = 'member-bob-session';
    const now = new Date();
    const old = new Date(Date.now() - 30 * 60_000);

    await fs.mkdir(path.join(tmpDir, 'teams', teamName), { recursive: true });
    await fs.writeFile(
      path.join(tmpDir, 'teams', teamName, 'config.json'),
      JSON.stringify(
        {
          name: teamName,
          projectPath,
          leadSessionId,
          members: [
            { name: 'team-lead', agentType: 'team-lead' },
            { name: 'Alice', agentType: 'general-purpose' },
            { name: 'Bob', agentType: 'general-purpose' },
            { name: 'Tom', agentType: 'general-purpose' },
          ],
        },
        null,
        2
      ),
      'utf8'
    );

    const projectRoot = path.join(tmpDir, 'projects', projectId);
    await fs.mkdir(path.join(projectRoot, leadSessionId, 'subagents'), { recursive: true });

    const leadPath = path.join(projectRoot, `${leadSessionId}.jsonl`);
    await fs.writeFile(
      leadPath,
      [
        JSON.stringify({
          timestamp: now.toISOString(),
          type: 'user',
          message: { role: 'user', content: `Lead for team "${teamName}" (${teamName})` },
        }),
        JSON.stringify({
          timestamp: now.toISOString(),
          type: 'system',
          subtype: 'api_error',
          retryInMs: 45_000,
        }),
      ].join('\n') + '\n',
      'utf8'
    );

    const alicePath = path.join(projectRoot, leadSessionId, 'subagents', 'agent-alice.jsonl');
    await fs.writeFile(
      alicePath,
      [
        JSON.stringify({
          timestamp: now.toISOString(),
          type: 'user',
          message: {
            role: 'user',
            content: `You are Alice, a reviewer on team "${teamName}" (${teamName}).`,
          },
        }),
        JSON.stringify({
          timestamp: now.toISOString(),
          type: 'system',
          subtype: 'api_error',
          retryInMs: 45_000,
        }),
      ].join('\n') + '\n',
      'utf8'
    );

    const bobPath = path.join(projectRoot, `${bobSessionId}.jsonl`);
    await fs.writeFile(
      bobPath,
      [
        JSON.stringify({
          timestamp: now.toISOString(),
          type: 'user',
          teamName,
          agentName: 'Bob',
          message: {
            role: 'user',
            content: `You are bootstrapping into team "${teamName}" as member "Bob".`,
          },
        }),
        JSON.stringify({
          timestamp: now.toISOString(),
          type: 'system',
          subtype: 'api_error',
          retryInMs: 45_000,
        }),
      ].join('\n') + '\n',
      'utf8'
    );

    const tomOldPath = path.join(projectRoot, leadSessionId, 'subagents', 'agent-tom.jsonl');
    await fs.writeFile(
      tomOldPath,
      JSON.stringify({
        timestamp: old.toISOString(),
        type: 'user',
        message: {
          role: 'user',
          content: `You are Tom, a developer on team "${teamName}" (${teamName}).`,
        },
      }) + '\n',
      'utf8'
    );
    await fs.utimes(tomOldPath, old, old);

    const finder = new TeamMemberLogsFinder();
    const refs = await finder.findRecentMemberLogFileRefsByMember(
      teamName,
      ['team-lead', 'Alice', 'Bob', 'Tom'],
      Date.now() - 10 * 60_000
    );

    expect(refs).toEqual(
      expect.arrayContaining([
        expect.objectContaining({ memberName: 'team-lead', filePath: leadPath }),
        expect.objectContaining({ memberName: 'Alice', filePath: alicePath }),
        expect.objectContaining({ memberName: 'Bob', filePath: bobPath }),
      ])
    );
    expect(refs.some((ref) => ref.memberName === 'Tom')).toBe(false);
  });

  it('does not leak old same-workspace subagent logs into a newly created team', async () => {
    tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'claude-team-logs-scope-'));
    setClaudeBasePathOverride(tmpDir);

    const teamName = 'aurora-room-scope';
    const projectPath = '/Users/test/shared-workspace';
    const projectId = '-Users-test-shared-workspace';
    const leadSessionId = 'fresh-lead-session';
    const unrelatedSessionId = 'old-other-team-session';
    const now = new Date();

    await fs.mkdir(path.join(tmpDir, 'teams', teamName), { recursive: true });
    await fs.writeFile(
      path.join(tmpDir, 'teams', teamName, 'config.json'),
      JSON.stringify(
        {
          name: teamName,
          projectPath,
          leadSessionId,
          members: [
            { name: 'team-lead', agentType: 'team-lead' },
            { name: 'Alice', agentType: 'general-purpose' },
          ],
        },
        null,
        2
      ),
      'utf8'
    );

    const projectRoot = path.join(tmpDir, 'projects', projectId);
    const currentSubagentsDir = path.join(projectRoot, leadSessionId, 'subagents');
    const unrelatedSubagentsDir = path.join(projectRoot, unrelatedSessionId, 'subagents');
    await fs.mkdir(currentSubagentsDir, { recursive: true });
    await fs.mkdir(unrelatedSubagentsDir, { recursive: true });

    const leadPath = path.join(projectRoot, `${leadSessionId}.jsonl`);
    await fs.writeFile(
      leadPath,
      JSON.stringify({
        timestamp: now.toISOString(),
        type: 'user',
        message: { role: 'user', content: `Lead for team "${teamName}" (${teamName})` },
      }) + '\n',
      'utf8'
    );

    const currentAlicePath = path.join(currentSubagentsDir, 'agent-alice.jsonl');
    await fs.writeFile(
      currentAlicePath,
      JSON.stringify({
        timestamp: now.toISOString(),
        type: 'user',
        message: {
          role: 'user',
          content: `You are Alice, a developer on team "${teamName}" (${teamName}).`,
        },
      }) + '\n',
      'utf8'
    );

    const unrelatedAlicePath = path.join(unrelatedSubagentsDir, 'agent-alice.jsonl');
    await fs.writeFile(
      unrelatedAlicePath,
      JSON.stringify({
        timestamp: now.toISOString(),
        type: 'user',
        message: {
          role: 'user',
          content: 'You are Alice, a developer on team "old-team" (old-team).',
        },
      }) + '\n',
      'utf8'
    );

    const refs = await new TeamMemberLogsFinder().findRecentMemberLogFileRefsByMember(
      teamName,
      ['team-lead', 'Alice'],
      { forceRefresh: true }
    );

    expect(refs).toEqual(
      expect.arrayContaining([
        expect.objectContaining({ memberName: 'team-lead', filePath: leadPath }),
        expect.objectContaining({ memberName: 'Alice', filePath: currentAlicePath }),
      ])
    );
    expect(refs.some((ref) => ref.filePath === unrelatedAlicePath)).toBe(false);
  });

  it('can skip untracked team subagent session discovery for graph previews', async () => {
    tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'claude-team-logs-preview-scope-'));
    setClaudeBasePathOverride(tmpDir);

    const teamName = 'preview-known-session-scope';
    const projectPath = '/Users/test/preview-known-session-scope';
    const projectId = '-Users-test-preview-known-session-scope';
    const leadSessionId = 'known-lead-session';
    const untrackedSessionId = 'team-subagent-only-session';
    const now = new Date();

    await fs.mkdir(path.join(tmpDir, 'teams', teamName), { recursive: true });
    await fs.writeFile(
      path.join(tmpDir, 'teams', teamName, 'config.json'),
      JSON.stringify(
        {
          name: teamName,
          projectPath,
          leadSessionId,
          members: [
            { name: 'team-lead', agentType: 'team-lead' },
            { name: 'Alice', agentType: 'general-purpose' },
            { name: 'Bob', agentType: 'general-purpose' },
          ],
        },
        null,
        2
      ),
      'utf8'
    );

    const projectRoot = path.join(tmpDir, 'projects', projectId);
    const knownSubagentsDir = path.join(projectRoot, leadSessionId, 'subagents');
    const untrackedSubagentsDir = path.join(projectRoot, untrackedSessionId, 'subagents');
    await fs.mkdir(knownSubagentsDir, { recursive: true });
    await fs.mkdir(untrackedSubagentsDir, { recursive: true });

    await fs.writeFile(
      path.join(projectRoot, `${leadSessionId}.jsonl`),
      JSON.stringify({
        timestamp: now.toISOString(),
        type: 'user',
        message: { role: 'user', content: `Lead for team "${teamName}" (${teamName})` },
      }) + '\n',
      'utf8'
    );

    const knownAlicePath = path.join(knownSubagentsDir, 'agent-alice.jsonl');
    await fs.writeFile(
      knownAlicePath,
      JSON.stringify({
        timestamp: now.toISOString(),
        type: 'user',
        message: {
          role: 'user',
          content: `You are Alice, a developer on team "${teamName}" (${teamName}).`,
        },
      }) + '\n',
      'utf8'
    );

    const untrackedBobPath = path.join(untrackedSubagentsDir, 'agent-bob.jsonl');
    await fs.writeFile(
      untrackedBobPath,
      JSON.stringify({
        timestamp: now.toISOString(),
        type: 'user',
        message: {
          role: 'user',
          content: `You are Bob, a developer on team "${teamName}" (${teamName}).`,
        },
      }) + '\n',
      'utf8'
    );

    const finder = new TeamMemberLogsFinder();
    const fastRefs = await finder.findRecentMemberLogFileRefsByMember(teamName, ['Alice', 'Bob'], {
      forceRefresh: true,
      includeTeamSubagentSessionDiscovery: false,
    });
    expect(fastRefs.some((ref) => ref.filePath === knownAlicePath)).toBe(true);
    expect(fastRefs.some((ref) => ref.filePath === untrackedBobPath)).toBe(false);

    const fullRefs = await finder.findRecentMemberLogFileRefsByMember(teamName, ['Alice', 'Bob'], {
      forceRefresh: true,
    });
    expect(fullRefs.some((ref) => ref.filePath === knownAlicePath)).toBe(true);
    expect(fullRefs.some((ref) => ref.filePath === untrackedBobPath)).toBe(true);
  });

  it('applies recent-ref object options to discovery, lead refs, metadata, and requested-member attribution', async () => {
    tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'claude-team-logs-'));
    setClaudeBasePathOverride(tmpDir);

    const teamName = 'member-stream-ref-options';
    const projectPath = '/Users/test/member-stream-ref-options';
    const projectId = '-Users-test-member-stream-ref-options';
    const leadSessionId = 'lead-session';
    const recentSince = Date.now() - 10 * 60_000;
    const old = new Date(Date.now() - 30 * 60_000);
    const now = new Date();
    const projectRoot = path.join(tmpDir, 'projects', projectId);
    const subagentsDir = path.join(projectRoot, leadSessionId, 'subagents');
    await fs.mkdir(subagentsDir, { recursive: true });

    const leadPath = path.join(projectRoot, `${leadSessionId}.jsonl`);
    await fs.writeFile(
      leadPath,
      JSON.stringify({
        timestamp: old.toISOString(),
        type: 'user',
        message: { role: 'user', content: `Lead for team "${teamName}" (${teamName})` },
      }) + '\n',
      'utf8'
    );
    await fs.utimes(leadPath, old, old);

    const zoePath = path.join(subagentsDir, 'agent-zoe.jsonl');
    await fs.writeFile(
      zoePath,
      [
        JSON.stringify({
          timestamp: now.toISOString(),
          type: 'user',
          message: {
            role: 'user',
            content: `You are Zoe, a developer on team "${teamName}" (${teamName}).`,
          },
        }),
        JSON.stringify({
          timestamp: now.toISOString(),
          type: 'assistant',
          message: { role: 'assistant', content: [{ type: 'text', text: 'Ready' }] },
        }),
      ].join('\n') + '\n',
      'utf8'
    );
    await fs.utimes(zoePath, now, now);

    const projectResolver = {
      getContext: vi.fn(() =>
        Promise.resolve({
          projectDir: projectRoot,
          projectId,
          sessionIds: [leadSessionId],
          config: {
            name: teamName,
            projectPath,
            leadSessionId,
            members: [{ name: 'team-lead', agentType: 'team-lead' }],
          },
        })
      ),
    };
    const finder = new TeamMemberLogsFinder(
      undefined,
      undefined,
      undefined,
      projectResolver as never
    );

    const refs = await finder.findRecentMemberLogFileRefsByMember(teamName, ['team-lead', 'Zoe'], {
      mtimeSinceMs: recentSince,
      forceRefresh: true,
    });

    expect(projectResolver.getContext).toHaveBeenCalledWith(
      teamName,
      expect.objectContaining({ forceRefresh: true })
    );
    expect(refs).toEqual([
      expect.objectContaining({
        memberName: 'Zoe',
        filePath: zoePath,
        kind: 'subagent',
        sizeBytes: expect.any(Number),
      }),
    ]);
    expect(refs.some((ref) => ref.filePath === leadPath)).toBe(false);
  });

  it('listAttributedSubagentFiles only returns files from the current lead session for live tracking', async () => {
    tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'claude-team-logs-'));
    setClaudeBasePathOverride(tmpDir);

    const teamName = 'live-tools';
    const projectPath = '/Users/test/live-tools';
    const projectId = '-Users-test-live-tools';
    const currentSessionId = 'session-current';
    const oldSessionId = 'session-old';

    await fs.mkdir(path.join(tmpDir, 'teams', teamName), { recursive: true });
    await fs.writeFile(
      path.join(tmpDir, 'teams', teamName, 'config.json'),
      JSON.stringify(
        {
          name: teamName,
          projectPath,
          leadSessionId: currentSessionId,
          sessionHistory: [oldSessionId],
          members: [
            { name: 'team-lead', agentType: 'team-lead' },
            { name: 'alice', agentType: 'general-purpose' },
          ],
        },
        null,
        2
      ),
      'utf8'
    );

    const projectRoot = path.join(tmpDir, 'projects', projectId);
    await fs.mkdir(path.join(projectRoot, currentSessionId, 'subagents'), { recursive: true });
    await fs.mkdir(path.join(projectRoot, oldSessionId, 'subagents'), { recursive: true });

    const attributedLog =
      [
        JSON.stringify({
          timestamp: '2026-01-01T00:00:01.000Z',
          type: 'user',
          message: {
            role: 'user',
            content: `You are alice, a developer on team "${teamName}" (${teamName}).`,
          },
        }),
        JSON.stringify({
          timestamp: '2026-01-01T00:00:02.000Z',
          type: 'assistant',
          message: { role: 'assistant', content: [{ type: 'text', text: 'OK' }] },
        }),
      ].join('\n') + '\n';

    await fs.writeFile(
      path.join(projectRoot, currentSessionId, 'subagents', 'agent-current.jsonl'),
      attributedLog,
      'utf8'
    );
    await fs.writeFile(
      path.join(projectRoot, oldSessionId, 'subagents', 'agent-old.jsonl'),
      attributedLog,
      'utf8'
    );

    const finder = new TeamMemberLogsFinder();
    const files = await finder.listAttributedSubagentFiles(teamName);

    expect(files).toHaveLength(1);
    expect(files[0]?.sessionId).toBe(currentSessionId);
    expect(files[0]?.filePath).toContain(path.join(currentSessionId, 'subagents'));
  });

  it('detects member via teammate_id attribute in <teammate-message> tag', async () => {
    tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'claude-team-logs-'));
    setClaudeBasePathOverride(tmpDir);

    const teamName = 't2';
    const projectPath = '/Users/test/proj2';
    const projectId = '-Users-test-proj2';
    const leadSessionId = 's2';

    await fs.mkdir(path.join(tmpDir, 'teams', teamName), { recursive: true });
    await fs.writeFile(
      path.join(tmpDir, 'teams', teamName, 'config.json'),
      JSON.stringify({
        name: teamName,
        projectPath,
        leadSessionId,
        members: [
          { name: 'team-lead', agentType: 'team-lead' },
          { name: 'alice', agentType: 'general-purpose' },
        ],
      }),
      'utf8'
    );

    const projectRoot = path.join(tmpDir, 'projects', projectId);
    await fs.mkdir(path.join(projectRoot, leadSessionId, 'subagents'), { recursive: true });

    // Lead session file
    await fs.writeFile(
      path.join(projectRoot, `${leadSessionId}.jsonl`),
      JSON.stringify({
        timestamp: '2026-01-01T00:00:00.000Z',
        type: 'user',
        message: { role: 'user', content: 'Start' },
      }) + '\n',
      'utf8'
    );

    // Subagent file using <teammate-message> format (no "You are" pattern)
    await fs.writeFile(
      path.join(projectRoot, leadSessionId, 'subagents', 'agent-xyz789.jsonl'),
      [
        JSON.stringify({
          timestamp: '2026-01-01T00:00:01.000Z',
          type: 'user',
          message: {
            role: 'user',
            content:
              '<teammate-message teammate_id="alice" color="green" summary="Implement feature X">Please implement the login page</teammate-message>',
          },
        }),
        JSON.stringify({
          timestamp: '2026-01-01T00:00:05.000Z',
          type: 'assistant',
          message: { role: 'assistant', content: [{ type: 'text', text: 'Working on it' }] },
        }),
      ].join('\n') + '\n',
      'utf8'
    );

    const finder = new TeamMemberLogsFinder();
    const aliceLogs = await finder.findMemberLogs(teamName, 'alice');

    expect(aliceLogs).toHaveLength(1);
    expect(aliceLogs[0]?.kind).toBe('subagent');
    if (aliceLogs[0]?.kind === 'subagent') {
      expect(aliceLogs[0].subagentId).toBe('xyz789');
      expect(aliceLogs[0].description).toBe('Implement feature X');
    }
  });

  it('routing.sender overrides teammate_id="team-lead" from spawn message', async () => {
    tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'claude-team-logs-'));
    setClaudeBasePathOverride(tmpDir);

    const teamName = 'tA';
    const projectPath = '/Users/test/projA';
    const projectId = '-Users-test-projA';
    const leadSessionId = 'sA';

    await fs.mkdir(path.join(tmpDir, 'teams', teamName), { recursive: true });
    await fs.writeFile(
      path.join(tmpDir, 'teams', teamName, 'config.json'),
      JSON.stringify({
        name: teamName,
        projectPath,
        leadSessionId,
        members: [
          { name: 'team-lead', agentType: 'team-lead' },
          { name: 'carol', agentType: 'general-purpose' },
        ],
      }),
      'utf8'
    );

    const projectRoot = path.join(tmpDir, 'projects', projectId);
    await fs.mkdir(path.join(projectRoot, leadSessionId, 'subagents'), { recursive: true });

    // Lead session file
    await fs.writeFile(
      path.join(projectRoot, `${leadSessionId}.jsonl`),
      JSON.stringify({
        timestamp: '2026-01-01T00:00:00.000Z',
        type: 'user',
        message: { role: 'user', content: 'Start' },
      }) + '\n',
      'utf8'
    );

    // Subagent file: first message has teammate_id="team-lead" (sender),
    // but routing.sender="carol" (the actual agent) appears later.
    await fs.writeFile(
      path.join(projectRoot, leadSessionId, 'subagents', 'agent-carol01.jsonl'),
      [
        JSON.stringify({
          timestamp: '2026-01-01T00:00:01.000Z',
          type: 'user',
          message: {
            role: 'user',
            content:
              '<teammate-message teammate_id="team-lead" color="yellow" summary="Fix button layout">You are carol, a developer.</teammate-message>',
          },
        }),
        JSON.stringify({
          timestamp: '2026-01-01T00:00:02.000Z',
          type: 'assistant',
          message: { role: 'assistant', content: [{ type: 'text', text: 'Working on it' }] },
        }),
        JSON.stringify({
          timestamp: '2026-01-01T00:00:03.000Z',
          toolUseResult: { routing: { sender: 'carol' } },
        }),
      ].join('\n') + '\n',
      'utf8'
    );

    const finder = new TeamMemberLogsFinder();
    const carolLogs = await finder.findMemberLogs(teamName, 'carol');

    expect(carolLogs).toHaveLength(1);
    expect(carolLogs[0]?.kind).toBe('subagent');
    if (carolLogs[0]?.kind === 'subagent') {
      expect(carolLogs[0].subagentId).toBe('carol01');
      expect(carolLogs[0].description).toBe('Fix button layout');
    }
  });

  it('process.team.memberName overrides teammate_id and text_mention', async () => {
    tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'claude-team-logs-'));
    setClaudeBasePathOverride(tmpDir);

    const teamName = 'tB';
    const projectPath = '/Users/test/projB';
    const projectId = '-Users-test-projB';
    const leadSessionId = 'sB';

    await fs.mkdir(path.join(tmpDir, 'teams', teamName), { recursive: true });
    await fs.writeFile(
      path.join(tmpDir, 'teams', teamName, 'config.json'),
      JSON.stringify({
        name: teamName,
        projectPath,
        leadSessionId,
        members: [
          { name: 'team-lead', agentType: 'team-lead' },
          { name: 'alice', agentType: 'general-purpose' },
          { name: 'bob', agentType: 'general-purpose' },
        ],
      }),
      'utf8'
    );

    const projectRoot = path.join(tmpDir, 'projects', projectId);
    await fs.mkdir(path.join(projectRoot, leadSessionId, 'subagents'), { recursive: true });

    // Lead session file
    await fs.writeFile(
      path.join(projectRoot, `${leadSessionId}.jsonl`),
      JSON.stringify({
        timestamp: '2026-01-01T00:00:00.000Z',
        type: 'user',
        message: { role: 'user', content: 'Start' },
      }) + '\n',
      'utf8'
    );

    // Subagent file: teammate_id="alice" but process.team.memberName="bob"
    await fs.writeFile(
      path.join(projectRoot, leadSessionId, 'subagents', 'agent-bob01.jsonl'),
      [
        JSON.stringify({
          timestamp: '2026-01-01T00:00:01.000Z',
          type: 'user',
          message: {
            role: 'user',
            content:
              '<teammate-message teammate_id="alice" color="green" summary="Refactor code">Do the work</teammate-message>',
          },
        }),
        JSON.stringify({
          timestamp: '2026-01-01T00:00:02.000Z',
          process: { team: { memberName: 'bob' } },
        }),
      ].join('\n') + '\n',
      'utf8'
    );

    const finder = new TeamMemberLogsFinder();
    const bobLogs = await finder.findMemberLogs(teamName, 'bob');

    expect(bobLogs).toHaveLength(1);
    expect(bobLogs[0]?.kind).toBe('subagent');
    if (bobLogs[0]?.kind === 'subagent') {
      expect(bobLogs[0].subagentId).toBe('bob01');
    }

    // Verify alice does NOT get this file
    const aliceLogs = await finder.findMemberLogs(teamName, 'alice');
    const aliceSubagents = aliceLogs.filter((l) => l.kind === 'subagent');
    expect(aliceSubagents).toHaveLength(0);
  });

  it('reports accurate messageCount from full file (not limited by scan lines)', async () => {
    tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'claude-team-logs-'));
    setClaudeBasePathOverride(tmpDir);

    const teamName = 't3';
    const projectPath = '/Users/test/proj3';
    const projectId = '-Users-test-proj3';
    const leadSessionId = 's3';

    await fs.mkdir(path.join(tmpDir, 'teams', teamName), { recursive: true });
    await fs.writeFile(
      path.join(tmpDir, 'teams', teamName, 'config.json'),
      JSON.stringify({
        name: teamName,
        projectPath,
        leadSessionId,
        members: [
          { name: 'team-lead', agentType: 'team-lead' },
          { name: 'carol', agentType: 'general-purpose' },
        ],
      }),
      'utf8'
    );

    const projectRoot = path.join(tmpDir, 'projects', projectId);
    await fs.mkdir(path.join(projectRoot, leadSessionId, 'subagents'), { recursive: true });

    await fs.writeFile(
      path.join(projectRoot, `${leadSessionId}.jsonl`),
      JSON.stringify({
        timestamp: '2026-01-01T00:00:00.000Z',
        type: 'user',
        message: { role: 'user', content: 'Go' },
      }) + '\n',
      'utf8'
    );

    // Build a 200-line subagent file — well beyond ATTRIBUTION_SCAN_LINES (50)
    const lines: string[] = [];
    // First line: spawn prompt with teammate_id
    lines.push(
      JSON.stringify({
        timestamp: '2026-01-01T00:00:01.000Z',
        type: 'user',
        message: {
          role: 'user',
          content:
            '<teammate-message teammate_id="carol" color="yellow" summary="Big task">Do 200 things</teammate-message>',
        },
      })
    );
    // Lines 2-200: alternating assistant/user messages
    for (let i = 2; i <= 200; i++) {
      const role = i % 2 === 0 ? 'assistant' : 'user';
      lines.push(
        JSON.stringify({
          timestamp: `2026-01-01T00:${String(Math.floor(i / 60)).padStart(2, '0')}:${String(i % 60).padStart(2, '0')}.000Z`,
          type: role,
          message: { role, content: `Message ${i}` },
        })
      );
    }

    await fs.writeFile(
      path.join(projectRoot, leadSessionId, 'subagents', 'agent-big123.jsonl'),
      lines.join('\n') + '\n',
      'utf8'
    );

    const finder = new TeamMemberLogsFinder();
    const carolLogs = await finder.findMemberLogs(teamName, 'carol');

    expect(carolLogs).toHaveLength(1);
    expect(carolLogs[0]?.kind).toBe('subagent');
    // Full file has 200 messages — must NOT be capped at 50 or 100
    expect(carolLogs[0]?.messageCount).toBe(200);
  });

  it('findLogsForTask does not treat arbitrary "#<id>" as a task reference', async () => {
    tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'claude-team-task-logs-'));
    setClaudeBasePathOverride(tmpDir);

    const teamName = 't4';
    const projectPath = '/Users/test/proj4';
    const projectId = '-Users-test-proj4';
    const leadSessionId = 's4';

    await fs.mkdir(path.join(tmpDir, 'teams', teamName), { recursive: true });
    await fs.writeFile(
      path.join(tmpDir, 'teams', teamName, 'config.json'),
      JSON.stringify({
        name: teamName,
        projectPath,
        leadSessionId,
        members: [
          { name: 'team-lead', agentType: 'team-lead' },
          { name: 'bob', agentType: 'general-purpose' },
        ],
      }),
      'utf8'
    );

    const projectRoot = path.join(tmpDir, 'projects', projectId);
    await fs.mkdir(path.join(projectRoot, leadSessionId, 'subagents'), { recursive: true });

    // Lead session mentions "PR #1" but NOT a task reference
    await fs.writeFile(
      path.join(projectRoot, `${leadSessionId}.jsonl`),
      [
        JSON.stringify({
          timestamp: '2026-01-01T00:00:00.000Z',
          type: 'assistant',
          message: { role: 'assistant', content: [{ type: 'text', text: 'Fix PR #1 please' }] },
        }),
      ].join('\n') + '\n',
      'utf8'
    );

    // Subagent session includes a structured taskId reference (should match)
    await fs.writeFile(
      path.join(projectRoot, leadSessionId, 'subagents', 'agent-abc111.jsonl'),
      [
        JSON.stringify({
          timestamp: '2026-01-01T00:00:01.000Z',
          type: 'user',
          message: { role: 'user', content: 'You are bob, a developer on team "t4" (t4).' },
        }),
        JSON.stringify({
          timestamp: '2026-01-01T00:00:02.000Z',
          type: 'assistant',
          message: {
            role: 'assistant',
            content: [
              {
                type: 'tool_use',
                name: 'TaskUpdate',
                input: { team_name: teamName, taskId: '1', status: 'in_progress' },
              },
            ],
          },
        }),
      ].join('\n') + '\n',
      'utf8'
    );

    const finder = new TeamMemberLogsFinder();
    const logs = await finder.findLogsForTask(teamName, '1');

    // Should include the subagent log, but must NOT include the lead session just because it had "PR #1"
    expect(logs.some((l) => l.kind === 'lead_session')).toBe(false);
    expect(logs.some((l) => l.kind === 'subagent')).toBe(true);
  });

  it('findLogsForTask includes only owner sessions overlapping workIntervals', async () => {
    tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'claude-team-task-owner-since-'));
    setClaudeBasePathOverride(tmpDir);

    const teamName = 't5';
    const projectPath = '/Users/test/proj5';
    const projectId = '-Users-test-proj5';
    const leadSessionId = 's5';

    await fs.mkdir(path.join(tmpDir, 'teams', teamName), { recursive: true });
    await fs.writeFile(
      path.join(tmpDir, 'teams', teamName, 'config.json'),
      JSON.stringify({
        name: teamName,
        projectPath,
        leadSessionId,
        members: [
          { name: 'team-lead', agentType: 'team-lead' },
          { name: 'bob', agentType: 'general-purpose' },
          { name: 'alice', agentType: 'general-purpose' },
        ],
      }),
      'utf8'
    );

    const projectRoot = path.join(tmpDir, 'projects', projectId);
    await fs.mkdir(path.join(projectRoot, leadSessionId, 'subagents'), { recursive: true });

    // Alice file references taskId 10 via structured tool input (so results is non-empty).
    await fs.writeFile(
      path.join(projectRoot, leadSessionId, 'subagents', 'agent-alice10.jsonl'),
      [
        JSON.stringify({
          timestamp: '2026-01-01T00:00:01.000Z',
          type: 'user',
          message: { role: 'user', content: 'You are alice, a developer on team "t5" (t5).' },
        }),
        JSON.stringify({
          timestamp: '2026-01-01T00:00:02.000Z',
          type: 'assistant',
          message: {
            role: 'assistant',
            content: [
              {
                type: 'tool_use',
                name: 'TaskUpdate',
                input: { team_name: teamName, taskId: '10', status: 'pending' },
              },
            ],
          },
        }),
      ].join('\n') + '\n',
      'utf8'
    );

    // Bob has an old session (should NOT be pulled in by owner include).
    await fs.writeFile(
      path.join(projectRoot, leadSessionId, 'subagents', 'agent-bob-old.jsonl'),
      [
        JSON.stringify({
          timestamp: '2025-12-31T00:00:00.000Z',
          type: 'user',
          message: { role: 'user', content: 'You are bob, a developer on team "t5" (t5).' },
        }),
        JSON.stringify({
          timestamp: '2025-12-31T00:00:01.000Z',
          type: 'assistant',
          message: { role: 'assistant', content: [{ type: 'text', text: 'Old work' }] },
        }),
      ].join('\n') + '\n',
      'utf8'
    );

    // Bob has a recent session within workIntervals (should be included).
    await fs.writeFile(
      path.join(projectRoot, leadSessionId, 'subagents', 'agent-bob-new.jsonl'),
      [
        JSON.stringify({
          timestamp: '2026-01-01T12:00:00.000Z',
          type: 'user',
          message: { role: 'user', content: 'You are bob, a developer on team "t5" (t5).' },
        }),
        JSON.stringify({
          timestamp: '2026-01-01T12:00:01.000Z',
          type: 'assistant',
          message: { role: 'assistant', content: [{ type: 'text', text: 'New work' }] },
        }),
      ].join('\n') + '\n',
      'utf8'
    );

    const finder = new TeamMemberLogsFinder();
    const logs = await finder.findLogsForTask(teamName, '10', {
      owner: 'bob',
      status: 'in_progress',
      intervals: [
        { startedAt: '2026-01-01T10:00:00.000Z', completedAt: '2026-01-01T13:00:00.000Z' },
      ],
    });

    const bobDescriptions = logs
      .filter((l) => l.kind === 'subagent' && l.memberName?.toLowerCase() === 'bob')
      .map((l) => l.description);

    expect(bobDescriptions.some((d) => d.includes('Old'))).toBe(false);
    // At least one bob log should be present (the recent one).
    expect(logs.some((l) => l.kind === 'subagent' && l.memberName?.toLowerCase() === 'bob')).toBe(
      true
    );
  });

  it('findLogsForTask does not treat malformed empty completedAt intervals as open', async () => {
    tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'claude-team-task-owner-malformed-'));
    setClaudeBasePathOverride(tmpDir);

    const teamName = 't5-malformed';
    const projectPath = '/Users/test/proj5-malformed';
    const projectId = '-Users-test-proj5-malformed';
    const leadSessionId = 's5-malformed';

    await fs.mkdir(path.join(tmpDir, 'teams', teamName), { recursive: true });
    await fs.writeFile(
      path.join(tmpDir, 'teams', teamName, 'config.json'),
      JSON.stringify({
        name: teamName,
        projectPath,
        leadSessionId,
        members: [
          { name: 'team-lead', agentType: 'team-lead' },
          { name: 'bob', agentType: 'general-purpose' },
          { name: 'alice', agentType: 'general-purpose' },
        ],
      }),
      'utf8'
    );

    const projectRoot = path.join(tmpDir, 'projects', projectId);
    await fs.mkdir(path.join(projectRoot, leadSessionId, 'subagents'), { recursive: true });

    await fs.writeFile(
      path.join(projectRoot, leadSessionId, 'subagents', 'agent-alice10.jsonl'),
      [
        JSON.stringify({
          timestamp: '2026-01-01T00:00:01.000Z',
          type: 'user',
          message: {
            role: 'user',
            content: `You are alice, a developer on team "${teamName}" (${teamName}).`,
          },
        }),
        JSON.stringify({
          timestamp: '2026-01-01T00:00:02.000Z',
          type: 'assistant',
          message: {
            role: 'assistant',
            content: [
              {
                type: 'tool_use',
                name: 'TaskUpdate',
                input: { team_name: teamName, taskId: '10', status: 'pending' },
              },
            ],
          },
        }),
      ].join('\n') + '\n',
      'utf8'
    );

    await fs.writeFile(
      path.join(projectRoot, leadSessionId, 'subagents', 'agent-bob-near.jsonl'),
      [
        JSON.stringify({
          timestamp: '2026-01-01T10:00:01.000Z',
          type: 'user',
          message: {
            role: 'user',
            content: `You are bob, a developer on team "${teamName}" (${teamName}).`,
          },
        }),
        JSON.stringify({
          timestamp: '2026-01-01T10:00:02.000Z',
          type: 'assistant',
          message: {
            role: 'assistant',
            content: [{ type: 'text', text: 'Near malformed interval' }],
          },
        }),
      ].join('\n') + '\n',
      'utf8'
    );

    await fs.writeFile(
      path.join(projectRoot, leadSessionId, 'subagents', 'agent-bob-late.jsonl'),
      [
        JSON.stringify({
          timestamp: '2026-01-01T12:00:00.000Z',
          type: 'user',
          message: {
            role: 'user',
            content: `You are bob, a developer on team "${teamName}" (${teamName}).`,
          },
        }),
        JSON.stringify({
          timestamp: '2026-01-01T12:00:01.000Z',
          type: 'assistant',
          message: {
            role: 'assistant',
            content: [{ type: 'text', text: 'Late malformed interval' }],
          },
        }),
      ].join('\n') + '\n',
      'utf8'
    );

    const finder = new TeamMemberLogsFinder();
    const options = {
      owner: 'bob',
      status: 'in_progress',
      intervals: [{ startedAt: '2026-01-01T10:00:00.000Z', completedAt: '' }],
    };
    const logs = await finder.findLogsForTask(teamName, '10', options);

    const bobFilePaths = logs
      .filter((l) => l.kind === 'subagent' && l.memberName?.toLowerCase() === 'bob')
      .map((l) => l.filePath ?? '');

    expect(bobFilePaths.some((filePath) => filePath.endsWith('agent-bob-near.jsonl'))).toBe(true);
    expect(bobFilePaths.some((filePath) => filePath.endsWith('agent-bob-late.jsonl'))).toBe(false);

    const reversedIntervalLogs = await finder.findLogsForTask(teamName, '10', {
      ...options,
      intervals: [
        {
          startedAt: '2026-01-01T10:00:00.000Z',
          completedAt: '2026-01-01T09:59:00.000Z',
        },
      ],
    });
    const reversedBobFilePaths = reversedIntervalLogs
      .filter((l) => l.kind === 'subagent' && l.memberName?.toLowerCase() === 'bob')
      .map((l) => l.filePath ?? '');

    expect(reversedBobFilePaths.some((filePath) => filePath.endsWith('agent-bob-near.jsonl'))).toBe(
      true
    );
    expect(reversedBobFilePaths.some((filePath) => filePath.endsWith('agent-bob-late.jsonl'))).toBe(
      false
    );

    const refs = await finder.findLogFileRefsForTask(teamName, '10', options);
    const bobRefPaths = refs
      .filter((ref) => ref.memberName.toLowerCase() === 'bob')
      .map((ref) => ref.filePath);

    expect(bobRefPaths.some((filePath) => filePath.endsWith('agent-bob-late.jsonl'))).toBe(false);
  });

  it('findLogsForTask does not auto-include owner sessions when owner is team-lead', async () => {
    tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'claude-team-task-lead-owner-'));
    setClaudeBasePathOverride(tmpDir);

    const teamName = 't6';
    const projectPath = '/Users/test/proj6';
    const projectId = '-Users-test-proj6';
    const leadSessionId = 's6';

    await fs.mkdir(path.join(tmpDir, 'teams', teamName), { recursive: true });
    await fs.writeFile(
      path.join(tmpDir, 'teams', teamName, 'config.json'),
      JSON.stringify({
        name: teamName,
        projectPath,
        leadSessionId,
        members: [{ name: 'team-lead', agentType: 'team-lead' }],
      }),
      'utf8'
    );

    const projectRoot = path.join(tmpDir, 'projects', projectId);
    await fs.mkdir(path.join(projectRoot, leadSessionId, 'subagents'), { recursive: true });

    // Lead session exists but does NOT reference taskId 42.
    await fs.writeFile(
      path.join(projectRoot, `${leadSessionId}.jsonl`),
      JSON.stringify({
        timestamp: '2026-01-01T00:00:00.000Z',
        type: 'assistant',
        message: { role: 'assistant', content: [{ type: 'text', text: 'Hello' }] },
      }) + '\n',
      'utf8'
    );

    const finder = new TeamMemberLogsFinder();
    const logs = await finder.findLogsForTask(teamName, '42', {
      owner: 'team-lead',
      status: 'in_progress',
      intervals: [{ startedAt: '2026-01-01T10:00:00.000Z' }],
    });

    // We only want sessions that explicitly reference the task id.
    expect(logs).toHaveLength(0);
  });

  it('findLogsForTask does not mix tasks across teams sharing a projectPath', async () => {
    tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'claude-team-task-cross-team-'));
    setClaudeBasePathOverride(tmpDir);

    const projectPath = '/Users/test/shared-proj';
    const projectId = '-Users-test-shared-proj';
    const sessionId = 's-shared';

    // Two teams pointing at the same project path (realistic when multiple teams work in one repo)
    const teamA = 'team-a';
    const teamB = 'team-b';

    await fs.mkdir(path.join(tmpDir, 'teams', teamA), { recursive: true });
    await fs.mkdir(path.join(tmpDir, 'teams', teamB), { recursive: true });

    await fs.writeFile(
      path.join(tmpDir, 'teams', teamA, 'config.json'),
      JSON.stringify({
        name: teamA,
        projectPath,
        members: [
          { name: 'team-lead', agentType: 'team-lead' },
          { name: 'alice', agentType: 'general-purpose' },
        ],
      }),
      'utf8'
    );
    await fs.writeFile(
      path.join(tmpDir, 'teams', teamB, 'config.json'),
      JSON.stringify({
        name: teamB,
        projectPath,
        members: [
          { name: 'team-lead', agentType: 'team-lead' },
          { name: 'bob', agentType: 'general-purpose' },
        ],
      }),
      'utf8'
    );

    const projectRoot = path.join(tmpDir, 'projects', projectId);
    await fs.mkdir(path.join(projectRoot, sessionId, 'subagents'), { recursive: true });

    // Team A subagent referencing taskId 9 (no team_name in tool input, as in Solo/older runs)
    await fs.writeFile(
      path.join(projectRoot, sessionId, 'subagents', 'agent-a1.jsonl'),
      [
        JSON.stringify({
          timestamp: '2026-01-01T00:00:01.000Z',
          type: 'user',
          message: {
            role: 'user',
            content: 'You are alice, a developer on team "team-a" (team-a).',
          },
        }),
        JSON.stringify({
          timestamp: '2026-01-01T00:00:02.000Z',
          type: 'assistant',
          message: {
            role: 'assistant',
            content: [
              {
                type: 'tool_use',
                name: 'TaskUpdate',
                input: { taskId: '9', status: 'in_progress' },
              },
            ],
          },
        }),
      ].join('\n') + '\n',
      'utf8'
    );

    // Team B subagent referencing taskId 9 (must NOT be included when querying team-a)
    await fs.writeFile(
      path.join(projectRoot, sessionId, 'subagents', 'agent-b1.jsonl'),
      [
        JSON.stringify({
          timestamp: '2026-01-01T00:00:03.000Z',
          type: 'user',
          message: { role: 'user', content: 'You are bob, a developer on team "team-b" (team-b).' },
        }),
        JSON.stringify({
          timestamp: '2026-01-01T00:00:04.000Z',
          type: 'assistant',
          message: {
            role: 'assistant',
            content: [
              {
                type: 'tool_use',
                name: 'TaskUpdate',
                input: { taskId: '9', status: 'in_progress' },
              },
            ],
          },
        }),
      ].join('\n') + '\n',
      'utf8'
    );

    const finder = new TeamMemberLogsFinder();
    const logsForA = await finder.findLogsForTask(teamA, '9');

    expect(
      logsForA.some((l) => l.kind === 'subagent' && l.memberName?.toLowerCase() === 'alice')
    ).toBe(true);
    expect(
      logsForA.some((l) => l.kind === 'subagent' && l.memberName?.toLowerCase() === 'bob')
    ).toBe(false);
  });

  it('detects structured task markers and ignores legacy teamctl command lines', async () => {
    tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'claude-team-marker-logs-'));

    const structuredPath = path.join(tmpDir, 'structured.jsonl');
    await fs.writeFile(
      structuredPath,
      JSON.stringify({
        timestamp: '2026-01-01T00:00:00.000Z',
        type: 'assistant',
        message: {
          role: 'assistant',
          content: [
            {
              type: 'tool_use',
              name: 'task_start',
              input: { teamName: 'demo', taskId: 'task-42' },
            },
          ],
        },
      }) + '\n',
      'utf8'
    );

    const legacyPath = path.join(tmpDir, 'legacy.jsonl');
    await fs.writeFile(
      legacyPath,
      JSON.stringify({
        timestamp: '2026-01-01T00:00:01.000Z',
        type: 'assistant',
        message: {
          role: 'assistant',
          content: [
            {
              type: 'tool_use',
              name: 'Bash',
              input: { command: 'node "teamctl.js" --team demo task start task-42' },
            },
          ],
        },
      }) + '\n',
      'utf8'
    );

    const noisePath = path.join(tmpDir, 'noise.jsonl');
    await fs.writeFile(
      noisePath,
      JSON.stringify({
        timestamp: '2026-01-01T00:00:02.000Z',
        type: 'assistant',
        message: { role: 'assistant', content: [{ type: 'text', text: 'No task markers here' }] },
      }) + '\n',
      'utf8'
    );

    const finder = new TeamMemberLogsFinder();

    await expect(finder.hasTaskUpdateMarker(structuredPath, 'task-42')).resolves.toBe(true);
    await expect(finder.hasTaskUpdateMarker(legacyPath, 'task-42')).resolves.toBe(false);
    await expect(finder.hasTaskUpdateMarker(noisePath, 'task-42')).resolves.toBe(false);
  });

  it('detects fully-qualified agent-teams task markers in JSONL', async () => {
    tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'claude-team-markers-'));
    const qualifiedPath = path.join(tmpDir, 'qualified.jsonl');

    await fs.writeFile(
      qualifiedPath,
      JSON.stringify({
        timestamp: '2026-01-01T00:00:00.000Z',
        type: 'assistant',
        message: {
          role: 'assistant',
          content: [
            {
              type: 'tool_use',
              name: 'mcp__agent-teams__task_start',
              input: { teamName: 'demo', taskId: 'task-42' },
            },
          ],
        },
      }) + '\n',
      'utf8'
    );

    const finder = new TeamMemberLogsFinder();

    await expect(finder.hasTaskUpdateMarker(qualifiedPath, 'task-42')).resolves.toBe(true);
  });

  it('findLogFileRefsForTask returns correct refs for a task', async () => {
    tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'claude-team-refs-'));
    setClaudeBasePathOverride(tmpDir);

    const teamName = 'refs-team';
    const projectPath = '/Users/test/ref-proj';
    const projectId = '-Users-test-ref-proj';
    const sessionId = 'sr1';

    await fs.mkdir(path.join(tmpDir, 'teams', teamName), { recursive: true });
    await fs.writeFile(
      path.join(tmpDir, 'teams', teamName, 'config.json'),
      JSON.stringify({
        name: teamName,
        projectPath,
        members: [
          { name: 'team-lead', agentType: 'team-lead' },
          { name: 'dev', agentType: 'general-purpose' },
        ],
      }),
      'utf8'
    );

    const projectRoot = path.join(tmpDir, 'projects', projectId);
    await fs.mkdir(path.join(projectRoot, sessionId, 'subagents'), { recursive: true });

    await fs.writeFile(
      path.join(projectRoot, sessionId, 'subagents', 'agent-ref1.jsonl'),
      [
        JSON.stringify({
          timestamp: '2026-01-01T00:00:01.000Z',
          type: 'user',
          message: {
            role: 'user',
            content: 'You are dev, a developer on team "refs-team" (refs-team).',
          },
        }),
        JSON.stringify({
          timestamp: '2026-01-01T00:00:02.000Z',
          type: 'assistant',
          message: {
            role: 'assistant',
            content: [
              {
                type: 'tool_use',
                name: 'TaskUpdate',
                input: { taskId: '5', status: 'in_progress' },
              },
            ],
          },
        }),
      ].join('\n') + '\n',
      'utf8'
    );

    const finder = new TeamMemberLogsFinder();
    const refs = await finder.findLogFileRefsForTask(teamName, '5');

    expect(refs).toHaveLength(1);
    expect(refs[0].memberName.toLowerCase()).toBe('dev');
    expect(refs[0].filePath).toContain('agent-ref1.jsonl');
  });

  it('indexes task mentions without changing matching semantics', async () => {
    tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'claude-team-refs-index-'));
    setClaudeBasePathOverride(tmpDir);

    const teamName = 'mention-index-team';
    const projectPath = '/Users/test/mention-index-proj';
    const projectId = '-Users-test-mention-index-proj';
    const sessionId = 'six';
    const fullTaskId = 'abcdef12-1111-4222-8333-444444444444';

    await fs.mkdir(path.join(tmpDir, 'teams', teamName), { recursive: true });
    await fs.writeFile(
      path.join(tmpDir, 'teams', teamName, 'config.json'),
      JSON.stringify({
        name: teamName,
        projectPath,
        members: [
          { name: 'team-lead', agentType: 'team-lead' },
          { name: 'dev', agentType: 'general-purpose' },
        ],
      }),
      'utf8'
    );

    const projectRoot = path.join(tmpDir, 'projects', projectId);
    await fs.mkdir(path.join(projectRoot, sessionId, 'subagents'), { recursive: true });
    await fs.writeFile(
      path.join(projectRoot, sessionId, 'subagents', 'agent-indexed.jsonl'),
      [
        JSON.stringify({
          timestamp: '2026-01-01T00:00:01.000Z',
          type: 'user',
          message: {
            role: 'user',
            content: `You are dev, a developer on team "${teamName}" (${teamName}).`,
          },
        }),
        JSON.stringify({
          timestamp: '2026-01-01T00:00:02.000Z',
          type: 'assistant',
          message: {
            role: 'assistant',
            content: [
              { type: 'tool_use', name: 'TaskGet', input: { taskId: 'ignored-task' } },
              {
                type: 'tool_use',
                name: 'TaskUpdate',
                input: { taskId: fullTaskId.slice(0, 8), status: 'completed' },
              },
              {
                type: 'tool_use',
                name: 'TaskUpdate',
                input: { taskId: 'wrong-team-task', teamName: 'other-team', status: 'completed' },
              },
            ],
          },
        }),
      ].join('\n') + '\n',
      'utf8'
    );
    await fs.writeFile(
      path.join(projectRoot, sessionId, 'subagents', 'agent-no-team.jsonl'),
      [
        JSON.stringify({
          timestamp: '2026-01-01T00:00:03.000Z',
          type: 'assistant',
          message: {
            role: 'assistant',
            content: [
              {
                type: 'tool_use',
                name: 'TaskUpdate',
                input: { taskId: 'no-team-task', status: 'completed' },
              },
            ],
          },
        }),
      ].join('\n') + '\n',
      'utf8'
    );

    const finder = new TeamMemberLogsFinder();

    await expect(finder.findLogFileRefsForTask(teamName, fullTaskId)).resolves.toHaveLength(1);
    await expect(finder.findLogFileRefsForTask(teamName, 'ignored-task')).resolves.toHaveLength(0);
    await expect(finder.findLogFileRefsForTask(teamName, 'wrong-team-task')).resolves.toHaveLength(
      0
    );
    await expect(finder.findLogFileRefsForTask(teamName, 'no-team-task')).resolves.toHaveLength(0);
  });

  it('findLogFileRefsForTask does not mix tasks across teams sharing a projectPath', async () => {
    tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'claude-team-refs-cross-'));
    setClaudeBasePathOverride(tmpDir);

    const projectPath = '/Users/test/shared-ref-proj';
    const projectId = '-Users-test-shared-ref-proj';
    const sessionId = 'sref';
    const teamA = 'ref-a';
    const teamB = 'ref-b';

    await fs.mkdir(path.join(tmpDir, 'teams', teamA), { recursive: true });
    await fs.mkdir(path.join(tmpDir, 'teams', teamB), { recursive: true });

    await fs.writeFile(
      path.join(tmpDir, 'teams', teamA, 'config.json'),
      JSON.stringify({
        name: teamA,
        projectPath,
        members: [
          { name: 'team-lead', agentType: 'team-lead' },
          { name: 'alice', agentType: 'general-purpose' },
        ],
      }),
      'utf8'
    );
    await fs.writeFile(
      path.join(tmpDir, 'teams', teamB, 'config.json'),
      JSON.stringify({
        name: teamB,
        projectPath,
        members: [
          { name: 'team-lead', agentType: 'team-lead' },
          { name: 'bob', agentType: 'general-purpose' },
        ],
      }),
      'utf8'
    );

    const projectRoot = path.join(tmpDir, 'projects', projectId);
    await fs.mkdir(path.join(projectRoot, sessionId, 'subagents'), { recursive: true });

    // Team A agent with task 7
    await fs.writeFile(
      path.join(projectRoot, sessionId, 'subagents', 'agent-ra.jsonl'),
      [
        JSON.stringify({
          timestamp: '2026-01-01T00:00:01.000Z',
          type: 'user',
          message: { role: 'user', content: 'You are alice, a developer on team "ref-a" (ref-a).' },
        }),
        JSON.stringify({
          timestamp: '2026-01-01T00:00:02.000Z',
          type: 'assistant',
          message: {
            role: 'assistant',
            content: [
              { type: 'tool_use', name: 'TaskUpdate', input: { taskId: '7', status: 'completed' } },
            ],
          },
        }),
      ].join('\n') + '\n',
      'utf8'
    );

    // Team B agent with same task id 7
    await fs.writeFile(
      path.join(projectRoot, sessionId, 'subagents', 'agent-rb.jsonl'),
      [
        JSON.stringify({
          timestamp: '2026-01-01T00:00:03.000Z',
          type: 'user',
          message: { role: 'user', content: 'You are bob, a developer on team "ref-b" (ref-b).' },
        }),
        JSON.stringify({
          timestamp: '2026-01-01T00:00:04.000Z',
          type: 'assistant',
          message: {
            role: 'assistant',
            content: [
              { type: 'tool_use', name: 'TaskUpdate', input: { taskId: '7', status: 'completed' } },
            ],
          },
        }),
      ].join('\n') + '\n',
      'utf8'
    );

    const finder = new TeamMemberLogsFinder();
    const refsA = await finder.findLogFileRefsForTask(teamA, '7');

    expect(refsA.some((r) => r.memberName.toLowerCase() === 'alice')).toBe(true);
    expect(refsA.some((r) => r.memberName.toLowerCase() === 'bob')).toBe(false);
  });

  it('findLogFileRefsForTask does not duplicate refs for owner logs', async () => {
    tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'claude-team-refs-dedup-'));
    setClaudeBasePathOverride(tmpDir);

    const teamName = 'dedup-team';
    const projectPath = '/Users/test/dedup-proj';
    const projectId = '-Users-test-dedup-proj';
    const sessionId = 'sdd';

    await fs.mkdir(path.join(tmpDir, 'teams', teamName), { recursive: true });
    await fs.writeFile(
      path.join(tmpDir, 'teams', teamName, 'config.json'),
      JSON.stringify({
        name: teamName,
        projectPath,
        members: [
          { name: 'team-lead', agentType: 'team-lead' },
          { name: 'dev', agentType: 'general-purpose' },
        ],
      }),
      'utf8'
    );

    const projectRoot = path.join(tmpDir, 'projects', projectId);
    await fs.mkdir(path.join(projectRoot, sessionId, 'subagents'), { recursive: true });

    // Agent file that mentions task AND belongs to owner 'dev'
    await fs.writeFile(
      path.join(projectRoot, sessionId, 'subagents', 'agent-dd1.jsonl'),
      [
        JSON.stringify({
          timestamp: '2026-01-01T00:00:01.000Z',
          type: 'user',
          message: {
            role: 'user',
            content: 'You are dev, a developer on team "dedup-team" (dedup-team).',
          },
        }),
        JSON.stringify({
          timestamp: '2026-01-01T00:00:02.000Z',
          type: 'assistant',
          message: {
            role: 'assistant',
            content: [
              {
                type: 'tool_use',
                name: 'TaskUpdate',
                input: { taskId: '3', status: 'in_progress' },
              },
            ],
          },
        }),
      ].join('\n') + '\n',
      'utf8'
    );

    const finder = new TeamMemberLogsFinder();
    // File found as direct task hit AND as owner log — should appear once
    const refs = await finder.findLogFileRefsForTask(teamName, '3', {
      owner: 'dev',
      status: 'in_progress',
    });

    // Count refs with this file path — should be exactly 1
    const deduped = refs.filter((r) => r.filePath.includes('agent-dd1.jsonl'));
    expect(deduped).toHaveLength(1);
    expect(deduped[0].memberName.toLowerCase()).toBe('dev');
  });

  it('findMemberLogs returns results sorted by startTime descending', async () => {
    tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'claude-team-sort-'));
    setClaudeBasePathOverride(tmpDir);

    const teamName = 'sort-team';
    const projectPath = '/Users/test/sort-proj';
    const projectId = '-Users-test-sort-proj';
    const sessionId = 'ss1';

    await fs.mkdir(path.join(tmpDir, 'teams', teamName), { recursive: true });
    await fs.writeFile(
      path.join(tmpDir, 'teams', teamName, 'config.json'),
      JSON.stringify({
        name: teamName,
        projectPath,
        members: [
          { name: 'team-lead', agentType: 'team-lead' },
          { name: 'dev', agentType: 'general-purpose' },
        ],
      }),
      'utf8'
    );

    const projectRoot = path.join(tmpDir, 'projects', projectId);
    await fs.mkdir(path.join(projectRoot, sessionId, 'subagents'), { recursive: true });

    // 3 agent files with different startTimes: 00:03, 00:01, 00:02
    for (const [id, ts] of [
      ['agent-s1.jsonl', '2026-01-01T00:00:03.000Z'],
      ['agent-s2.jsonl', '2026-01-01T00:00:01.000Z'],
      ['agent-s3.jsonl', '2026-01-01T00:00:02.000Z'],
    ] as const) {
      await fs.writeFile(
        path.join(projectRoot, sessionId, 'subagents', id),
        [
          JSON.stringify({
            timestamp: ts,
            type: 'user',
            message: {
              role: 'user',
              content: `You are dev, a developer on team "${teamName}" (${teamName}).`,
            },
          }),
          JSON.stringify({
            timestamp: ts,
            type: 'assistant',
            message: { role: 'assistant', content: [{ type: 'text', text: 'OK' }] },
          }),
        ].join('\n') + '\n',
        'utf8'
      );
    }

    const finder = new TeamMemberLogsFinder();
    const logs = await finder.findMemberLogs(teamName, 'dev');

    expect(logs).toHaveLength(3);
    // Must be descending: 00:03, 00:02, 00:01
    const times = logs.map((l) => new Date(l.startTime).getTime());
    expect(times[0]).toBeGreaterThan(times[1]);
    expect(times[1]).toBeGreaterThan(times[2]);
  });
});
