@peterxiaoyang/superspec 0.1.52 → 0.1.53

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 CHANGED
@@ -4,194 +4,49 @@
4
4
  [![Node.js](https://img.shields.io/badge/node-%3E%3D20.19.0-brightgreen?style=flat-square)](https://nodejs.org)
5
5
  [![OpenSpec](https://img.shields.io/badge/OpenSpec-compatible-6f42c1?style=flat-square)](https://github.com/Fission-AI/OpenSpec)
6
6
 
7
- > SuperSpec 是一套“先想清楚,再动手”的工作流。
8
-
9
- 它解决的是一个很常见的问题:AI 编程工具写代码很快,但有时候还没搞清楚需求、现有代码和测试边界,就已经开始改文件了。
10
-
11
- SuperSpec 会把一次需求变更拆成 4 个阶段:
7
+ SuperSpec 是一套面向 AI 编程代理的需求变更工作流。它在 OpenSpec 材料之上增加阶段控制、审查和验证记录,帮助 AI 从需求理解推进到可回放的交付结果。
12
8
 
13
9
  ```text
14
- 探索需求 -> 写方案 -> 做实现 -> 代码审查与最终验证 -> accepted
10
+ Explore Propose Apply Review Accepted
15
11
  ```
16
12
 
17
- 这样做的目的很简单:
18
-
19
- - 改代码前先弄清楚现状
20
- - 写实现前先有方案和任务
21
- - 任务完成前先有测试或验证记录
22
- - 宣布完成前先经过审查
23
- - 进入 accepted 时保留关键过程记录
24
-
25
- ## 适合谁
26
-
27
- 适合已经在用 AI 编程工具或命令行代理做项目开发,并希望流程更稳一点的团队或个人。
28
-
29
- 如果你遇到过这些情况,SuperSpec 会有帮助:
13
+ SuperSpec 适合跨模块、涉及接口或需要方案审查的变更。简单的单文件修改不必强行使用完整工作流。普通请求也不会自动进入 SuperSpec,只有明确调用对应入口或继续已有 change 时才会启动。
30
14
 
31
- - 需求还没说清楚,AI 就开始写代码
32
- - 方案写得很粗,后面实现时靠猜
33
- - 测试只跑了命令,但没人说明它证明了什么
34
- - 代码写完后缺少真正的审查
35
- - 过几天想回看当时为什么这么改,却找不到过程记录
36
-
37
- 如果你只是想让 AI 快速改一个很小的文件,且不需要完整方案、审查和记录,那 SuperSpec 可能会显得偏重。
38
-
39
- ## 和 OpenSpec 有什么区别
40
-
41
- 一句话区别:
42
-
43
- ```text
44
- OpenSpec 管“这次要改什么”。
45
- SuperSpec 管“AI 应该怎样把这次改动做稳”。
46
- ```
15
+ ## 安装
47
16
 
48
- 更具体一点:
49
-
50
- | 问题 | OpenSpec 主要负责 | SuperSpec 额外补上 |
51
- |---|---|---|
52
- | 这次变更是什么 | 方案、规格、设计、任务和归档 | 要求 AI 在写方案前先调查现状 |
53
- | 方案怎么写 | 提供标准的变更文档结构 | 要求方案前后有范围、风险、业务约束和测试思路 |
54
- | 代码怎么做 | 记录任务清单和完成状态 | 要求按任务实现,并留下测试或验证记录 |
55
- | 做完怎么算稳 | 可以校验规格和归档 | 增加代码审查、架构审查、反方审查和最终验证 |
56
- | 以后怎么追溯 | 保留 OpenSpec 的变更文档 | 额外保留探索、测试和审查记录 |
57
-
58
- 举个例子:
59
-
60
- OpenSpec 会帮你记录“要增加登录功能、需要哪些规格、设计和任务”。
61
- SuperSpec 会进一步要求 AI 先看看现有登录/权限代码在哪里、哪些业务规则不能破坏、哪些场景必须测试、实现后要经过哪些审查,最终进入 accepted 完成本轮流程。
62
-
63
- 所以 SuperSpec 不是 OpenSpec 的替代品。它更像是 OpenSpec 外面的一层执行纪律,专门约束 AI 编程工具不要跳过关键步骤。
64
-
65
- ## 快速开始
66
-
67
- ### 1. 安装
17
+ 要求 Node.js `>=20.19.0`。
68
18
 
69
19
  ```bash
70
20
  npm install -g @peterxiaoyang/superspec@latest
71
- ```
72
-
73
- 需要 Node.js `>= 20.19.0`。
74
-
75
- ### 2. 初始化当前项目
76
-
77
- 进入你的项目根目录,然后运行:
78
-
79
- ```bash
21
+ cd <your-project>
80
22
  superspec install
81
23
  ```
82
24
 
83
- 这条命令的意思是:把 SuperSpec 当前可用的工作流入口安装到项目里。
84
- 安装前会检查全局 `openspec` CLI;缺失或版本不一致时,会自动执行 `npm install -g @fission-ai/openspec@1.4.1`,确保后续工作流能调用 OpenSpec。
85
- 当前 beta 会安装 `.superspec/` 引擎目录、`.codex/skills/superspec-*` 阶段入口、`.codex/prompts/*.md` 角色 prompt、`.codex/agents/*.toml` 子智能体配置,补齐 `.codex/config.toml` 的多 agent 开关,并在项目根 `AGENTS.md` 中维护 SuperSpec 轻量门禁片段。`superspec init --scope project` 仍作为兼容别名可用。
86
-
87
- Windows PowerShell 如果拦截 npm 的 `.ps1` 脚本,请改用:
88
-
89
- ```powershell
90
- superspec.cmd install
91
- ```
92
-
93
- ### 3. 按步骤使用
94
-
95
- 在你使用的 AI 编程工具或 CLI 里,按下面的阶段入口推进。不同工具的触发方式可以不同,但入口名和顺序保持一致。
96
-
97
- 开始时先探索需求:
98
-
99
- ```text
100
- 使用 superspec-explore,帮我梳理这个需求:……
101
- ```
102
-
103
- 探索完成后,写正式方案:
104
-
105
- ```text
106
- 使用 superspec-propose,把刚才的探索结果整理成方案。
107
- ```
108
-
109
- 方案确认后,开始实现:
110
-
111
- ```text
112
- 使用 superspec-apply,按任务实现。
113
- ```
114
-
115
- 实现完成后,代码审查与最终验证:
116
-
117
- ```text
118
- 使用 superspec-review,完成代码审查、问题处理和最终验证。
119
- ```
120
-
121
- 审查通过并进入 `accepted` 后,本轮工作流完成。如果之后需要补充或修改需求、方案或验收内容,直接用自然语言告诉 Agent;Agent 会按引擎返回的内部 continuation 自动回到计划阶段并重新完成后续审查。
25
+ `superspec install` 会把工作流入口、角色配置和运行时目录同步到当前项目,并准备 OpenSpec 依赖。`superspec init --scope project` 是兼容别名。
122
26
 
123
- ## 四个入口分别做什么
124
-
125
- | 入口 | 什么时候用 | 它会要求做什么 |
126
- |---|---|---|
127
- | `superspec-explore` | 需求刚开始时 | 读代码、查现状、整理范围和风险;这一步不改业务代码 |
128
- | `superspec-propose` | 需求已经清楚后 | 写正式方案、规格、设计和任务,并提前规划测试 |
129
- | `superspec-apply` | 方案通过后 | 按任务实现代码,记录测试或验证结果 |
130
- | `superspec-review` | 实现完成后 | 检查代码实现是否符合方案,处理审查问题,并完成最终验证 |
131
-
132
- 你日常主要记住这四个入口就够了。
133
-
134
- 工作流档位由项目配置统一控制,不通过命令行临时指定。探索阶段会按配置创建相应审查工作项;计划阶段按同一档位创建 `critic`、`architect`、`test-engineer` 等必要审查后再进入实现准备。
135
-
136
- ## 它会多保存哪些记录
137
-
138
- SuperSpec 会在每次变更下面保存一些辅助记录,方便后续追溯。
139
-
140
- 主要包括:
141
-
142
- - 探索记录:这次需求是什么、当前代码是什么情况、有哪些风险
143
- - 业务约束:哪些业务规则不能被改坏
144
- - 测试约定:哪些场景必须验证
145
- - 实现记录:每个任务怎么验证通过
146
- - 审查记录:谁检查了什么、发现了什么、最后为什么通过或退回
147
-
148
- 这些记录默认放在:
149
-
150
- ```text
151
- openspec/changes/<变更ID>/.superspec/
152
- ```
153
-
154
- 这里的 `<变更ID>` 就是一次需求变更的名字。
155
-
156
- ## 重要边界
157
-
158
- SuperSpec 能让流程更规范,但它不是安全锁。
159
-
160
- 它能帮助你:
161
-
162
- - 减少 AI 还没想清楚就改代码的情况
163
- - 让测试、审查和用户确认留下记录
164
- - 在进入下一步前提醒缺少什么
165
- - 让一次变更之后更容易回看原因
166
-
167
- 它不能保证:
168
-
169
- - 阻止人手动绕过流程直接改文件
170
- - 阻止人删除过程记录
171
- - 阻止恶意伪造记录
172
- - 替代正式的安全审计、合规审计或法律证明
173
-
174
- 也就是说,SuperSpec 目前是“流程纪律 + 审计辅助工具”,不是“强制安全系统”。默认 hook 只增强子智能体活动的可见性;显式/manual `PreToolUse` 才会进入保守写入策略,但仍然不能替代正式的安全控制。
175
-
176
- ## 常用命令
177
-
178
- 查看当前 SuperSpec CLI 版本:
27
+ 检查安装:
179
28
 
180
29
  ```bash
181
- superspec --version
30
+ superspec version
31
+ superspec status
182
32
  ```
183
33
 
184
- 安装到当前项目:
34
+ ## 工作流入口
185
35
 
186
- ```bash
187
- superspec install
188
- ```
36
+ 在 Codex 中显式调用对应 Skill。新需求通常从 `$superspec-explore` 开始;已有 change 则从当前阶段继续。
37
+
38
+ | Skill | 作用 |
39
+ | --- | --- |
40
+ | `$superspec-explore` | 调查现状,确认事实和待决策事项 |
41
+ | `$superspec-propose` | 生成规格、设计和可执行任务 |
42
+ | `$superspec-apply` | 按已批准任务修改代码并验证 |
43
+ | `$superspec-review` | 审查实现并完成最终验证 |
189
44
 
190
- `superspec init --scope project` 是兼容别名,也会执行同一套安装逻辑。
45
+ 工作流会根据问题性质留在当前阶段修复,或回到计划阶段重新确认需求、验收和技术取舍。
191
46
 
192
- `superspec install` 会创建缺失的 `openspec/config.yaml`,或在没有顶层 `context` 时追加这段官方中文 context。如果文件已经有顶层 `context`,SuperSpec 不会覆盖它。
47
+ ## 配置
193
48
 
194
- 它还会创建项目级 `.superspec/config.json`,用于统一设置整个工作流的默认档位:
49
+ 项目级配置位于 `.superspec/config.json`。未创建配置或未声明模式时,默认使用 `normal`:
195
50
 
196
51
  ```json
197
52
  {
@@ -201,75 +56,21 @@ superspec install
201
56
  }
202
57
  ```
203
58
 
204
- 可选值为 `minimal`、`normal`、`strict`。`next`、Explore、Propose、进入 Apply Review 都读取此配置;`--risk` 不再是用户可用的工作流入口。已启动的 task attempt 和已经建立的审查策略仍按其事件快照执行,不会被中途改配置追溯改写。
205
-
206
- 可以用下面的命令检查生成的 instructions 是否包含语言上下文:
207
-
208
- ```bash
209
- openspec instructions proposal --change <change>
210
- ```
211
-
212
- 检查当前项目的 OpenSpec 探测结果:
213
-
214
- ```bash
215
- superspec status
216
- ```
217
-
218
- 同步当前项目的 SuperSpec 工作流入口:
219
-
220
- ```bash
221
- superspec update
222
- ```
223
-
224
- 这条命令会先检查 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 轻量门禁片段。
59
+ 可选模式为 `minimal`、`normal` 和 `strict`。模式主要影响计划阶段的审查强度;最终代码审查和验证仍由 Review 阶段负责。
225
60
 
226
- ## 进阶信息
61
+ ## 与 OpenSpec 的关系
227
62
 
228
- 以当前内置的 Codex 适配器为例,初始化后项目里会出现这些入口文件:
63
+ OpenSpec 负责变更材料和规格结构;SuperSpec 负责组织 AI 的探索、计划、实现、审查与证据记录。两者互补:
229
64
 
230
65
  ```text
231
- .codex/
232
- skills/superspec-explore/
233
- skills/superspec-propose/
234
- skills/superspec-apply/
235
- skills/superspec-review/
236
- prompts/architect.md
237
- prompts/code-reviewer.md
238
- prompts/critic.md
239
- prompts/executor.md
240
- prompts/explore.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/test-engineer.toml
250
- agents/test-runner.toml
251
- agents/verifier.toml
252
- config.toml
253
- ```
254
-
255
- 当前 beta 的阶段入口由 `superspec transition next --change <变更ID>` 驱动,不再提供旧版 `superspec check ...` surface。
256
-
257
- 如果你要开发 SuperSpec 本身:
258
-
259
- ```bash
260
- npm run build
261
- npm run typecheck
262
- npm test
263
- npm pack --dry-run
66
+ OpenSpec:这次要改变什么
67
+ SuperSpec:如何在边界内把它交付
264
68
  ```
265
69
 
266
- 更多细节见:
70
+ ## 设计边界
267
71
 
268
- - `docs/plans/SUPERSPEC_TRANSITION_ENGINE_SPEC_LITE.md`:transition engine 设计
269
- - `templates/workflow/skills/superspec-*/SKILL.md`:当前 Codex 适配器使用的阶段入口说明
72
+ SuperSpec 是流程和审计辅助工具,不是安全隔离或发布审批系统。它不能替代代码审计、权限控制、合规检查和人工判断。
270
73
 
271
- ## 致谢与灵感来源
74
+ ## 致谢
272
75
 
273
76
  - [OpenSpec](https://github.com/Fission-AI/OpenSpec)
274
- - [oh-my-codex](https://github.com/Yeachan-Heo/oh-my-codex)
275
- - [Superpowers](https://github.com/obra/superpowers)
package/dist/format.js CHANGED
@@ -799,14 +799,18 @@ export function pendingTasksInContent(content) {
799
799
  }
800
800
  /** 在 tasks.md 中按 taskId 精确查找任务(词边界,不误判子串) */
801
801
  export function findTaskInLines(lines, taskId) {
802
+ const escaped = taskId.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
803
+ // 回退匹配也必须停留在顶格 checkbox 任务行内;否则上一任务的
804
+ // “依赖/边界/验收”文本提到该 ID 时,会被误当成目标任务行。
805
+ const taskIdAtLineStart = new RegExp(`^${escaped}(?=\\s|$|[.,:;!?)\\]])`);
802
806
  for (let i = 0; i < lines.length; i++) {
803
807
  // 精确匹配行内的 taskId token
804
808
  const m = lines[i].match(TASK_LINE_RE);
805
809
  if (m && m[3] === taskId)
806
810
  return i;
807
- // 回退:用转义正则匹配(兼容 taskId 后跟标点的情况)
808
- const escaped = taskId.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
809
- if (new RegExp("(?:^|\\s)" + escaped + "(?:\\s|$|[.,:;!?)\\]])").test(lines[i]))
811
+ // 回退:兼容 taskId 后跟标点的任务行,但不扫描普通说明文本。
812
+ const taskLine = lines[i].match(/^-\s+\[[ xX]\]\s+(.+)$/);
813
+ if (taskLine && taskIdAtLineStart.test(taskLine[1]))
810
814
  return i;
811
815
  }
812
816
  return -1;
package/dist/install.js CHANGED
@@ -253,7 +253,6 @@ function ensureWorkflowConfig(projectRoot) {
253
253
  mkdirSync(dirname(configPath), { recursive: true });
254
254
  if (!existsSync(configPath)) {
255
255
  // install/update 是显式迁移动作:为以后各轮写入 normal 默认值。
256
- // 未执行安装的旧项目仍由 workflowRiskForProject 保守回放 strict。
257
256
  writeFileSync(configPath, JSON.stringify({ workflow: { mode: DEFAULT_WORKFLOW_RISK } }, null, 2) + "\n");
258
257
  }
259
258
  return WORKFLOW_CONFIG_PATH;
@@ -245,20 +245,21 @@ function buildActions(change, boundary, scope, question, specs, risk) {
245
245
  export function isPhaseConfirmationScope(value) {
246
246
  return typeof value === "string" && value.startsWith(PHASE_CONFIRMATION_SCOPE_PREFIX);
247
247
  }
248
- export function phaseConfirmationForBoundary(projectRoot, events, snapshot, boundary, risk = "strict") {
248
+ export function phaseConfirmationForBoundary(projectRoot, events, snapshot, boundary, risk) {
249
249
  const spec = SPECS[boundary];
250
250
  if (snapshot.state !== spec.state)
251
251
  return null;
252
+ const resolvedRisk = risk ?? workflowRiskForPhaseConfirmation(projectRoot, events, snapshot);
252
253
  const epoch = spec.epoch(events);
253
254
  const epochEventId = epoch?.event_id ?? `legacy-${spec.state}`;
254
- const digest = materialDigest(projectRoot, events, snapshot, boundary, risk);
255
+ const digest = materialDigest(projectRoot, events, snapshot, boundary, resolvedRisk);
255
256
  const scope = `${spec.scopePrefix}:${epochEventId}:${digest}`;
256
257
  const summary = boundary === "propose_to_apply"
257
258
  ? proposeTaskDeliverySummary(openspecChangeRoot(projectRoot, snapshot.change_id))
258
259
  : null;
259
260
  const boundaryQuestion = `${spec.question}\n\n${CURRENT_USER_DECISION_NOTICE}`;
260
261
  const question = summary ? `${summary}\n\n${boundaryQuestion}` : boundaryQuestion;
261
- const actions = buildActions(snapshot.change_id, boundary, scope, question, spec.actions, risk);
262
+ const actions = buildActions(snapshot.change_id, boundary, scope, question, spec.actions, resolvedRisk);
262
263
  return {
263
264
  boundary,
264
265
  epoch_event_id: epochEventId,
@@ -273,11 +274,12 @@ export function phaseConfirmationForBoundary(projectRoot, events, snapshot, boun
273
274
  },
274
275
  };
275
276
  }
276
- export function phaseConfirmationForCurrentState(projectRoot, events, snapshot, risk = "strict") {
277
+ export function phaseConfirmationForCurrentState(projectRoot, events, snapshot, risk) {
277
278
  const boundary = boundaryForState(snapshot.state);
278
- return boundary
279
- ? phaseConfirmationForBoundary(projectRoot, events, snapshot, boundary, risk)
280
- : null;
279
+ if (!boundary)
280
+ return null;
281
+ const resolvedRisk = risk ?? workflowRiskForPhaseConfirmation(projectRoot, events, snapshot);
282
+ return phaseConfirmationForBoundary(projectRoot, events, snapshot, boundary, resolvedRisk);
281
283
  }
282
284
  export function phaseActionForAnswer(confirmation, answer) {
283
285
  return typeof answer === "string"
@@ -1376,6 +1376,13 @@ export function taskComplete(projectRoot, change, changeRoot, taskId, inputConte
1376
1376
  if (applyPlanningMaterialsChanged(changeRoot, events)) {
1377
1377
  return { skip: true, message: "Apply 期间计划材料已变化,不能完成当前任务;请回到 Propose 核对并重新批准计划" };
1378
1378
  }
1379
+ // task_completed 与 tasks.md 的完成标记必须作为一个可重试的提交边界;
1380
+ // 先确认目标仍是实际 checkbox 任务,避免契约模式只凭 attempt 记录完成事件。
1381
+ const taskLines = readFileSync(join(changeRoot, "tasks.md"), "utf8").split("\n");
1382
+ const taskLine = findTaskLine(taskLines, taskId);
1383
+ if (taskLine < 0) {
1384
+ return { skip: true, message: `tasks.md 中找不到任务 ${taskId},未登记完成事件` };
1385
+ }
1379
1386
  const readiness = taskEvidenceReadiness(projectRoot, change, changeRoot, attempt);
1380
1387
  if (!readiness.ready)
1381
1388
  return { skip: true, message: `任务 ${taskId} 无法完成:${readiness.reason}` };
@@ -1393,24 +1400,23 @@ export function taskComplete(projectRoot, change, changeRoot, taskId, inputConte
1393
1400
  reason: `任务 ${taskId} 完成`,
1394
1401
  extraEvents: [{ type: "task_completed", payload: completedPayload }],
1395
1402
  postCommit: (pr, _ch, cr) => {
1396
- completedPayload.java_staging = stageProductionJavaFilesSince(pr, taskStartBoundary);
1397
1403
  const lines = readFileSync(join(cr, "tasks.md"), "utf8").split("\n");
1398
1404
  const idx = findTaskLine(lines, taskId);
1399
1405
  if (idx < 0) {
1400
- completedPayload.checkbox_update = { status: "failed", reason: `找不到 ${taskId} 的任务行` };
1401
- return;
1406
+ throw new Error(`任务 ${taskId} 完成失败:tasks.md 中找不到任务行,未写入完成事件`);
1402
1407
  }
1403
1408
  if (lines[idx].match(/- \[[xX]\]/)) {
1404
1409
  completedPayload.checkbox_update = { status: "applied" };
1405
- return;
1406
1410
  }
1407
- if (!lines[idx].match(/- \[ \]/)) {
1408
- completedPayload.checkbox_update = { status: "failed", reason: `找不到 ${taskId} 的未完成复选框` };
1409
- return;
1411
+ else {
1412
+ if (!lines[idx].match(/- \[ \]/)) {
1413
+ throw new Error(`任务 ${taskId} 完成失败:tasks.md 中找不到可勾选复选框,未写入完成事件`);
1414
+ }
1415
+ lines[idx] = lines[idx].replace(/- \[ \]/, "- [x]");
1416
+ writeFileSync(join(cr, "tasks.md"), lines.join("\n"));
1417
+ completedPayload.checkbox_update = { status: "applied" };
1410
1418
  }
1411
- lines[idx] = lines[idx].replace(/- \[ \]/, "- [x]");
1412
- writeFileSync(join(cr, "tasks.md"), lines.join("\n"));
1413
- completedPayload.checkbox_update = { status: "applied" };
1419
+ completedPayload.java_staging = stageProductionJavaFilesSince(pr, taskStartBoundary);
1414
1420
  },
1415
1421
  };
1416
1422
  },
@@ -1,7 +1,7 @@
1
1
  import type { ReviewRisk } from "./review.ts";
2
2
  import type { Event, State } from "./types.ts";
3
3
  export declare const WORKFLOW_CONFIG_PATH = ".superspec/config.json";
4
- /** 新安装项目写入配置时采用的默认档位。 */
4
+ /** 项目未声明 workflow.mode 时采用的默认档位。 */
5
5
  export declare const DEFAULT_WORKFLOW_RISK: ReviewRisk;
6
6
  export declare class WorkflowConfigError extends Error {
7
7
  constructor(message: string);
@@ -18,7 +18,7 @@ export declare function workflowRiskForApplyRound(events: Event[], fallback: Rev
18
18
  /** 供阶段确认和登记共用,避免任何调用者从 JSON/CLI 注入本轮 mode。 */
19
19
  export declare function workflowRiskForState(events: Event[], state: State, fallback: ReviewRisk): ReviewRisk;
20
20
  /**
21
- * 读取项目默认模式。新安装项目由配置提供 normal;缺少配置的旧项目保留 strict
21
+ * 读取项目默认模式。缺少配置或缺少 workflow.mode 时使用 normal。
22
22
  * 配置格式:{ "workflow": { "mode": "normal" } }
23
23
  */
24
24
  export declare function workflowRiskForProject(projectRoot: string): ReviewRisk;
@@ -3,10 +3,8 @@
3
3
  import { existsSync, readFileSync } from "node:fs";
4
4
  import { join } from "node:path";
5
5
  export const WORKFLOW_CONFIG_PATH = ".superspec/config.json";
6
- /** 新安装项目写入配置时采用的默认档位。 */
6
+ /** 项目未声明 workflow.mode 时采用的默认档位。 */
7
7
  export const DEFAULT_WORKFLOW_RISK = "normal";
8
- /** 未安装项目继续沿用历史 strict 默认,避免无配置的旧项目静默放宽。 */
9
- const LEGACY_WORKFLOW_RISK = "strict";
10
8
  export class WorkflowConfigError extends Error {
11
9
  constructor(message) {
12
10
  super(message);
@@ -94,13 +92,13 @@ export function workflowRiskForState(events, state, fallback) {
94
92
  }
95
93
  }
96
94
  /**
97
- * 读取项目默认模式。新安装项目由配置提供 normal;缺少配置的旧项目保留 strict
95
+ * 读取项目默认模式。缺少配置或缺少 workflow.mode 时使用 normal。
98
96
  * 配置格式:{ "workflow": { "mode": "normal" } }
99
97
  */
100
98
  export function workflowRiskForProject(projectRoot) {
101
99
  const configPath = join(projectRoot, WORKFLOW_CONFIG_PATH);
102
100
  if (!existsSync(configPath))
103
- return LEGACY_WORKFLOW_RISK;
101
+ return DEFAULT_WORKFLOW_RISK;
104
102
  let parsed;
105
103
  try {
106
104
  parsed = JSON.parse(readFileSync(configPath, "utf8"));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@peterxiaoyang/superspec",
3
- "version": "0.1.52",
3
+ "version": "0.1.53",
4
4
  "description": "SuperSpec 流程引擎 — transition engine with lightweight fact-sync",
5
5
  "type": "module",
6
6
  "engines": {
@@ -28,6 +28,8 @@
28
28
  "eval:dynamic": "node evals/probe.mjs --scenario evals/scenarios/probe-dynamic-accepted.json",
29
29
  "eval:m2": "node evals/m2.mjs",
30
30
  "eval:m2:validate": "node evals/m2.mjs --validate-faults",
31
+ "eval:m3": "node evals/regression.mjs",
32
+ "eval:m3:validate": "node evals/regression.mjs --validate-faults",
31
33
  "eval:arena": "node evals/arena.mjs",
32
34
  "eval:delegation": "node evals/delegation-probe.mjs",
33
35
  "prepack": "npm run build",