openspecui 1.5.1 → 1.6.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.
Files changed (32) hide show
  1. package/dist/cli.mjs +245 -13
  2. package/dist/index.mjs +1 -1
  3. package/dist/{src-16GA3our.mjs → src-Brh3druE.mjs} +101 -4
  4. package/package.json +3 -3
  5. package/web/assets/{BufferResource-Bn1UWy0D.js → BufferResource-LpIscPOh.js} +1 -1
  6. package/web/assets/{CanvasRenderer-D8NiU8la.js → CanvasRenderer-hdBAgk8Z.js} +1 -1
  7. package/web/assets/{Filter-CRwq487x.js → Filter-DJVBXWdE.js} +1 -1
  8. package/web/assets/{RenderTargetSystem-CtoB_qTm.js → RenderTargetSystem-CEuUrT8d.js} +1 -1
  9. package/web/assets/{WebGLRenderer-BgKO8R0a.js → WebGLRenderer-DUSFltAk.js} +1 -1
  10. package/web/assets/{WebGPURenderer-CQeL2efC.js → WebGPURenderer-DafJULoy.js} +1 -1
  11. package/web/assets/{browserAll-DP6sOYev.js → browserAll-DYvdsmhE.js} +1 -1
  12. package/web/assets/{ghostty-web-evxujSxm.js → ghostty-web-BitlvuYh.js} +1 -1
  13. package/web/assets/{index-BnT52DZ8.js → index-5zLYkWge.js} +1 -1
  14. package/web/assets/{index-B0IbsqHi.js → index-B8AH-4mO.js} +1 -1
  15. package/web/assets/{index-D2Tp4F9B.js → index-BXlwoClD.js} +1 -1
  16. package/web/assets/{index-dSf1u0YV.js → index-BhxOfd1V.js} +1 -1
  17. package/web/assets/{index-f0QdJSzm.js → index-BwqUcS4o.js} +1 -1
  18. package/web/assets/{index-DTeOcXKn.js → index-CGTRVPmS.js} +1 -1
  19. package/web/assets/{index-T8xoxmUb.js → index-CKfGQiMr.js} +236 -205
  20. package/web/assets/{index-BejnsZfY.js → index-CYTyn82I.js} +1 -1
  21. package/web/assets/{index-DJqmTRAR.js → index-CjHce-bH.js} +1 -1
  22. package/web/assets/{index-B147AOgf.js → index-CuiBRRWA.js} +1 -1
  23. package/web/assets/{index-BPZ3nG0r.js → index-DEgCfC-b.js} +1 -1
  24. package/web/assets/{index-DcXyAs0z.js → index-DbrGteUX.js} +1 -1
  25. package/web/assets/{index-BMashGQn.js → index-Dge9ymDE.js} +1 -1
  26. package/web/assets/{index-CBCPR3Qb.js → index-DtJ1xKq6.js} +1 -1
  27. package/web/assets/index-USotIqwU.css +1 -0
  28. package/web/assets/{index-D6ardy54.js → index-gPPYc26D.js} +1 -1
  29. package/web/assets/{index-4MAU81Qk.js → index-hXwe0Xwc.js} +1 -1
  30. package/web/assets/{webworkerAll-DA2HufNb.js → webworkerAll-CFbGR7bA.js} +1 -1
  31. package/web/index.html +2 -2
  32. package/web/assets/index-Ys2MTD3W.css +0 -1
package/dist/cli.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { a as SchemaInfoSchema, c as CliExecutor, d as OpenSpecAdapter, f as __commonJS, i as SchemaDetailSchema, l as ConfigManager, o as SchemaResolutionSchema, p as __toESM, r as require_dist, s as TemplatesSchema, t as startServer, u as DEFAULT_CONFIG } from "./src-16GA3our.mjs";
2
+ import { a as SchemaInfoSchema, c as toOpsxDisplayPath, d as DEFAULT_CONFIG, f as OpenSpecAdapter, i as SchemaDetailSchema, l as CliExecutor, m as __toESM, o as SchemaResolutionSchema, p as __commonJS, r as require_dist, s as TemplatesSchema, t as startServer, u as ConfigManager } from "./src-Brh3druE.mjs";
3
3
  import { createRequire } from "node:module";
4
4
  import { basename, dirname, extname, join, normalize, relative, resolve } from "path";
5
5
  import { readFile } from "node:fs/promises";
@@ -8,7 +8,8 @@ import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync
8
8
  import { readFileSync as readFileSync$1, readdirSync as readdirSync$1, statSync as statSync$1, writeFile as writeFile$1 } from "fs";
9
9
  import { format, inspect } from "util";
10
10
  import { fileURLToPath } from "url";
11
- import { spawn } from "node:child_process";
11
+ import { execFile, spawn } from "node:child_process";
12
+ import { promisify as promisify$1 } from "node:util";
12
13
  import { fileURLToPath as fileURLToPath$1 } from "node:url";
13
14
  import { notStrictEqual, strictEqual } from "assert";
14
15
 
@@ -4506,11 +4507,12 @@ var yargs_default = Yargs;
4506
4507
  //#endregion
4507
4508
  //#region package.json
4508
4509
  var import_dist = require_dist();
4509
- var version = "1.5.1";
4510
+ var version = "1.6.0";
4510
4511
 
4511
4512
  //#endregion
4512
4513
  //#region src/export.ts
4513
4514
  const __dirname$1 = dirname$1(fileURLToPath$1(import.meta.url));
4515
+ const execFileAsync = promisify$1(execFile);
4514
4516
  function parseCliJson(raw, schema, label) {
4515
4517
  const trimmed = raw.trim();
4516
4518
  if (!trimmed) throw new Error(`${label} returned empty output`);
@@ -4559,6 +4561,184 @@ function parseSchemaYaml(content) {
4559
4561
  if (!validated.success) throw new Error(`Invalid schema.yaml detail: ${validated.error.message}`);
4560
4562
  return validated.data;
4561
4563
  }
4564
+ function isAbsoluteFsPath(path$1) {
4565
+ const normalized = path$1.replace(/\\/g, "/");
4566
+ return normalized.startsWith("/") || /^[A-Za-z]:\//.test(normalized);
4567
+ }
4568
+ function toAbsoluteProjectPath(projectDir, path$1) {
4569
+ return isAbsoluteFsPath(path$1) ? path$1 : resolve$1(projectDir, path$1);
4570
+ }
4571
+ function normalizeGitPath(path$1) {
4572
+ return path$1.replace(/\\/g, "/").replace(/^\.\//, "");
4573
+ }
4574
+ function parseRelatedChanges(paths) {
4575
+ const related = /* @__PURE__ */ new Set();
4576
+ for (const rawPath of paths) {
4577
+ const path$1 = normalizeGitPath(rawPath);
4578
+ const activeMatch = /^openspec\/changes\/([^/]+)\//.exec(path$1);
4579
+ if (activeMatch?.[1]) {
4580
+ related.add(activeMatch[1]);
4581
+ continue;
4582
+ }
4583
+ const archiveMatch = /^openspec\/changes\/archive\/([^/]+)\//.exec(path$1);
4584
+ if (archiveMatch?.[1]) related.add(archiveMatch[1].replace(/^\d{4}-\d{2}-\d{2}-/, ""));
4585
+ }
4586
+ return [...related].sort((a, b) => a.localeCompare(b));
4587
+ }
4588
+ function parseNumstat(numstatOutput) {
4589
+ let files = 0;
4590
+ let insertions = 0;
4591
+ let deletions = 0;
4592
+ for (const line of numstatOutput.split("\n")) {
4593
+ const trimmed = line.trim();
4594
+ if (!trimmed) continue;
4595
+ const [addRaw, delRaw] = trimmed.split(" ");
4596
+ if (!addRaw || !delRaw) continue;
4597
+ files += 1;
4598
+ if (addRaw !== "-") insertions += Number(addRaw) || 0;
4599
+ if (delRaw !== "-") deletions += Number(delRaw) || 0;
4600
+ }
4601
+ return {
4602
+ files,
4603
+ insertions,
4604
+ deletions
4605
+ };
4606
+ }
4607
+ async function readDefaultBranch(projectDir) {
4608
+ try {
4609
+ const { stdout } = await execFileAsync("git", [
4610
+ "symbolic-ref",
4611
+ "--quiet",
4612
+ "--short",
4613
+ "refs/remotes/origin/HEAD"
4614
+ ], {
4615
+ cwd: projectDir,
4616
+ encoding: "utf8",
4617
+ maxBuffer: 1024 * 1024
4618
+ });
4619
+ const branch = stdout.trim();
4620
+ if (branch.length > 0) return branch;
4621
+ } catch {}
4622
+ try {
4623
+ const { stdout } = await execFileAsync("git", [
4624
+ "rev-parse",
4625
+ "--abbrev-ref",
4626
+ "HEAD"
4627
+ ], {
4628
+ cwd: projectDir,
4629
+ encoding: "utf8",
4630
+ maxBuffer: 1024 * 1024
4631
+ });
4632
+ const branch = stdout.trim();
4633
+ if (branch.length > 0 && branch !== "HEAD") return branch;
4634
+ } catch {}
4635
+ return "main";
4636
+ }
4637
+ function parseRecentCommitLog(output) {
4638
+ const commits = [];
4639
+ let current = null;
4640
+ const pushCurrent = () => {
4641
+ if (!current) return;
4642
+ commits.push({
4643
+ hash: current.hash,
4644
+ title: current.title,
4645
+ committedAt: current.committedAt,
4646
+ relatedChanges: parseRelatedChanges(current.changedPaths),
4647
+ diff: parseNumstat(current.numstatLines.join("\n"))
4648
+ });
4649
+ current = null;
4650
+ };
4651
+ for (const line of output.split("\n")) {
4652
+ if (line.startsWith("__COMMIT__ ")) {
4653
+ pushCurrent();
4654
+ const [_, hash, tsRaw, ...titleParts] = line.split(" ");
4655
+ const committedAt = Number(tsRaw) * 1e3;
4656
+ current = {
4657
+ hash: hash ?? "",
4658
+ title: titleParts.join(" ").trim() || (hash ? hash.slice(0, 8) : "commit"),
4659
+ committedAt: Number.isFinite(committedAt) && committedAt > 0 ? committedAt : 0,
4660
+ numstatLines: [],
4661
+ changedPaths: []
4662
+ };
4663
+ continue;
4664
+ }
4665
+ if (!current) continue;
4666
+ const trimmed = line.trim();
4667
+ if (!trimmed) continue;
4668
+ const [addRaw, delRaw, ...pathParts] = trimmed.split(" ");
4669
+ if (!addRaw || !delRaw || pathParts.length === 0) continue;
4670
+ const path$1 = pathParts.join(" ");
4671
+ current.numstatLines.push(`${addRaw}\t${delRaw}\t${path$1}`);
4672
+ current.changedPaths.push(path$1);
4673
+ }
4674
+ pushCurrent();
4675
+ return commits.filter((commit) => commit.hash.length > 0);
4676
+ }
4677
+ function normalizeRepositoryUrl(raw) {
4678
+ const value = raw.trim();
4679
+ if (!value) return null;
4680
+ if (value.startsWith("http://") || value.startsWith("https://")) return value.replace(/\.git$/i, "");
4681
+ const gitAtMatch = /^git@([^:]+):(.+)$/.exec(value);
4682
+ if (gitAtMatch?.[1] && gitAtMatch[2]) return `https://${gitAtMatch[1]}/${gitAtMatch[2].replace(/\.git$/i, "")}`;
4683
+ if (value.startsWith("ssh://")) try {
4684
+ const parsed = new URL(value);
4685
+ const pathname = parsed.pathname.replace(/^\/+/, "").replace(/\.git$/i, "");
4686
+ if (!pathname) return null;
4687
+ return `https://${parsed.hostname}/${pathname}`;
4688
+ } catch {}
4689
+ return value.replace(/\.git$/i, "");
4690
+ }
4691
+ async function readSnapshotGit(projectDir) {
4692
+ try {
4693
+ const defaultBranch = await readDefaultBranch(projectDir);
4694
+ const { stdout: latestTsRaw } = await execFileAsync("git", [
4695
+ "log",
4696
+ "-1",
4697
+ "--format=%ct"
4698
+ ], {
4699
+ cwd: projectDir,
4700
+ encoding: "utf8",
4701
+ maxBuffer: 1024 * 1024
4702
+ });
4703
+ const latestSeconds = Number(latestTsRaw.trim());
4704
+ const latestCommitTs = Number.isFinite(latestSeconds) && latestSeconds > 0 ? latestSeconds * 1e3 : null;
4705
+ let repositoryUrl = null;
4706
+ try {
4707
+ const { stdout: remoteRaw } = await execFileAsync("git", [
4708
+ "config",
4709
+ "--get",
4710
+ "remote.origin.url"
4711
+ ], {
4712
+ cwd: projectDir,
4713
+ encoding: "utf8",
4714
+ maxBuffer: 1024 * 1024
4715
+ });
4716
+ repositoryUrl = normalizeRepositoryUrl(remoteRaw);
4717
+ } catch {
4718
+ repositoryUrl = null;
4719
+ }
4720
+ const { stdout: logOutput } = await execFileAsync("git", [
4721
+ "log",
4722
+ "-n",
4723
+ "5",
4724
+ "--format=__COMMIT__%x09%H%x09%ct%x09%s",
4725
+ "--numstat",
4726
+ "--"
4727
+ ], {
4728
+ cwd: projectDir,
4729
+ encoding: "utf8",
4730
+ maxBuffer: 8 * 1024 * 1024
4731
+ });
4732
+ return {
4733
+ defaultBranch,
4734
+ repositoryUrl,
4735
+ latestCommitTs,
4736
+ recentCommits: parseRecentCommitLog(logOutput)
4737
+ };
4738
+ } catch {
4739
+ return;
4740
+ }
4741
+ }
4562
4742
  /**
4563
4743
  * Generate a complete data snapshot of the OpenSpec project
4564
4744
  * (Kept for backwards compatibility and testing)
@@ -4641,8 +4821,10 @@ async function generateSnapshot(projectDir) {
4641
4821
  let configYaml;
4642
4822
  let schemas = [];
4643
4823
  const schemaDetails = {};
4824
+ const schemaYamls = {};
4644
4825
  const schemaResolutions = {};
4645
4826
  const templates = {};
4827
+ const templateContents = {};
4646
4828
  const changeMetadata = {};
4647
4829
  try {
4648
4830
  configYaml = await readFile(join$1(projectDir, "openspec", "config.yaml"), "utf-8");
@@ -4660,16 +4842,56 @@ async function generateSnapshot(projectDir) {
4660
4842
  const resolutionResult = await cliExecutor.schemaWhich(schema.name);
4661
4843
  if (resolutionResult.success) {
4662
4844
  const resolution = parseCliJson(resolutionResult.stdout, SchemaResolutionSchema, "openspec schema which");
4663
- schemaResolutions[schema.name] = resolution;
4845
+ schemaResolutions[schema.name] = {
4846
+ ...resolution,
4847
+ displayPath: toOpsxDisplayPath(resolution.path, {
4848
+ source: resolution.source,
4849
+ projectDir
4850
+ }),
4851
+ shadows: resolution.shadows.map((shadow) => ({
4852
+ ...shadow,
4853
+ displayPath: toOpsxDisplayPath(shadow.path, {
4854
+ source: shadow.source,
4855
+ projectDir
4856
+ })
4857
+ }))
4858
+ };
4664
4859
  try {
4665
4860
  const schemaContent = await readFile(join$1(resolution.path, "schema.yaml"), "utf-8");
4666
4861
  schemaDetails[schema.name] = parseSchemaYaml(schemaContent);
4862
+ schemaYamls[schema.name] = schemaContent;
4667
4863
  } catch {}
4668
4864
  }
4669
4865
  } catch {}
4670
4866
  try {
4671
4867
  const templatesResult = await cliExecutor.templates(schema.name);
4672
- if (templatesResult.success) templates[schema.name] = parseCliJson(templatesResult.stdout, TemplatesSchema, "openspec templates");
4868
+ if (templatesResult.success) {
4869
+ const parsedTemplates = parseCliJson(templatesResult.stdout, TemplatesSchema, "openspec templates");
4870
+ const normalizedTemplates = Object.fromEntries(Object.entries(parsedTemplates).map(([artifactId, info]) => [artifactId, {
4871
+ ...info,
4872
+ path: toAbsoluteProjectPath(projectDir, info.path),
4873
+ displayPath: toOpsxDisplayPath(info.path, {
4874
+ source: info.source,
4875
+ projectDir
4876
+ })
4877
+ }]));
4878
+ templates[schema.name] = normalizedTemplates;
4879
+ const contents = await Promise.all(Object.entries(normalizedTemplates).map(async ([artifactId, info]) => {
4880
+ let content = null;
4881
+ try {
4882
+ content = await readFile(info.path, "utf-8");
4883
+ } catch {
4884
+ content = null;
4885
+ }
4886
+ return [artifactId, {
4887
+ content,
4888
+ path: info.path,
4889
+ displayPath: info.displayPath,
4890
+ source: info.source
4891
+ }];
4892
+ }));
4893
+ templateContents[schema.name] = Object.fromEntries(contents);
4894
+ }
4673
4895
  } catch {}
4674
4896
  }
4675
4897
  try {
@@ -4680,6 +4902,7 @@ async function generateSnapshot(projectDir) {
4680
4902
  changeMetadata[changeId] = null;
4681
4903
  }
4682
4904
  } catch {}
4905
+ const git = await readSnapshotGit(projectDir);
4683
4906
  return {
4684
4907
  meta: {
4685
4908
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
@@ -4691,6 +4914,7 @@ async function generateSnapshot(projectDir) {
4691
4914
  changesCount: changes.filter((c) => c !== null).length,
4692
4915
  archivesCount: archives.length
4693
4916
  },
4917
+ git,
4694
4918
  config: uiConfig,
4695
4919
  specs,
4696
4920
  changes: changes.filter((c) => c !== null),
@@ -4701,8 +4925,10 @@ async function generateSnapshot(projectDir) {
4701
4925
  configYaml,
4702
4926
  schemas,
4703
4927
  schemaDetails,
4928
+ schemaYamls,
4704
4929
  schemaResolutions,
4705
4930
  templates,
4931
+ templateContents,
4706
4932
  changeMetadata
4707
4933
  }
4708
4934
  };
@@ -4723,7 +4949,7 @@ function runCommand(cmd, args, cwd) {
4723
4949
  const child = spawn(cmd, args, {
4724
4950
  stdio: "inherit",
4725
4951
  cwd,
4726
- shell: true
4952
+ shell: false
4727
4953
  });
4728
4954
  child.on("close", (code) => {
4729
4955
  if (code === 0) resolvePromise();
@@ -4757,7 +4983,7 @@ function detectPackageManager() {
4757
4983
  return "npm";
4758
4984
  }
4759
4985
  /**
4760
- * Get the command to run a local binary (like vite)
4986
+ * Get the command to run a binary in a package-manager agnostic way.
4761
4987
  */
4762
4988
  function getRunCommand(pm, bin) {
4763
4989
  switch (pm) {
@@ -4767,11 +4993,11 @@ function getRunCommand(pm, bin) {
4767
4993
  };
4768
4994
  case "pnpm": return {
4769
4995
  cmd: "pnpm",
4770
- args: ["exec", bin]
4996
+ args: ["dlx", bin]
4771
4997
  };
4772
4998
  case "yarn": return {
4773
4999
  cmd: "yarn",
4774
- args: [bin]
5000
+ args: ["dlx", bin]
4775
5001
  };
4776
5002
  case "deno": return {
4777
5003
  cmd: "deno",
@@ -5043,6 +5269,10 @@ async function main() {
5043
5269
  }).option("preview-port", {
5044
5270
  describe: "Port for the preview server (used with --open)",
5045
5271
  type: "number"
5272
+ }).option("port", {
5273
+ alias: "p",
5274
+ describe: "Alias of --open --preview-port <port>",
5275
+ type: "number"
5046
5276
  }).option("preview-host", {
5047
5277
  describe: "Host for the preview server (used with --open)",
5048
5278
  type: "string"
@@ -5050,6 +5280,8 @@ async function main() {
5050
5280
  }, async (argv) => {
5051
5281
  const projectDir = resolve$1(originalCwd, argv.dir || ".");
5052
5282
  const outputDir = resolve$1(originalCwd, argv.output);
5283
+ const previewPort = argv.port ?? argv["preview-port"];
5284
+ const shouldOpen = argv.open || argv.port !== void 0;
5053
5285
  try {
5054
5286
  await exportStaticSite({
5055
5287
  projectDir,
@@ -5057,16 +5289,16 @@ async function main() {
5057
5289
  format: argv.format,
5058
5290
  basePath: argv["base-path"],
5059
5291
  clean: argv.clean,
5060
- open: argv.open,
5061
- previewPort: argv["preview-port"],
5292
+ open: shouldOpen,
5293
+ previewPort,
5062
5294
  previewHost: argv["preview-host"]
5063
5295
  });
5064
- if (!argv.open) process.exit(0);
5296
+ if (!shouldOpen) process.exit(0);
5065
5297
  } catch (error) {
5066
5298
  console.error("❌ Export failed:", error);
5067
5299
  process.exit(1);
5068
5300
  }
5069
- }).example("$0", "Start server in current directory").example("$0 ./my-project", "Start server with specific project").example("$0 -p 8080", "Start server on custom port").example("$0 export -o ./dist", "Export HTML to ./dist directory").example("$0 export -o ./dist -f json", "Export JSON data only").example("$0 export -o ./dist --base-path=/docs/", "Export for subdirectory deployment").example("$0 export -o ./dist --clean", "Clean output directory before export").version(getVersion()).alias("v", "version").help().alias("h", "help").parse();
5301
+ }).example("$0", "Start server in current directory").example("$0 ./my-project", "Start server with specific project").example("$0 -p 8080", "Start server on custom port").example("$0 export -o ./dist", "Export HTML to ./dist directory").example("$0 export -o ./dist -f json", "Export JSON data only").example("$0 export -o ./dist -p 8092", "Export and open preview on port 8092").example("$0 export -o ./dist --base-path=/docs/", "Export for subdirectory deployment").example("$0 export -o ./dist --clean", "Clean output directory before export").version(getVersion()).alias("v", "version").help().alias("h", "help").parse();
5070
5302
  }
5071
5303
  main();
5072
5304
 
package/dist/index.mjs CHANGED
@@ -1,3 +1,3 @@
1
- import { n as createServer, t as startServer } from "./src-16GA3our.mjs";
1
+ import { n as createServer, t as startServer } from "./src-Brh3druE.mjs";
2
2
 
3
3
  export { createServer, startServer };
@@ -7094,6 +7094,68 @@ const DASHBOARD_METRIC_KEYS = [
7094
7094
  "taskCompletionPercent"
7095
7095
  ];
7096
7096
 
7097
+ //#endregion
7098
+ //#region ../core/src/opsx-display-path.ts
7099
+ const VIRTUAL_PROJECT_DIRNAME = "project";
7100
+ const WINDOWS_DRIVE_PREFIX = /^[A-Za-z]:\//;
7101
+ function normalizeFsPath(path$1) {
7102
+ return path$1.replace(/\\/g, "/").replace(/\/+$/g, "");
7103
+ }
7104
+ function isAbsolutePath(path$1) {
7105
+ return path$1.startsWith("/") || WINDOWS_DRIVE_PREFIX.test(path$1);
7106
+ }
7107
+ function stripRelativePrefix(path$1) {
7108
+ return path$1.replace(/^\.?\//, "");
7109
+ }
7110
+ function splitSegments(path$1) {
7111
+ return normalizeFsPath(path$1).split("/").filter(Boolean);
7112
+ }
7113
+ function toNpmSpecifier(path$1) {
7114
+ const normalized = normalizeFsPath(path$1);
7115
+ const match$1 = /(?:^|\/)node_modules\/(?:\.pnpm\/[^/]+\/node_modules\/)?(@[^/]+\/[^/]+|[^/]+)(\/.*)?/.exec(normalized);
7116
+ const pkgName = match$1?.[1];
7117
+ if (!pkgName) return null;
7118
+ return `npm:${pkgName}${match$1[2] ?? ""}`;
7119
+ }
7120
+ function toVirtualProjectPath(path$1) {
7121
+ return `${VIRTUAL_PROJECT_DIRNAME}:${stripRelativePrefix(path$1)}`;
7122
+ }
7123
+ function isPathInside(root, target) {
7124
+ const normalizedRoot = normalizeFsPath(root);
7125
+ const normalizedTarget = normalizeFsPath(target);
7126
+ const rootLower = normalizedRoot.toLowerCase();
7127
+ const targetLower = normalizedTarget.toLowerCase();
7128
+ return targetLower === rootLower || targetLower.startsWith(`${rootLower}/`);
7129
+ }
7130
+ function toRelativeFromRoot(root, target) {
7131
+ const rootSegments = splitSegments(root);
7132
+ const targetSegments = splitSegments(target);
7133
+ let index = 0;
7134
+ while (index < rootSegments.length && index < targetSegments.length) {
7135
+ if (rootSegments[index]?.toLowerCase() !== targetSegments[index]?.toLowerCase()) break;
7136
+ index += 1;
7137
+ }
7138
+ return targetSegments.slice(index).join("/");
7139
+ }
7140
+ function findOpspecSlice(path$1) {
7141
+ const segments = splitSegments(path$1);
7142
+ const idx = segments.lastIndexOf("openspec");
7143
+ if (idx < 0) return null;
7144
+ return segments.slice(idx).join("/");
7145
+ }
7146
+ function toOpsxDisplayPath(absoluteOrRelativePath, options) {
7147
+ const normalized = normalizeFsPath(absoluteOrRelativePath);
7148
+ const npmSpecifier = toNpmSpecifier(normalized);
7149
+ if (options?.source === "package" || npmSpecifier) {
7150
+ if (npmSpecifier) return npmSpecifier;
7151
+ }
7152
+ if (!isAbsolutePath(normalized)) return toVirtualProjectPath(normalized);
7153
+ if (options?.projectDir && isPathInside(options.projectDir, normalized)) return toVirtualProjectPath(toRelativeFromRoot(options.projectDir, normalized));
7154
+ const openspecSlice = findOpspecSlice(normalized);
7155
+ if (openspecSlice) return toVirtualProjectPath(openspecSlice);
7156
+ return toVirtualProjectPath(splitSegments(normalized).slice(-4).join("/"));
7157
+ }
7158
+
7097
7159
  //#endregion
7098
7160
  //#region ../core/src/opsx-types.ts
7099
7161
  const ArtifactStatusSchema = objectType({
@@ -7177,17 +7239,20 @@ const SchemaResolutionSchema = objectType({
7177
7239
  "package"
7178
7240
  ]),
7179
7241
  path: stringType(),
7242
+ displayPath: stringType().optional(),
7180
7243
  shadows: arrayType(objectType({
7181
7244
  source: enumType([
7182
7245
  "project",
7183
7246
  "user",
7184
7247
  "package"
7185
7248
  ]),
7186
- path: stringType()
7249
+ path: stringType(),
7250
+ displayPath: stringType().optional()
7187
7251
  }))
7188
7252
  });
7189
7253
  const TemplatesSchema = recordType(objectType({
7190
7254
  path: stringType(),
7255
+ displayPath: stringType().optional(),
7191
7256
  source: enumType([
7192
7257
  "project",
7193
7258
  "user",
@@ -13884,6 +13949,12 @@ function parseCliJson(raw$1, schema$6, label) {
13884
13949
  function toRelativePath(root, absolutePath) {
13885
13950
  return relative$1(root, absolutePath).split(sep).join("/");
13886
13951
  }
13952
+ function isAbsoluteFsPath(path$1) {
13953
+ return path$1.startsWith("/") || /^[A-Za-z]:\//.test(path$1);
13954
+ }
13955
+ function toAbsoluteProjectPath(projectDir, path$1) {
13956
+ return isAbsoluteFsPath(path$1.replace(/\\/g, "/")) ? path$1 : resolve$1(projectDir, path$1);
13957
+ }
13887
13958
  async function readEntriesUnderRoot(root) {
13888
13959
  if (!(await reactiveStat(root))?.isDirectory) return [];
13889
13960
  const collectEntries = async (dir) => {
@@ -14322,7 +14393,21 @@ var OpsxKernel = class {
14322
14393
  await touchOpsxProjectDeps(this.projectDir);
14323
14394
  const result = await this.cliExecutor.schemaWhich(name);
14324
14395
  if (!result.success) throw new Error(result.stderr || `openspec schema which failed (exit ${result.exitCode ?? "null"})`);
14325
- return parseCliJson(result.stdout, SchemaResolutionSchema, "openspec schema which");
14396
+ const parsed = parseCliJson(result.stdout, SchemaResolutionSchema, "openspec schema which");
14397
+ return {
14398
+ ...parsed,
14399
+ displayPath: toOpsxDisplayPath(parsed.path, {
14400
+ source: parsed.source,
14401
+ projectDir: this.projectDir
14402
+ }),
14403
+ shadows: parsed.shadows.map((shadow) => ({
14404
+ ...shadow,
14405
+ displayPath: toOpsxDisplayPath(shadow.path, {
14406
+ source: shadow.source,
14407
+ projectDir: this.projectDir
14408
+ })
14409
+ }))
14410
+ };
14326
14411
  }
14327
14412
  async fetchSchemaDetail(name) {
14328
14413
  await touchOpsxProjectDeps(this.projectDir);
@@ -14346,7 +14431,15 @@ var OpsxKernel = class {
14346
14431
  await touchOpsxProjectDeps(this.projectDir);
14347
14432
  const result = await this.cliExecutor.templates(schema$6);
14348
14433
  if (!result.success) throw new Error(result.stderr || `openspec templates failed (exit ${result.exitCode ?? "null"})`);
14349
- return parseCliJson(result.stdout, TemplatesSchema, "openspec templates");
14434
+ const templates = parseCliJson(result.stdout, TemplatesSchema, "openspec templates");
14435
+ return Object.fromEntries(Object.entries(templates).map(([artifactId, info]) => [artifactId, {
14436
+ ...info,
14437
+ path: toAbsoluteProjectPath(this.projectDir, info.path),
14438
+ displayPath: toOpsxDisplayPath(info.path, {
14439
+ source: info.source,
14440
+ projectDir: this.projectDir
14441
+ })
14442
+ }]));
14350
14443
  }
14351
14444
  async fetchTemplateContents(schema$6) {
14352
14445
  await this.ensureTemplates(schema$6);
@@ -14355,6 +14448,10 @@ var OpsxKernel = class {
14355
14448
  return [artifactId, {
14356
14449
  content: await reactiveReadFile(info.path),
14357
14450
  path: info.path,
14451
+ displayPath: info.displayPath ?? toOpsxDisplayPath(info.path, {
14452
+ source: info.source,
14453
+ projectDir: this.projectDir
14454
+ }),
14358
14455
  source: info.source
14359
14456
  }];
14360
14457
  }));
@@ -25370,4 +25467,4 @@ async function startServer$1(options = {}) {
25370
25467
  }
25371
25468
 
25372
25469
  //#endregion
25373
- export { SchemaInfoSchema as a, CliExecutor as c, OpenSpecAdapter as d, __commonJS$1 as f, SchemaDetailSchema as i, ConfigManager as l, createServer$2 as n, SchemaResolutionSchema as o, __toESM$1 as p, require_dist as r, TemplatesSchema as s, startServer$1 as t, DEFAULT_CONFIG as u };
25470
+ export { SchemaInfoSchema as a, toOpsxDisplayPath as c, DEFAULT_CONFIG as d, OpenSpecAdapter as f, SchemaDetailSchema as i, CliExecutor as l, __toESM$1 as m, createServer$2 as n, SchemaResolutionSchema as o, __commonJS$1 as p, require_dist as r, TemplatesSchema as s, startServer$1 as t, ConfigManager as u };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openspecui",
3
- "version": "1.5.1",
3
+ "version": "1.6.0",
4
4
  "description": "OpenSpec UI - Visual interface for spec-driven development",
5
5
  "type": "module",
6
6
  "main": "dist/index.mjs",
@@ -33,8 +33,8 @@
33
33
  "typescript": "^5.7.2",
34
34
  "vitest": "^2.1.8",
35
35
  "yargs": "^18.0.0",
36
- "@openspecui/web": "1.5.0",
37
- "@openspecui/server": "1.5.0"
36
+ "@openspecui/server": "1.6.0",
37
+ "@openspecui/web": "1.6.0"
38
38
  },
39
39
  "scripts": {
40
40
  "build": "pnpm run build:web && pnpm run build:copy-web && tsdown",
@@ -1,4 +1,4 @@
1
- import{y as U,z as g,A as c,B as S,D as _,F as m,H as I,J as p}from"./index-T8xoxmUb.js";const x={name:"local-uniform-bit",vertex:{header:`
1
+ import{y as U,z as g,A as c,B as S,D as _,F as m,H as I,J as p}from"./index-CKfGQiMr.js";const x={name:"local-uniform-bit",vertex:{header:`
2
2
 
3
3
  struct LocalUniforms {
4
4
  uTransformMatrix:mat3x3<f32>,