@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.
Files changed (53) hide show
  1. package/INSTALL.md +1 -2
  2. package/README.md +5 -0
  3. package/README.zh-CN.md +5 -0
  4. package/assets/manifest.json +1 -0
  5. package/assets/scripts/superflow-coding-ready.mjs +88 -19
  6. package/assets/scripts/superflow-document-audit.mjs +29 -5
  7. package/assets/scripts/superflow-hook-guard.sh +3 -14
  8. package/assets/scripts/superflow-review-coverage.mjs +258 -0
  9. package/assets/skills/superflow-docs/SKILL.md +5 -0
  10. package/assets/skills/superflow-pipeline/SKILL.md +10 -8
  11. package/assets/skills/superflow-pipeline/references/document-readiness.md +4 -0
  12. package/assets/skills/superflow-pipeline/references/document-review-coverage.md +82 -0
  13. package/assets/skills/superflow-pipeline/references/implementation-prompt-template.md +8 -8
  14. package/assets/skills/superflow-pipeline/references/quality-gate.md +4 -4
  15. package/assets/skills/superflow-pipeline/references/quality-standards.md +1 -1
  16. package/assets/skills/superflow-pipeline/references/superpower-technical-design-template.md +2 -2
  17. package/assets/skills/superflow-pipeline/references/test-execution-template.md +6 -0
  18. package/assets/skills/superflow-pipeline/scripts/superflow-guard.sh +7 -5
  19. package/assets/skills/superflow-pipeline/scripts/superflow-handoff.sh +15 -2
  20. package/assets/skills/superflow-requirement-review/SKILL.md +4 -0
  21. package/assets/skills/superflow-verify/SKILL.md +4 -0
  22. package/assets/skills-en/superflow-docs/SKILL.md +6 -0
  23. package/assets/skills-en/superflow-pipeline/SKILL.md +4 -4
  24. package/assets/skills-en/superflow-pipeline/references/batch-prompt-template.md +1 -1
  25. package/assets/skills-en/superflow-pipeline/references/document-readiness.md +4 -0
  26. package/assets/skills-en/superflow-pipeline/references/document-review-coverage.md +89 -0
  27. package/assets/skills-en/superflow-pipeline/references/implementation-prompt-template.md +4 -3
  28. package/assets/skills-en/superflow-pipeline/references/quality-gate.md +1 -1
  29. package/assets/skills-en/superflow-pipeline/references/superpower-technical-design-template.md +1 -2
  30. package/assets/skills-en/superflow-pipeline/references/test-execution-template.md +9 -0
  31. package/assets/skills-en/superflow-pipeline/scripts/superflow-guard.sh +7 -5
  32. package/assets/skills-en/superflow-pipeline/scripts/superflow-handoff.sh +15 -2
  33. package/assets/skills-en/superflow-requirement-review/SKILL.md +5 -0
  34. package/assets/skills-en/superflow-verify/SKILL.md +5 -0
  35. package/dist/app/commands/init.js +4 -5
  36. package/dist/app/commands/init.js.map +1 -1
  37. package/dist/domains/config/cli-help.js +2 -2
  38. package/dist/domains/config/cli-help.js.map +1 -1
  39. package/dist/domains/managed-work/cleanup.js +6 -2
  40. package/dist/domains/managed-work/cleanup.js.map +1 -1
  41. package/dist/domains/managed-work/contract.js +4 -1
  42. package/dist/domains/managed-work/contract.js.map +1 -1
  43. package/dist/domains/managed-work/gitignore.js +29 -0
  44. package/dist/domains/managed-work/gitignore.js.map +1 -0
  45. package/dist/domains/managed-work/runner.js +56 -0
  46. package/dist/domains/managed-work/runner.js.map +1 -1
  47. package/dist/domains/managed-work/storage.js +6 -0
  48. package/dist/domains/managed-work/storage.js.map +1 -1
  49. package/dist/domains/sdd-readiness.js +9 -0
  50. package/dist/domains/sdd-readiness.js.map +1 -1
  51. package/dist/domains/skill/deploy.js +2 -9
  52. package/dist/domains/skill/deploy.js.map +1 -1
  53. 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 --overwrite` | 覆盖已安装的 SuperBridge Flow 技能 |
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 次执行、
@@ -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
- ["YAML 状态", "bash", [path.join(pipelineScripts, "superflow-yaml-validate.sh"), changeDir]],
22
- ["docs 门禁", "bash", [path.join(pipelineScripts, "superflow-guard.sh"), changeDir, "docs"]],
23
- ["design 门禁", "bash", [path.join(pipelineScripts, "superflow-guard.sh"), changeDir, "design"]],
24
- ["implement 门禁", "bash", [path.join(pipelineScripts, "superflow-guard.sh"), changeDir, "implement"]],
25
- ["文档交付审计", process.execPath, [path.join(scriptDir, "superflow-document-audit.mjs"), changeDir, "--json"]],
26
- ["环境预检", process.execPath, [path.join(scriptDir, "superflow-environment-preflight.mjs"), changeDir, "--json"]],
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("openspec", ["validate", path.basename(changeDir), "--strict"], {
44
- cwd: projectRoot(changeDir),
45
- encoding: "utf-8",
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(`OpenSpec strict 失败:${tail(openspec.stderr || openspec.stdout)}`);
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(receiptDir, `.coding-ready-${process.pid}-${Date.now()}.tmp`);
73
- fs.writeFileSync(temporary, `${JSON.stringify(receipt, null, 2)}\n`, { mode: 0o600 });
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(currentScriptDir, "..", "skills", "superflow-pipeline", "scripts"),
85
- path.join(os.homedir(), ".codex", "skills", "superflow-pipeline", "scripts"),
86
- path.join(os.homedir(), ".claude", "skills", "superflow-pipeline", "scripts"),
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(targetIssues, receipt = { codingReady: false, issues: targetIssues }) {
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.relative(changeDir, prompt).replaceAll("\\", "/");
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("文档评审凭证 schemaVersion 必须为 superflow.document-review.v1");
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(`第 ${round.round ?? "?"} 轮评审不是基于当前 handoff hash`);
113
+ targetIssues.push(
114
+ `第 ${round.round ?? "?"} 轮评审不是基于当前 handoff hash`,
115
+ );
94
116
  }
95
117
  const openFindings = (round.findings ?? []).filter(
96
- (finding) => finding.status !== "closed",
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) => /^(alt|opt|loop|par|critical)\b/.test(line)).length;
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
- python3 - "$receipt" "$expected_hash" <<'PY'
111
- import json, sys
112
- try:
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` 必须记录 handoff 文件路径、hash、生成命令和生成时间。
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` 和 handoff hash,并声明 OpenSpec/SDD 文档仍是
247
- WHAT/API/DB/tests 的事实源。
248
- - 每个实现 prompt 必须引用 `.sdd/handoff/sdd-context.md`,写明继承的
249
- `handoff_hash`,并要求 agent 在编码前核对 hash 是否与当前文档一致。
250
- - `test-report.md` 必须记录本批次使用的 handoff hash、状态阶段和执行过的
251
- SDD hook/guard 脚本。
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
- - `design.md`/prompt/test-report 中记录的 handoff hash 与
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