Files
wehub-resource-sync a06f331eb8
CI / benchmark (push) Has been skipped
install-script / posix-syntax (push) Successful in 6m1s
CI / build-onnx (push) Failing after 6m43s
init-smoke / dry-run (push) Failing after 15m57s
security / govulncheck (push) Has been cancelled
security / trivy-fs (push) Has been cancelled
CI / test (1.26, ubuntu-latest) (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
CI / test (1.26, macos-latest) (push) Has been cancelled
CI / build-windows (push) Has been cancelled
CI / lint (push) Has been cancelled
install-script / powershell-syntax (push) Has been cancelled
install-script / install (macos-14) (push) Has been cancelled
install-script / install (ubuntu-latest) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:33:42 +08:00

273 lines
9.0 KiB
Go

package profiles
import (
"os"
"path/filepath"
"strings"
"testing"
)
// Per-profile instruction-body byte ceilings. The body is re-read into
// the model on every API call of every session (via the @-include), so
// a ballooning body is a per-turn tax — blowing a ceiling fails loudly
// instead of silently re-inflating the ambient prefix.
//
// core inherits the pre-profiles global-section ceiling (6.5 KiB); full
// gets slack for its longer surface description only; localization is
// the diet profile and must stay under 2 KiB (~0.45k tokens).
var bodyByteCeilings = map[string]int{
"core": 6656,
"full": 6912,
"localization": 2304, // explore opener + row joined at the explore-branch consolidation
}
func TestProfileBodyByteCeilings(t *testing.T) {
for _, p := range Table() {
ceiling, ok := bodyByteCeilings[p.Name]
if !ok {
t.Errorf("profile %q has no body byte ceiling — add one to bodyByteCeilings", p.Name)
continue
}
got := len(p.Body())
t.Logf("profile %-13s body bytes=%d (ceiling %d)", p.Name, got, ceiling)
if got > ceiling {
t.Errorf("profile %q body is %d bytes, over the %d ceiling", p.Name, got, ceiling)
}
if got == 0 {
t.Errorf("profile %q renders an empty body", p.Name)
}
}
}
// positioningCues are the load-bearing fragments EVERY profile must
// keep, lean ones included: the mandatory-rule sentinel, the deny-hook
// warning, the one-shot opener, the memory triggers, the discovery
// path, the CLI fallback, and the switch-back line. Trimming any of
// these is what costs tool adoption — the diet only ever removes
// elaboration around them.
var positioningCues = []string{
"## MANDATORY: Use Gortex MCP tools", // idempotency sentinel + the rule itself
"MUST prefer graph queries",
"deny",
"smart_context",
"distill_session",
"surface_memories",
"save_note",
"store_memory",
"tools_search",
"gortex://guide",
"gortex call",
"gortex instructions switch",
"NEW sessions only",
}
func TestEveryProfileKeepsPositioningCues(t *testing.T) {
for _, p := range Table() {
body := p.Body()
for _, cue := range positioningCues {
if !strings.Contains(body, cue) {
t.Errorf("profile %q body lost the positioning cue %q", p.Name, cue)
}
}
}
}
// TestLocalizationEagerToolsAllCued is the no-drift gate between the
// localization tool preset and the localization instructions body:
// every eager tool must be documented (table row or prose cue), and
// every table row must correspond to an eager tool.
func TestLocalizationEagerToolsAllCued(t *testing.T) {
p, ok := ByName("localization")
if !ok {
t.Fatal("localization profile missing from the table")
}
body := p.Body()
rowTools := map[string]bool{}
for _, r := range localizationRows {
rowTools[r.tool] = true
}
for _, tool := range p.EagerTools {
if !rowTools[tool] && !localizationNonTableTools[tool] {
t.Errorf("eager tool %q has neither a table row nor a prose cue — add one", tool)
}
if !strings.Contains(body, tool) {
t.Errorf("eager tool %q does not appear in the localization body", tool)
}
}
eager := map[string]bool{}
for _, tool := range p.EagerTools {
eager[tool] = true
}
for _, r := range localizationRows {
if !eager[r.tool] {
t.Errorf("table row for %q has no matching eager tool — remove the row or add the tool", r.tool)
}
}
for tool := range localizationNonTableTools {
if !eager[tool] {
t.Errorf("prose cue for %q has no matching eager tool", tool)
}
}
}
func TestGenerateAndSwitch(t *testing.T) {
dir := t.TempDir()
if err := Generate(dir); err != nil {
t.Fatalf("generate: %v", err)
}
for _, p := range Table() {
raw, err := os.ReadFile(filepath.Join(dir, p.Name+".md"))
if err != nil {
t.Fatalf("profile file %s: %v", p.Name, err)
}
if string(raw) != p.Body() {
t.Errorf("profile file %s.md is not the rendered body", p.Name)
}
}
// Before any switch, the active copy is the default profile.
active, err := os.ReadFile(filepath.Join(dir, ActiveFileName))
if err != nil {
t.Fatalf("active.md: %v", err)
}
def, _ := ByName(DefaultName)
if string(active) != def.Body() {
t.Error("active.md is not a byte copy of the default profile before any switch")
}
if got := ActiveName(dir); got != DefaultName {
t.Errorf("ActiveName = %q before any switch, want %q", got, DefaultName)
}
// Switch to localization: active.md becomes a byte copy, state
// records the name, and it is never a symlink.
p, err := Switch(dir, "localization")
if err != nil {
t.Fatalf("switch: %v", err)
}
if p.Name != "localization" {
t.Errorf("switch returned profile %q", p.Name)
}
active, err = os.ReadFile(filepath.Join(dir, ActiveFileName))
if err != nil {
t.Fatalf("active.md after switch: %v", err)
}
loc, _ := ByName("localization")
if string(active) != loc.Body() {
t.Error("active.md is not a byte copy of the switched profile")
}
if fi, err := os.Lstat(filepath.Join(dir, ActiveFileName)); err != nil || fi.Mode()&os.ModeSymlink != 0 {
t.Errorf("active.md must be a regular file, got mode %v err %v", fi.Mode(), err)
}
if got := ActiveName(dir); got != "localization" {
t.Errorf("ActiveName = %q after switch, want localization", got)
}
if Active(dir).HookTier != HookTierStandard {
t.Error("localization ships the standard hook tier (the lean tier is opt-in machinery)")
}
// Re-generate keeps the switched selection.
if err := Generate(dir); err != nil {
t.Fatalf("re-generate: %v", err)
}
active, _ = os.ReadFile(filepath.Join(dir, ActiveFileName))
if string(active) != loc.Body() {
t.Error("re-generate clobbered the active selection")
}
// Unknown profile is refused and changes nothing.
if _, err := Switch(dir, "nope"); err == nil {
t.Error("switch to unknown profile must error")
}
if got := ActiveName(dir); got != "localization" {
t.Errorf("failed switch mutated state: ActiveName = %q", got)
}
}
func TestActiveStateDegradesToDefault(t *testing.T) {
dir := t.TempDir()
// Missing state file.
if got := ActiveName(dir); got != DefaultName {
t.Errorf("missing state: ActiveName = %q, want %q", got, DefaultName)
}
// Corrupt state file.
if err := os.WriteFile(filepath.Join(dir, stateFileName), []byte("{nope"), 0o644); err != nil {
t.Fatal(err)
}
if got := ActiveName(dir); got != DefaultName {
t.Errorf("corrupt state: ActiveName = %q, want %q", got, DefaultName)
}
// Unknown name in state file.
if err := os.WriteFile(filepath.Join(dir, stateFileName), []byte(`{"name":"gone"}`), 0o644); err != nil {
t.Fatal(err)
}
if got := ActiveName(dir); got != DefaultName {
t.Errorf("unknown state name: ActiveName = %q, want %q", got, DefaultName)
}
}
func TestActiveEnvOverride(t *testing.T) {
dir := t.TempDir()
if _, err := Switch(dir, "full"); err != nil {
t.Fatal(err)
}
t.Setenv(ActiveEnv, "localization")
if got := ActiveName(dir); got != "localization" {
t.Errorf("env override ignored: ActiveName = %q", got)
}
// Unknown env value falls through to the recorded state.
t.Setenv(ActiveEnv, "not-a-profile")
if got := ActiveName(dir); got != "full" {
t.Errorf("unknown env value must fall through to state, got %q", got)
}
}
// Single-home markers: content that relocated to gortex://guide and the
// schema resources must never re-inflate an instruction body. Mirrors
// the pre-profiles guard on the CLAUDE.md rule block (the profile file
// is that block's new home).
var relocatedContentMarkers = []string{
"`local` / `anthropic` / `openai` / `azure` / `ollama` / `claudecli` / `codex` / `copilot` / `cursor` / `opencode` / `gemini` / `bedrock` / `deepseek`", // provider matrix
"Tarjan's SCC", // analyze catalog deep-dive
"compact tabular text, lossy", // wire-format deep-dive
"k8s_resources", // analyze kind catalog
"error-not-wrapped", // search_ast detector catalog
"gortex://report", // analyzer rollup roster
}
// fullPolicyTokens is the policy core the standard-depth profiles must
// carry — the machine-level single home for the full memory-workflow
// triggers. The localization profile intentionally keeps only the
// positioning cues (TestEveryProfileKeepsPositioningCues).
var fullPolicyTokens = []string{
"search_symbols", "find_usages", "get_callers", "get_call_chain",
"get_symbol_source", "get_editing_context", "read_file",
"smart_context", "edit_file", "rename_symbol", "compress_bodies",
"distill_session", "surface_memories", "save_note", "store_memory",
"query_notes", "query_memories",
"tools_search", "gortex://guide", "gortex daemon start",
}
func TestBodies_PolicyCoreAndSingleHome(t *testing.T) {
for _, name := range []string{"core", "full"} {
p, ok := ByName(name)
if !ok {
t.Fatalf("profile %q missing", name)
}
body := p.Body()
for _, token := range fullPolicyTokens {
if !strings.Contains(body, token) {
t.Errorf("%s body no longer mentions %q — policy core regression", name, token)
}
}
}
for _, p := range Table() {
body := p.Body()
for _, banned := range relocatedContentMarkers {
if strings.Contains(body, banned) {
t.Errorf("%s body re-carries relocated content %q — single-home violation", p.Name, banned)
}
}
}
}