@peterxiaoyang/superspec 0.1.27 → 0.1.29

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 (3) hide show
  1. package/README.md +2 -1
  2. package/dist/cli.js +146 -7
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -81,6 +81,7 @@ superspec install
81
81
  ```
82
82
 
83
83
  这条命令的意思是:把 SuperSpec 当前可用的工作流入口安装到项目里。
84
+ 安装前会检查全局 `openspec` CLI;缺失或版本不一致时,会自动执行 `npm install -g @fission-ai/openspec@1.4.1`,确保后续工作流能调用 OpenSpec。
84
85
  当前 beta 会安装 `.superspec/` 引擎目录、`.codex/skills/superspec-*` 阶段入口、`.codex/prompts/*.md` 角色 prompt、`.codex/agents/*.toml` 子智能体配置,补齐 `.codex/config.toml` 的多 agent 开关,并在项目根 `AGENTS.md` 中维护 SuperSpec 轻量门禁片段。`superspec init --scope project` 仍作为兼容别名可用。
85
86
 
86
87
  Windows PowerShell 如果拦截 npm 的 `.ps1` 脚本,请改用:
@@ -246,7 +247,7 @@ superspec status
246
247
  superspec update
247
248
  ```
248
249
 
249
- 这条命令会先检查 npm 上的 latest 版本;如果有新版,会自动执行全局升级并用新版 CLI 重新同步项目入口。同步内容包括补齐 `.superspec/changes` 运行时目录,把当前 CLI 内置的 `.codex/skills/superspec-*`、`.codex/prompts/*.md`、`.codex/agents/*.toml` 同步到项目里,并更新 `AGENTS.md` 中 marker 包裹的 SuperSpec 轻量门禁片段。
250
+ 这条命令会先检查 npm 上的 latest 版本;如果有新版,会自动执行全局升级并用新版 CLI 重新同步项目入口。随后会把全局 OpenSpec CLI 拉齐到 `@fission-ai/openspec@1.4.1`。同步内容包括补齐 `.superspec/changes` 运行时目录,把当前 CLI 内置的 `.codex/skills/superspec-*`、`.codex/prompts/*.md`、`.codex/agents/*.toml` 同步到项目里,并更新 `AGENTS.md` 中 marker 包裹的 SuperSpec 轻量门禁片段。
250
251
 
251
252
  ## 进阶信息
252
253
 
package/dist/cli.js CHANGED
@@ -13,6 +13,8 @@ import { recordTestRun, recordTestRunContent } from "./task.js";
13
13
  import { probeOpenSpec, openspecStatus, changeRoot } from "./openspec.js";
14
14
  import { SUPERSPEC_VERSION } from "./version.js";
15
15
  const PACKAGE_NAME = "@peterxiaoyang/superspec";
16
+ const OPENSPEC_PACKAGE_NAME = "@fission-ai/openspec";
17
+ const OPENSPEC_REQUIRED_VERSION = "1.4.1";
16
18
  // ===== 参数解析 =====
17
19
  function parseArgs(argv) {
18
20
  const [command, subcommand, ...rest] = argv;
@@ -110,6 +112,16 @@ class SelfUpdateError extends Error {
110
112
  this.latest = latest;
111
113
  }
112
114
  }
115
+ class OpenSpecDependencyError extends Error {
116
+ phase;
117
+ before;
118
+ constructor(phase, message, before = null) {
119
+ super(message);
120
+ this.name = "OpenSpecDependencyError";
121
+ this.phase = phase;
122
+ this.before = before;
123
+ }
124
+ }
113
125
  function isTestMode() {
114
126
  return process.env.SUPERSPEC_TEST_MODE === "1" || process.env.NODE_ENV === "test";
115
127
  }
@@ -122,12 +134,23 @@ function runtimePlatform() {
122
134
  function windowsAwareCommand(name) {
123
135
  return runtimePlatform() === "win32" ? `${name}.cmd` : name;
124
136
  }
137
+ function windowsCommandHost() {
138
+ return testEnv("SUPERSPEC_TEST_CMD_EXE") ?? process.env.ComSpec ?? "cmd.exe";
139
+ }
140
+ function execFileTextSync(command, args, options) {
141
+ if (runtimePlatform() !== "win32")
142
+ return execFileSync(command, args, options);
143
+ return execFileSync(windowsCommandHost(), ["/d", "/s", "/c", command, ...args], options);
144
+ }
125
145
  function npmCommand() {
126
146
  return windowsAwareCommand("npm");
127
147
  }
128
148
  function superspecCommand() {
129
149
  return windowsAwareCommand("superspec");
130
150
  }
151
+ function openspecCommand() {
152
+ return windowsAwareCommand("openspec");
153
+ }
131
154
  function selfUpdateError(phase, err, latest = null) {
132
155
  return new SelfUpdateError(phase, commandErrorMessage(err), latest);
133
156
  }
@@ -161,6 +184,112 @@ function selfUpdateFailurePayload(err) {
161
184
  },
162
185
  };
163
186
  }
187
+ function openSpecDependencyFailurePayload(err) {
188
+ return {
189
+ ok: false,
190
+ message: err.message,
191
+ openspec: {
192
+ package: OPENSPEC_PACKAGE_NAME,
193
+ required_version: OPENSPEC_REQUIRED_VERSION,
194
+ before: err.before,
195
+ after: null,
196
+ action: "failed",
197
+ phase: err.phase,
198
+ },
199
+ };
200
+ }
201
+ function parseOpenSpecVersion(output) {
202
+ const match = output.trim().match(/(\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?)/);
203
+ return match?.[1] ?? null;
204
+ }
205
+ let openSpecVersionProbeCount = 0;
206
+ function mockedOpenSpecVersion() {
207
+ const sequence = testEnv("SUPERSPEC_TEST_OPENSPEC_VERSION_SEQUENCE");
208
+ if (sequence !== undefined) {
209
+ const parts = sequence.split("|");
210
+ const raw = parts[Math.min(openSpecVersionProbeCount, parts.length - 1)]?.trim() ?? "";
211
+ openSpecVersionProbeCount += 1;
212
+ if (/^(missing|error)$/i.test(raw))
213
+ throw new Error("openspec CLI 不可用");
214
+ return raw;
215
+ }
216
+ const explicit = testEnv("SUPERSPEC_TEST_OPENSPEC_VERSION");
217
+ if (explicit !== undefined) {
218
+ if (/^(missing|error)$/i.test(explicit.trim()))
219
+ throw new Error("openspec CLI 不可用");
220
+ return explicit;
221
+ }
222
+ return isTestMode() ? OPENSPEC_REQUIRED_VERSION : undefined;
223
+ }
224
+ function pathOpenSpecVersion() {
225
+ const mocked = mockedOpenSpecVersion();
226
+ if (mocked !== undefined) {
227
+ const parsed = parseOpenSpecVersion(mocked);
228
+ if (!parsed)
229
+ throw new Error(`无法解析 openspec --version 输出:${mocked}`);
230
+ return parsed;
231
+ }
232
+ const output = execFileTextSync(openspecCommand(), ["--version"], {
233
+ encoding: "utf8",
234
+ env: process.env,
235
+ });
236
+ const parsed = parseOpenSpecVersion(output);
237
+ if (!parsed)
238
+ throw new Error(`无法解析 openspec --version 输出:${output.trim()}`);
239
+ return parsed;
240
+ }
241
+ function installRequiredOpenSpecGlobal(before) {
242
+ const testError = testEnv("SUPERSPEC_TEST_OPENSPEC_INSTALL_ERROR");
243
+ if (testError)
244
+ throw new OpenSpecDependencyError("global_install", testError, before);
245
+ if (testEnv("SUPERSPEC_TEST_SKIP_OPENSPEC_INSTALL") === "1")
246
+ return;
247
+ try {
248
+ execFileTextSync(npmCommand(), ["install", "-g", `${OPENSPEC_PACKAGE_NAME}@${OPENSPEC_REQUIRED_VERSION}`], {
249
+ encoding: "utf8",
250
+ stdio: ["ignore", "pipe", "pipe"],
251
+ });
252
+ }
253
+ catch (err) {
254
+ throw new OpenSpecDependencyError("global_install", `安装 OpenSpec ${OPENSPEC_REQUIRED_VERSION} 失败:${commandErrorMessage(err)}`, before);
255
+ }
256
+ }
257
+ function ensureOpenSpecCli() {
258
+ let before = null;
259
+ try {
260
+ before = pathOpenSpecVersion();
261
+ }
262
+ catch {
263
+ before = null;
264
+ }
265
+ if (before === OPENSPEC_REQUIRED_VERSION) {
266
+ return {
267
+ package: OPENSPEC_PACKAGE_NAME,
268
+ required_version: OPENSPEC_REQUIRED_VERSION,
269
+ before,
270
+ after: before,
271
+ action: "already_satisfied",
272
+ };
273
+ }
274
+ installRequiredOpenSpecGlobal(before);
275
+ let after;
276
+ try {
277
+ after = pathOpenSpecVersion();
278
+ }
279
+ catch (err) {
280
+ throw new OpenSpecDependencyError("version_check", `安装后仍无法读取 openspec --version:${commandErrorMessage(err)}`, before);
281
+ }
282
+ if (after !== OPENSPEC_REQUIRED_VERSION) {
283
+ throw new OpenSpecDependencyError("version_mismatch", `全局 openspec 版本仍为 ${after},期望 ${OPENSPEC_REQUIRED_VERSION}。请检查 npm 全局 bin 是否在 PATH 前置。`, before);
284
+ }
285
+ return {
286
+ package: OPENSPEC_PACKAGE_NAME,
287
+ required_version: OPENSPEC_REQUIRED_VERSION,
288
+ before,
289
+ after,
290
+ action: before === null ? "installed" : "updated",
291
+ };
292
+ }
164
293
  function npmLatestVersion() {
165
294
  const testError = testEnv("SUPERSPEC_TEST_NPM_VIEW_ERROR");
166
295
  if (testError)
@@ -169,7 +298,7 @@ function npmLatestVersion() {
169
298
  if (testLatest)
170
299
  return testLatest;
171
300
  try {
172
- const output = execFileSync(npmCommand(), ["view", PACKAGE_NAME, "version"], { encoding: "utf8" });
301
+ const output = execFileTextSync(npmCommand(), ["view", PACKAGE_NAME, "version"], { encoding: "utf8" });
173
302
  return output.trim().replace(/^"|"$/g, "");
174
303
  }
175
304
  catch (err) {
@@ -183,7 +312,7 @@ function installLatestGlobal() {
183
312
  if (testEnv("SUPERSPEC_TEST_SKIP_GLOBAL_INSTALL") === "1")
184
313
  return;
185
314
  try {
186
- execFileSync(npmCommand(), ["install", "-g", `${PACKAGE_NAME}@latest`], {
315
+ execFileTextSync(npmCommand(), ["install", "-g", `${PACKAGE_NAME}@latest`], {
187
316
  encoding: "utf8",
188
317
  stdio: ["ignore", "pipe", "pipe"],
189
318
  });
@@ -210,7 +339,7 @@ function pathCliVersion() {
210
339
  throw new Error(`无法解析 superspec --version 输出:${testOutput}`);
211
340
  return parsed;
212
341
  }
213
- const output = execFileSync(superspecCommand(), ["--version"], {
342
+ const output = execFileTextSync(superspecCommand(), ["--version"], {
214
343
  encoding: "utf8",
215
344
  env: process.env,
216
345
  });
@@ -239,7 +368,7 @@ function rerunUpdatedCli(projectRoot, args) {
239
368
  if (testOutput)
240
369
  return testOutput;
241
370
  try {
242
- return execFileSync(superspecCommand(), args, {
371
+ return execFileTextSync(superspecCommand(), args, {
243
372
  cwd: projectRoot,
244
373
  encoding: "utf8",
245
374
  env: process.env,
@@ -413,13 +542,19 @@ jobs 子命令:
413
542
  return rerun.exitCode;
414
543
  }
415
544
  }
416
- console.log(JSON.stringify(installProject(projectRoot)));
545
+ const openspec = ensureOpenSpecCli();
546
+ console.log(JSON.stringify({
547
+ ...installProject(projectRoot),
548
+ openspec,
549
+ }));
417
550
  return 0;
418
551
  }
419
552
  catch (err) {
420
553
  console.log(JSON.stringify(err instanceof SelfUpdateError
421
554
  ? selfUpdateFailurePayload(err)
422
- : { ok: false, message: commandErrorMessage(err) }));
555
+ : err instanceof OpenSpecDependencyError
556
+ ? openSpecDependencyFailurePayload(err)
557
+ : { ok: false, message: commandErrorMessage(err) }));
423
558
  return 1;
424
559
  }
425
560
  }
@@ -433,9 +568,11 @@ jobs 子命令:
433
568
  return rerun.exitCode;
434
569
  }
435
570
  }
571
+ const openspec = ensureOpenSpecCli();
436
572
  const result = installProject(projectRoot, { allowLegacyState: true });
437
573
  console.log(JSON.stringify({
438
574
  ...result,
575
+ openspec,
439
576
  message: `SuperSpec ${SUPERSPEC_VERSION} 已更新项目工作流`,
440
577
  }));
441
578
  return 0;
@@ -443,7 +580,9 @@ jobs 子命令:
443
580
  catch (err) {
444
581
  console.log(JSON.stringify(err instanceof SelfUpdateError
445
582
  ? selfUpdateFailurePayload(err)
446
- : { ok: false, message: commandErrorMessage(err) }));
583
+ : err instanceof OpenSpecDependencyError
584
+ ? openSpecDependencyFailurePayload(err)
585
+ : { ok: false, message: commandErrorMessage(err) }));
447
586
  return 1;
448
587
  }
449
588
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@peterxiaoyang/superspec",
3
- "version": "0.1.27",
3
+ "version": "0.1.29",
4
4
  "description": "SuperSpec 流程引擎 — transition engine with lightweight fact-sync",
5
5
  "type": "module",
6
6
  "engines": {