@gitkraken/core-gitlens 0.5.113 → 0.5.115
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.
- package/CHANGELOG.md +42 -1
- package/dist/git/cache.d.ts +4 -0
- package/dist/git/cache.d.ts.map +1 -1
- package/dist/git/cache.js +33 -0
- package/dist/git/cache.js.map +1 -1
- package/dist/git/context.d.ts +10 -0
- package/dist/git/context.d.ts.map +1 -1
- package/dist/git/context.js.map +1 -1
- package/dist/git/errors.d.ts +6 -3
- package/dist/git/errors.d.ts.map +1 -1
- package/dist/git/errors.js +9 -1
- package/dist/git/errors.js.map +1 -1
- package/dist/git/features.d.ts +1 -1
- package/dist/git/features.d.ts.map +1 -1
- package/dist/git/features.js +38 -0
- package/dist/git/features.js.map +1 -1
- package/dist/git/gitHealth.d.ts +153 -0
- package/dist/git/gitHealth.d.ts.map +1 -0
- package/dist/git/gitHealth.js +353 -0
- package/dist/git/gitHealth.js.map +1 -0
- package/dist/git/models/search.d.ts +6 -3
- package/dist/git/models/search.d.ts.map +1 -1
- package/dist/git/models/search.js +5 -1
- package/dist/git/models/search.js.map +1 -1
- package/dist/git/parsers/diffParser.d.ts +8 -2
- package/dist/git/parsers/diffParser.d.ts.map +1 -1
- package/dist/git/parsers/diffParser.js +27 -4
- package/dist/git/parsers/diffParser.js.map +1 -1
- package/dist/git/providers/config.d.ts +36 -3
- package/dist/git/providers/config.d.ts.map +1 -1
- package/dist/git/providers/graph.d.ts +9 -0
- package/dist/git/providers/graph.d.ts.map +1 -1
- package/dist/git/providers/maintenance.d.ts +222 -0
- package/dist/git/providers/maintenance.d.ts.map +1 -0
- package/dist/git/providers/maintenance.js +20 -0
- package/dist/git/providers/maintenance.js.map +1 -0
- package/dist/git/providers/operations.d.ts +10 -0
- package/dist/git/providers/operations.d.ts.map +1 -1
- package/dist/git/providers/pausedOperations.d.ts +3 -0
- package/dist/git/providers/pausedOperations.d.ts.map +1 -1
- package/dist/git/providers/provider.d.ts +2 -0
- package/dist/git/providers/provider.d.ts.map +1 -1
- package/dist/git/providers/tags.d.ts +3 -0
- package/dist/git/providers/tags.d.ts.map +1 -1
- package/dist/git/repositoryService.d.ts +1 -0
- package/dist/git/repositoryService.d.ts.map +1 -1
- package/dist/git/repositoryService.js.map +1 -1
- package/dist/git/run.types.d.ts +8 -0
- package/dist/git/run.types.d.ts.map +1 -1
- package/dist/git/service.js +3 -0
- package/dist/git/service.js.map +1 -1
- package/dist/git/utils/search.utils.d.ts.map +1 -1
- package/dist/git/utils/search.utils.js +112 -5
- package/dist/git/utils/search.utils.js.map +1 -1
- package/dist/git/watching/watchService.d.ts +3 -0
- package/dist/git/watching/watchService.d.ts.map +1 -1
- package/dist/git/watching/watchService.js +8 -0
- package/dist/git/watching/watchService.js.map +1 -1
- package/dist/git-cli/cliGitProvider.d.ts +11 -0
- package/dist/git-cli/cliGitProvider.d.ts.map +1 -1
- package/dist/git-cli/cliGitProvider.js +12 -0
- package/dist/git-cli/cliGitProvider.js.map +1 -1
- package/dist/git-cli/exec/git.d.ts +23 -1
- package/dist/git-cli/exec/git.d.ts.map +1 -1
- package/dist/git-cli/exec/git.js +83 -8
- package/dist/git-cli/exec/git.js.map +1 -1
- package/dist/git-cli/exec/gitQueue.d.ts +2 -0
- package/dist/git-cli/exec/gitQueue.d.ts.map +1 -1
- package/dist/git-cli/exec/gitQueue.js +19 -17
- package/dist/git-cli/exec/gitQueue.js.map +1 -1
- package/dist/git-cli/parsers/logParser.d.ts.map +1 -1
- package/dist/git-cli/parsers/logParser.js +4 -1
- package/dist/git-cli/parsers/logParser.js.map +1 -1
- package/dist/git-cli/providers/blame.d.ts.map +1 -1
- package/dist/git-cli/providers/blame.js +4 -7
- package/dist/git-cli/providers/blame.js.map +1 -1
- package/dist/git-cli/providers/branches.d.ts.map +1 -1
- package/dist/git-cli/providers/branches.js +13 -3
- package/dist/git-cli/providers/branches.js.map +1 -1
- package/dist/git-cli/providers/commits.d.ts.map +1 -1
- package/dist/git-cli/providers/commits.js +34 -3
- package/dist/git-cli/providers/commits.js.map +1 -1
- package/dist/git-cli/providers/config.d.ts +27 -0
- package/dist/git-cli/providers/config.d.ts.map +1 -1
- package/dist/git-cli/providers/config.js +31 -6
- package/dist/git-cli/providers/config.js.map +1 -1
- package/dist/git-cli/providers/graph.d.ts +8 -0
- package/dist/git-cli/providers/graph.d.ts.map +1 -1
- package/dist/git-cli/providers/graph.js +85 -6
- package/dist/git-cli/providers/graph.js.map +1 -1
- package/dist/git-cli/providers/maintenance.d.ts +306 -0
- package/dist/git-cli/providers/maintenance.d.ts.map +1 -0
- package/dist/git-cli/providers/maintenance.js +2129 -0
- package/dist/git-cli/providers/maintenance.js.map +1 -0
- package/dist/git-cli/providers/operations.d.ts +1 -0
- package/dist/git-cli/providers/operations.d.ts.map +1 -1
- package/dist/git-cli/providers/operations.js +8 -0
- package/dist/git-cli/providers/operations.js.map +1 -1
- package/dist/git-cli/providers/pausedOperations.d.ts +1 -0
- package/dist/git-cli/providers/pausedOperations.d.ts.map +1 -1
- package/dist/git-cli/providers/pausedOperations.js +1 -1
- package/dist/git-cli/providers/pausedOperations.js.map +1 -1
- package/dist/git-cli/providers/tags.d.ts +3 -0
- package/dist/git-cli/providers/tags.d.ts.map +1 -1
- package/dist/git-cli/providers/tags.js +23 -1
- package/dist/git-cli/providers/tags.js.map +1 -1
- package/dist/plus/agents/agentCapabilities.d.ts +77 -0
- package/dist/plus/agents/agentCapabilities.d.ts.map +1 -0
- package/dist/plus/agents/agentCapabilities.js +197 -0
- package/dist/plus/agents/agentCapabilities.js.map +1 -0
- package/dist/plus/agents/providers/claudeCodeTranscript.d.ts +77 -13
- package/dist/plus/agents/providers/claudeCodeTranscript.d.ts.map +1 -1
- package/dist/plus/agents/providers/claudeCodeTranscript.js +263 -55
- package/dist/plus/agents/providers/claudeCodeTranscript.js.map +1 -1
- package/dist/plus/agents/providers/{claudeCodeProvider.d.ts → gkAgentProvider.d.ts} +52 -19
- package/dist/plus/agents/providers/gkAgentProvider.d.ts.map +1 -0
- package/dist/plus/agents/providers/{claudeCodeProvider.js → gkAgentProvider.js} +935 -262
- package/dist/plus/agents/providers/gkAgentProvider.js.map +1 -0
- package/dist/plus/agents/types.d.ts +113 -35
- package/dist/plus/agents/types.d.ts.map +1 -1
- package/dist/plus/agents/types.js +7 -4
- package/dist/plus/agents/types.js.map +1 -1
- package/dist/plus/ai/models/promptTemplates.d.ts +1 -1
- package/dist/plus/ai/models/promptTemplates.d.ts.map +1 -1
- package/dist/plus/ai/prompts.d.ts +4 -0
- package/dist/plus/ai/prompts.d.ts.map +1 -1
- package/dist/plus/ai/prompts.js +59 -4
- package/dist/plus/ai/prompts.js.map +1 -1
- package/dist/plus/git-github/api/github.d.ts +44 -1
- package/dist/plus/git-github/api/github.d.ts.map +1 -1
- package/dist/plus/git-github/api/github.js +355 -62
- package/dist/plus/git-github/api/github.js.map +1 -1
- package/dist/plus/git-github/api/issueSearchQuery.d.ts +13 -0
- package/dist/plus/git-github/api/issueSearchQuery.d.ts.map +1 -1
- package/dist/plus/git-github/api/issueSearchQuery.js +41 -0
- package/dist/plus/git-github/api/issueSearchQuery.js.map +1 -1
- package/dist/plus/git-github/providers/github/blame.d.ts.map +1 -1
- package/dist/plus/git-github/providers/github/blame.js +2 -1
- package/dist/plus/git-github/providers/github/blame.js.map +1 -1
- package/dist/plus/integrations/collectionMetadata.d.ts +2 -0
- package/dist/plus/integrations/collectionMetadata.d.ts.map +1 -1
- package/dist/plus/integrations/collectionMetadata.js +17 -1
- package/dist/plus/integrations/collectionMetadata.js.map +1 -1
- package/dist/plus/integrations/context.d.ts +4 -1
- package/dist/plus/integrations/context.d.ts.map +1 -1
- package/dist/plus/integrations/index.d.ts +1 -0
- package/dist/plus/integrations/index.d.ts.map +1 -1
- package/dist/plus/integrations/integrationService.d.ts +20 -1
- package/dist/plus/integrations/integrationService.d.ts.map +1 -1
- package/dist/plus/integrations/integrationService.js +12 -4
- package/dist/plus/integrations/integrationService.js.map +1 -1
- package/dist/plus/integrations/manager.d.ts +48 -0
- package/dist/plus/integrations/manager.d.ts.map +1 -1
- package/dist/plus/integrations/models/gitHostIntegration.d.ts +53 -26
- package/dist/plus/integrations/models/gitHostIntegration.d.ts.map +1 -1
- package/dist/plus/integrations/models/gitHostIntegration.js +123 -47
- package/dist/plus/integrations/models/gitHostIntegration.js.map +1 -1
- package/dist/plus/integrations/models/integration.d.ts +10 -1
- package/dist/plus/integrations/models/integration.d.ts.map +1 -1
- package/dist/plus/integrations/models/integration.js +52 -19
- package/dist/plus/integrations/models/integration.js.map +1 -1
- package/dist/plus/integrations/models/issueCache.d.ts +16 -0
- package/dist/plus/integrations/models/issueCache.d.ts.map +1 -0
- package/dist/plus/integrations/models/issueCache.js +9 -0
- package/dist/plus/integrations/models/issueCache.js.map +1 -0
- package/dist/plus/integrations/models/issueReads.d.ts +13 -0
- package/dist/plus/integrations/models/issueReads.d.ts.map +1 -1
- package/dist/plus/integrations/models/issuesIntegration.d.ts +11 -13
- package/dist/plus/integrations/models/issuesIntegration.d.ts.map +1 -1
- package/dist/plus/integrations/models/issuesIntegration.js +15 -1
- package/dist/plus/integrations/models/issuesIntegration.js.map +1 -1
- package/dist/plus/integrations/providers/azureDevOps.d.ts +3 -5
- package/dist/plus/integrations/providers/azureDevOps.d.ts.map +1 -1
- package/dist/plus/integrations/providers/azureDevOps.js +9 -8
- package/dist/plus/integrations/providers/azureDevOps.js.map +1 -1
- package/dist/plus/integrations/providers/bitbucket-server.d.ts +3 -5
- package/dist/plus/integrations/providers/bitbucket-server.d.ts.map +1 -1
- package/dist/plus/integrations/providers/bitbucket-server.js +2 -2
- package/dist/plus/integrations/providers/bitbucket-server.js.map +1 -1
- package/dist/plus/integrations/providers/bitbucket.d.ts +3 -5
- package/dist/plus/integrations/providers/bitbucket.d.ts.map +1 -1
- package/dist/plus/integrations/providers/bitbucket.js +2 -1
- package/dist/plus/integrations/providers/bitbucket.js.map +1 -1
- package/dist/plus/integrations/providers/github.d.ts +12 -5
- package/dist/plus/integrations/providers/github.d.ts.map +1 -1
- package/dist/plus/integrations/providers/github.js +10 -3
- package/dist/plus/integrations/providers/github.js.map +1 -1
- package/dist/plus/integrations/providers/gitlab.d.ts +3 -5
- package/dist/plus/integrations/providers/gitlab.d.ts.map +1 -1
- package/dist/plus/integrations/providers/gitlab.js +2 -2
- package/dist/plus/integrations/providers/gitlab.js.map +1 -1
- package/dist/plus/integrations/providers/jira.d.ts +3 -7
- package/dist/plus/integrations/providers/jira.d.ts.map +1 -1
- package/dist/plus/integrations/providers/jira.js +35 -22
- package/dist/plus/integrations/providers/jira.js.map +1 -1
- package/dist/plus/integrations/providers/jiraIssueByKey.d.ts +7 -0
- package/dist/plus/integrations/providers/jiraIssueByKey.d.ts.map +1 -0
- package/dist/plus/integrations/providers/jiraIssueByKey.js +102 -0
- package/dist/plus/integrations/providers/jiraIssueByKey.js.map +1 -0
- package/dist/plus/integrations/providers/linear.d.ts +3 -7
- package/dist/plus/integrations/providers/linear.d.ts.map +1 -1
- package/dist/plus/integrations/providers/linear.js +23 -22
- package/dist/plus/integrations/providers/linear.js.map +1 -1
- package/dist/plus/integrations/providers/models.d.ts +3 -1
- package/dist/plus/integrations/providers/models.d.ts.map +1 -1
- package/dist/plus/integrations/providers/models.js +10 -4
- package/dist/plus/integrations/providers/models.js.map +1 -1
- package/dist/plus/integrations/providers/providerErrors.d.ts +7 -0
- package/dist/plus/integrations/providers/providerErrors.d.ts.map +1 -0
- package/dist/plus/integrations/providers/providerErrors.js +91 -0
- package/dist/plus/integrations/providers/providerErrors.js.map +1 -0
- package/dist/plus/integrations/providers/providersApi.d.ts +3 -2
- package/dist/plus/integrations/providers/providersApi.d.ts.map +1 -1
- package/dist/plus/integrations/providers/providersApi.js +18 -52
- package/dist/plus/integrations/providers/providersApi.js.map +1 -1
- package/dist/plus/integrations/providers/trello.d.ts +2 -7
- package/dist/plus/integrations/providers/trello.d.ts.map +1 -1
- package/dist/plus/integrations/providers/trello.js +3 -1
- package/dist/plus/integrations/providers/trello.js.map +1 -1
- package/dist/plus/integrations/providers/utils/providerPaging.d.ts +2 -0
- package/dist/plus/integrations/providers/utils/providerPaging.d.ts.map +1 -1
- package/dist/plus/integrations/providers/utils/providerPaging.js +12 -7
- package/dist/plus/integrations/providers/utils/providerPaging.js.map +1 -1
- package/dist/plus/integrations/reads/broaden.d.ts +47 -10
- package/dist/plus/integrations/reads/broaden.d.ts.map +1 -1
- package/dist/plus/integrations/reads/broaden.js +144 -33
- package/dist/plus/integrations/reads/broaden.js.map +1 -1
- package/dist/plus/integrations/reads/filters.d.ts +18 -0
- package/dist/plus/integrations/reads/filters.d.ts.map +1 -1
- package/dist/plus/integrations/reads/filters.js +37 -1
- package/dist/plus/integrations/reads/filters.js.map +1 -1
- package/dist/plus/integrations/reads/issueBatch.d.ts +51 -0
- package/dist/plus/integrations/reads/issueBatch.d.ts.map +1 -0
- package/dist/plus/integrations/reads/issueBatch.js +91 -0
- package/dist/plus/integrations/reads/issueBatch.js.map +1 -0
- package/dist/plus/integrations/reads/issueTracker.d.ts.map +1 -1
- package/dist/plus/integrations/reads/issueTracker.js +23 -17
- package/dist/plus/integrations/reads/issueTracker.js.map +1 -1
- package/dist/plus/integrations/reads/sweeps.d.ts.map +1 -1
- package/dist/plus/integrations/reads/sweeps.js +5 -3
- package/dist/plus/integrations/reads/sweeps.js.map +1 -1
- package/dist/plus/integrations/reads/trackerIssue.d.ts +21 -0
- package/dist/plus/integrations/reads/trackerIssue.d.ts.map +1 -0
- package/dist/plus/integrations/reads/trackerIssue.js +51 -0
- package/dist/plus/integrations/reads/trackerIssue.js.map +1 -0
- package/dist/plus/integrations/reads/warnings.d.ts +2 -15
- package/dist/plus/integrations/reads/warnings.d.ts.map +1 -1
- package/dist/plus/integrations/reads/warnings.js +19 -1
- package/dist/plus/integrations/reads/warnings.js.map +1 -1
- package/dist/plus/integrations/results.d.ts +4 -20
- package/dist/plus/integrations/results.d.ts.map +1 -1
- package/dist/plus/integrations/results.js.map +1 -1
- package/dist/utils/promise.d.ts +2 -0
- package/dist/utils/promise.d.ts.map +1 -1
- package/dist/utils/promise.js +11 -0
- package/dist/utils/promise.js.map +1 -1
- package/dist/utils/promiseCache.d.ts +19 -0
- package/dist/utils/promiseCache.d.ts.map +1 -1
- package/dist/utils/promiseCache.js +30 -0
- package/dist/utils/promiseCache.js.map +1 -1
- package/dist/utils/resourceUsage.d.ts +5 -0
- package/dist/utils/resourceUsage.d.ts.map +1 -0
- package/dist/utils/resourceUsage.js +2 -0
- package/dist/utils/resourceUsage.js.map +1 -0
- package/dist/utils/string.d.ts +12 -0
- package/dist/utils/string.d.ts.map +1 -1
- package/dist/utils/string.js +36 -1
- package/dist/utils/string.js.map +1 -1
- package/docs/integrations.md +48 -33
- package/docs/kepler-read-api-parity.md +25 -4
- package/package.json +12 -4
- package/src/git/cache.ts +36 -0
- package/src/git/context.ts +11 -0
- package/src/git/errors.ts +13 -1
- package/src/git/features.ts +51 -0
- package/src/git/gitHealth.ts +522 -0
- package/src/git/models/search.ts +19 -4
- package/src/git/parsers/diffParser.ts +27 -4
- package/src/git/providers/config.ts +42 -1
- package/src/git/providers/graph.ts +12 -0
- package/src/git/providers/maintenance.ts +224 -0
- package/src/git/providers/operations.ts +10 -0
- package/src/git/providers/pausedOperations.ts +3 -0
- package/src/git/providers/provider.ts +2 -0
- package/src/git/providers/tags.ts +1 -0
- package/src/git/repositoryService.ts +1 -0
- package/src/git/run.types.ts +8 -0
- package/src/git/service.ts +3 -0
- package/src/git/utils/search.utils.ts +129 -5
- package/src/git/watching/watchService.ts +10 -0
- package/src/git-cli/cliGitProvider.ts +25 -0
- package/src/git-cli/exec/git.ts +120 -6
- package/src/git-cli/exec/gitQueue.ts +19 -17
- package/src/git-cli/parsers/logParser.ts +4 -1
- package/src/git-cli/providers/blame.ts +3 -8
- package/src/git-cli/providers/branches.ts +28 -8
- package/src/git-cli/providers/commits.ts +34 -2
- package/src/git-cli/providers/config.ts +38 -6
- package/src/git-cli/providers/graph.ts +107 -7
- package/src/git-cli/providers/maintenance.ts +2599 -0
- package/src/git-cli/providers/operations.ts +11 -0
- package/src/git-cli/providers/pausedOperations.ts +7 -2
- package/src/git-cli/providers/tags.ts +36 -1
- package/src/plus/agents/agentCapabilities.ts +286 -0
- package/src/plus/agents/providers/claudeCodeTranscript.ts +313 -58
- package/src/plus/agents/providers/{claudeCodeProvider.ts → gkAgentProvider.ts} +1123 -277
- package/src/plus/agents/types.ts +137 -40
- package/src/plus/ai/models/promptTemplates.ts +1 -1
- package/src/plus/ai/prompts.ts +62 -4
- package/src/plus/git-github/api/github.ts +404 -82
- package/src/plus/git-github/api/issueSearchQuery.ts +49 -1
- package/src/plus/git-github/providers/github/blame.ts +2 -1
- package/src/plus/integrations/collectionMetadata.ts +23 -1
- package/src/plus/integrations/context.ts +1 -1
- package/src/plus/integrations/index.ts +1 -0
- package/src/plus/integrations/integrationService.ts +29 -4
- package/src/plus/integrations/manager.ts +48 -0
- package/src/plus/integrations/models/gitHostIntegration.ts +165 -129
- package/src/plus/integrations/models/integration.ts +57 -22
- package/src/plus/integrations/models/issueCache.ts +29 -0
- package/src/plus/integrations/models/issueReads.ts +15 -0
- package/src/plus/integrations/models/issuesIntegration.ts +38 -8
- package/src/plus/integrations/providers/azureDevOps.ts +16 -13
- package/src/plus/integrations/providers/bitbucket-server.ts +4 -6
- package/src/plus/integrations/providers/bitbucket.ts +3 -4
- package/src/plus/integrations/providers/github.ts +23 -7
- package/src/plus/integrations/providers/gitlab.ts +4 -6
- package/src/plus/integrations/providers/jira.ts +52 -26
- package/src/plus/integrations/providers/jiraIssueByKey.ts +155 -0
- package/src/plus/integrations/providers/linear.ts +38 -25
- package/src/plus/integrations/providers/models.ts +17 -5
- package/src/plus/integrations/providers/providerErrors.ts +115 -0
- package/src/plus/integrations/providers/providersApi.ts +26 -68
- package/src/plus/integrations/providers/trello.ts +5 -4
- package/src/plus/integrations/providers/utils/providerPaging.ts +14 -7
- package/src/plus/integrations/reads/broaden.ts +282 -70
- package/src/plus/integrations/reads/filters.ts +39 -1
- package/src/plus/integrations/reads/issueBatch.ts +197 -0
- package/src/plus/integrations/reads/issueTracker.ts +26 -24
- package/src/plus/integrations/reads/sweeps.ts +19 -12
- package/src/plus/integrations/reads/trackerIssue.ts +105 -0
- package/src/plus/integrations/reads/warnings.ts +23 -17
- package/src/plus/integrations/results.ts +4 -20
- package/src/utils/promise.ts +15 -0
- package/src/utils/promiseCache.ts +43 -0
- package/src/utils/resourceUsage.ts +5 -0
- package/src/utils/string.ts +38 -1
- package/dist/plus/agents/providers/claudeCodeProvider.d.ts.map +0 -1
- package/dist/plus/agents/providers/claudeCodeProvider.js.map +0 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createReadStream } from 'fs';
|
|
2
|
-
import { open, readdir, stat } from 'fs/promises';
|
|
2
|
+
import { open, readdir, readFile, stat } from 'fs/promises';
|
|
3
3
|
import { homedir } from 'os';
|
|
4
|
-
import { basename, join } from 'path';
|
|
4
|
+
import { basename, dirname, join } from 'path';
|
|
5
5
|
import { createInterface } from 'readline';
|
|
6
6
|
|
|
7
7
|
export interface TranscriptTitles {
|
|
@@ -31,7 +31,18 @@ export interface TranscriptSessionListing {
|
|
|
31
31
|
readonly total: number;
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
|
|
34
|
+
/** A transcript found outside the queried cwd's project directory, plus the directory Claude
|
|
35
|
+
* actually uses to resolve it for `--resume`. */
|
|
36
|
+
export interface ResumableTranscriptSessionSummary extends TranscriptSessionSummary {
|
|
37
|
+
readonly cwd: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface ResumableTranscriptSessionListing {
|
|
41
|
+
readonly sessions: ResumableTranscriptSessionSummary[];
|
|
42
|
+
readonly total: number;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface EndedTranscriptDetails {
|
|
35
46
|
titles: TranscriptTitles;
|
|
36
47
|
firstPrompt?: string;
|
|
37
48
|
lastPrompt?: string;
|
|
@@ -50,7 +61,13 @@ interface ListingCacheEntry {
|
|
|
50
61
|
resolvedAt: number;
|
|
51
62
|
}
|
|
52
63
|
|
|
64
|
+
interface NameCacheEntry {
|
|
65
|
+
names: string[];
|
|
66
|
+
resolvedAt: number;
|
|
67
|
+
}
|
|
68
|
+
|
|
53
69
|
interface SummaryCacheEntry {
|
|
70
|
+
path: string;
|
|
54
71
|
mtimeMs: number;
|
|
55
72
|
size: number;
|
|
56
73
|
summary: TranscriptSessionSummary;
|
|
@@ -72,12 +89,12 @@ const summaryWindowSize = 64 * 1024;
|
|
|
72
89
|
* Time-based rather than dir-mtime-based because appends move file mtimes without touching the dir. */
|
|
73
90
|
const listingCacheTtlMs = 10 * 1000;
|
|
74
91
|
/** Summaries are keyed by session, and a busy project has hundreds — unlike the per-live-session
|
|
75
|
-
* title cache, this needs a ceiling.
|
|
76
|
-
|
|
92
|
+
* title cache, this needs a ceiling. Sized to hold a whole junk-heavy store: the listing scan runs
|
|
93
|
+
* to exhaustion, and each entry is tiny (a path plus a few short strings), so a cap below the
|
|
94
|
+
* store's size would evict-and-re-read the junk tail on every listing instead of paying its
|
|
95
|
+
* windowed reads once. */
|
|
96
|
+
const summaryCacheLimit = 1000;
|
|
77
97
|
const defaultListLimit = 50;
|
|
78
|
-
/** How far past `limit` the top-up scan may read when transcripts turn out empty — a junk-filled
|
|
79
|
-
* store reads at most `limit + listScanSlack` summaries, staying well under {@link summaryCacheLimit}. */
|
|
80
|
-
const listScanSlack = 25;
|
|
81
98
|
|
|
82
99
|
/**
|
|
83
100
|
* Reads Claude Code transcript JSONL files at `~/.claude/projects/<encoded-cwd>/<sessionId>.jsonl`
|
|
@@ -95,9 +112,33 @@ export class ClaudeCodeTranscriptReader {
|
|
|
95
112
|
private readonly _cache = new Map<string, CacheEntry>();
|
|
96
113
|
private readonly _generations = new Map<string, number>();
|
|
97
114
|
private readonly _listings = new Map<string, ListingCacheEntry>();
|
|
115
|
+
/** Transcript file names per project directory — the readdir behind both the plain listing and
|
|
116
|
+
* the by-id scan, which sweeps every project directory. */
|
|
117
|
+
private readonly _names = new Map<string, NameCacheEntry>();
|
|
118
|
+
/** Project directories under {@link getProjectsRoot}, re-scanned by every by-id recovery. */
|
|
119
|
+
private _projectDirs: { dirs: string[]; resolvedAt: number } | undefined;
|
|
98
120
|
/** Insertion-ordered LRU — re-inserted on hit, oldest key evicted past {@link summaryCacheLimit}. */
|
|
99
121
|
private readonly _summaries = new Map<string, SummaryCacheEntry>();
|
|
122
|
+
/** Resolved project path per transcript path, bounded like {@link _summaries}. */
|
|
123
|
+
private readonly _transcriptCwds = new Map<string, string>();
|
|
100
124
|
private _nextGen = 0;
|
|
125
|
+
/** Bumped by {@link invalidateListings}; async listing scans capture it at entry and skip their
|
|
126
|
+
* cache write when it moved, so a stale in-flight result can't re-seed a just-cleared cache. */
|
|
127
|
+
private _listingGen = 0;
|
|
128
|
+
|
|
129
|
+
/** Drops the directory-listing caches (entries, names, project dirs) so the next listing re-reads
|
|
130
|
+
* the store. Called when a session ends or is removed: its transcript may have appeared inside
|
|
131
|
+
* the TTL window, and a cached listing would hide the new file from the very fetch meant to
|
|
132
|
+
* surface it. Summary and cwd caches are keyed by file identity and stay valid, so they're kept.
|
|
133
|
+
* The generation bump makes any in-flight listing skip its own cache write — otherwise a scan
|
|
134
|
+
* started before the invalidation would re-seed the caches with pre-end results, freshly
|
|
135
|
+
* timestamped (same discipline as `resolve`'s per-session generations). */
|
|
136
|
+
invalidateListings(): void {
|
|
137
|
+
this._listingGen++;
|
|
138
|
+
this._listings.clear();
|
|
139
|
+
this._names.clear();
|
|
140
|
+
this._projectDirs = undefined;
|
|
141
|
+
}
|
|
101
142
|
|
|
102
143
|
async resolve(sessionId: string, cwd: string | undefined): Promise<TranscriptTitles | undefined> {
|
|
103
144
|
const gen = ++this._nextGen;
|
|
@@ -155,9 +196,9 @@ export class ClaudeCodeTranscriptReader {
|
|
|
155
196
|
}
|
|
156
197
|
|
|
157
198
|
/**
|
|
158
|
-
* One-shot read for
|
|
199
|
+
* One-shot read for an *ended* (terminal, static) session: extracts the transcript titles plus
|
|
159
200
|
* the first and last user prompts. Unlike {@link resolve}, it bypasses the tail cache — a finished
|
|
160
|
-
* transcript never grows, so it's read once on demand (when
|
|
201
|
+
* transcript never grows, so it's read once on demand (when an ended row is opened) and the
|
|
161
202
|
* result applied to the session. `last-prompt` entries carry the prompt as of that point; the first
|
|
162
203
|
* one bearing a value is the session's opening prompt, the last its most recent. Returns `undefined`
|
|
163
204
|
* when no transcript is found (archived/purged) — the caller keeps whatever durable-store fields it
|
|
@@ -169,10 +210,7 @@ export class ClaudeCodeTranscriptReader {
|
|
|
169
210
|
* past any fixed head window, and a windowed read would then mistake a later prompt for the first,
|
|
170
211
|
* poisoning the derived session name.
|
|
171
212
|
*/
|
|
172
|
-
async
|
|
173
|
-
sessionId: string,
|
|
174
|
-
cwd: string | undefined,
|
|
175
|
-
): Promise<CompletedTranscriptDetails | undefined> {
|
|
213
|
+
async resolveEndedDetails(sessionId: string, cwd: string | undefined): Promise<EndedTranscriptDetails | undefined> {
|
|
176
214
|
const path = await this.locateTranscript(sessionId, cwd);
|
|
177
215
|
if (path == null) return undefined;
|
|
178
216
|
|
|
@@ -213,16 +251,17 @@ export class ClaudeCodeTranscriptReader {
|
|
|
213
251
|
|
|
214
252
|
/**
|
|
215
253
|
* Lists the transcripts of sessions whose working directory is `cwd`, most-recently-active first,
|
|
216
|
-
* summarizing until `limit` summarizable transcripts are found
|
|
217
|
-
*
|
|
218
|
-
*
|
|
219
|
-
*
|
|
220
|
-
*
|
|
254
|
+
* summarizing until `limit` summarizable transcripts are found — not just the first `limit` on
|
|
255
|
+
* disk, since junk transcripts (dropped below) would otherwise starve the result.
|
|
256
|
+
* `excludeSessionIds` is skipped before `limit` applies, and excluded entries are also dropped
|
|
257
|
+
* from `total` — they're already shown elsewhere (e.g. as live sessions), so a caller's "N of M"
|
|
258
|
+
* count must not double-count them.
|
|
221
259
|
*
|
|
222
|
-
* Claude homes a transcript under the directory encoding the
|
|
223
|
-
*
|
|
224
|
-
*
|
|
225
|
-
*
|
|
260
|
+
* Claude homes a transcript under the directory encoding the cwd from which the session started.
|
|
261
|
+
* That directory is authoritative for where `claude --resume <id>` can find the transcript, but
|
|
262
|
+
* Claude does not reliably move it when the session later changes worktrees. Callers that have a
|
|
263
|
+
* durable session-to-worktree association can recover those moved sessions with
|
|
264
|
+
* {@link listSessionsByIds}; this method intentionally remains the cheap legacy directory listing.
|
|
226
265
|
*
|
|
227
266
|
* Discovery (readdir + stat) covers the whole directory and is cheap; summarizing is not, so it's
|
|
228
267
|
* capped. Entries whose summary yields neither a title nor a prompt are dropped — those are aborted
|
|
@@ -240,37 +279,51 @@ export class ClaudeCodeTranscriptReader {
|
|
|
240
279
|
|
|
241
280
|
const limit = options?.limit ?? defaultListLimit;
|
|
242
281
|
const exclude = options?.excludeSessionIds;
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
const
|
|
246
|
-
const
|
|
247
|
-
|
|
248
|
-
|
|
282
|
+
// Zero-byte transcripts can never summarize — skip the read entirely and keep them out of
|
|
283
|
+
// `total`, which otherwise counts sessions no `limit` could ever surface.
|
|
284
|
+
const candidates = entries.filter(e => e.size > 0 && exclude?.has(e.sessionId) !== true);
|
|
285
|
+
const { sessions, exhausted } = await this.collectSessions(candidates, limit, async entry => {
|
|
286
|
+
const summary = await this.resolveSummary(entry);
|
|
287
|
+
return summary != null && hasSummaryContent(summary) ? summary : undefined;
|
|
288
|
+
});
|
|
249
289
|
|
|
250
|
-
|
|
251
|
-
|
|
290
|
+
// An exhausted scan proved the exact valid count; otherwise unscanned candidates keep
|
|
291
|
+
// `total` an upper bound so paging knows more MAY exist.
|
|
292
|
+
return { sessions: sessions, total: exhausted ? sessions.length : candidates.length };
|
|
293
|
+
}
|
|
252
294
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
295
|
+
/**
|
|
296
|
+
* Finds a known set of session ids across every Claude project directory except `excludeCwd`'s.
|
|
297
|
+
* This is the recovery path for durable CLI records whose worktree changed after Claude chose the
|
|
298
|
+
* transcript's project directory. The caller supplies the association; transcript contents alone
|
|
299
|
+
* are deliberately not used to guess which current worktree owns an arbitrary old session.
|
|
300
|
+
*
|
|
301
|
+
* Each result carries the project path encoded by the directory that actually holds the transcript,
|
|
302
|
+
* so resuming it does not repeat discovery from the wrong worktree. Duplicate ids are counted once,
|
|
303
|
+
* with the newest transcript winning.
|
|
304
|
+
*/
|
|
305
|
+
async listSessionsByIds(
|
|
306
|
+
sessionIds: ReadonlySet<string>,
|
|
307
|
+
options?: { limit?: number; excludeCwd?: string },
|
|
308
|
+
): Promise<ResumableTranscriptSessionListing> {
|
|
309
|
+
if (sessionIds.size === 0) return { sessions: [], total: 0 };
|
|
310
|
+
|
|
311
|
+
const excludeDir = await this.resolveProjectDir(options?.excludeCwd);
|
|
312
|
+
// Same zero-byte drop as `listSessions` — see the rationale there.
|
|
313
|
+
const entries = (await this.listEntriesByIds(sessionIds, excludeDir)).filter(e => e.size > 0);
|
|
314
|
+
if (entries.length === 0) return { sessions: [], total: 0 };
|
|
256
315
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
}
|
|
316
|
+
const limit = options?.limit ?? defaultListLimit;
|
|
317
|
+
const { sessions, exhausted } = await this.collectSessions(entries, limit, async entry => {
|
|
318
|
+
const summary = await this.resolveSummary(entry);
|
|
319
|
+
if (summary == null || !hasSummaryContent(summary)) return undefined;
|
|
262
320
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
// Clamped to the remaining ceiling budget too — a run of junk entries must not let a
|
|
267
|
-
// need-sized batch read past `limit + listScanSlack`.
|
|
268
|
-
const batch = candidates.slice(cursor, cursor + Math.min(limit - sessions.length, ceiling - cursor));
|
|
269
|
-
cursor += batch.length;
|
|
270
|
-
pushSummaries(await Promise.allSettled(batch.map(e => this.resolveSummary(e))));
|
|
271
|
-
}
|
|
321
|
+
const cwd = await this.resolveTranscriptCwd(entry);
|
|
322
|
+
return cwd != null ? { ...summary, cwd: cwd } : undefined;
|
|
323
|
+
});
|
|
272
324
|
|
|
273
|
-
|
|
325
|
+
// Exhausted scans prove the exact count — see `listSessions`.
|
|
326
|
+
return { sessions: sessions, total: exhausted ? sessions.length : entries.length };
|
|
274
327
|
}
|
|
275
328
|
|
|
276
329
|
/** Discovers every transcript in `dir`, newest first. The directory also holds one sibling
|
|
@@ -280,14 +333,27 @@ export class ClaudeCodeTranscriptReader {
|
|
|
280
333
|
const cached = this._listings.get(dir);
|
|
281
334
|
if (cached != null && Date.now() - cached.resolvedAt < listingCacheTtlMs) return cached.entries;
|
|
282
335
|
|
|
336
|
+
const gen = this._listingGen;
|
|
283
337
|
let names: string[];
|
|
284
338
|
try {
|
|
285
|
-
|
|
286
|
-
names = dirents.filter(d => d.isFile() && d.name.endsWith('.jsonl')).map(d => d.name);
|
|
339
|
+
names = await this.listTranscriptNames(dir);
|
|
287
340
|
} catch {
|
|
288
341
|
return [];
|
|
289
342
|
}
|
|
290
343
|
|
|
344
|
+
const entries = (await this.statEntries(dir, names)).sort((a, b) => b.lastActivityMs - a.lastActivityMs);
|
|
345
|
+
|
|
346
|
+
if (gen === this._listingGen) {
|
|
347
|
+
this._listings.set(dir, { entries: entries, resolvedAt: Date.now() });
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
return entries;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/** Stats `names` in `dir`, dropping the ones that failed — a transcript can vanish (or be
|
|
354
|
+
* archived) between the readdir and the stat. Unsorted; callers order as they need.
|
|
355
|
+
* Protected (like {@link readSummary}) so tests can interleave against the listing flow. */
|
|
356
|
+
protected async statEntries(dir: string, names: string[]): Promise<TranscriptSessionEntry[]> {
|
|
291
357
|
const settled = await Promise.allSettled(
|
|
292
358
|
names.map(async (name): Promise<TranscriptSessionEntry> => {
|
|
293
359
|
const path = join(dir, name);
|
|
@@ -301,19 +367,145 @@ export class ClaudeCodeTranscriptReader {
|
|
|
301
367
|
}),
|
|
302
368
|
);
|
|
303
369
|
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
.map(r => r.value)
|
|
307
|
-
.sort((a, b) => b.lastActivityMs - a.lastActivityMs);
|
|
370
|
+
return settled.filter(r => r.status === 'fulfilled').map(r => r.value);
|
|
371
|
+
}
|
|
308
372
|
|
|
309
|
-
|
|
310
|
-
|
|
373
|
+
/** The `.jsonl` file names in `dir`, cached for {@link listingCacheTtlMs}. Failures propagate
|
|
374
|
+
* uncached: an unreadable directory means different things to different callers, and the next
|
|
375
|
+
* call must be free to get a real answer. */
|
|
376
|
+
private async listTranscriptNames(dir: string): Promise<string[]> {
|
|
377
|
+
const cached = this._names.get(dir);
|
|
378
|
+
if (cached != null && Date.now() - cached.resolvedAt < listingCacheTtlMs) return cached.names;
|
|
379
|
+
|
|
380
|
+
const gen = this._listingGen;
|
|
381
|
+
const dirents = await readdir(dir, { withFileTypes: true });
|
|
382
|
+
const names = dirents.filter(d => d.isFile() && d.name.endsWith('.jsonl')).map(d => d.name);
|
|
383
|
+
if (gen === this._listingGen) {
|
|
384
|
+
this._names.set(dir, { names: names, resolvedAt: Date.now() });
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
return names;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/** Every project directory under the projects root, cached for {@link listingCacheTtlMs} — a
|
|
391
|
+
* by-id recovery sweeps all of them, once per queried worktree. */
|
|
392
|
+
private async listProjectDirs(): Promise<string[]> {
|
|
393
|
+
const cached = this._projectDirs;
|
|
394
|
+
if (cached != null && Date.now() - cached.resolvedAt < listingCacheTtlMs) return cached.dirs;
|
|
395
|
+
|
|
396
|
+
const gen = this._listingGen;
|
|
397
|
+
const root = this.getProjectsRoot();
|
|
398
|
+
const dirents = await readdir(root, { withFileTypes: true });
|
|
399
|
+
const dirs = dirents.filter(d => d.isDirectory()).map(d => join(root, d.name));
|
|
400
|
+
if (gen === this._listingGen) {
|
|
401
|
+
this._projectDirs = { dirs: dirs, resolvedAt: Date.now() };
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
return dirs;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
/** Scans project directory names once, then stats only requested transcript ids. */
|
|
408
|
+
private async listEntriesByIds(
|
|
409
|
+
sessionIds: ReadonlySet<string>,
|
|
410
|
+
excludeDir: string | undefined,
|
|
411
|
+
): Promise<TranscriptSessionEntry[]> {
|
|
412
|
+
let idsToFind = sessionIds;
|
|
413
|
+
if (excludeDir != null) {
|
|
414
|
+
try {
|
|
415
|
+
const names = await this.listTranscriptNames(excludeDir);
|
|
416
|
+
const idsInExcludedDir = new Set(names.map(name => basename(name, '.jsonl')));
|
|
417
|
+
idsToFind = new Set([...sessionIds].filter(id => !idsInExcludedDir.has(id)));
|
|
418
|
+
if (idsToFind.size === 0) return [];
|
|
419
|
+
} catch {
|
|
420
|
+
// If the exact directory can't be inspected, don't risk returning a duplicate from a
|
|
421
|
+
// stale transcript copy elsewhere. The ordinary listing still owns the exact path.
|
|
422
|
+
return [];
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
let dirs: string[];
|
|
427
|
+
try {
|
|
428
|
+
dirs = await this.listProjectDirs();
|
|
429
|
+
} catch (ex) {
|
|
430
|
+
if (!isMissingEntry(ex)) throw ex;
|
|
431
|
+
|
|
432
|
+
return [];
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
const settledDirs = await Promise.allSettled(
|
|
436
|
+
dirs.map(async dir => {
|
|
437
|
+
if (dir === excludeDir) return [];
|
|
438
|
+
|
|
439
|
+
const names = (await this.listTranscriptNames(dir)).filter(name =>
|
|
440
|
+
idsToFind.has(basename(name, '.jsonl')),
|
|
441
|
+
);
|
|
442
|
+
return this.statEntries(dir, names);
|
|
443
|
+
}),
|
|
444
|
+
);
|
|
445
|
+
|
|
446
|
+
const bySessionId = new Map<string, TranscriptSessionEntry>();
|
|
447
|
+
for (const settledDir of settledDirs) {
|
|
448
|
+
if (settledDir.status !== 'fulfilled') continue;
|
|
449
|
+
|
|
450
|
+
for (const entry of settledDir.value) {
|
|
451
|
+
const existing = bySessionId.get(entry.sessionId);
|
|
452
|
+
if (existing == null || entry.lastActivityMs > existing.lastActivityMs) {
|
|
453
|
+
bySessionId.set(entry.sessionId, entry);
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
return [...bySessionId.values()].sort((a, b) => b.lastActivityMs - a.lastActivityMs);
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
/** `exhausted` reports whether every candidate was scanned — an exhausted scan has proven the
|
|
462
|
+
* exact valid count, so callers can report it instead of an upper bound that would keep a
|
|
463
|
+
* "Show More" affordance alive with nothing left to show. */
|
|
464
|
+
private async collectSessions<T>(
|
|
465
|
+
candidates: readonly TranscriptSessionEntry[],
|
|
466
|
+
limit: number,
|
|
467
|
+
resolve: (entry: TranscriptSessionEntry) => Promise<T | undefined>,
|
|
468
|
+
): Promise<{ sessions: T[]; exhausted: boolean }> {
|
|
469
|
+
const sessions: T[] = [];
|
|
470
|
+
const pushResolved = (settled: PromiseSettledResult<T | undefined>[]): void => {
|
|
471
|
+
for (const result of settled) {
|
|
472
|
+
if (result.status !== 'fulfilled' || result.value == null) continue;
|
|
473
|
+
|
|
474
|
+
sessions.push(result.value);
|
|
475
|
+
}
|
|
476
|
+
};
|
|
477
|
+
|
|
478
|
+
// limit <= 0 means "no ceiling" — resolve every candidate.
|
|
479
|
+
if (limit <= 0) {
|
|
480
|
+
pushResolved(await Promise.allSettled(candidates.map(resolve)));
|
|
481
|
+
return { sessions: sessions, exhausted: true };
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
// Scans until `limit` results or the candidates run out — a fixed scan ceiling would let a run
|
|
485
|
+
// of junk transcripts hide every older real session behind it (and the picker then claims there
|
|
486
|
+
// are none at all). The cost stays bounded: callers pre-drop zero-byte candidates read-free, and
|
|
487
|
+
// a junk summary is windowed, tiny, and LRU-cached, so the sweep is paid in syscalls, once.
|
|
488
|
+
let cursor = 0;
|
|
489
|
+
while (sessions.length < limit && cursor < candidates.length) {
|
|
490
|
+
// Batched by remaining need so a hit-rich head stops the scan early instead of resolving
|
|
491
|
+
// every candidate up front.
|
|
492
|
+
const batch = candidates.slice(cursor, cursor + (limit - sessions.length));
|
|
493
|
+
cursor += batch.length;
|
|
494
|
+
pushResolved(await Promise.allSettled(batch.map(resolve)));
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
return { sessions: sessions, exhausted: cursor >= candidates.length };
|
|
311
498
|
}
|
|
312
499
|
|
|
313
500
|
/** Summarizes one transcript, reusing the cached result while its mtime and size are unchanged. */
|
|
314
501
|
private async resolveSummary(entry: TranscriptSessionEntry): Promise<TranscriptSessionSummary | undefined> {
|
|
315
502
|
const cached = this._summaries.get(entry.sessionId);
|
|
316
|
-
if (
|
|
503
|
+
if (
|
|
504
|
+
cached != null &&
|
|
505
|
+
cached.path === entry.path &&
|
|
506
|
+
cached.mtimeMs === entry.lastActivityMs &&
|
|
507
|
+
cached.size === entry.size
|
|
508
|
+
) {
|
|
317
509
|
// Refresh recency for the LRU.
|
|
318
510
|
this._summaries.delete(entry.sessionId);
|
|
319
511
|
this._summaries.set(entry.sessionId, cached);
|
|
@@ -328,6 +520,7 @@ export class ClaudeCodeTranscriptReader {
|
|
|
328
520
|
}
|
|
329
521
|
|
|
330
522
|
this._summaries.set(entry.sessionId, {
|
|
523
|
+
path: entry.path,
|
|
331
524
|
mtimeMs: entry.lastActivityMs,
|
|
332
525
|
size: entry.size,
|
|
333
526
|
summary: summary,
|
|
@@ -341,6 +534,68 @@ export class ClaudeCodeTranscriptReader {
|
|
|
341
534
|
return summary;
|
|
342
535
|
}
|
|
343
536
|
|
|
537
|
+
/** Recovers the project path represented by the transcript's containing directory. A resolved
|
|
538
|
+
* answer is cached: it's a line-scan of the whole transcript, and the directory holding a given
|
|
539
|
+
* path never changes. A miss isn't cached — a `cwd` line can still land on append. */
|
|
540
|
+
private async resolveTranscriptCwd(entry: TranscriptSessionEntry): Promise<string | undefined> {
|
|
541
|
+
const cached = this._transcriptCwds.get(entry.path);
|
|
542
|
+
if (cached != null) return cached;
|
|
543
|
+
|
|
544
|
+
const projectDir = dirname(entry.path);
|
|
545
|
+
const projectDirName = basename(projectDir);
|
|
546
|
+
const matchesProjectDir = (cwd: string): boolean =>
|
|
547
|
+
encodeProjectDirName(cwd).toLowerCase() === projectDirName.toLowerCase();
|
|
548
|
+
|
|
549
|
+
try {
|
|
550
|
+
const index = JSON.parse(await readFile(join(projectDir, 'sessions-index.json'), 'utf8')) as {
|
|
551
|
+
entries?: { sessionId?: unknown; projectPath?: unknown }[];
|
|
552
|
+
};
|
|
553
|
+
if (Array.isArray(index.entries)) {
|
|
554
|
+
const indexed = index.entries.find(item => item?.sessionId === entry.sessionId);
|
|
555
|
+
if (typeof indexed?.projectPath === 'string' && matchesProjectDir(indexed.projectPath)) {
|
|
556
|
+
this.rememberTranscriptCwd(entry.path, indexed.projectPath);
|
|
557
|
+
return indexed.projectPath;
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
} catch {
|
|
561
|
+
// The index is optional and can lag or be malformed. The transcript is authoritative below.
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
const rl = createInterface({ input: createReadStream(entry.path, { encoding: 'utf8' }), crlfDelay: Infinity });
|
|
565
|
+
try {
|
|
566
|
+
for await (const line of rl) {
|
|
567
|
+
if (!line.includes('"cwd"')) continue;
|
|
568
|
+
|
|
569
|
+
let parsed: { cwd?: unknown; sessionId?: unknown };
|
|
570
|
+
try {
|
|
571
|
+
parsed = JSON.parse(line) as { cwd?: unknown; sessionId?: unknown };
|
|
572
|
+
} catch {
|
|
573
|
+
continue;
|
|
574
|
+
}
|
|
575
|
+
if (parsed.sessionId != null && parsed.sessionId !== entry.sessionId) continue;
|
|
576
|
+
|
|
577
|
+
if (typeof parsed.cwd === 'string' && matchesProjectDir(parsed.cwd)) {
|
|
578
|
+
this.rememberTranscriptCwd(entry.path, parsed.cwd);
|
|
579
|
+
return parsed.cwd;
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
} finally {
|
|
583
|
+
rl.close();
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
return undefined;
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
private rememberTranscriptCwd(path: string, cwd: string): void {
|
|
590
|
+
this._transcriptCwds.set(path, cwd);
|
|
591
|
+
if (this._transcriptCwds.size > summaryCacheLimit) {
|
|
592
|
+
const oldest = this._transcriptCwds.keys().next();
|
|
593
|
+
if (!oldest.done) {
|
|
594
|
+
this._transcriptCwds.delete(oldest.value);
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
|
|
344
599
|
/**
|
|
345
600
|
* Reads a summary from the first and last {@link summaryWindowSize} bytes of the transcript.
|
|
346
601
|
*
|