@peterxiaoyang/superspec 0.1.26 → 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 +150 -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,12 +112,37 @@ 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
  }
116
128
  function testEnv(name) {
117
129
  return isTestMode() ? process.env[name] : undefined;
118
130
  }
131
+ function runtimePlatform() {
132
+ return testEnv("SUPERSPEC_TEST_PLATFORM") ?? process.platform;
133
+ }
134
+ function windowsAwareCommand(name) {
135
+ return runtimePlatform() === "win32" ? `${name}.cmd` : name;
136
+ }
137
+ function npmCommand() {
138
+ return windowsAwareCommand("npm");
139
+ }
140
+ function superspecCommand() {
141
+ return windowsAwareCommand("superspec");
142
+ }
143
+ function openspecCommand() {
144
+ return windowsAwareCommand("openspec");
145
+ }
119
146
  function selfUpdateError(phase, err, latest = null) {
120
147
  return new SelfUpdateError(phase, commandErrorMessage(err), latest);
121
148
  }
@@ -149,6 +176,112 @@ function selfUpdateFailurePayload(err) {
149
176
  },
150
177
  };
151
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
+ }
152
285
  function npmLatestVersion() {
153
286
  const testError = testEnv("SUPERSPEC_TEST_NPM_VIEW_ERROR");
154
287
  if (testError)
@@ -157,7 +290,7 @@ function npmLatestVersion() {
157
290
  if (testLatest)
158
291
  return testLatest;
159
292
  try {
160
- const output = execFileSync("npm", ["view", PACKAGE_NAME, "version"], { encoding: "utf8" });
293
+ const output = execFileSync(npmCommand(), ["view", PACKAGE_NAME, "version"], { encoding: "utf8" });
161
294
  return output.trim().replace(/^"|"$/g, "");
162
295
  }
163
296
  catch (err) {
@@ -171,7 +304,7 @@ function installLatestGlobal() {
171
304
  if (testEnv("SUPERSPEC_TEST_SKIP_GLOBAL_INSTALL") === "1")
172
305
  return;
173
306
  try {
174
- execFileSync("npm", ["install", "-g", `${PACKAGE_NAME}@latest`], {
307
+ execFileSync(npmCommand(), ["install", "-g", `${PACKAGE_NAME}@latest`], {
175
308
  encoding: "utf8",
176
309
  stdio: ["ignore", "pipe", "pipe"],
177
310
  });
@@ -198,7 +331,7 @@ function pathCliVersion() {
198
331
  throw new Error(`无法解析 superspec --version 输出:${testOutput}`);
199
332
  return parsed;
200
333
  }
201
- const output = execFileSync("superspec", ["--version"], {
334
+ const output = execFileSync(superspecCommand(), ["--version"], {
202
335
  encoding: "utf8",
203
336
  env: process.env,
204
337
  });
@@ -227,7 +360,7 @@ function rerunUpdatedCli(projectRoot, args) {
227
360
  if (testOutput)
228
361
  return testOutput;
229
362
  try {
230
- return execFileSync("superspec", args, {
363
+ return execFileSync(superspecCommand(), args, {
231
364
  cwd: projectRoot,
232
365
  encoding: "utf8",
233
366
  env: process.env,
@@ -401,13 +534,19 @@ jobs 子命令:
401
534
  return rerun.exitCode;
402
535
  }
403
536
  }
404
- console.log(JSON.stringify(installProject(projectRoot)));
537
+ const openspec = ensureOpenSpecCli();
538
+ console.log(JSON.stringify({
539
+ ...installProject(projectRoot),
540
+ openspec,
541
+ }));
405
542
  return 0;
406
543
  }
407
544
  catch (err) {
408
545
  console.log(JSON.stringify(err instanceof SelfUpdateError
409
546
  ? selfUpdateFailurePayload(err)
410
- : { ok: false, message: commandErrorMessage(err) }));
547
+ : err instanceof OpenSpecDependencyError
548
+ ? openSpecDependencyFailurePayload(err)
549
+ : { ok: false, message: commandErrorMessage(err) }));
411
550
  return 1;
412
551
  }
413
552
  }
@@ -421,9 +560,11 @@ jobs 子命令:
421
560
  return rerun.exitCode;
422
561
  }
423
562
  }
563
+ const openspec = ensureOpenSpecCli();
424
564
  const result = installProject(projectRoot, { allowLegacyState: true });
425
565
  console.log(JSON.stringify({
426
566
  ...result,
567
+ openspec,
427
568
  message: `SuperSpec ${SUPERSPEC_VERSION} 已更新项目工作流`,
428
569
  }));
429
570
  return 0;
@@ -431,7 +572,9 @@ jobs 子命令:
431
572
  catch (err) {
432
573
  console.log(JSON.stringify(err instanceof SelfUpdateError
433
574
  ? selfUpdateFailurePayload(err)
434
- : { ok: false, message: commandErrorMessage(err) }));
575
+ : err instanceof OpenSpecDependencyError
576
+ ? openSpecDependencyFailurePayload(err)
577
+ : { ok: false, message: commandErrorMessage(err) }));
435
578
  return 1;
436
579
  }
437
580
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@peterxiaoyang/superspec",
3
- "version": "0.1.26",
3
+ "version": "0.1.28",
4
4
  "description": "SuperSpec 流程引擎 — transition engine with lightweight fact-sync",
5
5
  "type": "module",
6
6
  "engines": {