@openspecui/server 1.1.0 → 1.1.2

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 (2) hide show
  1. package/dist/index.mjs +91 -264
  2. package/package.json +3 -3
package/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  import { serve } from "@hono/node-server";
2
- import { ApplyInstructionsSchema, ArtifactInstructionsSchema, ChangeStatusSchema, CliExecutor, ConfigManager, OpenSpecAdapter, OpenSpecWatcher, OpsxKernel, PtyClientMessageSchema, ReactiveContext, SchemaDetailSchema, SchemaInfoSchema, SchemaResolutionSchema, TemplatesSchema, getAllTools, getAvailableTools, getConfiguredTools, getDefaultCliCommandString, initWatcherPool, isWatcherPoolInitialized, reactiveReadDir, reactiveReadFile, reactiveStat, sniffGlobalCli } from "@openspecui/core";
2
+ import { CliExecutor, ConfigManager, OpenSpecAdapter, OpenSpecWatcher, OpsxKernel, PtyClientMessageSchema, ReactiveContext, getAllTools, getAvailableTools, getConfiguredTools, getDefaultCliCommandString, initWatcherPool, isWatcherPoolInitialized, sniffGlobalCli } from "@openspecui/core";
3
3
  import { fetchRequestHandler } from "@trpc/server/adapters/fetch";
4
4
  import { applyWSSHandler } from "@trpc/server/adapters/ws";
5
5
  import { Hono } from "hono";
@@ -12,9 +12,8 @@ import { SearchQuerySchema } from "@openspecui/search";
12
12
  import { initTRPC } from "@trpc/server";
13
13
  import { observable } from "@trpc/server/observable";
14
14
  import { mkdir, rm, writeFile } from "node:fs/promises";
15
- import { dirname, join, matchesGlob, relative, resolve, sep } from "node:path";
15
+ import { dirname, join, resolve, sep } from "node:path";
16
16
  import { z } from "zod";
17
- import { parse } from "yaml";
18
17
  import { EventEmitter as EventEmitter$1 } from "node:events";
19
18
  import { NodeWorkerSearchProvider } from "@openspecui/search/node";
20
19
 
@@ -465,53 +464,6 @@ function createCliStreamObservable(startStream) {
465
464
  });
466
465
  }
467
466
 
468
- //#endregion
469
- //#region src/opsx-schema.ts
470
- const SchemaYamlArtifactSchema = z.object({
471
- id: z.string(),
472
- generates: z.string(),
473
- description: z.string().optional(),
474
- template: z.string().optional(),
475
- instruction: z.string().optional(),
476
- requires: z.array(z.string()).optional()
477
- });
478
- const SchemaYamlSchema = z.object({
479
- name: z.string(),
480
- version: z.union([z.string(), z.number()]).optional(),
481
- description: z.string().optional(),
482
- artifacts: z.array(SchemaYamlArtifactSchema),
483
- apply: z.object({
484
- requires: z.array(z.string()).optional(),
485
- tracks: z.string().optional(),
486
- instruction: z.string().optional()
487
- }).optional()
488
- });
489
- function parseSchemaYaml(content) {
490
- const raw = parse(content);
491
- const parsed = SchemaYamlSchema.safeParse(raw);
492
- if (!parsed.success) throw new Error(`Invalid schema.yaml: ${parsed.error.message}`);
493
- const { artifacts, apply, name, description, version } = parsed.data;
494
- const detail = {
495
- name,
496
- description,
497
- version,
498
- artifacts: artifacts.map((artifact) => ({
499
- id: artifact.id,
500
- outputPath: artifact.generates,
501
- description: artifact.description,
502
- template: artifact.template,
503
- instruction: artifact.instruction,
504
- requires: artifact.requires ?? []
505
- })),
506
- applyRequires: apply?.requires ?? [],
507
- applyTracks: apply?.tracks,
508
- applyInstruction: apply?.instruction
509
- };
510
- const validated = SchemaDetailSchema.safeParse(detail);
511
- if (!validated.success) throw new Error(`Invalid schema detail: ${validated.error.message}`);
512
- return validated.data;
513
- }
514
-
515
467
  //#endregion
516
468
  //#region src/reactive-kv.ts
517
469
  /**
@@ -639,20 +591,6 @@ function requireChangeId(changeId) {
639
591
  function ensureEditableSource(source, label) {
640
592
  if (source === "package") throw new Error(`${label} is read-only (package source)`);
641
593
  }
642
- function parseCliJson(raw, schema, label) {
643
- const trimmed = raw.trim();
644
- if (!trimmed) throw new Error(`${label} returned empty output`);
645
- let parsed;
646
- try {
647
- parsed = JSON.parse(trimmed);
648
- } catch (err) {
649
- const message = err instanceof Error ? err.message : String(err);
650
- throw new Error(`${label} returned invalid JSON: ${message}`);
651
- }
652
- const result = schema.safeParse(parsed);
653
- if (!result.success) throw new Error(`${label} returned unexpected JSON: ${result.error.message}`);
654
- return result.data;
655
- }
656
594
  function resolveEntryPath(root, entryPath) {
657
595
  const normalizedRoot = resolve(root);
658
596
  const resolvedPath = resolve(normalizedRoot, entryPath);
@@ -660,153 +598,62 @@ function resolveEntryPath(root, entryPath) {
660
598
  if (resolvedPath !== normalizedRoot && !resolvedPath.startsWith(rootPrefix)) throw new Error("Invalid path: outside schema root");
661
599
  return resolvedPath;
662
600
  }
663
- function toRelativePath(root, absolutePath) {
664
- return relative(root, absolutePath).split(sep).join("/");
665
- }
666
- async function readEntriesUnderRoot(root) {
667
- if (!(await reactiveStat(root))?.isDirectory) return [];
668
- const collectEntries = async (dir) => {
669
- const names = await reactiveReadDir(dir, { includeHidden: false });
670
- const entries = [];
671
- for (const name of names) {
672
- const fullPath = join(dir, name);
673
- const statInfo = await reactiveStat(fullPath);
674
- if (!statInfo) continue;
675
- const relativePath = toRelativePath(root, fullPath);
676
- if (statInfo.isDirectory) {
677
- entries.push({
678
- path: relativePath,
679
- type: "directory"
680
- });
681
- entries.push(...await collectEntries(fullPath));
682
- } else {
683
- const content = await reactiveReadFile(fullPath);
684
- const size = content ? Buffer.byteLength(content, "utf-8") : void 0;
685
- entries.push({
686
- path: relativePath,
687
- type: "file",
688
- content: content ?? void 0,
689
- size
690
- });
691
- }
692
- }
693
- return entries;
694
- };
695
- return collectEntries(root);
696
- }
697
- async function touchOpsxProjectDeps(projectDir) {
698
- const openspecDir = join(projectDir, "openspec");
699
- await reactiveReadFile(join(openspecDir, "config.yaml"));
700
- const schemaRoot = join(openspecDir, "schemas");
701
- const schemaDirs = await reactiveReadDir(schemaRoot, {
702
- directoriesOnly: true,
703
- includeHidden: true
704
- });
705
- await Promise.all(schemaDirs.map((name) => reactiveReadFile(join(schemaRoot, name, "schema.yaml"))));
706
- await reactiveReadDir(join(openspecDir, "changes"), {
707
- directoriesOnly: true,
708
- includeHidden: true,
709
- exclude: ["archive"]
710
- });
711
- }
712
- async function touchOpsxChangeDeps(projectDir, changeId) {
713
- const changeDir = join(projectDir, "openspec", "changes", changeId);
714
- await reactiveReadDir(changeDir, { includeHidden: true });
715
- await reactiveReadFile(join(changeDir, ".openspec.yaml"));
716
- }
717
- async function readGlobArtifactFiles(projectDir, changeId, outputPath) {
718
- return (await readEntriesUnderRoot(join(projectDir, "openspec", "changes", changeId))).filter((entry) => entry.type === "file" && matchesGlob(entry.path, outputPath)).map((entry) => ({
719
- path: entry.path,
720
- type: "file",
721
- content: entry.content ?? ""
722
- }));
723
- }
724
601
  async function fetchOpsxStatus(ctx, input) {
725
602
  const changeId = requireChangeId(input.change);
726
- await touchOpsxProjectDeps(ctx.projectDir);
727
- await touchOpsxChangeDeps(ctx.projectDir, changeId);
728
- const args = [
729
- "status",
730
- "--json",
731
- "--change",
732
- changeId
733
- ];
734
- if (input.schema) args.push("--schema", input.schema);
735
- const result = await ctx.cliExecutor.execute(args);
736
- if (!result.success) throw new Error(result.stderr || `openspec status failed (exit ${result.exitCode ?? "null"})`);
737
- const status = parseCliJson(result.stdout, ChangeStatusSchema, "openspec status");
738
- const changeRelDir = `openspec/changes/${changeId}`;
739
- for (const artifact of status.artifacts) artifact.relativePath = `${changeRelDir}/${artifact.outputPath}`;
740
- return status;
603
+ await ctx.kernel.waitForWarmup();
604
+ await ctx.kernel.ensureStatus(changeId, input.schema);
605
+ return ctx.kernel.getStatus(changeId, input.schema);
741
606
  }
742
607
  async function fetchOpsxStatusList(ctx) {
743
- const changeIds = await reactiveReadDir(join(ctx.projectDir, "openspec", "changes"), {
744
- directoriesOnly: true,
745
- includeHidden: false,
746
- exclude: ["archive"]
747
- });
748
- return await Promise.all(changeIds.map((changeId) => fetchOpsxStatus(ctx, { change: changeId })));
608
+ await ctx.kernel.waitForWarmup();
609
+ await ctx.kernel.ensureStatusList();
610
+ return ctx.kernel.getStatusList();
749
611
  }
750
612
  async function fetchOpsxInstructions(ctx, input) {
751
613
  const changeId = requireChangeId(input.change);
752
- await touchOpsxProjectDeps(ctx.projectDir);
753
- await touchOpsxChangeDeps(ctx.projectDir, changeId);
754
- const args = [
755
- "instructions",
756
- input.artifact,
757
- "--json",
758
- "--change",
759
- changeId
760
- ];
761
- if (input.schema) args.push("--schema", input.schema);
762
- const result = await ctx.cliExecutor.execute(args);
763
- if (!result.success) throw new Error(result.stderr || `openspec instructions failed (exit ${result.exitCode ?? "null"})`);
764
- return parseCliJson(result.stdout, ArtifactInstructionsSchema, "openspec instructions");
614
+ await ctx.kernel.waitForWarmup();
615
+ await ctx.kernel.ensureInstructions(changeId, input.artifact, input.schema);
616
+ return ctx.kernel.getInstructions(changeId, input.artifact, input.schema);
765
617
  }
766
618
  async function fetchOpsxApplyInstructions(ctx, input) {
767
619
  const changeId = requireChangeId(input.change);
768
- await touchOpsxProjectDeps(ctx.projectDir);
769
- await touchOpsxChangeDeps(ctx.projectDir, changeId);
770
- const args = [
771
- "instructions",
772
- "apply",
773
- "--json",
774
- "--change",
775
- changeId
776
- ];
777
- if (input.schema) args.push("--schema", input.schema);
778
- const result = await ctx.cliExecutor.execute(args);
779
- if (!result.success) throw new Error(result.stderr || `openspec instructions apply failed (exit ${result.exitCode ?? "null"})`);
780
- return parseCliJson(result.stdout, ApplyInstructionsSchema, "openspec instructions apply");
620
+ await ctx.kernel.waitForWarmup();
621
+ await ctx.kernel.ensureApplyInstructions(changeId, input.schema);
622
+ return ctx.kernel.getApplyInstructions(changeId, input.schema);
781
623
  }
782
- async function fetchOpsxSchemas(ctx) {
783
- await touchOpsxProjectDeps(ctx.projectDir);
784
- const result = await ctx.cliExecutor.schemas();
785
- if (!result.success) throw new Error(result.stderr || `openspec schemas failed (exit ${result.exitCode ?? "null"})`);
786
- return parseCliJson(result.stdout, z.array(SchemaInfoSchema), "openspec schemas");
624
+ async function fetchOpsxConfigBundle(ctx) {
625
+ await ctx.kernel.ensureSchemas();
626
+ const schemas = ctx.kernel.getSchemas();
627
+ for (const schema of schemas) {
628
+ ctx.kernel.ensureSchemaDetail(schema.name).catch(() => {});
629
+ ctx.kernel.ensureSchemaResolution(schema.name).catch(() => {});
630
+ }
631
+ const schemaDetails = {};
632
+ const schemaResolutions = {};
633
+ for (const schema of schemas) {
634
+ schemaDetails[schema.name] = ctx.kernel.peekSchemaDetail(schema.name);
635
+ schemaResolutions[schema.name] = ctx.kernel.peekSchemaResolution(schema.name);
636
+ }
637
+ return {
638
+ schemas,
639
+ schemaDetails,
640
+ schemaResolutions
641
+ };
787
642
  }
788
643
  async function fetchOpsxSchemaResolution(ctx, name) {
789
- await touchOpsxProjectDeps(ctx.projectDir);
790
- const result = await ctx.cliExecutor.schemaWhich(name);
791
- if (!result.success) throw new Error(result.stderr || `openspec schema which failed (exit ${result.exitCode ?? "null"})`);
792
- return parseCliJson(result.stdout, SchemaResolutionSchema, "openspec schema which");
644
+ await ctx.kernel.waitForWarmup();
645
+ await ctx.kernel.ensureSchemaResolution(name);
646
+ return ctx.kernel.getSchemaResolution(name);
793
647
  }
794
648
  async function fetchOpsxTemplates(ctx, schema) {
795
- await touchOpsxProjectDeps(ctx.projectDir);
796
- const result = await ctx.cliExecutor.templates(schema);
797
- if (!result.success) throw new Error(result.stderr || `openspec templates failed (exit ${result.exitCode ?? "null"})`);
798
- return parseCliJson(result.stdout, TemplatesSchema, "openspec templates");
649
+ await ctx.kernel.waitForWarmup();
650
+ await ctx.kernel.ensureTemplates(schema);
651
+ return ctx.kernel.getTemplates(schema);
799
652
  }
800
653
  async function fetchOpsxTemplateContents(ctx, schema) {
801
- const templates = await fetchOpsxTemplates(ctx, schema);
802
- const entries = await Promise.all(Object.entries(templates).map(async ([artifactId, info]) => {
803
- return [artifactId, {
804
- content: await reactiveReadFile(info.path),
805
- path: info.path,
806
- source: info.source
807
- }];
808
- }));
809
- return Object.fromEntries(entries);
654
+ await ctx.kernel.waitForWarmup();
655
+ await ctx.kernel.ensureTemplateContents(schema);
656
+ return ctx.kernel.getTemplateContents(schema);
810
657
  }
811
658
  /**
812
659
  * Spec router - spec CRUD operations
@@ -1196,11 +1043,11 @@ const opsxRouter = router({
1196
1043
  })).subscription(({ ctx, input }) => {
1197
1044
  return createReactiveSubscription(() => fetchOpsxApplyInstructions(ctx, input));
1198
1045
  }),
1199
- schemas: publicProcedure.query(async ({ ctx }) => {
1200
- return fetchOpsxSchemas(ctx);
1046
+ configBundle: publicProcedure.query(async ({ ctx }) => {
1047
+ return fetchOpsxConfigBundle(ctx);
1201
1048
  }),
1202
- subscribeSchemas: publicProcedure.subscription(({ ctx }) => {
1203
- return createReactiveSubscription(() => fetchOpsxSchemas(ctx));
1049
+ subscribeConfigBundle: publicProcedure.subscription(({ ctx }) => {
1050
+ return createReactiveSubscription(() => fetchOpsxConfigBundle(ctx));
1204
1051
  }),
1205
1052
  templates: publicProcedure.input(z.object({ schema: z.string().optional() }).optional()).query(async ({ ctx, input }) => {
1206
1053
  return fetchOpsxTemplates(ctx, input?.schema);
@@ -1214,53 +1061,34 @@ const opsxRouter = router({
1214
1061
  subscribeTemplateContents: publicProcedure.input(z.object({ schema: z.string().optional() }).optional()).subscription(({ ctx, input }) => {
1215
1062
  return createReactiveSubscription(() => fetchOpsxTemplateContents(ctx, input?.schema));
1216
1063
  }),
1217
- schemaResolution: publicProcedure.input(z.object({ name: z.string() })).query(async ({ ctx, input }) => {
1218
- return fetchOpsxSchemaResolution(ctx, input.name);
1219
- }),
1220
- subscribeSchemaResolution: publicProcedure.input(z.object({ name: z.string() })).subscription(({ ctx, input }) => {
1221
- return createReactiveSubscription(() => fetchOpsxSchemaResolution(ctx, input.name));
1222
- }),
1223
- schemaDetail: publicProcedure.input(z.object({ name: z.string() })).query(async ({ ctx, input }) => {
1224
- await touchOpsxProjectDeps(ctx.projectDir);
1225
- const schemaPath = join((await fetchOpsxSchemaResolution(ctx, input.name)).path, "schema.yaml");
1226
- const content = await reactiveReadFile(schemaPath);
1227
- if (!content) throw new Error(`schema.yaml not found at ${schemaPath}`);
1228
- return parseSchemaYaml(content);
1229
- }),
1230
- subscribeSchemaDetail: publicProcedure.input(z.object({ name: z.string() })).subscription(({ ctx, input }) => {
1231
- return createReactiveSubscription(async () => {
1232
- await touchOpsxProjectDeps(ctx.projectDir);
1233
- const schemaPath = join((await fetchOpsxSchemaResolution(ctx, input.name)).path, "schema.yaml");
1234
- const content = await reactiveReadFile(schemaPath);
1235
- if (!content) throw new Error(`schema.yaml not found at ${schemaPath}`);
1236
- return parseSchemaYaml(content);
1237
- });
1238
- }),
1239
1064
  schemaFiles: publicProcedure.input(z.object({ name: z.string() })).query(async ({ ctx, input }) => {
1240
- await touchOpsxProjectDeps(ctx.projectDir);
1241
- return readEntriesUnderRoot((await fetchOpsxSchemaResolution(ctx, input.name)).path);
1065
+ await ctx.kernel.waitForWarmup();
1066
+ await ctx.kernel.ensureSchemaFiles(input.name);
1067
+ return ctx.kernel.getSchemaFiles(input.name);
1242
1068
  }),
1243
1069
  subscribeSchemaFiles: publicProcedure.input(z.object({ name: z.string() })).subscription(({ ctx, input }) => {
1244
1070
  return createReactiveSubscription(async () => {
1245
- await touchOpsxProjectDeps(ctx.projectDir);
1246
- return readEntriesUnderRoot((await fetchOpsxSchemaResolution(ctx, input.name)).path);
1071
+ await ctx.kernel.waitForWarmup();
1072
+ await ctx.kernel.ensureSchemaFiles(input.name);
1073
+ return ctx.kernel.getSchemaFiles(input.name);
1247
1074
  });
1248
1075
  }),
1249
1076
  schemaYaml: publicProcedure.input(z.object({ name: z.string() })).query(async ({ ctx, input }) => {
1250
- await touchOpsxProjectDeps(ctx.projectDir);
1251
- return reactiveReadFile(join((await fetchOpsxSchemaResolution(ctx, input.name)).path, "schema.yaml"));
1077
+ await ctx.kernel.waitForWarmup();
1078
+ await ctx.kernel.ensureSchemaYaml(input.name);
1079
+ return ctx.kernel.getSchemaYaml(input.name);
1252
1080
  }),
1253
1081
  subscribeSchemaYaml: publicProcedure.input(z.object({ name: z.string() })).subscription(({ ctx, input }) => {
1254
1082
  return createReactiveSubscription(async () => {
1255
- await touchOpsxProjectDeps(ctx.projectDir);
1256
- return reactiveReadFile(join((await fetchOpsxSchemaResolution(ctx, input.name)).path, "schema.yaml"));
1083
+ await ctx.kernel.waitForWarmup();
1084
+ await ctx.kernel.ensureSchemaYaml(input.name);
1085
+ return ctx.kernel.getSchemaYaml(input.name);
1257
1086
  });
1258
1087
  }),
1259
1088
  writeSchemaYaml: publicProcedure.input(z.object({
1260
1089
  name: z.string(),
1261
1090
  content: z.string()
1262
1091
  })).mutation(async ({ ctx, input }) => {
1263
- await touchOpsxProjectDeps(ctx.projectDir);
1264
1092
  const resolution = await fetchOpsxSchemaResolution(ctx, input.name);
1265
1093
  ensureEditableSource(resolution.source, "schema.yaml");
1266
1094
  const schemaPath = join(resolution.path, "schema.yaml");
@@ -1273,7 +1101,6 @@ const opsxRouter = router({
1273
1101
  path: z.string(),
1274
1102
  content: z.string()
1275
1103
  })).mutation(async ({ ctx, input }) => {
1276
- await touchOpsxProjectDeps(ctx.projectDir);
1277
1104
  const resolution = await fetchOpsxSchemaResolution(ctx, input.schema);
1278
1105
  ensureEditableSource(resolution.source, "schema file");
1279
1106
  if (!input.path.trim()) throw new Error("path is required");
@@ -1287,7 +1114,6 @@ const opsxRouter = router({
1287
1114
  path: z.string(),
1288
1115
  content: z.string().optional()
1289
1116
  })).mutation(async ({ ctx, input }) => {
1290
- await touchOpsxProjectDeps(ctx.projectDir);
1291
1117
  const resolution = await fetchOpsxSchemaResolution(ctx, input.schema);
1292
1118
  ensureEditableSource(resolution.source, "schema file");
1293
1119
  if (!input.path.trim()) throw new Error("path is required");
@@ -1300,7 +1126,6 @@ const opsxRouter = router({
1300
1126
  schema: z.string(),
1301
1127
  path: z.string()
1302
1128
  })).mutation(async ({ ctx, input }) => {
1303
- await touchOpsxProjectDeps(ctx.projectDir);
1304
1129
  const resolution = await fetchOpsxSchemaResolution(ctx, input.schema);
1305
1130
  ensureEditableSource(resolution.source, "schema directory");
1306
1131
  if (!input.path.trim()) throw new Error("path is required");
@@ -1311,7 +1136,6 @@ const opsxRouter = router({
1311
1136
  schema: z.string(),
1312
1137
  path: z.string()
1313
1138
  })).mutation(async ({ ctx, input }) => {
1314
- await touchOpsxProjectDeps(ctx.projectDir);
1315
1139
  const resolution = await fetchOpsxSchemaResolution(ctx, input.schema);
1316
1140
  ensureEditableSource(resolution.source, "schema entry");
1317
1141
  if (!input.path.trim()) throw new Error("path is required");
@@ -1327,26 +1151,18 @@ const opsxRouter = router({
1327
1151
  schema: z.string(),
1328
1152
  artifactId: z.string()
1329
1153
  })).query(async ({ ctx, input }) => {
1330
- const info = (await fetchOpsxTemplates(ctx, input.schema))[input.artifactId];
1154
+ const info = (await fetchOpsxTemplateContents(ctx, input.schema))[input.artifactId];
1331
1155
  if (!info) throw new Error(`Template not found for ${input.schema}:${input.artifactId}`);
1332
- return {
1333
- content: await reactiveReadFile(info.path),
1334
- path: info.path,
1335
- source: info.source
1336
- };
1156
+ return info;
1337
1157
  }),
1338
1158
  subscribeTemplateContent: publicProcedure.input(z.object({
1339
1159
  schema: z.string(),
1340
1160
  artifactId: z.string()
1341
1161
  })).subscription(({ ctx, input }) => {
1342
1162
  return createReactiveSubscription(async () => {
1343
- const info = (await fetchOpsxTemplates(ctx, input.schema))[input.artifactId];
1163
+ const info = (await fetchOpsxTemplateContents(ctx, input.schema))[input.artifactId];
1344
1164
  if (!info) throw new Error(`Template not found for ${input.schema}:${input.artifactId}`);
1345
- return {
1346
- content: await reactiveReadFile(info.path),
1347
- path: info.path,
1348
- source: info.source
1349
- };
1165
+ return info;
1350
1166
  });
1351
1167
  }),
1352
1168
  writeTemplateContent: publicProcedure.input(z.object({
@@ -1362,7 +1178,6 @@ const opsxRouter = router({
1362
1178
  return { success: true };
1363
1179
  }),
1364
1180
  deleteSchema: publicProcedure.input(z.object({ name: z.string() })).mutation(async ({ ctx, input }) => {
1365
- await touchOpsxProjectDeps(ctx.projectDir);
1366
1181
  const resolution = await fetchOpsxSchemaResolution(ctx, input.name);
1367
1182
  ensureEditableSource(resolution.source, "schema");
1368
1183
  await rm(resolution.path, {
@@ -1372,11 +1187,15 @@ const opsxRouter = router({
1372
1187
  return { success: true };
1373
1188
  }),
1374
1189
  projectConfig: publicProcedure.query(async ({ ctx }) => {
1375
- return reactiveReadFile(join(ctx.projectDir, "openspec", "config.yaml"));
1190
+ await ctx.kernel.waitForWarmup();
1191
+ await ctx.kernel.ensureProjectConfig();
1192
+ return ctx.kernel.getProjectConfig();
1376
1193
  }),
1377
1194
  subscribeProjectConfig: publicProcedure.subscription(({ ctx }) => {
1378
1195
  return createReactiveSubscription(async () => {
1379
- return reactiveReadFile(join(ctx.projectDir, "openspec", "config.yaml"));
1196
+ await ctx.kernel.waitForWarmup();
1197
+ await ctx.kernel.ensureProjectConfig();
1198
+ return ctx.kernel.getProjectConfig();
1380
1199
  });
1381
1200
  }),
1382
1201
  writeProjectConfig: publicProcedure.input(z.object({ content: z.string() })).mutation(async ({ ctx, input }) => {
@@ -1386,55 +1205,63 @@ const opsxRouter = router({
1386
1205
  return { success: true };
1387
1206
  }),
1388
1207
  listChanges: publicProcedure.query(async ({ ctx }) => {
1389
- return reactiveReadDir(join(ctx.projectDir, "openspec", "changes"), {
1390
- directoriesOnly: true,
1391
- exclude: ["archive"],
1392
- includeHidden: false
1393
- });
1208
+ await ctx.kernel.waitForWarmup();
1209
+ await ctx.kernel.ensureChangeIds();
1210
+ return ctx.kernel.getChangeIds();
1394
1211
  }),
1395
1212
  subscribeChanges: publicProcedure.subscription(({ ctx }) => {
1396
1213
  return createReactiveSubscription(async () => {
1397
- return reactiveReadDir(join(ctx.projectDir, "openspec", "changes"), {
1398
- directoriesOnly: true,
1399
- exclude: ["archive"],
1400
- includeHidden: false
1401
- });
1214
+ await ctx.kernel.waitForWarmup();
1215
+ await ctx.kernel.ensureChangeIds();
1216
+ return ctx.kernel.getChangeIds();
1402
1217
  });
1403
1218
  }),
1404
1219
  changeMetadata: publicProcedure.input(z.object({ changeId: z.string() })).query(async ({ ctx, input }) => {
1405
- return reactiveReadFile(join(ctx.projectDir, "openspec", "changes", input.changeId, ".openspec.yaml"));
1220
+ await ctx.kernel.waitForWarmup();
1221
+ await ctx.kernel.ensureChangeMetadata(input.changeId);
1222
+ return ctx.kernel.getChangeMetadata(input.changeId);
1406
1223
  }),
1407
1224
  subscribeChangeMetadata: publicProcedure.input(z.object({ changeId: z.string() })).subscription(({ ctx, input }) => {
1408
1225
  return createReactiveSubscription(async () => {
1409
- return reactiveReadFile(join(ctx.projectDir, "openspec", "changes", input.changeId, ".openspec.yaml"));
1226
+ await ctx.kernel.waitForWarmup();
1227
+ await ctx.kernel.ensureChangeMetadata(input.changeId);
1228
+ return ctx.kernel.getChangeMetadata(input.changeId);
1410
1229
  });
1411
1230
  }),
1412
1231
  readArtifactOutput: publicProcedure.input(z.object({
1413
1232
  changeId: z.string(),
1414
1233
  outputPath: z.string()
1415
1234
  })).query(async ({ ctx, input }) => {
1416
- return reactiveReadFile(join(ctx.projectDir, "openspec", "changes", input.changeId, input.outputPath));
1235
+ await ctx.kernel.waitForWarmup();
1236
+ await ctx.kernel.ensureArtifactOutput(input.changeId, input.outputPath);
1237
+ return ctx.kernel.getArtifactOutput(input.changeId, input.outputPath);
1417
1238
  }),
1418
1239
  subscribeArtifactOutput: publicProcedure.input(z.object({
1419
1240
  changeId: z.string(),
1420
1241
  outputPath: z.string()
1421
1242
  })).subscription(({ ctx, input }) => {
1422
1243
  return createReactiveSubscription(async () => {
1423
- return reactiveReadFile(join(ctx.projectDir, "openspec", "changes", input.changeId, input.outputPath));
1244
+ await ctx.kernel.waitForWarmup();
1245
+ await ctx.kernel.ensureArtifactOutput(input.changeId, input.outputPath);
1246
+ return ctx.kernel.getArtifactOutput(input.changeId, input.outputPath);
1424
1247
  });
1425
1248
  }),
1426
1249
  readGlobArtifactFiles: publicProcedure.input(z.object({
1427
1250
  changeId: z.string(),
1428
1251
  outputPath: z.string()
1429
1252
  })).query(async ({ ctx, input }) => {
1430
- return readGlobArtifactFiles(ctx.projectDir, input.changeId, input.outputPath);
1253
+ await ctx.kernel.waitForWarmup();
1254
+ await ctx.kernel.ensureGlobArtifactFiles(input.changeId, input.outputPath);
1255
+ return ctx.kernel.getGlobArtifactFiles(input.changeId, input.outputPath);
1431
1256
  }),
1432
1257
  subscribeGlobArtifactFiles: publicProcedure.input(z.object({
1433
1258
  changeId: z.string(),
1434
1259
  outputPath: z.string()
1435
1260
  })).subscription(({ ctx, input }) => {
1436
1261
  return createReactiveSubscription(async () => {
1437
- return readGlobArtifactFiles(ctx.projectDir, input.changeId, input.outputPath);
1262
+ await ctx.kernel.waitForWarmup();
1263
+ await ctx.kernel.ensureGlobArtifactFiles(input.changeId, input.outputPath);
1264
+ return ctx.kernel.getGlobArtifactFiles(input.changeId, input.outputPath);
1438
1265
  });
1439
1266
  }),
1440
1267
  writeArtifactOutput: publicProcedure.input(z.object({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openspecui/server",
3
- "version": "1.1.0",
3
+ "version": "1.1.2",
4
4
  "type": "module",
5
5
  "main": "dist/index.mjs",
6
6
  "exports": {
@@ -20,8 +20,8 @@
20
20
  "yaml": "^2.8.0",
21
21
  "yargs": "^18.0.0",
22
22
  "zod": "^3.24.1",
23
- "@openspecui/search": "1.1.0",
24
- "@openspecui/core": "1.1.0"
23
+ "@openspecui/core": "1.1.2",
24
+ "@openspecui/search": "1.1.0"
25
25
  },
26
26
  "devDependencies": {
27
27
  "@types/node": "^22.10.2",