Release v2.1.2: Chronicle Repair & Context Stability improvements

This commit is contained in:
g1nation
2026-05-14 02:37:49 +09:00
parent a374aa703a
commit f521c3f557
13 changed files with 142 additions and 54 deletions
+72 -3
View File
@@ -982,6 +982,64 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn
await this._context.globalState.update(SidebarChatProvider.chronicleProjectsStateKey, projects);
}
/**
* One-time repair for chronicle projects that were stored before the
* multi-subproject fix landed.
*
* Old activation code always treated `workspaceFolders[0]` (= the open
* parent folder, e.g. `/.../Antigravity`) as the project root, so every
* subproject the user activated (`ConnectAI`, `Datacollector_MAC`,
* `Skybound`, …) ended up with `projectRoot === <parent>` in globalState.
* That breaks reload: `_ensureActiveProjectForWorkspace` case 2 matches
* the corrupted entry on every boot and force-switches the active
* project to whichever bad row it found first.
*
* The repair: for each chronicle project whose `projectName` does NOT
* match the basename of its stored `projectRoot` (i.e. it can't actually
* be the project root), and where a same-name subfolder exists under
* the current workspace, retarget the entry to that subfolder. Idempotent
* — re-running it after the first pass changes nothing.
*/
async _repairCorruptedChronicleProjectRoots(): Promise<void> {
const wsRoot = vscode.workspace.workspaceFolders?.[0]?.uri.fsPath;
if (!wsRoot) return;
const projects = this._getChronicleProjects();
if (projects.length === 0) return;
const norm = (p: string | undefined) => (p || '').replace(/[\\/]+$/, '').toLowerCase();
const wsRootNorm = norm(wsRoot);
let changed = false;
const repaired = projects.map((p) => {
if (!p.projectName || !p.projectRoot) return p;
const rootBase = path.basename(p.projectRoot).toLowerCase();
const nameMatches = rootBase === p.projectName.toLowerCase();
if (nameMatches) return p; // root basename agrees with name → trust it
// Only repair entries that look like they were silently captured at
// the workspace parent. Anything else (different machine, custom
// path) we leave alone.
if (norm(p.projectRoot) !== wsRootNorm) return p;
const candidate = path.join(wsRoot, p.projectName);
try {
if (!fs.existsSync(candidate) || !fs.statSync(candidate).isDirectory()) return p;
} catch {
return p;
}
const newDocPath = path.join(candidate, '.astra', 'project-context', 'architecture.md');
changed = true;
return {
...p,
projectRoot: candidate,
recordRoot: path.join(candidate, 'docs', 'records', p.projectName),
architectureDocPath: fs.existsSync(newDocPath) ? newDocPath : p.architectureDocPath,
};
});
if (!changed) return;
await this._putChronicleProjects(repaired);
const fixedCount = repaired.filter((p, i) => p !== projects[i]).length;
logInfo('architecture: repaired chronicle projects with wrong projectRoot.', {
wsRoot, count: fixedCount,
});
}
// ─── Project Architecture Context (Feature 2) ──────────────────────────────
//
// Activation flow:
@@ -1225,10 +1283,21 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn
// always reporting the parent.
const hint = vscode.window.activeTextEditor?.document.uri.fsPath
?? vscode.window.visibleTextEditors[0]?.document.uri.fsPath;
const workspaceRoot = resolveActiveSubprojectRoot(wsRoot, hint);
const projects = this._getChronicleProjects();
const active = this._getActiveChronicleProject();
const resolved = resolveActiveSubprojectRoot(wsRoot, hint);
const norm = (p: string | undefined) => (p || '').replace(/[\\/]+$/, '').toLowerCase();
const active = this._getActiveChronicleProject();
const projects = this._getChronicleProjects();
// Did the editor hint actually point at a nested subproject? If the
// resolver fell back to the workspace root (no nested marker, or the
// Astra sidebar itself was focused so no editor existed), we must NOT
// clobber the user's current active project — they may have set it by
// hand via the project picker, or by typing a project intent. Auto-
// switching is only safe when there is a clear editor-driven signal.
const isNestedHit = !!resolved && norm(resolved) !== norm(wsRoot);
if (!isNestedHit && active) {
return active;
}
const workspaceRoot = isNestedHit ? resolved : wsRoot;
if (active && active.projectRoot && norm(active.projectRoot) === norm(workspaceRoot)) {
return active;
}