Canonicalize material quotes and own checkpoint status
Why: Independent re-review found single-word decision quotes, project-prefix scope mixing, revoked totals and approval status bypasses. Evidence: Each new counterexample failed before repair; Backend go test ./... -count=1 and selected live-tag fake-stream tests passed after. Impact: Display complete visible source sentences, parse only bounded positive count clauses, and set material progress text from the host phase. Refs: Dev task feature/fast-slow-u2-u3-plan; r0.3 second review Important findings
This commit is contained in:
@@ -12,18 +12,18 @@ import (
|
|||||||
// These are deliberately narrow source grammars, not natural-language proofs.
|
// These are deliberately narrow source grammars, not natural-language proofs.
|
||||||
// A missed expression produces a pending question instead of a model claim.
|
// A missed expression produces a pending question instead of a model claim.
|
||||||
var countPremisePatterns = map[string]*regexp.Regexp{
|
var countPremisePatterns = map[string]*regexp.Regexp{
|
||||||
"total": regexp.MustCompile(`(?:交付包含|总共|共计|共有|总计|总)([0-9]+)项(?:核心需求|需求|功能|任务|[,。、;;[:space:]]|$)`),
|
"total": regexp.MustCompile(`^(?:交付包含|总共|共计|共有|总计|总)([0-9]+)项(?:核心需求|需求|功能|任务)?$`),
|
||||||
"phase": regexp.MustCompile(`(?:第一期|第1期|一期)(?:先)?(?:完成|交付|安排)?([0-9]+)项(?:核心需求|需求|功能|任务|[,。、;;[:space:]]|$)`),
|
"phase": regexp.MustCompile(`^(?:第一期|第1期|一期)(?:先)?(?:完成|交付|安排)?([0-9]+)项(?:核心需求|需求|功能|任务)?$`),
|
||||||
"dependent": regexp.MustCompile(`其中([0-9]+)项依赖(?:外部)?接口`),
|
"dependent": regexp.MustCompile(`^其中([0-9]+)项依赖(?:外部)?接口(?:的需求)?$`),
|
||||||
}
|
}
|
||||||
|
|
||||||
var directRelationPattern = regexp.MustCompile(`(?:第一期|第二期|第1期|第2期|一期|二期)[^。;;]*?(?:包含|包括|完成|交付)[0-9]+项|其中[0-9]+项依赖(?:外部)?接口|(?:会议决定|已批准|已经批准|审批通过)`)
|
var directRelationPattern = regexp.MustCompile(`(?:第一期|第二期|第1期|第2期|一期|二期)[^。;;]*?(?:包含|包括|完成|交付)[0-9]+项|其中[0-9]+项依赖(?:外部)?接口|(?:会议决定|已批准|已经批准|审批通过)`)
|
||||||
var sourceContextRiskPattern = regexp.MustCompile(`[0-90-9]|[一二三四五六七八九十]+(?:项|期|万元)|已决定|已经决定|已批准|已经批准|已获批|批复同意|已通过审批|审批通过|包含|包括|属于|剩余|第一期|第二期`)
|
var sourceContextRiskPattern = regexp.MustCompile(`[0-90-9]|[一二三四五六七八九十]+(?:项|期|万元)|已决定|已经决定|已批准|已经批准|已获批|批复同意|已通过审批|审批通过|包含|包括|属于|剩余|第一期|第二期`)
|
||||||
var consequentialClaimPattern = regexp.MustCompile(sourceContextRiskPattern.String() + `|接口|依赖|批次|批|轮|阶段|分期|归属|后续|全部|全数|全放|均|项目|范围|预算|审批|决定|安排`)
|
var consequentialClaimPattern = regexp.MustCompile(sourceContextRiskPattern.String() + `|接口|依赖|批次|批|轮|阶段|分期|归属|后续|全部|全数|全放|均|项目|范围|预算|审批|决定|安排`)
|
||||||
var cautiousAnalysisPattern = regexp.MustCompile(`存在差异|意见不同|不一致|需核对|需要核对|待核对|待确认|请确认|未明确|尚未给出|建议|可能`)
|
var cautiousAnalysisPattern = regexp.MustCompile(`存在差异|意见不同|不一致|需核对|需要核对|待核对|待确认|请确认|未明确|尚未给出|建议|可能`)
|
||||||
var safeStatusPrefix = regexp.MustCompile(`^(?:核对|继续核对|整理|继续整理|汇总|待|仍需|查看|确认|补齐|检查|阅读|梳理|形成)`)
|
var negatedPremisePattern = regexp.MustCompile(`并非|不是|不等于|不代表|尚未|并未|没有|未|否认|否定|不得|不能|已取消|取消|废止|撤销|作废|失效|不再`)
|
||||||
var negatedPremisePattern = regexp.MustCompile(`并非|不是|不等于|不代表|尚未|并未|没有|未|否认|不得|不能`)
|
var explicitScopePattern = regexp.MustCompile(`(?:^|[。;;,,[:space:]])(?:([A-Za-z0-9甲乙丙丁]{1,8})项目|项目([A-Za-z0-9甲乙丙丁]{1,8}))`)
|
||||||
var explicitScopePattern = regexp.MustCompile(`(?:^|[。;;,,[:space:]])([\p{Han}A-Za-z0-9]{1,16})项目`)
|
var scopePrefixPattern = regexp.MustCompile(`^(?:[A-Za-z0-9甲乙丙丁]{1,8}项目|项目[A-Za-z0-9甲乙丙丁]{1,8})`)
|
||||||
|
|
||||||
// sourceClause restores the visible sentence around an exact short quote. A
|
// sourceClause restores the visible sentence around an exact short quote. A
|
||||||
// duplicate or oversized sentence is ambiguous and cannot prove a relation.
|
// duplicate or oversized sentence is ambiguous and cannot prove a relation.
|
||||||
@@ -70,7 +70,13 @@ func explicitScope(clause string) (string, bool) {
|
|||||||
return "", false
|
return "", false
|
||||||
}
|
}
|
||||||
if len(matches) == 1 {
|
if len(matches) == 1 {
|
||||||
return matches[0][1], true
|
if matches[0][1] != "" {
|
||||||
|
return matches[0][1], true
|
||||||
|
}
|
||||||
|
return matches[0][2], true
|
||||||
|
}
|
||||||
|
if strings.Contains(clause, "项目") {
|
||||||
|
return "", false
|
||||||
}
|
}
|
||||||
return "", true
|
return "", true
|
||||||
}
|
}
|
||||||
@@ -80,12 +86,21 @@ func parseCountPremise(quote, role string, value int) bool {
|
|||||||
if pattern == nil || value < 0 {
|
if pattern == nil || value < 0 {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
matches := pattern.FindAllStringSubmatch(quote, -1)
|
matched := -1
|
||||||
if len(matches) != 1 {
|
for _, segment := range strings.FieldsFunc(quote, func(r rune) bool { return strings.ContainsRune(",,。;;!??!\n", r) }) {
|
||||||
return false
|
segment = strings.TrimSpace(segment)
|
||||||
|
segment = scopePrefixPattern.ReplaceAllString(segment, "")
|
||||||
|
parts := pattern.FindStringSubmatch(segment)
|
||||||
|
if len(parts) != 2 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
count, err := strconv.Atoi(parts[1])
|
||||||
|
if err != nil || matched >= 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
matched = count
|
||||||
}
|
}
|
||||||
count, err := strconv.Atoi(matches[0][1])
|
return matched == value
|
||||||
return err == nil && count == value
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func pendingEvidenceFinding(f core.WorkFindingCandidate) core.WorkFindingCandidate {
|
func pendingEvidenceFinding(f core.WorkFindingCandidate) core.WorkFindingCandidate {
|
||||||
@@ -136,16 +151,16 @@ func normalizeMaterialEvidence(candidate core.WorkCheckpointCandidate, sources m
|
|||||||
}
|
}
|
||||||
if f.Evidence == nil {
|
if f.Evidence == nil {
|
||||||
if f.Kind == "material_record" {
|
if f.Kind == "material_record" {
|
||||||
for _, quote := range f.Sources {
|
for j, quote := range f.Sources {
|
||||||
if !sourceContextRiskPattern.MatchString(quote.Quote) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
clause, ok := sourceClause(sources, quote)
|
clause, ok := sourceClause(sources, quote)
|
||||||
if !ok || strings.TrimRight(strings.TrimSpace(quote.Quote), "。;;!??!") != clause {
|
if !ok {
|
||||||
normalized.Findings[i] = pendingEvidenceFinding(f)
|
normalized.Findings[i] = pendingEvidenceFinding(f)
|
||||||
notes = append(notes, f.FindingKey+":短引缺少原句语境,已转待确认")
|
notes = append(notes, f.FindingKey+":原句语境不唯一,已转待确认")
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
f.Sources = append([]core.TaskSourceQuote(nil), f.Sources...)
|
||||||
|
f.Sources[j].Quote = clause
|
||||||
|
normalized.Findings[i] = f
|
||||||
}
|
}
|
||||||
} else if consequentialClaimPattern.MatchString(f.Text+f.Impact+f.Assumptions) || !cautiousAnalysisPattern.MatchString(f.Text) {
|
} else if consequentialClaimPattern.MatchString(f.Text+f.Impact+f.Assumptions) || !cautiousAnalysisPattern.MatchString(f.Text) {
|
||||||
normalized.Findings[i] = pendingEvidenceFinding(f)
|
normalized.Findings[i] = pendingEvidenceFinding(f)
|
||||||
|
|||||||
@@ -209,7 +209,7 @@ func TestMaterialEvidenceDoesNotDropNegationOutsideShortQuote(t *testing.T) {
|
|||||||
sources := materialSources(docs, 48000)
|
sources := materialSources(docs, 48000)
|
||||||
approval := core.WorkCheckpointCandidate{CheckpointID: "approval-short", PhaseID: "crosscheck", Findings: []core.WorkFindingCandidate{{FindingKey: "approval", Text: "已批准", Kind: "material_record", Sources: []core.TaskSourceQuote{evidenceQuote(t, sources, "review", "已批准")}}}}
|
approval := core.WorkCheckpointCandidate{CheckpointID: "approval-short", PhaseID: "crosscheck", Findings: []core.WorkFindingCandidate{{FindingKey: "approval", Text: "已批准", Kind: "material_record", Sources: []core.TaskSourceQuote{evidenceQuote(t, sources, "review", "已批准")}}}}
|
||||||
normalized, _, err := normalizeMaterialEvidence(approval, sources)
|
normalized, _, err := normalizeMaterialEvidence(approval, sources)
|
||||||
if err != nil || normalized.Findings[0].Kind != "needs_user" {
|
if err != nil || normalized.Findings[0].Kind != "material_record" || !strings.Contains(normalized.Findings[0].Sources[0].Quote, "不等于已批准") {
|
||||||
t.Fatalf("negation was removed from a source record: %+v %v", normalized, err)
|
t.Fatalf("negation was removed from a source record: %+v %v", normalized, err)
|
||||||
}
|
}
|
||||||
approval.Findings[0].Evidence = &core.WorkFindingEvidence{Mode: "direct_relation", Premises: []core.WorkEvidencePremise{{Role: "direct_relation", SourceIndex: 0}}}
|
approval.Findings[0].Evidence = &core.WorkFindingEvidence{Mode: "direct_relation", Premises: []core.WorkEvidencePremise{{Role: "direct_relation", SourceIndex: 0}}}
|
||||||
@@ -225,6 +225,49 @@ func TestMaterialEvidenceDoesNotDropNegationOutsideShortQuote(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestMaterialEvidenceRestoresNegationForUnlistedDecisionWord(t *testing.T) {
|
||||||
|
docs := []core.ParsedDocument{{Meta: core.DocMeta{Filename: "方案.txt", Origin: &core.DocumentOrigin{ArtifactID: "decision", ContentHash: "decision-hash", ParserRevision: "txt.v1"}}, Blocks: []core.DocBlock{{Type: core.BlockPara, Text: "方案尚未通过。"}}}}
|
||||||
|
sources := materialSources(docs, 48000)
|
||||||
|
candidate := core.WorkCheckpointCandidate{CheckpointID: "decision-short", PhaseID: "crosscheck", Findings: []core.WorkFindingCandidate{{FindingKey: "decision", Text: "通过", Kind: "material_record", Sources: []core.TaskSourceQuote{evidenceQuote(t, sources, "decision", "通过")}}}}
|
||||||
|
normalized, _, err := normalizeMaterialEvidence(candidate, sources)
|
||||||
|
if err != nil || normalized.Findings[0].Kind != "material_record" || normalized.Findings[0].Sources[0].Quote != "方案尚未通过" {
|
||||||
|
t.Fatalf("short decision quote lost negation: %+v %v", normalized, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMaterialEvidenceRejectsProjectPrefixScopesAndCancelledCounts(t *testing.T) {
|
||||||
|
n := func(v int) *int { return &v }
|
||||||
|
for _, tc := range []struct {
|
||||||
|
name, total, phase, dependent string
|
||||||
|
}{
|
||||||
|
{"two scopes in one source", "项目A总12项需求,项目B第一期8项需求,其中12项依赖接口", "项目A总12项需求,项目B第一期8项需求,其中12项依赖接口", "项目A总12项需求,项目B第一期8项需求,其中12项依赖接口"},
|
||||||
|
{"cancelled total", "已取消总12项需求", "第一期先完成8项核心需求", "其中3项依赖外部接口"},
|
||||||
|
} {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
docs := []core.ParsedDocument{
|
||||||
|
{Meta: core.DocMeta{Filename: "范围.txt", Origin: &core.DocumentOrigin{ArtifactID: "total", ContentHash: "h-total", ParserRevision: "txt.v1"}}, Blocks: []core.DocBlock{{Type: core.BlockPara, Text: tc.total}}},
|
||||||
|
{Meta: core.DocMeta{Filename: "阶段.txt", Origin: &core.DocumentOrigin{ArtifactID: "phase", ContentHash: "h-phase", ParserRevision: "txt.v1"}}, Blocks: []core.DocBlock{{Type: core.BlockPara, Text: tc.phase}}},
|
||||||
|
{Meta: core.DocMeta{Filename: "依赖.txt", Origin: &core.DocumentOrigin{ArtifactID: "dependent", ContentHash: "h-dependent", ParserRevision: "txt.v1"}}, Blocks: []core.DocBlock{{Type: core.BlockPara, Text: tc.dependent}}},
|
||||||
|
}
|
||||||
|
sources := materialSources(docs, 48000)
|
||||||
|
candidate := core.WorkCheckpointCandidate{CheckpointID: "unsafe-scope", PhaseID: "crosscheck", Findings: []core.WorkFindingCandidate{{FindingKey: "scope", Text: "差额与归属确定", Kind: "analysis", Assumptions: "模型推断", Sources: []core.TaskSourceQuote{evidenceQuote(t, sources, "total", tc.total), evidenceQuote(t, sources, "phase", tc.phase), evidenceQuote(t, sources, "dependent", tc.dependent)}, Evidence: &core.WorkFindingEvidence{Mode: "unresolved_relation", Premises: []core.WorkEvidencePremise{{Role: "total", SourceIndex: 0, Value: n(12)}, {Role: "phase", SourceIndex: 1, Value: n(8)}, {Role: "dependent", SourceIndex: 2, Value: n(12)}}}}}}
|
||||||
|
if tc.name == "two scopes in one source" {
|
||||||
|
candidate.Findings[0].Sources = candidate.Findings[0].Sources[:1]
|
||||||
|
candidate.Findings[0].Evidence.Premises[1].SourceIndex = 0
|
||||||
|
candidate.Findings[0].Evidence.Premises[2].SourceIndex = 0
|
||||||
|
} else {
|
||||||
|
candidate.Findings[0].Evidence.Mode = "difference"
|
||||||
|
candidate.Findings[0].Evidence.Premises = candidate.Findings[0].Evidence.Premises[:2]
|
||||||
|
candidate.Findings[0].Evidence.Result = n(4)
|
||||||
|
}
|
||||||
|
normalized, _, err := normalizeMaterialEvidence(candidate, sources)
|
||||||
|
if err != nil || normalized.Findings[0].Kind != "needs_user" {
|
||||||
|
t.Fatalf("unsafe scope/count was treated as proven: %+v %v", normalized, err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestMaterialEvidenceDifferentExplicitScopesBecomePending(t *testing.T) {
|
func TestMaterialEvidenceDifferentExplicitScopesBecomePending(t *testing.T) {
|
||||||
docs := []core.ParsedDocument{
|
docs := []core.ParsedDocument{
|
||||||
{Meta: core.DocMeta{Filename: "A.txt", Origin: &core.DocumentOrigin{ArtifactID: "req", ContentHash: "req-hash", ParserRevision: "txt.v1"}}, Blocks: []core.DocBlock{{Type: core.BlockPara, Text: "A项目总12项需求。"}}},
|
{Meta: core.DocMeta{Filename: "A.txt", Origin: &core.DocumentOrigin{ArtifactID: "req", ContentHash: "req-hash", ParserRevision: "txt.v1"}}, Blocks: []core.DocBlock{{Type: core.BlockPara, Text: "A项目总12项需求。"}}},
|
||||||
|
|||||||
@@ -69,22 +69,31 @@ func (c *runCtx) PublishWorkCheckpoint(candidate core.WorkCheckpointCandidate) (
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
statusNormalized := false
|
if normalized.Focus != "" || normalized.Remaining != "" || normalized.NextStep != "" {
|
||||||
for _, field := range []struct {
|
notes = append(notes, "任务状态按工作阶段显示中性说明")
|
||||||
value *string
|
|
||||||
text string
|
|
||||||
}{{&normalized.Focus, "核对材料中可证实的要点"}, {&normalized.Remaining, "数字或归属仍待核对"}, {&normalized.NextStep, "核对原文后继续整理"}} {
|
|
||||||
if consequentialClaimPattern.MatchString(*field.value) || (*field.value != "" && !safeStatusPrefix.MatchString(*field.value)) {
|
|
||||||
*field.value = field.text
|
|
||||||
statusNormalized = true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if statusNormalized {
|
switch candidate.PhaseID {
|
||||||
notes = append(notes, "任务状态中的未证实判断已改为中性说明")
|
case "reading":
|
||||||
|
normalized.Focus, normalized.Remaining, normalized.NextStep = "整理材料中的事实", "仍需核对差异和缺项", "继续核对材料"
|
||||||
|
case "crosscheck":
|
||||||
|
normalized.Focus, normalized.Remaining, normalized.NextStep = "核对材料中的差异", "仍需整理结论", "结合依据形成结果"
|
||||||
|
case "summarizing":
|
||||||
|
normalized.Focus, normalized.Remaining, normalized.NextStep = "整理已核对的要点", "仍需形成完整答复", "完成整理结果"
|
||||||
|
case "draft-ready":
|
||||||
|
normalized.Focus, normalized.Remaining, normalized.NextStep = "形成整理结果", "等待完整答复", "输出最终结果"
|
||||||
}
|
}
|
||||||
if err := validateMaterialApprovalClaims(normalized); err != nil {
|
if err := validateMaterialApprovalClaims(normalized); err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
for i, f := range normalized.Findings {
|
||||||
|
for j, q := range f.Sources {
|
||||||
|
src, err := c.sources.validate(q)
|
||||||
|
if err != nil || src.ID != verifiedSources[i][j].ID || src.ArtifactID != verifiedSources[i][j].ArtifactID {
|
||||||
|
return "", fmt.Errorf("checkpoint.source: normalized quote unavailable")
|
||||||
|
}
|
||||||
|
verifiedSources[i][j] = src
|
||||||
|
}
|
||||||
|
}
|
||||||
findings := make([]core.TaskFinding, 0, len(normalized.Findings))
|
findings := make([]core.TaskFinding, 0, len(normalized.Findings))
|
||||||
critical := false
|
critical := false
|
||||||
for i, f := range normalized.Findings {
|
for i, f := range normalized.Findings {
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ func checkpointEvidenceEnvironment(t *testing.T) *runCtx {
|
|||||||
conv := recoveryConversation(t, st)
|
conv := recoveryConversation(t, st)
|
||||||
parent := recoveryRun(t, st, conv, core.RunRunning)
|
parent := recoveryRun(t, st, conv, core.RunRunning)
|
||||||
inputs := []struct{ name, text string }{
|
inputs := []struct{ name, text string }{
|
||||||
{"需求.txt", "交付包含12项需求,其中3项依赖外部接口。"},
|
{"需求.txt", "交付包含12项需求,其中3项依赖外部接口。方案尚未通过。"},
|
||||||
{"会议.txt", "会议决定分两期交付,第一期先完成8项核心需求。"},
|
{"会议.txt", "会议决定分两期交付,第一期先完成8项核心需求。"},
|
||||||
}
|
}
|
||||||
docs := make([]core.ParsedDocument, 0, len(inputs))
|
docs := make([]core.ParsedDocument, 0, len(inputs))
|
||||||
@@ -183,3 +183,45 @@ func TestWorkCheckpointEvidenceUnknownStatusAssertionIsNeutralized(t *testing.T)
|
|||||||
t.Fatal("unknown status assertion escaped", card.Task.Focus)
|
t.Fatal("unknown status assertion escaped", card.Task.Focus)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestWorkCheckpointEvidenceStatusDoesNotInferApprovalFromConfirmVerb(t *testing.T) {
|
||||||
|
c := checkpointEnvironment(t)
|
||||||
|
cp := validCheckpoint(c, "approval-status")
|
||||||
|
cp.Focus = "确认方案获准"
|
||||||
|
cp.NextStep = "确认方案已生效"
|
||||||
|
if _, err := c.PublishWorkCheckpoint(cp); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
card, err := c.mgr.st.GetCard(c.run.Owner, c.task.cardID)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if card.Task.Focus == cp.Focus || card.Task.NextStep == cp.NextStep {
|
||||||
|
t.Fatalf("model approval status escaped: %q / %q", card.Task.Focus, card.Task.NextStep)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWorkCheckpointEvidencePersistsFullNegativeSourceSentence(t *testing.T) {
|
||||||
|
c := checkpointEvidenceEnvironment(t)
|
||||||
|
var quote core.TaskSourceQuote
|
||||||
|
for id, source := range c.sources {
|
||||||
|
if strings.Contains(source.Ref.Label, "需求.txt") {
|
||||||
|
quote = core.TaskSourceQuote{SourceID: id, BlockIndex: source.Ref.BlockIndex, Quote: "通过"}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if quote.SourceID == "" {
|
||||||
|
t.Fatal("requirements source missing")
|
||||||
|
}
|
||||||
|
cp := core.WorkCheckpointCandidate{CheckpointID: "negative-record", PhaseID: "crosscheck", Findings: []core.WorkFindingCandidate{{FindingKey: "decision", Text: "方案通过", Kind: "material_record", Sources: []core.TaskSourceQuote{quote}}}}
|
||||||
|
if _, err := c.PublishWorkCheckpoint(cp); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
card, err := c.mgr.st.GetCard(c.run.Owner, c.task.cardID)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(card.Task.Findings) != 1 || !strings.Contains(card.Task.Findings[0].Text, "方案尚未通过") || strings.Contains(card.Task.Findings[0].Text, ":「通过」") {
|
||||||
|
t.Fatalf("short affirmative quote reached the card: %+v", card.Task.Findings)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user