* Handle codegraph_status
(args: Record<string, unknown>)
| 7989 | * Handle codegraph_status |
| 7990 | */ |
| 7991 | private async handleStatus(args: Record<string, unknown>): Promise<ToolResult> { |
| 7992 | let cg = this.getCodeGraph(args.projectPath as string | undefined); |
| 7993 | // Same trick as withStalenessNotice — when an explicit projectPath |
| 7994 | // resolves to the same project as the default session cg, prefer the |
| 7995 | // default so getPendingFiles() (only populated by the default's watcher) |
| 7996 | // is non-empty when there are pending edits. |
| 7997 | if (this.cg && cg !== this.cg) { |
| 7998 | try { |
| 7999 | if (resolvePath(this.cg.getProjectRoot()) === resolvePath(cg.getProjectRoot())) { |
| 8000 | cg = this.cg; |
| 8001 | } |
| 8002 | } catch { /* closed instance — leave as is */ } |
| 8003 | } |
| 8004 | const stats = cg.getStats(); |
| 8005 | |
| 8006 | // Warn when this index actually belongs to a different git working tree |
| 8007 | // (e.g. the server resolved up from a nested worktree to the main checkout). |
| 8008 | // Queries then reflect that tree's branch, not the worktree being edited. |
| 8009 | // status shows the verbose, multi-line form; the read tools get the compact |
| 8010 | // one-liner via withWorktreeNotice. Both share the cached detection. |
| 8011 | const mismatch = this.worktreeMismatchFor(args.projectPath as string | undefined); |
| 8012 | |
| 8013 | const lines: string[] = [ |
| 8014 | '**CodeGraph Status**', |
| 8015 | '', |
| 8016 | ]; |
| 8017 | if (mismatch) { |
| 8018 | lines.push(`> ⚠ ${worktreeMismatchWarning(mismatch).replace(/\n/g, '\n> ')}`, ''); |
| 8019 | } |
| 8020 | lines.push( |
| 8021 | `**Files indexed:** ${stats.fileCount}`, |
| 8022 | `**Total nodes:** ${stats.nodeCount}`, |
| 8023 | `**Total edges:** ${stats.edgeCount}`, |
| 8024 | `**Database size:** ${(stats.dbSizeBytes / 1024 / 1024).toFixed(2)} MB`, |
| 8025 | ); |
| 8026 | |
| 8027 | // Exact CLI-parity change counts are measured on a worker: Git or the |
| 8028 | // filesystem fallback can stall on a large/busy checkout, but status must |
| 8029 | // not block the shared daemon's transport (#1959). Unknown is never zero. |
| 8030 | const lastIndexedAt = cg.getLastIndexedAt(); |
| 8031 | const changes = await measurePendingChanges(cg.getProjectRoot()); |
| 8032 | lines.push( |
| 8033 | `**Latest file indexed:** ${lastIndexedAt == null ? 'never' : new Date(lastIndexedAt).toISOString()}`, |
| 8034 | changes |
| 8035 | ? `**Changes since index:** ${changes.added} added, ${changes.modified} modified, ${changes.removed} removed` |
| 8036 | : '**Changes since index:** unknown (measurement timed out or failed; do not assume the index is current)', |
| 8037 | ); |
| 8038 | |
| 8039 | // Surface the active SQLite backend (node:sqlite, Node's built-in real |
| 8040 | // SQLite — full WAL + FTS5, no native build). |
| 8041 | lines.push(`**Backend:** node:sqlite (Node built-in) — full WAL + FTS5`); |
| 8042 | |
| 8043 | // Effective journal mode. 'wal' ⇒ concurrent reads never block on a writer; |
| 8044 | // anything else ⇒ they can ("database is locked"). node:sqlite supports WAL |
| 8045 | // everywhere, so a non-wal mode means the filesystem can't (network/ |
| 8046 | // virtualized mounts, WSL2 /mnt). See issue #238. |
| 8047 | const journalMode = cg.getJournalMode(); |
| 8048 | if (journalMode === 'wal') { |
no test coverage detected