mirror of
https://github.com/esengine/DeepSeek-Reasonix.git
synced 2026-11-07 10:45:59 -10:00
fix(studio): report plugin hook declaration warnings
This commit is contained in:
1 parent
921e7bdb95
commit
7a8dfa5499
5 files changed
+161
No files matched your search
@@ -0,0 +1,30 @@
|
||||
package hook
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"maps"
|
||||
"slices"
|
||||
|
||||
"reasonix/internal/ext/pluginpkg"
|
||||
)
|
||||
|
||||
// PackageWarnings diagnoses hook declarations without consulting the enabled
|
||||
// registry or executing commands.
|
||||
func PackageWarnings(pkg pluginpkg.Package) []string {
|
||||
var warnings []string
|
||||
for _, eventName := range slices.Sorted(maps.Keys(pkg.Manifest.Hooks)) {
|
||||
if !IsKnownEvent(eventName) {
|
||||
warnings = append(warnings, fmt.Sprintf("hooks.%s: unknown event; these hooks will not run", eventName))
|
||||
continue
|
||||
}
|
||||
if !UsesToolMatcher(Event(eventName)) {
|
||||
continue
|
||||
}
|
||||
for i, entry := range pkg.Manifest.Hooks[eventName] {
|
||||
if warning := ValidateMatcher(entry.Match); warning != "" {
|
||||
warnings = append(warnings, fmt.Sprintf("hooks.%s[%d]: %s", eventName, i, warning))
|
||||
}
|
||||
}
|
||||
}
|
||||
return warnings
|
||||
}
|
||||
@@ -465,6 +465,7 @@ func pluginDoctorCommand(args []string) int {
|
||||
}
|
||||
}
|
||||
warnings = append(warnings, theme.PluginWarnings(pkg)...)
|
||||
warnings = append(warnings, hook.PackageWarnings(pkg)...)
|
||||
for _, warning := range warnings {
|
||||
fmt.Println("warning:", warning)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"reasonix/internal/base/testenv"
|
||||
"reasonix/internal/ext/pluginpkg"
|
||||
)
|
||||
|
||||
func TestPluginDoctorReportsHookDeclarationWarningsWhenDisabled(t *testing.T) {
|
||||
home := testenv.TempDir(t)
|
||||
t.Setenv("REASONIX_HOME", home)
|
||||
root := filepath.Join(home, "plugins", "hook-warning-kit")
|
||||
const manifest = `{"apiVersion":"reasonix.io/plugin/v2","name":"hook-warning-kit","contributes":{"hooks":{
|
||||
"SesionStart":[{"contextFile":"note.md"}],
|
||||
"PreToolUse":[{"contextFile":"note.md","match":"["},{"contextFile":"note.md","match":"*"}],
|
||||
"PostToolUse":[{"contextFile":"note.md","match":"["}],
|
||||
"PostToolUseFailure":[{"contextFile":"note.md","match":"["}],
|
||||
"PermissionRequest":[{"contextFile":"note.md","match":"["}],
|
||||
"SessionStart":[{"contextFile":"note.md","match":"["}]
|
||||
}}}`
|
||||
path := filepath.Join(root, pluginpkg.NativeManifest)
|
||||
writePluginTestFile(t, path, manifest)
|
||||
writePluginTestFile(t, filepath.Join(root, "note.md"), "Local author note")
|
||||
for _, enabled := range []bool{true, false} {
|
||||
if err := pluginpkg.Upsert(home, pluginpkg.InstalledPlugin{Name: "hook-warning-kit", Root: "plugins/hook-warning-kit", ManifestKind: "reasonix", Enabled: enabled}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
out := captureStdout(t, func() {
|
||||
if rc := pluginCommand([]string{"doctor", "hook-warning-kit"}); rc != 0 {
|
||||
t.Fatalf("doctor rc=%d", rc)
|
||||
}
|
||||
})
|
||||
if strings.Count(out, "warning:") != 5 {
|
||||
t.Fatalf("enabled=%v doctor=%s", enabled, out)
|
||||
}
|
||||
for _, event := range []string{"PreToolUse", "PostToolUse", "PostToolUseFailure", "PermissionRequest"} {
|
||||
if !strings.Contains(out, "hooks."+event+"[0]: invalid matcher regex") {
|
||||
t.Errorf("enabled=%v missing %s warning: %s", enabled, event, out)
|
||||
}
|
||||
}
|
||||
if !strings.Contains(out, "hooks.SesionStart: unknown event") || strings.Contains(out, "hooks.SessionStart") {
|
||||
t.Fatalf("unknown or non-tool event warnings incorrect: %s", out)
|
||||
}
|
||||
}
|
||||
corrected := strings.Replace(manifest, `"SesionStart"`, `"Stop"`, 1)
|
||||
corrected = strings.ReplaceAll(corrected, `"match":"["`, `"match":"read_file"`)
|
||||
writePluginTestFile(t, path, corrected)
|
||||
out := captureStdout(t, func() {
|
||||
if rc := pluginCommand([]string{"doctor", "hook-warning-kit"}); rc != 0 {
|
||||
t.Fatalf("corrected doctor rc=%d", rc)
|
||||
}
|
||||
})
|
||||
if strings.Contains(out, "warning:") {
|
||||
t.Fatalf("corrected declarations still have warnings: %s", out)
|
||||
}
|
||||
}
|
||||
@@ -121,6 +121,7 @@ func pluginViewFor(home, workspaceRoot string, p pluginpkg.InstalledPlugin) plug
|
||||
return view
|
||||
}
|
||||
view.Warnings = append(warnings, theme.PluginWarnings(pkg)...)
|
||||
view.Warnings = append(view.Warnings, hook.PackageWarnings(pkg)...)
|
||||
view.Compatibility = pkg.Compatibility.Status
|
||||
view.Skipped = pkg.Compatibility.Skipped
|
||||
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
package serve
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"reasonix/internal/base/testenv"
|
||||
)
|
||||
|
||||
func TestInstalledPluginReportsHookDeclarationWarnings(t *testing.T) {
|
||||
_, _, base := pluginHome(t)
|
||||
source := testenv.TempDir(t)
|
||||
const manifest = `{"apiVersion":"reasonix.io/plugin/v2","name":"hook-warning-kit","contributes":{"hooks":{
|
||||
"SesionStart":[{"contextFile":"note.md","match":"["}],
|
||||
"PreToolUse":[{"contextFile":"note.md","match":"["}],
|
||||
"SessionStart":[{"contextFile":"note.md","match":"["}]
|
||||
}}}`
|
||||
writePluginFile(t, filepath.Join(source, "reasonix-plugin.json"), manifest)
|
||||
writePluginFile(t, filepath.Join(source, "note.md"), "Local author note")
|
||||
install := func(replace bool) {
|
||||
t.Helper()
|
||||
args := map[string]any{"source": source, "replace": replace}
|
||||
plan := postJSON(t, base+"/plugins/plan", args)
|
||||
body := decodeInstallSource(t, plan)
|
||||
if plan.StatusCode != http.StatusOK || body["planId"] == "" {
|
||||
t.Fatalf("plan status=%d: %v", plan.StatusCode, body)
|
||||
}
|
||||
args["planId"] = body["planId"]
|
||||
resp := postJSON(t, base+"/plugins/install", args)
|
||||
if body := decodeInstallSource(t, resp); resp.StatusCode != http.StatusOK || body["status"] != "done" {
|
||||
t.Fatalf("install status=%d: %v", resp.StatusCode, body)
|
||||
}
|
||||
}
|
||||
read := func() []string {
|
||||
t.Helper()
|
||||
plugins := getPlugins(t, base)
|
||||
if len(plugins) != 1 || plugins[0].Name != "hook-warning-kit" || len(plugins[0].Hooks) != 3 || plugins[0].Error != "" {
|
||||
t.Fatalf("plugins=%+v", plugins)
|
||||
}
|
||||
return plugins[0].Warnings
|
||||
}
|
||||
install(false)
|
||||
warnings := read()
|
||||
if len(warnings) != 2 || !strings.Contains(warnings[0], "hooks.PreToolUse[0]: invalid matcher regex") || !strings.Contains(warnings[1], `hooks.SesionStart: unknown event`) {
|
||||
t.Fatalf("hook warnings=%v", warnings)
|
||||
}
|
||||
for _, enabled := range []bool{false, true} {
|
||||
resp := postJSON(t, base+"/plugins/enabled", map[string]any{"name": "hook-warning-kit", "enabled": enabled})
|
||||
resp.Body.Close()
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
t.Fatalf("enabled status=%d", resp.StatusCode)
|
||||
}
|
||||
if got := read(); !slices.Equal(got, warnings) {
|
||||
t.Fatalf("enabled=%v warnings=%v", enabled, got)
|
||||
}
|
||||
}
|
||||
corrected := strings.Replace(manifest, `"SesionStart"`, `"Stop"`, 1)
|
||||
corrected = strings.ReplaceAll(corrected, `"match":"["`, `"match":"read_file"`)
|
||||
writePluginFile(t, filepath.Join(source, "reasonix-plugin.json"), corrected)
|
||||
if got := read(); !slices.Equal(got, warnings) {
|
||||
t.Fatal("source edits changed the installed copy's warnings")
|
||||
}
|
||||
install(true)
|
||||
if got := read(); len(got) != 0 {
|
||||
t.Fatalf("corrected replacement warnings=%v", got)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user