mirror of
https://github.com/affaan-m/ECC.git
synced 2026-09-21 09:08:01 +02:00
fix(session-start): scope summary lookup to repository identity (#3160)
This commit is contained in:
@@ -11,7 +11,7 @@
|
||||
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const { getSessionsDir, getDateString, getTimeString, getSessionIdShort, sanitizeSessionId, getProjectName, ensureDir, readFile, writeFile, runCommand, stripAnsi, log } = require('../lib/utils');
|
||||
const { getSessionsDir, getDateString, getTimeString, getSessionIdShort, sanitizeSessionId, getProjectName, getRepoIdentity, ensureDir, readFile, writeFile, runCommand, stripAnsi, log } = require('../lib/utils');
|
||||
const { generateSessionSummary, getContextRemainingPct, getContextThreshold } = require('../lib/llm-summary');
|
||||
|
||||
const SUMMARY_START_MARKER = '<!-- ECC:SUMMARY:START -->';
|
||||
@@ -130,7 +130,8 @@ function getSessionMetadata() {
|
||||
return {
|
||||
project: getProjectName() || 'unknown',
|
||||
branch: branchResult.success ? branchResult.output : 'unknown',
|
||||
worktree: process.cwd()
|
||||
worktree: process.cwd(),
|
||||
repo: getRepoIdentity()
|
||||
};
|
||||
}
|
||||
|
||||
@@ -145,16 +146,20 @@ function buildSessionHeader(today, currentTime, metadata, existingContent = '')
|
||||
const date = extractHeaderField(existingContent, 'Date') || today;
|
||||
const started = extractHeaderField(existingContent, 'Started') || currentTime;
|
||||
|
||||
return [
|
||||
const lines = [
|
||||
heading,
|
||||
`**Date:** ${date}`,
|
||||
`**Started:** ${started}`,
|
||||
`**Last Updated:** ${currentTime}`,
|
||||
`**Project:** ${metadata.project}`,
|
||||
`**Branch:** ${metadata.branch}`,
|
||||
`**Worktree:** ${metadata.worktree}`,
|
||||
''
|
||||
].join('\n');
|
||||
`**Worktree:** ${metadata.worktree}`
|
||||
];
|
||||
if (metadata.repo) {
|
||||
lines.push(`**Repo:** ${metadata.repo}`);
|
||||
}
|
||||
lines.push('');
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
||||
function mergeSessionHeader(content, today, currentTime, metadata) {
|
||||
|
||||
@@ -14,6 +14,7 @@ const {
|
||||
getSessionSearchDirs,
|
||||
getLearnedSkillsDir,
|
||||
getProjectName,
|
||||
getRepoIdentity,
|
||||
findFiles,
|
||||
ensureDir,
|
||||
readFile,
|
||||
@@ -254,6 +255,7 @@ function pruneExpiredSessions(searchDirs, retentionDays) {
|
||||
* Session files written by session-end.js contain header fields like:
|
||||
* **Project:** my-project
|
||||
* **Worktree:** /path/to/project
|
||||
* **Repo:** /path/to/main-worktree/.git
|
||||
*
|
||||
* This function reads each session file once, caching its content, and
|
||||
* returns both the selected session object and its already-read content
|
||||
@@ -261,11 +263,18 @@ function pruneExpiredSessions(searchDirs, retentionDays) {
|
||||
*
|
||||
* Priority (highest to lowest):
|
||||
* 1. Exact worktree (cwd) match — most recent
|
||||
* 2. Same project name match for legacy sessions without Worktree metadata
|
||||
* 3. No injection when sessions belong to a different worktree/project
|
||||
* 2. Repository identity match: the session was recorded in another
|
||||
* worktree or subdirectory of the same repository. Identity is the
|
||||
* main worktree's common git dir (issue #3160), taken from the
|
||||
* recorded **Repo:** field or resolved from the recorded **Worktree:**
|
||||
* path for older session files. Unrelated repositories never match.
|
||||
* 3. Same project name match for legacy sessions without Worktree/Repo
|
||||
* metadata
|
||||
* 4. No injection when sessions belong to a different repository
|
||||
*
|
||||
* Sessions are already sorted newest-first, so the first match in each
|
||||
* category wins.
|
||||
* category wins; the scan continues past repository and project matches so
|
||||
* an exact worktree match always takes precedence.
|
||||
*
|
||||
* @param {Array<Object>} sessions - Deduplicated session list, sorted newest-first.
|
||||
* @param {string} cwd - Current working directory (process.cwd()).
|
||||
@@ -279,7 +288,17 @@ function selectMatchingSession(sessions, cwd, currentProject) {
|
||||
|
||||
// Normalize cwd once outside the loop to avoid repeated syscalls
|
||||
const normalizedCwd = normalizePath(cwd);
|
||||
const currentRepoId = getRepoIdentity(cwd);
|
||||
const repoIdByWorktree = new Map();
|
||||
const repoIdOfRecordedWorktree = (recordedWorktree) => {
|
||||
if (!repoIdByWorktree.has(recordedWorktree)) {
|
||||
repoIdByWorktree.set(recordedWorktree, getRepoIdentity(recordedWorktree));
|
||||
}
|
||||
return repoIdByWorktree.get(recordedWorktree);
|
||||
};
|
||||
|
||||
let repoMatch = null;
|
||||
let repoMatchContent = null;
|
||||
let projectMatch = null;
|
||||
let projectMatchContent = null;
|
||||
let readableSessions = 0;
|
||||
@@ -289,9 +308,11 @@ function selectMatchingSession(sessions, cwd, currentProject) {
|
||||
if (!content) continue;
|
||||
readableSessions++;
|
||||
|
||||
// Extract **Worktree:** field
|
||||
// Extract **Worktree:** and **Repo:** fields
|
||||
const worktreeMatch = content.match(/\*\*Worktree:\*\*\s*(.+)$/m);
|
||||
const sessionWorktree = worktreeMatch ? worktreeMatch[1].trim() : '';
|
||||
const repoFieldMatch = content.match(/\*\*Repo:\*\*\s*(.+)$/m);
|
||||
const sessionRepo = repoFieldMatch ? repoFieldMatch[1].trim() : '';
|
||||
|
||||
// Exact worktree match — best possible, return immediately
|
||||
// Normalize both paths to handle symlinks and case-insensitive filesystems
|
||||
@@ -299,9 +320,24 @@ function selectMatchingSession(sessions, cwd, currentProject) {
|
||||
return { session, content, matchReason: 'worktree' };
|
||||
}
|
||||
|
||||
// Repository identity match (#3160): the summary lookup is scoped to the
|
||||
// repository, not the cwd path, so a session recorded in worktree A is
|
||||
// eligible in worktree B only when both resolve to the same common git
|
||||
// dir. Unrelated repositories never share.
|
||||
if (!repoMatch && currentRepoId && (sessionRepo || sessionWorktree)) {
|
||||
const sessionRepoId = sessionRepo
|
||||
? normalizePath(sessionRepo)
|
||||
: repoIdOfRecordedWorktree(sessionWorktree);
|
||||
if (sessionRepoId && sessionRepoId === currentRepoId) {
|
||||
repoMatch = session;
|
||||
repoMatchContent = content;
|
||||
}
|
||||
}
|
||||
|
||||
// Project name match is only safe for legacy session files written before
|
||||
// Worktree metadata existed. A different explicit Worktree is not a match.
|
||||
if (!projectMatch && currentProject && !sessionWorktree) {
|
||||
// Worktree/Repo metadata existed. A different explicit Worktree or Repo
|
||||
// is not a match.
|
||||
if (!projectMatch && currentProject && !sessionWorktree && !sessionRepo) {
|
||||
const projectFieldMatch = content.match(/\*\*Project:\*\*\s*(.+)$/m);
|
||||
const sessionProject = projectFieldMatch ? projectFieldMatch[1].trim() : '';
|
||||
if (sessionProject && sessionProject === currentProject) {
|
||||
@@ -311,6 +347,10 @@ function selectMatchingSession(sessions, cwd, currentProject) {
|
||||
}
|
||||
}
|
||||
|
||||
if (repoMatch) {
|
||||
return { session: repoMatch, content: repoMatchContent, matchReason: 'repo' };
|
||||
}
|
||||
|
||||
if (projectMatch) {
|
||||
return { session: projectMatch, content: projectMatchContent, matchReason: 'project' };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user