@peterxiaoyang/superspec 0.1.14 → 0.1.15-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/dist/cli.d.ts +2 -0
- package/dist/cli.js +302 -0
- package/dist/format.d.ts +29 -0
- package/dist/format.js +141 -0
- package/dist/next.d.ts +3 -0
- package/dist/next.js +161 -0
- package/dist/openspec.d.ts +9 -0
- package/dist/openspec.js +41 -0
- package/dist/record.d.ts +20 -0
- package/dist/record.js +211 -0
- package/dist/skill_loop.d.ts +23 -0
- package/dist/skill_loop.js +58 -0
- package/dist/store.d.ts +31 -0
- package/dist/store.js +213 -0
- package/dist/sync.d.ts +3 -0
- package/dist/sync.js +135 -0
- package/dist/task.d.ts +7 -0
- package/dist/task.js +44 -0
- package/dist/transition.d.ts +34 -0
- package/dist/transition.js +400 -0
- package/dist/types.d.ts +138 -0
- package/dist/types.js +3 -0
- package/package.json +8 -53
- package/templates/workflow/skills/superspec-apply/SKILL.md +46 -118
- package/templates/workflow/skills/superspec-archive/SKILL.md +22 -47
- package/templates/workflow/skills/superspec-explore/SKILL.md +38 -69
- package/templates/workflow/skills/superspec-propose/SKILL.md +58 -60
- package/templates/workflow/skills/superspec-review/SKILL.md +36 -81
- package/adapters/codex/agents/architect.toml +0 -13
- package/adapters/codex/agents/code-reviewer.toml +0 -13
- package/adapters/codex/agents/critic.toml +0 -13
- package/adapters/codex/agents/executor.toml +0 -13
- package/adapters/codex/agents/explore.toml +0 -13
- package/adapters/codex/agents/test-engineer.toml +0 -13
- package/adapters/codex/agents/test-runner.toml +0 -13
- package/adapters/codex/agents/verifier.toml +0 -13
- package/adapters/codex/install-map.json +0 -116
- package/bin/launch.js +0 -37
- package/bin/superspec-check.js +0 -4
- package/bin/superspec-hook.js +0 -4
- package/bin/superspec-init.js +0 -4
- package/bin/superspec.js +0 -4
- package/dist/src/apply_worker_chain.d.ts +0 -60
- package/dist/src/apply_worker_chain.js +0 -1283
- package/dist/src/apply_worker_chain_lifecycle.d.ts +0 -21
- package/dist/src/apply_worker_chain_lifecycle.js +0 -409
- package/dist/src/archive.d.ts +0 -23
- package/dist/src/archive.js +0 -430
- package/dist/src/cli.d.ts +0 -1
- package/dist/src/cli.js +0 -33
- package/dist/src/cli_args.d.ts +0 -32
- package/dist/src/cli_args.js +0 -454
- package/dist/src/core.d.ts +0 -20
- package/dist/src/core.js +0 -394
- package/dist/src/disclosure.d.ts +0 -35
- package/dist/src/disclosure.js +0 -671
- package/dist/src/doctor.d.ts +0 -44
- package/dist/src/doctor.js +0 -230
- package/dist/src/evidence.d.ts +0 -29
- package/dist/src/evidence.js +0 -1107
- package/dist/src/gates.d.ts +0 -19
- package/dist/src/gates.js +0 -1631
- package/dist/src/git.d.ts +0 -8
- package/dist/src/git.js +0 -112
- package/dist/src/hooks/adapter.d.ts +0 -5
- package/dist/src/hooks/adapter.js +0 -347
- package/dist/src/hooks/guard_api.d.ts +0 -12
- package/dist/src/hooks/guard_api.js +0 -797
- package/dist/src/hooks/health.d.ts +0 -4
- package/dist/src/hooks/health.js +0 -86
- package/dist/src/hooks/policy_event.d.ts +0 -41
- package/dist/src/hooks/policy_event.js +0 -2634
- package/dist/src/hooks/types.d.ts +0 -72
- package/dist/src/hooks/types.js +0 -1
- package/dist/src/hooks/validation.d.ts +0 -3
- package/dist/src/hooks/validation.js +0 -70
- package/dist/src/i18n.d.ts +0 -21
- package/dist/src/i18n.js +0 -720
- package/dist/src/init_cli.d.ts +0 -28
- package/dist/src/init_cli.js +0 -404
- package/dist/src/install_engine.d.ts +0 -71
- package/dist/src/install_engine.js +0 -538
- package/dist/src/invariants.d.ts +0 -16
- package/dist/src/invariants.js +0 -363
- package/dist/src/openspec.d.ts +0 -37
- package/dist/src/openspec.js +0 -274
- package/dist/src/packet_render.d.ts +0 -5
- package/dist/src/packet_render.js +0 -1767
- package/dist/src/packet_schema.d.ts +0 -59
- package/dist/src/packet_schema.js +0 -1
- package/dist/src/paths.d.ts +0 -22
- package/dist/src/paths.js +0 -203
- package/dist/src/project_init.d.ts +0 -33
- package/dist/src/project_init.js +0 -191
- package/dist/src/self_update.d.ts +0 -14
- package/dist/src/self_update.js +0 -56
- package/dist/src/state.d.ts +0 -37
- package/dist/src/state.js +0 -464
- package/dist/src/tasks.d.ts +0 -38
- package/dist/src/tasks.js +0 -373
- package/dist/src/util.d.ts +0 -163
- package/dist/src/util.js +0 -792
- package/dist/superspec.d.ts +0 -6
- package/dist/superspec.js +0 -97
- package/dist/superspec_guard.d.ts +0 -4
- package/dist/superspec_guard.js +0 -19
- package/dist/superspec_hook.d.ts +0 -4
- package/dist/superspec_hook.js +0 -35
- package/dist/superspec_init.d.ts +0 -2
- package/dist/superspec_init.js +0 -17
- package/schemas/hook-event.schema.json +0 -27
- package/schemas/install-manifest.schema.json +0 -97
- package/templates/hooks/codex-hooks.json +0 -35
- package/templates/sidecar/archive-preservation.json +0 -11
- package/templates/sidecar/business-invariants.md +0 -38
- package/templates/sidecar/config.yaml +0 -13
- package/templates/sidecar/discovery.md +0 -33
- package/templates/sidecar/test-contract.md +0 -27
- package/templates/workflow/prompts/architect.md +0 -27
- package/templates/workflow/prompts/code-reviewer.md +0 -34
- package/templates/workflow/prompts/critic.md +0 -30
- package/templates/workflow/prompts/executor.md +0 -32
- package/templates/workflow/prompts/explore.md +0 -27
- package/templates/workflow/prompts/test-engineer.md +0 -28
- package/templates/workflow/prompts/test-runner.md +0 -35
- package/templates/workflow/prompts/verifier.md +0 -35
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// SuperSpec 流程引擎 — CLI 入口
|
|
3
|
+
import { writeFileSync, mkdirSync, existsSync } from "node:fs";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { writeSnapshot } from "./store.js";
|
|
6
|
+
import { rebuildSnapshot } from "./sync.js";
|
|
7
|
+
import { next as nextCmd } from "./next.js";
|
|
8
|
+
import { proposeReady, commitTransition, transitionInit, transitionExplore, startApply, taskStart, taskComplete, reviewReady, accept, archive } from "./transition.js";
|
|
9
|
+
import { recordJobSubmit, recordUserDecision, jobsList, jobsPacket } from "./record.js";
|
|
10
|
+
import { recordTestRun } from "./task.js";
|
|
11
|
+
import { probeOpenSpec, openspecStatus, changeRoot } from "./openspec.js";
|
|
12
|
+
// ===== 参数解析 =====
|
|
13
|
+
function parseArgs(argv) {
|
|
14
|
+
const [command, subcommand, ...rest] = argv;
|
|
15
|
+
const opts = {};
|
|
16
|
+
for (let i = 0; i < rest.length; i++) {
|
|
17
|
+
if (rest[i].startsWith("--")) {
|
|
18
|
+
const key = rest[i].slice(2);
|
|
19
|
+
const val = rest[i + 1] && !rest[i + 1].startsWith("--") ? rest[++i] : "true";
|
|
20
|
+
opts[key] = val;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
// 如果 subcommand 以 -- 开头,说明没有 subcommand
|
|
24
|
+
if (subcommand && subcommand.startsWith("--")) {
|
|
25
|
+
Object.entries(parseFlags([subcommand, ...rest])).forEach(([k, v]) => { opts[k] = v; });
|
|
26
|
+
return { command, opts };
|
|
27
|
+
}
|
|
28
|
+
return { command, subcommand, opts };
|
|
29
|
+
}
|
|
30
|
+
function parseFlags(args) {
|
|
31
|
+
const opts = {};
|
|
32
|
+
for (let i = 0; i < args.length; i++) {
|
|
33
|
+
if (args[i].startsWith("--")) {
|
|
34
|
+
const key = args[i].slice(2);
|
|
35
|
+
const val = args[i + 1] && !args[i + 1].startsWith("--") ? args[++i] : "true";
|
|
36
|
+
opts[key] = val;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return opts;
|
|
40
|
+
}
|
|
41
|
+
// ===== 初始化 transition init =====
|
|
42
|
+
// ===== init/explore 现在在 transition.ts 中(走统一锁内路径)=====
|
|
43
|
+
// ===== 主分发 =====
|
|
44
|
+
async function main(argv) {
|
|
45
|
+
// --help / 无参数 → 打印用法
|
|
46
|
+
if (argv.length === 0 || argv.includes("--help") || argv.includes("-h")) {
|
|
47
|
+
console.log(`SuperSpec 流程引擎 0.1.15-alpha
|
|
48
|
+
|
|
49
|
+
用法:superspec <命令> [选项]
|
|
50
|
+
|
|
51
|
+
命令:
|
|
52
|
+
status --change <C> 查看状态
|
|
53
|
+
transition <子命令> --change <C> 状态流转(见下)
|
|
54
|
+
record <子命令> --change <C> 登记证据(见下)
|
|
55
|
+
jobs <子命令> --change <C> 工作项管理(见下)
|
|
56
|
+
install [--global] 安装到项目或全局
|
|
57
|
+
update 更新 SuperSpec
|
|
58
|
+
version 版本号
|
|
59
|
+
|
|
60
|
+
transition 子命令:
|
|
61
|
+
init / explore / sync / next / propose-ready / start-apply
|
|
62
|
+
task-start --task <T> / task-complete --task <T>
|
|
63
|
+
review-ready / accept / archive
|
|
64
|
+
|
|
65
|
+
record 子命令:
|
|
66
|
+
job-submit --job <J> --report <F>
|
|
67
|
+
user-decision --input <F>
|
|
68
|
+
test-run --input <F>
|
|
69
|
+
|
|
70
|
+
jobs 子命令:
|
|
71
|
+
list / packet --job <J>
|
|
72
|
+
`);
|
|
73
|
+
return 0;
|
|
74
|
+
}
|
|
75
|
+
// version
|
|
76
|
+
if (argv[0] === "version" || argv[0] === "--version" || argv[0] === "-v") {
|
|
77
|
+
console.log("SuperSpec 0.1.15-alpha");
|
|
78
|
+
return 0;
|
|
79
|
+
}
|
|
80
|
+
const { command, subcommand, opts } = parseArgs(argv);
|
|
81
|
+
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) {
|
|
91
|
+
console.log(JSON.stringify({
|
|
92
|
+
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
|
+
"或在全新项目目录中安装。",
|
|
97
|
+
}));
|
|
98
|
+
return 1;
|
|
99
|
+
}
|
|
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
|
+
}
|
|
108
|
+
if (command === "update") {
|
|
109
|
+
// 检测是否从老版 update 过来
|
|
110
|
+
const { existsSync } = await import("node:fs");
|
|
111
|
+
const isLegacyUpdate = !existsSync(join(projectRoot, ".superspec", "changes"));
|
|
112
|
+
if (isLegacyUpdate) {
|
|
113
|
+
console.log(JSON.stringify({
|
|
114
|
+
ok: false,
|
|
115
|
+
message: "SuperSpec 0.1.15-alpha 是全新引擎,不能从 0.x 直接 update。\n" +
|
|
116
|
+
"请用 npm install -g @peterxiaoyang/superspec@0.1.15-alpha 手动安装。\n" +
|
|
117
|
+
"现有 change 请先用 0.1.x 完成归档。",
|
|
118
|
+
}));
|
|
119
|
+
return 1;
|
|
120
|
+
}
|
|
121
|
+
console.log(JSON.stringify({ ok: true, message: "已是最新版本 0.1.15-alpha" }));
|
|
122
|
+
return 0;
|
|
123
|
+
}
|
|
124
|
+
const change = opts.change;
|
|
125
|
+
if (!change && command !== "status") {
|
|
126
|
+
console.error("错误:缺少 --change(用 --help 查看用法)");
|
|
127
|
+
return 1;
|
|
128
|
+
}
|
|
129
|
+
try {
|
|
130
|
+
switch (command) {
|
|
131
|
+
case "status": {
|
|
132
|
+
if (!change) {
|
|
133
|
+
// 全局 status
|
|
134
|
+
const probe = probeOpenSpec(projectRoot);
|
|
135
|
+
console.log(JSON.stringify({ openspec: probe }, null, 2));
|
|
136
|
+
return 0;
|
|
137
|
+
}
|
|
138
|
+
const cr = changeRoot(projectRoot, change);
|
|
139
|
+
const snapshot = rebuildSnapshot(projectRoot, change, cr);
|
|
140
|
+
const jobs = jobsList(projectRoot, change);
|
|
141
|
+
console.log(JSON.stringify({
|
|
142
|
+
change,
|
|
143
|
+
state: snapshot.state,
|
|
144
|
+
open_jobs: jobs.open.length,
|
|
145
|
+
accepted_jobs: jobs.accepted.length,
|
|
146
|
+
rejected_jobs: jobs.rejected.length,
|
|
147
|
+
document_digests: snapshot.document_digests,
|
|
148
|
+
last_transition: snapshot.last_transition,
|
|
149
|
+
}, null, 2));
|
|
150
|
+
return 0;
|
|
151
|
+
}
|
|
152
|
+
case "transition": {
|
|
153
|
+
const cr = changeRoot(projectRoot, change);
|
|
154
|
+
switch (subcommand) {
|
|
155
|
+
case "init":
|
|
156
|
+
console.log(JSON.stringify(transitionInit(projectRoot, change, cr), null, 2));
|
|
157
|
+
return 0;
|
|
158
|
+
case "explore":
|
|
159
|
+
console.log(JSON.stringify(transitionExplore(projectRoot, change, cr), null, 2));
|
|
160
|
+
return 0;
|
|
161
|
+
case "sync": {
|
|
162
|
+
const snapshot = rebuildSnapshot(projectRoot, change, cr, openspecStatus(projectRoot, change));
|
|
163
|
+
writeSnapshot(projectRoot, change, snapshot);
|
|
164
|
+
console.log(JSON.stringify({
|
|
165
|
+
ok: true,
|
|
166
|
+
state: snapshot.state,
|
|
167
|
+
message: "snapshot 已重建",
|
|
168
|
+
stale_accepted_removed: 0, // sync 不报告具体移除(轻量)
|
|
169
|
+
}, null, 2));
|
|
170
|
+
return 0;
|
|
171
|
+
}
|
|
172
|
+
case "next": {
|
|
173
|
+
const risk = opts.risk ?? "normal";
|
|
174
|
+
const result = nextCmd(projectRoot, change, cr, risk);
|
|
175
|
+
console.log(JSON.stringify(result, null, 2));
|
|
176
|
+
return 0;
|
|
177
|
+
}
|
|
178
|
+
case "propose-ready": {
|
|
179
|
+
const risk = opts.risk ?? "normal";
|
|
180
|
+
const result = proposeReady(projectRoot, change, cr, risk);
|
|
181
|
+
console.log(JSON.stringify(result, null, 2));
|
|
182
|
+
return result.events_written === 0 && result.message.includes("不能") ? 1 : 0;
|
|
183
|
+
}
|
|
184
|
+
case "start-apply": {
|
|
185
|
+
const result = startApply(projectRoot, change, cr);
|
|
186
|
+
console.log(JSON.stringify(result, null, 2));
|
|
187
|
+
return result.events_written === 0 ? 1 : 0;
|
|
188
|
+
}
|
|
189
|
+
case "task-start": {
|
|
190
|
+
const taskId = opts.task;
|
|
191
|
+
if (!taskId) {
|
|
192
|
+
console.error("task-start 需要 --task");
|
|
193
|
+
return 1;
|
|
194
|
+
}
|
|
195
|
+
const result = taskStart(projectRoot, change, cr, taskId);
|
|
196
|
+
console.log(JSON.stringify(result, null, 2));
|
|
197
|
+
return result.events_written === 0 ? 1 : 0;
|
|
198
|
+
}
|
|
199
|
+
case "task-complete": {
|
|
200
|
+
const taskId = opts.task;
|
|
201
|
+
if (!taskId) {
|
|
202
|
+
console.error("task-complete 需要 --task");
|
|
203
|
+
return 1;
|
|
204
|
+
}
|
|
205
|
+
const result = taskComplete(projectRoot, change, cr, taskId);
|
|
206
|
+
console.log(JSON.stringify(result, null, 2));
|
|
207
|
+
return result.events_written === 0 ? 1 : 0;
|
|
208
|
+
}
|
|
209
|
+
case "review-ready": {
|
|
210
|
+
const risk = opts.risk ?? "normal";
|
|
211
|
+
const result = reviewReady(projectRoot, change, cr, risk);
|
|
212
|
+
console.log(JSON.stringify(result, null, 2));
|
|
213
|
+
return result.events_written === 0 ? 1 : 0;
|
|
214
|
+
}
|
|
215
|
+
case "accept": {
|
|
216
|
+
const result = accept(projectRoot, change, cr);
|
|
217
|
+
console.log(JSON.stringify(result, null, 2));
|
|
218
|
+
return result.events_written === 0 ? 1 : 0;
|
|
219
|
+
}
|
|
220
|
+
case "archive": {
|
|
221
|
+
const result = archive(projectRoot, change, cr);
|
|
222
|
+
console.log(JSON.stringify(result, null, 2));
|
|
223
|
+
return result.events_written === 0 ? 1 : 0;
|
|
224
|
+
}
|
|
225
|
+
default:
|
|
226
|
+
console.error(`未知的 transition 子命令:${subcommand}`);
|
|
227
|
+
return 1;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
case "record": {
|
|
231
|
+
const cr = changeRoot(projectRoot, change);
|
|
232
|
+
switch (subcommand) {
|
|
233
|
+
case "job-submit": {
|
|
234
|
+
const jobId = opts.job;
|
|
235
|
+
const report = opts.report;
|
|
236
|
+
if (!jobId || !report) {
|
|
237
|
+
console.error("record job-submit 需要 --job 和 --report");
|
|
238
|
+
return 1;
|
|
239
|
+
}
|
|
240
|
+
const result = recordJobSubmit(projectRoot, change, cr, jobId, report);
|
|
241
|
+
console.log(JSON.stringify(result, null, 2));
|
|
242
|
+
return result.accepted ? 0 : 1;
|
|
243
|
+
}
|
|
244
|
+
case "user-decision": {
|
|
245
|
+
const inputFile = opts.input;
|
|
246
|
+
if (!inputFile) {
|
|
247
|
+
console.error("record user-decision 需要 --input");
|
|
248
|
+
return 1;
|
|
249
|
+
}
|
|
250
|
+
const result = recordUserDecision(projectRoot, change, inputFile);
|
|
251
|
+
console.log(JSON.stringify(result, null, 2));
|
|
252
|
+
return result.accepted ? 0 : 1;
|
|
253
|
+
}
|
|
254
|
+
case "test-run": {
|
|
255
|
+
const inputFile = opts.input;
|
|
256
|
+
if (!inputFile) {
|
|
257
|
+
console.error("record test-run 需要 --input");
|
|
258
|
+
return 1;
|
|
259
|
+
}
|
|
260
|
+
const result = recordTestRun(projectRoot, change, inputFile);
|
|
261
|
+
console.log(JSON.stringify(result, null, 2));
|
|
262
|
+
return result.accepted ? 0 : 1;
|
|
263
|
+
}
|
|
264
|
+
default:
|
|
265
|
+
console.error(`未知的 record 子命令:${subcommand}`);
|
|
266
|
+
return 1;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
case "jobs": {
|
|
270
|
+
switch (subcommand) {
|
|
271
|
+
case "list": {
|
|
272
|
+
const result = jobsList(projectRoot, change);
|
|
273
|
+
console.log(JSON.stringify(result, null, 2));
|
|
274
|
+
return 0;
|
|
275
|
+
}
|
|
276
|
+
case "packet": {
|
|
277
|
+
const jobId = opts.job;
|
|
278
|
+
if (!jobId) {
|
|
279
|
+
console.error("jobs packet 需要 --job");
|
|
280
|
+
return 1;
|
|
281
|
+
}
|
|
282
|
+
const result = jobsPacket(projectRoot, change, jobId);
|
|
283
|
+
console.log(JSON.stringify(result, null, 2));
|
|
284
|
+
return result.found ? 0 : 1;
|
|
285
|
+
}
|
|
286
|
+
default:
|
|
287
|
+
console.error(`未知的 jobs 子命令:${subcommand}`);
|
|
288
|
+
return 1;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
default:
|
|
292
|
+
console.error(`未知命令:${command}。可用:status, transition, record, jobs`);
|
|
293
|
+
return 1;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
catch (err) {
|
|
297
|
+
console.error(`错误:${err.message}`);
|
|
298
|
+
return 2;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
// 入口
|
|
302
|
+
main(process.argv.slice(2)).then(exitCode => process.exit(exitCode));
|
package/dist/format.d.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/** 从 discovery.md 提取"待确认问题"段内的未确认项数量 */
|
|
2
|
+
export declare function countDiscoveryOpenQuestions(content: string): number;
|
|
3
|
+
/** 完整校验 discovery.md:存在 + 非空 + 无未确认问题 */
|
|
4
|
+
export declare function validateDiscovery(changeRoot: string): {
|
|
5
|
+
ok: boolean;
|
|
6
|
+
message: string;
|
|
7
|
+
openCount: number;
|
|
8
|
+
};
|
|
9
|
+
export interface ParsedTask {
|
|
10
|
+
taskId: string;
|
|
11
|
+
lineIdx: number;
|
|
12
|
+
done: boolean;
|
|
13
|
+
tddRequired: boolean;
|
|
14
|
+
noTddReason: string | null;
|
|
15
|
+
}
|
|
16
|
+
/** 解析 tasks.md 的全部任务行 */
|
|
17
|
+
export declare function parseTasksMd(content: string): ParsedTask[];
|
|
18
|
+
/** 在 tasks.md 中按 taskId 精确查找任务(词边界,不误判子串) */
|
|
19
|
+
export declare function findTaskInLines(lines: string[], taskId: string): number;
|
|
20
|
+
/** tasks.md 结构指纹(复选框归一化) */
|
|
21
|
+
export declare function tasksStructureDigest(content: string, sha256Text: (s: string) => string): string;
|
|
22
|
+
export declare function validateTestRunInput(tr: Record<string, unknown>): {
|
|
23
|
+
ok: boolean;
|
|
24
|
+
message: string;
|
|
25
|
+
};
|
|
26
|
+
export declare function validateUserDecision(d: Record<string, unknown>): {
|
|
27
|
+
ok: boolean;
|
|
28
|
+
message: string;
|
|
29
|
+
};
|
package/dist/format.js
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
// SuperSpec 流程引擎 — format.ts:文档格式解析的唯一权威源
|
|
2
|
+
//
|
|
3
|
+
// 所有文档的格式定义和解析逻辑都在这里。skills 文案引用这里的格式。
|
|
4
|
+
// 修改格式 = 修改这里 + 对应 skill。禁止在其它地方重复解析。
|
|
5
|
+
import { readFileSync, existsSync } from "node:fs";
|
|
6
|
+
import { join } from "node:path";
|
|
7
|
+
// ===== discovery.md =====
|
|
8
|
+
//
|
|
9
|
+
// 格式(explore skill 定义):
|
|
10
|
+
// ## 待确认问题
|
|
11
|
+
// - [ ] 问题1的描述
|
|
12
|
+
// - [ ] 问题2的描述
|
|
13
|
+
//
|
|
14
|
+
// 引擎只解析"待确认问题"段内的 `- [ ]`,不误判正常 checklist。
|
|
15
|
+
/** 从 discovery.md 提取"待确认问题"段内的未确认项数量 */
|
|
16
|
+
export function countDiscoveryOpenQuestions(content) {
|
|
17
|
+
// 找"待确认问题"标题(中英文兼容)
|
|
18
|
+
const sectionMatch = content.match(/^#{1,6}\s*(待确认问题|Open Questions|Pending Questions)\s*$/im);
|
|
19
|
+
if (!sectionMatch)
|
|
20
|
+
return 0;
|
|
21
|
+
const sectionStart = sectionMatch.index + sectionMatch[0].length;
|
|
22
|
+
// 截取到下一个标题或文件末尾
|
|
23
|
+
const restContent = content.slice(sectionStart);
|
|
24
|
+
const nextHeadingMatch = restContent.match(/^#{1,6}\s+/m);
|
|
25
|
+
const sectionBody = nextHeadingMatch ? restContent.slice(0, nextHeadingMatch.index) : restContent;
|
|
26
|
+
// 数未确认项
|
|
27
|
+
const matches = sectionBody.match(/- \[ \]/g);
|
|
28
|
+
return matches ? matches.length : 0;
|
|
29
|
+
}
|
|
30
|
+
/** 完整校验 discovery.md:存在 + 非空 + 无未确认问题 */
|
|
31
|
+
export function validateDiscovery(changeRoot) {
|
|
32
|
+
const path = join(changeRoot, ".superspec", "artifacts", "discovery.md");
|
|
33
|
+
if (!existsSync(path))
|
|
34
|
+
return { ok: false, message: "discovery.md 不存在", openCount: -1 };
|
|
35
|
+
const content = readFileSync(path, "utf8");
|
|
36
|
+
if (!content.trim())
|
|
37
|
+
return { ok: false, message: "discovery.md 为空", openCount: -1 };
|
|
38
|
+
const openCount = countDiscoveryOpenQuestions(content);
|
|
39
|
+
if (openCount > 0)
|
|
40
|
+
return { ok: false, message: `discovery.md 有 ${openCount} 个未确认问题`, openCount };
|
|
41
|
+
return { ok: true, message: "discovery.md 就绪", openCount: 0 };
|
|
42
|
+
}
|
|
43
|
+
const TASK_LINE_RE = /^(- \[([ xX])\])\s+(\S+)/;
|
|
44
|
+
/** 解析 tasks.md 的全部任务行 */
|
|
45
|
+
export function parseTasksMd(content) {
|
|
46
|
+
const lines = content.split("\n");
|
|
47
|
+
const tasks = [];
|
|
48
|
+
for (let i = 0; i < lines.length; i++) {
|
|
49
|
+
const m = lines[i].match(TASK_LINE_RE);
|
|
50
|
+
if (!m)
|
|
51
|
+
continue;
|
|
52
|
+
const taskId = m[3];
|
|
53
|
+
const done = m[2].toLowerCase() === "x";
|
|
54
|
+
// 用正则锚定字段边界,不依赖 includes()
|
|
55
|
+
const tddMatch = lines[i].match(/\btdd_required:(true|false)\b/);
|
|
56
|
+
const reasonMatch = lines[i].match(/\bno_tdd_reason:(\S+)\b/);
|
|
57
|
+
tasks.push({
|
|
58
|
+
taskId,
|
|
59
|
+
lineIdx: i,
|
|
60
|
+
done,
|
|
61
|
+
tddRequired: tddMatch ? tddMatch[1] === "true" : true, // 默认 true
|
|
62
|
+
noTddReason: reasonMatch ? reasonMatch[1] : null,
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
return tasks;
|
|
66
|
+
}
|
|
67
|
+
/** 在 tasks.md 中按 taskId 精确查找任务(词边界,不误判子串) */
|
|
68
|
+
export function findTaskInLines(lines, taskId) {
|
|
69
|
+
for (let i = 0; i < lines.length; i++) {
|
|
70
|
+
// 精确匹配行内的 taskId token
|
|
71
|
+
const m = lines[i].match(TASK_LINE_RE);
|
|
72
|
+
if (m && m[3] === taskId)
|
|
73
|
+
return i;
|
|
74
|
+
// 回退:用转义正则匹配(兼容 taskId 后跟标点的情况)
|
|
75
|
+
const escaped = taskId.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
76
|
+
if (new RegExp("(?:^|\\s)" + escaped + "(?:\\s|$|[.,:;!?)\\]])").test(lines[i]))
|
|
77
|
+
return i;
|
|
78
|
+
}
|
|
79
|
+
return -1;
|
|
80
|
+
}
|
|
81
|
+
/** tasks.md 结构指纹(复选框归一化) */
|
|
82
|
+
export function tasksStructureDigest(content, sha256Text) {
|
|
83
|
+
return sha256Text(content.replace(/- \[[xX]\]/g, "- [ ]"));
|
|
84
|
+
}
|
|
85
|
+
// ===== test-run JSON =====
|
|
86
|
+
//
|
|
87
|
+
// 格式(apply skill 定义):
|
|
88
|
+
// {
|
|
89
|
+
// "test_id": "TEST-XXX",
|
|
90
|
+
// "attempt_id": "ATT-TASK-XXX-...",
|
|
91
|
+
// "task_structure_digest": "sha256:...",
|
|
92
|
+
// "command": "npm test",
|
|
93
|
+
// "cwd": "/path",
|
|
94
|
+
// "exit_code": 1,
|
|
95
|
+
// "semantic_status": "expected_failure",
|
|
96
|
+
// "target_fingerprint": "sha256:..."
|
|
97
|
+
// }
|
|
98
|
+
//
|
|
99
|
+
// 引擎校验:test_id + task_structure_digest 必填,其余可选
|
|
100
|
+
export function validateTestRunInput(tr) {
|
|
101
|
+
if (!tr.test_id)
|
|
102
|
+
return { ok: false, message: "缺少 test_id" };
|
|
103
|
+
if (!tr.task_structure_digest)
|
|
104
|
+
return { ok: false, message: "缺少 task_structure_digest" };
|
|
105
|
+
return { ok: true, message: "" };
|
|
106
|
+
}
|
|
107
|
+
// ===== user-decision JSON =====
|
|
108
|
+
//
|
|
109
|
+
// 格式(explore skill 定义):
|
|
110
|
+
// {
|
|
111
|
+
// "scope": "enter_propose",
|
|
112
|
+
// "question": "是否进入计划阶段?",
|
|
113
|
+
// "answer": "yes"
|
|
114
|
+
// }
|
|
115
|
+
//
|
|
116
|
+
// 引擎校验:scope + answer 必填
|
|
117
|
+
export function validateUserDecision(d) {
|
|
118
|
+
if (!d.scope)
|
|
119
|
+
return { ok: false, message: "决策文件缺少 scope" };
|
|
120
|
+
if (!d.answer)
|
|
121
|
+
return { ok: false, message: "决策文件缺少 answer" };
|
|
122
|
+
return { ok: true, message: "" };
|
|
123
|
+
}
|
|
124
|
+
// ===== business-invariants.md =====
|
|
125
|
+
//
|
|
126
|
+
// 格式(propose skill 定义):
|
|
127
|
+
// # Business Invariants
|
|
128
|
+
// - INV-001 用户密码必须加密存储
|
|
129
|
+
//
|
|
130
|
+
// 引擎行为:Phase 1-5 只校验文件存在性(轻量)。
|
|
131
|
+
// 内部结构(INV-XXX 编号)是 agent 指引,引擎不逐行解析。
|
|
132
|
+
// ===== test-contract.md =====
|
|
133
|
+
//
|
|
134
|
+
// 格式(propose skill 定义):
|
|
135
|
+
// # Test Contract
|
|
136
|
+
// | test_id | invariant | scenario |
|
|
137
|
+
// |---|---|---|
|
|
138
|
+
// | TEST-001 | INV-001 | 注册时密码被加密 |
|
|
139
|
+
//
|
|
140
|
+
// 引擎行为:Phase 1-5 只校验文件存在性(轻量)。
|
|
141
|
+
// 表格结构是 agent 指引,引擎不逐行解析。
|
package/dist/next.d.ts
ADDED
package/dist/next.js
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
// SuperSpec 流程引擎 — next:返回可执行路径
|
|
2
|
+
import { rebuildSnapshot } from "./sync.js";
|
|
3
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { validateDiscovery, countDiscoveryOpenQuestions } from "./format.js";
|
|
6
|
+
function packetCommand(change, jobId) {
|
|
7
|
+
return `superspec jobs packet --change "${change}" --job "${jobId}"`;
|
|
8
|
+
}
|
|
9
|
+
function transitionCommand(change, name, extra = "") {
|
|
10
|
+
return `superspec transition ${name} --change "${change}"${extra ? " " + extra : ""}`;
|
|
11
|
+
}
|
|
12
|
+
/** next 命令:读 snapshot,返回唯一可执行路径 */
|
|
13
|
+
export function next(projectRoot, change, changeRoot, defaultRisk = "normal") {
|
|
14
|
+
const snapshot = rebuildSnapshot(projectRoot, change, changeRoot);
|
|
15
|
+
switch (snapshot.state) {
|
|
16
|
+
case "init":
|
|
17
|
+
return {
|
|
18
|
+
state: "init",
|
|
19
|
+
path: "next_command",
|
|
20
|
+
next_command: transitionCommand(change, "explore"),
|
|
21
|
+
reason: "初始化完成,开始探索",
|
|
22
|
+
missing_inputs: [],
|
|
23
|
+
};
|
|
24
|
+
case "explore": {
|
|
25
|
+
// Phase 2:检查 discovery.md
|
|
26
|
+
const discoveryCheck = validateDiscovery(changeRoot);
|
|
27
|
+
if (!discoveryCheck.ok) {
|
|
28
|
+
const ask = {
|
|
29
|
+
question: discoveryCheck.message + ",请处理后继续",
|
|
30
|
+
allowed_answers: ["已处理"],
|
|
31
|
+
scope: "explore_discovery",
|
|
32
|
+
};
|
|
33
|
+
return { state: "explore", path: "ask_user", ask_user: ask, reason: discoveryCheck.message };
|
|
34
|
+
}
|
|
35
|
+
// 检查"待确认问题"段(用 format.ts 的段感知解析)
|
|
36
|
+
const content = readFileSync(join(changeRoot, ".superspec", "artifacts", "discovery.md"), "utf8");
|
|
37
|
+
const openQs = countDiscoveryOpenQuestions(content);
|
|
38
|
+
if (openQs > 0) {
|
|
39
|
+
const ask = {
|
|
40
|
+
question: `discovery.md 有 ${openQs} 个未解决的待确认问题,请逐个确认`,
|
|
41
|
+
allowed_answers: ["所有问题已确认"],
|
|
42
|
+
scope: "explore_open_questions",
|
|
43
|
+
};
|
|
44
|
+
return { state: "explore", path: "ask_user", ask_user: ask, reason: `有 ${openQs} 个未确认问题` };
|
|
45
|
+
}
|
|
46
|
+
return {
|
|
47
|
+
state: "explore",
|
|
48
|
+
path: "next_command",
|
|
49
|
+
next_command: transitionCommand(change, "explore"),
|
|
50
|
+
reason: "探索完成,推进到计划阶段",
|
|
51
|
+
missing_inputs: [],
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
case "propose": {
|
|
55
|
+
if (snapshot.open_jobs.length > 0) {
|
|
56
|
+
const jobs = snapshot.open_jobs.map(j => ({
|
|
57
|
+
job_id: j.job_id,
|
|
58
|
+
role: j.role,
|
|
59
|
+
packet_command: packetCommand(change, j.job_id),
|
|
60
|
+
}));
|
|
61
|
+
return {
|
|
62
|
+
state: "propose",
|
|
63
|
+
path: "required_job",
|
|
64
|
+
required_jobs: jobs,
|
|
65
|
+
reason: `有 ${jobs.length} 个待完成工作项`,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
const riskFlag = `--risk ${defaultRisk}`;
|
|
69
|
+
return {
|
|
70
|
+
state: "propose",
|
|
71
|
+
path: "next_command",
|
|
72
|
+
next_command: transitionCommand(change, "propose-ready", riskFlag),
|
|
73
|
+
reason: "计划文档就绪,提交 propose-ready",
|
|
74
|
+
missing_inputs: [],
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
case "propose_ready":
|
|
78
|
+
return {
|
|
79
|
+
state: "propose_ready",
|
|
80
|
+
path: "next_command",
|
|
81
|
+
next_command: transitionCommand(change, "start-apply"),
|
|
82
|
+
reason: "计划就绪,开始执行",
|
|
83
|
+
missing_inputs: [],
|
|
84
|
+
};
|
|
85
|
+
case "apply": {
|
|
86
|
+
// 有 open job → 做
|
|
87
|
+
if (snapshot.open_jobs.length > 0) {
|
|
88
|
+
return {
|
|
89
|
+
state: "apply",
|
|
90
|
+
path: "required_job",
|
|
91
|
+
required_jobs: snapshot.open_jobs.map(j => ({ job_id: j.job_id, role: j.role, packet_command: packetCommand(change, j.job_id) })),
|
|
92
|
+
reason: `有 ${snapshot.open_jobs.length} 个待完成工作项`,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
// 检查是否所有任务已完成
|
|
96
|
+
const tasksContent = readFileSync(join(changeRoot, "tasks.md"), "utf8");
|
|
97
|
+
const allDone = !tasksContent.split("\n").some(l => l.includes("- [ ]"));
|
|
98
|
+
if (allDone) {
|
|
99
|
+
return {
|
|
100
|
+
state: "apply",
|
|
101
|
+
path: "next_command",
|
|
102
|
+
next_command: transitionCommand(change, "review-ready"),
|
|
103
|
+
reason: "所有任务完成,进入审查",
|
|
104
|
+
missing_inputs: [],
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
return {
|
|
108
|
+
state: "apply",
|
|
109
|
+
path: "next_command",
|
|
110
|
+
next_command: transitionCommand(change, "task-start", "--task TASK-XXX"),
|
|
111
|
+
reason: "执行中:查看 tasks.md 找下一个未完成任务",
|
|
112
|
+
missing_inputs: [],
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
case "apply_done": {
|
|
116
|
+
if (snapshot.open_jobs.length > 0) {
|
|
117
|
+
return {
|
|
118
|
+
state: "apply_done",
|
|
119
|
+
path: "required_job",
|
|
120
|
+
required_jobs: snapshot.open_jobs.map(j => ({ job_id: j.job_id, role: j.role, packet_command: packetCommand(change, j.job_id) })),
|
|
121
|
+
reason: `有 ${snapshot.open_jobs.length} 个待完成工作项`,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
return {
|
|
125
|
+
state: "apply_done",
|
|
126
|
+
path: "next_command",
|
|
127
|
+
next_command: transitionCommand(change, "review-ready"),
|
|
128
|
+
reason: "所有任务完成,进入审查",
|
|
129
|
+
missing_inputs: [],
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
case "review":
|
|
133
|
+
return {
|
|
134
|
+
state: "review",
|
|
135
|
+
path: "next_command",
|
|
136
|
+
next_command: transitionCommand(change, "accept"),
|
|
137
|
+
reason: "审查完成,提交接受",
|
|
138
|
+
missing_inputs: [],
|
|
139
|
+
};
|
|
140
|
+
case "accepted":
|
|
141
|
+
return {
|
|
142
|
+
state: "accepted",
|
|
143
|
+
path: "next_command",
|
|
144
|
+
next_command: transitionCommand(change, "archive"),
|
|
145
|
+
reason: "审查通过,提交归档",
|
|
146
|
+
missing_inputs: [],
|
|
147
|
+
};
|
|
148
|
+
case "archive":
|
|
149
|
+
return {
|
|
150
|
+
state: "archive",
|
|
151
|
+
path: "done",
|
|
152
|
+
reason: "已归档,流程完成。",
|
|
153
|
+
};
|
|
154
|
+
default:
|
|
155
|
+
return {
|
|
156
|
+
state: snapshot.state,
|
|
157
|
+
path: "done",
|
|
158
|
+
reason: `状态 ${snapshot.state} 超出 Phase 1 范围`,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export interface OpenSpecProbe {
|
|
2
|
+
available: boolean;
|
|
3
|
+
version: string | null;
|
|
4
|
+
changeExists: boolean;
|
|
5
|
+
error?: string;
|
|
6
|
+
}
|
|
7
|
+
export declare function probeOpenSpec(projectRoot: string, change?: string): OpenSpecProbe;
|
|
8
|
+
export declare function openspecStatus(projectRoot: string, change: string): string;
|
|
9
|
+
export declare function changeRoot(projectRoot: string, change: string): string;
|