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:
g1nation
2026-05-14 02:19:33 +09:00
parent 373abfd920
commit 398703466f
13 changed files with 150 additions and 38 deletions
+50
View File
@@ -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`