fix(worktree): audit observation fetch/display for composite project names
Three sites didn't account for parent/worktree composite naming: - PaginationHelper.stripProjectPath: marker used full composite, breaking path sanitization for worktrees checked out outside a parent/leaf layout. Now extracts the leaf segment. - observations/store.ts: fallback imported getCurrentProjectName from shared/paths.ts (a duplicate impl without worktree detection). Switched to getProjectContext().primary so writes key into the same project as reads. - SearchManager.getRecentContext: fallback used basename(cwd) and lost the parent prefix, making the MCP tool find nothing in worktrees. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
File diff suppressed because one or more lines are too long
+129
-129
File diff suppressed because one or more lines are too long
@@ -6,7 +6,7 @@
|
|||||||
import { createHash } from 'crypto';
|
import { createHash } from 'crypto';
|
||||||
import { Database } from 'bun:sqlite';
|
import { Database } from 'bun:sqlite';
|
||||||
import { logger } from '../../../utils/logger.js';
|
import { logger } from '../../../utils/logger.js';
|
||||||
import { getCurrentProjectName } from '../../../shared/paths.js';
|
import { getProjectContext } from '../../../utils/project-name.js';
|
||||||
import type { ObservationInput, StoreObservationResult } from './types.js';
|
import type { ObservationInput, StoreObservationResult } from './types.js';
|
||||||
|
|
||||||
/** Deduplication window: observations with the same content hash within this window are skipped */
|
/** Deduplication window: observations with the same content hash within this window are skipped */
|
||||||
@@ -62,7 +62,7 @@ export function storeObservation(
|
|||||||
const timestampIso = new Date(timestampEpoch).toISOString();
|
const timestampIso = new Date(timestampEpoch).toISOString();
|
||||||
|
|
||||||
// Guard against empty project string (race condition where project isn't set yet)
|
// Guard against empty project string (race condition where project isn't set yet)
|
||||||
const resolvedProject = project || getCurrentProjectName();
|
const resolvedProject = project || getProjectContext(process.cwd()).primary;
|
||||||
|
|
||||||
// Content-hash deduplication
|
// Content-hash deduplication
|
||||||
const contentHash = computeObservationContentHash(memorySessionId, observation.title, observation.narrative);
|
const contentHash = computeObservationContentHash(memorySessionId, observation.title, observation.narrative);
|
||||||
|
|||||||
@@ -24,15 +24,17 @@ export class PaginationHelper {
|
|||||||
* Uses first occurrence of project name from left (project root)
|
* Uses first occurrence of project name from left (project root)
|
||||||
*/
|
*/
|
||||||
private stripProjectPath(filePath: string, projectName: string): string {
|
private stripProjectPath(filePath: string, projectName: string): string {
|
||||||
const marker = `/${projectName}/`;
|
// Composite names ("parent/worktree") don't appear in on-disk paths for
|
||||||
|
// standard git worktrees — only the checkout basename does. Match on the
|
||||||
|
// leaf segment so the heuristic works regardless of worktree layout.
|
||||||
|
const leaf = projectName.includes('/') ? projectName.split('/').pop()! : projectName;
|
||||||
|
const marker = `/${leaf}/`;
|
||||||
const index = filePath.indexOf(marker);
|
const index = filePath.indexOf(marker);
|
||||||
|
|
||||||
if (index !== -1) {
|
if (index !== -1) {
|
||||||
// Strip everything before and including the project name
|
|
||||||
return filePath.substring(index + marker.length);
|
return filePath.substring(index + marker.length);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fallback: return original path if project name not found
|
|
||||||
return filePath;
|
return filePath;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,6 @@
|
|||||||
* - TimelineBuilder: Timeline construction
|
* - TimelineBuilder: Timeline construction
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { basename } from 'path';
|
|
||||||
import { SessionSearch } from '../sqlite/SessionSearch.js';
|
import { SessionSearch } from '../sqlite/SessionSearch.js';
|
||||||
import { SessionStore } from '../sqlite/SessionStore.js';
|
import { SessionStore } from '../sqlite/SessionStore.js';
|
||||||
import { ChromaSync } from '../sync/ChromaSync.js';
|
import { ChromaSync } from '../sync/ChromaSync.js';
|
||||||
@@ -22,6 +21,7 @@ import { TimelineService } from './TimelineService.js';
|
|||||||
import type { TimelineItem } from './TimelineService.js';
|
import type { TimelineItem } from './TimelineService.js';
|
||||||
import type { ObservationSearchResult, SessionSummarySearchResult, UserPromptSearchResult } from '../sqlite/types.js';
|
import type { ObservationSearchResult, SessionSummarySearchResult, UserPromptSearchResult } from '../sqlite/types.js';
|
||||||
import { logger } from '../../utils/logger.js';
|
import { logger } from '../../utils/logger.js';
|
||||||
|
import { getProjectContext } from '../../utils/project-name.js';
|
||||||
import { formatDate, formatTime, formatDateTime, extractFirstFile, groupByDate, estimateTokens } from '../../shared/timeline-formatting.js';
|
import { formatDate, formatTime, formatDateTime, extractFirstFile, groupByDate, estimateTokens } from '../../shared/timeline-formatting.js';
|
||||||
import { ModeManager } from '../domain/ModeManager.js';
|
import { ModeManager } from '../domain/ModeManager.js';
|
||||||
|
|
||||||
@@ -1319,7 +1319,7 @@ export class SearchManager {
|
|||||||
* Tool handler: get_recent_context
|
* Tool handler: get_recent_context
|
||||||
*/
|
*/
|
||||||
async getRecentContext(args: any): Promise<any> {
|
async getRecentContext(args: any): Promise<any> {
|
||||||
const project = args.project || basename(process.cwd());
|
const project = args.project || getProjectContext(process.cwd()).primary;
|
||||||
const limit = args.limit || 3;
|
const limit = args.limit || 3;
|
||||||
|
|
||||||
const sessions = this.sessionStore.getRecentSessionsWithStatus(project, limit);
|
const sessions = this.sessionStore.getRecentSessionsWithStatus(project, limit);
|
||||||
|
|||||||
Reference in New Issue
Block a user