Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 78 additions & 0 deletions cli/cmd/agentplugins/devin_project_admission_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package main

import (
"context"
"path/filepath"
"reflect"
"strings"
"testing"

"github.com/777genius/plugin-kit-ai/cli/internal/agentpluginscli"
"github.com/777genius/plugin-kit-ai/install/integrationctl/agentplugins/domain"
)

func TestProjectDevinNoSaveUsesSelectedHostAndAbsentLegacyProfile(t *testing.T) {
transport := newProjectJourneyTransport(t, "git")
devinProjectCommitSource(t, transport, projectJourneySkill, "https://managed.test/v1")
f := newDevinProjectJourney(t, transport)
user, b := securityFixtureTree(t, f.devinRoot("")), securityFixtureTree(t, f.devinRoot(f.b))
f.run(t, f.a, "add", transport.repo, "--no-save", "--target", "devin")
state := f.state(t, f.a)
if len(state.Installations) != 1 || len(state.Installations[0].Clients) != 1 || len(state.EnvironmentReceipts) != 0 {
t.Fatalf("no-save changed ownership route: %+v", state)
}
installation := state.Installations[0]
var binding domain.ClientBinding
for _, owned := range installation.Clients {
binding = owned
}
if binding.ClientID != "windsurf" || binding.NativeProfileRoot != f.devinRoot(f.a) || binding.Activation != domain.ActivationPrepared || binding.Verification != domain.VerificationInstalled {
t.Fatalf("no-save project host evidence missing: %+v", binding)
}
if !strings.Contains(projectJourneyRead(t, filepath.Join(f.devinRoot(f.a), "mcp_config.json")), "managed.test/v1") {
t.Fatal("no-save current Devin dedicated MCP missing")
}
f.run(t, f.a, "remove", installation.InstallationID, "--target", "windsurf")
projectJourneyAbsent(t, binding.TargetLocator, filepath.Join(f.devinRoot(f.a), "skills", "docs"))
if !reflect.DeepEqual(user, securityFixtureTree(t, f.devinRoot(""))) || !reflect.DeepEqual(b, securityFixtureTree(t, f.devinRoot(f.b))) {
t.Fatal("no-save changed another native scope")
}
}

type devinUnqualifiedDetector struct{ client domain.DetectedClient }

func (detector devinUnqualifiedDetector) Detect(_ context.Context) ([]domain.DetectedClient, error) {
return []domain.DetectedClient{detector.client}, nil
}

func TestProjectDevinCLIRefusesMissingOlderAndWrongHarnessBeforeNativeEffects(t *testing.T) {
transport := newProjectJourneyTransport(t, "zip")
devinProjectCommitSource(t, transport, projectJourneySkill, "https://managed.test/v1")
for _, version := range []string{"", "3000.3.0", "3000.11.205-alpha", "wrong-harness"} {
t.Run("version="+version, func(t *testing.T) {
f := newDevinProjectJourney(t, transport)
client := domain.DetectedClient{ClientID: domain.ClientWindsurf, Status: domain.DetectionDetected, ExecutablePath: filepath.Join(f.home, "devin"), Version: version}
if version == "wrong-harness" {
client.Version = "3000.11.205"
client.ExecutablePath = filepath.Join(f.home, "windsurf")
}
compose := f.app.ForScope
f.app.ForScope = func(scope domain.InstallScope, root string) (agentpluginscli.App, error) {
selected, err := compose(scope, root)
if err != nil {
return selected, err
}
selected.Detector = devinUnqualifiedDetector{client: client}
return selected, nil
}
before, user, b := securityFixtureTree(t, f.devinRoot(f.a)), securityFixtureTree(t, f.devinRoot("")), securityFixtureTree(t, f.devinRoot(f.b))
_, err := f.execute(f.a, "add", transport.repo, "--no-save", "--target", "windsurf")
if err == nil || !strings.Contains(err.Error(), "explicitly selected primary") {
t.Fatalf("unqualified CLI host accepted: %v", err)
}
if !reflect.DeepEqual(before, securityFixtureTree(t, f.devinRoot(f.a))) || !reflect.DeepEqual(user, securityFixtureTree(t, f.devinRoot(""))) || !reflect.DeepEqual(b, securityFixtureTree(t, f.devinRoot(f.b))) {
t.Fatal("unqualified CLI host changed native scope")
}
})
}
}
123 changes: 123 additions & 0 deletions cli/cmd/agentplugins/devin_project_fixture_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
package main

import (
"context"
"path/filepath"
"strings"
"testing"

"github.com/777genius/plugin-kit-ai/cli/internal/agentpluginscli"
"github.com/777genius/plugin-kit-ai/install/integrationctl/adapters/pathpolicy"
"github.com/777genius/plugin-kit-ai/install/integrationctl/agentplugins/adapters/sourceacquisition"
"github.com/777genius/plugin-kit-ai/install/integrationctl/agentplugins/adapters/specregistry"
"github.com/777genius/plugin-kit-ai/install/integrationctl/agentplugins/domain"
"github.com/777genius/plugin-kit-ai/install/integrationctl/agentplugins/ports"
)

type devinProjectDetector struct{ root string }

func (detector devinProjectDetector) Detect(context.Context) ([]domain.DetectedClient, error) {
return []domain.DetectedClient{{ClientID: domain.ClientWindsurf, ConfigRoot: "", ExecutablePath: filepath.Join(detector.root, "devin"), Version: "3000.11.205", Status: domain.DetectionDetected}}, nil
}

// Reuse the core journey's production CLI/state helpers and real TLS Git/ZIP
// transports. Devin supplies its own detected profile and native layout.
type devinProjectJourney struct{ projectJourney }

func newDevinProjectJourney(t *testing.T, transport *projectJourneyTransport) devinProjectJourney {
t.Helper()
f := devinProjectJourney{projectJourney{home: devinJourneyTemp(t), a: devinJourneyTemp(t), b: devinJourneyTemp(t), transport: transport}}
f.cache = filepath.Join(f.home, "uap")
for _, root := range []string{"", f.a, f.b} {
native := f.devinRoot(root)
projectJourneyWrite(t, filepath.Join(native, "mcp_config.json"), `{"mcpServers":{"foreign":{"url":"https://foreign.test/mcp","disabled":true}},"foreignPreference":"keep"}`)
projectJourneyWrite(t, filepath.Join(native, "skills", "foreign", "SKILL.md"), "foreign skill\n")
projectJourneyWrite(t, filepath.Join(native, "hooks.json"), "foreign hooks\n")
}
registry, err := specregistry.New()
if err != nil {
t.Fatal(err)
}
app, err := newAgentpluginsCLIApp(f.home, f.cache, nil, nil, nil, registry)
if err != nil {
t.Fatal(err)
}
f.app = configureDevinProjectJourney(app, f.home, transport)
return f
}

// Preserve the production scoped composer, installer, ownership kernel, store,
// registry and solver. Only host discovery and security assessment are inert.
func configureDevinProjectJourney(app agentpluginscli.App, home string, transport *projectJourneyTransport) agentpluginscli.App {
app.Detector = devinProjectDetector{root: filepath.Join(home, ".devin")}
app.SecurityIndex, app.DirectoryClient, app.DiscoveryClient = nil, nil, nil
app.SecurityEvaluator = projectJourneySecurity{}
app.SecurityEvaluatorForDryRun = func(string) ports.PackageSecurityEvaluator { return projectJourneySecurity{} }
app.SourceAcquirer = sourceacquisition.Acquirer{TempRoot: home, HTTPClient: transport.server.Client()}
app.SourceAcquirerForDryRun = func(root string) agentpluginscli.SourceAcquirer {
return sourceacquisition.Acquirer{TempRoot: root, HTTPClient: transport.server.Client()}
}
app.DirectoryClientForDryRun, app.CommitDirectoryPreview = nil, nil
compose := app.ForScope
app.ForScope = func(scope domain.InstallScope, root string) (agentpluginscli.App, error) {
selected, err := compose(scope, root)
if err != nil {
return selected, err
}
return configureDevinProjectJourney(selected, home, transport), nil
}
return app
}

func (f devinProjectJourney) devinRoot(project string) string {
if project == "" {
return filepath.Join(f.home, ".devin")
}
return filepath.Join(project, ".devin")
}

func (f devinProjectJourney) assertDevinOwned(t *testing.T, project, skill, endpoint string) (domain.Installation, domain.ClientBinding) {
t.Helper()
installation, binding := f.owned(t, project)
if binding.Scope != string(domain.ScopeProject) || binding.NativeProfileRoot != f.devinRoot(project) || binding.Activation != domain.ActivationPrepared || binding.Verification != domain.VerificationInstalled {
t.Fatalf("Devin project lifecycle lost its exact scope or conservative observation: %+v", binding)
}
if err := pathpolicy.RequireContainedChild(filepath.Join(project, ".uap"), binding.TargetLocator); err != nil {
t.Fatal(err)
}
for _, root := range []string{binding.TargetLocator, f.devinRoot(project)} {
if projectJourneyRead(t, filepath.Join(root, "skills", "docs", "SKILL.md")) != skill {
t.Fatal("Devin managed/native skill bytes differ from the selected pin")
}
}
settings := projectJourneyRead(t, filepath.Join(f.devinRoot(project), "mcp_config.json"))
for _, text := range []string{endpoint, "foreign.test", "foreignPreference", `"disabled":true`} {
if !strings.Contains(strings.ReplaceAll(settings, " ", ""), text) {
t.Fatalf("Devin settings lost selected or foreign member %q: %s", text, settings)
}
}
return installation, binding
}

// Select inert remote configuration so the composed proof never executes an
// MCP command, copies a test binary as a launcher, or starts a native client.
func devinProjectCommitSource(t *testing.T, transport *projectJourneyTransport, skill, endpoint string) {
t.Helper()
projectJourneyWrite(t, filepath.Join(transport.repo, "skills", "docs", "SKILL.md"), skill)
projectJourneyWrite(t, filepath.Join(transport.repo, "mcp.json"), `{"$schema":"https://agent-plugins.org/schemas/1.0.0/mcp.schema.json","mcpServers":{"docs":{"type":"streamable-http","url":"`+endpoint+`"}}}`)
projectJourneyGit(t, transport.repo, "add", ".")
projectJourneyGit(t, transport.repo, "commit", "-m", "select inert Devin project components")
projectJourneyGit(t, transport.repo, "push", "--quiet", transport.remote, "main")
transport.mu.Lock()
defer transport.mu.Unlock()
transport.archive = projectJourneyArchive(t, transport.repo)
}

func devinJourneyTemp(t *testing.T) string {
t.Helper()
root, err := filepath.EvalSymlinks(t.TempDir())
if err != nil {
t.Fatal(err)
}
return root
}
102 changes: 102 additions & 0 deletions cli/cmd/agentplugins/devin_project_lifecycle_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
package main

import (
"os"
"path/filepath"
"reflect"
"strings"
"testing"
)

// This is the production CLI/dependency/installer/native-filesystem composition
// with real loopback TLS Git and ZIP acquisition. Native Devin discovery,
// enablement, MCP initialization, model use and runtime are not certified.
func TestProjectDevinComposedSourceLifecycle(t *testing.T) {
for _, kind := range []string{"git", "zip"} {
t.Run(kind, func(t *testing.T) {
transport := newProjectJourneyTransport(t, kind)
devinProjectCommitSource(t, transport, projectJourneySkill, "https://managed.test/v1")
f := newDevinProjectJourney(t, transport)
userBefore := securityFixtureTree(t, f.devinRoot(""))
for _, root := range []string{f.a, f.b} {
f.run(t, root, "add", transport.endpoint, "--alias", "docs", "--target", "windsurf")
f.assertDevinOwned(t, root, projectJourneySkill, "https://managed.test/v1")
}
bNative := securityFixtureTree(t, f.devinRoot(f.b))
bState := projectJourneyRead(t, filepath.Join(f.namespace(f.b), "state-v2.json"))
devinProjectReconcileAndRepair(t, f)
devinProjectUpdateAndTransfer(t, f)
devinProjectRemove(t, f)
if !reflect.DeepEqual(userBefore, securityFixtureTree(t, f.devinRoot(""))) || !reflect.DeepEqual(bNative, securityFixtureTree(t, f.devinRoot(f.b))) || bState != projectJourneyRead(t, filepath.Join(f.namespace(f.b), "state-v2.json")) {
t.Fatal("Devin project-A lifecycle changed user or project-B native configuration/state")
}
f.assertDevinOwned(t, f.b, projectJourneySkill, "https://managed.test/v1")
})
}
}

func devinProjectReconcileAndRepair(t *testing.T, f devinProjectJourney) {
t.Helper()
statePath := filepath.Join(f.namespace(f.a), "state-v2.json")
before := projectJourneyRead(t, statePath)
f.run(t, f.a, "deps", "sync", "--locked", "--yes")
if before != projectJourneyRead(t, statePath) {
t.Fatal("Devin locked no-op changed exact ownership receipts")
}
installation, _ := f.owned(t, f.a)
native := securityFixtureTree(t, f.devinRoot(f.a))
for _, args := range [][]string{{"list"}, {"info", installation.InstallationID}, {"doctor", installation.InstallationID}, {"outdated", installation.InstallationID}, {"deps", "status"}, {"deps", "tree"}} {
f.run(t, f.a, args...)
}
if before != projectJourneyRead(t, statePath) || !reflect.DeepEqual(native, securityFixtureTree(t, f.devinRoot(f.a))) {
t.Fatal("Devin project read commands changed native files or ownership receipts")
}
if err := os.RemoveAll(filepath.Join(f.devinRoot(f.a), "skills", "docs")); err != nil {
t.Fatal(err)
}
f.run(t, f.a, "repair", installation.InstallationID, "--target", "windsurf")
f.assertDevinOwned(t, f.a, projectJourneySkill, "https://managed.test/v1")
f.inspectNoRecovery(t, f.a)
}

func devinProjectUpdateAndTransfer(t *testing.T, f devinProjectJourney) {
t.Helper()
previous, binding := f.owned(t, f.a)
devinProjectCommitSource(t, f.transport, projectJourneyAdvancedSkill, "https://managed.test/v2")
f.run(t, f.a, "update", "docs", "--target", "windsurf")
updated, selected := f.assertDevinOwned(t, f.a, projectJourneyAdvancedSkill, "https://managed.test/v2")
if updated.Source.SourceBindingID != previous.Source.SourceBindingID || updated.Source.TreeDigest == previous.Source.TreeDigest || selected.ClientBindingID != binding.ClientBindingID || selected.TargetLocator != binding.TargetLocator {
t.Fatal("Devin update did not retain source/binding authority while advancing the pin")
}
pair := f.assertPortablePair(t, f.a)
exchange := t.TempDir()
f.run(t, f.a, "deps", "export", exchange, "--yes")
if pair != f.assertPortablePair(t, exchange) {
t.Fatal("Devin export changed exact portable pins")
}
cold := newDevinProjectJourney(t, f.transport)
projectJourneyAbsent(t, cold.cache, filepath.Join(cold.a, ".uap"))
cold.run(t, cold.a, "deps", "import", exchange, "--install", "--yes")
restored, _ := cold.assertDevinOwned(t, cold.a, projectJourneyAdvancedSkill, "https://managed.test/v2")
if restored.Source.SourceBindingID != updated.Source.SourceBindingID || restored.Source.TreeDigest != updated.Source.TreeDigest || pair != cold.assertPortablePair(t, cold.a) {
t.Fatal("Devin cold import changed source identity or portable pins")
}
cold.inspectNoRecovery(t, cold.a)
}

func devinProjectRemove(t *testing.T, f devinProjectJourney) {
t.Helper()
_, binding := f.owned(t, f.a)
f.run(t, f.a, "remove", "docs", "--target", "windsurf")
projectJourneyAbsent(t, binding.TargetLocator, filepath.Join(f.devinRoot(f.a), "skills", "docs"))
settings := projectJourneyRead(t, filepath.Join(f.devinRoot(f.a), "mcp_config.json"))
if strings.Contains(settings, "managed.test") || !strings.Contains(settings, "foreign.test") || !strings.Contains(settings, "foreignPreference") {
t.Fatalf("Devin removal changed foreign MCP settings or retained its entry: %s", settings)
}
if projectJourneyRead(t, filepath.Join(f.devinRoot(f.a), "skills", "foreign", "SKILL.md")) != "foreign skill\n" || projectJourneyRead(t, filepath.Join(f.devinRoot(f.a), "hooks.json")) != "foreign hooks\n" {
t.Fatal("Devin removal changed foreign skill or hooks")
}
if len(f.state(t, f.a).EnvironmentReceipts) != 0 {
t.Fatal("Devin removal retained an environment ownership receipt")
}
}
3 changes: 3 additions & 0 deletions cli/internal/agentpluginscli/deps_sync.go
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,9 @@ func (adapter *dependencyInstaller) populateDependencyTargets(view installer.Ins
continue
}
for _, binding := range installation.Bindings {
if binding.ClientID == request.ClientID {
continue
}
sibling, ok := adapter.detected[domain.ClientID(binding.ClientID)]
if !ok {
return fmt.Errorf("installed sibling %s is not detected", binding.ClientID)
Expand Down
68 changes: 68 additions & 0 deletions cli/internal/agentpluginscli/project_host_version_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package agentpluginscli

import (
"errors"
"path/filepath"
"strings"
"testing"

"github.com/777genius/plugin-kit-ai/install/integrationctl/agentplugins/clients"
"github.com/777genius/plugin-kit-ai/install/integrationctl/agentplugins/clients/codex"
"github.com/777genius/plugin-kit-ai/install/integrationctl/agentplugins/domain"
"github.com/777genius/plugin-kit-ai/install/integrationctl/agentplugins/installer"
)

type versionBoundaryCodex struct {
*codex.Adapter
observed *string
}

func (adapter *versionBoundaryCodex) CheckPlanPrecondition(in clients.PlanInput, _ *domain.DeliveryPlan) error {
*adapter.observed = in.Client.Version
return errors.New("inert host-version boundary reached")
}

// Windsurf's direct no-save path already retains the domain client. This proves
// the existing qualified facade builder forwards its own selected metadata too.
func TestProjectHostVersionCrossesExistingNoSaveFacadeBoundary(t *testing.T) {
client := fixtureClient(t, domain.ClientCodex)
client.ExecutablePath = filepath.Join(t.TempDir(), "codex")
client.Version = "selected-host-version"
f := newCLIFixture(t, []domain.DetectedClient{client})
observed := ""
registry, err := clients.NewRegistry(&versionBoundaryCodex{Adapter: codex.New(), observed: &observed})
if err != nil {
t.Fatal(err)
}
engine, err := installer.New(installer.Config{StateRoot: filepath.Join(f.root, "facade"), Registry: registry})
if err != nil {
t.Fatal(err)
}
f.app.Installer = engine
f.app.Lifecycle.Planner = nil
_, _, err = f.execute(false, "add", writeCLIPlugin(t), "--no-save", "--target", "codex", "--format", "json")
if err == nil || !strings.Contains(err.Error(), "inert host-version boundary reached") || observed != client.Version {
t.Fatalf("selected host evidence=%q,%v", observed, err)
}
}

func TestProjectDependencyKnownFactsRetainOnlyUnselectedSiblings(t *testing.T) {
root := t.TempDir()
sibling := domain.DetectedClient{ClientID: domain.ClientGemini, ConfigRoot: filepath.Join(root, "gemini"), ExecutablePath: filepath.Join(root, "gemini-cli")}
adapter := dependencyInstaller{app: App{Scope: domain.ScopeProject, ProjectRoot: root}, detected: map[domain.ClientID]domain.DetectedClient{domain.ClientGemini: sibling}}
view := installer.Inspection{Installations: []installer.InspectedInstallation{{InstallationID: "selected", Bindings: []installer.InspectedBinding{{ClientID: "windsurf", BindingID: "selected-binding"}, {ClientID: "gemini", BindingID: "sibling-binding"}}}}}
request := installer.Request{ClientID: "windsurf", ClientVersion: "3000.11.205"}
if err := adapter.populateDependencyTargets(view, "selected", &request); err != nil {
t.Fatal(err)
}
if len(request.KnownTargets) != 1 || request.KnownTargets[0] != (installer.TargetFacts{ClientID: "gemini", BindingID: "sibling-binding", ConfigRoot: sibling.ConfigRoot, Executable: sibling.ExecutablePath}) {
t.Fatalf("selected/sibling facts mixed: %+v", request.KnownTargets)
}
if request.ClientVersion != "3000.11.205" || request.ClientConfigRoot != "" {
t.Fatal("primary host evidence changed")
}
delete(adapter.detected, domain.ClientGemini)
if err := adapter.populateDependencyTargets(view, "selected", &installer.Request{ClientID: "windsurf"}); err == nil {
t.Fatal("unselected sibling missing facts accepted")
}
}
Loading
Loading