@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.
- package/.next/standalone/packages/ui/.next/BUILD_ID +1 -1
- package/.next/standalone/packages/ui/.next/app-path-routes-manifest.json +3 -0
- package/.next/standalone/packages/ui/.next/build-manifest.json +2 -2
- package/.next/standalone/packages/ui/.next/prerender-manifest.json +3 -3
- package/.next/standalone/packages/ui/.next/routes-manifest.json +20 -0
- package/.next/standalone/packages/ui/.next/server/app/_global-error/page.js +1 -1
- package/.next/standalone/packages/ui/.next/server/app/_global-error/page.js.nft.json +1 -1
- package/.next/standalone/packages/ui/.next/server/app/_global-error.html +2 -2
- package/.next/standalone/packages/ui/.next/server/app/_global-error.rsc +1 -1
- package/.next/standalone/packages/ui/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/.next/standalone/packages/ui/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/.next/standalone/packages/ui/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/.next/standalone/packages/ui/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/packages/ui/.next/server/app/_not-found/page.js +2 -2
- package/.next/standalone/packages/ui/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/.next/standalone/packages/ui/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/standalone/packages/ui/.next/server/app/_not-found.html +2 -2
- package/.next/standalone/packages/ui/.next/server/app/_not-found.rsc +3 -3
- package/.next/standalone/packages/ui/.next/server/app/_not-found.segments/_full.segment.rsc +3 -3
- package/.next/standalone/packages/ui/.next/server/app/_not-found.segments/_index.segment.rsc +2 -2
- package/.next/standalone/packages/ui/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/.next/standalone/packages/ui/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/.next/standalone/packages/ui/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/packages/ui/.next/server/app/api/local-projects/[id]/route/app-paths-manifest.json +3 -0
- package/.next/standalone/packages/ui/.next/server/app/api/local-projects/[id]/route/build-manifest.json +11 -0
- package/.next/standalone/packages/ui/.next/server/app/api/local-projects/[id]/route/server-reference-manifest.json +4 -0
- package/.next/standalone/packages/ui/.next/server/app/api/local-projects/[id]/route.js +7 -0
- package/.next/standalone/packages/ui/.next/server/app/api/local-projects/[id]/route.js.map +5 -0
- package/.next/standalone/packages/ui/.next/server/app/api/local-projects/[id]/route.js.nft.json +1 -0
- package/.next/standalone/packages/ui/.next/server/app/api/local-projects/[id]/route_client-reference-manifest.js +2 -0
- package/.next/standalone/packages/ui/.next/server/app/api/local-projects/discover/route/app-paths-manifest.json +3 -0
- package/.next/standalone/packages/ui/.next/server/app/api/local-projects/discover/route/build-manifest.json +11 -0
- package/.next/standalone/packages/ui/.next/server/app/api/local-projects/discover/route/server-reference-manifest.json +4 -0
- package/.next/standalone/packages/ui/.next/server/app/api/local-projects/discover/route.js +7 -0
- package/.next/standalone/packages/ui/.next/server/app/api/local-projects/discover/route.js.map +5 -0
- package/.next/standalone/packages/ui/.next/server/app/api/local-projects/discover/route.js.nft.json +1 -0
- package/.next/standalone/packages/ui/.next/server/app/api/local-projects/discover/route_client-reference-manifest.js +2 -0
- package/.next/standalone/packages/ui/.next/server/app/api/local-projects/route/app-paths-manifest.json +3 -0
- package/.next/standalone/packages/ui/.next/server/app/api/local-projects/route/build-manifest.json +11 -0
- package/.next/standalone/packages/ui/.next/server/app/api/local-projects/route/server-reference-manifest.json +4 -0
- package/.next/standalone/packages/ui/.next/server/app/api/local-projects/route.js +7 -0
- package/.next/standalone/packages/ui/.next/server/app/api/local-projects/route.js.map +5 -0
- package/.next/standalone/packages/ui/.next/server/app/api/local-projects/route.js.nft.json +1 -0
- package/.next/standalone/packages/ui/.next/server/app/api/local-projects/route_client-reference-manifest.js +2 -0
- package/.next/standalone/packages/ui/.next/server/app/api/projects/[id]/specs/route.js +2 -1
- package/.next/standalone/packages/ui/.next/server/app/api/projects/[id]/specs/route.js.nft.json +1 -1
- package/.next/standalone/packages/ui/.next/server/app/api/projects/route.js +2 -1
- package/.next/standalone/packages/ui/.next/server/app/api/projects/route.js.nft.json +1 -1
- package/.next/standalone/packages/ui/.next/server/app/api/revalidate/route.js +5 -2
- package/.next/standalone/packages/ui/.next/server/app/api/revalidate/route.js.nft.json +1 -1
- package/.next/standalone/packages/ui/.next/server/app/api/specs/[id]/dependency-graph/route.js +5 -2
- package/.next/standalone/packages/ui/.next/server/app/api/specs/[id]/dependency-graph/route.js.nft.json +1 -1
- package/.next/standalone/packages/ui/.next/server/app/api/specs/[id]/route.js +5 -2
- package/.next/standalone/packages/ui/.next/server/app/api/specs/[id]/route.js.nft.json +1 -1
- package/.next/standalone/packages/ui/.next/server/app/api/specs/[id]/status/route.js +4 -2
- package/.next/standalone/packages/ui/.next/server/app/api/specs/[id]/status/route.js.nft.json +1 -1
- package/.next/standalone/packages/ui/.next/server/app/api/specs/[id]/subspecs/[file]/route.js +5 -2
- package/.next/standalone/packages/ui/.next/server/app/api/specs/[id]/subspecs/[file]/route.js.nft.json +1 -1
- package/.next/standalone/packages/ui/.next/server/app/api/stats/route.js +5 -2
- package/.next/standalone/packages/ui/.next/server/app/api/stats/route.js.nft.json +1 -1
- package/.next/standalone/packages/ui/.next/server/app/page.js +2 -2
- package/.next/standalone/packages/ui/.next/server/app/page.js.nft.json +1 -1
- package/.next/standalone/packages/ui/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/standalone/packages/ui/.next/server/app/specs/[id]/page.js +2 -2
- package/.next/standalone/packages/ui/.next/server/app/specs/[id]/page.js.nft.json +1 -1
- package/.next/standalone/packages/ui/.next/server/app/specs/[id]/page_client-reference-manifest.js +1 -1
- package/.next/standalone/packages/ui/.next/server/app/specs/page.js +2 -2
- package/.next/standalone/packages/ui/.next/server/app/specs/page.js.nft.json +1 -1
- package/.next/standalone/packages/ui/.next/server/app/specs/page_client-reference-manifest.js +1 -1
- package/.next/standalone/packages/ui/.next/server/app/stats/page.js +2 -2
- package/.next/standalone/packages/ui/.next/server/app/stats/page.js.nft.json +1 -1
- package/.next/standalone/packages/ui/.next/server/app/stats/page_client-reference-manifest.js +1 -1
- package/.next/standalone/packages/ui/.next/server/app-paths-manifest.json +3 -0
- package/.next/standalone/packages/ui/.next/server/chunks/730ea_ui__next-internal_server_app_api_local-projects_[id]_route_actions_664abe9c.js +3 -0
- package/.next/standalone/packages/ui/.next/server/chunks/730ea_ui__next-internal_server_app_api_local-projects_discover_route_actions_e6ec3fa7.js +3 -0
- package/.next/standalone/packages/ui/.next/server/chunks/[root-of-the-server]__4b3c3001._.js +21 -0
- package/.next/standalone/packages/ui/.next/server/chunks/[root-of-the-server]__4b77d48f._.js +3 -0
- package/.next/standalone/packages/ui/.next/server/chunks/[root-of-the-server]__57791a48._.js +3 -0
- package/.next/standalone/packages/ui/.next/server/chunks/[root-of-the-server]__60b6d106._.js +3 -0
- package/.next/standalone/packages/ui/.next/server/chunks/[root-of-the-server]__a627840f._.js +3 -0
- package/.next/standalone/packages/ui/.next/server/chunks/[root-of-the-server]__beb134c0._.js +3 -0
- package/.next/standalone/packages/ui/.next/server/chunks/[root-of-the-server]__c83721f3._.js +3 -0
- package/.next/standalone/packages/ui/.next/server/chunks/[root-of-the-server]__c8a20942._.js +3 -0
- package/.next/standalone/packages/ui/.next/server/chunks/[root-of-the-server]__cd1fb0a2._.js +2 -2
- package/.next/standalone/packages/ui/.next/server/chunks/[root-of-the-server]__d293d769._.js +3 -0
- package/.next/standalone/packages/ui/.next/server/chunks/[root-of-the-server]__dfa71dcd._.js +3 -0
- package/.next/standalone/packages/ui/.next/server/chunks/{[root-of-the-server]__3971eae5._.js → [root-of-the-server]__e9ba3fa9._.js} +2 -2
- package/.next/standalone/packages/ui/.next/server/chunks/[root-of-the-server]__fad83967._.js +3 -0
- package/.next/standalone/packages/ui/.next/server/chunks/ecee3_js-yaml_dist_js-yaml_mjs_0775f118._.js +3 -0
- package/.next/standalone/packages/ui/.next/server/chunks/node_modules__pnpm_731b55aa._.js +3 -0
- package/.next/standalone/packages/ui/.next/server/chunks/packages_ui__next-internal_server_app_api_local-projects_route_actions_9142d5ad.js +3 -0
- package/.next/standalone/packages/ui/.next/server/chunks/ssr/[root-of-the-server]__34ab4950._.js +3 -0
- package/.next/standalone/packages/ui/.next/server/chunks/ssr/{[root-of-the-server]__5ca2e973._.js → [root-of-the-server]__a9d7fd42._.js} +2 -2
- package/.next/standalone/packages/ui/.next/server/chunks/ssr/{[root-of-the-server]__41f5b5c0._.js → [root-of-the-server]__b3633d6e._.js} +2 -2
- package/.next/standalone/packages/ui/.next/server/chunks/ssr/_000dd317._.js +1 -1
- package/.next/standalone/packages/ui/.next/server/pages/404.html +2 -2
- package/.next/standalone/packages/ui/.next/server/pages/500.html +2 -2
- package/.next/standalone/packages/ui/.next/server/server-reference-manifest.js +1 -1
- package/.next/standalone/packages/ui/.next/server/server-reference-manifest.json +1 -1
- package/.next/standalone/packages/ui/.next/static/chunks/c0576ccd1437ac5e.css +1 -0
- package/.next/standalone/packages/ui/package.json +2 -1
- package/.next/standalone/packages/ui/src/app/api/local-projects/[id]/route.ts +117 -0
- package/.next/standalone/packages/ui/src/app/api/local-projects/discover/route.ts +41 -0
- package/.next/standalone/packages/ui/src/app/api/local-projects/route.ts +63 -0
- package/.next/standalone/packages/ui/src/components/project-switcher.tsx +230 -0
- package/.next/standalone/packages/ui/src/contexts/project-context.tsx +225 -0
- package/.next/standalone/packages/ui/src/lib/projects/index.ts +7 -0
- package/.next/standalone/packages/ui/src/lib/projects/registry.ts +393 -0
- package/.next/standalone/packages/ui/src/lib/projects/types.ts +37 -0
- package/.next/standalone/packages/ui/src/lib/specs/service.ts +13 -1
- package/.next/standalone/packages/ui/src/lib/specs/sources/multi-project-source.ts +258 -0
- package/.next/standalone/packages/ui/tsconfig.tsbuildinfo +1 -1
- package/.next/static/chunks/c0576ccd1437ac5e.css +1 -0
- package/package.json +2 -1
- package/.next/standalone/packages/ui/.next/server/chunks/[root-of-the-server]__482b093a._.js +0 -21
- package/.next/standalone/packages/ui/.next/server/chunks/[root-of-the-server]__6bca1621._.js +0 -3
- package/.next/standalone/packages/ui/.next/server/chunks/[root-of-the-server]__87a3475a._.js +0 -3
- package/.next/standalone/packages/ui/.next/server/chunks/[root-of-the-server]__9f0f4c0b._.js +0 -3
- package/.next/standalone/packages/ui/.next/server/chunks/[root-of-the-server]__c1c9f5f5._.js +0 -3
- package/.next/standalone/packages/ui/.next/server/chunks/[root-of-the-server]__e2071b2e._.js +0 -3
- package/.next/standalone/packages/ui/.next/server/chunks/ssr/[root-of-the-server]__299c81cc._.js +0 -3
- package/.next/standalone/packages/ui/.next/static/chunks/9a80c22382ddcfaf.css +0 -1
- package/.next/static/chunks/9a80c22382ddcfaf.css +0 -1
- /package/.next/standalone/packages/ui/.next/static/{C8sZuJV5DQETshOIgjsmH → fMRsihZys1Dhy9qQRwNrc}/_buildManifest.js +0 -0
- /package/.next/standalone/packages/ui/.next/static/{C8sZuJV5DQETshOIgjsmH → fMRsihZys1Dhy9qQRwNrc}/_clientMiddlewareManifest.json +0 -0
- /package/.next/standalone/packages/ui/.next/static/{C8sZuJV5DQETshOIgjsmH → fMRsihZys1Dhy9qQRwNrc}/_ssgManifest.js +0 -0
- /package/.next/static/{C8sZuJV5DQETshOIgjsmH → fMRsihZys1Dhy9qQRwNrc}/_buildManifest.js +0 -0
- /package/.next/static/{C8sZuJV5DQETshOIgjsmH → fMRsihZys1Dhy9qQRwNrc}/_clientMiddlewareManifest.json +0 -0
- /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
|
+
}
|