@danypops/papyrus 0.47.2 → 0.49.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,3 @@
1
- import { randomUUID } from "node:crypto";
2
- import { basename } from "node:path";
3
- import { TASK_PROJECT_ALIAS_MAX_COUNT } from "../constants.ts";
4
1
  import type {
5
2
  RegisterTaskProjectInput,
6
3
  TaskProject,
@@ -9,6 +6,8 @@ import type {
9
6
  TaskViewMode,
10
7
  TaskViewPreference,
11
8
  } from "../domain/task-scope.ts";
9
+ import type { ProjectRegistryStore } from "../ports/project-registry-store.ts";
10
+ import { InMemoryProjectRegistryStore } from "./in-memory-project-registry-store.ts";
12
11
 
13
12
  export interface TaskScopeStore {
14
13
  assign(taskId: string, projectRoot: string | undefined, source: TaskScopeSource): TaskProjectScope;
@@ -21,32 +20,38 @@ export interface TaskScopeStore {
21
20
  registerProject(input: RegisterTaskProjectInput): TaskProject;
22
21
  }
23
22
 
24
- function uniqueAliases(values: readonly string[], name: string): string[] {
25
- const seen = new Set([name.trim().toLowerCase()]);
26
- const aliases = values.flatMap((value) => {
27
- const trimmed = value.trim();
28
- const key = trimmed.toLowerCase();
29
- if (!trimmed || seen.has(key)) return [];
30
- seen.add(key);
31
- return [trimmed];
32
- });
33
- if (aliases.length > TASK_PROJECT_ALIAS_MAX_COUNT) {
34
- throw new Error(`project aliases cannot exceed ${TASK_PROJECT_ALIAS_MAX_COUNT} entries`);
35
- }
36
- return aliases;
37
- }
38
-
23
+ /**
24
+ * Task's own scope/view bookkeeping, composing a ProjectRegistryStore for the project-catalog
25
+ * methods (projects/matchingProjects/registerProject) rather than implementing that bookkeeping
26
+ * itself -- see project-registry-store.ts. Pass a shared registry instance to keep Task and a
27
+ * non-Task ArtifactScopeStore resolving against the exact same project identities; omitted, this
28
+ * store gets its own private one (matching this class's own behavior before the extraction).
29
+ */
39
30
  export class InMemoryTaskScopeStore implements TaskScopeStore {
40
31
  private readonly scopes = new Map<string, TaskProjectScope>();
41
32
  private readonly views = new Map<string, TaskViewPreference>();
42
- private readonly projectRows = new Map<string, TaskProject>();
33
+ private readonly registry: InMemoryProjectRegistryStore;
34
+
35
+ constructor(registry?: ProjectRegistryStore) {
36
+ this.registry = registry instanceof InMemoryProjectRegistryStore ? registry : new InMemoryProjectRegistryStore();
37
+ this.registry.subscribeRootMoved((previousRoot, nextRoot) => this.onRootMoved(previousRoot, nextRoot));
38
+ }
39
+
40
+ private onRootMoved(previousRoot: string, nextRoot: string): void {
41
+ for (const [taskId, scope] of this.scopes) {
42
+ if (scope.projectRoot === previousRoot) this.scopes.set(taskId, { ...scope, projectRoot: nextRoot });
43
+ }
44
+ const view = this.views.get(previousRoot);
45
+ if (view) {
46
+ this.views.delete(previousRoot);
47
+ this.views.set(nextRoot, { ...view, projectRoot: nextRoot });
48
+ }
49
+ }
43
50
 
44
51
  assign(taskId: string, projectRoot: string | undefined, source: TaskScopeSource): TaskProjectScope {
45
52
  const scope = { taskId, ...(projectRoot === undefined ? {} : { projectRoot }), source };
46
53
  this.scopes.set(taskId, scope);
47
- if (projectRoot !== undefined && ![...this.projectRows.values()].some((project) => project.projectRoot === projectRoot)) {
48
- this.registerProject({ projectRoot });
49
- }
54
+ if (projectRoot !== undefined && !this.registry.byRoot(projectRoot)) this.registerProject({ projectRoot });
50
55
  return scope;
51
56
  }
52
57
 
@@ -73,60 +78,14 @@ export class InMemoryTaskScopeStore implements TaskScopeStore {
73
78
  }
74
79
 
75
80
  projects(query: string | undefined, limit: number): TaskProject[] {
76
- const needle = query?.trim().toLowerCase();
77
- return [...this.projectRows.values()]
78
- .filter(
79
- (project) =>
80
- !needle ||
81
- project.name.toLowerCase().includes(needle) ||
82
- project.projectRoot.toLowerCase().includes(needle) ||
83
- project.aliases.some((alias) => alias.toLowerCase().includes(needle)),
84
- )
85
- .sort((left, right) => left.name.localeCompare(right.name) || left.projectRoot.localeCompare(right.projectRoot))
86
- .slice(0, limit);
81
+ return this.registry.projects(query, limit);
87
82
  }
88
83
 
89
84
  matchingProjects(reference: string): TaskProject[] {
90
- const needle = reference.trim().toLowerCase();
91
- return [...this.projectRows.values()]
92
- .filter(
93
- (project) =>
94
- project.id.toLowerCase() === needle ||
95
- project.name.toLowerCase() === needle ||
96
- project.projectRoot.toLowerCase() === needle ||
97
- project.aliases.some((alias) => alias.toLowerCase() === needle),
98
- )
99
- .slice(0, 11);
85
+ return this.registry.matchingProjects(reference);
100
86
  }
101
87
 
102
88
  registerProject(input: RegisterTaskProjectInput): TaskProject {
103
- const now = new Date().toISOString();
104
- const byRoot = [...this.projectRows.values()].find((project) => project.projectRoot === input.projectRoot);
105
- const existing = input.existingId ? this.projectRows.get(input.existingId) : byRoot;
106
- const name = input.name?.trim() || existing?.name || basename(input.projectRoot) || input.projectRoot;
107
- const aliases = uniqueAliases(
108
- [...(existing?.aliases ?? []), ...(existing && existing.name !== name ? [existing.name] : []), ...(input.aliases ?? [])],
109
- name,
110
- );
111
- const project: TaskProject = {
112
- id: existing?.id ?? randomUUID(),
113
- name,
114
- aliases,
115
- projectRoot: input.projectRoot,
116
- createdAt: existing?.createdAt ?? now,
117
- updatedAt: now,
118
- };
119
- if (existing && existing.projectRoot !== project.projectRoot) {
120
- for (const [taskId, scope] of this.scopes) {
121
- if (scope.projectRoot === existing.projectRoot) this.scopes.set(taskId, { ...scope, projectRoot: project.projectRoot });
122
- }
123
- const view = this.views.get(existing.projectRoot);
124
- if (view) {
125
- this.views.delete(existing.projectRoot);
126
- this.views.set(project.projectRoot, { ...view, projectRoot: project.projectRoot });
127
- }
128
- }
129
- this.projectRows.set(project.id, project);
130
- return project;
89
+ return this.registry.registerProject(input);
131
90
  }
132
91
  }