@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 +30 -229
- package/dist/format.js +7 -3
- package/dist/install.js +0 -1
- package/dist/phase_confirmation.js +9 -7
- package/dist/transition.js +16 -10
- package/dist/workflow_config.d.ts +2 -2
- package/dist/workflow_config.js +3 -5
- package/package.json +3 -1
package/README.md
CHANGED
|
@@ -4,194 +4,49 @@
|
|
|
4
4
|
[](https://nodejs.org)
|
|
5
5
|
[](https://github.com/Fission-AI/OpenSpec)
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
它解决的是一个很常见的问题:AI 编程工具写代码很快,但有时候还没搞清楚需求、现有代码和测试边界,就已经开始改文件了。
|
|
10
|
-
|
|
11
|
-
SuperSpec 会把一次需求变更拆成 4 个阶段:
|
|
7
|
+
SuperSpec 是一套面向 AI 编程代理的需求变更工作流。它在 OpenSpec 材料之上增加阶段控制、审查和验证记录,帮助 AI 从需求理解推进到可回放的交付结果。
|
|
12
8
|
|
|
13
9
|
```text
|
|
14
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
30
|
+
superspec version
|
|
31
|
+
superspec status
|
|
182
32
|
```
|
|
183
33
|
|
|
184
|
-
|
|
34
|
+
## 工作流入口
|
|
185
35
|
|
|
186
|
-
|
|
187
|
-
|
|
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
|
-
|
|
45
|
+
工作流会根据问题性质留在当前阶段修复,或回到计划阶段重新确认需求、验收和技术取舍。
|
|
191
46
|
|
|
192
|
-
|
|
47
|
+
## 配置
|
|
193
48
|
|
|
194
|
-
|
|
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
|
-
|
|
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
|
-
|
|
63
|
+
OpenSpec 负责变更材料和规格结构;SuperSpec 负责组织 AI 的探索、计划、实现、审查与证据记录。两者互补:
|
|
229
64
|
|
|
230
65
|
```text
|
|
231
|
-
|
|
232
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
808
|
-
const
|
|
809
|
-
if (
|
|
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
|
|
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,
|
|
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,
|
|
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
|
|
277
|
+
export function phaseConfirmationForCurrentState(projectRoot, events, snapshot, risk) {
|
|
277
278
|
const boundary = boundaryForState(snapshot.state);
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
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"
|
package/dist/transition.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
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
|
-
|
|
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
|
-
*
|
|
21
|
+
* 读取项目默认模式。缺少配置或缺少 workflow.mode 时使用 normal。
|
|
22
22
|
* 配置格式:{ "workflow": { "mode": "normal" } }
|
|
23
23
|
*/
|
|
24
24
|
export declare function workflowRiskForProject(projectRoot: string): ReviewRisk;
|
package/dist/workflow_config.js
CHANGED
|
@@ -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
|
-
*
|
|
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
|
|
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.
|
|
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",
|