feat(architecture): add multi-subproject awareness and automatic context resync
- Implemented subproject root resolution based on active editor hint - Added debounced event listener for active editor changes to trigger chip status updates - Updated sidebar provider to re-resolve active subproject root on every chip build - This ensures correct architecture context is injected when working in a monorepo or multi-root-style parent folder
This commit is contained in:
@@ -608,6 +608,20 @@ export async function activate(context: vscode.ExtensionContext) {
|
||||
if (!provider) return;
|
||||
await provider._openArchitectureDoc();
|
||||
}),
|
||||
// Re-resolve the active subproject when the user switches between files
|
||||
// in different subfolders. Debounced so rapid editor flicks don't churn
|
||||
// the chip / watcher. The actual resync is idempotent — if the active
|
||||
// subproject didn't change, nothing visible happens.
|
||||
(() => {
|
||||
let timer: NodeJS.Timeout | undefined;
|
||||
return vscode.window.onDidChangeActiveTextEditor(() => {
|
||||
if (!provider) return;
|
||||
if (timer) clearTimeout(timer);
|
||||
timer = setTimeout(() => {
|
||||
provider!._sendArchitectureStatus().catch(() => { /* swallow — chip is best-effort */ });
|
||||
}, 400);
|
||||
});
|
||||
})(),
|
||||
// ── 1인 기업 (Company) Mode commands ──────────────────────────────────
|
||||
// Thin shells over sidebar-provider methods so the runtime owns all
|
||||
// state mutation (chip status, watcher lifecycle, agent persistence).
|
||||
|
||||
@@ -130,6 +130,56 @@ export function architectureDocPathFor(projectRoot: string): string {
|
||||
return path.join(projectRoot, ARCH_DIR_REL, ARCH_FILE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the *effective* subproject root when the user has opened a parent
|
||||
* folder that contains several independent subprojects (each carrying its own
|
||||
* `.astra/project-context/` or `package.json`).
|
||||
*
|
||||
* Walks up from `hintFsPath` toward `workspaceRoot` and returns the first
|
||||
* ancestor that already looks like a subproject. Falls back to `workspaceRoot`
|
||||
* when no nested marker is found, when the hint lives outside the workspace,
|
||||
* or when the hint sits inside `node_modules` / build folders.
|
||||
*
|
||||
* Markers (in order):
|
||||
* 1. `.astra/project-context/` — an already-initialised Astra subproject.
|
||||
* 2. `package.json` — a Node project root (covers fresh subprojects that
|
||||
* haven't activated architecture mode yet).
|
||||
*/
|
||||
export function resolveActiveSubprojectRoot(workspaceRoot: string, hintFsPath?: string): string {
|
||||
if (!workspaceRoot || !hintFsPath) return workspaceRoot;
|
||||
let wsNorm: string;
|
||||
let hintNorm: string;
|
||||
try {
|
||||
wsNorm = path.resolve(workspaceRoot);
|
||||
hintNorm = path.resolve(hintFsPath);
|
||||
} catch {
|
||||
return workspaceRoot;
|
||||
}
|
||||
const rel = path.relative(wsNorm, hintNorm);
|
||||
if (!rel || rel.startsWith('..') || path.isAbsolute(rel)) {
|
||||
return workspaceRoot;
|
||||
}
|
||||
// Skip hints that live inside dependency / build folders — those are not
|
||||
// user-authored subprojects, and `node_modules/<pkg>/package.json` would
|
||||
// otherwise be mistaken for a subproject root.
|
||||
const skipSegments = new Set(['node_modules', '.git', 'out', 'dist', '.astra']);
|
||||
const relSegments = rel.split(/[\\/]+/);
|
||||
if (relSegments.some((s) => skipSegments.has(s))) return workspaceRoot;
|
||||
|
||||
let cur = path.dirname(hintNorm);
|
||||
while (true) {
|
||||
if (cur === wsNorm) return workspaceRoot;
|
||||
const r = path.relative(wsNorm, cur);
|
||||
if (!r || r.startsWith('..') || path.isAbsolute(r)) return workspaceRoot;
|
||||
const astraMarker = path.join(cur, ARCH_DIR_REL);
|
||||
const pkgMarker = path.join(cur, 'package.json');
|
||||
if (fs.existsSync(astraMarker) || fs.existsSync(pkgMarker)) return cur;
|
||||
const parent = path.dirname(cur);
|
||||
if (parent === cur) return workspaceRoot;
|
||||
cur = parent;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Backwards-compatible thin wrapper. The watcher / refresh path only needs the
|
||||
* shape-signature to decide whether to re-emit the doc, so we expose `scanProject`
|
||||
|
||||
+18
-2
@@ -31,6 +31,7 @@ import {
|
||||
buildOrRefreshArchitectureDoc,
|
||||
architectureDocPathFor,
|
||||
formatArchitectureContextForPrompt,
|
||||
resolveActiveSubprojectRoot,
|
||||
scanProject,
|
||||
} from './features/projectArchitecture';
|
||||
import { detectProjectIntent, KnownProject } from './features/projectArchitecture/intentDetector';
|
||||
@@ -1217,8 +1218,14 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn
|
||||
* is free.
|
||||
*/
|
||||
async _ensureActiveProjectForWorkspace(): Promise<ProjectProfile | null> {
|
||||
const workspaceRoot = vscode.workspace.workspaceFolders?.[0]?.uri.fsPath;
|
||||
if (!workspaceRoot) return null;
|
||||
const wsRoot = vscode.workspace.workspaceFolders?.[0]?.uri.fsPath;
|
||||
if (!wsRoot) return null;
|
||||
// When the parent folder contains several subprojects, use the active
|
||||
// editor's location to pick the *effective* subproject root instead of
|
||||
// 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 norm = (p: string | undefined) => (p || '').replace(/[\\/]+$/, '').toLowerCase();
|
||||
@@ -2484,6 +2491,15 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn
|
||||
logError('architecture: intent detection failed.', { error: e?.message ?? String(e) });
|
||||
}
|
||||
}
|
||||
// Re-resolve the active subproject from the currently-focused editor.
|
||||
// Without this, switching between subprojects (e.g. ConnectAI →
|
||||
// Datacollector) inside one VS Code window keeps loading the previous
|
||||
// subproject's architecture into the prompt.
|
||||
try {
|
||||
await this._ensureActiveProjectForWorkspace();
|
||||
} catch (e: any) {
|
||||
logError('architecture: workspace resync failed.', { error: e?.message ?? String(e) });
|
||||
}
|
||||
const projectArchitectureContext = this._buildProjectArchitectureContext();
|
||||
|
||||
// [File Processing v2] 파일 타입별 분류 처리
|
||||
|
||||
Reference in New Issue
Block a user