getPrompt method

  1. @override
Future<List<ContentBlock>> getPrompt(
  1. String args,
  2. ToolUseContext context
)
override

Build the prompt content for this command.

Implementation

@override
Future<List<ContentBlock>> getPrompt(
  String args,
  ToolUseContext context,
) async {
  // Scan sessions, aggregate data, and generate insights.
  final scannedSessions = await scanAllSessions();
  final totalSessionsScanned = scannedSessions.length;

  // Load cached session metas and identify uncached sessions.
  final allMetas = <SessionMeta>[];
  final uncachedSessions = <LiteSessionInfo>[];
  const maxSessionsToLoad = 200;

  for (final sessionInfo in scannedSessions) {
    final cached = await loadCachedSessionMeta(sessionInfo.sessionId);
    if (cached != null) {
      allMetas.add(cached);
    } else if (uncachedSessions.length < maxSessionsToLoad) {
      uncachedSessions.add(sessionInfo);
    }
  }

  // Deduplicate session branches.
  final deduplicated = deduplicateSessionBranches(allMetas);

  // Filter substantive sessions.
  final substantive = deduplicated.where(isSubstantiveSession).toList();

  // Load cached facets.
  final facets = <String, SessionFacets>{};
  for (final meta in substantive) {
    final cached = await loadCachedFacets(meta.sessionId);
    if (cached != null) {
      facets[meta.sessionId] = cached;
    }
  }

  // Filter out minimal sessions.
  final nonMinimal = substantive
      .where((s) => !isMinimalSession(s.sessionId, facets))
      .toList();

  final substantiveFacets = <String, SessionFacets>{};
  for (final entry in facets.entries) {
    if (!isMinimalSession(entry.key, facets)) {
      substantiveFacets[entry.key] = entry.value;
    }
  }

  // Aggregate data.
  final aggregated = aggregateData(nonMinimal, substantiveFacets);
  aggregated.totalSessionsScanned = totalSessionsScanned;

  // Build stats line.
  final sessionLabel =
      aggregated.totalSessionsScanned != null &&
          aggregated.totalSessionsScanned! > aggregated.totalSessions
      ? '${aggregated.totalSessionsScanned} sessions total, '
            '${aggregated.totalSessions} analyzed'
      : '${aggregated.totalSessions} sessions';

  final stats = [
    sessionLabel,
    '${aggregated.totalMessages} messages',
    '${aggregated.totalDurationHours.round()}h',
    '${aggregated.gitCommits} commits',
  ].join(' . ');

  final htmlPath = p.join(getDataDir(), 'report.html');
  final reportUrl = 'file://$htmlPath';

  return [
    TextBlock(
      'The user just ran /insights to generate a usage report analyzing '
      'their Neomage sessions.\n\n'
      'Stats: $stats\n'
      'Date range: ${aggregated.dateRange.start} to ${aggregated.dateRange.end}\n'
      'Sessions with facets: ${aggregated.sessionsWithFacets}\n'
      'Report URL: $reportUrl\n\n'
      'Summarize the session analysis for the user. Include:\n'
      '- Total sessions and messages\n'
      '- Top languages used: ${_topEntries(aggregated.languages, 3)}\n'
      '- Top tools used: ${_topEntries(aggregated.toolCounts, 3)}\n'
      '- Top goals: ${_topEntries(aggregated.goalCategories, 3)}\n'
      '- Outcomes: ${_topEntries(aggregated.outcomes, 3)}\n'
      '- Friction types: ${_topEntries(aggregated.friction, 3)}\n'
      '- Days active: ${aggregated.daysActive}\n'
      '- Messages per day: ${aggregated.messagesPerDay}\n'
      '- Lines added/removed: +${aggregated.totalLinesAdded}/-${aggregated.totalLinesRemoved}\n'
      '- Multi-clauding events: ${aggregated.multiClauding.overlapEvents}\n\n'
      'End with: "Your shareable insights report is ready: $reportUrl"\n'
      'Ask if they want to dig into any section.',
    ),
  ];
}