@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.
Files changed (35) hide show
  1. package/README.md +37 -36
  2. package/dist/cli.js +28 -29
  3. package/dist/install.d.ts +18 -0
  4. package/dist/install.js +161 -0
  5. package/dist/next.js +1 -1
  6. package/dist/record.js +61 -6
  7. package/dist/transition.d.ts +1 -1
  8. package/dist/transition.js +6 -6
  9. package/dist/types.d.ts +4 -1
  10. package/package.json +1 -1
  11. package/templates/workflow/agents/architect.toml +13 -0
  12. package/templates/workflow/agents/code-reviewer.toml +13 -0
  13. package/templates/workflow/agents/critic.toml +13 -0
  14. package/templates/workflow/agents/executor.toml +13 -0
  15. package/templates/workflow/agents/explore.toml +13 -0
  16. package/templates/workflow/agents/final-audit.toml +13 -0
  17. package/templates/workflow/agents/proposal-auditor.toml +13 -0
  18. package/templates/workflow/agents/test-engineer.toml +13 -0
  19. package/templates/workflow/agents/test-runner.toml +13 -0
  20. package/templates/workflow/agents/verifier.toml +13 -0
  21. package/templates/workflow/prompts/architect.md +43 -0
  22. package/templates/workflow/prompts/code-reviewer.md +34 -0
  23. package/templates/workflow/prompts/critic.md +46 -0
  24. package/templates/workflow/prompts/executor.md +32 -0
  25. package/templates/workflow/prompts/explore.md +27 -0
  26. package/templates/workflow/prompts/final-audit.md +32 -0
  27. package/templates/workflow/prompts/proposal-auditor.md +34 -0
  28. package/templates/workflow/prompts/test-engineer.md +44 -0
  29. package/templates/workflow/prompts/test-runner.md +35 -0
  30. package/templates/workflow/prompts/verifier.md +35 -0
  31. package/templates/workflow/skills/superspec-apply/SKILL.md +4 -14
  32. package/templates/workflow/skills/superspec-archive/SKILL.md +0 -7
  33. package/templates/workflow/skills/superspec-explore/SKILL.md +4 -9
  34. package/templates/workflow/skills/superspec-propose/SKILL.md +10 -11
  35. 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 init --scope project
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 init --scope project
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 init --scope project
204
+ superspec install
204
205
  ```
205
206
 
206
- 更新当前项目里的 SuperSpec 入口:
207
+ `superspec init --scope project` 是兼容别名,也会执行同一套安装逻辑。
207
208
 
208
- ```bash
209
- superspec update --scope project
210
- ```
211
-
212
- 这条命令会先通过 npm 更新全局 `@peterxiaoyang/superspec`,再用新版本更新当前项目里的入口文件。只想使用当前已安装包更新项目文件时,可以运行:
209
+ 检查当前项目的 OpenSpec 探测结果:
213
210
 
214
211
  ```bash
215
- superspec update --scope project --local-only
212
+ superspec status
216
213
  ```
217
214
 
218
- 卸载当前项目里的 SuperSpec 入口:
215
+ 更新当前 beta CLI:
219
216
 
220
217
  ```bash
221
- superspec uninstall --scope project
218
+ superspec update
222
219
  ```
223
220
 
224
- 这些命令默认不会删除已经生成的 `.superspec/` 过程记录。
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
- SuperSpec 内部还有一些检查命令,例如:
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 run pack:dry-run
265
+ npm pack --dry-run
264
266
  ```
265
267
 
266
268
  更多细节见:
267
269
 
268
- - `docs/SPEC.md`:完整设计和规则
269
- - `docs/DISTRIBUTION.md`:安装、升级、卸载和分发说明
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.15-alpha
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 [--global] 安装到项目或全局
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.15-alpha");
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
- const { mkdirSync, writeFileSync, existsSync, readdirSync, readFileSync } = await import("node:fs");
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: "检测到老版 SuperSpec (0.x) 的状态文件。\n" +
94
- "SuperSpec 0.1.15-alpha 是全新引擎,不兼容 0.x 的状态格式。\n" +
95
- "请先用老版(0.1.x)完成或归档现有 change,再安装 0.1.15-alpha。\n" +
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.15-alpha 是全新引擎,不能从 0.x 直接 update。\n" +
116
- "请用 npm install -g @peterxiaoyang/superspec@0.1.15-alpha 手动安装。\n" +
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.15-alpha" }));
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
- console.log(JSON.stringify(transitionExplore(projectRoot, change, cr), null, 2));
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;
@@ -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. 报告角色匹配(HIGH 5 修复)
99
+ // 0. 报告格式和角色匹配(最小 JSON contract)
69
100
  try {
70
101
  const report = JSON.parse(reportContent);
71
- if (report.role && report.role !== job.role) {
72
- checks.push(`报告角色 ${report.role} 与工作项角色 ${job.role} 不匹配`);
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
- // JSON 报告,Phase 1 允许(Phase 2 加正式 schema)
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: "report",
205
- output_instructions: `请审查 ${job.boundFiles.map(f => f.path).join(", ")},产出审查报告`,
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
  },
@@ -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;
@@ -12,12 +12,12 @@ const TRANSITION_REQUIREMENTS = {
12
12
  "propose-ready": {
13
13
  minimal: [],
14
14
  normal: ["proposal-auditor"],
15
- strict: ["critic-review", "architect-review", "test-engineer-review"],
15
+ strict: ["proposal-auditor", "critic", "architect", "test-engineer"],
16
16
  },
17
17
  "explore": {
18
18
  minimal: [],
19
19
  normal: [],
20
- strict: ["clarification-review"],
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 + 通用 job 审查(normal+ 需 clarification-review)
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"]?.["normal"] ?? [];
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" | "clarification-review" | "critic-review" | "architect-review" | "test-engineer-review" | "executor" | "test-run" | "final-audit";
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@peterxiaoyang/superspec",
3
- "version": "0.1.15-alpha",
3
+ "version": "0.1.16-alpha",
4
4
  "description": "SuperSpec 流程引擎 — transition engine with lightweight fact-sync",
5
5
  "type": "module",
6
6
  "engines": { "node": ">=20.19.0" },
@@ -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
+ """