@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.
- package/README.md +2 -1
- package/dist/cli.js +146 -7
- 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
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
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
|
|
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
|
-
|
|
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
|
-
:
|
|
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
|
-
:
|
|
583
|
+
: err instanceof OpenSpecDependencyError
|
|
584
|
+
? openSpecDependencyFailurePayload(err)
|
|
585
|
+
: { ok: false, message: commandErrorMessage(err) }));
|
|
447
586
|
return 1;
|
|
448
587
|
}
|
|
449
588
|
}
|