Skip to content
Merged
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
3 changes: 2 additions & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ make check
spec/hosts.json host adapter database
spec/hosts.schema.json host spec schema
testdata/ skill fixtures and golden cases
tests/go-golden/ repository-level Go parity harness
docs/ product and contract docs
examples/ minimal SDK consumer CLIs
Makefile root developer commands
Expand Down Expand Up @@ -58,7 +59,7 @@ Run the full parity gate before opening a pull request:
make check
```

This validates the shared spec, fixtures, generated host constants, TypeScript, Go, Rust, and examples.
This validates the shared spec, fixtures, generated host constants, standalone Go modules, TypeScript, Go, Rust, and examples.

## Host Adapter Changes

Expand Down
7 changes: 4 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,14 @@ RESET := \033[0m
TS_DIR := ts
GO_DIR := go
GO_COBRA_DIR := go-cobra
GO_GOLDEN_DIR := tests/go-golden
RUST_DIR := rust
PYTHON_DIR := python
EXAMPLE_TS_DIR := examples/ts
EXAMPLE_GO_DIR := examples/go
EXAMPLE_RUST_DIR := examples/rust
EXAMPLE_PYTHON_DIR := examples/python
GO_FILES := $(shell find $(GO_DIR) $(GO_COBRA_DIR) $(EXAMPLE_GO_DIR) -name '*.go' -type f)
GO_FILES := $(shell find $(GO_DIR) $(GO_COBRA_DIR) $(GO_GOLDEN_DIR) $(EXAMPLE_GO_DIR) -name '*.go' -type f)

# ── Quality ──────────────────────────────────────────────────────────────────

Expand All @@ -31,7 +32,7 @@ test-ts: ## Run TypeScript tests
pnpm --dir $(TS_DIR) test

test-go: ## Run Go SDK tests
cd $(GO_DIR) && go test ./...
node scripts/check-go-modules.mjs

test-go-cobra: ## Run Go Cobra adapter tests
cd $(GO_COBRA_DIR) && go test ./...
Expand All @@ -45,7 +46,7 @@ test-python: ## Run Python SDK tests
fmt: fmt-ts fmt-go fmt-rust fmt-python ## Format all SDK code

fmt-ts: ## Format TypeScript code
cd $(TS_DIR) && pnpm exec prettier --write src test ../examples/ts/cli.ts ../scripts/check.mjs ../scripts/prepare-release.mjs
cd $(TS_DIR) && pnpm exec prettier --write src test ../examples/ts/cli.ts ../scripts/check.mjs ../scripts/check-go-modules.mjs ../scripts/prepare-release.mjs

fmt-go: ## Format Go code
gofmt -w $(GO_FILES)
Expand Down
7 changes: 4 additions & 3 deletions docs/API.md
Original file line number Diff line number Diff line change
Expand Up @@ -415,12 +415,13 @@ In TTY mode, zero detected hosts prompts from all supported hosts. One detected

Selector semantics:

- `scope: "user"` installs into the first `userSkillsDirs` path for each host.
- `scope: "project"` installs into the first `projectSkillsDirs` path for each host.
- `agents: "auto"` uses host detection.
- `scope: "user"` keeps a valid kitup-owned target for the same skill on its configured path; normal ownership checks still decide whether the requesting app may mutate it. Otherwise, it reuses the first `userSkillsDirs` path that exists as a directory or falls back to the first configured path.
- `scope: "project"` keeps a valid kitup-owned target for the same skill on its configured path; normal ownership checks still decide whether the requesting app may mutate it. Otherwise, it reuses the first `projectSkillsDirs` path that exists as a directory or falls back to the first configured path.
- `agents: "auto"` detects a host when any of its non-generic detection paths exists; shared roots such as `.agents` do not identify a host by themselves.
- `agents: "*"` selects every host adapter.
- explicit agents select canonical host ids or aliases.
- hosts without a path for the selected scope return an `unsupported-scope` error.
- uninstall removes every valid kitup-owned copy for the requested `appId` and skill across the selected hosts' configured paths; unmanaged and other-owner directories are never removed.

## Reports

Expand Down
5 changes: 4 additions & 1 deletion docs/architecture.mmd
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,9 @@ flowchart TB
HOSTSPEC["Host Spec\nspec/hosts.json"]:::contract
SCHEMAS["Schemas\nspec/hosts.schema.json\ntestdata/cases.schema.json"]:::contract
CASES["Golden Cases\ntestdata/cases/*.json"]:::contract
GOGOLDEN["Go Golden Harness\ntests/go-golden"]:::artifact
GENHOSTS["Generated Host Constants\nts / go / rust / python"]:::artifact
VERIFY["Verification\ncheck.mjs · sync-hosts.mjs"]:::control
VERIFY["Verification\ncheck.mjs · sync-hosts.mjs · standalone Go modules"]:::control
GITHUB["GitHub API"]:::external
TARGETS["Agent Host\nDirectory State"]:::state
METADATA[".kitup.json"]:::state
Expand All @@ -38,6 +39,8 @@ flowchart TB
HOSTSPEC -.->|"generates"| GENHOSTS
VERIFY -.-> HOSTSPEC
VERIFY -.->|"checks parity"| CASES
VERIFY -.-> GOGOLDEN
CASES -.-> GOGOLDEN
VERIFY -.-> GENHOSTS

classDef actor fill:#1f2937,stroke:#94a3b8,color:#f8fafc,stroke-width:1px;
Expand Down
16 changes: 13 additions & 3 deletions docs/host-adapter-contract.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,15 @@ Each host entry describes where a local Agent Skill can be installed and how the

`projectSkillsDirs` and `userSkillsDirs` are ordered.

The first path is the canonical install target for that host. Later paths are compatible discovery roots that the host also scans. SDKs should install to the first path unless a caller explicitly requests another supported path.
The first path is the canonical install target for that host. Later paths are compatible discovery roots that the host also scans. SDKs keep using a configured path that already contains a valid kitup-owned target for the same skill; normal ownership checks still decide whether the requesting app may update or remove it. Otherwise, SDKs reuse the first configured path that exists as a directory or install to the first path when none exist. Regular files do not count as compatible install directories.

Project paths must be relative paths. User paths must be home-relative paths beginning with `~/`.
All adapter paths use `/` separators and non-empty segments; `..`, backslashes, colons, and NUL bytes are invalid.
All adapter paths use `/` separators and non-empty segments; `.`, `..`, backslashes, colons, and NUL bytes are invalid.

If multiple selected hosts resolve to the same target directory, SDKs must copy once and associate that installed target with every matching host. Shared roots such as `.agents/skills` are common and should not produce duplicate writes.

Uninstall removes every valid kitup-owned copy for the requested `appId` and skill across the selected hosts' configured paths. This cleans up duplicate owned copies left by earlier target selection while preserving unmanaged and other-owner directories.

## Aliases

`aliases` are accepted input names for one host adapter.
Expand All @@ -25,7 +27,15 @@ Aliases are for ecosystem compatibility only. SDK result objects should return t

`detect` is only a default selector for `agents: "auto"`.

Detection should check path existence. Entries may be home-relative paths such as `~/.codex` or project-relative paths such as `.replit`.
Detection checks path existence across every `detect` entry: a host is detected when any of its non-generic entries exists. Entries may be home-relative paths such as `~/.codex` or project-relative paths such as `.replit`.

Every `detect` entry must be evidence that this specific host is present:

- Generic shared roots (`~/.agents`, `~/.agents/skills`, `~/.config/agents`, `.agents`, `.agents/skills`, `package.json`) never count as evidence and are ignored by detection.
- An entry must not be one of the host's own install directories — a kitup install would create that directory itself and turn into next run's false detection evidence.
- An entry must not point at another host's namespace; a host that scans other tools' skill directories expresses that through `projectSkillsDirs`/`userSkillsDirs` compatibility paths, not through `detect`. Hosts that share their entire install surface (one product namespace) may share detection evidence.

SDK host-spec loaders reject non-generic detection paths that duplicate the same host's install directories, including in custom `hostsFile` overrides. `scripts/check.mjs` also enforces cross-host ownership for the canonical adapter table.

Detection must not run host binaries, start editors, mutate configuration, or require network access.

Expand Down
4 changes: 1 addition & 3 deletions examples/go/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,4 @@ module kitup-example-go

go 1.23

require github.com/lathe-cli/kitup/go v0.1.0

replace github.com/lathe-cli/kitup/go => ../../go
require github.com/lathe-cli/kitup/go v0.1.3
2 changes: 0 additions & 2 deletions go-cobra/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,3 @@ require (
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/spf13/pflag v1.0.6 // indirect
)

replace github.com/lathe-cli/kitup/go => ../go
2 changes: 2 additions & 0 deletions go-cobra/go.sum
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/lathe-cli/kitup/go v0.1.3 h1:7eEW8mDr5MbXFaTwr2dlnH8ebg+3Kmhj2/j4d9YfQNQ=
github.com/lathe-cli/kitup/go v0.1.3/go.mod h1:dZgJDmFRKjaFBZyaP1qlzOB9IEafnf1/ai4KX4hMA4c=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=
github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
Expand Down
15 changes: 11 additions & 4 deletions go-cobra/skill_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ func TestSkillCommandInstallsWithCoreFlags(t *testing.T) {
var out bytes.Buffer
cmd := NewSkillCommand(Options{
AppID: "example-cli",
Bundle: kitup.DirectoryBundle(filepath.Join("..", "testdata", "skills", "basic")),
Bundle: basicBundle(),
Home: home,
Out: &out,
})
Expand All @@ -36,7 +36,7 @@ func TestInstallCommandPromptsForScopeBeforeInstall(t *testing.T) {
var out bytes.Buffer
cmd := NewSkillCommand(Options{
AppID: "example-cli",
Bundle: kitup.DirectoryBundle(filepath.Join("..", "testdata", "skills", "basic")),
Bundle: basicBundle(),
Home: home,
CWD: workspace,
StdinTTY: true,
Expand Down Expand Up @@ -71,7 +71,7 @@ func TestInstallCommandForceOverwritesUnmanaged(t *testing.T) {
var out bytes.Buffer
cmd := NewSkillCommand(Options{
AppID: "example-cli",
Bundle: kitup.DirectoryBundle(filepath.Join("..", "testdata", "skills", "basic")),
Bundle: basicBundle(),
Home: home,
Out: &out,
})
Expand All @@ -89,7 +89,7 @@ func TestInstallCommandForceOverwritesUnmanaged(t *testing.T) {
func TestInstallCommandReturnsCoreFlagError(t *testing.T) {
cmd := NewInstallCommand(Options{
AppID: "example-cli",
Bundle: kitup.DirectoryBundle(filepath.Join("..", "testdata", "skills", "basic")),
Bundle: basicBundle(),
Home: t.TempDir(),
})
cmd.SetArgs([]string{"--scope", "bad"})
Expand All @@ -99,3 +99,10 @@ func TestInstallCommandReturnsCoreFlagError(t *testing.T) {
t.Fatalf("got %v, want %q", err, kitup.InstallUX.InvalidFlags)
}
}

func basicBundle() kitup.SkillBundle {
return kitup.FilesBundle([]kitup.SkillFile{{
Path: "SKILL.md",
Contents: []byte("---\nname: basic\ndescription: Basic fixture.\n---\n"),
}})
}
7 changes: 7 additions & 0 deletions go.work
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
go 1.23

use (
./examples/go
./go
./go-cobra
)
1 change: 1 addition & 0 deletions go.work.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
github.com/lathe-cli/kitup/go v0.1.3/go.mod h1:dZgJDmFRKjaFBZyaP1qlzOB9IEafnf1/ai4KX4hMA4c=
Loading
Loading