@peterxiaoyang/superspec 0.1.15-alpha → 0.1.16-alpha
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 +37 -36
- package/dist/cli.js +28 -29
- package/dist/install.d.ts +18 -0
- package/dist/install.js +161 -0
- package/dist/next.js +1 -1
- package/dist/record.js +61 -6
- package/dist/transition.d.ts +1 -1
- package/dist/transition.js +6 -6
- package/dist/types.d.ts +4 -1
- package/package.json +1 -1
- package/templates/workflow/agents/architect.toml +13 -0
- package/templates/workflow/agents/code-reviewer.toml +13 -0
- package/templates/workflow/agents/critic.toml +13 -0
- package/templates/workflow/agents/executor.toml +13 -0
- package/templates/workflow/agents/explore.toml +13 -0
- package/templates/workflow/agents/final-audit.toml +13 -0
- package/templates/workflow/agents/proposal-auditor.toml +13 -0
- package/templates/workflow/agents/test-engineer.toml +13 -0
- package/templates/workflow/agents/test-runner.toml +13 -0
- package/templates/workflow/agents/verifier.toml +13 -0
- package/templates/workflow/prompts/architect.md +43 -0
- package/templates/workflow/prompts/code-reviewer.md +34 -0
- package/templates/workflow/prompts/critic.md +46 -0
- package/templates/workflow/prompts/executor.md +32 -0
- package/templates/workflow/prompts/explore.md +27 -0
- package/templates/workflow/prompts/final-audit.md +32 -0
- package/templates/workflow/prompts/proposal-auditor.md +34 -0
- package/templates/workflow/prompts/test-engineer.md +44 -0
- package/templates/workflow/prompts/test-runner.md +35 -0
- package/templates/workflow/prompts/verifier.md +35 -0
- package/templates/workflow/skills/superspec-apply/SKILL.md +4 -14
- package/templates/workflow/skills/superspec-archive/SKILL.md +0 -7
- package/templates/workflow/skills/superspec-explore/SKILL.md +4 -9
- package/templates/workflow/skills/superspec-propose/SKILL.md +10 -11
- package/templates/workflow/skills/superspec-review/SKILL.md +0 -22
package/README.md
CHANGED
|
@@ -77,17 +77,16 @@ npm install -g @peterxiaoyang/superspec@latest
|
|
|
77
77
|
进入你的项目根目录,然后运行:
|
|
78
78
|
|
|
79
79
|
```bash
|
|
80
|
-
superspec
|
|
80
|
+
superspec install
|
|
81
81
|
```
|
|
82
82
|
|
|
83
83
|
这条命令的意思是:把 SuperSpec 当前可用的工作流入口安装到项目里。
|
|
84
|
-
|
|
85
|
-
初始化还会安装托管的 `.codex/hooks.json`。默认 manifest 只在子智能体启动和停止时调用 SuperSpec,记录 best-effort 审计信息;它不会拦截普通文件写入,也不会在测试命令后自动记录结果。
|
|
84
|
+
当前 beta 会安装 `.superspec/` 引擎目录、`.codex/skills/superspec-*` 阶段入口、`.codex/prompts/*.md` 角色 prompt、`.codex/agents/*.toml` 子智能体配置,并补齐 `.codex/config.toml` 的多 agent 开关。`superspec init --scope project` 仍作为兼容别名可用。
|
|
86
85
|
|
|
87
86
|
Windows PowerShell 如果拦截 npm 的 `.ps1` 脚本,请改用:
|
|
88
87
|
|
|
89
88
|
```powershell
|
|
90
|
-
superspec.cmd
|
|
89
|
+
superspec.cmd install
|
|
91
90
|
```
|
|
92
91
|
|
|
93
92
|
### 3. 按步骤使用
|
|
@@ -136,6 +135,8 @@ superspec.cmd init --scope project
|
|
|
136
135
|
|
|
137
136
|
你日常主要记住这五个入口就够了。
|
|
138
137
|
|
|
138
|
+
CLI 不带 `--risk` 时默认是 `normal`;但内置的 `superspec-explore` 和 `superspec-propose` 技能默认用 `--risk strict` 驱动。探索阶段会创建 `critic` 工作项审查需求澄清记录;计划阶段会创建 `proposal-auditor`、`critic`、`architect` 和 `test-engineer` 工作项后再进入实现准备。
|
|
139
|
+
|
|
139
140
|
## 它会多保存哪些记录
|
|
140
141
|
|
|
141
142
|
SuperSpec 会在每次变更下面保存一些辅助记录,方便后续追溯。
|
|
@@ -200,34 +201,24 @@ superspec --version
|
|
|
200
201
|
安装到当前项目:
|
|
201
202
|
|
|
202
203
|
```bash
|
|
203
|
-
superspec
|
|
204
|
+
superspec install
|
|
204
205
|
```
|
|
205
206
|
|
|
206
|
-
|
|
207
|
+
`superspec init --scope project` 是兼容别名,也会执行同一套安装逻辑。
|
|
207
208
|
|
|
208
|
-
|
|
209
|
-
superspec update --scope project
|
|
210
|
-
```
|
|
211
|
-
|
|
212
|
-
这条命令会先通过 npm 更新全局 `@peterxiaoyang/superspec`,再用新版本更新当前项目里的入口文件。只想使用当前已安装包更新项目文件时,可以运行:
|
|
209
|
+
检查当前项目的 OpenSpec 探测结果:
|
|
213
210
|
|
|
214
211
|
```bash
|
|
215
|
-
superspec
|
|
212
|
+
superspec status
|
|
216
213
|
```
|
|
217
214
|
|
|
218
|
-
|
|
215
|
+
更新当前 beta CLI:
|
|
219
216
|
|
|
220
217
|
```bash
|
|
221
|
-
superspec
|
|
218
|
+
superspec update
|
|
222
219
|
```
|
|
223
220
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
诊断全局安装、PATH、OpenSpec 依赖和 npm bin 指向问题:
|
|
227
|
-
|
|
228
|
-
```bash
|
|
229
|
-
superspec doctor
|
|
230
|
-
```
|
|
221
|
+
这条命令只检查当前 `.superspec/changes` 运行时是否已经是新引擎布局;真正升级全局 npm 包仍需使用 `npm install -g @peterxiaoyang/superspec@<version>`。
|
|
231
222
|
|
|
232
223
|
## 进阶信息
|
|
233
224
|
|
|
@@ -235,24 +226,35 @@ superspec doctor
|
|
|
235
226
|
|
|
236
227
|
```text
|
|
237
228
|
.codex/
|
|
238
|
-
hooks.json
|
|
239
229
|
skills/superspec-explore/
|
|
240
230
|
skills/superspec-propose/
|
|
241
231
|
skills/superspec-apply/
|
|
242
232
|
skills/superspec-review/
|
|
243
233
|
skills/superspec-archive/
|
|
234
|
+
prompts/architect.md
|
|
235
|
+
prompts/code-reviewer.md
|
|
236
|
+
prompts/critic.md
|
|
237
|
+
prompts/executor.md
|
|
238
|
+
prompts/explore.md
|
|
239
|
+
prompts/final-audit.md
|
|
240
|
+
prompts/proposal-auditor.md
|
|
241
|
+
prompts/test-engineer.md
|
|
242
|
+
prompts/test-runner.md
|
|
243
|
+
prompts/verifier.md
|
|
244
|
+
agents/architect.toml
|
|
245
|
+
agents/code-reviewer.toml
|
|
246
|
+
agents/critic.toml
|
|
247
|
+
agents/executor.toml
|
|
248
|
+
agents/explore.toml
|
|
249
|
+
agents/final-audit.toml
|
|
250
|
+
agents/proposal-auditor.toml
|
|
251
|
+
agents/test-engineer.toml
|
|
252
|
+
agents/test-runner.toml
|
|
253
|
+
agents/verifier.toml
|
|
254
|
+
config.toml
|
|
244
255
|
```
|
|
245
256
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
```bash
|
|
249
|
-
superspec check check-init --change <变更ID>
|
|
250
|
-
superspec check check-apply-ready --change <变更ID>
|
|
251
|
-
superspec check check-review-ready --change <变更ID>
|
|
252
|
-
superspec check check-archive-ready --change <变更ID>
|
|
253
|
-
```
|
|
254
|
-
|
|
255
|
-
普通使用者通常不需要手动运行这些命令;对应的阶段入口会在需要时使用它们。
|
|
257
|
+
当前 beta 的阶段入口由 `superspec transition next --change <变更ID>` 驱动,不再提供旧版 `superspec check ...` surface。
|
|
256
258
|
|
|
257
259
|
如果你要开发 SuperSpec 本身:
|
|
258
260
|
|
|
@@ -260,14 +262,13 @@ superspec check check-archive-ready --change <变更ID>
|
|
|
260
262
|
npm run build
|
|
261
263
|
npm run typecheck
|
|
262
264
|
npm test
|
|
263
|
-
npm
|
|
265
|
+
npm pack --dry-run
|
|
264
266
|
```
|
|
265
267
|
|
|
266
268
|
更多细节见:
|
|
267
269
|
|
|
268
|
-
- `docs/
|
|
269
|
-
- `
|
|
270
|
-
- `.codex/skills/superspec-*/SKILL.md`:当前 Codex 适配器使用的阶段入口说明
|
|
270
|
+
- `docs/plans/SUPERSPEC_TRANSITION_ENGINE_SPEC_LITE.md`:transition engine 设计
|
|
271
|
+
- `templates/workflow/skills/superspec-*/SKILL.md`:当前 Codex 适配器使用的阶段入口说明
|
|
271
272
|
|
|
272
273
|
## 致谢与灵感来源
|
|
273
274
|
|
package/dist/cli.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// SuperSpec 流程引擎 — CLI 入口
|
|
3
|
-
import { writeFileSync, mkdirSync, existsSync } from "node:fs";
|
|
4
3
|
import { join } from "node:path";
|
|
4
|
+
import { installProject } from "./install.js";
|
|
5
5
|
import { writeSnapshot } from "./store.js";
|
|
6
6
|
import { rebuildSnapshot } from "./sync.js";
|
|
7
7
|
import { next as nextCmd } from "./next.js";
|
|
@@ -44,7 +44,7 @@ function parseFlags(args) {
|
|
|
44
44
|
async function main(argv) {
|
|
45
45
|
// --help / 无参数 → 打印用法
|
|
46
46
|
if (argv.length === 0 || argv.includes("--help") || argv.includes("-h")) {
|
|
47
|
-
console.log(`SuperSpec 流程引擎 0.1.
|
|
47
|
+
console.log(`SuperSpec 流程引擎 0.1.16-alpha
|
|
48
48
|
|
|
49
49
|
用法:superspec <命令> [选项]
|
|
50
50
|
|
|
@@ -53,7 +53,8 @@ async function main(argv) {
|
|
|
53
53
|
transition <子命令> --change <C> 状态流转(见下)
|
|
54
54
|
record <子命令> --change <C> 登记证据(见下)
|
|
55
55
|
jobs <子命令> --change <C> 工作项管理(见下)
|
|
56
|
-
install
|
|
56
|
+
install 安装项目工作流入口
|
|
57
|
+
init --scope project install 的兼容别名
|
|
57
58
|
update 更新 SuperSpec
|
|
58
59
|
version 版本号
|
|
59
60
|
|
|
@@ -74,36 +75,31 @@ jobs 子命令:
|
|
|
74
75
|
}
|
|
75
76
|
// version
|
|
76
77
|
if (argv[0] === "version" || argv[0] === "--version" || argv[0] === "-v") {
|
|
77
|
-
console.log("SuperSpec 0.1.
|
|
78
|
+
console.log("SuperSpec 0.1.16-alpha");
|
|
78
79
|
return 0;
|
|
79
80
|
}
|
|
80
81
|
const { command, subcommand, opts } = parseArgs(argv);
|
|
81
82
|
const projectRoot = process.cwd();
|
|
82
|
-
// install / update 不需要 --change
|
|
83
|
-
if (command === "install") {
|
|
84
|
-
|
|
85
|
-
const engineDir = join(projectRoot, ".superspec");
|
|
86
|
-
// 检测老版残留(0.x 的 superspec-state.json / superspec-state.lock)
|
|
87
|
-
const oldStateFiles = ["superspec-state.json", "superspec-state.lock", "ledger.jsonl"];
|
|
88
|
-
const foundOld = oldStateFiles.some(f => existsSync(join(projectRoot, "openspec", "changes")) &&
|
|
89
|
-
readdirSync(join(projectRoot, "openspec", "changes")).some(c => existsSync(join(projectRoot, "openspec", "changes", c, ".superspec", f))));
|
|
90
|
-
if (foundOld) {
|
|
83
|
+
// install / init / update 不需要 --change
|
|
84
|
+
if (command === "install" || command === "init") {
|
|
85
|
+
if (command === "init" && opts.scope && opts.scope !== "project") {
|
|
91
86
|
console.log(JSON.stringify({
|
|
92
87
|
ok: false,
|
|
93
|
-
message: "
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
88
|
+
message: "当前 beta 只支持 init --scope project",
|
|
89
|
+
}));
|
|
90
|
+
return 1;
|
|
91
|
+
}
|
|
92
|
+
try {
|
|
93
|
+
console.log(JSON.stringify(installProject(projectRoot)));
|
|
94
|
+
return 0;
|
|
95
|
+
}
|
|
96
|
+
catch (err) {
|
|
97
|
+
console.log(JSON.stringify({
|
|
98
|
+
ok: false,
|
|
99
|
+
message: err.message,
|
|
97
100
|
}));
|
|
98
101
|
return 1;
|
|
99
102
|
}
|
|
100
|
-
if (!existsSync(engineDir))
|
|
101
|
-
mkdirSync(join(engineDir, "changes"), { recursive: true });
|
|
102
|
-
const gitignorePath = join(engineDir, ".gitignore");
|
|
103
|
-
if (!existsSync(gitignorePath))
|
|
104
|
-
writeFileSync(gitignorePath, "changes/\n*.log\n*.tmp\n");
|
|
105
|
-
console.log(JSON.stringify({ ok: true, message: "SuperSpec 0.1.15-alpha 已安装" }));
|
|
106
|
-
return 0;
|
|
107
103
|
}
|
|
108
104
|
if (command === "update") {
|
|
109
105
|
// 检测是否从老版 update 过来
|
|
@@ -112,13 +108,13 @@ jobs 子命令:
|
|
|
112
108
|
if (isLegacyUpdate) {
|
|
113
109
|
console.log(JSON.stringify({
|
|
114
110
|
ok: false,
|
|
115
|
-
message: "SuperSpec 0.1.
|
|
116
|
-
"请用 npm install -g @peterxiaoyang/superspec@0.1.
|
|
111
|
+
message: "SuperSpec 0.1.16-alpha 是全新引擎,不能从 0.x 直接 update。\n" +
|
|
112
|
+
"请用 npm install -g @peterxiaoyang/superspec@0.1.16-alpha 手动安装。\n" +
|
|
117
113
|
"现有 change 请先用 0.1.x 完成归档。",
|
|
118
114
|
}));
|
|
119
115
|
return 1;
|
|
120
116
|
}
|
|
121
|
-
console.log(JSON.stringify({ ok: true, message: "已是最新版本 0.1.
|
|
117
|
+
console.log(JSON.stringify({ ok: true, message: "已是最新版本 0.1.16-alpha" }));
|
|
122
118
|
return 0;
|
|
123
119
|
}
|
|
124
120
|
const change = opts.change;
|
|
@@ -156,7 +152,10 @@ jobs 子命令:
|
|
|
156
152
|
console.log(JSON.stringify(transitionInit(projectRoot, change, cr), null, 2));
|
|
157
153
|
return 0;
|
|
158
154
|
case "explore":
|
|
159
|
-
|
|
155
|
+
{
|
|
156
|
+
const risk = opts.risk ?? "normal";
|
|
157
|
+
console.log(JSON.stringify(transitionExplore(projectRoot, change, cr, risk), null, 2));
|
|
158
|
+
}
|
|
160
159
|
return 0;
|
|
161
160
|
case "sync": {
|
|
162
161
|
const snapshot = rebuildSnapshot(projectRoot, change, cr, openspecStatus(projectRoot, change));
|
|
@@ -289,7 +288,7 @@ jobs 子命令:
|
|
|
289
288
|
}
|
|
290
289
|
}
|
|
291
290
|
default:
|
|
292
|
-
console.error(`未知命令:${command}。可用:status, transition, record, jobs`);
|
|
291
|
+
console.error(`未知命令:${command}。可用:status, transition, record, jobs, install, init, update, version`);
|
|
293
292
|
return 1;
|
|
294
293
|
}
|
|
295
294
|
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export declare const WORKFLOW_SKILLS: readonly ["superspec-explore", "superspec-propose", "superspec-apply", "superspec-review", "superspec-archive"];
|
|
2
|
+
export declare const WORKFLOW_PROMPTS: readonly ["architect.md", "code-reviewer.md", "critic.md", "executor.md", "explore.md", "final-audit.md", "proposal-auditor.md", "test-engineer.md", "test-runner.md", "verifier.md"];
|
|
3
|
+
export declare const WORKFLOW_AGENTS: readonly ["architect.toml", "code-reviewer.toml", "critic.toml", "executor.toml", "explore.toml", "final-audit.toml", "proposal-auditor.toml", "test-engineer.toml", "test-runner.toml", "verifier.toml"];
|
|
4
|
+
export interface InstallResult {
|
|
5
|
+
ok: boolean;
|
|
6
|
+
message: string;
|
|
7
|
+
installed: {
|
|
8
|
+
engine_dir: string;
|
|
9
|
+
skills: string[];
|
|
10
|
+
prompts: string[];
|
|
11
|
+
agents: string[];
|
|
12
|
+
config: string;
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
export interface InstallOptions {
|
|
16
|
+
templateRoot?: string;
|
|
17
|
+
}
|
|
18
|
+
export declare function installProject(projectRoot: string, options?: InstallOptions): InstallResult;
|
package/dist/install.js
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { copyFileSync, existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
export const WORKFLOW_SKILLS = [
|
|
4
|
+
"superspec-explore",
|
|
5
|
+
"superspec-propose",
|
|
6
|
+
"superspec-apply",
|
|
7
|
+
"superspec-review",
|
|
8
|
+
"superspec-archive",
|
|
9
|
+
];
|
|
10
|
+
export const WORKFLOW_PROMPTS = [
|
|
11
|
+
"architect.md",
|
|
12
|
+
"code-reviewer.md",
|
|
13
|
+
"critic.md",
|
|
14
|
+
"executor.md",
|
|
15
|
+
"explore.md",
|
|
16
|
+
"final-audit.md",
|
|
17
|
+
"proposal-auditor.md",
|
|
18
|
+
"test-engineer.md",
|
|
19
|
+
"test-runner.md",
|
|
20
|
+
"verifier.md",
|
|
21
|
+
];
|
|
22
|
+
export const WORKFLOW_AGENTS = [
|
|
23
|
+
"architect.toml",
|
|
24
|
+
"code-reviewer.toml",
|
|
25
|
+
"critic.toml",
|
|
26
|
+
"executor.toml",
|
|
27
|
+
"explore.toml",
|
|
28
|
+
"final-audit.toml",
|
|
29
|
+
"proposal-auditor.toml",
|
|
30
|
+
"test-engineer.toml",
|
|
31
|
+
"test-runner.toml",
|
|
32
|
+
"verifier.toml",
|
|
33
|
+
];
|
|
34
|
+
function defaultTemplateRoot() {
|
|
35
|
+
return join(import.meta.dirname, "..", "templates", "workflow");
|
|
36
|
+
}
|
|
37
|
+
function legacyStateFound(projectRoot) {
|
|
38
|
+
const changesRoot = join(projectRoot, "openspec", "changes");
|
|
39
|
+
if (!existsSync(changesRoot))
|
|
40
|
+
return false;
|
|
41
|
+
const oldStateFiles = ["superspec-state.json", "superspec-state.lock", "ledger.jsonl"];
|
|
42
|
+
return oldStateFiles.some(file => readdirSync(changesRoot).some(change => existsSync(join(changesRoot, change, ".superspec", file))));
|
|
43
|
+
}
|
|
44
|
+
function assertWorkflowTemplates(templateRoot) {
|
|
45
|
+
const missing = [];
|
|
46
|
+
for (const skill of WORKFLOW_SKILLS) {
|
|
47
|
+
const file = join(templateRoot, "skills", skill, "SKILL.md");
|
|
48
|
+
if (!existsSync(file))
|
|
49
|
+
missing.push(file);
|
|
50
|
+
}
|
|
51
|
+
for (const prompt of WORKFLOW_PROMPTS) {
|
|
52
|
+
const file = join(templateRoot, "prompts", prompt);
|
|
53
|
+
if (!existsSync(file))
|
|
54
|
+
missing.push(file);
|
|
55
|
+
}
|
|
56
|
+
for (const agent of WORKFLOW_AGENTS) {
|
|
57
|
+
const file = join(templateRoot, "agents", agent);
|
|
58
|
+
if (!existsSync(file))
|
|
59
|
+
missing.push(file);
|
|
60
|
+
}
|
|
61
|
+
if (missing.length > 0) {
|
|
62
|
+
throw new Error(`workflow templates missing: ${missing.join(", ")}`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
function copySkills(templateRoot, projectRoot) {
|
|
66
|
+
const skillsDest = join(projectRoot, ".codex", "skills");
|
|
67
|
+
const installedSkills = [];
|
|
68
|
+
for (const skill of WORKFLOW_SKILLS) {
|
|
69
|
+
const src = join(templateRoot, "skills", skill, "SKILL.md");
|
|
70
|
+
mkdirSync(join(skillsDest, skill), { recursive: true });
|
|
71
|
+
copyFileSync(src, join(skillsDest, skill, "SKILL.md"));
|
|
72
|
+
installedSkills.push(skill);
|
|
73
|
+
}
|
|
74
|
+
return installedSkills;
|
|
75
|
+
}
|
|
76
|
+
function copyPrompts(templateRoot, projectRoot) {
|
|
77
|
+
const promptsDest = join(projectRoot, ".codex", "prompts");
|
|
78
|
+
const installedPrompts = [];
|
|
79
|
+
for (const prompt of WORKFLOW_PROMPTS) {
|
|
80
|
+
const src = join(templateRoot, "prompts", prompt);
|
|
81
|
+
mkdirSync(promptsDest, { recursive: true });
|
|
82
|
+
copyFileSync(src, join(promptsDest, prompt));
|
|
83
|
+
installedPrompts.push(prompt);
|
|
84
|
+
}
|
|
85
|
+
return installedPrompts;
|
|
86
|
+
}
|
|
87
|
+
function copyAgents(templateRoot, projectRoot) {
|
|
88
|
+
const agentsDest = join(projectRoot, ".codex", "agents");
|
|
89
|
+
const installedAgents = [];
|
|
90
|
+
for (const agent of WORKFLOW_AGENTS) {
|
|
91
|
+
const src = join(templateRoot, "agents", agent);
|
|
92
|
+
mkdirSync(agentsDest, { recursive: true });
|
|
93
|
+
copyFileSync(src, join(agentsDest, agent));
|
|
94
|
+
installedAgents.push(agent);
|
|
95
|
+
}
|
|
96
|
+
return installedAgents;
|
|
97
|
+
}
|
|
98
|
+
function insertMissingTableEntries(content, table, entries) {
|
|
99
|
+
const tablePattern = new RegExp(`^\\[${table}\\]\\s*$`, "m");
|
|
100
|
+
const tableMatch = tablePattern.exec(content);
|
|
101
|
+
const lines = Object.entries(entries).map(([key, value]) => `${key} = ${value}`);
|
|
102
|
+
if (!tableMatch) {
|
|
103
|
+
const separator = content.trim().length === 0 ? "" : "\n\n";
|
|
104
|
+
return `${content.trimEnd()}${separator}[${table}]\n${lines.join("\n")}\n`;
|
|
105
|
+
}
|
|
106
|
+
const tableStart = tableMatch.index;
|
|
107
|
+
const headerEnd = tableStart + tableMatch[0].length;
|
|
108
|
+
const nextTable = /^\[[^\]]+\]\s*$/m.exec(content.slice(headerEnd));
|
|
109
|
+
const tableEnd = nextTable ? headerEnd + nextTable.index : content.length;
|
|
110
|
+
const tableBody = content.slice(headerEnd, tableEnd);
|
|
111
|
+
const missing = lines.filter(line => {
|
|
112
|
+
const key = line.split(" = ", 1)[0];
|
|
113
|
+
return !new RegExp(`^\\s*${key}\\s*=`, "m").test(tableBody);
|
|
114
|
+
});
|
|
115
|
+
if (missing.length === 0)
|
|
116
|
+
return content;
|
|
117
|
+
return `${content.slice(0, headerEnd)}\n${missing.join("\n")}${content.slice(headerEnd)}`;
|
|
118
|
+
}
|
|
119
|
+
function ensureCodexConfig(projectRoot) {
|
|
120
|
+
const codexDir = join(projectRoot, ".codex");
|
|
121
|
+
const configPath = join(codexDir, "config.toml");
|
|
122
|
+
mkdirSync(codexDir, { recursive: true });
|
|
123
|
+
const current = existsSync(configPath) ? readFileSync(configPath, "utf8") : "";
|
|
124
|
+
let next = insertMissingTableEntries(current, "features", {
|
|
125
|
+
multi_agent: "true",
|
|
126
|
+
child_agents_md: "true",
|
|
127
|
+
});
|
|
128
|
+
next = insertMissingTableEntries(next, "agents", {
|
|
129
|
+
max_threads: "12",
|
|
130
|
+
max_depth: "1",
|
|
131
|
+
});
|
|
132
|
+
if (next !== current)
|
|
133
|
+
writeFileSync(configPath, next);
|
|
134
|
+
return ".codex/config.toml";
|
|
135
|
+
}
|
|
136
|
+
export function installProject(projectRoot, options = {}) {
|
|
137
|
+
if (legacyStateFound(projectRoot)) {
|
|
138
|
+
throw new Error("检测到老版 SuperSpec (0.x) 的状态文件。\n" +
|
|
139
|
+
"SuperSpec 0.1.16-alpha 是全新引擎,不兼容 0.x 的状态格式。\n" +
|
|
140
|
+
"请先用老版(0.1.x)完成或归档现有 change,再安装 0.1.16-alpha。\n" +
|
|
141
|
+
"或在全新项目目录中安装。");
|
|
142
|
+
}
|
|
143
|
+
const templateRoot = options.templateRoot ?? defaultTemplateRoot();
|
|
144
|
+
assertWorkflowTemplates(templateRoot);
|
|
145
|
+
const engineDir = join(projectRoot, ".superspec");
|
|
146
|
+
mkdirSync(join(engineDir, "changes"), { recursive: true });
|
|
147
|
+
const gitignorePath = join(engineDir, ".gitignore");
|
|
148
|
+
if (!existsSync(gitignorePath))
|
|
149
|
+
writeFileSync(gitignorePath, "changes/\n*.log\n*.tmp\n");
|
|
150
|
+
return {
|
|
151
|
+
ok: true,
|
|
152
|
+
message: "SuperSpec 0.1.16-alpha 已安装",
|
|
153
|
+
installed: {
|
|
154
|
+
engine_dir: ".superspec/",
|
|
155
|
+
skills: copySkills(templateRoot, projectRoot),
|
|
156
|
+
prompts: copyPrompts(templateRoot, projectRoot),
|
|
157
|
+
agents: copyAgents(templateRoot, projectRoot),
|
|
158
|
+
config: ensureCodexConfig(projectRoot),
|
|
159
|
+
},
|
|
160
|
+
};
|
|
161
|
+
}
|
package/dist/next.js
CHANGED
|
@@ -46,7 +46,7 @@ export function next(projectRoot, change, changeRoot, defaultRisk = "normal") {
|
|
|
46
46
|
return {
|
|
47
47
|
state: "explore",
|
|
48
48
|
path: "next_command",
|
|
49
|
-
next_command: transitionCommand(change, "explore"),
|
|
49
|
+
next_command: transitionCommand(change, "explore", `--risk ${defaultRisk}`),
|
|
50
50
|
reason: "探索完成,推进到计划阶段",
|
|
51
51
|
missing_inputs: [],
|
|
52
52
|
};
|
package/dist/record.js
CHANGED
|
@@ -2,6 +2,37 @@
|
|
|
2
2
|
import { readFileSync, existsSync } from "node:fs";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { ensureChangeLayout, readEvents, appendEvent, makeEvent, sha256File, withLock, } from "./store.js";
|
|
5
|
+
const REVIEW_REPORT_REQUIRED_FIELDS = ["role", "verdict", "findings"];
|
|
6
|
+
const REVIEW_REPORT_OPTIONAL_FIELDS = ["summary", "evidence_refs", "risks", "open_questions"];
|
|
7
|
+
function recommendedAgentForRole(role) {
|
|
8
|
+
switch (role) {
|
|
9
|
+
case "proposal-auditor": return "proposal-auditor";
|
|
10
|
+
case "critic": return "critic";
|
|
11
|
+
case "architect": return "architect";
|
|
12
|
+
case "test-engineer": return "test-engineer";
|
|
13
|
+
case "final-audit": return "final-audit";
|
|
14
|
+
case "executor": return "executor";
|
|
15
|
+
case "test-run": return "test-runner";
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
function roleDescription(role) {
|
|
19
|
+
switch (role) {
|
|
20
|
+
case "proposal-auditor":
|
|
21
|
+
return "审查 proposal/tasks/design/discovery/business-invariants/test-contract 是否足够进入实现计划门";
|
|
22
|
+
case "critic":
|
|
23
|
+
return "从反方角度审查需求澄清或计划材料中的隐藏假设、范围漂移、验收漏洞和证据缺口";
|
|
24
|
+
case "architect":
|
|
25
|
+
return "审查架构边界、接口契约、长期维护风险和设计取舍";
|
|
26
|
+
case "test-engineer":
|
|
27
|
+
return "审查测试契约、覆盖策略、RED/GREEN 可信度和验收场景映射";
|
|
28
|
+
case "final-audit":
|
|
29
|
+
return "最终审查 proposal、实现状态、任务完成、测试契约和 SuperSpec 证据一致性";
|
|
30
|
+
case "executor":
|
|
31
|
+
return "执行受限实现工作项";
|
|
32
|
+
case "test-run":
|
|
33
|
+
return "执行受限测试工作项";
|
|
34
|
+
}
|
|
35
|
+
}
|
|
5
36
|
/** 从 events 中查找 job(H4 修复:job 只在 transition_commit 的 new_jobs payload 里) */
|
|
6
37
|
function findJob(events, jobId) {
|
|
7
38
|
for (const ev of events) {
|
|
@@ -65,15 +96,34 @@ export function recordJobSubmit(projectRoot, change, changeRoot, jobId, reportFi
|
|
|
65
96
|
const reportDigest = sha256File(reportFile) ?? "sha256:unknown";
|
|
66
97
|
// acceptance checks
|
|
67
98
|
const checks = [];
|
|
68
|
-
// 0.
|
|
99
|
+
// 0. 报告格式和角色匹配(最小 JSON contract)
|
|
69
100
|
try {
|
|
70
101
|
const report = JSON.parse(reportContent);
|
|
71
|
-
if (report
|
|
72
|
-
checks.push(
|
|
102
|
+
if (!report || typeof report !== "object" || Array.isArray(report)) {
|
|
103
|
+
checks.push("报告必须是 JSON object");
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
const obj = report;
|
|
107
|
+
for (const field of REVIEW_REPORT_REQUIRED_FIELDS) {
|
|
108
|
+
if (!(field in obj))
|
|
109
|
+
checks.push(`报告缺少必填字段 ${field}`);
|
|
110
|
+
}
|
|
111
|
+
if (obj.role !== job.role) {
|
|
112
|
+
checks.push(`报告角色 ${String(obj.role)} 与工作项角色 ${job.role} 不匹配`);
|
|
113
|
+
}
|
|
114
|
+
if (obj.verdict !== "pass" && obj.verdict !== "fail") {
|
|
115
|
+
checks.push("报告 verdict 必须是 pass 或 fail");
|
|
116
|
+
}
|
|
117
|
+
if (!Array.isArray(obj.findings)) {
|
|
118
|
+
checks.push("报告 findings 必须是数组");
|
|
119
|
+
}
|
|
120
|
+
if (obj.verdict === "fail") {
|
|
121
|
+
checks.push("报告 verdict=fail,工作项未通过");
|
|
122
|
+
}
|
|
73
123
|
}
|
|
74
124
|
}
|
|
75
125
|
catch {
|
|
76
|
-
|
|
126
|
+
checks.push("报告必须是有效 JSON");
|
|
77
127
|
}
|
|
78
128
|
// 1. boundFiles 仍匹配当前文档(missing 也算不匹配)
|
|
79
129
|
for (const bf of job.boundFiles) {
|
|
@@ -199,10 +249,15 @@ export function jobsPacket(projectRoot, change, jobId) {
|
|
|
199
249
|
packet: {
|
|
200
250
|
job_id: job.job_id,
|
|
201
251
|
role: job.role,
|
|
252
|
+
recommended_agent: recommendedAgentForRole(job.role),
|
|
202
253
|
boundFiles: job.boundFiles,
|
|
203
254
|
packet_digest: job.packet_digest,
|
|
204
|
-
required_output_kind: "
|
|
205
|
-
|
|
255
|
+
required_output_kind: "job_report_json",
|
|
256
|
+
output_contract_fields: [...REVIEW_REPORT_REQUIRED_FIELDS],
|
|
257
|
+
output_contract_optional_fields: [...REVIEW_REPORT_OPTIONAL_FIELDS],
|
|
258
|
+
output_instructions: `${roleDescription(job.role)}。请审查 ${job.boundFiles.map(f => f.path).join(", ")},` +
|
|
259
|
+
`产出 JSON 报告文件并通过 superspec record job-submit 登记。` +
|
|
260
|
+
`最小格式:{"role":"${job.role}","verdict":"pass|fail","findings":[]}`,
|
|
206
261
|
stop_conditions: ["审查完成后提交报告,不要修改文档"],
|
|
207
262
|
created_from_transition: job.created_from_transition,
|
|
208
263
|
},
|
package/dist/transition.d.ts
CHANGED
|
@@ -24,7 +24,7 @@ export declare function commitTransition(projectRoot: string, change: string, ch
|
|
|
24
24
|
}): TransitionResult;
|
|
25
25
|
export declare function proposeReady(projectRoot: string, change: string, changeRoot: string, risk?: "minimal" | "normal" | "strict"): TransitionResult;
|
|
26
26
|
export declare function transitionInit(projectRoot: string, change: string, changeRoot: string): TransitionResult;
|
|
27
|
-
export declare function transitionExplore(projectRoot: string, change: string, changeRoot: string): TransitionResult;
|
|
27
|
+
export declare function transitionExplore(projectRoot: string, change: string, changeRoot: string, risk?: "minimal" | "normal" | "strict"): TransitionResult;
|
|
28
28
|
export declare function startApply(projectRoot: string, change: string, changeRoot: string): TransitionResult;
|
|
29
29
|
export declare function taskStart(projectRoot: string, change: string, changeRoot: string, taskId: string): TransitionResult;
|
|
30
30
|
export declare function reviewReady(projectRoot: string, change: string, changeRoot: string, risk?: "minimal" | "normal" | "strict"): TransitionResult;
|
package/dist/transition.js
CHANGED
|
@@ -12,12 +12,12 @@ const TRANSITION_REQUIREMENTS = {
|
|
|
12
12
|
"propose-ready": {
|
|
13
13
|
minimal: [],
|
|
14
14
|
normal: ["proposal-auditor"],
|
|
15
|
-
strict: ["
|
|
15
|
+
strict: ["proposal-auditor", "critic", "architect", "test-engineer"],
|
|
16
16
|
},
|
|
17
17
|
"explore": {
|
|
18
18
|
minimal: [],
|
|
19
19
|
normal: [],
|
|
20
|
-
strict: ["
|
|
20
|
+
strict: ["critic"],
|
|
21
21
|
},
|
|
22
22
|
};
|
|
23
23
|
/**
|
|
@@ -172,9 +172,9 @@ export function transitionInit(projectRoot, change, changeRoot) {
|
|
|
172
172
|
});
|
|
173
173
|
}
|
|
174
174
|
// ===== explore =====
|
|
175
|
-
export function transitionExplore(projectRoot, change, changeRoot) {
|
|
175
|
+
export function transitionExplore(projectRoot, change, changeRoot, risk = "normal") {
|
|
176
176
|
return commitTransition(projectRoot, change, changeRoot, {
|
|
177
|
-
name: "explore", idempotencyInputs: { phase: "explore" },
|
|
177
|
+
name: "explore", idempotencyInputs: { phase: "explore", risk },
|
|
178
178
|
decide: (snapshot) => {
|
|
179
179
|
if (snapshot.state === "init")
|
|
180
180
|
return { fromState: "init", toState: "explore", outcome: "advanced", reason: "进入探索阶段" };
|
|
@@ -182,12 +182,12 @@ export function transitionExplore(projectRoot, change, changeRoot) {
|
|
|
182
182
|
const discoveryPath = join(changeRoot, ".superspec", "artifacts", "discovery.md");
|
|
183
183
|
if (!existsSync(discoveryPath))
|
|
184
184
|
return { skip: true, message: "discovery.md 不存在" };
|
|
185
|
-
// explore→propose:校验 discovery +
|
|
185
|
+
// explore→propose:校验 discovery + strict 模式下的 critic 审查
|
|
186
186
|
const discoveryCheck = validateDiscovery(changeRoot);
|
|
187
187
|
if (!discoveryCheck.ok)
|
|
188
188
|
return { skip: true, message: discoveryCheck.message };
|
|
189
189
|
// 通用 job 审查(和 propose-ready 同一个 helper)
|
|
190
|
-
const requiredRoles = TRANSITION_REQUIREMENTS["explore"]?.[
|
|
190
|
+
const requiredRoles = TRANSITION_REQUIREMENTS["explore"]?.[risk] ?? [];
|
|
191
191
|
const reviewResult = checkOrCreateReviewJobs(snapshot, requiredRoles, changeRoot, change, "explore", [".superspec/artifacts/discovery.md"]);
|
|
192
192
|
if (reviewResult)
|
|
193
193
|
return reviewResult;
|
package/dist/types.d.ts
CHANGED
|
@@ -5,7 +5,7 @@ export type Ref = {
|
|
|
5
5
|
sha: string;
|
|
6
6
|
};
|
|
7
7
|
export type JobState = "requested" | "accepted" | "rejected";
|
|
8
|
-
export type JobRole = "proposal-auditor" | "
|
|
8
|
+
export type JobRole = "proposal-auditor" | "critic" | "architect" | "test-engineer" | "executor" | "test-run" | "final-audit";
|
|
9
9
|
export interface Job {
|
|
10
10
|
job_id: string;
|
|
11
11
|
role: JobRole;
|
|
@@ -18,9 +18,12 @@ export interface Job {
|
|
|
18
18
|
export interface JobPacket {
|
|
19
19
|
job_id: string;
|
|
20
20
|
role: JobRole;
|
|
21
|
+
recommended_agent?: string;
|
|
21
22
|
boundFiles: Ref[];
|
|
22
23
|
packet_digest: string;
|
|
23
24
|
required_output_kind: string;
|
|
25
|
+
output_contract_fields?: string[];
|
|
26
|
+
output_contract_optional_fields?: string[];
|
|
24
27
|
stop_conditions: string[];
|
|
25
28
|
created_from_transition: string;
|
|
26
29
|
}
|
package/package.json
CHANGED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# SuperSpec Codex agent: architect
|
|
2
|
+
name = "architect"
|
|
3
|
+
description = "System design, boundaries, interfaces, long-horizon tradeoffs"
|
|
4
|
+
model_reasoning_effort = "high"
|
|
5
|
+
developer_instructions = """
|
|
6
|
+
Role: Architect. Review system boundaries, interface contracts, data flow, maintenance risk, rollback risk, and design tradeoffs.
|
|
7
|
+
|
|
8
|
+
Prompt binding: load `.codex/prompts/architect.md` first, then read the provided `review-packet`, job packet, or `prompt_ref`. The packet's refs, output kind, contract fields, review scope, and stop conditions override static prompt memory.
|
|
9
|
+
|
|
10
|
+
Boundary: read-only. Do not edit files or judge materials you have not opened. Report missing context upward instead of guessing.
|
|
11
|
+
|
|
12
|
+
Output: concise Simplified Chinese. For `job_report_json`, submit JSON with `role:"architect"`, `verdict`, and `findings`. Otherwise put the conclusion first, cite file:line evidence, and write `无阻塞问题` when no blocking issue is found.
|
|
13
|
+
"""
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# SuperSpec Codex agent: code-reviewer
|
|
2
|
+
name = "code-reviewer"
|
|
3
|
+
description = "Comprehensive review across all concerns"
|
|
4
|
+
model_reasoning_effort = "high"
|
|
5
|
+
developer_instructions = """
|
|
6
|
+
Role: Code Reviewer. Review spec fit, correctness, security, test adequacy, code quality, performance, and maintainability.
|
|
7
|
+
|
|
8
|
+
Prompt binding: load `.codex/prompts/code-reviewer.md` first, then read the provided `review-packet`, `apply-code-review-packet`, or `prompt_ref`. The packet's refs, output kind, contract fields, review scope, declared write scope, executor report refs, and stop conditions override static prompt memory.
|
|
9
|
+
|
|
10
|
+
Boundary: read-only. Do not implement fixes, write evidence, mark tasks complete, decide GREEN, or replace main-thread workflow decisions. Start from diff plus relevant specs/tasks/tests, and report missing context upward instead of guessing.
|
|
11
|
+
|
|
12
|
+
Output: concise Simplified Chinese. Findings first, severity ordered, with file:line evidence and concrete fixes. Write `无阻塞问题` when no blocking issue is found.
|
|
13
|
+
"""
|