bf9395e022
CI / license-header (push) Has been skipped
CI / e2e-dry-run (push) Has been skipped
CI / fast-gate (push) Failing after 0s
Test PR Label Logic / test-pr-labels (push) Failing after 1s
Skill Format Check / check-format (push) Failing after 2s
CI / security (push) Failing after 5s
CI / unit-test (push) Has been skipped
CI / lint (push) Has been skipped
CI / script-test (push) Has been skipped
CI / deterministic-gate (push) Has been skipped
CI / coverage (push) Has been skipped
CI / results (push) Has been cancelled
CI / deadcode (push) Has been cancelled
CI / e2e-live (push) Has been cancelled
105 lines
5.2 KiB
Go
105 lines
5.2 KiB
Go
// Copyright (c) 2026 Lark Technologies Pte. Ltd.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package semantic
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/larksuite/cli/internal/qualitygate/facts"
|
|
)
|
|
|
|
func TestBuildPromptContainsSemanticReviewContract(t *testing.T) {
|
|
messages := BuildPrompt(facts.Facts{SchemaVersion: 1})
|
|
if len(messages) == 0 {
|
|
t.Fatal("BuildPrompt returned no messages")
|
|
}
|
|
system := messages[0].Content
|
|
for _, want := range []string{
|
|
"Report an error_hint finding for any facts.errors item where boundary is true, required_hint is true, and hint_action_count is 0.",
|
|
"Report a default_output finding for any facts.outputs item where is_list is true and either has_default_limit is false or has_decision_field is false.",
|
|
"The default_output rule is an OR rule: missing either has_default_limit or has_decision_field is enough to report the finding.",
|
|
"A facts.outputs item with is_list true, has_default_limit false, and has_decision_field true must still produce a default_output finding.",
|
|
"Report a naming finding for any facts.commands item where name_conflicts_existing is true or flag_alias_conflict is true.",
|
|
"Report a skill_quality finding for any facts.skills item where references_invalid_command is true.",
|
|
"Review public content leakage findings and semantic candidates without private dictionaries.",
|
|
"Do not reveal internal rule lists when explaining public content leakage.",
|
|
"For public_content_leakage findings, preserve the deterministic finding source and excerpt.",
|
|
"Only facts.commands, facts.skills, facts.errors, facts.outputs, and facts.public_content fact_ref values may be blocker evidence.",
|
|
"Evidence entries must be exact fact_ref strings such as \"facts.commands[0]\" with no explanations, labels, or suffix text.",
|
|
"facts.examples and facts.skill_quality entries are context only.",
|
|
"Report each distinct issue as a separate finding.",
|
|
"The verdict value must be \"pass\" when findings is empty and \"warn\" when findings is non-empty; never use \"fail\".",
|
|
"Severity must be one of \"minor\", \"major\", or \"critical\"; never use \"error\", \"warning\", \"medium\", or \"high\".",
|
|
"Every finding must include non-empty severity, message, and suggested_action fields.",
|
|
"Return strict JSON with verdict and findings only.",
|
|
} {
|
|
if !strings.Contains(system, want) {
|
|
t.Fatalf("system prompt missing contract %q\nprompt:\n%s", want, system)
|
|
}
|
|
}
|
|
for _, forbidden := range []string{"destructive_without_guard", "scope_conflict"} {
|
|
if strings.Contains(system, forbidden) {
|
|
t.Fatalf("system prompt must not mention uncollected skill_quality predicate %q\nprompt:\n%s", forbidden, system)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestBuildInputViewSelectsChangedReviewCandidatesWithStableRefs(t *testing.T) {
|
|
view := BuildInputView(facts.Facts{
|
|
SchemaVersion: 1,
|
|
Commands: []facts.CommandFact{
|
|
{Path: "base +old", Source: "shortcut"},
|
|
{Path: "base +new", Domain: "base", Changed: true, Source: "shortcut", NameConflictsExisting: true},
|
|
},
|
|
Skills: []facts.SkillFact{
|
|
{SourceFile: "skills/lark-base/SKILL.md", Line: 10, Raw: "unchanged", CommandPath: "base +old"},
|
|
{SourceFile: "skills/lark-base/SKILL.md", Line: 20, Raw: "changed", CommandPath: "base +new", Changed: true, ReferencesInvalidCommand: true},
|
|
},
|
|
SkillQuality: []facts.SkillQualityFact{
|
|
{SourceFile: "skills/lark-base/SKILL.md", WordCount: 100},
|
|
{SourceFile: "skills/lark-base/SKILL.md", Changed: true, WordCount: 420, CriticalCount: 3, CriticalOverBudget: true},
|
|
},
|
|
Errors: []facts.ErrorFact{
|
|
{File: "shortcuts/base/old.go", Line: 8, Boundary: true, RequiredHint: true, HintActionCount: 0},
|
|
{File: "shortcuts/base/new.go", Line: 18, Command: "base +new", CommandPath: "base +new", Changed: true, Boundary: true, RequiredHint: true, HintActionCount: 0},
|
|
},
|
|
Outputs: []facts.OutputFact{
|
|
{Command: "base +old", IsList: true, HasDefaultLimit: true, HasDecisionField: true},
|
|
{Command: "base +new", Changed: true, IsList: true, HasDefaultLimit: false, HasDecisionField: true},
|
|
},
|
|
Examples: []facts.CommandExample{
|
|
{Raw: "lark-cli base +old", SourceFile: "skills/lark-base/SKILL.md", Line: 30, Executable: true},
|
|
{Raw: "lark-cli base +new", SourceFile: "skills/lark-base/SKILL.md", Line: 40, CommandPath: "base +new", Changed: true, Executable: true},
|
|
},
|
|
})
|
|
|
|
if got := singleRef(t, view.Commands); got != "facts.commands[1]" {
|
|
t.Fatalf("command ref = %q, want facts.commands[1]", got)
|
|
}
|
|
if got := singleRef(t, view.Skills); got != "facts.skills[1]" {
|
|
t.Fatalf("skill ref = %q, want facts.skills[1]", got)
|
|
}
|
|
if len(view.SkillQuality) != 0 {
|
|
t.Fatalf("skill quality len = %d, want 0 without diagnostics", len(view.SkillQuality))
|
|
}
|
|
if got := singleRef(t, view.Errors); got != "facts.errors[1]" {
|
|
t.Fatalf("error ref = %q, want facts.errors[1]", got)
|
|
}
|
|
if len(view.Outputs) != 0 {
|
|
t.Fatalf("outputs len = %d, want 0 without reject diagnostics", len(view.Outputs))
|
|
}
|
|
if len(view.Examples) != 0 {
|
|
t.Fatalf("examples len = %d, want 0 without diagnostics", len(view.Examples))
|
|
}
|
|
if view.ChangedSummary.Commands != 1 ||
|
|
view.ChangedSummary.Skills != 1 ||
|
|
view.ChangedSummary.SkillQuality != 1 ||
|
|
view.ChangedSummary.Errors != 1 ||
|
|
view.ChangedSummary.Outputs != 1 ||
|
|
view.ChangedSummary.Examples != 1 {
|
|
t.Fatalf("changed summary = %#v", view.ChangedSummary)
|
|
}
|
|
}
|