fix(pluginpkg): finish rendering package data inside host sentences as literals

Skill shadow and unselected warnings, the migrated and remediation
sentences (plugin name), the persist-status warning, manifest read
warnings and the strict-decode error now show package data through
ShownLocator or ShownIdentity.
This commit is contained in:
yhh committed 2026-10-10 21:24:32 -07:00
1 parent 5047e081bc
commit 60f37ee7c6
7 files changed
+54 -10

No files matched your search

+1 -1
View File
@@ -83,7 +83,7 @@ func (t *Tool) applySkillRoot(req request, act *action) error {
}
}
if registeredPath == "" {
act.Warnings = append(act.Warnings, fmt.Sprintf("skill %q registered from %s is not selected in this workspace; current selection is %s", name, act.Source, sk.Path))
act.Warnings = append(act.Warnings, unselectedSkillWarning(name, act.Source, sk.Path))
continue
}
act.Discoverable = true
@@ -0,0 +1,11 @@
package installsource
import "fmt"
func shadowedSkillWarning(name, installed, selected string) string {
return fmt.Sprintf("skill %q installed at %s is shadowed in this workspace by %s", name, hostLiteral(installed), hostLiteral(selected))
}
func unselectedSkillWarning(name, source, selected string) string {
return fmt.Sprintf("skill %q registered from %s is not selected in this workspace; current selection is %s", name, hostLiteral(source), hostLiteral(selected))
}
@@ -11,3 +11,14 @@ func TestSkippedMarketplaceWarningShowsAnUnterminatedEscapeInsteadOfEatingIt(t *
t.Fatalf("got %q", got)
}
}
func TestSkillSentencesShowPathsAsLiterals(t *testing.T) {
for _, got := range []string{
shadowedSkillWarning("s", "/a\x1b]0;", "/b\x1b]0;"),
unselectedSkillWarning("s", "/a\x1b]0;", "/b\x1b]0;"),
} {
if strings.ContainsRune(got, 0x1b) || strings.Count(got, `\u{1b}]0;`) != 2 {
t.Fatalf("got %q", got)
}
}
}
+1 -1
View File
@@ -177,7 +177,7 @@ func (t *Tool) verifySkill(scope, name string, act *action) error {
}
installedPath := config.CanonicalSkillPath(act.CanonicalPath)
if config.CanonicalSkillPath(sk.Path) != installedPath {
act.Warnings = append(act.Warnings, fmt.Sprintf("skill %q installed at %s is shadowed in this workspace by %s", name, act.CanonicalPath, sk.Path))
act.Warnings = append(act.Warnings, shadowedSkillWarning(name, act.CanonicalPath, sk.Path))
} else {
act.Discoverable = true
for _, listed := range store.List() {
+11 -3
View File
@@ -12,6 +12,14 @@ import (
"reasonix/internal/base/textutil"
)
func migratedWarning(name string) string {
return fmt.Sprintf("%s: automatically migrated managed manifest to %s (backup: %s.bak)", textutil.ShownIdentity(name), ManifestAPIVersionV2, NativeManifest)
}
func migrateRemediation(name string) string {
return fmt.Sprintf("reasonix plugin migrate %s --to-v2", textutil.ShownIdentity(name))
}
// incompatibleWarning writes the whole sentence first and shows the error as a
// literal: its text can come from the package's own manifest.
func incompatibleWarning(name string, err error, remediation string) string {
@@ -42,7 +50,7 @@ func LoadInstalled(reasonixHome string) ([]InstalledPackage, []string) {
var migrated bool
pkg, pkgWarnings, migrated, err = migrateManagedManifest(root)
if migrated {
warnings = append(warnings, fmt.Sprintf("%s: automatically migrated managed manifest to %s (backup: %s.bak)", installed.Name, ManifestAPIVersionV2, NativeManifest))
warnings = append(warnings, migratedWarning(installed.Name))
}
}
if err != nil {
@@ -52,7 +60,7 @@ func LoadInstalled(reasonixHome string) ([]InstalledPackage, []string) {
}
remediation := "repair or reinstall the plugin"
if errors.Is(err, ErrMissingAPIVersion) {
remediation = fmt.Sprintf("reasonix plugin migrate %s --to-v2", installed.Name)
remediation = migrateRemediation(installed.Name)
}
warnings = append(warnings, incompatibleWarning(installed.Name, err, remediation))
continue
@@ -63,7 +71,7 @@ func LoadInstalled(reasonixHome string) ([]InstalledPackage, []string) {
out = append(out, InstalledPackage{Installed: installed, Package: pkg, Warnings: pkgWarnings})
}
if err := persistStatusVerdicts(reasonixHome, verdicts); err != nil {
warnings = append(warnings, "persist plugin compatibility status: "+err.Error())
warnings = append(warnings, "persist plugin compatibility status: "+textutil.ShownLocator(err.Error()))
}
sort.SliceStable(out, func(i, j int) bool { return out[i].Installed.Name < out[j].Installed.Name })
return out, warnings
+6 -5
View File
@@ -1,6 +1,7 @@
package pluginpkg
import (
"reasonix/internal/base/textutil"
"reasonix/internal/ext/extensioncontract"
"regexp"
@@ -96,7 +97,7 @@ func strictDecode(data []byte, v any, path string) error {
dec := json.NewDecoder(bytes.NewReader(data))
dec.DisallowUnknownFields()
if err := dec.Decode(v); err != nil {
return fmt.Errorf("%s: %w", path, err)
return fmt.Errorf("%s: %s", path, textutil.ShownLocator(err.Error()))
}
return nil
}
@@ -493,20 +494,20 @@ func validateV1Paths(root string, m *Manifest) ([]string, error) {
if errors.Is(err, os.ErrNotExist) {
warnings = append(warnings, fmt.Sprintf("%s path %q does not exist", kind, rel))
} else {
warnings = append(warnings, fmt.Sprintf("%s path %q is not readable: %v", kind, rel, err))
warnings = append(warnings, fmt.Sprintf("%s path %q is not readable: %s", kind, rel, textutil.ShownLocator(err.Error())))
}
return nil
}
resolved, err := filepath.EvalSymlinks(abs)
if err != nil {
warnings = append(warnings, fmt.Sprintf("%s path %q cannot be resolved: %v", kind, rel, err))
warnings = append(warnings, fmt.Sprintf("%s path %q cannot be resolved: %s", kind, rel, textutil.ShownLocator(err.Error())))
return nil
}
if !pathWithinRoot(resolvedRoot, resolved) {
return fmt.Errorf("%s path %q escapes the plugin root through a symlink", kind, rel)
}
if info, err := os.Stat(abs); err != nil {
warnings = append(warnings, fmt.Sprintf("%s path %q is not readable: %v", kind, rel, err))
warnings = append(warnings, fmt.Sprintf("%s path %q is not readable: %s", kind, rel, textutil.ShownLocator(err.Error())))
} else if !info.IsDir() {
warnings = append(warnings, fmt.Sprintf("%s path %q is not a directory; declare the containing directory instead", kind, rel))
}
@@ -539,7 +540,7 @@ func validateV1Paths(root string, m *Manifest) ([]string, error) {
if errors.Is(err, os.ErrNotExist) {
warnings = append(warnings, fmt.Sprintf("themes path %q does not exist", pattern))
} else {
warnings = append(warnings, fmt.Sprintf("themes path %q is not readable: %v", pattern, err))
warnings = append(warnings, fmt.Sprintf("themes path %q is not readable: %s", pattern, textutil.ShownLocator(err.Error())))
}
continue
}
@@ -40,3 +40,16 @@ func TestIncompatibleWarningShowsTheErrorAsALiteralAndKeepsItsRemediation(t *tes
t.Fatalf("the sentence lost its tail: %q", got)
}
}
func TestInstalledNameAndDecodeErrorsAreShownAsLiterals(t *testing.T) {
requireShown(t, migratedWarning("demo"+unterminated))
requireShown(t, migrateRemediation("demo"+unterminated))
var v struct{}
err := strictDecode([]byte(`{"bad`+unterminated+`": 1}`), &v, "plugin.json")
if err == nil {
t.Fatal("an unknown field must fail")
}
if strings.ContainsRune(err.Error(), 0x1b) {
t.Fatalf("a raw escape reached the decode error: %q", err.Error())
}
}