@openspecui/server 3.6.0 → 3.7.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.
- package/dist/index.mjs +172 -58
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { CliExecutor, CodeEditorThemeSchema, ConfigManager, CustomSoundHashSchema, DASHBOARD_METRIC_KEYS, DashboardConfigSchema, GitConfigSchema, HOSTED_SHELL_PROTOCOL_VERSION, MarkdownParser, NotificationPublishInputSchema, NotificationSettingsSchema, OPENSPECUI_HOOKS_VERSION, OpenSpecAdapter, OpenSpecWatcher, OpsxConfigSchema, OpsxKernel, PtyClientMessageSchema, ReactiveContext, TerminalConfigSchema, TerminalControlParser, TerminalRendererEngineSchema, getAllTools, getAvailableTools, getConfiguredTools, getDefaultCliCommandString, getDetectedProjectTools, getToolInitStates, getWatcherRuntimeStatus, initWatcherPool, isWatcherPoolInitialized, resolveTerminalShellDefaults, sniffGlobalCli, subscribeWatcherRuntimeStatus, terminalNotificationEventToPublishInput } from "@openspecui/core";
|
|
2
|
-
import { basename, dirname, join, relative, resolve, sep } from "node:path";
|
|
1
|
+
import { CliExecutor, CodeEditorThemeSchema, ConfigManager, CustomSoundHashSchema, DASHBOARD_METRIC_KEYS, DashboardConfigSchema, DocumentTranslationConfigSchema, GitConfigSchema, HOSTED_SHELL_PROTOCOL_VERSION, MarkdownParser, NotificationPublishInputSchema, NotificationSettingsSchema, OPENSPECUI_HOOKS_VERSION, OpenSpecAdapter, OpenSpecWatcher, OpsxConfigSchema, OpsxKernel, PtyClientMessageSchema, ReactiveContext, TerminalConfigSchema, TerminalControlParser, TerminalRendererEngineSchema, getAllTools, getAvailableTools, getConfiguredTools, getDefaultCliCommandString, getDetectedProjectTools, getToolInitStates, getWatcherRuntimeStatus, initWatcherPool, isWatcherPoolInitialized, parseOpsxEntityMetadata, parseOpsxSchemaDetail, resolveTerminalShellDefaults, sniffGlobalCli, subscribeWatcherRuntimeStatus, terminalNotificationEventToPublishInput } from "@openspecui/core";
|
|
2
|
+
import { basename, dirname, join, matchesGlob, relative, resolve, sep } from "node:path";
|
|
3
3
|
import { access, mkdir, readFile, realpath, rm, stat, writeFile } from "node:fs/promises";
|
|
4
4
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
5
5
|
import { createServer as createServer$1 } from "node:net";
|
|
@@ -35,6 +35,9 @@ function toErrorDiagnostic$1(error) {
|
|
|
35
35
|
function isNotNull(value) {
|
|
36
36
|
return value !== null;
|
|
37
37
|
}
|
|
38
|
+
function normalizeChangeFilePath(path) {
|
|
39
|
+
return path.replace(/\\/g, "/").replace(/^\/+/, "");
|
|
40
|
+
}
|
|
38
41
|
var DocumentService = class {
|
|
39
42
|
parser = new MarkdownParser();
|
|
40
43
|
constructor(projectDir, adapter, hookRuntime) {
|
|
@@ -176,37 +179,125 @@ var DocumentService = class {
|
|
|
176
179
|
return null;
|
|
177
180
|
}
|
|
178
181
|
}
|
|
182
|
+
async readEntityDetail(stage, changeId, consumer = "view", mode = "processed", options = {}) {
|
|
183
|
+
const detail = await this.adapter.readEntityDetail(stage, changeId, options);
|
|
184
|
+
if (!detail) return null;
|
|
185
|
+
const root = stage === "change" ? `openspec/changes/${changeId}` : `openspec/changes/archive/${changeId}`;
|
|
186
|
+
const processedByPath = /* @__PURE__ */ new Map();
|
|
187
|
+
const processArtifactFile = async (artifact, file) => {
|
|
188
|
+
const artifactFile = {
|
|
189
|
+
...await this.processEntityFile({
|
|
190
|
+
stage,
|
|
191
|
+
changeId,
|
|
192
|
+
root,
|
|
193
|
+
file,
|
|
194
|
+
consumer,
|
|
195
|
+
mode,
|
|
196
|
+
schemaName: detail.schemaName,
|
|
197
|
+
artifactId: artifact.id,
|
|
198
|
+
artifactOutputPath: artifact.outputPath
|
|
199
|
+
}),
|
|
200
|
+
type: "file"
|
|
201
|
+
};
|
|
202
|
+
processedByPath.set(file.path, artifactFile);
|
|
203
|
+
return artifactFile;
|
|
204
|
+
};
|
|
205
|
+
const artifacts = await Promise.all(detail.artifacts.map(async (artifact) => ({
|
|
206
|
+
...artifact,
|
|
207
|
+
files: await Promise.all(artifact.files.map((file) => processArtifactFile(artifact, file)))
|
|
208
|
+
})));
|
|
209
|
+
const files = await Promise.all(detail.files.map(async (file) => {
|
|
210
|
+
const processed = processedByPath.get(file.path);
|
|
211
|
+
if (processed) return processed;
|
|
212
|
+
return this.processEntityFile({
|
|
213
|
+
stage,
|
|
214
|
+
changeId,
|
|
215
|
+
root,
|
|
216
|
+
file,
|
|
217
|
+
consumer,
|
|
218
|
+
mode,
|
|
219
|
+
schemaName: detail.schemaName
|
|
220
|
+
});
|
|
221
|
+
}));
|
|
222
|
+
const filesByPath = new Map(files.map((file) => [file.path, file]));
|
|
223
|
+
const ungroupedFiles = detail.ungroupedFiles.map((file) => filesByPath.get(file.path) ?? file);
|
|
224
|
+
return {
|
|
225
|
+
...detail,
|
|
226
|
+
files,
|
|
227
|
+
artifacts,
|
|
228
|
+
ungroupedFiles
|
|
229
|
+
};
|
|
230
|
+
}
|
|
179
231
|
async readChangeFiles(changeId, consumer = "view", mode = "processed") {
|
|
180
232
|
const files = await this.adapter.readChangeFiles(changeId);
|
|
181
233
|
return this.processChangeFiles("change", changeId, files, consumer, mode);
|
|
182
234
|
}
|
|
235
|
+
async readChangeArtifactOutput(changeId, outputPath, consumer = "view", mode = "processed") {
|
|
236
|
+
const normalizedPath = normalizeChangeFilePath(outputPath);
|
|
237
|
+
return (await this.readChangeArtifactFiles(changeId, normalizedPath, consumer, mode)).find((file) => file.path === normalizedPath)?.content ?? null;
|
|
238
|
+
}
|
|
239
|
+
async readChangeGlobArtifactFiles(changeId, outputPath, consumer = "view", mode = "processed") {
|
|
240
|
+
const normalizedPattern = normalizeChangeFilePath(outputPath);
|
|
241
|
+
return this.readChangeArtifactFiles(changeId, normalizedPattern, consumer, mode);
|
|
242
|
+
}
|
|
183
243
|
async readArchivedChangeFiles(changeId, consumer = "view", mode = "processed") {
|
|
184
244
|
const files = await this.adapter.readArchivedChangeFiles(changeId);
|
|
185
245
|
return this.processChangeFiles("archive", changeId, files, consumer, mode);
|
|
186
246
|
}
|
|
187
247
|
async processChangeFiles(stage, changeId, files, consumer, mode) {
|
|
188
248
|
const root = stage === "change" ? `openspec/changes/${changeId}` : `openspec/changes/archive/${changeId}`;
|
|
189
|
-
return (await Promise.all(files.map(
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
249
|
+
return (await Promise.all(files.map((file) => this.processChangeFile(stage, changeId, root, file, consumer, mode)))).filter(isNotNull);
|
|
250
|
+
}
|
|
251
|
+
async readChangeArtifactFiles(changeId, outputPath, consumer, mode) {
|
|
252
|
+
const matchingFiles = (await this.adapter.readChangeFiles(changeId)).filter((file) => {
|
|
253
|
+
if (file.type !== "file" || file.content === void 0) return false;
|
|
254
|
+
return matchesGlob(file.path, outputPath) || file.path === outputPath;
|
|
255
|
+
});
|
|
256
|
+
const root = `openspec/changes/${changeId}`;
|
|
257
|
+
return (await Promise.all(matchingFiles.map((file) => this.processChangeFile("change", changeId, root, file, consumer, mode)))).filter(isNotNull).filter((file) => file.type === "file" && file.content !== void 0);
|
|
258
|
+
}
|
|
259
|
+
async processChangeFile(stage, changeId, root, file, consumer, mode) {
|
|
260
|
+
if (file.type !== "file" || file.content === void 0 || !file.path.endsWith(".md")) return file;
|
|
261
|
+
const kind = this.inferChangeFileKind(file.path);
|
|
262
|
+
if (!kind) return file;
|
|
263
|
+
const result = await this.processDocument({
|
|
264
|
+
consumer,
|
|
265
|
+
mode,
|
|
266
|
+
document: {
|
|
267
|
+
stage,
|
|
268
|
+
kind,
|
|
269
|
+
changeId,
|
|
270
|
+
relativePath: `${root}/${file.path}`,
|
|
271
|
+
absolutePath: join(this.projectDir, root, file.path)
|
|
272
|
+
},
|
|
273
|
+
source: file.content
|
|
274
|
+
});
|
|
275
|
+
return {
|
|
276
|
+
...file,
|
|
277
|
+
content: result.markdown
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
async processEntityFile(input) {
|
|
281
|
+
if (input.file.type !== "file" || input.file.content === void 0 || !input.file.path.endsWith(".md")) return input.file;
|
|
282
|
+
const result = await this.processDocument({
|
|
283
|
+
consumer: input.consumer,
|
|
284
|
+
mode: input.mode,
|
|
285
|
+
document: {
|
|
286
|
+
stage: input.stage,
|
|
287
|
+
kind: "artifact",
|
|
288
|
+
changeId: input.changeId,
|
|
289
|
+
schemaName: input.schemaName,
|
|
290
|
+
artifactId: input.artifactId,
|
|
291
|
+
artifactOutputPath: input.artifactOutputPath,
|
|
292
|
+
relativePath: `${input.root}/${input.file.path}`,
|
|
293
|
+
absolutePath: join(this.projectDir, input.root, input.file.path)
|
|
294
|
+
},
|
|
295
|
+
source: input.file.content
|
|
296
|
+
});
|
|
297
|
+
return {
|
|
298
|
+
...input.file,
|
|
299
|
+
content: result.markdown
|
|
300
|
+
};
|
|
210
301
|
}
|
|
211
302
|
inferChangeFileKind(path) {
|
|
212
303
|
if (path === "proposal.md") return "proposal";
|
|
@@ -1194,16 +1285,11 @@ async function loadDashboardOverview(ctx, reason = "dashboard-refresh") {
|
|
|
1194
1285
|
updatedAt: changeMeta.updatedAt
|
|
1195
1286
|
}));
|
|
1196
1287
|
const activeChanges = selectRecentDashboardItems(allActiveChanges);
|
|
1197
|
-
const archivedChanges =
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
createdAt: meta.createdAt,
|
|
1203
|
-
updatedAt: meta.updatedAt,
|
|
1204
|
-
tasksCompleted: change.tasks.filter((task) => task.completed).length
|
|
1205
|
-
};
|
|
1206
|
-
}))).filter((item) => item !== null);
|
|
1288
|
+
const archivedChanges = archiveMetas.map((meta) => ({
|
|
1289
|
+
id: meta.id,
|
|
1290
|
+
createdAt: meta.createdAt,
|
|
1291
|
+
updatedAt: meta.updatedAt
|
|
1292
|
+
}));
|
|
1207
1293
|
const allSpecifications = (await Promise.all(specMetas.map(async (meta) => {
|
|
1208
1294
|
const spec = await ctx.adapter.readSpec(meta.id);
|
|
1209
1295
|
if (!spec) return null;
|
|
@@ -1218,7 +1304,7 @@ async function loadDashboardOverview(ctx, reason = "dashboard-refresh") {
|
|
|
1218
1304
|
const requirements = allSpecifications.reduce((sum, spec) => sum + spec.requirements, 0);
|
|
1219
1305
|
const tasksTotal = allActiveChanges.reduce((sum, change) => sum + change.progress.total, 0);
|
|
1220
1306
|
const tasksCompleted = allActiveChanges.reduce((sum, change) => sum + change.progress.completed, 0);
|
|
1221
|
-
const archivedTasksCompleted =
|
|
1307
|
+
const archivedTasksCompleted = 0;
|
|
1222
1308
|
const taskCompletionPercent = tasksTotal > 0 ? Math.round(tasksCompleted / tasksTotal * 100) : null;
|
|
1223
1309
|
const inProgressChanges = allActiveChanges.filter((change) => change.progress.total > 0 && change.progress.completed < change.progress.total).length;
|
|
1224
1310
|
const specificationTrendEvents = specMetas.flatMap((spec) => {
|
|
@@ -1232,7 +1318,7 @@ async function loadDashboardOverview(ctx, reason = "dashboard-refresh") {
|
|
|
1232
1318
|
const ts = parseDatedIdTimestamp(archive.id) ?? resolveTrendTimestamp(archive.updatedAt, archive.createdAt);
|
|
1233
1319
|
return ts === null ? [] : [{
|
|
1234
1320
|
ts,
|
|
1235
|
-
value:
|
|
1321
|
+
value: 1
|
|
1236
1322
|
}];
|
|
1237
1323
|
});
|
|
1238
1324
|
const specMetaById = new Map(specMetas.map((meta) => [meta.id, meta]));
|
|
@@ -1339,6 +1425,29 @@ async function loadDashboardOverview(ctx, reason = "dashboard-refresh") {
|
|
|
1339
1425
|
};
|
|
1340
1426
|
}
|
|
1341
1427
|
|
|
1428
|
+
//#endregion
|
|
1429
|
+
//#region src/entity-read-options.ts
|
|
1430
|
+
async function readEntityMetadata(ctx, stage, id) {
|
|
1431
|
+
return (stage === "change" ? await ctx.adapter.readChangeFiles(id) : await ctx.adapter.readArchivedChangeFiles(id)).find((file) => file.type === "file" && file.path === ".openspec.yaml")?.content ?? null;
|
|
1432
|
+
}
|
|
1433
|
+
async function buildEntityReadOptions(ctx, stage, id) {
|
|
1434
|
+
const schemaName = parseOpsxEntityMetadata(await readEntityMetadata(ctx, stage, id)).schemaName;
|
|
1435
|
+
if (!schemaName) return {};
|
|
1436
|
+
try {
|
|
1437
|
+
await ctx.kernel.waitForWarmup();
|
|
1438
|
+
await ctx.kernel.ensureSchemaDetail(schemaName);
|
|
1439
|
+
await ctx.kernel.ensureSchemaYaml(schemaName);
|
|
1440
|
+
const schemaYaml = ctx.kernel.getSchemaYaml(schemaName);
|
|
1441
|
+
const diagnostics = schemaYaml ? parseOpsxSchemaDetail(schemaYaml, schemaName, { path: `schema:${schemaName}` }).diagnostics : [];
|
|
1442
|
+
return {
|
|
1443
|
+
schemas: { [schemaName]: ctx.kernel.getSchemaDetail(schemaName) },
|
|
1444
|
+
schemaDiagnostics: diagnostics.length > 0 ? { [schemaName]: diagnostics } : void 0
|
|
1445
|
+
};
|
|
1446
|
+
} catch {
|
|
1447
|
+
return { schemas: {} };
|
|
1448
|
+
}
|
|
1449
|
+
}
|
|
1450
|
+
|
|
1342
1451
|
//#endregion
|
|
1343
1452
|
//#region src/notification-service.ts
|
|
1344
1453
|
var NotificationService = class {
|
|
@@ -3283,19 +3392,21 @@ const archiveRouter = router({
|
|
|
3283
3392
|
return ctx.adapter.listArchivedChangesWithMeta();
|
|
3284
3393
|
}),
|
|
3285
3394
|
get: publicProcedure.input(z.object({ id: z.string() })).query(async ({ ctx, input }) => {
|
|
3286
|
-
return ctx.documentService.
|
|
3395
|
+
return ctx.documentService.readEntityDetail("archive", input.id, "view", "processed", await buildEntityReadOptions(ctx, "archive", input.id));
|
|
3287
3396
|
}),
|
|
3288
3397
|
getRaw: publicProcedure.input(z.object({ id: z.string() })).query(async ({ ctx, input }) => {
|
|
3289
|
-
return ctx.
|
|
3398
|
+
return ctx.documentService.readEntityDetail("archive", input.id, "view", "source", await buildEntityReadOptions(ctx, "archive", input.id));
|
|
3290
3399
|
}),
|
|
3291
3400
|
subscribe: publicProcedure.subscription(({ ctx }) => {
|
|
3292
3401
|
return createReactiveSubscription(() => ctx.adapter.listArchivedChangesWithMeta());
|
|
3293
3402
|
}),
|
|
3294
3403
|
subscribeOne: publicProcedure.input(z.object({ id: z.string() })).subscription(({ ctx, input }) => {
|
|
3295
|
-
return createReactiveSubscriptionWithInput((id) => ctx.documentService.
|
|
3404
|
+
return createReactiveSubscriptionWithInput(async (id) => ctx.documentService.readEntityDetail("archive", id, "view", "processed", await buildEntityReadOptions(ctx, "archive", id)))(input.id);
|
|
3296
3405
|
}),
|
|
3297
3406
|
subscribeFiles: publicProcedure.input(z.object({ id: z.string() })).subscription(({ ctx, input }) => {
|
|
3298
|
-
return createReactiveSubscriptionWithInput((id) =>
|
|
3407
|
+
return createReactiveSubscriptionWithInput(async (id) => {
|
|
3408
|
+
return (await ctx.documentService.readEntityDetail("archive", id, "view", "processed", await buildEntityReadOptions(ctx, "archive", id)))?.files ?? [];
|
|
3409
|
+
})(input.id);
|
|
3299
3410
|
})
|
|
3300
3411
|
});
|
|
3301
3412
|
z.object({
|
|
@@ -3397,13 +3508,14 @@ const configRouter = router({
|
|
|
3397
3508
|
terminal: TerminalConfigSchema.omit({ rendererEngine: true }).partial().extend({ rendererEngine: TerminalRendererEngineSchema.optional() }).optional(),
|
|
3398
3509
|
dashboard: DashboardConfigSchema.partial().optional(),
|
|
3399
3510
|
git: GitConfigSchema.partial().optional(),
|
|
3400
|
-
notifications: NotificationSettingsSchema.partial().optional()
|
|
3511
|
+
notifications: NotificationSettingsSchema.partial().optional(),
|
|
3512
|
+
translation: DocumentTranslationConfigSchema.partial().optional()
|
|
3401
3513
|
})).mutation(async ({ ctx, input }) => {
|
|
3402
3514
|
const hasCliCommand = input.cli !== void 0 && Object.prototype.hasOwnProperty.call(input.cli, "command");
|
|
3403
3515
|
const hasCliArgs = input.cli !== void 0 && Object.prototype.hasOwnProperty.call(input.cli, "args");
|
|
3404
3516
|
if (hasCliCommand && !hasCliArgs) {
|
|
3405
3517
|
await ctx.configManager.setCliCommand(input.cli?.command ?? "");
|
|
3406
|
-
if (input.theme !== void 0 || input.codeEditor !== void 0 || input.appBaseUrl !== void 0 || input.opsx !== void 0 || input.terminal !== void 0 || input.dashboard !== void 0 || input.git !== void 0 || input.notifications !== void 0) await ctx.configManager.writeConfig({
|
|
3518
|
+
if (input.theme !== void 0 || input.codeEditor !== void 0 || input.appBaseUrl !== void 0 || input.opsx !== void 0 || input.terminal !== void 0 || input.dashboard !== void 0 || input.git !== void 0 || input.notifications !== void 0 || input.translation !== void 0) await ctx.configManager.writeConfig({
|
|
3407
3519
|
theme: input.theme,
|
|
3408
3520
|
codeEditor: input.codeEditor,
|
|
3409
3521
|
appBaseUrl: input.appBaseUrl,
|
|
@@ -3411,7 +3523,8 @@ const configRouter = router({
|
|
|
3411
3523
|
terminal: input.terminal,
|
|
3412
3524
|
dashboard: input.dashboard,
|
|
3413
3525
|
git: input.git,
|
|
3414
|
-
notifications: input.notifications
|
|
3526
|
+
notifications: input.notifications,
|
|
3527
|
+
translation: input.translation
|
|
3415
3528
|
});
|
|
3416
3529
|
return { success: true };
|
|
3417
3530
|
}
|
|
@@ -3820,7 +3933,7 @@ const opsxRouter = router({
|
|
|
3820
3933
|
})).query(async ({ ctx, input }) => {
|
|
3821
3934
|
await ctx.kernel.waitForWarmup();
|
|
3822
3935
|
await ctx.kernel.ensureArtifactOutput(input.changeId, input.outputPath);
|
|
3823
|
-
return ctx.
|
|
3936
|
+
return ctx.documentService.readChangeArtifactOutput(input.changeId, input.outputPath, "view", "processed");
|
|
3824
3937
|
}),
|
|
3825
3938
|
subscribeArtifactOutput: publicProcedure.input(z.object({
|
|
3826
3939
|
changeId: z.string(),
|
|
@@ -3829,7 +3942,7 @@ const opsxRouter = router({
|
|
|
3829
3942
|
return createReactiveSubscription(async () => {
|
|
3830
3943
|
await ctx.kernel.waitForWarmup();
|
|
3831
3944
|
await ctx.kernel.ensureArtifactOutput(input.changeId, input.outputPath);
|
|
3832
|
-
return ctx.
|
|
3945
|
+
return ctx.documentService.readChangeArtifactOutput(input.changeId, input.outputPath, "view", "processed");
|
|
3833
3946
|
});
|
|
3834
3947
|
}),
|
|
3835
3948
|
readGlobArtifactFiles: publicProcedure.input(z.object({
|
|
@@ -3838,7 +3951,7 @@ const opsxRouter = router({
|
|
|
3838
3951
|
})).query(async ({ ctx, input }) => {
|
|
3839
3952
|
await ctx.kernel.waitForWarmup();
|
|
3840
3953
|
await ctx.kernel.ensureGlobArtifactFiles(input.changeId, input.outputPath);
|
|
3841
|
-
return ctx.
|
|
3954
|
+
return ctx.documentService.readChangeGlobArtifactFiles(input.changeId, input.outputPath, "view", "processed");
|
|
3842
3955
|
}),
|
|
3843
3956
|
subscribeGlobArtifactFiles: publicProcedure.input(z.object({
|
|
3844
3957
|
changeId: z.string(),
|
|
@@ -3847,7 +3960,7 @@ const opsxRouter = router({
|
|
|
3847
3960
|
return createReactiveSubscription(async () => {
|
|
3848
3961
|
await ctx.kernel.waitForWarmup();
|
|
3849
3962
|
await ctx.kernel.ensureGlobArtifactFiles(input.changeId, input.outputPath);
|
|
3850
|
-
return ctx.
|
|
3963
|
+
return ctx.documentService.readChangeGlobArtifactFiles(input.changeId, input.outputPath, "view", "processed");
|
|
3851
3964
|
});
|
|
3852
3965
|
}),
|
|
3853
3966
|
writeArtifactOutput: publicProcedure.input(z.object({
|
|
@@ -4067,7 +4180,7 @@ const appRouter = router({
|
|
|
4067
4180
|
function joinParts(parts) {
|
|
4068
4181
|
return parts.map((part) => part?.trim() ?? "").filter((part) => part.length > 0).join("\n\n");
|
|
4069
4182
|
}
|
|
4070
|
-
async function collectSearchDocuments(adapter, documentService) {
|
|
4183
|
+
async function collectSearchDocuments(adapter, documentService, resolveEntityReadOptions) {
|
|
4071
4184
|
const docs = [];
|
|
4072
4185
|
const specs = await adapter.listSpecsWithMeta();
|
|
4073
4186
|
for (const spec of specs) {
|
|
@@ -4104,20 +4217,16 @@ async function collectSearchDocuments(adapter, documentService) {
|
|
|
4104
4217
|
}
|
|
4105
4218
|
const archives = await adapter.listArchivedChangesWithMeta();
|
|
4106
4219
|
for (const archive of archives) {
|
|
4107
|
-
const
|
|
4108
|
-
|
|
4220
|
+
const entityOptions = resolveEntityReadOptions ? await resolveEntityReadOptions("archive", archive.id) : void 0;
|
|
4221
|
+
const entity = documentService ? await documentService.readEntityDetail("archive", archive.id, "search", "processed", entityOptions) : await adapter.readEntityDetail("archive", archive.id);
|
|
4222
|
+
if (!entity) continue;
|
|
4109
4223
|
docs.push({
|
|
4110
4224
|
id: `archive:${archive.id}`,
|
|
4111
4225
|
kind: "archive",
|
|
4112
4226
|
title: archive.name,
|
|
4113
4227
|
href: `/archive/${encodeURIComponent(archive.id)}`,
|
|
4114
4228
|
path: `openspec/changes/archive/${archive.id}`,
|
|
4115
|
-
content: joinParts(
|
|
4116
|
-
typeof raw.proposal === "string" ? raw.proposal : raw.proposal.markdown,
|
|
4117
|
-
typeof raw.tasks === "string" ? raw.tasks : raw.tasks.markdown,
|
|
4118
|
-
typeof raw.design === "string" ? raw.design : raw.design?.markdown,
|
|
4119
|
-
...raw.deltaSpecs.map((deltaSpec) => deltaSpec.content)
|
|
4120
|
-
]),
|
|
4229
|
+
content: joinParts(entity.files.filter((file) => file.type === "file").map((file) => file.content)),
|
|
4121
4230
|
updatedAt: archive.updatedAt
|
|
4122
4231
|
});
|
|
4123
4232
|
}
|
|
@@ -4133,9 +4242,10 @@ var SearchService = class {
|
|
|
4133
4242
|
initPromise = null;
|
|
4134
4243
|
rebuildPromise = null;
|
|
4135
4244
|
rebuildTimer = null;
|
|
4136
|
-
constructor(adapter, watcher, provider = new NodeWorkerSearchProvider(), documentService) {
|
|
4245
|
+
constructor(adapter, watcher, provider = new NodeWorkerSearchProvider(), documentService, resolveEntityReadOptions) {
|
|
4137
4246
|
this.adapter = adapter;
|
|
4138
4247
|
this.documentService = documentService;
|
|
4248
|
+
this.resolveEntityReadOptions = resolveEntityReadOptions;
|
|
4139
4249
|
this.provider = provider;
|
|
4140
4250
|
watcher?.on("change", () => {
|
|
4141
4251
|
this.scheduleRebuild();
|
|
@@ -4181,7 +4291,7 @@ var SearchService = class {
|
|
|
4181
4291
|
if (!forceInit && !this.initialized) return;
|
|
4182
4292
|
if (this.rebuildPromise) return this.rebuildPromise;
|
|
4183
4293
|
this.rebuildPromise = (async () => {
|
|
4184
|
-
const docs = await collectSearchDocuments(this.adapter, this.documentService);
|
|
4294
|
+
const docs = await collectSearchDocuments(this.adapter, this.documentService, this.resolveEntityReadOptions);
|
|
4185
4295
|
if (this.initialized) await this.provider.replaceAll(docs);
|
|
4186
4296
|
else {
|
|
4187
4297
|
await this.provider.init(docs);
|
|
@@ -4454,7 +4564,11 @@ function createServer(config) {
|
|
|
4454
4564
|
const notificationService = new NotificationService();
|
|
4455
4565
|
const customSoundService = new CustomSoundService();
|
|
4456
4566
|
const watcher = config.enableWatcher !== false ? new OpenSpecWatcher(config.projectDir) : void 0;
|
|
4457
|
-
const
|
|
4567
|
+
const entityReadOptionsContext = {
|
|
4568
|
+
adapter,
|
|
4569
|
+
kernel
|
|
4570
|
+
};
|
|
4571
|
+
const searchService = new SearchService(adapter, watcher, void 0, documentService, (stage, id) => buildEntityReadOptions(entityReadOptionsContext, stage, id));
|
|
4458
4572
|
const dashboardOverviewService = new DashboardOverviewService((reason) => loadDashboardOverview({
|
|
4459
4573
|
adapter,
|
|
4460
4574
|
configManager,
|