import { InMemoryProjectRegistryStore } from "../../project-registry/in-memory-project-registry-store.ts"; import type { ProjectRegistryStore } from "../../project-registry/project-registry-store.ts"; import type { RegisterTaskProjectInput, TaskProject, TaskProjectScope, TaskScopeSource, TaskViewMode, TaskViewPreference, } from "./task-scope.ts"; /** What a caller outside Task needs to assign a task's project scope -- the rest of TaskScopeStore (catalog, views, listing) stays Task-only. */ export interface TaskScopeAssigner { assign(taskId: string, projectRoot: string | undefined, source: TaskScopeSource): TaskProjectScope; } export interface TaskScopeStore extends TaskScopeAssigner { get(taskId: string): TaskProjectScope | undefined; taskIds(projectRoot: string | undefined, limit: number): string[]; view(projectRoot: string): TaskViewPreference; setView(projectRoot: string, mode: TaskViewMode, rootTaskId?: string): TaskViewPreference; projects(query: string | undefined, limit: number): TaskProject[]; matchingProjects(reference: string): TaskProject[]; registerProject(input: RegisterTaskProjectInput): TaskProject; } /** Pass a shared registry to keep Task and ArtifactScopeStore resolving the same project identities; omitted, this gets its own private one. */ export class InMemoryTaskScopeStore implements TaskScopeStore { private readonly scopes = new Map(); private readonly views = new Map(); private readonly registry: InMemoryProjectRegistryStore; constructor(registry?: ProjectRegistryStore) { this.registry = registry instanceof InMemoryProjectRegistryStore ? registry : new InMemoryProjectRegistryStore(); this.registry.subscribeRootMoved((previousRoot, nextRoot) => this.onRootMoved(previousRoot, nextRoot)); } private onRootMoved(previousRoot: string, nextRoot: string): void { for (const [taskId, scope] of this.scopes) { if (scope.projectRoot === previousRoot) this.scopes.set(taskId, { ...scope, projectRoot: nextRoot }); } const view = this.views.get(previousRoot); if (view) { this.views.delete(previousRoot); this.views.set(nextRoot, { ...view, projectRoot: nextRoot }); } } assign(taskId: string, projectRoot: string | undefined, source: TaskScopeSource): TaskProjectScope { const scope = { taskId, ...(projectRoot === undefined ? {} : { projectRoot }), source }; this.scopes.set(taskId, scope); if (projectRoot !== undefined && !this.registry.byRoot(projectRoot)) this.registerProject({ projectRoot }); return scope; } get(taskId: string): TaskProjectScope | undefined { return this.scopes.get(taskId); } taskIds(projectRoot: string | undefined, limit: number): string[] { return [...this.scopes.values()] .filter((scope) => scope.projectRoot === projectRoot) .map((scope) => scope.taskId) .sort() .slice(0, limit); } view(projectRoot: string): TaskViewPreference { return this.views.get(projectRoot) ?? { projectRoot, mode: "project" }; } setView(projectRoot: string, mode: TaskViewMode, rootTaskId?: string): TaskViewPreference { const view = { projectRoot, mode, ...(rootTaskId === undefined ? {} : { rootTaskId }) }; this.views.set(projectRoot, view); return view; } projects(query: string | undefined, limit: number): TaskProject[] { return this.registry.projects(query, limit); } matchingProjects(reference: string): TaskProject[] { return this.registry.matchingProjects(reference); } registerProject(input: RegisterTaskProjectInput): TaskProject { return this.registry.registerProject(input); } }