@chenmk/superflow 0.5.12 → 0.5.14

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 (97) hide show
  1. package/README.md +26 -0
  2. package/README.zh-CN.md +8 -0
  3. package/assets/manifest.json +33 -7
  4. package/assets/review-rules.json +24 -0
  5. package/assets/scripts/superflow-dependency-update-hook.sh +29 -0
  6. package/assets/scripts/superflow-doc-placement-hook.py +38 -0
  7. package/assets/scripts/superflow-java-build-hook.py +178 -0
  8. package/assets/scripts/superflow-java-stop-hook.sh +3 -0
  9. package/assets/scripts/superflow-loop-end-hook.sh +3 -0
  10. package/assets/scripts/superflow-loop-failure-hook.sh +3 -0
  11. package/assets/scripts/superflow-loop-observer.py +149 -0
  12. package/assets/scripts/superflow-sql-sync-hook.py +141 -3
  13. package/assets/scripts/superflow-test-report-lint.py +80 -2
  14. package/assets/skill-registry.json +229 -0
  15. package/assets/skills/openspec-propose/SKILL.md +14 -0
  16. package/assets/skills/superflow-clarify/SKILL.md +23 -1
  17. package/assets/skills/superflow-design/SKILL.md +6 -1
  18. package/assets/skills/superflow-docs/SKILL.md +24 -0
  19. package/assets/skills/superflow-implement/SKILL.md +8 -0
  20. package/assets/skills/superflow-pipeline/SKILL.md +31 -1
  21. package/assets/skills/superflow-pipeline/references/document-review-coverage.md +18 -0
  22. package/assets/skills/superflow-pipeline/references/superpower-technical-design-template.md +17 -0
  23. package/assets/skills/superflow-pipeline/references/test-execution-template.md +11 -0
  24. package/assets/skills/superflow-pipeline/scripts/superflow-guard.sh +8 -2
  25. package/assets/skills/superflow-quick-dev/SKILL.md +44 -0
  26. package/assets/skills/superflow-verify/SKILL.md +45 -1
  27. package/assets/skills-en/openspec-propose/SKILL.md +14 -0
  28. package/assets/skills-en/superflow-clarify/SKILL.md +23 -1
  29. package/assets/skills-en/superflow-design/SKILL.md +6 -1
  30. package/assets/skills-en/superflow-docs/SKILL.md +24 -0
  31. package/assets/skills-en/superflow-implement/SKILL.md +9 -0
  32. package/assets/skills-en/superflow-pipeline/SKILL.md +34 -0
  33. package/assets/skills-en/superflow-pipeline/references/document-review-coverage.md +20 -0
  34. package/assets/skills-en/superflow-pipeline/references/test-execution-template.md +12 -0
  35. package/assets/skills-en/superflow-pipeline/scripts/superflow-guard.sh +8 -2
  36. package/assets/skills-en/superflow-quick-dev/SKILL.md +43 -0
  37. package/assets/skills-en/superflow-verify/SKILL.md +52 -1
  38. package/dist/app/cli.js +98 -0
  39. package/dist/app/cli.js.map +1 -1
  40. package/dist/app/commands/check.js +7 -3
  41. package/dist/app/commands/check.js.map +1 -1
  42. package/dist/app/commands/cleanup-audit.js +13 -0
  43. package/dist/app/commands/cleanup-audit.js.map +1 -0
  44. package/dist/app/commands/context-audit.js +43 -0
  45. package/dist/app/commands/context-audit.js.map +1 -0
  46. package/dist/app/commands/docs-audit.js +11 -0
  47. package/dist/app/commands/docs-audit.js.map +1 -0
  48. package/dist/app/commands/doctor.js +10 -0
  49. package/dist/app/commands/doctor.js.map +1 -1
  50. package/dist/app/commands/hook-migrate.js +25 -0
  51. package/dist/app/commands/hook-migrate.js.map +1 -0
  52. package/dist/app/commands/init.js +9 -19
  53. package/dist/app/commands/init.js.map +1 -1
  54. package/dist/app/commands/map-audit.js +10 -0
  55. package/dist/app/commands/map-audit.js.map +1 -0
  56. package/dist/app/commands/pipeline.js +13 -1
  57. package/dist/app/commands/pipeline.js.map +1 -1
  58. package/dist/app/commands/quick.js +136 -0
  59. package/dist/app/commands/quick.js.map +1 -0
  60. package/dist/app/commands/review-coverage.js +84 -0
  61. package/dist/app/commands/review-coverage.js.map +1 -0
  62. package/dist/app/commands/skill-audit.js +36 -0
  63. package/dist/app/commands/skill-audit.js.map +1 -0
  64. package/dist/app/commands/status.js +108 -7
  65. package/dist/app/commands/status.js.map +1 -1
  66. package/dist/app/commands/update.js +9 -12
  67. package/dist/app/commands/update.js.map +1 -1
  68. package/dist/domains/cleanup-audit.js +38 -0
  69. package/dist/domains/cleanup-audit.js.map +1 -0
  70. package/dist/domains/config/cli-help.js +14 -0
  71. package/dist/domains/config/cli-help.js.map +1 -1
  72. package/dist/domains/config/context.js +13 -10
  73. package/dist/domains/config/context.js.map +1 -1
  74. package/dist/domains/context-audit.js +39 -0
  75. package/dist/domains/context-audit.js.map +1 -0
  76. package/dist/domains/deps.js +18 -8
  77. package/dist/domains/deps.js.map +1 -1
  78. package/dist/domains/docs-audit.js +34 -0
  79. package/dist/domains/docs-audit.js.map +1 -0
  80. package/dist/domains/hook-migration.js +127 -0
  81. package/dist/domains/hook-migration.js.map +1 -0
  82. package/dist/domains/hook.js +76 -2
  83. package/dist/domains/hook.js.map +1 -1
  84. package/dist/domains/managed-work/control.js +56 -2
  85. package/dist/domains/managed-work/control.js.map +1 -1
  86. package/dist/domains/map-audit.js +23 -0
  87. package/dist/domains/map-audit.js.map +1 -0
  88. package/dist/domains/quick-dev/gate.js +91 -0
  89. package/dist/domains/quick-dev/gate.js.map +1 -0
  90. package/dist/domains/review/coverage.js +119 -0
  91. package/dist/domains/review/coverage.js.map +1 -0
  92. package/dist/domains/review/report.js +62 -0
  93. package/dist/domains/review/report.js.map +1 -0
  94. package/dist/domains/skill/registry.js +87 -0
  95. package/dist/domains/skill/registry.js.map +1 -0
  96. package/package.json +1 -1
  97. package/scripts/check-design-principles.mjs +4 -0
@@ -64,6 +64,7 @@ per finding for the developer to apply):
64
64
  import json
65
65
  import os
66
66
  import re
67
+ import shlex
67
68
  import subprocess
68
69
  import sys
69
70
  from pathlib import Path
@@ -75,6 +76,15 @@ if not (os.path.isdir('openspec') or os.path.isdir('.sdd') or os.path.isfile('.s
75
76
 
76
77
 
77
78
  DB_CODE_FILE = re.compile(r"\.(java|xml)$", re.I)
79
+ MYBATIS_SAFE_EXPRESSIONS = frozenset({
80
+ "ew.sqlSegment", "ew.customSqlSegment", "ew.sqlSet", "ew.sqlComment",
81
+ })
82
+ MYBATIS_INTERPOLATION = re.compile(r"\$\{([^}]+)\}")
83
+ SECRET_PATTERNS = (
84
+ re.compile(r"\bAKIA[0-9A-Z]{16}\b"),
85
+ re.compile(r"\b(?:api[_-]?key|api[_-]?secret)\s*[:=]\s*['\"][^'\"]{8,}", re.I),
86
+ re.compile(r"\b[a-z_][a-z0-9_]*_(?:secret|token)\s*[:=]\s*['\"][^'\"]{16,}['\"]", re.I),
87
+ )
78
88
  SQL_SUMMARY_FILE = re.compile(r"(^|/)sql/.*\.sql$", re.I)
79
89
  NO_SQL_ACK_FILE = re.compile(
80
90
  r"(^|/)(openspec/.*\.(md|yaml|yml)|ReleaseNotes\.md)$",
@@ -514,10 +524,113 @@ def warn(message, block=False):
514
524
  print(f"[SDD SQL 收口{prefix}] {message}", file=sys.stderr)
515
525
 
516
526
 
527
+ def mapper_interpolations(file_path, content):
528
+ normalized = file_path.replace("\\", "/")
529
+ if not normalized.lower().endswith(".xml"):
530
+ return []
531
+ mapper_path = re.search(r"(?:^|/)(?:mapper|dao)/", normalized, re.I)
532
+ mapper_name = re.search(r"(?:Mapper|Dao)\.xml$", normalized)
533
+ mapper_body = re.search(r"<mapper\b|<!DOCTYPE\s+mapper", content, re.I)
534
+ if not (mapper_path or mapper_name or mapper_body):
535
+ return []
536
+ return [match.group(1).strip() for match in MYBATIS_INTERPOLATION.finditer(content)
537
+ if match.group(1).strip() not in MYBATIS_SAFE_EXPRESSIONS]
538
+
539
+
540
+ def git_segment_args(segment):
541
+ tokens = shlex.split(segment)
542
+ git_at = next((i for i, token in enumerate(tokens)
543
+ if token == "git" or token.endswith("/git")), -1)
544
+ if git_at < 0:
545
+ return None
546
+ return tokens[:git_at], tokens[git_at + 1:]
547
+
548
+
549
+ def git_subcommand_index(args):
550
+ index = 0
551
+ while index < len(args) and args[index].startswith("-"):
552
+ index += 2 if args[index] in ("-c", "-C") else 1
553
+ return index
554
+
555
+
556
+ def check_git_bypass(command):
557
+ for segment in re.split(r"\|\||&&|[;|\n]", command):
558
+ try:
559
+ parsed = git_segment_args(segment)
560
+ except ValueError:
561
+ if "git" in segment and ("--no-verify" in segment or "core.hooksPath" in segment):
562
+ return "Git 命令无法解析且包含 Hook 绕过参数"
563
+ continue
564
+ if parsed is None:
565
+ continue
566
+ prefix, args = parsed
567
+ config_override = any(
568
+ arg.startswith("core.hooksPath=") for arg in args
569
+ ) or any(token.startswith("GIT_CONFIG_") and "core.hooksPath" in token
570
+ for token in prefix)
571
+ if config_override or "--no-verify" in args:
572
+ return "禁止使用 --no-verify 或 core.hooksPath 绕过 Git Hook"
573
+ index = git_subcommand_index(args)
574
+ if index < len(args) and args[index] == "commit":
575
+ flags = []
576
+ skip_value = False
577
+ for arg in args[index + 1:]:
578
+ if skip_value:
579
+ skip_value = False
580
+ continue
581
+ if arg in ("-m", "--message", "-F", "--file"):
582
+ skip_value = True
583
+ continue
584
+ flags.append(arg)
585
+ if any(re.fullmatch(r"-[a-zA-Z]*n[a-zA-Z]*", arg)
586
+ for arg in flags):
587
+ return "禁止使用 git commit -n 绕过 Git Hook"
588
+ return None
589
+
590
+
591
+ def is_git_commit(command):
592
+ for segment in re.split(r"\|\||&&|[;|\n]", command):
593
+ try:
594
+ parsed = git_segment_args(segment)
595
+ except ValueError:
596
+ continue
597
+ if parsed is None:
598
+ continue
599
+ _, args = parsed
600
+ index = git_subcommand_index(args)
601
+ if index < len(args) and args[index] == "commit":
602
+ return True
603
+ return False
604
+
605
+
606
+ def check_staged_secrets(root):
607
+ result = run(["git", "diff", "--cached", "--unified=0", "--no-ext-diff"], cwd=root)
608
+ if result.returncode != 0:
609
+ return []
610
+ findings = []
611
+ current_path = ""
612
+ for line in result.stdout.splitlines():
613
+ if line.startswith("+++ b/"):
614
+ current_path = line[6:]
615
+ elif line.startswith("+") and not line.startswith("+++"):
616
+ if any(pattern.search(line[1:]) for pattern in SECRET_PATTERNS):
617
+ findings.append(current_path)
618
+ return sorted(set(findings))
619
+
620
+
517
621
  def handle_commit(command, root):
518
- if not re.search(r"\bgit\s+commit\b", command):
622
+ bypass = check_git_bypass(command)
623
+ if bypass:
624
+ warn(bypass, block=True)
625
+ return 2
626
+ if not is_git_commit(command):
519
627
  return 0
520
628
 
629
+ secrets = check_staged_secrets(root)
630
+ if secrets:
631
+ warn("暂存差异包含疑似硬编码密钥:" + ", ".join(secrets), block=True)
632
+ return 2
633
+
521
634
  files = staged_files(root)
522
635
  if not files:
523
636
  return 0
@@ -595,6 +708,11 @@ def handle_commit(command, root):
595
708
  def handle_edit(file_path, edit_content):
596
709
  if not file_path:
597
710
  return 0
711
+ unsafe = mapper_interpolations(file_path, edit_content)
712
+ if unsafe:
713
+ warn("Mapper XML 使用危险 ${} 插值:" + ", ".join(unsafe[:3])
714
+ + ";请使用 #{},动态标识符须在 Java 侧白名单校验。", block=True)
715
+ return 2
598
716
  if SQL_SUMMARY_FILE.search(file_path):
599
717
  sql_style_issues = lint_sql_style(file_path, edit_content)
600
718
  if sql_style_issues:
@@ -944,7 +1062,9 @@ def main():
944
1062
 
945
1063
  start = Path.cwd()
946
1064
  if file_path:
947
- start = Path(file_path).expanduser().resolve().parent
1065
+ candidate = Path(file_path).expanduser().resolve().parent
1066
+ if candidate.exists():
1067
+ start = candidate
948
1068
  root = repo_root(start)
949
1069
  if root is None:
950
1070
  return 0
@@ -952,7 +1072,25 @@ def main():
952
1072
  if command:
953
1073
  return handle_commit(command, root)
954
1074
 
955
- edit_content = tool_input.get("new_string") or tool_input.get("content") or ""
1075
+ patch = tool_input.get("patch") or ""
1076
+ if patch:
1077
+ sections = re.split(r"(?=^\*\*\* (?:Add|Update) File: )", patch, flags=re.M)
1078
+ for section in sections:
1079
+ target = re.search(r"^\*\*\* (?:Add|Update) File: (.+)$", section, re.M)
1080
+ if not target:
1081
+ continue
1082
+ added = "\n".join(line[1:] for line in section.splitlines()
1083
+ if line.startswith("+") and not line.startswith("+++"))
1084
+ if handle_edit(target.group(1), added) == 2:
1085
+ return 2
1086
+ return 0
1087
+
1088
+ edits = tool_input.get("edits")
1089
+ if isinstance(edits, list):
1090
+ edit_content = "\n".join(str(edit.get("new_string") or "")
1091
+ for edit in edits if isinstance(edit, dict))
1092
+ else:
1093
+ edit_content = tool_input.get("new_string") or tool_input.get("content") or ""
956
1094
  return handle_edit(file_path, edit_content)
957
1095
 
958
1096
 
@@ -14,6 +14,7 @@ mechanically:
14
14
  from __future__ import annotations
15
15
 
16
16
  import argparse
17
+ import json
17
18
  import re
18
19
  import subprocess
19
20
  import sys
@@ -99,6 +100,13 @@ EXTERNAL_EVIDENCE_RE = re.compile(
99
100
  re.I,
100
101
  )
101
102
  CASE_ID_RE = re.compile(r"\bT\d+(?:\.\d+)+\b")
103
+ CASE_EVIDENCE_HEADER = re.compile(
104
+ r"^\|\s*(?:用例\s*ID|Case\s*ID)\s*\|\s*(?:入口\s*ID|Entry\s*ID)\s*\|"
105
+ r"\s*(?:验收级别|Acceptance\s*level)\s*\|\s*(?:结果|Result)\s*\|"
106
+ r"\s*(?:证据|Evidence)(?:路径|\s*path)?\s*\|$",
107
+ re.I,
108
+ )
109
+ CASE_RESULTS = {"PASS", "FAIL", "BLOCKED", "PARTIAL"}
102
110
  OLD_PHRASES = (
103
111
  "不会按周期边界拆分子切片",
104
112
  "1 秒空窗落入下一周期对账无实质影响",
@@ -253,6 +261,69 @@ def lint_against_tests_contract(report_path: Path, report_text: str, tests_path:
253
261
  return issues
254
262
 
255
263
 
264
+ def lint_case_evidence(report_path: Path, report_text: str, review_path: Path) -> list[Issue]:
265
+ """Check the frozen R/E/C index against per-case execution receipts."""
266
+ issues: list[Issue] = []
267
+ try:
268
+ review = json.loads(review_path.read_text(encoding="utf-8"))
269
+ coverage = review["coverage"]
270
+ if coverage["schemaVersion"] != "superflow.review-coverage.v1":
271
+ raise ValueError("unsupported coverage schema")
272
+ cases = {case["id"]: case for case in coverage["cases"]}
273
+ decisions = coverage["decisions"]
274
+ except (OSError, ValueError, KeyError, TypeError) as error:
275
+ return [Issue("FAIL", report_path, 0, f"入口评审凭证无法读取:{error}")]
276
+
277
+ required = {
278
+ case_id
279
+ for decision in decisions
280
+ if decision.get("disposition") != "EXCLUDED"
281
+ for case_id in decision.get("caseIds", [])
282
+ }
283
+ lines = report_text.splitlines()
284
+ header_at = next(
285
+ (i for i, line in enumerate(lines) if CASE_EVIDENCE_HEADER.fullmatch(line.strip())),
286
+ None,
287
+ )
288
+ if header_at is None:
289
+ return [Issue("FAIL", report_path, 0, "缺少逐用例执行证据表;不能用汇总通过数代替 R/E/C 回填")]
290
+
291
+ receipts: dict[str, tuple[str, str, str, str]] = {}
292
+ for index, line in enumerate(lines[header_at + 2 :], start=header_at + 3):
293
+ if not line.startswith("|"):
294
+ break
295
+ columns = [column.strip() for column in line.strip().strip("|").split("|")]
296
+ if len(columns) != 5:
297
+ issues.append(Issue("FAIL", report_path, index, "逐用例执行证据表必须有五列"))
298
+ continue
299
+ case_id, entry_id, level, result, evidence = columns
300
+ if case_id in receipts:
301
+ issues.append(Issue("FAIL", report_path, index, f"用例 {case_id} 重复回填"))
302
+ receipts[case_id] = (entry_id, level, result.upper(), evidence)
303
+
304
+ for case_id in sorted(required):
305
+ if case_id not in receipts:
306
+ issues.append(Issue("FAIL", report_path, 0, f"用例 {case_id} 缺少逐项执行结果"))
307
+ continue
308
+ entry_id, level, result, evidence = receipts[case_id]
309
+ case = cases.get(case_id)
310
+ if case is None or entry_id != case.get("entryId") or level != case.get("level"):
311
+ issues.append(Issue("FAIL", report_path, 0, f"用例 {case_id} 的入口或验收级别与冻结合同不一致"))
312
+ if result not in CASE_RESULTS or not evidence or evidence.lower() in {"pending", "todo", "待补"}:
313
+ issues.append(Issue("FAIL", report_path, 0, f"用例 {case_id} 缺少有效结果或证据位置"))
314
+ elif result == "PASS" and not re.match(r"^https?://", evidence, re.I):
315
+ evidence_file = Path(evidence.split("#", 1)[0])
316
+ if not evidence_file.is_absolute():
317
+ evidence_file = report_path.parent / evidence_file
318
+ if not evidence_file.is_file():
319
+ issues.append(Issue("FAIL", report_path, 0, f"用例 {case_id} 的通过证据文件不存在:{evidence}"))
320
+ if result != "PASS" and re.search(r"验证结果\s*[::]\s*PASS|Verification Result\s*[::]\s*PASS", report_text, re.I):
321
+ issues.append(Issue("FAIL", report_path, 0, f"用例 {case_id} 为 {result},报告不能声明整体验证 PASS"))
322
+ for case_id in receipts.keys() - required:
323
+ issues.append(Issue("FAIL", report_path, 0, f"用例 {case_id} 未在冻结入口评审中要求执行"))
324
+ return issues
325
+
326
+
256
327
  def infer_tests_path(report: Path) -> Path | None:
257
328
  direct = report.parent / "tests.md"
258
329
  if direct.exists():
@@ -268,7 +339,8 @@ def infer_tests_path(report: Path) -> Path | None:
268
339
  return None
269
340
 
270
341
 
271
- def lint_report(path: Path, repo: Path | None, tests_path: Path | None = None) -> list[Issue]:
342
+ def lint_report(path: Path, repo: Path | None, tests_path: Path | None = None,
343
+ review_path: Path | None = None) -> list[Issue]:
272
344
  issues: list[Issue] = []
273
345
  text = path.read_text(encoding="utf-8", errors="replace")
274
346
  lines = text.splitlines()
@@ -278,6 +350,8 @@ def lint_report(path: Path, repo: Path | None, tests_path: Path | None = None) -
278
350
  blocked = bool(BLOCKED_RE.search(text))
279
351
  passed_or_partial = bool(PASS_WORDS.search(text) or re.search(r"Partially verified|部分验证", text, re.I))
280
352
  issues.extend(lint_against_tests_contract(path, text, tests_path or infer_tests_path(path)))
353
+ if review_path is not None:
354
+ issues.extend(lint_case_evidence(path, text, review_path))
281
355
 
282
356
  if db_backed and passed_or_partial and not blocked:
283
357
  if not TABLE_REVERSE_HEADER_RE.search(text):
@@ -395,6 +469,7 @@ def main() -> int:
395
469
  parser = argparse.ArgumentParser(description="Lint SuperBridge Flow test-report evidence")
396
470
  parser.add_argument("reports", nargs="+")
397
471
  parser.add_argument("--tests", default="")
472
+ parser.add_argument("--review", default="")
398
473
  parser.add_argument("--repo-root", default="")
399
474
  parser.add_argument("--warn-only", action="store_true")
400
475
  args = parser.parse_args()
@@ -407,7 +482,10 @@ def main() -> int:
407
482
  continue
408
483
  repo = Path(args.repo_root).resolve() if args.repo_root else repo_root_for(path)
409
484
  tests_path = Path(args.tests).resolve() if args.tests else None
410
- all_issues.extend(lint_report(path, repo, tests_path))
485
+ if tests_path is not None and not tests_path.exists():
486
+ all_issues.append(Issue("FAIL", path, 0, f"显式指定的 tests.md 不存在:{tests_path}"))
487
+ review_path = Path(args.review).resolve() if args.review else None
488
+ all_issues.extend(lint_report(path, repo, tests_path, review_path))
411
489
 
412
490
  printable = []
413
491
  for issue in all_issues:
@@ -0,0 +1,229 @@
1
+ {
2
+ "version": "1.0.0",
3
+ "contracts": [
4
+ {
5
+ "name": "openspec-apply-change",
6
+ "owner": "implement",
7
+ "triggers": [
8
+ "implement approved change"
9
+ ],
10
+ "excludes": [],
11
+ "outputs": [
12
+ "tasks.md progress"
13
+ ],
14
+ "neighbors": []
15
+ },
16
+ {
17
+ "name": "openspec-archive-change",
18
+ "owner": "archive",
19
+ "triggers": [
20
+ "archive completed change"
21
+ ],
22
+ "excludes": [],
23
+ "outputs": [
24
+ "archive result"
25
+ ],
26
+ "neighbors": []
27
+ },
28
+ {
29
+ "name": "openspec-explore",
30
+ "owner": "explore",
31
+ "triggers": [
32
+ "explore requirements"
33
+ ],
34
+ "excludes": [],
35
+ "outputs": [
36
+ "alignment.md"
37
+ ],
38
+ "neighbors": [
39
+ "openspec-propose"
40
+ ]
41
+ },
42
+ {
43
+ "name": "openspec-propose",
44
+ "owner": "propose",
45
+ "triggers": [
46
+ "generate OpenSpec proposal"
47
+ ],
48
+ "excludes": [],
49
+ "outputs": [
50
+ "proposal.md specs design.md tasks.md"
51
+ ],
52
+ "neighbors": [
53
+ "superflow-clarify",
54
+ "superflow-design",
55
+ "superflow-implement"
56
+ ]
57
+ },
58
+ {
59
+ "name": "superflow-archive",
60
+ "owner": "archive",
61
+ "triggers": [
62
+ "archive SDD change"
63
+ ],
64
+ "excludes": [],
65
+ "outputs": [
66
+ "archive report"
67
+ ],
68
+ "neighbors": [
69
+ "superflow-verify"
70
+ ]
71
+ },
72
+ {
73
+ "name": "superflow-clarify",
74
+ "owner": "clarify",
75
+ "triggers": [
76
+ "clarify requirements"
77
+ ],
78
+ "excludes": [],
79
+ "outputs": [
80
+ "source-code-audit.md feature-gates.md brainstorm-summary.md"
81
+ ],
82
+ "neighbors": [
83
+ "openspec-propose",
84
+ "superflow-docs"
85
+ ]
86
+ },
87
+ {
88
+ "name": "superflow-design",
89
+ "owner": "design",
90
+ "triggers": [
91
+ "technical design"
92
+ ],
93
+ "excludes": [],
94
+ "outputs": [
95
+ "technical_design"
96
+ ],
97
+ "neighbors": [
98
+ "superflow-docs",
99
+ "superflow-implement"
100
+ ]
101
+ },
102
+ {
103
+ "name": "superflow-docs",
104
+ "owner": "docs",
105
+ "triggers": [
106
+ "SDD contract documents"
107
+ ],
108
+ "excludes": [],
109
+ "outputs": [
110
+ "api.md tests.md sdd-quality-gate.md"
111
+ ],
112
+ "neighbors": [
113
+ "superflow-clarify",
114
+ "superflow-design",
115
+ "superflow-implement"
116
+ ]
117
+ },
118
+ {
119
+ "name": "superflow-hotfix",
120
+ "owner": "pipeline",
121
+ "triggers": [
122
+ "urgent small fix"
123
+ ],
124
+ "excludes": [],
125
+ "outputs": [
126
+ "bug-fix-plan.md"
127
+ ],
128
+ "neighbors": []
129
+ },
130
+ {
131
+ "name": "superflow-implement",
132
+ "owner": "implement",
133
+ "triggers": [
134
+ "generate implementation prompts"
135
+ ],
136
+ "excludes": [],
137
+ "outputs": [
138
+ "prompt/ test-report.md"
139
+ ],
140
+ "neighbors": [
141
+ "superflow-design",
142
+ "superflow-verify"
143
+ ]
144
+ },
145
+ {
146
+ "name": "superflow-pipeline",
147
+ "owner": "pipeline",
148
+ "triggers": [
149
+ "Superflow SDD pipeline"
150
+ ],
151
+ "excludes": [],
152
+ "outputs": [
153
+ "state.yaml handoff"
154
+ ],
155
+ "neighbors": []
156
+ },
157
+ {
158
+ "name": "superflow-requirement-review",
159
+ "owner": "review",
160
+ "triggers": [
161
+ "review requirements"
162
+ ],
163
+ "excludes": [],
164
+ "outputs": [
165
+ "requirement-review.md"
166
+ ],
167
+ "neighbors": []
168
+ },
169
+ {
170
+ "name": "superflow-table-impact-analysis",
171
+ "owner": "docs",
172
+ "triggers": [
173
+ "table impact analysis"
174
+ ],
175
+ "excludes": [],
176
+ "outputs": [
177
+ "impact analysis"
178
+ ],
179
+ "neighbors": []
180
+ },
181
+ {
182
+ "name": "superflow-tweak",
183
+ "owner": "pipeline",
184
+ "triggers": [
185
+ "small wording tweak"
186
+ ],
187
+ "excludes": [],
188
+ "outputs": [
189
+ "tweak result"
190
+ ],
191
+ "neighbors": []
192
+ },
193
+ {
194
+ "name": "superflow-quick-dev",
195
+ "owner": "quick",
196
+ "triggers": [
197
+ "small code task",
198
+ "quick development"
199
+ ],
200
+ "excludes": [
201
+ "public API or database change",
202
+ "cross-module change"
203
+ ],
204
+ "outputs": [
205
+ ".sdd/tasks/<date>-<slug>/spec.md",
206
+ "quick evidence"
207
+ ],
208
+ "neighbors": [
209
+ "superflow-clarify",
210
+ "superflow-verify"
211
+ ]
212
+ },
213
+ {
214
+ "name": "superflow-verify",
215
+ "owner": "verify",
216
+ "triggers": [
217
+ "verify implementation"
218
+ ],
219
+ "excludes": [],
220
+ "outputs": [
221
+ "verification report"
222
+ ],
223
+ "neighbors": [
224
+ "superflow-implement",
225
+ "superflow-archive"
226
+ ]
227
+ }
228
+ ]
229
+ }
@@ -22,6 +22,14 @@ When ready to implement, run /opsx:apply
22
22
 
23
23
  **Input**: The user's request should include a change name (kebab-case) OR a description of what they want to build.
24
24
 
25
+ When this skill follows `superflow-clarify`, read the confirmed change-level
26
+ Consensus Summary and its source evidence before creating artifacts. Put the
27
+ goal, scope, material owner decisions, rejected alternatives, migration risks,
28
+ and non-blocking reopen triggers in canonical `proposal.md`; cite the existing
29
+ source audit rather than copying it. A pending owner decision that changes the
30
+ solution blocks downstream spec/design/tasks. An explicit decision in the
31
+ user's original request needs no second confirmation.
32
+
25
33
  ### OpenSpec canonical change 目录(阻塞级)
26
34
 
27
35
  - change 必须只有一个实体目录:`openspec/changes/<name>/`。
@@ -108,6 +116,12 @@ After completing all artifacts, summarize:
108
116
  - Follow the `instruction` field from `openspec instructions` for each artifact type
109
117
  - The schema defines what each artifact should contain - follow it
110
118
  - Read dependency artifacts for context before creating new ones
119
+ - For a Superflow change, keep ordinary OpenSpec checkbox task syntax. For each
120
+ independently verifiable task, state its observable delivery and acceptance
121
+ signal; record real predecessor task IDs as `Blocked by` and link the relevant
122
+ requirement/scenario or traceability row. A free-text blocker does not change
123
+ Superflow's `[local_required]` / `[environment_required]` /
124
+ `[release_required]` machine classification. Do not invent dependencies.
111
125
  - Use `template` as the structure for your output file - fill in its sections
112
126
  - **IMPORTANT**: `context` and `rules` are constraints for YOU, not content for the file
113
127
  - Do NOT copy `<context>`, `<rules>`, `<project_context>` blocks into the artifact
@@ -13,7 +13,7 @@ any full SDD document or implementation prompt is generated.
13
13
 
14
14
  - Before freezing any new requirement or bug fix, perform platform-level impact
15
15
  discovery. Prefer understand-anything artifacts when present, such as
16
- `.understand-anything/`, `understand-anything.md`, `understand anything.md`,
16
+ `.ua/`, `.understand-anything/`, `understand-anything.md`, `understand anything.md`,
17
17
  or the project-specific graph output, but treat them only as locator evidence.
18
18
  If artifacts are missing, stale, incomplete, or unavailable, degrade to `rg`,
19
19
  source reading, Mapper/XML, configuration, database table names, log
@@ -171,6 +171,28 @@ Use the detailed workflow in:
171
171
  indexed feature. Do not batch-confirm multiple features from memory or a
172
172
  compressed chat summary.
173
173
 
174
+ ### Change-level alignment before OpenSpec artifacts
175
+
176
+ After all in-scope features are frozen, present one concise change-level
177
+ `Consensus Summary` covering the goal, in/out scope, verified source facts,
178
+ owner decisions with rejected alternatives, production and migration risks,
179
+ and remaining non-blocking questions with owner and reopen trigger. Derive it
180
+ from `source-code-audit.md`, `feature-gates.md`, and the confirmed decisions in
181
+ `.sdd/handoff/brainstorm-summary.md`; the summary is a handoff view, not a
182
+ second source of requirements. Record unresolved decisions as blockers and
183
+ ask only the next owner decision. Do not ask the user to reconfirm facts or
184
+ choices already explicit in the original request.
185
+
186
+ When a material owner choice was made during clarification, show the current
187
+ summary before generating OpenSpec artifacts. Reuse an explicit confirmation
188
+ already given for that exact choice and summary; ask once only if the summary
189
+ introduces a new choice or changes its consequence. The later `proposal.md`
190
+ must carry the confirmed change-level
191
+ decisions and cite their evidence. If a later finding changes a confirmed
192
+ choice, return to that decision, update affected canonical documents, refresh
193
+ the handoff hash and repeat document review and Coding Ready. Task checkbox
194
+ progress alone does not change the agreed contract.
195
+
174
196
  ## Handoff
175
197
 
176
198
  When the feature is frozen, tell the user to continue with `$openspec-propose`
@@ -64,7 +64,12 @@ Superpowers own source-level HOW without weakening OpenSpec/SDD contracts.
64
64
  5. Write:
65
65
  `docs/superpowers/specs/YYYY-MM-DD-<change-id>-technical-design.md`
66
66
  using `../superflow-pipeline/references/superpower-technical-design-template.md`.
67
- 6. Complete `复杂度减法评审` before expanding source-level HOW. For every new
67
+ 6. For each new or changed module interface, add a `Design-It-Twice` card to
68
+ the technical design: two materially different interfaces, seam and adapter
69
+ ownership, the real test surface, reuse evidence, caller consequences, and
70
+ an explicit choice. Renaming the same interface does not count as two
71
+ options; revisit it. This card is advisory and does not add a state gate.
72
+ 7. Complete `复杂度减法评审` before expanding source-level HOW. For every new
68
73
  table, field, API, Service/component, abstraction, cache, async/MQ/event
69
74
  flow, scheduled job, or compatibility layer, prove existing reuse options,
70
75
  necessity, and why the simplest direct implementation is insufficient.
@@ -37,6 +37,12 @@ Do not invent new fields or requirements.
37
37
 
38
38
  For complex requirements, verify these before writing docs:
39
39
 
40
+ - `proposal.md` carries the confirmed change-level Consensus Summary from
41
+ clarification: goal, scope, owner choices and rejected alternatives,
42
+ production/migration risks, and non-blocking reopen triggers. Check it
43
+ against original sources and `source-code-audit.md`; do not treat the
44
+ handoff summary as a second authority. If a material choice remains open,
45
+ return to `$superflow-clarify` before finalizing design or tasks.
40
46
  - `source-ingestion.md`, `feature-inventory.md`, and `feature-gates.md` prove
41
47
  that the original PRD/Lark/Feishu/screenshots were processed section by
42
48
  section, not summarized into tasks in one whole-document pass. Each feature
@@ -240,6 +246,12 @@ applicable:
240
246
  - `tasks.md` 每个 checkbox 必须带且只带一个机器分类:`[local_required]`、
241
247
  `[environment_required]` 或 `[release_required]`。能够在本地完成的编码、测试、启动和浏览器
242
248
  验证必须是 local_required;不得用自由文本 Blocker/owner 代替分类。
249
+ - For each implementation task, review observable delivery, acceptance signal,
250
+ requirement/scenario coverage, and actual predecessors. Show the currently
251
+ executable tasks whose predecessors are complete. Reuse
252
+ `traceability-matrix.md` for detailed coverage; do not create another task
253
+ state file. `Blocked by` expresses execution order and never changes the
254
+ machine task classification above.
243
255
  - `tests.md`
244
256
  - `test-report.md` skeleton
245
257
  - `sdd-quality-gate.md`
@@ -368,6 +380,18 @@ DB 状态 | 结算/通知/展示消费点 | 真实验证方式`.
368
380
  `用例ID | 需求/Scenario | 层级L1/L2/L3/L4 | 前置数据 | 操作步骤 |
369
381
  自动化命令 | 响应断言 | DB断言 | 日志断言 | RED预期失败 | GREEN预期通过 |
370
382
  test-report证据位置`.
383
+ - For cross-page management or database-backed display changes, reconcile the
384
+ frontend action/list inventory with real Controller, Service, exact Mapper
385
+ statement and table anchors before freezing tasks. Put the confirmed chain
386
+ and entry ID in `source-code-audit.md` and reuse it in `design.md`, `tasks.md`
387
+ and `tests.md`. A similarly named query is not evidence for the page route.
388
+ Include every visible mutation button, such as enable/disable, as an entry
389
+ even when the original task list omitted it.
390
+ - For a returned persisted field, `tests.md` must freeze at least one non-null
391
+ fixture and an allowed historical-NULL fixture separately. Assert API and DB
392
+ values for the same record ID, then browser rendering where a page is in
393
+ scope. Key-presence, HTTP 200, empty-page and NULL-only assertions cannot
394
+ close the non-null data path. Name the exact query/command and evidence file.
371
395
  - 对 L3/L4 的输入校验、协议拒绝或异常分支,`tests.md` 还必须冻结真实注入载荷及其到达
372
396
  目标解析/校验分支的证据;仅断言同一个 HTTP 状态码不算覆盖。原始畸形 JSON、错误
373
397
  Content-Type、越权身份或非法字段必须按真实线协议发送,并同时断言无副作用。