@leanspec/ui 0.2.5-dev.20251120070726 → 0.2.5-dev.20251120072524

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.
Files changed (129) hide show
  1. package/.next/standalone/packages/ui/.next/BUILD_ID +1 -1
  2. package/.next/standalone/packages/ui/.next/app-path-routes-manifest.json +3 -0
  3. package/.next/standalone/packages/ui/.next/build-manifest.json +2 -2
  4. package/.next/standalone/packages/ui/.next/prerender-manifest.json +3 -3
  5. package/.next/standalone/packages/ui/.next/routes-manifest.json +20 -0
  6. package/.next/standalone/packages/ui/.next/server/app/_global-error/page.js +1 -1
  7. package/.next/standalone/packages/ui/.next/server/app/_global-error/page.js.nft.json +1 -1
  8. package/.next/standalone/packages/ui/.next/server/app/_global-error.html +2 -2
  9. package/.next/standalone/packages/ui/.next/server/app/_global-error.rsc +1 -1
  10. package/.next/standalone/packages/ui/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  11. package/.next/standalone/packages/ui/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  12. package/.next/standalone/packages/ui/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  13. package/.next/standalone/packages/ui/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  14. package/.next/standalone/packages/ui/.next/server/app/_not-found/page.js +2 -2
  15. package/.next/standalone/packages/ui/.next/server/app/_not-found/page.js.nft.json +1 -1
  16. package/.next/standalone/packages/ui/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  17. package/.next/standalone/packages/ui/.next/server/app/_not-found.html +2 -2
  18. package/.next/standalone/packages/ui/.next/server/app/_not-found.rsc +3 -3
  19. package/.next/standalone/packages/ui/.next/server/app/_not-found.segments/_full.segment.rsc +3 -3
  20. package/.next/standalone/packages/ui/.next/server/app/_not-found.segments/_index.segment.rsc +2 -2
  21. package/.next/standalone/packages/ui/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  22. package/.next/standalone/packages/ui/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  23. package/.next/standalone/packages/ui/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  24. package/.next/standalone/packages/ui/.next/server/app/api/local-projects/[id]/route/app-paths-manifest.json +3 -0
  25. package/.next/standalone/packages/ui/.next/server/app/api/local-projects/[id]/route/build-manifest.json +11 -0
  26. package/.next/standalone/packages/ui/.next/server/app/api/local-projects/[id]/route/server-reference-manifest.json +4 -0
  27. package/.next/standalone/packages/ui/.next/server/app/api/local-projects/[id]/route.js +7 -0
  28. package/.next/standalone/packages/ui/.next/server/app/api/local-projects/[id]/route.js.map +5 -0
  29. package/.next/standalone/packages/ui/.next/server/app/api/local-projects/[id]/route.js.nft.json +1 -0
  30. package/.next/standalone/packages/ui/.next/server/app/api/local-projects/[id]/route_client-reference-manifest.js +2 -0
  31. package/.next/standalone/packages/ui/.next/server/app/api/local-projects/discover/route/app-paths-manifest.json +3 -0
  32. package/.next/standalone/packages/ui/.next/server/app/api/local-projects/discover/route/build-manifest.json +11 -0
  33. package/.next/standalone/packages/ui/.next/server/app/api/local-projects/discover/route/server-reference-manifest.json +4 -0
  34. package/.next/standalone/packages/ui/.next/server/app/api/local-projects/discover/route.js +7 -0
  35. package/.next/standalone/packages/ui/.next/server/app/api/local-projects/discover/route.js.map +5 -0
  36. package/.next/standalone/packages/ui/.next/server/app/api/local-projects/discover/route.js.nft.json +1 -0
  37. package/.next/standalone/packages/ui/.next/server/app/api/local-projects/discover/route_client-reference-manifest.js +2 -0
  38. package/.next/standalone/packages/ui/.next/server/app/api/local-projects/route/app-paths-manifest.json +3 -0
  39. package/.next/standalone/packages/ui/.next/server/app/api/local-projects/route/build-manifest.json +11 -0
  40. package/.next/standalone/packages/ui/.next/server/app/api/local-projects/route/server-reference-manifest.json +4 -0
  41. package/.next/standalone/packages/ui/.next/server/app/api/local-projects/route.js +7 -0
  42. package/.next/standalone/packages/ui/.next/server/app/api/local-projects/route.js.map +5 -0
  43. package/.next/standalone/packages/ui/.next/server/app/api/local-projects/route.js.nft.json +1 -0
  44. package/.next/standalone/packages/ui/.next/server/app/api/local-projects/route_client-reference-manifest.js +2 -0
  45. package/.next/standalone/packages/ui/.next/server/app/api/projects/[id]/specs/route.js +2 -1
  46. package/.next/standalone/packages/ui/.next/server/app/api/projects/[id]/specs/route.js.nft.json +1 -1
  47. package/.next/standalone/packages/ui/.next/server/app/api/projects/route.js +2 -1
  48. package/.next/standalone/packages/ui/.next/server/app/api/projects/route.js.nft.json +1 -1
  49. package/.next/standalone/packages/ui/.next/server/app/api/revalidate/route.js +5 -2
  50. package/.next/standalone/packages/ui/.next/server/app/api/revalidate/route.js.nft.json +1 -1
  51. package/.next/standalone/packages/ui/.next/server/app/api/specs/[id]/dependency-graph/route.js +5 -2
  52. package/.next/standalone/packages/ui/.next/server/app/api/specs/[id]/dependency-graph/route.js.nft.json +1 -1
  53. package/.next/standalone/packages/ui/.next/server/app/api/specs/[id]/route.js +5 -2
  54. package/.next/standalone/packages/ui/.next/server/app/api/specs/[id]/route.js.nft.json +1 -1
  55. package/.next/standalone/packages/ui/.next/server/app/api/specs/[id]/status/route.js +4 -2
  56. package/.next/standalone/packages/ui/.next/server/app/api/specs/[id]/status/route.js.nft.json +1 -1
  57. package/.next/standalone/packages/ui/.next/server/app/api/specs/[id]/subspecs/[file]/route.js +5 -2
  58. package/.next/standalone/packages/ui/.next/server/app/api/specs/[id]/subspecs/[file]/route.js.nft.json +1 -1
  59. package/.next/standalone/packages/ui/.next/server/app/api/stats/route.js +5 -2
  60. package/.next/standalone/packages/ui/.next/server/app/api/stats/route.js.nft.json +1 -1
  61. package/.next/standalone/packages/ui/.next/server/app/page.js +2 -2
  62. package/.next/standalone/packages/ui/.next/server/app/page.js.nft.json +1 -1
  63. package/.next/standalone/packages/ui/.next/server/app/page_client-reference-manifest.js +1 -1
  64. package/.next/standalone/packages/ui/.next/server/app/specs/[id]/page.js +2 -2
  65. package/.next/standalone/packages/ui/.next/server/app/specs/[id]/page.js.nft.json +1 -1
  66. package/.next/standalone/packages/ui/.next/server/app/specs/[id]/page_client-reference-manifest.js +1 -1
  67. package/.next/standalone/packages/ui/.next/server/app/specs/page.js +2 -2
  68. package/.next/standalone/packages/ui/.next/server/app/specs/page.js.nft.json +1 -1
  69. package/.next/standalone/packages/ui/.next/server/app/specs/page_client-reference-manifest.js +1 -1
  70. package/.next/standalone/packages/ui/.next/server/app/stats/page.js +2 -2
  71. package/.next/standalone/packages/ui/.next/server/app/stats/page.js.nft.json +1 -1
  72. package/.next/standalone/packages/ui/.next/server/app/stats/page_client-reference-manifest.js +1 -1
  73. package/.next/standalone/packages/ui/.next/server/app-paths-manifest.json +3 -0
  74. package/.next/standalone/packages/ui/.next/server/chunks/730ea_ui__next-internal_server_app_api_local-projects_[id]_route_actions_664abe9c.js +3 -0
  75. package/.next/standalone/packages/ui/.next/server/chunks/730ea_ui__next-internal_server_app_api_local-projects_discover_route_actions_e6ec3fa7.js +3 -0
  76. package/.next/standalone/packages/ui/.next/server/chunks/[root-of-the-server]__4b3c3001._.js +21 -0
  77. package/.next/standalone/packages/ui/.next/server/chunks/[root-of-the-server]__4b77d48f._.js +3 -0
  78. package/.next/standalone/packages/ui/.next/server/chunks/[root-of-the-server]__57791a48._.js +3 -0
  79. package/.next/standalone/packages/ui/.next/server/chunks/[root-of-the-server]__60b6d106._.js +3 -0
  80. package/.next/standalone/packages/ui/.next/server/chunks/[root-of-the-server]__a627840f._.js +3 -0
  81. package/.next/standalone/packages/ui/.next/server/chunks/[root-of-the-server]__beb134c0._.js +3 -0
  82. package/.next/standalone/packages/ui/.next/server/chunks/[root-of-the-server]__c83721f3._.js +3 -0
  83. package/.next/standalone/packages/ui/.next/server/chunks/[root-of-the-server]__c8a20942._.js +3 -0
  84. package/.next/standalone/packages/ui/.next/server/chunks/[root-of-the-server]__cd1fb0a2._.js +2 -2
  85. package/.next/standalone/packages/ui/.next/server/chunks/[root-of-the-server]__d293d769._.js +3 -0
  86. package/.next/standalone/packages/ui/.next/server/chunks/[root-of-the-server]__dfa71dcd._.js +3 -0
  87. package/.next/standalone/packages/ui/.next/server/chunks/{[root-of-the-server]__3971eae5._.js → [root-of-the-server]__e9ba3fa9._.js} +2 -2
  88. package/.next/standalone/packages/ui/.next/server/chunks/[root-of-the-server]__fad83967._.js +3 -0
  89. package/.next/standalone/packages/ui/.next/server/chunks/ecee3_js-yaml_dist_js-yaml_mjs_0775f118._.js +3 -0
  90. package/.next/standalone/packages/ui/.next/server/chunks/node_modules__pnpm_731b55aa._.js +3 -0
  91. package/.next/standalone/packages/ui/.next/server/chunks/packages_ui__next-internal_server_app_api_local-projects_route_actions_9142d5ad.js +3 -0
  92. package/.next/standalone/packages/ui/.next/server/chunks/ssr/[root-of-the-server]__34ab4950._.js +3 -0
  93. package/.next/standalone/packages/ui/.next/server/chunks/ssr/{[root-of-the-server]__5ca2e973._.js → [root-of-the-server]__a9d7fd42._.js} +2 -2
  94. package/.next/standalone/packages/ui/.next/server/chunks/ssr/{[root-of-the-server]__41f5b5c0._.js → [root-of-the-server]__b3633d6e._.js} +2 -2
  95. package/.next/standalone/packages/ui/.next/server/chunks/ssr/_000dd317._.js +1 -1
  96. package/.next/standalone/packages/ui/.next/server/pages/404.html +2 -2
  97. package/.next/standalone/packages/ui/.next/server/pages/500.html +2 -2
  98. package/.next/standalone/packages/ui/.next/server/server-reference-manifest.js +1 -1
  99. package/.next/standalone/packages/ui/.next/server/server-reference-manifest.json +1 -1
  100. package/.next/standalone/packages/ui/.next/static/chunks/c0576ccd1437ac5e.css +1 -0
  101. package/.next/standalone/packages/ui/package.json +2 -1
  102. package/.next/standalone/packages/ui/src/app/api/local-projects/[id]/route.ts +117 -0
  103. package/.next/standalone/packages/ui/src/app/api/local-projects/discover/route.ts +41 -0
  104. package/.next/standalone/packages/ui/src/app/api/local-projects/route.ts +63 -0
  105. package/.next/standalone/packages/ui/src/components/project-switcher.tsx +230 -0
  106. package/.next/standalone/packages/ui/src/contexts/project-context.tsx +225 -0
  107. package/.next/standalone/packages/ui/src/lib/projects/index.ts +7 -0
  108. package/.next/standalone/packages/ui/src/lib/projects/registry.ts +393 -0
  109. package/.next/standalone/packages/ui/src/lib/projects/types.ts +37 -0
  110. package/.next/standalone/packages/ui/src/lib/specs/service.ts +13 -1
  111. package/.next/standalone/packages/ui/src/lib/specs/sources/multi-project-source.ts +258 -0
  112. package/.next/standalone/packages/ui/tsconfig.tsbuildinfo +1 -1
  113. package/.next/static/chunks/c0576ccd1437ac5e.css +1 -0
  114. package/package.json +2 -1
  115. package/.next/standalone/packages/ui/.next/server/chunks/[root-of-the-server]__482b093a._.js +0 -21
  116. package/.next/standalone/packages/ui/.next/server/chunks/[root-of-the-server]__6bca1621._.js +0 -3
  117. package/.next/standalone/packages/ui/.next/server/chunks/[root-of-the-server]__87a3475a._.js +0 -3
  118. package/.next/standalone/packages/ui/.next/server/chunks/[root-of-the-server]__9f0f4c0b._.js +0 -3
  119. package/.next/standalone/packages/ui/.next/server/chunks/[root-of-the-server]__c1c9f5f5._.js +0 -3
  120. package/.next/standalone/packages/ui/.next/server/chunks/[root-of-the-server]__e2071b2e._.js +0 -3
  121. package/.next/standalone/packages/ui/.next/server/chunks/ssr/[root-of-the-server]__299c81cc._.js +0 -3
  122. package/.next/standalone/packages/ui/.next/static/chunks/9a80c22382ddcfaf.css +0 -1
  123. package/.next/static/chunks/9a80c22382ddcfaf.css +0 -1
  124. /package/.next/standalone/packages/ui/.next/static/{C8sZuJV5DQETshOIgjsmH → fMRsihZys1Dhy9qQRwNrc}/_buildManifest.js +0 -0
  125. /package/.next/standalone/packages/ui/.next/static/{C8sZuJV5DQETshOIgjsmH → fMRsihZys1Dhy9qQRwNrc}/_clientMiddlewareManifest.json +0 -0
  126. /package/.next/standalone/packages/ui/.next/static/{C8sZuJV5DQETshOIgjsmH → fMRsihZys1Dhy9qQRwNrc}/_ssgManifest.js +0 -0
  127. /package/.next/static/{C8sZuJV5DQETshOIgjsmH → fMRsihZys1Dhy9qQRwNrc}/_buildManifest.js +0 -0
  128. /package/.next/static/{C8sZuJV5DQETshOIgjsmH → fMRsihZys1Dhy9qQRwNrc}/_clientMiddlewareManifest.json +0 -0
  129. /package/.next/static/{C8sZuJV5DQETshOIgjsmH → fMRsihZys1Dhy9qQRwNrc}/_ssgManifest.js +0 -0
@@ -0,0 +1,258 @@
1
+ /**
2
+ * Multi-project filesystem source
3
+ * Extends FilesystemSource to support multiple local projects
4
+ */
5
+
6
+ import * as fs from 'node:fs/promises';
7
+ import * as path from 'node:path';
8
+ import matter from 'gray-matter';
9
+ import type { SpecSource, CachedSpec } from '../types';
10
+ import type { Spec } from '../../db/schema';
11
+ import { projectRegistry } from '../../projects';
12
+
13
+ // Cache TTL from environment
14
+ const isDev = process.env.NODE_ENV === 'development';
15
+ const CACHE_TTL = parseInt(process.env.CACHE_TTL || (isDev ? '0' : '60000'), 10);
16
+
17
+ /**
18
+ * Multi-project filesystem source
19
+ * Manages specs from multiple local filesystem projects
20
+ */
21
+ export class MultiProjectFilesystemSource implements SpecSource {
22
+ private cache = new Map<string, CachedSpec<Spec | Spec[]>>();
23
+
24
+ /**
25
+ * Get all specs from a specific project
26
+ */
27
+ async getAllSpecs(projectId?: string): Promise<Spec[]> {
28
+ if (!projectId) {
29
+ throw new Error('projectId is required for multi-project mode');
30
+ }
31
+
32
+ const cacheKey = `project:${projectId}:all`;
33
+ const cached = this.cache.get(cacheKey);
34
+
35
+ if (cached && Date.now() < cached.expiresAt) {
36
+ return cached.data as Spec[];
37
+ }
38
+
39
+ const project = await projectRegistry.getProject(projectId);
40
+ if (!project) {
41
+ throw new Error(`Project not found: ${projectId}`);
42
+ }
43
+
44
+ const specs = await this.loadSpecsFromDirectory(project.specsDir, projectId);
45
+
46
+ this.cache.set(cacheKey, {
47
+ data: specs,
48
+ expiresAt: Date.now() + CACHE_TTL,
49
+ });
50
+
51
+ return specs;
52
+ }
53
+
54
+ /**
55
+ * Get a single spec by path
56
+ */
57
+ async getSpec(specPath: string, projectId?: string): Promise<Spec | null> {
58
+ if (!projectId) {
59
+ throw new Error('projectId is required for multi-project mode');
60
+ }
61
+
62
+ const cacheKey = `project:${projectId}:spec:${specPath}`;
63
+ const cached = this.cache.get(cacheKey);
64
+
65
+ if (cached && Date.now() < cached.expiresAt) {
66
+ return cached.data as Spec;
67
+ }
68
+
69
+ const project = await projectRegistry.getProject(projectId);
70
+ if (!project) {
71
+ throw new Error(`Project not found: ${projectId}`);
72
+ }
73
+
74
+ // Parse spec number from path
75
+ const specNum = parseInt(specPath.split('-')[0], 10);
76
+ if (isNaN(specNum)) {
77
+ return null;
78
+ }
79
+
80
+ const spec = await this.loadSpecByNumber(project.specsDir, specNum, projectId);
81
+
82
+ if (spec) {
83
+ this.cache.set(cacheKey, {
84
+ data: spec,
85
+ expiresAt: Date.now() + CACHE_TTL,
86
+ });
87
+ }
88
+
89
+ return spec;
90
+ }
91
+
92
+ /**
93
+ * Get specs by status
94
+ */
95
+ async getSpecsByStatus(
96
+ status: 'planned' | 'in-progress' | 'complete' | 'archived',
97
+ projectId?: string
98
+ ): Promise<Spec[]> {
99
+ const allSpecs = await this.getAllSpecs(projectId);
100
+ return allSpecs.filter((spec) => spec.status === status);
101
+ }
102
+
103
+ /**
104
+ * Search specs
105
+ */
106
+ async searchSpecs(query: string, projectId?: string): Promise<Spec[]> {
107
+ const allSpecs = await this.getAllSpecs(projectId);
108
+ const lowerQuery = query.toLowerCase();
109
+
110
+ return allSpecs.filter((spec) => {
111
+ return (
112
+ spec.specName.toLowerCase().includes(lowerQuery) ||
113
+ spec.title?.toLowerCase().includes(lowerQuery) ||
114
+ spec.contentMd.toLowerCase().includes(lowerQuery)
115
+ );
116
+ });
117
+ }
118
+
119
+ /**
120
+ * Invalidate cache
121
+ */
122
+ invalidateCache(specPath?: string): void {
123
+ if (specPath) {
124
+ // Invalidate specific spec cache across all projects
125
+ for (const key of this.cache.keys()) {
126
+ if (key.includes(`:spec:${specPath}`)) {
127
+ this.cache.delete(key);
128
+ }
129
+ }
130
+ } else {
131
+ // Clear all cache
132
+ this.cache.clear();
133
+ }
134
+ }
135
+
136
+ /**
137
+ * Load all specs from a directory
138
+ */
139
+ private async loadSpecsFromDirectory(specsDir: string, projectId: string): Promise<Spec[]> {
140
+ try {
141
+ const entries = await fs.readdir(specsDir, { withFileTypes: true });
142
+ const specs: Spec[] = [];
143
+
144
+ for (const entry of entries) {
145
+ if (!entry.isDirectory()) {
146
+ continue;
147
+ }
148
+
149
+ // Check if directory matches spec pattern (number-name)
150
+ const match = entry.name.match(/^(\d+)-(.+)$/);
151
+ if (!match) {
152
+ continue;
153
+ }
154
+
155
+ const specNum = parseInt(match[1], 10);
156
+ const specName = match[2];
157
+ const specDir = path.join(specsDir, entry.name);
158
+ const readmePath = path.join(specDir, 'README.md');
159
+
160
+ try {
161
+ const content = await fs.readFile(readmePath, 'utf-8');
162
+ const spec = this.parseSpec(content, specNum, specName, readmePath, projectId);
163
+ if (spec) {
164
+ specs.push(spec);
165
+ }
166
+ } catch (error) {
167
+ console.warn(`Failed to read spec ${entry.name}:`, error);
168
+ }
169
+ }
170
+
171
+ return specs.sort((a, b) => (a.specNumber || 0) - (b.specNumber || 0));
172
+ } catch (error) {
173
+ console.error(`Error loading specs from ${specsDir}:`, error);
174
+ return [];
175
+ }
176
+ }
177
+
178
+ /**
179
+ * Load a specific spec by number
180
+ */
181
+ private async loadSpecByNumber(
182
+ specsDir: string,
183
+ specNum: number,
184
+ projectId: string
185
+ ): Promise<Spec | null> {
186
+ try {
187
+ const entries = await fs.readdir(specsDir, { withFileTypes: true });
188
+
189
+ for (const entry of entries) {
190
+ if (!entry.isDirectory()) {
191
+ continue;
192
+ }
193
+
194
+ const match = entry.name.match(/^(\d+)-(.+)$/);
195
+ if (!match) {
196
+ continue;
197
+ }
198
+
199
+ const entryNum = parseInt(match[1], 10);
200
+ if (entryNum === specNum) {
201
+ const specName = match[2];
202
+ const specDir = path.join(specsDir, entry.name);
203
+ const readmePath = path.join(specDir, 'README.md');
204
+
205
+ const content = await fs.readFile(readmePath, 'utf-8');
206
+ return this.parseSpec(content, specNum, specName, readmePath, projectId);
207
+ }
208
+ }
209
+
210
+ return null;
211
+ } catch (error) {
212
+ console.error(`Error loading spec ${specNum}:`, error);
213
+ return null;
214
+ }
215
+ }
216
+
217
+ /**
218
+ * Parse spec from markdown content
219
+ */
220
+ private parseSpec(
221
+ content: string,
222
+ specNum: number,
223
+ specName: string,
224
+ filePath: string,
225
+ projectId: string
226
+ ): Spec | null {
227
+ try {
228
+ const { data: frontmatter, content: markdown } = matter(content);
229
+
230
+ // Extract title from markdown (first h1)
231
+ const titleMatch = markdown.match(/^#\s+(.+)$/m);
232
+ const title = titleMatch ? titleMatch[1] : frontmatter.title || specName;
233
+
234
+ return {
235
+ id: `${projectId}:${specNum.toString().padStart(3, '0')}`,
236
+ projectId,
237
+ specNumber: specNum,
238
+ specName,
239
+ title,
240
+ status: frontmatter.status as any,
241
+ priority: frontmatter.priority as any,
242
+ tags: frontmatter.tags ? JSON.stringify(frontmatter.tags) : null,
243
+ assignee: frontmatter.assignee || null,
244
+ contentMd: content,
245
+ contentHtml: null,
246
+ createdAt: frontmatter.created_at ? new Date(frontmatter.created_at) : null,
247
+ updatedAt: frontmatter.updated_at ? new Date(frontmatter.updated_at) : null,
248
+ completedAt: frontmatter.completed_at ? new Date(frontmatter.completed_at) : null,
249
+ filePath,
250
+ githubUrl: null,
251
+ syncedAt: new Date(),
252
+ };
253
+ } catch (error) {
254
+ console.error(`Error parsing spec ${specName}:`, error);
255
+ return null;
256
+ }
257
+ }
258
+ }