This file provides guidance to AI agents when working with code in this repository.
kdn is a command-line interface for launching and managing AI agents (Claude Code, Goose, Cursor, OpenCode, OpenClaw) with custom configurations. It provides a unified way to start different agents with specific settings including skills, MCP server connections, and LLM integrations.
All build and test commands are available through the Makefile. Run make help to see all available commands.
make buildAfter building, the kdn binary will be created in the current directory:
# Display help and available commands
./kdn --help
# Execute a specific command
./kdn <command> [flags]# Run all tests
make test
# Run tests with coverage report
make test-coverageFor more granular testing (specific packages or tests), use Go directly:
# Run tests in a specific package
go test ./pkg/cmd
# Run a specific test
go test -run TestName ./pkg/cmd# Format all Go files in the project
make fmt
# Check if code is formatted (without modifying files)
make check-fmtCode should be formatted before committing. Run make fmt to ensure consistent style across the codebase.
The project includes pre-commit and commit-msg hooks in .githooks/ that enforce formatting checks (make check-fmt) and signed-off commits. Activate them once after cloning:
make setup-hooks# Run integration tests (requires Podman)
make test-integration# Run go vet
make vet
# Run all CI checks (format check, vet, tests)
make ci-checks
# Clean build artifacts
make clean
# Install binary to GOPATH/bin
make install- Entry point:
cmd/kdn/main.go→ callscmd.NewRootCmd().Execute()and handles errors withos.Exit(1) - Root command:
pkg/cmd/root.goexportsNewRootCmd()which creates and configures the root command - Subcommands: Each command is in
pkg/cmd/<command>.gowith aNew<Command>Cmd()factory function - Commands use a factory pattern: each command exports a
New<Command>Cmd()function that returns*cobra.Command - Command registration:
NewRootCmd()callsrootCmd.AddCommand(New<Command>Cmd())for each subcommand - No global variables or
init()functions - all configuration is explicit through factory functions
Global flags are defined as persistent flags in pkg/cmd/root.go and are available to all commands.
The --storage flag specifies the directory where kdn stores all its files. The default path is computed at runtime using os.UserHomeDir() and filepath.Join() to ensure cross-platform compatibility (Linux, macOS, Windows). The default is $HOME/.kdn with a fallback to .kdn in the current directory if the home directory cannot be determined.
Environment Variable: The KDN_STORAGE environment variable can be used to set the storage directory path. The flag --storage will override the environment variable if both are specified.
Priority order (highest to lowest):
--storageflag (if specified)KDN_STORAGEenvironment variable (if set)- Default:
$HOME/.kdn
To access this value in any command:
func NewExampleCmd() *cobra.Command {
return &cobra.Command{
Use: "example",
Short: "An example command",
Run: func(cmd *cobra.Command, args []string) {
storagePath, _ := cmd.Flags().GetString("storage")
// Use storagePath...
},
}
}Important: Never hardcode paths with ~ as it's not cross-platform. Always use os.UserHomeDir() and filepath.Join() for path construction.
All modules (packages outside of cmd/) MUST follow the interface-based design pattern to ensure proper encapsulation, testability, and API safety.
Required Pattern:
- Public types are interfaces - All public types must be declared as interfaces
- Implementations are unexported - Concrete struct implementations must be unexported (lowercase names)
- Compile-time interface checks - Add unnamed variable declarations to verify interface implementation at compile time
- Factory functions - Provide
New*()functions that return the interface type
Benefits:
- Prevents direct struct instantiation (compile-time enforcement)
- Forces usage of factory functions for proper validation and initialization
- Enables easy mocking in tests
- Clear API boundaries
- Better encapsulation
This pattern is MANDATORY for all new modules in pkg/.
When designing JSON storage structures for persistent data, use nested objects with subfields instead of flat structures with naming conventions.
Preferred Pattern (nested structure):
{
"id": "dc610bffa75f21b5b043f98aff12b157fb16fae6c0ac3139c28f85d6defbe017",
"paths": {
"source": "/Users/user/project",
"configuration": "/Users/user/project/.kaiden"
}
}Benefits:
- Better organization - Related fields are grouped together
- Clarity - Field relationships are explicit through nesting
- Extensibility - Easy to add new subfields without polluting the top level
- No naming conflicts - Avoids debates about snake_case vs camelCase
- Self-documenting - Structure communicates intent
The runtime system provides a pluggable architecture for managing workspaces on different container/VM platforms (Podman, MicroVM, Kubernetes, etc.).
Key Components:
- Runtime Interface (
pkg/runtime/runtime.go): Contract all runtimes must implement - Registry (
pkg/runtime/registry.go): Manages runtime registration and discovery - Runtime Implementations (
pkg/runtime/<runtime-name>/): Platform-specific packages (e.g.,fake,podman,openshell) - Centralized Registration (
pkg/runtimesetup/register.go): Automatically registers all available runtimes
Optional Interfaces:
- StorageAware: Enables runtimes to persist data in a dedicated storage directory
- AgentLister: Enables runtimes to report which agents they support
- Terminal: Enables interactive terminal sessions with instances (auto-starts if needed)
- Dashboard: Enables runtimes to expose a web dashboard URL (
GetURL(ctx, instanceID) (string, error)) - FlagProvider: Enables runtimes to declare runtime-specific CLI flags (
Flags() []FlagDef). Flag values flow throughAddOptions.RuntimeOptionsandCreateParams.RuntimeOptionsasmap[string]string, keeping the command layer runtime-agnostic. Theruntimesetup.ListFlags()bridge discovers flags from all available runtimes for registration on cobra commands. - HostResolver: Declares the container hostname used to reach the host machine from inside the workspace. Runtimes that do not use
host.containers.internal(e.g., OpenShell useshost.openshell.internal) implementContainerHostname() string. The manager passes this value toagent.SetModel()so that localhost URLs are rewritten to the correct hostname. When absent,containerurl.ContainerHost(host.containers.internal) is used. - ConfigTransformer: Allows a runtime to transform the merged
WorkspaceConfigurationbefore it is applied. The methodTransformConfig(cfg *workspace.WorkspaceConfiguration) erroris called after config merging. OpenShell implements this to rewrite MCP command args tohost.openshell.internal. - Experimental: Signals that a runtime's support is experimental. The
initcommand detects this interface viamanager.GetRuntime()and prints⚠️ <DisplayName> runtime support is experimentalto stderr using the runtime'sDisplayName()(suppressed in JSON output mode). No return value — presence of the interface is the signal. Currently implemented by:openshell.
For detailed runtime implementation guidance, use: /working-with-runtime-system
To add a new runtime, use: /add-runtime
The OpenShell runtime downloads three binaries (openshell, openshell-gateway, openshell-driver-vm) from official GitHub releases at https://github.com/NVIDIA/OpenShell/releases.
Default version constant: pkg/runtime/openshell/version.go defines DefaultVersion (currently v0.0.39). To bump the default, update both files:
pkg/runtime/openshell/version.go— change theDefaultVersionconstantAGENTS.md— update the version mentioned in this section (the "currentlyvX.Y.Z" parenthetical above)
--openshell-version flag: Users can override the version at kdn init time (e.g., kdn init --openshell-version v0.1.0). The flag value flows through RuntimeOptions["openshell-version"] and is read in Create() before binaries are downloaded.
Binary caching: Binaries are cached per version at <storageDir>/bin/<version>/. Different versions coexist without conflict.
When a workspace has network.mode = deny, the Podman runtime enforces outbound traffic filtering on every Start() using two layers. Allowed hosts come from network.hosts and are automatically augmented by host patterns derived from configured secrets. With no allowed hosts at all, the approval-handler denies every request (fully-isolated workspace).
- nftables firewall (kernel level): A
network-guardsidecar withCAP_NET_ADMINruns nftables rules that DROP outbound traffic from the agent's UID. Loopback andhost.containers.internalare always allowed. This prevents bypassing the proxy by unsettingHTTP_PROXY. - OneCLI proxy (application level): All existing OneCLI rules are deleted, a single
manual_approvalrule for*is created ("allow"is not a valid OneCLI action), and theapproval-handlersidecar approves/denies intercepted requests by hostname pattern.
Secret host auto-injection: When secrets are configured, collectSecretHosts() (in network.go) reads their host patterns from the secret service registry (known types) or stored metadata (other type) and merges them with the explicit network.hosts list. The podmanRuntime receives the secretservice.Registry via SetSecretServiceRegistry(), called by manager.RegisterRuntime() for runtimes that implement the secretServiceRegistryAware interface.
Key files:
pkg/runtime/podman/network.go—configureNetworking/clearNetworkingRules/setupFirewallRules/clearFirewallRules/buildNftScript/collectSecretHosts/mergeHostspkg/runtime/podman/pods/approval-handler.ts— Node.js sidecar (TypeScript, runs viatsx)pkg/runtime/podman/pods/onecli-pod.yaml— pod manifest with the approval-handler, network-guard, and OneCLI containerspkg/runtime/podman/system/path.go/path_windows.go—HostPathToMachinePath/MachinePathToHostPathfor translating host paths to Podman Machine (WSL2) paths on Windows; no-ops on Linux/macOS
Per-workspace temporary directories: The Podman runtime writes files into subdirectories of <storage-dir>/<dir>/<workspace-name>/ for each workspace it creates. These directories are cleaned up when the workspace is removed. The authoritative list is the workspaceTempDirs variable in pkg/runtime/podman/remove.go. If you add a new per-workspace directory under storageDir, add its name to that slice so Remove() cleans it up automatically.
For the full design, use: /working-with-onecli
The secret service system provides a pluggable architecture for managing secret service definitions that describe how secrets are applied to workspace requests.
Key Components:
- SecretService Interface (
pkg/secretservice/secretservice.go): Contract all secret services must implement (Name(),Description(),HostsPatterns(),Path(),EnvVars(),HeaderName(),HeaderTemplate()) - Registry (
pkg/secretservice/registry.go): Manages secret service registration and discovery - Centralized Registration (
pkg/secretservicesetup/register.go): Automatically registers all available secret services;ListAvailable()returns the names of all registered services (used by commands to derive valid--typevalues dynamically);ListServices()returns fully-constructed service instances (used byautoconfto iterate env vars)
For detailed guidance on the full secrets abstraction (Store, registry, adding new types), use: /working-with-secrets
The credential system provides a pluggable architecture for intercepting sensitive credential files that users mount into workspace containers. When a declared mount targets a known credential file (e.g., $HOME/.config/gcloud/application_default_credentials.json), kdn replaces the real file with a placeholder and configures OneCLI to inject the real credential at request time.
Key Components:
Credentialinterface (pkg/credential/credential.go): Contract all credentials must implement:Name() string— unique identifierContainerFilePath() string— absolute path inside the container where the credential file livesDetect(mounts []workspace.Mount, homeDir string) (hostFilePath string, intercepted *workspace.Mount)— returns the real host path and the mount to intercept, or("", nil)if not applicableFakeFile(hostFilePath string) ([]byte, error)— returns placeholder file content (no real credentials)Configure(ctx, client onecli.Client, hostFilePath string) error— reads the real file and registers the credential with OneCLIHostPatterns(hostFilePath string) []string— hostnames OneCLI must be allowed to reach for this credential
- Registry (
pkg/credential/registry.go): Thread-safe ordered list;NewRegistry() Registry;Register()rejects nil, empty name, and duplicates - Centralized Registration (
pkg/credentialsetup/register.go):RegisterAll(registrar)registers all built-in credentials (currentlygcloud) CredentialRegistryAwareoptional runtime interface (pkg/runtime/runtime.go): Runtimes that implementSetCredentialRegistry(credential.Registry)receive the registry automatically whenmanager.RegisterRuntime()is called
Podman runtime integration:
detectCredentials()increate.goiterates the registry atCreatetime, writes fake files to<storageDir>/credentials/<workspaceName>/<credName>/credential(mode0600), and returns[]activeCredential- Fake credential mounts are added to the container; intercepted original mounts are suppressed via an
interceptedMounts map[mountKey]bool setupOneclicallscred.Configure()for each active credential to register it with OneCLIcollectCredentialHosts()instart.gomerges credential host patterns into the network allow-list- Credential directories are cleaned up on workspace removal via the
"credentials"entry inworkspaceTempDirs(pkg/runtime/podman/remove.go)
Per-workspace credential directory layout:
<storageDir>/
credentials/
<workspaceName>/
<credName>/
credential ← fake placeholder file (mode 0600)
Implementations:
pkg/credential/gcloud/— Google Cloud ADC (application_default_credentials.json); configures OneCLI Vertex AI viaConnectApppkg/credential/kubeconfig/— Kubernetes token-based auth (~/.kube/config); detects by mount target ($HOME/.kube/configor$HOME/.kube), activates only when the current context uses token auth (not client certs), writes a pruned single-context kubeconfig with a placeholder token, and registers an OneCLIAuthorization: Bearersecret for the cluster API server host
The pkg/devcontainers/features package models, downloads, and orders Dev Container Features (OCI registry artifacts or local file trees) prior to container image generation.
Key Components:
Featureinterface (pkg/devcontainers/features/features.go):ID()+Download(ctx, destDir) (FeatureMetadata, error)FeatureMetadatainterface: exposesContainerEnv(),Options(),InstallsAfter()parsed fromdevcontainer-feature.jsonFeatureOptionsinterface:Merge(userOptions) (map[string]string, error)— normalises keys (uppercase, non-alphanumeric →_), applies defaults, validates types and enumsFromMap: classifies IDs (./…→ local,https?://→ error, otherwise → OCI), returns a sorted[]Featureslice plus the user-options map — no I/OOrder: topological sort (Kahn's algorithm) oninstallsAfterfields; matches versionlessinstallsAfterIDs against versioned registered IDs by stripping the tag
Typical call sequence: FromMap → Feature.Download (parallel) → Order → FeatureOptions.Merge per feature.
Podman runtime integration (pkg/runtime/podman/create.go):
prepareFeatures()drives the full sequence whenWorkspaceConfig.Featuresis non-empty- Features are downloaded into
<instanceDir>/features/feature-{i}/(stable names based onFromMap's sorted output) - Each feature becomes a
featureInstallInfo{dirName, options, envVars}passed togenerateContainerfile() WorkspaceConfigDiris required inCreateParamssoFromMapcan resolve./local-featurepaths- In the Containerfile, feature
COPY/RUN/ENVinstructions are placed after user creation (useradd -m agent) but beforeUSER agent:agent— features still run as root so they can install system-wide tools, but/home/agentand theagentaccount exist so install scripts canchown, write dotfiles, andsuto the target user._REMOTE_USERand_REMOTE_USER_HOMEare exported asENVimmediately before the feature block
Config merger requirement: The pkg/config/merger.go Merge() and copyConfig() functions must explicitly handle every field of WorkspaceConfiguration. Fields not wired in are silently dropped. Features is handled via mergeFeatures() / copyFeatures(); Ports is handled via mergePorts() / inline copy.
For full implementation details, use: /working-with-devcontainers
The StepLogger system provides user-facing progress feedback during runtime operations with spinners and completion messages.
Key Points:
- Commands inject StepLogger into context based on output mode (text with spinners vs JSON silent)
- Runtime methods retrieve logger from context and report progress steps
- Automatic behavior: animated spinners in text mode, silent in JSON mode
For detailed StepLogger integration guidance, use: /working-with-steplogger
The Logger system (pkg/logger) routes stdout and stderr from runtime CLI commands (e.g., podman build) to the user. It is controlled by the --show-logs flag.
Key Points:
- Commands inject a
logger.Loggerinto context based on the--show-logsflag - Runtime methods retrieve it from context and pass its writers to CLI command execution
- When
--show-logsis set, output is written to the command's stdout/stderr; otherwise it is discarded --show-logscannot be combined with--output json(enforced inpreRun)
Interface (pkg/logger/logger.go):
type Logger interface {
Stdout() io.Writer
Stderr() io.Writer
}Context integration (pkg/logger/context.go): WithLogger() / FromContext() — mirrors the StepLogger pattern.
The pkg/envvars package provides shared helpers for reading environment variables.
IsTruthy(key string) bool — returns true if the environment variable named by key is set to a recognised truthy string (1, true, True, TRUE, yes, Yes, YES). Use this instead of inlining a switch whenever a boolean env var controls behaviour.
The OneCLI system (pkg/onecli) provides a typed HTTP client and higher-level abstractions for interacting with the OneCLI API, which proxies outbound HTTP requests and injects secrets as headers inside workspace containers.
Key Points:
Client— raw CRUD for secrets and networking rules against the OneCLI APICredentialProvider— retrieves theoc_API key from/api/user/api-key(bootstraps local user on first call)SecretMapper— convertsworkspace.Secretvalues toCreateSecretInputfor the API; handles known types via the secret service registry and theothertype via explicit fieldsSecretProvisioner— idempotently creates or updates secrets; handles 409 conflicts by patching the existing secret
For detailed OneCLI integration guidance, use: /working-with-onecli
The config system manages workspace configuration for injecting environment variables, mounting directories, providing skills, configuring MCP servers, managing secrets and controlling network access into workspaces (different from runtime-specific configuration).
Multi-Level Configuration:
- Workspace-level (
.kaiden/workspace.json) - Project configuration, set via--workspace-configurationflag - Project-specific (
~/.kdn/config/projects.json) - User's custom config for specific projects - Global (empty string
""key inprojects.json) - Settings applied to all projects - Agent-specific (
~/.kdn/config/agents.json) - Per-agent overrides
Configuration Precedence: Agent > Project > Global > Workspace (highest to lowest)
Key config interfaces in pkg/config/:
WorkspaceConfigUpdater(workspaceupdater.go): Reads/writes.kaiden/workspace.json. Methods:AddSecret(name),AddEnvVar(name, value),AddMount(host, target, ro).ProjectConfigUpdater(projectsupdater.go): Reads/writes~/.kdn/config/projects.json. Methods:AddSecret(projectID, secretName),AddMount(projectID, host, target string, ro bool). Pass""asprojectIDfor the global entry. Both methods are idempotent.AgentConfigUpdater(agents.go): Reads/writes~/.kdn/config/agents.jsonper agent. Methods:AddEnvVar(agentName, name, value),AddMount(agentName, host, target, ro). Used bykdn autoconfto record Vertex AI config for theclaudeagent.AgentConfigLoader(agents.go): Loads aWorkspaceConfigurationfor a named agent fromagents.json. Returns an empty config (not an error) when the file or agent key is absent.
A separate mechanism (distinct from env/mount config) allows default dotfiles to be baked into the workspace image:
- Location:
~/.kdn/config/<agent>/(e.g.,~/.kdn/config/claude/) - Files are read by
manager.readAgentSettings()into amap[string]agent.SettingsFileand passed to the runtime viaruntime.CreateParams.AgentSettings - After reading, the manager calls
agent.SkipOnboarding(),agent.SetModel()(if a model is set), andagent.SetMCPServers()(if MCP is configured) to further modify the settings map - The Podman runtime writes these files into the build context as
agent-settings/and addsCOPY --chown=agent:agent agent-settings/. /home/agent/to the Containerfile - Result: every file under
config/<agent>/lands at the corresponding path under/home/agent/inside the image
For detailed guidance, use: /working-with-config-system
For detailed configuration guidance, use: /working-with-config-system
The Podman runtime supports runtime-specific configuration for building and configuring containers (base image, packages, sudo permissions, agent setup).
Configuration Files:
<storage-dir>/runtimes/podman/config/image.json- Base image configuration<storage-dir>/runtimes/podman/config/claude.json- Claude agent configuration<storage-dir>/runtimes/podman/config/goose.json- Goose agent configuration<storage-dir>/runtimes/podman/config/opencode.json- OpenCode agent configuration<storage-dir>/runtimes/podman/config/openclaw.json- OpenClaw agent configuration
For Podman runtime configuration details, use: /working-with-podman-runtime-config
The OpenClaw agent runs a local gateway on port 18789 inside the container. The terminal command starts the gateway in the background, waits for it to be ready, then launches the openclaw CLI. Type talk to agent in the CLI to start a chatbot conversation.
To access the OpenClaw web UI, first start the workspace terminal and wait for the CLI to become ready:
kdn terminal <workspace-name>Then, in a separate session, open the web UI:
kdn workspace open <workspace-name>This opens the browser to the gateway's control UI. Enter openclaw123 as the gateway token to authenticate.
The gateway token and other defaults are configured via SkipOnboarding() in pkg/agent/openclaw.go, which writes to .openclaw/openclaw.json inside the container.
Skills are reusable capabilities that can be discovered and executed by AI agents:
- Location:
.agents/skills/<skill-name>/SKILL.md - Claude support:
.claude/skillsis a symlink to../.agents/skills, so Claude Code discovers skills automatically - Format: Each SKILL.md contains:
- YAML frontmatter with
name,description,argument-hint - Detailed instructions for execution
- Usage examples
- YAML frontmatter with
Skills can be provided to workspaces via the skills field in workspace.json (or any other config level). Each entry is the path to a single skill directory on the host. kdn mounts it read-only into the agent's skills directory inside the container using the directory's basename as the skill name:
| Agent | Container skills directory |
|---|---|
| Claude Code | $HOME/.claude/skills/ |
| Goose | $HOME/.agents/skills/ |
| Cursor | $HOME/.cursor/skills/ |
| OpenCode | $HOME/.opencode/skills/ |
| OpenClaw | $HOME/.openclaw/skills/ |
The Agent interface (pkg/agent/agent.go) exposes SkillsDir() string which returns the container path (using the $HOME variable) where skill directories should be mounted. The manager calls this during Add() to convert WorkspaceConfig.Skills entries into workspace.Mount entries before passing the config to the runtime.
- Create directory:
.agents/skills/<skill-name>/ - Create SKILL.md with frontmatter and instructions
- No symlink step needed —
.claude/skillsalready symlinks to.agents/skills/
Available Skills:
/add-command-simple- For commands without JSON output support/add-command-with-json- For commands with JSON output support/add-alias-command- For alias commands that delegate to existing commands/add-parent-command- For parent commands with subcommands
All commands MUST:
- Define the
Argsfield for argument validation (cobra.NoArgs,cobra.ExactArgs(n), etc.) - Include an
Examplefield with usage examples - Have a corresponding
Test<Command>Cmd_Examplestest function to validate examples
For advanced command patterns, use: /implementing-command-patterns
For JSON output types (where they are defined and how to use them), use: /working-with-json-output-types
For testing commands, use: /testing-commands
The instances manager provides the API for managing workspace instances:
// Create manager
manager, err := instances.NewManager(storageDir)
// Register runtimes
runtimesetup.RegisterAll(manager)
// Register agents
agentsetup.RegisterAll(manager)
// Register secret services
secretservicesetup.RegisterAll(manager)
// Add instance
manager.Add(ctx, instances.AddOptions{...})
// List, Get, Delete instances
manager.List()
manager.Get(id)
manager.Delete(id)
// Start, Stop instances
manager.Start(ctx, id)
manager.Stop(ctx, id)
// Dashboard URL (returns ErrDashboardNotSupported if runtime does not implement Dashboard)
manager.GetDashboardURL(ctx, id)
// Interactive terminal
manager.Terminal(ctx, id, []string{"bash"})
// Retrieve a registered runtime by type (e.g. to check optional interfaces like Experimental)
manager.GetRuntime(runtimeType)Workspace Name Sanitization: The manager automatically sanitizes workspace names — whether auto-generated from the source directory basename or provided via --name. Names are lowercased and any run of invalid characters (spaces, @, etc.) is collapsed into a single hyphen. This ensures compatibility with runtimes like Podman that require lowercase image names.
For detailed manager API and project detection, use: /working-with-instances-manager
The pkg/project package provides a single shared implementation for computing stable project identifiers from a source directory. Both the instances manager and the autoconf command use it so the identifier is always derived the same way.
Key Components:
Detectorinterface (pkg/project/project.go): Single methodDetectProject(ctx, dir) stringNewDetector(git.Detector) Detector: Factory — wraps agit.Detectorto resolve repository info
Detection rules (in priority order):
- Git repo with remote URL →
<remoteURL>/or<remoteURL>/<relPath> - Git repo without remote →
filepath.Join(rootDir, relPath)(or justrootDir) - Not a git repo →
diras-is
Usage:
detector := project.NewDetector(git.NewDetector())
projectID := detector.DetectProject(ctx, sourceDir)Integration points:
instances.NewManagerwrapsproject.NewDetector(git.NewDetector())internally;newManagerWithFactoryaccepts aproject.Detectorfor test injectionautoconfCmdstores aproject.Detectorfield with a nil guard inpreRun; inject a fake in tests
Core Rules:
- Host paths (files on disk): Use
filepath.Join()- works on Windows, macOS, Linux - Container paths (inside Podman): Use
path.Join()- containers are always Unix/Linux - Convert relative paths to absolute with
filepath.Abs() - Never hardcode paths with
~- useos.UserHomeDir()instead - Use
t.TempDir()for ALL temporary directories in tests
Example:
import (
"path" // For container paths
"path/filepath" // For host paths
)
// Host path (cross-platform)
configDir := filepath.Join(storageDir, ".kaiden")
// Container path (always Unix)
workspacePath := path.Join("/workspace", "sources")For detailed cross-platform patterns, use: /cross-platform-development
All markdown files (*.md) in this repository must follow these standards:
Fenced Code Blocks:
- ALWAYS include a language tag in fenced code blocks
- Use the appropriate language identifier (
bash,go,json,yaml,text, etc.) - For output examples or plain text content, use
textas the language tag - This ensures markdown linters (markdownlint MD040) pass and improves syntax highlighting
Common Language Tags:
bash- Shell commands and scriptsgo- Go source codejson- JSON data structuresyaml- YAML configuration filestext- Plain text output, error messages, or generic contentmarkdown- Markdown examples
All source files must include Apache License 2.0 copyright headers with Red Hat copyright. Use the /copyright-headers skill to add or update headers automatically. The current year is 2026.
- Cobra (github.com/spf13/cobra): CLI framework
- Go 1.26+
Tests follow Go conventions with *_test.go files alongside source files. Tests use the standard testing package and should cover command initialization, execution, and error cases.
All tests MUST call t.Parallel() as the first line of the test function.
Exception: Tests using t.Setenv() cannot use t.Parallel() on the parent test function.
For general testing best practices, use: /testing-best-practices
For command testing patterns, use: /testing-commands
For cross-platform testing, use: /cross-platform-development
Before submitting a PR (code, tests, docs checklist), use: /complete-pr
GitHub Actions workflows are stored in .github/workflows/. All workflows must use commit SHA1 hashes instead of version tags for security reasons (to prevent supply chain attacks from tag manipulation).
Example:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2Always include the version as a comment for readability.