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
118 changes: 118 additions & 0 deletions cli/cmd/agentplugins/project_kimi_e2e_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
package main

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

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

// Public saved CLI, real TLS Git and the production source/store/planner,
// installer/native transactions. Detection/security are inert host boundaries.
// The trusted test executable is copied as launcher bytes but never executed;
// this test certifies no Kimi, MCP, model, trust or native discovery behavior.
func TestProjectKimiComposedSavedLifecycle(t *testing.T) {
transport := newProjectJourneyTransport(t, "git")
journey := newProjectJourney(t, transport)
user := filepath.Join(journey.home, ".kimi")
journey.app = configureProjectKimiJourney(journey.app, user, filepath.Join(journey.home, "kimi"))
foreign := `{"mcpServers":{"foreign":{"transport":"http","url":"https://foreign.test/mcp","enabled":false}},"policy":"keep"}`
for _, project := range []string{user, journey.a, journey.b} {
native := filepath.Join(project, ".kimi-code")
if project == user {
native = user
} else {
if err := os.Mkdir(filepath.Join(project, ".git"), 0700); err != nil {
t.Fatal(err)
}
}
projectJourneyWrite(t, filepath.Join(native, "mcp.json"), foreign)
projectJourneyWrite(t, filepath.Join(native, "skills", "foreign", "SKILL.md"), "foreign Kimi skill\n")
}
projectJourneyWrite(t, filepath.Join(journey.a, ".mcp.json"), foreign)
userBefore := securityFixtureTree(t, user)
for _, project := range []string{journey.a, journey.b} {
journey.run(t, project, "add", transport.endpoint, "--alias", "docs", "--target", "kimi")
projectKimiOwned(t, journey, project, projectJourneySkill)
}
projectJourneyWrite(t, filepath.Join(journey.b, "B-retained-marker"), "other project\n")
bBefore := securityFixtureTree(t, journey.b)
statePath := filepath.Join(journey.namespace(journey.a), "state-v2.json")
stateBefore := projectJourneyRead(t, statePath)
journey.run(t, journey.a, "deps", "sync", "--locked", "--yes")
if stateBefore != projectJourneyRead(t, statePath) {
t.Fatal("unchanged Kimi reconciliation rewrote ownership")
}
installation, binding := projectKimiOwned(t, journey, journey.a, projectJourneySkill)
projectJourneyDataMarker(t, journey, journey.a, installation)
projectJourneyWrite(t, filepath.Join(binding.TargetLocator, "skills", "docs", "SKILL.md"), "TEST immutable drift\n")
if err := os.RemoveAll(filepath.Join(journey.a, ".kimi-code", "skills", "docs")); err != nil {
t.Fatal(err)
}
projectJourneyWrite(t, filepath.Join(transport.repo, "skills", "docs", "SKILL.md"), projectJourneyAdvancedSkill)
projectJourneyGit(t, transport.repo, "add", ".")
projectJourneyGit(t, transport.repo, "commit", "-m", "advance disposable Kimi source")
projectJourneyGit(t, transport.repo, "push", "--quiet", transport.remote, "main")
journey.run(t, journey.a, "repair", installation.InstallationID, "--target", "kimi")
projectKimiOwned(t, journey, journey.a, projectJourneySkill)
projectKimiRecover(t, journey)
projectKimiUpdateRemove(t, journey, installation, binding)
if !reflect.DeepEqual(userBefore, securityFixtureTree(t, user)) || !reflect.DeepEqual(bBefore, securityFixtureTree(t, journey.b)) {
t.Fatal("Kimi A lifecycle mutated the user profile or B")
}
}

func projectKimiUpdateRemove(t *testing.T, journey projectJourney, installation domain.Installation, binding domain.ClientBinding) {
t.Helper()
journey.run(t, journey.a, "update", "docs", "--target", "kimi")
updated, current := projectKimiOwned(t, journey, journey.a, projectJourneyAdvancedSkill)
if updated.Source.TreeDigest == installation.Source.TreeDigest || current.ClientBindingID != binding.ClientBindingID || current.TargetLocator != binding.TargetLocator || updated.Source.SourceBindingID != installation.Source.SourceBindingID {
t.Fatal("Kimi update lost exact source/binding authority or did not advance")
}
status := journey.run(t, journey.a, "deps", "status")
for _, expected := range []string{`"kimi"`, `"project"`, `"activation_prepared"`} {
if !strings.Contains(status, expected) {
t.Fatalf("Kimi status omitted %s: %s", expected, status)
}
}
journey.run(t, journey.a, "remove", "docs", "--target", "kimi")
projectJourneyAbsent(t, binding.TargetLocator, filepath.Join(journey.a, ".kimi-code", "skills", "docs"))
config := projectJourneyRead(t, filepath.Join(journey.a, ".kimi-code", "mcp.json"))
if strings.Contains(config, `"docs"`) || !strings.Contains(config, "foreign.test") || !strings.Contains(config, `"enabled":false`) {
t.Fatalf("Kimi exact removal changed foreign settings: %s", config)
}
if projectJourneyRead(t, filepath.Join(journey.a, ".kimi-code", "skills", "foreign", "SKILL.md")) != "foreign Kimi skill\n" {
t.Fatal("Kimi removal changed an unrelated project skill")
}
for key, receipt := range installation.DataReceipts {
if updated.DataReceipts[key].Locator != receipt.Locator || projectJourneyRead(t, filepath.Join(receipt.Locator, "keep.txt")) != "retained environment data\n" {
t.Fatal("Kimi update/remove changed retained data authority")
}
}
if len(journey.state(t, journey.a).EnvironmentReceipts) != 0 {
t.Fatal("Kimi remove retained saved environment ownership")
}
after := securityFixtureTree(t, journey.a)
_, _ = journey.execute(journey.a, "remove", "docs", "--target", "kimi")
if !reflect.DeepEqual(after, securityFixtureTree(t, journey.a)) {
t.Fatal("repeat absent Kimi removal changed bytes")
}
}

func projectKimiRecover(t *testing.T, journey projectJourney) {
t.Helper()
github := securityFixtureTree(t, filepath.Join(journey.a, ".kimi-code"))
config := projectJourneyRead(t, filepath.Join(journey.a, ".kimi-code", "mcp.json"))
staging, active := projectJourneyInterruptDirectoryJournal(t, journey)
for range 2 {
journey.run(t, journey.a, "deps", "recover", "--yes")
}
journey.inspectNoRecovery(t, journey.a)
projectJourneyAbsent(t, active)
if !reflect.DeepEqual(github, securityFixtureTree(t, filepath.Join(journey.a, ".kimi-code"))) || config != projectJourneyRead(t, filepath.Join(journey.a, ".kimi-code", "mcp.json")) || projectJourneyRead(t, filepath.Join(staging, "unpublished.txt")) != "caller-owned unpublished bytes\n" {
t.Fatal("Kimi bounded pending-journal recovery changed native/caller bytes")
}
}
44 changes: 44 additions & 0 deletions cli/cmd/agentplugins/project_kimi_fixture_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package main

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

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

type projectKimiDetector struct{ user, executable string }

func (d projectKimiDetector) Detect(context.Context) ([]domain.DetectedClient, error) {
return []domain.DetectedClient{{ClientID: domain.ClientKimi, ConfigRoot: d.user,
Version: "2.3.1", ExecutablePath: d.executable, Status: domain.DetectionDetected}}, nil
}

func configureProjectKimiJourney(app agentpluginscli.App, user, executable string) agentpluginscli.App {
app.Detector = projectKimiDetector{user: user, executable: executable}
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 configureProjectKimiJourney(selected, user, executable), nil
}
return app
}

func projectKimiOwned(t *testing.T, journey projectJourney, project, expected string) (domain.Installation, domain.ClientBinding) {
t.Helper()
installation, binding := journey.owned(t, project)
if binding.ClientID != "kimi" || binding.Scope != "project" || binding.NativeProfileRoot != filepath.Join(project, ".kimi-code") || binding.Activation != domain.ActivationPrepared || binding.Verification != domain.VerificationInstalled {
t.Fatalf("Kimi binding lost project authority or conservative native status: %+v", binding)
}
for _, path := range []string{filepath.Join(project, ".kimi-code", "skills", "docs", "SKILL.md"), filepath.Join(binding.TargetLocator, "skills", "docs", "SKILL.md")} {
if projectJourneyRead(t, path) != expected {
t.Fatalf("Kimi managed/native skill differs at %s", path)
}
}
return installation, binding
}
44 changes: 44 additions & 0 deletions cli/cmd/agentplugins/project_kimi_imperative_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package main

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

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

func TestProjectKimiImperativeLocalLifecyclePreservesUserAndB(t *testing.T) {
transport := newProjectJourneyTransport(t, "git")
journey := newProjectJourney(t, transport)
user := filepath.Join(journey.home, ".kimi-code")
journey.app = configureProjectKimiJourney(journey.app, user, filepath.Join(journey.home, "kimi"))
for _, root := range []string{journey.a, journey.b} {
if err := os.Mkdir(filepath.Join(root, ".git"), 0700); err != nil {
t.Fatal(err)
}
}
projectJourneyWrite(t, filepath.Join(user, "mcp.json"), `{"mcpServers":{"foreign":{"url":"https://foreign.test","enabled":false}}}`)
projectJourneyWrite(t, filepath.Join(journey.b, ".kimi-code", "skills", "foreign", "SKILL.md"), "other project\n")
beforeUser, beforeB := securityFixtureTree(t, user), securityFixtureTree(t, journey.b)
journey.run(t, journey.a, "add", transport.repo, "--target", "kimi", "--no-save")
state := journey.state(t, journey.a)
if len(state.Installations) != 1 || len(state.EnvironmentReceipts) != 0 {
t.Fatalf("imperative route changed saved ownership: %+v", state)
}
installation := state.Installations[0]
for _, binding := range installation.Clients {
if binding.ClientID != "kimi" || binding.Scope != "project" || binding.Activation != domain.ActivationPrepared || binding.Verification != domain.VerificationInstalled {
t.Fatalf("imperative Kimi overclaimed or lost scope: %+v", binding)
}
if projectJourneyRead(t, filepath.Join(journey.a, ".kimi-code", "skills", "docs", "SKILL.md")) != projectJourneySkill {
t.Fatal("imperative skill projection missing")
}
}
journey.run(t, journey.a, "remove", installation.InstallationID, "--target", "kimi")
projectJourneyAbsent(t, filepath.Join(journey.a, ".kimi-code", "skills", "docs"))
if !reflect.DeepEqual(beforeUser, securityFixtureTree(t, user)) || !reflect.DeepEqual(beforeB, securityFixtureTree(t, journey.b)) {
t.Fatal("imperative project lifecycle changed user or B")
}
}
35 changes: 35 additions & 0 deletions cli/cmd/agentplugins/project_kimi_transfer_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package main

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

// Cold-cache portable import uses the production transport/store/scope path;
// native Kimi and the inert trusted launcher are never executed.
func TestProjectKimiColdSavedImportPreservesPinnedAgreement(t *testing.T) {
transport := newProjectJourneyTransport(t, "archive")
original := newProjectJourney(t, transport)
original.app = configureProjectKimiJourney(original.app, filepath.Join(original.home, ".kimi-code"), filepath.Join(original.home, "kimi"))
if err := os.Mkdir(filepath.Join(original.a, ".git"), 0700); err != nil {
t.Fatal(err)
}
original.run(t, original.a, "add", transport.endpoint, "--alias", "docs", "--target", "kimi")
installation, _ := projectKimiOwned(t, original, original.a, projectJourneySkill)
pair := original.assertPortablePair(t, original.a)
exchange := t.TempDir()
original.run(t, original.a, "deps", "export", exchange, "--yes")
cold := newProjectJourney(t, transport)
cold.app = configureProjectKimiJourney(cold.app, filepath.Join(cold.home, ".kimi-code"), filepath.Join(cold.home, "kimi"))
if err := os.Mkdir(filepath.Join(cold.a, ".git"), 0700); err != nil {
t.Fatal(err)
}
projectJourneyAbsent(t, cold.cache, filepath.Join(cold.a, ".uap"))
cold.run(t, cold.a, "deps", "import", exchange, "--install", "--yes")
restored, _ := projectKimiOwned(t, cold, cold.a, projectJourneySkill)
if restored.Source.SourceBindingID != installation.Source.SourceBindingID || restored.Source.TreeDigest != installation.Source.TreeDigest || pair != cold.assertPortablePair(t, cold.a) {
t.Fatal("Kimi cold restore changed exact source/portable agreement")
}
cold.run(t, cold.a, "remove", "docs", "--target", "kimi")
}
2 changes: 1 addition & 1 deletion docs/plans/project-install-e2e/client-plans.md
Original file line number Diff line number Diff line change
Expand Up @@ -620,6 +620,6 @@ is replaced by explicit unsupported. R2 fixes independently verified GO in
| qoder | GO | Verified | GO | Root assignment required |
| cline | GO | Verified | GO | Root assignment required |
| windsurf | GO | Verified | GO | Root assignment required |
| kimi | GO (current docs) | Verified | GO | Root assignment required |
| kimi | GO (current docs) | Verified | GO | Authored project components; final implementation R1/R2 pending; native project discovery pending |
| chatgpt | GO | Verified | GO | Explicit project refusal implemented; independent implementation review pending |
| vscode | GO | Verified | GO | Root assignment required |
77 changes: 77 additions & 0 deletions docs/plans/project-install-e2e/kimi-code-review-r1.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# Kimi Code project adapter — independent code R1

Reviewed frozen clean `7111008f61adc7a7859d6ce44dbee848943db74c` in
`work/uap-project-kimi`, including adapter checkpoint `456cb039` and its public
composition follow-up. **GO.** Reviewer did not author the implementation.
The original client plan has two GOs, pre-code pinned evidence is committed,
and the exact shared host-version prerequisite retains its separate two GOs.
No Kimi/native client, launcher, model, trust/auth or MCP process was executed
by this review.

## Source and authority

Independently read the captured official exact-tag 2.3.1 configLoader,
config-schema and skillRoots source, with recorded blob IDs/provenance. These
confirm strict JSON loading, user then worktree-root compatibility then selected
cwd branded MCP precedence, explicit stdio/http/sse transport, and nearest .git
skill-root selection. The adapter follows that current component contract;
the preserved user plugin registry is not reused as a project installer.
Static source/help evidence remains separate from actual project discovery.

New install/update/repair requires the selected absolute clean kimi/kimi.exe
and exact host-observed 2.3.1. No adapter/SDK probe or global environment change
is added. UserConfigRoot remains the original selected root. Scope, native root,
exact physical-artifact leaf and `.uap/managed/clients/kimi/<artifact>` must
agree. The generic entry gate plus both project hooks qualify only project
scope; user SSE/declarative support is preserved. A selected skill root cannot
silently move into another Git ancestor or follow an aliased marker.

Both composed and direct activation reject a rebound/empty delivered path;
pre-staging preflight alone allows an absent Delivery. Receipt/layout checks
refuse wrong scope, package, root, unknown/duplicate/path-escaped objects and
stale foreign entries. Active projection recomputes and compares the complete
stored native entry against the canonical selected package/data/cwd/launcher
without writing immutable bytes. Wrong selected data requires refresh rather
than adopting old locators. Native mutation uses the already reviewed shared
component transaction and exact kernel receipts, preserving foreign disabled
members, unknown policy, skills and other environments.

Positive activation attestation is rejected before generic fallback. Configured
files remain prepared with installation verification, never attested/loaded.
Native/user/compatibility precedence is warning context, not evidence of trusted
effective session loading. Exact recorded removal reconstructs package/native
authority without current version or Git admission and never invokes a user
registry/native cleanup command. Recovery and persistent data retain the
existing core ownership rules. Four compatibility goldens change only Kimi's
scope list, retaining every other field; old client tests/source remain present.

## Independent verification

- Project provider suite: **PASS 0.087s**, including scope/artifact/direct-path,
receipt/collision/stale ownership, data binding, nearest Git, component update/
repair/removal and attestation boundaries.
- Public SDK project suite: **PASS 0.149s**; dedicated native codec tests:
**PASS 0.003s**.
- All three public composed CLI scenarios: **PASS 8.388s**, using genuine
loopback TLS Git/ZIP acquisition and production loader/store/planner/installer/
transactions, with expressly inert detection/security and unexecuted launcher
bytes. They cover saved lifecycle, exact old-pin repair, pending directory
journal recovery, selected update/remove, cold portable restore, no-save
composition and user/B/data preservation.
- Complete preserved Kimi client suite: **PASS 0.013s**; `git diff --check`
passed. The author's exact changed/full-file lint evidence is separate.

No blocking finding remains. Genuine project discovery, effective extra roots,
workspace trust, session policy, MCP initialization, model/auth and restart
remain pending; source/help and fixture E2E do not qualify those or a release.
Root owns publication and feature integration after the other independent GO.

## Mechanical integration preservation closure

GO remains valid at clean `83c85702acf1697a59d724c26159e341bc8ba942`. Independently compared every path in the authored `3e072c99..7111008f` delta against the refreshed head: all 33 paths are byte-identical. The merge brings the reviewed root base and evidence without changing the Kimi implementation or its assertions. No new compilation or native process was needed for this byte-preservation check.

## Final authority/admission correction closure

**GO at frozen `95b5f34cc9fb7796e79e0a76e3b853d8a8ce5386`.** Independently inspected the narrow delta after the byte-preserved 83c baseline. Project ValidateBindingProfile now requires literal ClientID kimi before profile/root checks, and a copied codex binding is refused with exact unchanged inventory. The admission fixture now supplies pathpolicy.Policy, first proves the qualified host reaches PlanReady, and requires the actual current-version/harness diagnostic for every negative case. This removes the prior possibility of a generic missing-Paths error satisfying those negatives. No remaining code blocker found.

The prior independent full provider/SDK/codec/public CLI/legacy passes at711 remain recorded above. Latest independent two-case execution was attempted once and failed during setup writing /dev/shm/go-build.../_testmain.go with ENOSPC, before assertions; it is not a pass. Parent directed static final closure using the unchanged prior evidence plus separately attributed author's corrected provider PASS0.139s/focused0.016s and changed lint0, rather than repeating resource failures. Genuine native qualification remains pending and publication remains the parent's decision.
Loading
Loading