@peterxiaoyang/superspec 0.1.17-alpha → 0.1.20
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 -2
- package/dist/cli.js +14 -12
- package/dist/install.d.ts +1 -0
- package/dist/install.js +99 -12
- package/dist/version.d.ts +1 -0
- package/dist/version.js +9 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -232,13 +232,13 @@ openspec instructions proposal --change <change>
|
|
|
232
232
|
superspec status
|
|
233
233
|
```
|
|
234
234
|
|
|
235
|
-
|
|
235
|
+
同步当前项目的 SuperSpec 工作流入口:
|
|
236
236
|
|
|
237
237
|
```bash
|
|
238
238
|
superspec update
|
|
239
239
|
```
|
|
240
240
|
|
|
241
|
-
|
|
241
|
+
这条命令会补齐 `.superspec/changes` 运行时目录,并把当前 CLI 内置的 `.codex/skills/superspec-*`、`.codex/prompts/*.md`、`.codex/agents/*.toml` 同步到项目里。真正升级全局 npm 包仍需使用 `npm install -g @peterxiaoyang/superspec@<version>`。
|
|
242
242
|
|
|
243
243
|
## 进阶信息
|
|
244
244
|
|
package/dist/cli.js
CHANGED
|
@@ -9,6 +9,7 @@ import { proposeReady, commitTransition, transitionInit, transitionExplore, star
|
|
|
9
9
|
import { recordJobSubmit, recordUserDecision, jobsList, jobsPacket } from "./record.js";
|
|
10
10
|
import { recordTestRun } from "./task.js";
|
|
11
11
|
import { probeOpenSpec, openspecStatus, changeRoot } from "./openspec.js";
|
|
12
|
+
import { SUPERSPEC_VERSION } from "./version.js";
|
|
12
13
|
// ===== 参数解析 =====
|
|
13
14
|
function parseArgs(argv) {
|
|
14
15
|
const [command, subcommand, ...rest] = argv;
|
|
@@ -44,7 +45,7 @@ function parseFlags(args) {
|
|
|
44
45
|
async function main(argv) {
|
|
45
46
|
// --help / 无参数 → 打印用法
|
|
46
47
|
if (argv.length === 0 || argv.includes("--help") || argv.includes("-h")) {
|
|
47
|
-
console.log(`SuperSpec 流程引擎
|
|
48
|
+
console.log(`SuperSpec 流程引擎 ${SUPERSPEC_VERSION}
|
|
48
49
|
|
|
49
50
|
用法:superspec <命令> [选项]
|
|
50
51
|
|
|
@@ -55,7 +56,7 @@ async function main(argv) {
|
|
|
55
56
|
jobs <子命令> --change <C> 工作项管理(见下)
|
|
56
57
|
install 安装项目工作流入口
|
|
57
58
|
init --scope project install 的兼容别名
|
|
58
|
-
update
|
|
59
|
+
update 同步项目工作流模板
|
|
59
60
|
version 版本号
|
|
60
61
|
|
|
61
62
|
transition 子命令:
|
|
@@ -75,7 +76,7 @@ jobs 子命令:
|
|
|
75
76
|
}
|
|
76
77
|
// version
|
|
77
78
|
if (argv[0] === "version" || argv[0] === "--version" || argv[0] === "-v") {
|
|
78
|
-
console.log(
|
|
79
|
+
console.log(`SuperSpec ${SUPERSPEC_VERSION}`);
|
|
79
80
|
return 0;
|
|
80
81
|
}
|
|
81
82
|
const { command, subcommand, opts } = parseArgs(argv);
|
|
@@ -102,20 +103,21 @@ jobs 子命令:
|
|
|
102
103
|
}
|
|
103
104
|
}
|
|
104
105
|
if (command === "update") {
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
106
|
+
try {
|
|
107
|
+
const result = installProject(projectRoot, { allowLegacyState: true });
|
|
108
|
+
console.log(JSON.stringify({
|
|
109
|
+
...result,
|
|
110
|
+
message: `SuperSpec ${SUPERSPEC_VERSION} 已更新项目工作流`,
|
|
111
|
+
}));
|
|
112
|
+
return 0;
|
|
113
|
+
}
|
|
114
|
+
catch (err) {
|
|
109
115
|
console.log(JSON.stringify({
|
|
110
116
|
ok: false,
|
|
111
|
-
message:
|
|
112
|
-
"请用 npm install -g @peterxiaoyang/superspec@0.1.16-alpha 手动安装。\n" +
|
|
113
|
-
"现有 change 请先用 0.1.x 完成归档。",
|
|
117
|
+
message: err.message,
|
|
114
118
|
}));
|
|
115
119
|
return 1;
|
|
116
120
|
}
|
|
117
|
-
console.log(JSON.stringify({ ok: true, message: "已是最新版本 0.1.16-alpha" }));
|
|
118
|
-
return 0;
|
|
119
121
|
}
|
|
120
122
|
const change = opts.change;
|
|
121
123
|
if (!change && command !== "status") {
|
package/dist/install.d.ts
CHANGED
package/dist/install.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { join } from "node:path";
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { SUPERSPEC_VERSION } from "./version.js";
|
|
3
4
|
export const WORKFLOW_SKILLS = [
|
|
4
5
|
"superspec-explore",
|
|
5
6
|
"superspec-propose",
|
|
@@ -58,13 +59,23 @@ function assertWorkflowTemplates(templateRoot) {
|
|
|
58
59
|
throw new Error(`workflow templates missing: ${missing.join(", ")}`);
|
|
59
60
|
}
|
|
60
61
|
}
|
|
62
|
+
function writeBundledFile(src, dest) {
|
|
63
|
+
mkdirSync(dirname(dest), { recursive: true });
|
|
64
|
+
const next = readFileSync(src, "utf8");
|
|
65
|
+
if (existsSync(dest)) {
|
|
66
|
+
const current = readFileSync(dest, "utf8");
|
|
67
|
+
if (current === next)
|
|
68
|
+
return;
|
|
69
|
+
writeFileSync(`${dest}.bak`, current);
|
|
70
|
+
}
|
|
71
|
+
writeFileSync(dest, next);
|
|
72
|
+
}
|
|
61
73
|
function copySkills(templateRoot, projectRoot) {
|
|
62
74
|
const skillsDest = join(projectRoot, ".codex", "skills");
|
|
63
75
|
const installedSkills = [];
|
|
64
76
|
for (const skill of WORKFLOW_SKILLS) {
|
|
65
77
|
const src = join(templateRoot, "skills", skill, "SKILL.md");
|
|
66
|
-
|
|
67
|
-
copyFileSync(src, join(skillsDest, skill, "SKILL.md"));
|
|
78
|
+
writeBundledFile(src, join(skillsDest, skill, "SKILL.md"));
|
|
68
79
|
installedSkills.push(skill);
|
|
69
80
|
}
|
|
70
81
|
return installedSkills;
|
|
@@ -74,8 +85,7 @@ function copyPrompts(templateRoot, projectRoot) {
|
|
|
74
85
|
const installedPrompts = [];
|
|
75
86
|
for (const prompt of WORKFLOW_PROMPTS) {
|
|
76
87
|
const src = join(templateRoot, "prompts", prompt);
|
|
77
|
-
|
|
78
|
-
copyFileSync(src, join(promptsDest, prompt));
|
|
88
|
+
writeBundledFile(src, join(promptsDest, prompt));
|
|
79
89
|
installedPrompts.push(prompt);
|
|
80
90
|
}
|
|
81
91
|
return installedPrompts;
|
|
@@ -85,12 +95,88 @@ function copyAgents(templateRoot, projectRoot) {
|
|
|
85
95
|
const installedAgents = [];
|
|
86
96
|
for (const agent of WORKFLOW_AGENTS) {
|
|
87
97
|
const src = join(templateRoot, "agents", agent);
|
|
88
|
-
|
|
89
|
-
copyFileSync(src, join(agentsDest, agent));
|
|
98
|
+
writeBundledFile(src, join(agentsDest, agent));
|
|
90
99
|
installedAgents.push(agent);
|
|
91
100
|
}
|
|
92
101
|
return installedAgents;
|
|
93
102
|
}
|
|
103
|
+
function isRecord(value) {
|
|
104
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
105
|
+
}
|
|
106
|
+
function isLegacySuperspecHook(value) {
|
|
107
|
+
return isRecord(value)
|
|
108
|
+
&& typeof value.command === "string"
|
|
109
|
+
&& /\bsuperspec-hook\b/.test(value.command);
|
|
110
|
+
}
|
|
111
|
+
function removeLegacySuperspecHookCommands(hooks) {
|
|
112
|
+
if (!isRecord(hooks))
|
|
113
|
+
return { hooks, removed: 0 };
|
|
114
|
+
let removed = 0;
|
|
115
|
+
const nextHooks = { ...hooks };
|
|
116
|
+
for (const [eventName, eventValue] of Object.entries(hooks)) {
|
|
117
|
+
if (!Array.isArray(eventValue))
|
|
118
|
+
continue;
|
|
119
|
+
const nextEvent = [];
|
|
120
|
+
for (const entry of eventValue) {
|
|
121
|
+
if (!isRecord(entry) || !Array.isArray(entry.hooks)) {
|
|
122
|
+
nextEvent.push(entry);
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
const keptCommands = entry.hooks.filter(command => {
|
|
126
|
+
if (!isLegacySuperspecHook(command))
|
|
127
|
+
return true;
|
|
128
|
+
removed += 1;
|
|
129
|
+
return false;
|
|
130
|
+
});
|
|
131
|
+
if (keptCommands.length > 0) {
|
|
132
|
+
nextEvent.push({ ...entry, hooks: keptCommands });
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
if (nextEvent.length > 0)
|
|
136
|
+
nextHooks[eventName] = nextEvent;
|
|
137
|
+
else
|
|
138
|
+
delete nextHooks[eventName];
|
|
139
|
+
}
|
|
140
|
+
return { hooks: nextHooks, removed };
|
|
141
|
+
}
|
|
142
|
+
function hasUserHookContent(config) {
|
|
143
|
+
return Object.entries(config).some(([key, value]) => {
|
|
144
|
+
if (key === "superspec")
|
|
145
|
+
return false;
|
|
146
|
+
if (key === "hooks" && isRecord(value) && Object.keys(value).length === 0)
|
|
147
|
+
return false;
|
|
148
|
+
return true;
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
function migrateLegacyManagedHooks(projectRoot) {
|
|
152
|
+
const hooksPath = join(projectRoot, ".codex", "hooks.json");
|
|
153
|
+
if (!existsSync(hooksPath))
|
|
154
|
+
return;
|
|
155
|
+
const current = readFileSync(hooksPath, "utf8");
|
|
156
|
+
let parsed;
|
|
157
|
+
try {
|
|
158
|
+
parsed = JSON.parse(current);
|
|
159
|
+
}
|
|
160
|
+
catch {
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
if (!isRecord(parsed) || !isRecord(parsed.superspec) || parsed.superspec.managed !== true)
|
|
164
|
+
return;
|
|
165
|
+
const migrated = removeLegacySuperspecHookCommands(parsed.hooks);
|
|
166
|
+
if (migrated.removed === 0)
|
|
167
|
+
return;
|
|
168
|
+
writeFileSync(`${hooksPath}.bak`, current);
|
|
169
|
+
const nextConfig = { ...parsed, hooks: migrated.hooks };
|
|
170
|
+
delete nextConfig.superspec;
|
|
171
|
+
if (isRecord(nextConfig.hooks) && Object.keys(nextConfig.hooks).length === 0) {
|
|
172
|
+
delete nextConfig.hooks;
|
|
173
|
+
}
|
|
174
|
+
if (!hasUserHookContent(nextConfig)) {
|
|
175
|
+
rmSync(hooksPath);
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
writeFileSync(hooksPath, `${JSON.stringify(nextConfig, null, 2)}\n`);
|
|
179
|
+
}
|
|
94
180
|
function insertMissingTableEntries(content, table, entries) {
|
|
95
181
|
const tablePattern = new RegExp(`^\\[${table}\\]\\s*$`, "m");
|
|
96
182
|
const tableMatch = tablePattern.exec(content);
|
|
@@ -157,10 +243,10 @@ function ensureOpenSpecChineseContext(projectRoot) {
|
|
|
157
243
|
return OPENSPEC_CONFIG_PATH;
|
|
158
244
|
}
|
|
159
245
|
export function installProject(projectRoot, options = {}) {
|
|
160
|
-
if (legacyStateFound(projectRoot)) {
|
|
246
|
+
if (!options.allowLegacyState && legacyStateFound(projectRoot)) {
|
|
161
247
|
throw new Error("检测到老版 SuperSpec (0.x) 的状态文件。\n" +
|
|
162
|
-
|
|
163
|
-
|
|
248
|
+
`SuperSpec ${SUPERSPEC_VERSION} 是全新引擎,不兼容 0.x 的状态格式。\n` +
|
|
249
|
+
`请先用老版(0.1.x)完成或归档现有 change,再安装 ${SUPERSPEC_VERSION}。\n` +
|
|
164
250
|
"或在全新项目目录中安装。");
|
|
165
251
|
}
|
|
166
252
|
const templateRoot = options.templateRoot ?? defaultTemplateRoot();
|
|
@@ -170,9 +256,10 @@ export function installProject(projectRoot, options = {}) {
|
|
|
170
256
|
const gitignorePath = join(engineDir, ".gitignore");
|
|
171
257
|
if (!existsSync(gitignorePath))
|
|
172
258
|
writeFileSync(gitignorePath, "changes/\n*.log\n*.tmp\n");
|
|
259
|
+
migrateLegacyManagedHooks(projectRoot);
|
|
173
260
|
return {
|
|
174
261
|
ok: true,
|
|
175
|
-
message:
|
|
262
|
+
message: `SuperSpec ${SUPERSPEC_VERSION} 已安装`,
|
|
176
263
|
installed: {
|
|
177
264
|
engine_dir: ".superspec/",
|
|
178
265
|
skills: copySkills(templateRoot, projectRoot),
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const SUPERSPEC_VERSION: string;
|
package/dist/version.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
const require = createRequire(import.meta.url);
|
|
3
|
+
function readPackageVersion() {
|
|
4
|
+
const pkg = require("../package.json");
|
|
5
|
+
if (!pkg.version)
|
|
6
|
+
throw new Error("package.json 缺少 version 字段");
|
|
7
|
+
return pkg.version;
|
|
8
|
+
}
|
|
9
|
+
export const SUPERSPEC_VERSION = readPackageVersion();
|