openspecui 1.5.1 → 1.6.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 (33) hide show
  1. package/dist/cli.mjs +251 -20
  2. package/dist/index.mjs +1 -1
  3. package/dist/{src-16GA3our.mjs → src-CVJCbdiP.mjs} +166 -24
  4. package/package.json +3 -3
  5. package/web/assets/{BufferResource-Bn1UWy0D.js → BufferResource-BFEntdoN.js} +1 -1
  6. package/web/assets/{CanvasRenderer-D8NiU8la.js → CanvasRenderer-ZQxN8ITJ.js} +1 -1
  7. package/web/assets/{Filter-CRwq487x.js → Filter-trCu1CQG.js} +1 -1
  8. package/web/assets/{RenderTargetSystem-CtoB_qTm.js → RenderTargetSystem-DHoDBu3v.js} +1 -1
  9. package/web/assets/{WebGLRenderer-BgKO8R0a.js → WebGLRenderer-DYS9LSzA.js} +1 -1
  10. package/web/assets/{WebGPURenderer-CQeL2efC.js → WebGPURenderer-CDj9ZzDs.js} +1 -1
  11. package/web/assets/{browserAll-DP6sOYev.js → browserAll-B8FBs1Rt.js} +1 -1
  12. package/web/assets/{ghostty-web-evxujSxm.js → ghostty-web-C6hNf21K.js} +1 -1
  13. package/web/assets/{index-B147AOgf.js → index-BByfw4ws.js} +1 -1
  14. package/web/assets/{index-D2Tp4F9B.js → index-BDu-q_RJ.js} +1 -1
  15. package/web/assets/{index-B0IbsqHi.js → index-BRrJSB9v.js} +1 -1
  16. package/web/assets/{index-CBCPR3Qb.js → index-CFiXnV5n.js} +1 -1
  17. package/web/assets/{index-D6ardy54.js → index-CLKNqdA1.js} +1 -1
  18. package/web/assets/{index-BPZ3nG0r.js → index-Cja7GFdu.js} +1 -1
  19. package/web/assets/{index-DcXyAs0z.js → index-Cq8vn29r.js} +1 -1
  20. package/web/assets/{index-f0QdJSzm.js → index-CqDTsKqD.js} +1 -1
  21. package/web/assets/{index-DTeOcXKn.js → index-CxCFae0J.js} +1 -1
  22. package/web/assets/{index-dSf1u0YV.js → index-CxF-jUR9.js} +1 -1
  23. package/web/assets/{index-BMashGQn.js → index-Dj2t_cJN.js} +1 -1
  24. package/web/assets/{index-BnT52DZ8.js → index-Drwn-PWd.js} +1 -1
  25. package/web/assets/index-DzNp3fwv.css +1 -0
  26. package/web/assets/{index-BejnsZfY.js → index-FMaDFama.js} +1 -1
  27. package/web/assets/index-YEDqrGu6.js +1540 -0
  28. package/web/assets/{index-DJqmTRAR.js → index-hID85NKg.js} +1 -1
  29. package/web/assets/{index-4MAU81Qk.js → index-vJWwTEOs.js} +1 -1
  30. package/web/assets/{webworkerAll-DA2HufNb.js → webworkerAll-Pgv9S8Bp.js} +1 -1
  31. package/web/index.html +2 -2
  32. package/web/assets/index-T8xoxmUb.js +0 -1516
  33. 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-CVJCbdiP.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.2";
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)
@@ -4586,12 +4766,11 @@ async function generateSnapshot(projectDir) {
4586
4766
  const changesMeta = await adapter.listChangesWithMeta();
4587
4767
  const changes = await Promise.all(changesMeta.map(async (meta) => {
4588
4768
  const change = await adapter.readChange(meta.id);
4589
- if (!change) return null;
4590
4769
  const files = await adapter.readChangeFiles(meta.id);
4591
4770
  const proposalFile = files.find((f) => f.path === "proposal.md");
4592
4771
  const tasksFile = files.find((f) => f.path === "tasks.md");
4593
4772
  const designFile = files.find((f) => f.path === "design.md");
4594
- const deltas = (change.deltaSpecs || []).map((ds) => ({
4773
+ const deltas = (change?.deltaSpecs || []).map((ds) => ({
4595
4774
  capability: ds.specId,
4596
4775
  content: ds.content || ""
4597
4776
  }));
@@ -4601,11 +4780,11 @@ async function generateSnapshot(projectDir) {
4601
4780
  proposal: proposalFile?.content || "",
4602
4781
  tasks: tasksFile?.content,
4603
4782
  design: designFile?.content,
4604
- why: change.why || "",
4605
- whatChanges: change.whatChanges || "",
4606
- parsedTasks: change.tasks || [],
4783
+ why: change?.why || "",
4784
+ whatChanges: change?.whatChanges || "",
4785
+ parsedTasks: change?.tasks || [],
4607
4786
  deltas,
4608
- progress: meta.progress,
4787
+ progress: change?.progress ?? meta.progress,
4609
4788
  createdAt: meta.createdAt,
4610
4789
  updatedAt: meta.updatedAt
4611
4790
  };
@@ -4641,8 +4820,10 @@ async function generateSnapshot(projectDir) {
4641
4820
  let configYaml;
4642
4821
  let schemas = [];
4643
4822
  const schemaDetails = {};
4823
+ const schemaYamls = {};
4644
4824
  const schemaResolutions = {};
4645
4825
  const templates = {};
4826
+ const templateContents = {};
4646
4827
  const changeMetadata = {};
4647
4828
  try {
4648
4829
  configYaml = await readFile(join$1(projectDir, "openspec", "config.yaml"), "utf-8");
@@ -4660,16 +4841,56 @@ async function generateSnapshot(projectDir) {
4660
4841
  const resolutionResult = await cliExecutor.schemaWhich(schema.name);
4661
4842
  if (resolutionResult.success) {
4662
4843
  const resolution = parseCliJson(resolutionResult.stdout, SchemaResolutionSchema, "openspec schema which");
4663
- schemaResolutions[schema.name] = resolution;
4844
+ schemaResolutions[schema.name] = {
4845
+ ...resolution,
4846
+ displayPath: toOpsxDisplayPath(resolution.path, {
4847
+ source: resolution.source,
4848
+ projectDir
4849
+ }),
4850
+ shadows: resolution.shadows.map((shadow) => ({
4851
+ ...shadow,
4852
+ displayPath: toOpsxDisplayPath(shadow.path, {
4853
+ source: shadow.source,
4854
+ projectDir
4855
+ })
4856
+ }))
4857
+ };
4664
4858
  try {
4665
4859
  const schemaContent = await readFile(join$1(resolution.path, "schema.yaml"), "utf-8");
4666
4860
  schemaDetails[schema.name] = parseSchemaYaml(schemaContent);
4861
+ schemaYamls[schema.name] = schemaContent;
4667
4862
  } catch {}
4668
4863
  }
4669
4864
  } catch {}
4670
4865
  try {
4671
4866
  const templatesResult = await cliExecutor.templates(schema.name);
4672
- if (templatesResult.success) templates[schema.name] = parseCliJson(templatesResult.stdout, TemplatesSchema, "openspec templates");
4867
+ if (templatesResult.success) {
4868
+ const parsedTemplates = parseCliJson(templatesResult.stdout, TemplatesSchema, "openspec templates");
4869
+ const normalizedTemplates = Object.fromEntries(Object.entries(parsedTemplates).map(([artifactId, info]) => [artifactId, {
4870
+ ...info,
4871
+ path: toAbsoluteProjectPath(projectDir, info.path),
4872
+ displayPath: toOpsxDisplayPath(info.path, {
4873
+ source: info.source,
4874
+ projectDir
4875
+ })
4876
+ }]));
4877
+ templates[schema.name] = normalizedTemplates;
4878
+ const contents = await Promise.all(Object.entries(normalizedTemplates).map(async ([artifactId, info]) => {
4879
+ let content = null;
4880
+ try {
4881
+ content = await readFile(info.path, "utf-8");
4882
+ } catch {
4883
+ content = null;
4884
+ }
4885
+ return [artifactId, {
4886
+ content,
4887
+ path: info.path,
4888
+ displayPath: info.displayPath,
4889
+ source: info.source
4890
+ }];
4891
+ }));
4892
+ templateContents[schema.name] = Object.fromEntries(contents);
4893
+ }
4673
4894
  } catch {}
4674
4895
  }
4675
4896
  try {
@@ -4680,6 +4901,7 @@ async function generateSnapshot(projectDir) {
4680
4901
  changeMetadata[changeId] = null;
4681
4902
  }
4682
4903
  } catch {}
4904
+ const git = await readSnapshotGit(projectDir);
4683
4905
  return {
4684
4906
  meta: {
4685
4907
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
@@ -4691,9 +4913,10 @@ async function generateSnapshot(projectDir) {
4691
4913
  changesCount: changes.filter((c) => c !== null).length,
4692
4914
  archivesCount: archives.length
4693
4915
  },
4916
+ git,
4694
4917
  config: uiConfig,
4695
4918
  specs,
4696
- changes: changes.filter((c) => c !== null),
4919
+ changes,
4697
4920
  archives,
4698
4921
  projectMd,
4699
4922
  agentsMd,
@@ -4701,8 +4924,10 @@ async function generateSnapshot(projectDir) {
4701
4924
  configYaml,
4702
4925
  schemas,
4703
4926
  schemaDetails,
4927
+ schemaYamls,
4704
4928
  schemaResolutions,
4705
4929
  templates,
4930
+ templateContents,
4706
4931
  changeMetadata
4707
4932
  }
4708
4933
  };
@@ -4723,7 +4948,7 @@ function runCommand(cmd, args, cwd) {
4723
4948
  const child = spawn(cmd, args, {
4724
4949
  stdio: "inherit",
4725
4950
  cwd,
4726
- shell: true
4951
+ shell: false
4727
4952
  });
4728
4953
  child.on("close", (code) => {
4729
4954
  if (code === 0) resolvePromise();
@@ -4757,7 +4982,7 @@ function detectPackageManager() {
4757
4982
  return "npm";
4758
4983
  }
4759
4984
  /**
4760
- * Get the command to run a local binary (like vite)
4985
+ * Get the command to run a binary in a package-manager agnostic way.
4761
4986
  */
4762
4987
  function getRunCommand(pm, bin) {
4763
4988
  switch (pm) {
@@ -4767,11 +4992,11 @@ function getRunCommand(pm, bin) {
4767
4992
  };
4768
4993
  case "pnpm": return {
4769
4994
  cmd: "pnpm",
4770
- args: ["exec", bin]
4995
+ args: ["dlx", bin]
4771
4996
  };
4772
4997
  case "yarn": return {
4773
4998
  cmd: "yarn",
4774
- args: [bin]
4999
+ args: ["dlx", bin]
4775
5000
  };
4776
5001
  case "deno": return {
4777
5002
  cmd: "deno",
@@ -5043,6 +5268,10 @@ async function main() {
5043
5268
  }).option("preview-port", {
5044
5269
  describe: "Port for the preview server (used with --open)",
5045
5270
  type: "number"
5271
+ }).option("port", {
5272
+ alias: "p",
5273
+ describe: "Alias of --open --preview-port <port>",
5274
+ type: "number"
5046
5275
  }).option("preview-host", {
5047
5276
  describe: "Host for the preview server (used with --open)",
5048
5277
  type: "string"
@@ -5050,6 +5279,8 @@ async function main() {
5050
5279
  }, async (argv) => {
5051
5280
  const projectDir = resolve$1(originalCwd, argv.dir || ".");
5052
5281
  const outputDir = resolve$1(originalCwd, argv.output);
5282
+ const previewPort = argv.port ?? argv["preview-port"];
5283
+ const shouldOpen = argv.open || argv.port !== void 0;
5053
5284
  try {
5054
5285
  await exportStaticSite({
5055
5286
  projectDir,
@@ -5057,16 +5288,16 @@ async function main() {
5057
5288
  format: argv.format,
5058
5289
  basePath: argv["base-path"],
5059
5290
  clean: argv.clean,
5060
- open: argv.open,
5061
- previewPort: argv["preview-port"],
5291
+ open: shouldOpen,
5292
+ previewPort,
5062
5293
  previewHost: argv["preview-host"]
5063
5294
  });
5064
- if (!argv.open) process.exit(0);
5295
+ if (!shouldOpen) process.exit(0);
5065
5296
  } catch (error) {
5066
5297
  console.error("❌ Export failed:", error);
5067
5298
  process.exit(1);
5068
5299
  }
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();
5300
+ }).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
5301
  }
5071
5302
  main();
5072
5303
 
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-CVJCbdiP.mjs";
2
2
 
3
3
  export { createServer, startServer };