@peterxiaoyang/superspec 0.1.27 → 0.1.28

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 +134 -3
  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
  }
@@ -128,6 +140,9 @@ function npmCommand() {
128
140
  function superspecCommand() {
129
141
  return windowsAwareCommand("superspec");
130
142
  }
143
+ function openspecCommand() {
144
+ return windowsAwareCommand("openspec");
145
+ }
131
146
  function selfUpdateError(phase, err, latest = null) {
132
147
  return new SelfUpdateError(phase, commandErrorMessage(err), latest);
133
148
  }
@@ -161,6 +176,112 @@ function selfUpdateFailurePayload(err) {
161
176
  },
162
177
  };
163
178
  }
179
+ function openSpecDependencyFailurePayload(err) {
180
+ return {
181
+ ok: false,
182
+ message: err.message,
183
+ openspec: {
184
+ package: OPENSPEC_PACKAGE_NAME,
185
+ required_version: OPENSPEC_REQUIRED_VERSION,
186
+ before: err.before,
187
+ after: null,
188
+ action: "failed",
189
+ phase: err.phase,
190
+ },
191
+ };
192
+ }
193
+ function parseOpenSpecVersion(output) {
194
+ const match = output.trim().match(/(\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?)/);
195
+ return match?.[1] ?? null;
196
+ }
197
+ let openSpecVersionProbeCount = 0;
198
+ function mockedOpenSpecVersion() {
199
+ const sequence = testEnv("SUPERSPEC_TEST_OPENSPEC_VERSION_SEQUENCE");
200
+ if (sequence !== undefined) {
201
+ const parts = sequence.split("|");
202
+ const raw = parts[Math.min(openSpecVersionProbeCount, parts.length - 1)]?.trim() ?? "";
203
+ openSpecVersionProbeCount += 1;
204
+ if (/^(missing|error)$/i.test(raw))
205
+ throw new Error("openspec CLI 不可用");
206
+ return raw;
207
+ }
208
+ const explicit = testEnv("SUPERSPEC_TEST_OPENSPEC_VERSION");
209
+ if (explicit !== undefined) {
210
+ if (/^(missing|error)$/i.test(explicit.trim()))
211
+ throw new Error("openspec CLI 不可用");
212
+ return explicit;
213
+ }
214
+ return isTestMode() ? OPENSPEC_REQUIRED_VERSION : undefined;
215
+ }
216
+ function pathOpenSpecVersion() {
217
+ const mocked = mockedOpenSpecVersion();
218
+ if (mocked !== undefined) {
219
+ const parsed = parseOpenSpecVersion(mocked);
220
+ if (!parsed)
221
+ throw new Error(`无法解析 openspec --version 输出:${mocked}`);
222
+ return parsed;
223
+ }
224
+ const output = execFileSync(openspecCommand(), ["--version"], {
225
+ encoding: "utf8",
226
+ env: process.env,
227
+ });
228
+ const parsed = parseOpenSpecVersion(output);
229
+ if (!parsed)
230
+ throw new Error(`无法解析 openspec --version 输出:${output.trim()}`);
231
+ return parsed;
232
+ }
233
+ function installRequiredOpenSpecGlobal(before) {
234
+ const testError = testEnv("SUPERSPEC_TEST_OPENSPEC_INSTALL_ERROR");
235
+ if (testError)
236
+ throw new OpenSpecDependencyError("global_install", testError, before);
237
+ if (testEnv("SUPERSPEC_TEST_SKIP_OPENSPEC_INSTALL") === "1")
238
+ return;
239
+ try {
240
+ execFileSync(npmCommand(), ["install", "-g", `${OPENSPEC_PACKAGE_NAME}@${OPENSPEC_REQUIRED_VERSION}`], {
241
+ encoding: "utf8",
242
+ stdio: ["ignore", "pipe", "pipe"],
243
+ });
244
+ }
245
+ catch (err) {
246
+ throw new OpenSpecDependencyError("global_install", `安装 OpenSpec ${OPENSPEC_REQUIRED_VERSION} 失败:${commandErrorMessage(err)}`, before);
247
+ }
248
+ }
249
+ function ensureOpenSpecCli() {
250
+ let before = null;
251
+ try {
252
+ before = pathOpenSpecVersion();
253
+ }
254
+ catch {
255
+ before = null;
256
+ }
257
+ if (before === OPENSPEC_REQUIRED_VERSION) {
258
+ return {
259
+ package: OPENSPEC_PACKAGE_NAME,
260
+ required_version: OPENSPEC_REQUIRED_VERSION,
261
+ before,
262
+ after: before,
263
+ action: "already_satisfied",
264
+ };
265
+ }
266
+ installRequiredOpenSpecGlobal(before);
267
+ let after;
268
+ try {
269
+ after = pathOpenSpecVersion();
270
+ }
271
+ catch (err) {
272
+ throw new OpenSpecDependencyError("version_check", `安装后仍无法读取 openspec --version:${commandErrorMessage(err)}`, before);
273
+ }
274
+ if (after !== OPENSPEC_REQUIRED_VERSION) {
275
+ throw new OpenSpecDependencyError("version_mismatch", `全局 openspec 版本仍为 ${after},期望 ${OPENSPEC_REQUIRED_VERSION}。请检查 npm 全局 bin 是否在 PATH 前置。`, before);
276
+ }
277
+ return {
278
+ package: OPENSPEC_PACKAGE_NAME,
279
+ required_version: OPENSPEC_REQUIRED_VERSION,
280
+ before,
281
+ after,
282
+ action: before === null ? "installed" : "updated",
283
+ };
284
+ }
164
285
  function npmLatestVersion() {
165
286
  const testError = testEnv("SUPERSPEC_TEST_NPM_VIEW_ERROR");
166
287
  if (testError)
@@ -413,13 +534,19 @@ jobs 子命令:
413
534
  return rerun.exitCode;
414
535
  }
415
536
  }
416
- console.log(JSON.stringify(installProject(projectRoot)));
537
+ const openspec = ensureOpenSpecCli();
538
+ console.log(JSON.stringify({
539
+ ...installProject(projectRoot),
540
+ openspec,
541
+ }));
417
542
  return 0;
418
543
  }
419
544
  catch (err) {
420
545
  console.log(JSON.stringify(err instanceof SelfUpdateError
421
546
  ? selfUpdateFailurePayload(err)
422
- : { ok: false, message: commandErrorMessage(err) }));
547
+ : err instanceof OpenSpecDependencyError
548
+ ? openSpecDependencyFailurePayload(err)
549
+ : { ok: false, message: commandErrorMessage(err) }));
423
550
  return 1;
424
551
  }
425
552
  }
@@ -433,9 +560,11 @@ jobs 子命令:
433
560
  return rerun.exitCode;
434
561
  }
435
562
  }
563
+ const openspec = ensureOpenSpecCli();
436
564
  const result = installProject(projectRoot, { allowLegacyState: true });
437
565
  console.log(JSON.stringify({
438
566
  ...result,
567
+ openspec,
439
568
  message: `SuperSpec ${SUPERSPEC_VERSION} 已更新项目工作流`,
440
569
  }));
441
570
  return 0;
@@ -443,7 +572,9 @@ jobs 子命令:
443
572
  catch (err) {
444
573
  console.log(JSON.stringify(err instanceof SelfUpdateError
445
574
  ? selfUpdateFailurePayload(err)
446
- : { ok: false, message: commandErrorMessage(err) }));
575
+ : err instanceof OpenSpecDependencyError
576
+ ? openSpecDependencyFailurePayload(err)
577
+ : { ok: false, message: commandErrorMessage(err) }));
447
578
  return 1;
448
579
  }
449
580
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@peterxiaoyang/superspec",
3
- "version": "0.1.27",
3
+ "version": "0.1.28",
4
4
  "description": "SuperSpec 流程引擎 — transition engine with lightweight fact-sync",
5
5
  "type": "module",
6
6
  "engines": {