@chenmk/superflow 0.1.0
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.en.md +106 -0
- package/INSTALL.md +664 -0
- package/LICENSE +21 -0
- package/README.md +142 -0
- package/README.zh-CN.md +117 -0
- package/assets/context-templates/business-rules.md +98 -0
- package/assets/context-templates/decisions.md +153 -0
- package/assets/context-templates/external-systems.md +166 -0
- package/assets/context-templates/incidents.md +89 -0
- package/assets/manifest.json +53 -0
- package/assets/prompts/superflow-archive.md +9 -0
- package/assets/prompts/superflow-clarify.md +10 -0
- package/assets/prompts/superflow-design.md +10 -0
- package/assets/prompts/superflow-docs.md +10 -0
- package/assets/prompts/superflow-implement.md +10 -0
- package/assets/prompts/superflow-pipeline.md +13 -0
- package/assets/prompts/superflow-verify.md +10 -0
- package/assets/rules/superflow-phase-guard.md +50 -0
- package/assets/scripts/claude-auto-backup-hook.sh +313 -0
- package/assets/scripts/codex-auto-backup-hook.sh +361 -0
- package/assets/scripts/install-sql-pre-commit.sh +44 -0
- package/assets/scripts/superflow-contract-hooks.sh +744 -0
- package/assets/scripts/superflow-delivery-check.sh +315 -0
- package/assets/scripts/superflow-dependency-update-hook.sh +161 -0
- package/assets/scripts/superflow-enforce-hook.sh +70 -0
- package/assets/scripts/superflow-hook-guard.sh +132 -0
- package/assets/scripts/superflow-integration-evidence-hook.sh +80 -0
- package/assets/scripts/superflow-sql-sync-hook.py +950 -0
- package/assets/scripts/superflow-test-report-lint.py +433 -0
- package/assets/scripts/superflow-verify-integration.sh +90 -0
- package/assets/scripts/sync-settings-json.py +52 -0
- package/assets/skills/api-doc-changelog/SKILL.md +193 -0
- package/assets/skills/openspec-apply-change/SKILL.md +156 -0
- package/assets/skills/openspec-archive-change/SKILL.md +114 -0
- package/assets/skills/openspec-explore/SKILL.md +288 -0
- package/assets/skills/openspec-propose/SKILL.md +110 -0
- package/assets/skills/superflow-archive/SKILL.md +61 -0
- package/assets/skills/superflow-clarify/SKILL.md +146 -0
- package/assets/skills/superflow-clarify/agents/openai.yaml +4 -0
- package/assets/skills/superflow-design/SKILL.md +83 -0
- package/assets/skills/superflow-design/agents/openai.yaml +4 -0
- package/assets/skills/superflow-docs/SKILL.md +316 -0
- package/assets/skills/superflow-docs/agents/openai.yaml +4 -0
- package/assets/skills/superflow-hotfix/SKILL.md +48 -0
- package/assets/skills/superflow-implement/SKILL.md +461 -0
- package/assets/skills/superflow-implement/agents/openai.yaml +4 -0
- package/assets/skills/superflow-pipeline/SKILL.md +844 -0
- package/assets/skills/superflow-pipeline/agents/openai.yaml +4 -0
- package/assets/skills/superflow-pipeline/references/api-design-template.md +431 -0
- package/assets/skills/superflow-pipeline/references/architecture-design-template.md +119 -0
- package/assets/skills/superflow-pipeline/references/batch-prompt-template.md +536 -0
- package/assets/skills/superflow-pipeline/references/batch-split-guide.md +140 -0
- package/assets/skills/superflow-pipeline/references/decision-point.md +30 -0
- package/assets/skills/superflow-pipeline/references/dirty-worktree.md +35 -0
- package/assets/skills/superflow-pipeline/references/document-templates.md +123 -0
- package/assets/skills/superflow-pipeline/references/feature-gated-workflow.md +124 -0
- package/assets/skills/superflow-pipeline/references/implementation-prompt-template.md +1056 -0
- package/assets/skills/superflow-pipeline/references/mock-strategy-guide.md +86 -0
- package/assets/skills/superflow-pipeline/references/openspec-format.md +57 -0
- package/assets/skills/superflow-pipeline/references/orchestration.md +639 -0
- package/assets/skills/superflow-pipeline/references/p0-baseline-template.md +174 -0
- package/assets/skills/superflow-pipeline/references/project-config.md +40 -0
- package/assets/skills/superflow-pipeline/references/prompt-usage-template.md +152 -0
- package/assets/skills/superflow-pipeline/references/quality-gate.md +299 -0
- package/assets/skills/superflow-pipeline/references/quality-standards.md +190 -0
- package/assets/skills/superflow-pipeline/references/reviewer-checklist.md +154 -0
- package/assets/skills/superflow-pipeline/references/sql-risk-review-checklist.md +323 -0
- package/assets/skills/superflow-pipeline/references/subagent-progress.md +90 -0
- package/assets/skills/superflow-pipeline/references/superpower-technical-design-template.md +125 -0
- package/assets/skills/superflow-pipeline/references/test-execution-template.md +220 -0
- package/assets/skills/superflow-pipeline/references/test-guide.md +30 -0
- package/assets/skills/superflow-pipeline/references/traceability-matrix.md +106 -0
- package/assets/skills/superflow-pipeline/references/validation-integrity.md +134 -0
- package/assets/skills/superflow-pipeline/scripts/superflow-archive.sh +178 -0
- package/assets/skills/superflow-pipeline/scripts/superflow-env.sh +118 -0
- package/assets/skills/superflow-pipeline/scripts/superflow-guard.sh +428 -0
- package/assets/skills/superflow-pipeline/scripts/superflow-handoff.sh +296 -0
- package/assets/skills/superflow-pipeline/scripts/superflow-state.sh +574 -0
- package/assets/skills/superflow-pipeline/scripts/superflow-status.sh +172 -0
- package/assets/skills/superflow-pipeline/scripts/superflow-yaml-validate.sh +138 -0
- package/assets/skills/superflow-table-impact-analysis/SKILL.md +77 -0
- package/assets/skills/superflow-tweak/SKILL.md +46 -0
- package/assets/skills/superflow-verify/SKILL.md +112 -0
- package/assets/skills-en/api-doc-changelog/SKILL.md +193 -0
- package/assets/skills-en/openspec-apply-change/SKILL.md +156 -0
- package/assets/skills-en/openspec-archive-change/SKILL.md +114 -0
- package/assets/skills-en/openspec-explore/SKILL.md +288 -0
- package/assets/skills-en/openspec-propose/SKILL.md +110 -0
- package/assets/skills-en/superflow-archive/SKILL.md +61 -0
- package/assets/skills-en/superflow-clarify/SKILL.md +146 -0
- package/assets/skills-en/superflow-clarify/agents/openai.yaml +4 -0
- package/assets/skills-en/superflow-design/SKILL.md +83 -0
- package/assets/skills-en/superflow-design/agents/openai.yaml +4 -0
- package/assets/skills-en/superflow-docs/SKILL.md +316 -0
- package/assets/skills-en/superflow-docs/agents/openai.yaml +4 -0
- package/assets/skills-en/superflow-hotfix/SKILL.md +48 -0
- package/assets/skills-en/superflow-implement/SKILL.md +461 -0
- package/assets/skills-en/superflow-implement/agents/openai.yaml +4 -0
- package/assets/skills-en/superflow-pipeline/SKILL.md +844 -0
- package/assets/skills-en/superflow-pipeline/agents/openai.yaml +4 -0
- package/assets/skills-en/superflow-pipeline/references/api-design-template.md +431 -0
- package/assets/skills-en/superflow-pipeline/references/architecture-design-template.md +119 -0
- package/assets/skills-en/superflow-pipeline/references/batch-prompt-template.md +536 -0
- package/assets/skills-en/superflow-pipeline/references/batch-split-guide.md +140 -0
- package/assets/skills-en/superflow-pipeline/references/decision-point.md +30 -0
- package/assets/skills-en/superflow-pipeline/references/dirty-worktree.md +35 -0
- package/assets/skills-en/superflow-pipeline/references/document-templates.md +123 -0
- package/assets/skills-en/superflow-pipeline/references/feature-gated-workflow.md +124 -0
- package/assets/skills-en/superflow-pipeline/references/implementation-prompt-template.md +1056 -0
- package/assets/skills-en/superflow-pipeline/references/mock-strategy-guide.md +86 -0
- package/assets/skills-en/superflow-pipeline/references/openspec-format.md +57 -0
- package/assets/skills-en/superflow-pipeline/references/orchestration.md +639 -0
- package/assets/skills-en/superflow-pipeline/references/p0-baseline-template.md +174 -0
- package/assets/skills-en/superflow-pipeline/references/project-config.md +40 -0
- package/assets/skills-en/superflow-pipeline/references/prompt-usage-template.md +152 -0
- package/assets/skills-en/superflow-pipeline/references/quality-gate.md +299 -0
- package/assets/skills-en/superflow-pipeline/references/quality-standards.md +190 -0
- package/assets/skills-en/superflow-pipeline/references/reviewer-checklist.md +154 -0
- package/assets/skills-en/superflow-pipeline/references/sql-risk-review-checklist.md +323 -0
- package/assets/skills-en/superflow-pipeline/references/subagent-progress.md +90 -0
- package/assets/skills-en/superflow-pipeline/references/superpower-technical-design-template.md +125 -0
- package/assets/skills-en/superflow-pipeline/references/test-execution-template.md +220 -0
- package/assets/skills-en/superflow-pipeline/references/test-guide.md +30 -0
- package/assets/skills-en/superflow-pipeline/references/traceability-matrix.md +106 -0
- package/assets/skills-en/superflow-pipeline/references/validation-integrity.md +134 -0
- package/assets/skills-en/superflow-pipeline/scripts/superflow-archive.sh +178 -0
- package/assets/skills-en/superflow-pipeline/scripts/superflow-env.sh +118 -0
- package/assets/skills-en/superflow-pipeline/scripts/superflow-guard.sh +428 -0
- package/assets/skills-en/superflow-pipeline/scripts/superflow-handoff.sh +296 -0
- package/assets/skills-en/superflow-pipeline/scripts/superflow-state.sh +574 -0
- package/assets/skills-en/superflow-pipeline/scripts/superflow-status.sh +172 -0
- package/assets/skills-en/superflow-pipeline/scripts/superflow-yaml-validate.sh +138 -0
- package/assets/skills-en/superflow-table-impact-analysis/SKILL.md +77 -0
- package/assets/skills-en/superflow-tweak/SKILL.md +46 -0
- package/assets/skills-en/superflow-verify/SKILL.md +112 -0
- package/dist/cli/index.js +186 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/commands/archive.js +6 -0
- package/dist/commands/archive.js.map +1 -0
- package/dist/commands/clarify.js +6 -0
- package/dist/commands/clarify.js.map +1 -0
- package/dist/commands/design.js +6 -0
- package/dist/commands/design.js.map +1 -0
- package/dist/commands/docs.js +6 -0
- package/dist/commands/docs.js.map +1 -0
- package/dist/commands/doctor.js +473 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/implement.js +6 -0
- package/dist/commands/implement.js.map +1 -0
- package/dist/commands/init.js +471 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/pipeline.js +6 -0
- package/dist/commands/pipeline.js.map +1 -0
- package/dist/commands/scan.js +59 -0
- package/dist/commands/scan.js.map +1 -0
- package/dist/commands/status.js +173 -0
- package/dist/commands/status.js.map +1 -0
- package/dist/commands/uninstall.js +213 -0
- package/dist/commands/uninstall.js.map +1 -0
- package/dist/commands/update.js +187 -0
- package/dist/commands/update.js.map +1 -0
- package/dist/commands/verify.js +6 -0
- package/dist/commands/verify.js.map +1 -0
- package/dist/core/assets.js +27 -0
- package/dist/core/assets.js.map +1 -0
- package/dist/core/context.js +100 -0
- package/dist/core/context.js.map +1 -0
- package/dist/core/dependencies.js +146 -0
- package/dist/core/dependencies.js.map +1 -0
- package/dist/core/detect.js +71 -0
- package/dist/core/detect.js.map +1 -0
- package/dist/core/i18n.js +103 -0
- package/dist/core/i18n.js.map +1 -0
- package/dist/core/integrity.js +46 -0
- package/dist/core/integrity.js.map +1 -0
- package/dist/core/manifest.js +18 -0
- package/dist/core/manifest.js.map +1 -0
- package/dist/core/prompts.js +20 -0
- package/dist/core/prompts.js.map +1 -0
- package/dist/core/registry.js +134 -0
- package/dist/core/registry.js.map +1 -0
- package/dist/core/rules.js +17 -0
- package/dist/core/rules.js.map +1 -0
- package/dist/core/scripts.js +40 -0
- package/dist/core/scripts.js.map +1 -0
- package/dist/core/skill-check.js +31 -0
- package/dist/core/skill-check.js.map +1 -0
- package/dist/core/skills.js +56 -0
- package/dist/core/skills.js.map +1 -0
- package/dist/core/state.js +43 -0
- package/dist/core/state.js.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/path.js +11 -0
- package/dist/utils/path.js.map +1 -0
- package/dist/utils/shell.js +29 -0
- package/dist/utils/shell.js.map +1 -0
- package/package.json +60 -0
|
@@ -0,0 +1,433 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""SuperBridge Flow test-report evidence linter.
|
|
3
|
+
|
|
4
|
+
This hook catches delivery-report issues that are cheap to detect
|
|
5
|
+
mechanically:
|
|
6
|
+
|
|
7
|
+
- skipped tests being reported as passed
|
|
8
|
+
- contradictory `Tests run` / `N/N` evidence for the same command
|
|
9
|
+
- stale source anchors such as deleted methods referenced as current evidence
|
|
10
|
+
- cross-repo evidence that claims test success without acknowledging skipTests
|
|
11
|
+
- L3/L4 or real-entry tests being closed with mock-only/unit-only evidence
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import argparse
|
|
17
|
+
import re
|
|
18
|
+
import subprocess
|
|
19
|
+
import sys
|
|
20
|
+
from dataclasses import dataclass
|
|
21
|
+
from pathlib import Path
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
PASS_WORDS = re.compile(r"(✅|pass|passed|通过|成功|Real integration passed)", re.I)
|
|
25
|
+
RISK_WORDS = re.compile(
|
|
26
|
+
r"(风险|默认|直接跑|会被|被跳过|假绿|不能|不得|临时|回滚|需|必须|注意|说明|blocked|skipTests)",
|
|
27
|
+
re.I,
|
|
28
|
+
)
|
|
29
|
+
DELETED_CONTEXT = re.compile(r"(旧|已删除|删除|不再|清理|移除|历史|旧实现|旧口径)", re.I)
|
|
30
|
+
COMMAND_RE = re.compile(r"(mvn\s+[^\n`|;。]+(?:test|surefire:test)[^\n`|;。]*)")
|
|
31
|
+
TESTS_RUN_RE = re.compile(
|
|
32
|
+
r"Tests run:\s*(\d+),\s*Failures:\s*(\d+),\s*Errors:\s*(\d+),\s*Skipped:\s*(\d+)",
|
|
33
|
+
re.I,
|
|
34
|
+
)
|
|
35
|
+
RATIO_RE = re.compile(r"(?<![\w./-])(\d{1,4})/(\d{1,4})(?![\w./-])")
|
|
36
|
+
ANCHOR_RE = re.compile(r"\b([A-Z][A-Za-z0-9_]+)\.([a-z][A-Za-z0-9_]+)\b")
|
|
37
|
+
DB_BACKED_RE = re.compile(
|
|
38
|
+
r"("
|
|
39
|
+
r"数据库|数据表|表结构|字段|状态字段|SQL|Mapper|XML|@TableName|BaseMapper|"
|
|
40
|
+
r"SHOW\s+CREATE\s+TABLE|SHOW\s+COLUMNS|DESC\s+|SELECT\s+|UPDATE\s+|INSERT\s+|DELETE\s+|"
|
|
41
|
+
r"\b[a-z][a-z0-9]*_[a-z0-9_]*\b|running_status|connector_status|own_status|is_online|is_use|deleted|is_deleted"
|
|
42
|
+
r")",
|
|
43
|
+
re.I,
|
|
44
|
+
)
|
|
45
|
+
TABLE_REVERSE_HEADER_RE = re.compile(
|
|
46
|
+
r"表/字段.*写入方.*(读取|过滤).*真实入口.*(反向状态|验证证据|必测用例)",
|
|
47
|
+
re.I | re.S,
|
|
48
|
+
)
|
|
49
|
+
REAL_CONSUMER_RE = re.compile(
|
|
50
|
+
r"(真实入口|小程序|扫码|启动充电|订单创建|支付|退款|通知|回调|第三方|MQ|定时任务|"
|
|
51
|
+
r"curl|POST\s+|GET\s+|PUT\s+|DELETE\s+|接口调用|业务入口)",
|
|
52
|
+
re.I,
|
|
53
|
+
)
|
|
54
|
+
SYNC_ONLY_RE = re.compile(r"(同步.*成功|任务.*成功|写入.*成功|更新.*成功)", re.I)
|
|
55
|
+
BLOCKED_RE = re.compile(r"(Blocked|阻塞|未验证|无法验证|待.*确认)", re.I)
|
|
56
|
+
REAL_TEST_REQUIRED_RE = re.compile(
|
|
57
|
+
r"("
|
|
58
|
+
r"\bL[34]\b|T\d+(?:\.\d+)+|真实入口|真实接口|真实链路|真实环境|联调|端到端|E2E|"
|
|
59
|
+
r"curl|Postman|Newman|pytest|RestAssured|HTTP|POST\s+|GET\s+|PUT\s+|DELETE\s+|"
|
|
60
|
+
r"dev\s*tool|dev工具|第三方|开放平台|外部事件|外部平台|key|secret|"
|
|
61
|
+
r"设备|回调|callback|MQ|数据库|DB|SELECT\s+|日志|grep\s+.*ERROR"
|
|
62
|
+
r")",
|
|
63
|
+
re.I,
|
|
64
|
+
)
|
|
65
|
+
EXTERNAL_TEST_REQUIRED_RE = re.compile(
|
|
66
|
+
r"(第三方|开放平台|dev\s*tool|dev工具|外部事件|外部平台|"
|
|
67
|
+
r"callback|回调|key|secret|设备|业务终态|对账)",
|
|
68
|
+
re.I,
|
|
69
|
+
)
|
|
70
|
+
MOCK_ONLY_RE = re.compile(
|
|
71
|
+
r"(mock-only|mock only|仅.*mock|只.*mock|仅.*单元|只.*单元|虚设数据|模拟数据|Mockito|@MockBean)",
|
|
72
|
+
re.I,
|
|
73
|
+
)
|
|
74
|
+
REAL_COMMAND_RE = re.compile(
|
|
75
|
+
r"(curl\s+|http://|https://|POST\s+|GET\s+|PUT\s+|DELETE\s+|PATCH\s+|"
|
|
76
|
+
r"Postman|Newman|pytest|RestAssured|dev\s*tool|dev工具)",
|
|
77
|
+
re.I,
|
|
78
|
+
)
|
|
79
|
+
REAL_ENV_RE = re.compile(
|
|
80
|
+
r"(PID|进程|Started .*Application|JVM running|端口|server\.port|Base URL|"
|
|
81
|
+
r"localhost:\d+|127\.0\.0\.1:\d+|10\.\d+\.\d+\.\d+:\d+|测试环境|dev环境|"
|
|
82
|
+
r"actuator/health|健康检查)",
|
|
83
|
+
re.I,
|
|
84
|
+
)
|
|
85
|
+
RESPONSE_ASSERTION_RE = re.compile(
|
|
86
|
+
r"(HTTP/[0-9.]+\s+2\d\d|HTTP\s*2\d\d|status\s*[:=]\s*2\d\d|响应|response|"
|
|
87
|
+
r"断言|assert|success|matched|pending|accepted|confirmed|对账|业务终态)",
|
|
88
|
+
re.I,
|
|
89
|
+
)
|
|
90
|
+
DB_EVIDENCE_RE = re.compile(
|
|
91
|
+
r"(SELECT\s+|SHOW\s+CREATE\s+TABLE|SHOW\s+COLUMNS|DESC\s+|数据库|DB|mysql|"
|
|
92
|
+
r"记录数|字段值|表数据|写入|更新后)",
|
|
93
|
+
re.I,
|
|
94
|
+
)
|
|
95
|
+
LOG_EVIDENCE_RE = re.compile(r"(grep\s+.*ERROR|无\s*ERROR|日志|log|ERROR|WARN)", re.I)
|
|
96
|
+
EXTERNAL_EVIDENCE_RE = re.compile(
|
|
97
|
+
r"(第三方|开放平台|dev\s*tool|dev工具|外部事件|外部平台|callback|回调|"
|
|
98
|
+
r"外部请求|外部响应|业务终态|对账|10\.\d+\.\d+\.\d+:\d+)",
|
|
99
|
+
re.I,
|
|
100
|
+
)
|
|
101
|
+
CASE_ID_RE = re.compile(r"\bT\d+(?:\.\d+)+\b")
|
|
102
|
+
OLD_PHRASES = (
|
|
103
|
+
"不会按周期边界拆分子切片",
|
|
104
|
+
"1 秒空窗落入下一周期对账无实质影响",
|
|
105
|
+
"空窗子段优惠电费金额极小",
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
@dataclass
|
|
110
|
+
class Issue:
|
|
111
|
+
level: str
|
|
112
|
+
path: Path
|
|
113
|
+
line: int
|
|
114
|
+
message: str
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def repo_root_for(path: Path) -> Path | None:
|
|
118
|
+
cur = path.parent if path.is_file() else path
|
|
119
|
+
while True:
|
|
120
|
+
if (cur / ".git").exists():
|
|
121
|
+
return cur
|
|
122
|
+
if cur.parent == cur:
|
|
123
|
+
return None
|
|
124
|
+
cur = cur.parent
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def normalize_command(command: str) -> str:
|
|
128
|
+
command = re.sub(r"\s+", " ", command.strip())
|
|
129
|
+
command = command.replace('"', "'")
|
|
130
|
+
command = re.sub(r"\s+-s\s+\S+", "", command)
|
|
131
|
+
command = re.sub(r"\s+-DfailIfNoTests=\S+", "", command)
|
|
132
|
+
command = re.sub(r"\s+-DskipTests=\S+", "", command)
|
|
133
|
+
command = re.sub(r"\s+-Dmaven\.test\.skip=\S+", "", command)
|
|
134
|
+
return command
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def grep_source(repo: Path, method: str) -> bool:
|
|
138
|
+
try:
|
|
139
|
+
subprocess.check_output(
|
|
140
|
+
["rg", "-n", rf"\b{re.escape(method)}\s*\(", "src"],
|
|
141
|
+
cwd=str(repo),
|
|
142
|
+
text=True,
|
|
143
|
+
stderr=subprocess.DEVNULL,
|
|
144
|
+
)
|
|
145
|
+
return True
|
|
146
|
+
except Exception:
|
|
147
|
+
return False
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def class_seen_in_source(repo: Path, class_name: str) -> bool:
|
|
151
|
+
try:
|
|
152
|
+
subprocess.check_output(
|
|
153
|
+
["rg", "-n", rf"\b{re.escape(class_name)}\b", "src"],
|
|
154
|
+
cwd=str(repo),
|
|
155
|
+
text=True,
|
|
156
|
+
stderr=subprocess.DEVNULL,
|
|
157
|
+
)
|
|
158
|
+
return True
|
|
159
|
+
except Exception:
|
|
160
|
+
return False
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def find_recent_command(lines: list[str], index: int) -> str | None:
|
|
164
|
+
start = max(0, index - 8)
|
|
165
|
+
for j in range(index, start - 1, -1):
|
|
166
|
+
match = COMMAND_RE.search(lines[j])
|
|
167
|
+
if match:
|
|
168
|
+
return normalize_command(match.group(1))
|
|
169
|
+
return None
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def first_missing_evidence(text: str, required: list[tuple[str, re.Pattern[str]]]) -> str | None:
|
|
173
|
+
for label, pattern in required:
|
|
174
|
+
if not pattern.search(text):
|
|
175
|
+
return label
|
|
176
|
+
return None
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def lint_against_tests_contract(report_path: Path, report_text: str, tests_path: Path | None) -> list[Issue]:
|
|
180
|
+
issues: list[Issue] = []
|
|
181
|
+
if tests_path is None or not tests_path.exists():
|
|
182
|
+
return issues
|
|
183
|
+
|
|
184
|
+
tests_text = tests_path.read_text(encoding="utf-8", errors="replace")
|
|
185
|
+
real_required = bool(REAL_TEST_REQUIRED_RE.search(tests_text))
|
|
186
|
+
external_required = bool(EXTERNAL_TEST_REQUIRED_RE.search(tests_text))
|
|
187
|
+
blocked = bool(BLOCKED_RE.search(report_text))
|
|
188
|
+
passed_or_partial = bool(
|
|
189
|
+
PASS_WORDS.search(report_text)
|
|
190
|
+
or re.search(r"Real integration passed|Partially verified|部分验证", report_text, re.I)
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
if not real_required or blocked or not passed_or_partial:
|
|
194
|
+
return issues
|
|
195
|
+
|
|
196
|
+
if MOCK_ONLY_RE.search(report_text):
|
|
197
|
+
issues.append(
|
|
198
|
+
Issue(
|
|
199
|
+
"FAIL",
|
|
200
|
+
report_path,
|
|
201
|
+
0,
|
|
202
|
+
f"{tests_path.name} 要求真实链路/L3/L4 证据,但 test-report 出现 mock-only/单元测试闭环口径;"
|
|
203
|
+
"mock 只能作为辅助证据,不能作为完成态。",
|
|
204
|
+
)
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
required = [
|
|
208
|
+
("真实命令/curl/dev 工具/API 调用", REAL_COMMAND_RE),
|
|
209
|
+
("真实环境/进程/端口/健康检查", REAL_ENV_RE),
|
|
210
|
+
("响应与业务断言", RESPONSE_ASSERTION_RE),
|
|
211
|
+
("数据库 SELECT/字段校验", DB_EVIDENCE_RE),
|
|
212
|
+
("日志/ERROR 检查", LOG_EVIDENCE_RE),
|
|
213
|
+
]
|
|
214
|
+
missing = first_missing_evidence(report_text, required)
|
|
215
|
+
if missing:
|
|
216
|
+
issues.append(
|
|
217
|
+
Issue(
|
|
218
|
+
"FAIL",
|
|
219
|
+
report_path,
|
|
220
|
+
0,
|
|
221
|
+
f"{tests_path.name} 声明真实链路/L3/L4 验证,但 test-report 缺少 `{missing}` 证据;"
|
|
222
|
+
"不能用单元测试、BUILD SUCCESS 或关键词替代真实验收。",
|
|
223
|
+
)
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
if external_required and not EXTERNAL_EVIDENCE_RE.search(report_text):
|
|
227
|
+
issues.append(
|
|
228
|
+
Issue(
|
|
229
|
+
"FAIL",
|
|
230
|
+
report_path,
|
|
231
|
+
0,
|
|
232
|
+
f"{tests_path.name} 涉及第三方/dev 工具/设备/回调验证,但 test-report 缺少外部平台请求、"
|
|
233
|
+
"回调、业务终态或双方数据对账证据。",
|
|
234
|
+
)
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
tests_cases = set(CASE_ID_RE.findall(tests_text))
|
|
238
|
+
report_cases = set(CASE_ID_RE.findall(report_text))
|
|
239
|
+
missing_cases = sorted(tests_cases - report_cases)
|
|
240
|
+
if tests_cases and missing_cases:
|
|
241
|
+
preview = ", ".join(missing_cases[:8])
|
|
242
|
+
suffix = " ..." if len(missing_cases) > 8 else ""
|
|
243
|
+
issues.append(
|
|
244
|
+
Issue(
|
|
245
|
+
"FAIL",
|
|
246
|
+
report_path,
|
|
247
|
+
0,
|
|
248
|
+
f"test-report 缺少 tests.md 用例 ID 回填:{preview}{suffix};"
|
|
249
|
+
"每个要求执行的 case 必须记录 Passed/Blocked/Partially verified。",
|
|
250
|
+
)
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
return issues
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
def infer_tests_path(report: Path) -> Path | None:
|
|
257
|
+
direct = report.parent / "tests.md"
|
|
258
|
+
if direct.exists():
|
|
259
|
+
return direct
|
|
260
|
+
cur = report.parent
|
|
261
|
+
for _ in range(4):
|
|
262
|
+
candidate = cur / "tests.md"
|
|
263
|
+
if candidate.exists():
|
|
264
|
+
return candidate
|
|
265
|
+
if cur.parent == cur:
|
|
266
|
+
break
|
|
267
|
+
cur = cur.parent
|
|
268
|
+
return None
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
def lint_report(path: Path, repo: Path | None, tests_path: Path | None = None) -> list[Issue]:
|
|
272
|
+
issues: list[Issue] = []
|
|
273
|
+
text = path.read_text(encoding="utf-8", errors="replace")
|
|
274
|
+
lines = text.splitlines()
|
|
275
|
+
command_tests: dict[str, set[tuple[int, int, int, int]]] = {}
|
|
276
|
+
command_ratios: dict[str, set[tuple[int, int]]] = {}
|
|
277
|
+
db_backed = bool(DB_BACKED_RE.search(text))
|
|
278
|
+
blocked = bool(BLOCKED_RE.search(text))
|
|
279
|
+
passed_or_partial = bool(PASS_WORDS.search(text) or re.search(r"Partially verified|部分验证", text, re.I))
|
|
280
|
+
issues.extend(lint_against_tests_contract(path, text, tests_path or infer_tests_path(path)))
|
|
281
|
+
|
|
282
|
+
if db_backed and passed_or_partial and not blocked:
|
|
283
|
+
if not TABLE_REVERSE_HEADER_RE.search(text):
|
|
284
|
+
issues.append(
|
|
285
|
+
Issue(
|
|
286
|
+
"FAIL",
|
|
287
|
+
path,
|
|
288
|
+
0,
|
|
289
|
+
"数据库/状态字段相关报告缺少数据表反向影响面表:"
|
|
290
|
+
"`表/字段 | 写入方 | 读取/过滤方 | 跨仓/外部消费方 | 真实入口 | 反向状态场景 | 验证证据`。",
|
|
291
|
+
)
|
|
292
|
+
)
|
|
293
|
+
if not REAL_CONSUMER_RE.search(text):
|
|
294
|
+
issues.append(
|
|
295
|
+
Issue(
|
|
296
|
+
"FAIL",
|
|
297
|
+
path,
|
|
298
|
+
0,
|
|
299
|
+
"数据库/状态字段相关报告缺少真实消费入口证据;不能只证明 SQL/同步任务成功。",
|
|
300
|
+
)
|
|
301
|
+
)
|
|
302
|
+
if SYNC_ONLY_RE.search(text) and not TABLE_REVERSE_HEADER_RE.search(text):
|
|
303
|
+
issues.append(
|
|
304
|
+
Issue(
|
|
305
|
+
"FAIL",
|
|
306
|
+
path,
|
|
307
|
+
0,
|
|
308
|
+
"报告出现同步/写入成功口径,但缺少表字段反向消费方验证;同步成功不能替代业务入口通过。",
|
|
309
|
+
)
|
|
310
|
+
)
|
|
311
|
+
|
|
312
|
+
for i, line in enumerate(lines):
|
|
313
|
+
line_no = i + 1
|
|
314
|
+
if "Tests are skipped" in line or "测试被跳过" in line:
|
|
315
|
+
window = "\n".join(lines[max(0, i - 2) : min(len(lines), i + 3)])
|
|
316
|
+
if PASS_WORDS.search(window) and not RISK_WORDS.search(window):
|
|
317
|
+
issues.append(
|
|
318
|
+
Issue("FAIL", path, line_no, "测试被跳过却被写成通过;BUILD SUCCESS 不能替代 Tests run。")
|
|
319
|
+
)
|
|
320
|
+
|
|
321
|
+
for phrase in OLD_PHRASES:
|
|
322
|
+
if phrase in line:
|
|
323
|
+
window = "\n".join(lines[max(0, i - 1) : min(len(lines), i + 2)])
|
|
324
|
+
if re.search(r"(不再包含|不再存在|已清理|旧措辞|旧口径|不得|禁止)", window):
|
|
325
|
+
issues.append(
|
|
326
|
+
Issue("WARN", path, line_no, f"报告反向引用旧口径 `{phrase}`;建议改为概括性表述。")
|
|
327
|
+
)
|
|
328
|
+
else:
|
|
329
|
+
issues.append(Issue("FAIL", path, line_no, f"报告仍包含旧口径 `{phrase}`。"))
|
|
330
|
+
|
|
331
|
+
tests_match = TESTS_RUN_RE.search(line)
|
|
332
|
+
if tests_match:
|
|
333
|
+
command = find_recent_command(lines, i)
|
|
334
|
+
if command:
|
|
335
|
+
command_tests.setdefault(command, set()).add(
|
|
336
|
+
tuple(int(tests_match.group(n)) for n in range(1, 5))
|
|
337
|
+
)
|
|
338
|
+
|
|
339
|
+
command_match = COMMAND_RE.search(line)
|
|
340
|
+
if command_match and line.count("mvn ") == 1:
|
|
341
|
+
normalized = normalize_command(command_match.group(1))
|
|
342
|
+
for ratio in RATIO_RE.finditer(line):
|
|
343
|
+
passed, total = int(ratio.group(1)), int(ratio.group(2))
|
|
344
|
+
if passed == total and total > 0:
|
|
345
|
+
command_ratios.setdefault(normalized, set()).add((passed, total))
|
|
346
|
+
|
|
347
|
+
if repo is not None:
|
|
348
|
+
for class_name, method in ANCHOR_RE.findall(line):
|
|
349
|
+
if class_name.startswith("D"):
|
|
350
|
+
continue
|
|
351
|
+
if method in {"java", "xml", "yml", "yaml", "properties", "class", "md"}:
|
|
352
|
+
continue
|
|
353
|
+
if class_name in {"BigDecimal", "LocalDateTime", "DateUtil", "StrUtil"}:
|
|
354
|
+
continue
|
|
355
|
+
if DELETED_CONTEXT.search(line):
|
|
356
|
+
continue
|
|
357
|
+
if class_name.endswith(("Test", "DTO", "VO", "PO", "Mapper")):
|
|
358
|
+
continue
|
|
359
|
+
if not class_seen_in_source(repo, class_name):
|
|
360
|
+
continue
|
|
361
|
+
if not grep_source(repo, method):
|
|
362
|
+
issues.append(
|
|
363
|
+
Issue(
|
|
364
|
+
"FAIL",
|
|
365
|
+
path,
|
|
366
|
+
line_no,
|
|
367
|
+
f"源码锚点 `{class_name}.{method}` 在当前 src/ 下未找到;若是旧实现请改写为已删除说明。",
|
|
368
|
+
)
|
|
369
|
+
)
|
|
370
|
+
|
|
371
|
+
for command, results in command_tests.items():
|
|
372
|
+
if len(results) > 1:
|
|
373
|
+
issues.append(Issue("FAIL", path, 0, f"同一测试命令 `{command}` 出现多个 Tests run 结果:{sorted(results)}。"))
|
|
374
|
+
|
|
375
|
+
for command, ratios in command_ratios.items():
|
|
376
|
+
tests = command_tests.get(command)
|
|
377
|
+
if not tests:
|
|
378
|
+
continue
|
|
379
|
+
test_totals = {item[0] for item in tests}
|
|
380
|
+
ratio_totals = {item[1] for item in ratios}
|
|
381
|
+
mismatch = ratio_totals - test_totals
|
|
382
|
+
if mismatch:
|
|
383
|
+
issues.append(
|
|
384
|
+
Issue("FAIL", path, 0, f"命令 `{command}` 的 N/N 口径 {sorted(ratios)} 与 Tests run {sorted(tests)} 不一致。")
|
|
385
|
+
)
|
|
386
|
+
|
|
387
|
+
if re.search(r"(interconnect|跨仓|sibling)", text, re.I):
|
|
388
|
+
if "Tests are skipped" in text and "临时" not in text and "回滚" not in text:
|
|
389
|
+
issues.append(Issue("FAIL", path, 0, "跨仓报告提到测试被跳过,但缺少临时改 skipTests 后真执行并回滚的证据。"))
|
|
390
|
+
|
|
391
|
+
return issues
|
|
392
|
+
|
|
393
|
+
|
|
394
|
+
def main() -> int:
|
|
395
|
+
parser = argparse.ArgumentParser(description="Lint SuperBridge Flow test-report evidence")
|
|
396
|
+
parser.add_argument("reports", nargs="+")
|
|
397
|
+
parser.add_argument("--tests", default="")
|
|
398
|
+
parser.add_argument("--repo-root", default="")
|
|
399
|
+
parser.add_argument("--warn-only", action="store_true")
|
|
400
|
+
args = parser.parse_args()
|
|
401
|
+
|
|
402
|
+
all_issues: list[Issue] = []
|
|
403
|
+
for report in args.reports:
|
|
404
|
+
path = Path(report).resolve()
|
|
405
|
+
if not path.exists():
|
|
406
|
+
all_issues.append(Issue("FAIL", path, 0, "文件不存在"))
|
|
407
|
+
continue
|
|
408
|
+
repo = Path(args.repo_root).resolve() if args.repo_root else repo_root_for(path)
|
|
409
|
+
tests_path = Path(args.tests).resolve() if args.tests else None
|
|
410
|
+
all_issues.extend(lint_report(path, repo, tests_path))
|
|
411
|
+
|
|
412
|
+
printable = []
|
|
413
|
+
for issue in all_issues:
|
|
414
|
+
if args.warn_only and issue.level == "FAIL":
|
|
415
|
+
issue = Issue("WARN", issue.path, issue.line, issue.message)
|
|
416
|
+
printable.append(issue)
|
|
417
|
+
|
|
418
|
+
for issue in printable:
|
|
419
|
+
loc = f"{issue.path}:{issue.line}" if issue.line else str(issue.path)
|
|
420
|
+
print(f"{issue.level} [{loc}] {issue.message}")
|
|
421
|
+
|
|
422
|
+
failed = any(issue.level == "FAIL" for issue in all_issues)
|
|
423
|
+
if failed and not args.warn_only:
|
|
424
|
+
print("\n[SuperBridge Flow test-report 证据一致性拦截]")
|
|
425
|
+
print("请修正测试数字、假绿、真实链路证据、源码锚点或跨仓证据后重新提交。")
|
|
426
|
+
return 2
|
|
427
|
+
if not failed:
|
|
428
|
+
print("SuperBridge Flow test-report 证据一致性检查通过")
|
|
429
|
+
return 0
|
|
430
|
+
|
|
431
|
+
|
|
432
|
+
if __name__ == "__main__":
|
|
433
|
+
sys.exit(main())
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# SDD integration evidence verifier
|
|
3
|
+
# Checks that a test-report contains real application-level API integration
|
|
4
|
+
# evidence: startup, curl/API call, database query, log check, and test output.
|
|
5
|
+
|
|
6
|
+
set -u
|
|
7
|
+
|
|
8
|
+
usage() {
|
|
9
|
+
cat <<'USAGE'
|
|
10
|
+
用法:
|
|
11
|
+
superflow-verify-integration.sh <test-report.md> [更多 test-report.md]
|
|
12
|
+
|
|
13
|
+
说明:
|
|
14
|
+
该脚本检查 test-report 是否包含真实启动应用后的 API 集成测试证据。
|
|
15
|
+
它不替代 curl/mysql/日志命令本身,只负责阻止空泛报告冒充完成。
|
|
16
|
+
USAGE
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if [ "$#" -eq 0 ]; then
|
|
20
|
+
usage
|
|
21
|
+
exit 2
|
|
22
|
+
fi
|
|
23
|
+
|
|
24
|
+
FAILED=0
|
|
25
|
+
|
|
26
|
+
check_pattern() {
|
|
27
|
+
local file="$1"
|
|
28
|
+
local label="$2"
|
|
29
|
+
local pattern="$3"
|
|
30
|
+
|
|
31
|
+
if ! grep -Eiq "$pattern" "$file"; then
|
|
32
|
+
echo "FAIL [$file] 缺少${label}证据"
|
|
33
|
+
FAILED=1
|
|
34
|
+
else
|
|
35
|
+
echo "OK [$file] ${label}证据存在"
|
|
36
|
+
fi
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
for REPORT in "$@"; do
|
|
40
|
+
if [ ! -f "$REPORT" ]; then
|
|
41
|
+
echo "FAIL [$REPORT] 文件不存在"
|
|
42
|
+
FAILED=1
|
|
43
|
+
continue
|
|
44
|
+
fi
|
|
45
|
+
|
|
46
|
+
echo "== 检查 $REPORT =="
|
|
47
|
+
|
|
48
|
+
check_pattern "$REPORT" "编译/测试执行" \
|
|
49
|
+
"BUILD SUCCESS|Tests run:|mvn .*test|gradle .*test|clean compile|compile"
|
|
50
|
+
check_pattern "$REPORT" "应用启动" \
|
|
51
|
+
"PID|进程|启动时间|Started .*Application|JVM running|端口|健康检查|actuator/health|UP"
|
|
52
|
+
check_pattern "$REPORT" "真实接口调用" \
|
|
53
|
+
"curl|HTTP|POST |GET |PUT |DELETE |PATCH |接口调用|请求|响应"
|
|
54
|
+
check_pattern "$REPORT" "数据库验证" \
|
|
55
|
+
"SHOW CREATE TABLE|SHOW COLUMNS|DESC |SELECT |数据库|DB|mysql"
|
|
56
|
+
check_pattern "$REPORT" "日志检查" \
|
|
57
|
+
"grep .*ERROR|无 ERROR|日志|ERROR|app\\.log"
|
|
58
|
+
check_pattern "$REPORT" "证据等级/真实验证结论" \
|
|
59
|
+
"Real integration passed|Partial real entry|Partially verified|Blocked|真实入口|API 集成|冒烟|Smoke"
|
|
60
|
+
|
|
61
|
+
if [ -x "$HOME/.codex/hooks/superflow-test-report-lint.py" ]; then
|
|
62
|
+
"$HOME/.codex/hooks/superflow-test-report-lint.py" "$REPORT"
|
|
63
|
+
if [ $? -ne 0 ]; then
|
|
64
|
+
FAILED=1
|
|
65
|
+
fi
|
|
66
|
+
fi
|
|
67
|
+
|
|
68
|
+
if grep -Eiq "跳过|未执行|待补充|TODO|后续测试|建议.*测试|是否.*测试" "$REPORT"; then
|
|
69
|
+
echo "FAIL [$REPORT] 存在跳过/待补充/后续测试类表述,不能标记完成"
|
|
70
|
+
FAILED=1
|
|
71
|
+
fi
|
|
72
|
+
done
|
|
73
|
+
|
|
74
|
+
if [ "$FAILED" -ne 0 ]; then
|
|
75
|
+
cat <<'BLOCK_MSG'
|
|
76
|
+
|
|
77
|
+
[SDD 集成验收未通过]
|
|
78
|
+
任务不能交付。请补齐真实启动应用后的 API 集成测试证据:
|
|
79
|
+
1. 编译/测试真实输出
|
|
80
|
+
2. 应用新进程启动证据:PID、启动时间、端口、健康检查
|
|
81
|
+
3. 真实 curl/API 请求与响应摘要
|
|
82
|
+
4. SHOW CREATE TABLE / SELECT 等数据库验证
|
|
83
|
+
5. grep ERROR 或等效日志检查
|
|
84
|
+
6. 明确证据等级:Real integration passed / Partially verified / Blocked
|
|
85
|
+
BLOCK_MSG
|
|
86
|
+
exit 2
|
|
87
|
+
fi
|
|
88
|
+
|
|
89
|
+
echo "SDD 集成验收证据检查通过"
|
|
90
|
+
exit 0
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Claude Code settings.json 备份过滤器。
|
|
4
|
+
删除运行时状态和敏感信息,保留用户显式配置。
|
|
5
|
+
|
|
6
|
+
用法:
|
|
7
|
+
python3 sync-settings-json.py <src.json> <dest.json>
|
|
8
|
+
"""
|
|
9
|
+
import json
|
|
10
|
+
import sys
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def filter_settings(data: dict) -> dict:
|
|
14
|
+
result = {}
|
|
15
|
+
for key, value in data.items():
|
|
16
|
+
# 完全删除的运行时状态键
|
|
17
|
+
if key in ("permissions", "enabledPlugins", "extraKnownMarketplaces"):
|
|
18
|
+
continue
|
|
19
|
+
|
|
20
|
+
# env 中删除敏感 token
|
|
21
|
+
if key == "env" and isinstance(value, dict):
|
|
22
|
+
env_copy = dict(value)
|
|
23
|
+
env_copy.pop("ANTHROPIC_AUTH_TOKEN", None)
|
|
24
|
+
if env_copy:
|
|
25
|
+
result[key] = env_copy
|
|
26
|
+
continue
|
|
27
|
+
|
|
28
|
+
result[key] = value
|
|
29
|
+
|
|
30
|
+
return result
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def main():
|
|
34
|
+
if len(sys.argv) != 3:
|
|
35
|
+
print("用法: python3 sync-settings-json.py <src.json> <dest.json>", file=sys.stderr)
|
|
36
|
+
sys.exit(1)
|
|
37
|
+
|
|
38
|
+
src_path = sys.argv[1]
|
|
39
|
+
dest_path = sys.argv[2]
|
|
40
|
+
|
|
41
|
+
with open(src_path, "r", encoding="utf-8") as f:
|
|
42
|
+
data = json.load(f)
|
|
43
|
+
|
|
44
|
+
filtered = filter_settings(data)
|
|
45
|
+
|
|
46
|
+
with open(dest_path, "w", encoding="utf-8") as f:
|
|
47
|
+
json.dump(filtered, f, indent=2, ensure_ascii=False)
|
|
48
|
+
f.write("\n")
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
if __name__ == "__main__":
|
|
52
|
+
main()
|