@chenmk/superflow 0.5.8 → 0.5.10
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/INSTALL.md +1 -2
- package/README.md +5 -0
- package/README.zh-CN.md +5 -0
- package/assets/manifest.json +1 -0
- package/assets/scripts/superflow-coding-ready.mjs +88 -19
- package/assets/scripts/superflow-document-audit.mjs +29 -5
- package/assets/scripts/superflow-hook-guard.sh +3 -14
- package/assets/scripts/superflow-review-coverage.mjs +258 -0
- package/assets/skills/superflow-docs/SKILL.md +5 -0
- package/assets/skills/superflow-pipeline/SKILL.md +10 -8
- package/assets/skills/superflow-pipeline/references/document-readiness.md +4 -0
- package/assets/skills/superflow-pipeline/references/document-review-coverage.md +82 -0
- package/assets/skills/superflow-pipeline/references/implementation-prompt-template.md +8 -8
- package/assets/skills/superflow-pipeline/references/quality-gate.md +4 -4
- package/assets/skills/superflow-pipeline/references/quality-standards.md +1 -1
- package/assets/skills/superflow-pipeline/references/superpower-technical-design-template.md +2 -2
- package/assets/skills/superflow-pipeline/references/test-execution-template.md +6 -0
- package/assets/skills/superflow-pipeline/scripts/superflow-guard.sh +7 -5
- package/assets/skills/superflow-pipeline/scripts/superflow-handoff.sh +15 -2
- package/assets/skills/superflow-requirement-review/SKILL.md +4 -0
- package/assets/skills/superflow-verify/SKILL.md +4 -0
- package/assets/skills-en/superflow-docs/SKILL.md +6 -0
- package/assets/skills-en/superflow-pipeline/SKILL.md +4 -4
- package/assets/skills-en/superflow-pipeline/references/batch-prompt-template.md +1 -1
- package/assets/skills-en/superflow-pipeline/references/document-readiness.md +4 -0
- package/assets/skills-en/superflow-pipeline/references/document-review-coverage.md +89 -0
- package/assets/skills-en/superflow-pipeline/references/implementation-prompt-template.md +4 -3
- package/assets/skills-en/superflow-pipeline/references/quality-gate.md +1 -1
- package/assets/skills-en/superflow-pipeline/references/superpower-technical-design-template.md +1 -2
- package/assets/skills-en/superflow-pipeline/references/test-execution-template.md +9 -0
- package/assets/skills-en/superflow-pipeline/scripts/superflow-guard.sh +7 -5
- package/assets/skills-en/superflow-pipeline/scripts/superflow-handoff.sh +15 -2
- package/assets/skills-en/superflow-requirement-review/SKILL.md +5 -0
- package/assets/skills-en/superflow-verify/SKILL.md +5 -0
- package/dist/app/commands/init.js +4 -5
- package/dist/app/commands/init.js.map +1 -1
- package/dist/domains/config/cli-help.js +2 -2
- package/dist/domains/config/cli-help.js.map +1 -1
- package/dist/domains/managed-work/cleanup.js +6 -2
- package/dist/domains/managed-work/cleanup.js.map +1 -1
- package/dist/domains/managed-work/contract.js +4 -1
- package/dist/domains/managed-work/contract.js.map +1 -1
- package/dist/domains/managed-work/gitignore.js +29 -0
- package/dist/domains/managed-work/gitignore.js.map +1 -0
- package/dist/domains/managed-work/runner.js +56 -0
- package/dist/domains/managed-work/runner.js.map +1 -1
- package/dist/domains/managed-work/storage.js +6 -0
- package/dist/domains/managed-work/storage.js.map +1 -1
- package/dist/domains/sdd-readiness.js +9 -0
- package/dist/domains/sdd-readiness.js.map +1 -1
- package/dist/domains/skill/deploy.js +2 -9
- package/dist/domains/skill/deploy.js.map +1 -1
- package/package.json +1 -1
package/INSTALL.md
CHANGED
|
@@ -142,8 +142,7 @@ CLI 会弹出工具选择列表。你可以只选 Codex、只选 Claude Code,
|
|
|
142
142
|
| `superflow init --dry-run` | 只打印计划,不写文件 |
|
|
143
143
|
| `superflow init --json` | 输出 JSON,适合脚本集成 |
|
|
144
144
|
| `superflow init --resume` | 从上次失败步骤继续 |
|
|
145
|
-
| `superflow init --
|
|
146
|
-
| `superflow init --skip-existing` | 保留已存在技能,不覆盖 |
|
|
145
|
+
| `superflow init --skip-existing` | 保留已存在技能,不覆盖;默认直接更新受管理技能且不生成备份副本 |
|
|
147
146
|
|
|
148
147
|
语言可以通过两种方式选择:
|
|
149
148
|
|
package/README.md
CHANGED
|
@@ -48,6 +48,11 @@ state, and dependency guards for Claude Code and Codex.
|
|
|
48
48
|
|
|
49
49
|
## Highlights
|
|
50
50
|
|
|
51
|
+
0.5.10 strengthens document delivery: requirement × real-entry coverage (including
|
|
52
|
+
exclusions), real-entry test contracts, and content-bound Coding Ready receipts.
|
|
53
|
+
Existing document receipts need [coverage migration and re-review](assets/skills-en/superflow-pipeline/references/document-review-coverage.md)
|
|
54
|
+
before new dispatch or source edits. Semantic correctness remains an Agent review responsibility.
|
|
55
|
+
|
|
51
56
|
- **Dual-Agent managed delivery.** The current host Agent reviews directly while
|
|
52
57
|
the peer Agent develops, starts, and verifies in one CLI session without a
|
|
53
58
|
tool-turn limit, then actively returns one structured result. A later session
|
package/README.zh-CN.md
CHANGED
|
@@ -41,6 +41,11 @@ CLI 会把 SuperBridge Flow 技能、配套 hook/command 脚本整合为单一 n
|
|
|
41
41
|
|
|
42
42
|
## 亮点
|
|
43
43
|
|
|
44
|
+
0.5.10 加强文档交付:逐需求×真实入口核对(含排除项)、真实入口验收合同、
|
|
45
|
+
绑定内容指纹的 Coding Ready。旧文档凭证需先完成
|
|
46
|
+
[覆盖补齐与复审](assets/skills/superflow-pipeline/references/document-review-coverage.md),
|
|
47
|
+
才能新启动任务或编辑源码;业务语义正确性仍由智能体独立评审负责。
|
|
48
|
+
|
|
44
49
|
- **双 Agent 托管到真实交付。** 当前 Host Agent 直接监督,另一个 Agent 在一次不限制工具
|
|
45
50
|
轮数的 CLI 会话中持续开发、启动和验证,完成后主动返回结构化结果;只有评审整改、进程异常
|
|
46
51
|
或人工恢复才创建后续会话。工作区与落盘 finding 负责交接,默认最多 5 轮评审、7 次执行、
|
package/assets/manifest.json
CHANGED
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
"superflow-test-report-lint.py",
|
|
33
33
|
"superflow-managed-work-check.mjs",
|
|
34
34
|
"superflow-document-audit.mjs",
|
|
35
|
+
"superflow-review-coverage.mjs",
|
|
35
36
|
"superflow-environment-preflight.mjs",
|
|
36
37
|
"superflow-coding-ready.mjs",
|
|
37
38
|
"superflow-managed-executor-preflight.mjs",
|
|
@@ -5,6 +5,7 @@ import fs from "node:fs";
|
|
|
5
5
|
import os from "node:os";
|
|
6
6
|
import path from "node:path";
|
|
7
7
|
import { fileURLToPath } from "node:url";
|
|
8
|
+
import { fileHash, reviewPath } from "./superflow-review-coverage.mjs";
|
|
8
9
|
|
|
9
10
|
const args = process.argv.slice(2);
|
|
10
11
|
const changeArg = args.find((arg) => !arg.startsWith("--"));
|
|
@@ -18,12 +19,40 @@ const changeDir = fs.realpathSync(path.resolve(changeArg));
|
|
|
18
19
|
const scriptDir = path.dirname(fileURLToPath(import.meta.url));
|
|
19
20
|
const pipelineScripts = resolvePipelineScripts(scriptDir);
|
|
20
21
|
const checks = [
|
|
21
|
-
[
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
[
|
|
22
|
+
[
|
|
23
|
+
"YAML 状态",
|
|
24
|
+
"bash",
|
|
25
|
+
[path.join(pipelineScripts, "superflow-yaml-validate.sh"), changeDir],
|
|
26
|
+
],
|
|
27
|
+
[
|
|
28
|
+
"docs 门禁",
|
|
29
|
+
"bash",
|
|
30
|
+
[path.join(pipelineScripts, "superflow-guard.sh"), changeDir, "docs"],
|
|
31
|
+
],
|
|
32
|
+
[
|
|
33
|
+
"design 门禁",
|
|
34
|
+
"bash",
|
|
35
|
+
[path.join(pipelineScripts, "superflow-guard.sh"), changeDir, "design"],
|
|
36
|
+
],
|
|
37
|
+
[
|
|
38
|
+
"implement 门禁",
|
|
39
|
+
"bash",
|
|
40
|
+
[path.join(pipelineScripts, "superflow-guard.sh"), changeDir, "implement"],
|
|
41
|
+
],
|
|
42
|
+
[
|
|
43
|
+
"文档交付审计",
|
|
44
|
+
process.execPath,
|
|
45
|
+
[path.join(scriptDir, "superflow-document-audit.mjs"), changeDir, "--json"],
|
|
46
|
+
],
|
|
47
|
+
[
|
|
48
|
+
"环境预检",
|
|
49
|
+
process.execPath,
|
|
50
|
+
[
|
|
51
|
+
path.join(scriptDir, "superflow-environment-preflight.mjs"),
|
|
52
|
+
changeDir,
|
|
53
|
+
"--json",
|
|
54
|
+
],
|
|
55
|
+
],
|
|
27
56
|
];
|
|
28
57
|
const issues = [];
|
|
29
58
|
const evidence = [];
|
|
@@ -40,17 +69,29 @@ for (const [label, command, commandArgs] of checks) {
|
|
|
40
69
|
}
|
|
41
70
|
}
|
|
42
71
|
|
|
43
|
-
const openspec = spawnSync(
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
72
|
+
const openspec = spawnSync(
|
|
73
|
+
"openspec",
|
|
74
|
+
["validate", path.basename(changeDir), "--strict"],
|
|
75
|
+
{
|
|
76
|
+
cwd: projectRoot(changeDir),
|
|
77
|
+
encoding: "utf-8",
|
|
78
|
+
},
|
|
79
|
+
);
|
|
47
80
|
evidence.push({ label: "OpenSpec strict", exitCode: openspec.status ?? 1 });
|
|
48
81
|
if (openspec.status !== 0) {
|
|
49
|
-
issues.push(
|
|
82
|
+
issues.push(
|
|
83
|
+
`OpenSpec strict 失败:${tail(openspec.stderr || openspec.stdout)}`,
|
|
84
|
+
);
|
|
50
85
|
}
|
|
51
86
|
|
|
52
87
|
const handoffHash = readHandoffHash(changeDir, issues);
|
|
53
88
|
const reviewRounds = readReviewRounds(changeDir);
|
|
89
|
+
let reviewHash = "";
|
|
90
|
+
try {
|
|
91
|
+
reviewHash = fileHash(path.join(changeDir, reviewPath));
|
|
92
|
+
} catch {
|
|
93
|
+
issues.push("无法冻结文档评审凭证指纹");
|
|
94
|
+
}
|
|
54
95
|
const receipt = {
|
|
55
96
|
schemaVersion: "superflow.coding-ready.v1",
|
|
56
97
|
codingReady: issues.length === 0,
|
|
@@ -58,21 +99,28 @@ const receipt = {
|
|
|
58
99
|
environmentReadiness: issues.length === 0 ? "READY" : "BLOCKED",
|
|
59
100
|
reviewRounds,
|
|
60
101
|
handoffHash,
|
|
102
|
+
reviewHash,
|
|
61
103
|
checkedAt: new Date().toISOString(),
|
|
62
104
|
evidence,
|
|
63
105
|
ownerDecisions: 0,
|
|
64
106
|
issues,
|
|
65
107
|
};
|
|
66
108
|
|
|
67
|
-
if (issues.length > 0) fail(issues, receipt);
|
|
68
|
-
|
|
69
109
|
const receiptDir = path.join(changeDir, ".sdd", "readiness");
|
|
70
110
|
fs.mkdirSync(receiptDir, { recursive: true });
|
|
71
111
|
const target = path.join(receiptDir, "coding-ready.json");
|
|
72
|
-
const temporary = path.join(
|
|
73
|
-
|
|
112
|
+
const temporary = path.join(
|
|
113
|
+
receiptDir,
|
|
114
|
+
`.coding-ready-${process.pid}-${Date.now()}.tmp`,
|
|
115
|
+
);
|
|
116
|
+
fs.writeFileSync(temporary, `${JSON.stringify(receipt, null, 2)}\n`, {
|
|
117
|
+
mode: 0o600,
|
|
118
|
+
});
|
|
74
119
|
fs.renameSync(temporary, target);
|
|
75
120
|
|
|
121
|
+
// Persist BLOCKED too: a failed recheck must revoke an earlier READY receipt.
|
|
122
|
+
if (issues.length > 0) fail(issues, receipt);
|
|
123
|
+
|
|
76
124
|
if (jsonOutput) {
|
|
77
125
|
process.stdout.write(`${JSON.stringify(receipt, null, 2)}\n`);
|
|
78
126
|
} else {
|
|
@@ -81,9 +129,27 @@ if (jsonOutput) {
|
|
|
81
129
|
|
|
82
130
|
function resolvePipelineScripts(currentScriptDir) {
|
|
83
131
|
const candidates = [
|
|
84
|
-
path.resolve(
|
|
85
|
-
|
|
86
|
-
|
|
132
|
+
path.resolve(
|
|
133
|
+
currentScriptDir,
|
|
134
|
+
"..",
|
|
135
|
+
"skills",
|
|
136
|
+
"superflow-pipeline",
|
|
137
|
+
"scripts",
|
|
138
|
+
),
|
|
139
|
+
path.join(
|
|
140
|
+
os.homedir(),
|
|
141
|
+
".codex",
|
|
142
|
+
"skills",
|
|
143
|
+
"superflow-pipeline",
|
|
144
|
+
"scripts",
|
|
145
|
+
),
|
|
146
|
+
path.join(
|
|
147
|
+
os.homedir(),
|
|
148
|
+
".claude",
|
|
149
|
+
"skills",
|
|
150
|
+
"superflow-pipeline",
|
|
151
|
+
"scripts",
|
|
152
|
+
),
|
|
87
153
|
];
|
|
88
154
|
const found = candidates.find((candidate) =>
|
|
89
155
|
fs.existsSync(path.join(candidate, "superflow-guard.sh")),
|
|
@@ -123,7 +189,10 @@ function tail(value) {
|
|
|
123
189
|
return String(value).trim().split("\n").slice(-4).join(" | ").slice(0, 800);
|
|
124
190
|
}
|
|
125
191
|
|
|
126
|
-
function fail(
|
|
192
|
+
function fail(
|
|
193
|
+
targetIssues,
|
|
194
|
+
receipt = { codingReady: false, issues: targetIssues },
|
|
195
|
+
) {
|
|
127
196
|
if (jsonOutput) {
|
|
128
197
|
process.stderr.write(`${JSON.stringify(receipt, null, 2)}\n`);
|
|
129
198
|
} else {
|
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
import fs from "node:fs";
|
|
4
4
|
import path from "node:path";
|
|
5
|
+
import {
|
|
6
|
+
currentHandoffHash,
|
|
7
|
+
validateCoverage,
|
|
8
|
+
} from "./superflow-review-coverage.mjs";
|
|
5
9
|
|
|
6
10
|
const args = process.argv.slice(2);
|
|
7
11
|
const changeArg = args.find((arg) => !arg.startsWith("--"));
|
|
@@ -34,7 +38,9 @@ for (const document of aggregateDocuments) {
|
|
|
34
38
|
}
|
|
35
39
|
const links = markdownTargets(fs.readFileSync(documentPath, "utf-8"));
|
|
36
40
|
for (const prompt of prompts) {
|
|
37
|
-
const relativePrompt = path
|
|
41
|
+
const relativePrompt = path
|
|
42
|
+
.relative(changeDir, prompt)
|
|
43
|
+
.replaceAll("\\", "/");
|
|
38
44
|
if (!links.has(relativePrompt)) {
|
|
39
45
|
issues.push(`${document} 未链接 ${relativePrompt}`);
|
|
40
46
|
}
|
|
@@ -47,6 +53,14 @@ const review = readJson(
|
|
|
47
53
|
issues,
|
|
48
54
|
);
|
|
49
55
|
validateReview(review, handoffHash, issues);
|
|
56
|
+
validateCoverage(changeDir, review, issues);
|
|
57
|
+
try {
|
|
58
|
+
if (currentHandoffHash(changeDir) !== handoffHash) {
|
|
59
|
+
issues.push("冻结文档内容已变化,请刷新 handoff 并重新评审");
|
|
60
|
+
}
|
|
61
|
+
} catch (error) {
|
|
62
|
+
issues.push(error.message);
|
|
63
|
+
}
|
|
50
64
|
validateMermaid(changeDir, issues);
|
|
51
65
|
|
|
52
66
|
const result = {
|
|
@@ -67,7 +81,9 @@ print(result);
|
|
|
67
81
|
function validateReview(review, currentHash, targetIssues) {
|
|
68
82
|
if (!review) return;
|
|
69
83
|
if (review.schemaVersion !== "superflow.document-review.v1") {
|
|
70
|
-
targetIssues.push(
|
|
84
|
+
targetIssues.push(
|
|
85
|
+
"文档评审凭证 schemaVersion 必须为 superflow.document-review.v1",
|
|
86
|
+
);
|
|
71
87
|
}
|
|
72
88
|
if (review.handoffHash !== currentHash) {
|
|
73
89
|
targetIssues.push("文档评审凭证未绑定当前 handoff hash");
|
|
@@ -88,12 +104,18 @@ function validateReview(review, currentHash, targetIssues) {
|
|
|
88
104
|
"e2e-environment",
|
|
89
105
|
]);
|
|
90
106
|
for (const round of review.rounds) {
|
|
107
|
+
if (!round || !Array.isArray(round.findings)) {
|
|
108
|
+
targetIssues.push("评审轮必须显式提供 findings 数组");
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
91
111
|
requiredLenses.delete(round.lens);
|
|
92
112
|
if (round.inputHash !== currentHash) {
|
|
93
|
-
targetIssues.push(
|
|
113
|
+
targetIssues.push(
|
|
114
|
+
`第 ${round.round ?? "?"} 轮评审不是基于当前 handoff hash`,
|
|
115
|
+
);
|
|
94
116
|
}
|
|
95
117
|
const openFindings = (round.findings ?? []).filter(
|
|
96
|
-
(finding) => finding
|
|
118
|
+
(finding) => finding?.status !== "closed",
|
|
97
119
|
);
|
|
98
120
|
if (openFindings.length > 0) {
|
|
99
121
|
targetIssues.push(`第 ${round.round ?? "?"} 轮仍有未关闭评审发现`);
|
|
@@ -149,7 +171,9 @@ function validateMermaidBlock(block, targetIssues) {
|
|
|
149
171
|
if (arrows.length === 0) {
|
|
150
172
|
targetIssues.push("Mermaid sequenceDiagram 缺少有效消息箭头");
|
|
151
173
|
}
|
|
152
|
-
const openings = lines.filter((line) =>
|
|
174
|
+
const openings = lines.filter((line) =>
|
|
175
|
+
/^(alt|opt|loop|par|critical)\b/.test(line),
|
|
176
|
+
).length;
|
|
153
177
|
const endings = lines.filter((line) => line === "end").length;
|
|
154
178
|
if (openings !== endings) {
|
|
155
179
|
targetIssues.push("Mermaid sequenceDiagram 分支块未正确闭合");
|
|
@@ -107,20 +107,9 @@ superflow check <change> --level coding-ready
|
|
|
107
107
|
BLOCK_MSG
|
|
108
108
|
return 2
|
|
109
109
|
fi
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
with open(sys.argv[1], encoding="utf-8") as handle:
|
|
114
|
-
receipt = json.load(handle)
|
|
115
|
-
except Exception:
|
|
116
|
-
raise SystemExit(2)
|
|
117
|
-
if receipt.get("codingReady") is not True:
|
|
118
|
-
raise SystemExit(2)
|
|
119
|
-
if receipt.get("schemaVersion") != "superflow.coding-ready.v1":
|
|
120
|
-
raise SystemExit(2)
|
|
121
|
-
if receipt.get("handoffHash") != sys.argv[2]:
|
|
122
|
-
raise SystemExit(2)
|
|
123
|
-
PY
|
|
110
|
+
# Same validator as CLI/managed start. Source code is expected to change during
|
|
111
|
+
# implementation; only dispatch rechecks code snapshots, not every write.
|
|
112
|
+
node "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/superflow-review-coverage.mjs" "$change_dir"
|
|
124
113
|
if [ $? -ne 0 ]; then
|
|
125
114
|
cat <<'BLOCK_MSG' >&2
|
|
126
115
|
[SDD Coding Ready] 凭证已过期或与当前 handoff hash 不一致。
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import { createHash } from "node:crypto";
|
|
5
|
+
import { spawnSync } from "node:child_process";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
7
|
+
|
|
8
|
+
export const reviewPath = ".sdd/reviews/document-review.json";
|
|
9
|
+
export function fileHash(file) {
|
|
10
|
+
return createHash("sha256").update(fs.readFileSync(file)).digest("hex");
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// Reuse the handoff's canonical projection: progress checkboxes/report updates
|
|
14
|
+
// must not invalidate a frozen contract. Do not invent a second hash algorithm.
|
|
15
|
+
export function currentHandoffHash(root) {
|
|
16
|
+
const directory = path.dirname(fileURLToPath(import.meta.url));
|
|
17
|
+
const candidates = [
|
|
18
|
+
path.resolve(directory, "../skills/superflow-pipeline/scripts"),
|
|
19
|
+
path.resolve(directory, "../skills-en/superflow-pipeline/scripts"),
|
|
20
|
+
path.join(os.homedir(), ".codex/skills/superflow-pipeline/scripts"),
|
|
21
|
+
path.join(os.homedir(), ".claude/skills/superflow-pipeline/scripts"),
|
|
22
|
+
];
|
|
23
|
+
const script = candidates
|
|
24
|
+
.map((dir) => path.join(dir, "superflow-handoff.sh"))
|
|
25
|
+
.find((file) => fs.existsSync(file));
|
|
26
|
+
if (!script) throw new Error("缺少 handoff 脚本,请执行 superflow update");
|
|
27
|
+
const result = spawnSync("bash", [script, root, "--hash-only"], {
|
|
28
|
+
encoding: "utf-8",
|
|
29
|
+
timeout: 30000,
|
|
30
|
+
});
|
|
31
|
+
const hash = result.stdout?.trim();
|
|
32
|
+
if (result.status !== 0 || !/^[a-f0-9]{64}$/.test(hash ?? "")) {
|
|
33
|
+
throw new Error("无法重新计算 handoff hash,请检查冻结文档");
|
|
34
|
+
}
|
|
35
|
+
return hash;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const nonempty = (value) =>
|
|
39
|
+
typeof value === "string" && value.trim().length > 0;
|
|
40
|
+
const list = (value) => (Array.isArray(value) ? value : []);
|
|
41
|
+
|
|
42
|
+
// This validates trace structure and evidence identity, NOT business semantics.
|
|
43
|
+
export function validateCoverage(root, review, issues, checkCode = true) {
|
|
44
|
+
const coverage = review?.coverage;
|
|
45
|
+
if (coverage?.schemaVersion !== "superflow.review-coverage.v1") {
|
|
46
|
+
issues.push(
|
|
47
|
+
"缺少需求×入口评审 coverage;按 document-review-coverage.md 补齐并重新评审",
|
|
48
|
+
);
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
for (const field of [
|
|
52
|
+
"sources",
|
|
53
|
+
"requirements",
|
|
54
|
+
"entries",
|
|
55
|
+
"cases",
|
|
56
|
+
"decisions",
|
|
57
|
+
]) {
|
|
58
|
+
if (
|
|
59
|
+
!Array.isArray(coverage[field]) ||
|
|
60
|
+
coverage[field].some(
|
|
61
|
+
(item) => !item || typeof item !== "object" || Array.isArray(item),
|
|
62
|
+
)
|
|
63
|
+
) {
|
|
64
|
+
issues.push(`coverage.${field} 必须是对象数组`);
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
const index = (items, label) => {
|
|
69
|
+
const result = new Map();
|
|
70
|
+
if (!Array.isArray(items) || items.length === 0)
|
|
71
|
+
issues.push(`${label}不能为空`);
|
|
72
|
+
for (const item of list(items)) {
|
|
73
|
+
if (!nonempty(item?.id) || result.has(item.id)) {
|
|
74
|
+
issues.push(`${label}存在空白或重复 ID`);
|
|
75
|
+
} else result.set(item.id, item);
|
|
76
|
+
}
|
|
77
|
+
return result;
|
|
78
|
+
};
|
|
79
|
+
const sources = index(coverage.sources, "来源清单");
|
|
80
|
+
const requirements = index(coverage.requirements, "需求清单");
|
|
81
|
+
const entries = index(coverage.entries, "真实入口清单");
|
|
82
|
+
const cases = new Map();
|
|
83
|
+
for (const item of list(coverage.cases)) {
|
|
84
|
+
if (!nonempty(item?.id) || cases.has(item.id))
|
|
85
|
+
issues.push("验收用例存在空白或重复 ID");
|
|
86
|
+
else cases.set(item.id, item);
|
|
87
|
+
}
|
|
88
|
+
const refs = (values, role, label) => {
|
|
89
|
+
if (
|
|
90
|
+
!Array.isArray(values) ||
|
|
91
|
+
values.length === 0 ||
|
|
92
|
+
values.some((id) => !sources.has(id))
|
|
93
|
+
) {
|
|
94
|
+
issues.push(`${label}缺少有效来源引用`);
|
|
95
|
+
} else if (role && !values.some((id) => sources.get(id).role === role)) {
|
|
96
|
+
issues.push(`${label}缺少 ${role} 来源`);
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
for (const source of sources.values()) {
|
|
100
|
+
if (
|
|
101
|
+
!nonempty(source.path) ||
|
|
102
|
+
!/^[a-f0-9]{64}$/.test(source.sha256 ?? "") ||
|
|
103
|
+
!["requirement", "code", "contract"].includes(source.role)
|
|
104
|
+
) {
|
|
105
|
+
issues.push(`来源 ${source.id} 缺少路径、角色或 SHA-256`);
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
if (!checkCode && source.role === "code") continue;
|
|
109
|
+
try {
|
|
110
|
+
if (fileHash(path.resolve(root, source.path)) !== source.sha256) {
|
|
111
|
+
issues.push(`来源 ${source.id} 内容已变化,需要重新评审`);
|
|
112
|
+
}
|
|
113
|
+
} catch {
|
|
114
|
+
issues.push(`来源 ${source.id} 不可读取`);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
for (const req of requirements.values()) {
|
|
118
|
+
if (!nonempty(req.statement)) issues.push(`需求 ${req.id} 缺少原始语义`);
|
|
119
|
+
refs(req.sourceRefs, "requirement", `需求 ${req.id}`);
|
|
120
|
+
}
|
|
121
|
+
for (const entry of entries.values()) {
|
|
122
|
+
if (
|
|
123
|
+
!nonempty(entry.actor) ||
|
|
124
|
+
!nonempty(entry.route) ||
|
|
125
|
+
!["browser", "api", "job", "event", "library", "cli"].includes(entry.kind)
|
|
126
|
+
) {
|
|
127
|
+
issues.push(`入口 ${entry.id} 缺少角色、实际路由或入口类型`);
|
|
128
|
+
}
|
|
129
|
+
refs(entry.sourceRefs, "code", `入口 ${entry.id}`);
|
|
130
|
+
}
|
|
131
|
+
for (const test of cases.values()) {
|
|
132
|
+
const entry = entries.get(test.entryId);
|
|
133
|
+
if (!entry || !nonempty(test.action) || test.level !== entry.kind) {
|
|
134
|
+
issues.push(`用例 ${test.id} 未对应真实入口及验收等级`);
|
|
135
|
+
}
|
|
136
|
+
for (const key of ["response", "state", "forbiddenEffects"]) {
|
|
137
|
+
if (!nonempty(test.assertions?.[key]))
|
|
138
|
+
issues.push(`用例 ${test.id} 缺少 ${key} 断言`);
|
|
139
|
+
}
|
|
140
|
+
refs(test.sourceRefs, "contract", `用例 ${test.id}`);
|
|
141
|
+
}
|
|
142
|
+
const pairs = new Set();
|
|
143
|
+
for (const decision of list(coverage.decisions)) {
|
|
144
|
+
const key = JSON.stringify([decision?.requirementId, decision?.entryId]);
|
|
145
|
+
if (
|
|
146
|
+
!requirements.has(decision?.requirementId) ||
|
|
147
|
+
!entries.has(decision?.entryId) ||
|
|
148
|
+
pairs.has(key)
|
|
149
|
+
) {
|
|
150
|
+
issues.push("需求×入口存在未知或重复映射");
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
pairs.add(key);
|
|
154
|
+
if (
|
|
155
|
+
!["FIX", "VERIFY_EXISTING", "EXCLUDED"].includes(decision.disposition) ||
|
|
156
|
+
!nonempty(decision.rationale) ||
|
|
157
|
+
!nonempty(decision.currentBehavior) ||
|
|
158
|
+
!nonempty(decision.targetBehavior)
|
|
159
|
+
) {
|
|
160
|
+
issues.push(`${key} 缺少适用性结论、理由或当前/目标行为`);
|
|
161
|
+
}
|
|
162
|
+
refs(
|
|
163
|
+
decision.sourceRefs,
|
|
164
|
+
decision.disposition === "EXCLUDED" ? "requirement" : null,
|
|
165
|
+
key,
|
|
166
|
+
);
|
|
167
|
+
if (
|
|
168
|
+
decision.disposition !== "EXCLUDED" &&
|
|
169
|
+
(!Array.isArray(decision.caseIds) ||
|
|
170
|
+
decision.caseIds.length === 0 ||
|
|
171
|
+
decision.caseIds.some(
|
|
172
|
+
(id) => cases.get(id)?.entryId !== decision.entryId,
|
|
173
|
+
))
|
|
174
|
+
) {
|
|
175
|
+
issues.push(`${key} 缺少对应真实入口的验收用例`);
|
|
176
|
+
}
|
|
177
|
+
if (
|
|
178
|
+
decision.disposition === "EXCLUDED" &&
|
|
179
|
+
list(decision.caseIds).length > 0
|
|
180
|
+
) {
|
|
181
|
+
issues.push(`${key} 排除项不能同时声明已被验收覆盖`);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
const expected = [];
|
|
185
|
+
for (const req of requirements.keys())
|
|
186
|
+
for (const entry of entries.keys()) {
|
|
187
|
+
const pair = [req, entry];
|
|
188
|
+
expected.push(JSON.stringify(pair));
|
|
189
|
+
if (!pairs.has(JSON.stringify(pair)))
|
|
190
|
+
issues.push(`需求×入口缺少映射: ${req} / ${entry}`);
|
|
191
|
+
}
|
|
192
|
+
for (const lens of ["source-contract", "e2e-environment"]) {
|
|
193
|
+
const rounds = list(review.rounds).filter((round) => round?.lens === lens);
|
|
194
|
+
const reviewed = new Set(
|
|
195
|
+
rounds.flatMap((round) =>
|
|
196
|
+
list(round.reviewedPairs).map((pair) => JSON.stringify(pair)),
|
|
197
|
+
),
|
|
198
|
+
);
|
|
199
|
+
if (
|
|
200
|
+
expected.some((pair) => !reviewed.has(pair)) ||
|
|
201
|
+
[...reviewed].some((pair) => !pairs.has(pair))
|
|
202
|
+
) {
|
|
203
|
+
issues.push(
|
|
204
|
+
`${lens} 未逐项评审全部需求×入口(含 EXCLUDED/VERIFY_EXISTING)`,
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
export function checkReceipt(root, { checkCode = false } = {}) {
|
|
211
|
+
const issues = [];
|
|
212
|
+
try {
|
|
213
|
+
const receipt = JSON.parse(
|
|
214
|
+
fs.readFileSync(
|
|
215
|
+
path.join(root, ".sdd/readiness/coding-ready.json"),
|
|
216
|
+
"utf8",
|
|
217
|
+
),
|
|
218
|
+
);
|
|
219
|
+
const state = fs.readFileSync(path.join(root, ".sdd/state.yaml"), "utf8");
|
|
220
|
+
const stateHash = state.match(
|
|
221
|
+
/^handoff_hash:\s*["']?([a-f0-9]{64})["']?\s*$/m,
|
|
222
|
+
)?.[1];
|
|
223
|
+
if (
|
|
224
|
+
receipt.schemaVersion !== "superflow.coding-ready.v1" ||
|
|
225
|
+
receipt.codingReady !== true ||
|
|
226
|
+
receipt.handoffHash !== stateHash ||
|
|
227
|
+
receipt.handoffHash !== currentHandoffHash(root) ||
|
|
228
|
+
receipt.reviewHash !== fileHash(path.join(root, reviewPath))
|
|
229
|
+
) {
|
|
230
|
+
issues.push("Coding Ready 内容已过期或缺少评审指纹");
|
|
231
|
+
}
|
|
232
|
+
const review = JSON.parse(
|
|
233
|
+
fs.readFileSync(path.join(root, reviewPath), "utf8"),
|
|
234
|
+
);
|
|
235
|
+
validateCoverage(root, review, issues, checkCode);
|
|
236
|
+
} catch {
|
|
237
|
+
issues.push("Coding Ready 凭证或当前合同不可验证");
|
|
238
|
+
}
|
|
239
|
+
return issues;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
if (
|
|
243
|
+
process.argv[1] &&
|
|
244
|
+
fs.realpathSync(process.argv[1]) === fileURLToPath(import.meta.url)
|
|
245
|
+
) {
|
|
246
|
+
const root = process.argv[2];
|
|
247
|
+
const issues = root
|
|
248
|
+
? checkReceipt(path.resolve(root), {
|
|
249
|
+
checkCode: process.argv.includes("--check-sources"),
|
|
250
|
+
})
|
|
251
|
+
: ["缺少 change 目录"];
|
|
252
|
+
if (issues.length) {
|
|
253
|
+
process.stderr.write(
|
|
254
|
+
`${issues.join("\n")}\n请重新评审并执行 superflow check <change> --level coding-ready\n`,
|
|
255
|
+
);
|
|
256
|
+
process.exitCode = 1;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
@@ -5,6 +5,11 @@ description: Use when confirmed OpenSpec artifacts need SDD contract docs such a
|
|
|
5
5
|
|
|
6
6
|
# SDD Docs
|
|
7
7
|
|
|
8
|
+
完整文档交付必须读取并执行
|
|
9
|
+
[需求×真实入口评审](../superflow-pipeline/references/document-review-coverage.md):
|
|
10
|
+
扩展现有三轮评审凭证,逐项核对原始需求、真实调用分支、EXCLUDED/VERIFY_EXISTING
|
|
11
|
+
及验收断言。禁止用“文档内部一致”代替对用户原始语义的核验。
|
|
12
|
+
|
|
8
13
|
Use this skill after a feature is frozen or after `$openspec-propose` has
|
|
9
14
|
created the base OpenSpec artifacts. This skill is an SDD contract-doc wrapper;
|
|
10
15
|
it must not replace the installed OpenSpec skills for core artifact generation
|
|
@@ -233,7 +233,7 @@ SDD 的核心价值是可追溯事实链、接口验证、hook 质量监控、
|
|
|
233
233
|
|
|
234
234
|
**强制记录:**
|
|
235
235
|
|
|
236
|
-
- `sdd-quality-gate.md`
|
|
236
|
+
- `sdd-quality-gate.md` 记录 handoff 文件路径和生成命令;hash 仅绑定在 `.sdd/state.yaml`,不在正文重复回填。任务勾选和测试报告属于执行进度,不触发合同 hash 更新。
|
|
237
237
|
- 需求澄清、方案讨论或设计路线仍在变化时,必须增量维护
|
|
238
238
|
`.sdd/handoff/brainstorm-summary.md`,把已确认结论、候选方案、待确认问题、
|
|
239
239
|
放弃方案和下一步决策点分开记录。它是压缩恢复检查点,不替代 OpenSpec/SDD
|
|
@@ -243,12 +243,14 @@ SDD 的核心价值是可追溯事实链、接口验证、hook 质量监控、
|
|
|
243
243
|
手动压缩或明确确认继续。确认结果写入 `.sdd/state.yaml` 的
|
|
244
244
|
`context_compression` 相关记录或 `sdd-quality-gate.md`。
|
|
245
245
|
- `design.md` 的 "Superpowers Technical Design Handoff" 必须引用
|
|
246
|
-
`technical_design
|
|
247
|
-
WHAT/API/DB/tests 的事实源。
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
246
|
+
`technical_design`,并声明 OpenSpec/SDD 文档仍是
|
|
247
|
+
WHAT/API/DB/tests 的事实源。handoff hash 的绑定关系由 `.sdd/state.yaml`
|
|
248
|
+
的 `handoff_hash` 字段单点维护,不要求把原始 hash 抄进正文文档。
|
|
249
|
+
- 每个实现 prompt 必须引用 `.sdd/handoff/sdd-context.md`;继承校验以
|
|
250
|
+
`.sdd/state.yaml` 的 `handoff_hash` 与
|
|
251
|
+
`.sdd/handoff/sdd-context.sha256` 一致为准。
|
|
252
|
+
- `test-report.md` 必须记录本批次的 status 阶段和执行过的
|
|
253
|
+
SDD hook/guard 脚本;无需手工维护 handoff hash。
|
|
252
254
|
|
|
253
255
|
**阻塞规则:**
|
|
254
256
|
|
|
@@ -258,7 +260,7 @@ SDD 的核心价值是可追溯事实链、接口验证、hook 质量监控、
|
|
|
258
260
|
confirmed/candidate/pending/rejected,阻塞创建最终设计。
|
|
259
261
|
- `brainstorm-summary.md` 已定稿但未经过主动压缩门禁或用户确认继续,阻塞进入
|
|
260
262
|
`design.md` 生成/重写。
|
|
261
|
-
- `
|
|
263
|
+
- `.sdd/state.yaml` 的 `handoff_hash` 与
|
|
262
264
|
`.sdd/handoff/sdd-context.sha256` 不一致,阻塞。
|
|
263
265
|
- 会话压缩、换 agent、切 worktree 或并行窗口后,只凭聊天记忆继续设计/编码,
|
|
264
266
|
未先读 handoff 和原始文档,阻塞。
|
|
@@ -23,6 +23,10 @@ applicability:
|
|
|
23
23
|
|
|
24
24
|
## 三轮评审凭证
|
|
25
25
|
|
|
26
|
+
下方为基础字段示例,不是完整凭证。0.5.10 起必须同时按
|
|
27
|
+
[需求×真实入口评审](document-review-coverage.md) 添加 `coverage`,以及 source-contract/
|
|
28
|
+
e2e-environment 轮的 `reviewedPairs`。旧凭证需要补齐并重新评审。
|
|
29
|
+
|
|
26
30
|
文件:`.sdd/reviews/document-review.json`
|
|
27
31
|
|
|
28
32
|
```json
|