Skip to content

Commit e2e27f5

Browse files
committed
feat: Create example OpenCode plugin mapping hooks to taskwing hooks
1 parent b218678 commit e2e27f5

1 file changed

Lines changed: 76 additions & 0 deletions

File tree

internal/bootstrap/initializer_test.go

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -714,3 +714,79 @@ func TestInitializer_GenerateTwBrief(t *testing.T) {
714714
t.Error("Skill name 'tw-brief' doesn't match required pattern")
715715
}
716716
}
717+
718+
// TestInitializer_GenerateOpenCodePlugin tests that OpenCode plugin is generated correctly
719+
// with proper hook mappings and ctx.$ Bun shell API usage.
720+
func TestInitializer_GenerateOpenCodePlugin(t *testing.T) {
721+
tmpDir := t.TempDir()
722+
init := NewInitializer(tmpDir)
723+
724+
// Run initialization with opencode
725+
err := init.Run(false, []string{"opencode"})
726+
if err != nil {
727+
t.Fatalf("Run failed: %v", err)
728+
}
729+
730+
// Verify plugin file exists
731+
pluginPath := filepath.Join(tmpDir, ".opencode", "plugins", "taskwing-hooks.js")
732+
content, err := os.ReadFile(pluginPath)
733+
if err != nil {
734+
t.Fatalf("Failed to read taskwing-hooks.js: %v", err)
735+
}
736+
737+
contentStr := string(content)
738+
739+
// Verify exports default as async function
740+
if !strings.Contains(contentStr, "export default async") {
741+
t.Error("Plugin missing 'export default async' export")
742+
}
743+
744+
// Verify ctx parameter is used
745+
if !strings.Contains(contentStr, "(ctx)") {
746+
t.Error("Plugin missing ctx parameter in default export")
747+
}
748+
749+
// Verify session.created hook exists
750+
if !strings.Contains(contentStr, `"session.created"`) {
751+
t.Error("Plugin missing session.created hook handler")
752+
}
753+
754+
// Verify session.idle hook exists
755+
if !strings.Contains(contentStr, `"session.idle"`) {
756+
t.Error("Plugin missing session.idle hook handler")
757+
}
758+
759+
// Verify ctx.$ calls to taskwing hook commands (Bun shell API)
760+
if !strings.Contains(contentStr, "ctx.$`taskwing hook session-init`") {
761+
t.Error("Plugin missing ctx.$`taskwing hook session-init` call")
762+
}
763+
if !strings.Contains(contentStr, "ctx.$`taskwing hook continue-check") {
764+
t.Error("Plugin missing ctx.$`taskwing hook continue-check` call")
765+
}
766+
767+
// Verify no inline secrets (basic check)
768+
secretPatterns := []string{
769+
"api_key",
770+
"apikey",
771+
"secret",
772+
"password",
773+
"token",
774+
"credential",
775+
}
776+
contentLower := strings.ToLower(contentStr)
777+
for _, pattern := range secretPatterns {
778+
// Skip if it's just a reference (like error.message)
779+
if strings.Contains(contentLower, pattern) && !strings.Contains(contentLower, "error.message") {
780+
// Allow "token" in comments explaining what the plugin does
781+
if pattern == "token" && strings.Contains(contentStr, "// ") {
782+
continue
783+
}
784+
t.Errorf("Plugin may contain sensitive data (found pattern: %s)", pattern)
785+
}
786+
}
787+
788+
// Verify managed marker exists (for update detection)
789+
if !strings.Contains(contentStr, "TASKWING_MANAGED_PLUGIN") {
790+
t.Error("Plugin missing TASKWING_MANAGED_PLUGIN marker")
791+
}
792+
}

0 commit comments

Comments
 (0)