internals/skills/go/SKILL.md
Go CLI development: building the ov binary, running tests, understanding the source code structure. MUST be invoked before reading or modifying any Go source file in ov/.
npx skillsauth add overthinkos/overthink-plugins goInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
The ov CLI is a Go program in the ov/ directory. It uses the Kong CLI framework, go-containerregistry for OCI operations, and YAML parsing for configuration. All computation, validation, and building logic lives in Go. Taskfiles are used only for bootstrapping (building ov itself).
LoadUnified)The unified format's entry point is LoadUnified(dir) at ov/unified.go. It reads <dir>/overthink.yml, recursively resolves the import: statement (max depth 8, cycle-safe via visited set), parses every file as a YAML multi-document stream (so bundle files with --- separators work), and routes each document by shape:
version:, import:, discover:, defaults:, distro:, builder:, init:, image:, layer:, deploy: → parsed as UnifiedFile, merged root-wins.layer:, image:, deploy:, builder:, distro:, init: + a name: inside → registered under name: in the matching map.UnifiedFile.ApplyDiscover(rootDir) walks discover: roots after initial merge — generic over six entity kinds via a single applyScanSpecsKindKeyed + applyScanSpecsLayers pair. Explicit map entries always win over discovered entries.
Projections to today's concrete types: ProjectConfig() → *Config, ProjectDistroConfig() → *DistroConfig, etc. Existing LoadConfig / LoadBuildConfigForImage / LoadDeployConfig continue to work unchanged — migration to the unified entry point is incremental.
UnifiedFile.Import + Namespaces)UnifiedFile.Import (type ImportList, YAML tag import) is the single composition statement. Its custom UnmarshalYAML accepts a mixed-shape sequence: a scalar item → ImportEntry{Ref: …} (flat, Namespace == ""); a single-key mapping item → ImportEntry{Namespace: alias, Ref: …} (namespaced). A matching MarshalYAML round-trips both shapes (migrators rely on it). validateNamespaceAlias enforces a bare lowercase-hyphenated alias (no dots).
loadUnifiedInto processes the queue:
UnifiedFile (same root-wins merge that drives same-repo file splits + shared build.yml).loadNamespaceCached(ref, base, nsCache), which loads the target as a fully-resolved, isolated UnifiedFile (its own flat imports + its own namespaced imports, with a FRESH visited set for its file-cycle detection) and mounts it under merged.Namespaces[alias]. These entries are NOT flat-merged into the root maps — they are referenced qualified.UnifiedFile.Namespaces (map[string]*UnifiedFile, YAML tag -, never authored directly) holds the mounted children; projectConfigCached projects it to Config.Namespaces (map[string]*Config). The shared nsCache is the cycle-break: loadNamespaceCached records an in-progress node in nsCache[key] BEFORE recursing, so the intentional main ↔ cachyos mutual import (main imports cachyos; cachyos imports ov, i.e. main) terminates instead of looping. canonicalRef keys the cache; a whole-repo ref with an empty sub-path resolves to that repo's overthink.yml.
ov/namespace.go)The resolver implements Go-package-member semantics over Config.Namespaces:
splitNamespaceRef(ref) — splits a qualified ref on its FIRST . into (ns, rest); a bare ref returns ok=false; the remainder may itself be qualified (a.b.c → "a", "b.c").resolveImageRef(ref) / resolveLocalRef(ref) — bare names resolve in the current Config; ns.name descends into c.Namespaces[ns] recursively, returning the entry plus the Config (namespace context) it lives in.resolveNamespacedBases(out, …) — after the local image set resolves, pulls every namespace-qualified base: (and qualified builder: ref, but only for images that actually have layers to build) into out, keyed by fully-qualified name, iterating to a fixpoint (a pulled-in image may reference a deeper namespaced base).pullNamespacedImage(from, ref, keyPrefix, …) — descends the namespace chain to the leaf, re-keys the entry's own internal base to the fully-qualified ancestor so the build graph references it correctly, and recurses to pull that ancestor.The inheritance rule lives here: distro:/build: are VALUES → inherited across a namespace boundary; builder: is a map of namespace-relative REFS → NOT inherited (the consumer declares its own). See the file header comment for the rationale (avoid leaking a base-namespace-relative ref into a consumer where that namespace doesn't exist). leafName(ref) strips every namespace prefix to the final member name (ov.arch-builder → arch-builder); paired with resolveImageRef's returned namespace Config it keys the resolved entity in that Config.Image map (used by the reachability walk below).
ov/refs.go + ov/layers.go) — per-entity version + reachability-scoped collection@github layer refs resolve in TWO phases: the :vTAG git tag is only the FETCH coordinate (which commit to clone); the layer's own version: field — read AFTER fetch — is the authoritative identity that drives dedup + warn-and-newest-wins.
LayerRef (ov/refs.go) — the single representation of a require: / layer: ref. It stores the ORIGINAL ref string (Raw, with any @repo prefix and :version suffix); .Bare() (the map-key form), .Version() (the pinned git tag — the FETCH coordinate, NOT the identity), and .IsRemote() are DERIVED. A resolved slot carries the qualified sibling key set by qualifyRemoteSiblingDeps after a remote layer is fetched, so ONE list serves both the graph (keys on .Bare()) and the transitive fetch (keys on the immutable .Raw). Layer.Require / Layer.IncludedLayer are []LayerRef — there are no parallel bare/raw arrays.CollectRemoteRefsOpts (ov/refs.go) collects EVERY distinct (repo, git-tag) a bare ref is referenced at — it does NOT collapse to one winning tag and does NOT warn (the git tag is just where to clone from). The ScanAllLayerWithConfigOpts fix-point (ov/layers.go) fetches each (repo, git-tag) (tracking scanned (repo,git-tag,ref) triples), reads each materialization's per-entity version:, accumulates candidates per bare ref, then pickLayerVersion arbitrates: same per-entity version across different git tags → NO warning, the newest git tag wins for freshness (compareSemver); different per-entity versions → warn once (naming both per-entity versions + sources) and the newest per-entity version wins (compareCalVer). Exactly one materialization per bare ref reaches the layer map, so the graph + intermediates are unchanged. A fetched layer with NO version: is a HARD ERROR (no fallback — first-party remotes are backfilled by remote-cache auto-migration, EnsureRepoDownloaded → RunProjectMigrations). pickLayerVersion is the SOLE arbiter for direct AND transitive refs, so a transitive dep can never silently pull a different version of an already-resolved layer. This is why a repo re-tag of an UNCHANGED layer no longer warns — the old resolver compared the repo git tag, which advances on every push.CollectRemoteRefsOpts.collectImage) — collection walks ONLY the enabled root images + the namespaced images reachable via their base:/builder: edges (resolveImageRef + leafName), plus local layers' transitive deps. It does NOT scan every image and kind:local template of every imported namespace (that over-collection pulled unrelated layers pinned at a different tag — e.g. a namespace's ov-cachyos workstation template's chrome — and tripped the version policy). Builder edges ARE followed when an image builds (a namespaced ov.fedora-builder is built as an intermediate and needs its rpmfusion/yay layers); dropping them under-collects ("unknown layer").populateLayerFromYAML, ov/unified.go) — both scanLayer (discovered-layer-dir path) and synthesizeInlineLayer (overthink.yml inline path) call it, so they can't drift. The Has* predicates (HasEnv/HasPorts/HasVolumes/…) are derived methods; only the filesystem-probe caches (HasPixiToml/HasSrcDir/…) stay fields.ov image reconcile (see /ov-build:reconcile) is the operator tool that aligns the on-disk git-tag pins so every reference of a repo fetches one commit, clearing any residual per-entity-version warning.
ImageMetadata alias + label completeness checkCapabilities = ImageMetadata (type alias in ov/capabilities.go). CapabilityLabelMap lists every field with its OCI label home; TestCapabilityLabelCompleteness fails the build if an ImageMetadata field lacks a mapping. This invariant keeps ov deploy from-image reliable: every field deploy code might consult is readable from a pushed image's labels alone, independent of overthink.yml.
K8sDeployTarget (ov/k8s_target.go) sits alongside OCITarget, PodDeployTarget, LocalDeployTarget, VmDeployTarget. Unlike local/VM targets, K8s doesn't consume install plans — it emits Kustomize manifests directly from (Capabilities, DeployImageConfig, ClusterProfile) via GenerateK8sKustomize (ov/k8s_generate.go). The workload-kind heuristic (selectWorkloadKind) translates the generic kind: enum to Deployment/StatefulSet/DaemonSet/Job/CronJob/Pod based on intent + storage presence.
VmDeployTarget (ov/deploy_target_vm.go) executes InstallPlans inside a running VM over SSH. Same IR as LocalDeployTarget, but bash bodies run via ssh guest 'sudo bash -s' through an SSHExecutor (ov/deploy_executor_ssh.go). Ledger writes land on the guest filesystem. The DeployExecutor interface (ov/deploy_executor.go) decouples "how shell commands run" from the target walking logic — ShellExecutor + SSHExecutor are the two implementations.
ov deploy add vm:<name> dispatches through deploy_add_cmd.go::dispatchNode → ResolveTarget → VmUnifiedTarget.Add / .Del (no per-kind dispatch function); deploy_add_cmd_vm.go carries the VM-only helpers (deployNestedPodsInGuest, buildVmReverseRunner, vmNameFromDeployName). Full architecture + preflight flow lives in /ov-internals:vm-deploy-target.
The codebase keeps wire format (YAML keys) and internal names (Go fields/types) in strict symmetry — plural YAML keys get plural Go identifiers, singular get singular. The singular builder: / distro: / init: top-level keys in build.yml and image.yml carry singular Go identifiers: BuilderMap, ImageConfig.Builder, BuilderConfig.Builder, DistroConfig.Distro, InitConfig.Init. The rule: if you change a YAML tag, also rename the Go identifier. Tests enforce this indirectly — struct literals won't compile if they disagree. Note: the OCI label key is grouped under platform.* / builder.* sub-namespaces — see LabelPlatformDistro, LabelPlatformFormats, LabelBuilderUses, LabelBuilderProvides in labels.go. Label wire-names are decoupled from YAML/Go identifiers by design.
default:"withargs" for parent+leaf commandsKong normally treats a struct as either a branch (has child cmd:"" subcommands) OR a leaf (accepts arg:"" positionals and has a Run() method) — not both. When you want both shapes on the same parent command (e.g., ov eval live <image> runs tests AND ov eval cdp … dispatches to a subcommand), tag the default child with default:"withargs". Kong then dispatches to that child when the first token doesn't match a subcommand name, passing positional args/flags through.
Two uses in the codebase:
ov/config_image.go:14-21 — ImageConfigCmd.Setup is the default; ov config <image> routes through ImageConfigSetupCmd while ov config mount|status|… dispatch explicitly.ov/test_cmd.go:22-31 — TestCmd.Run is the default; ov eval live <image> runs declarative tests while ov eval cdp|wl|dbus|vnc … dispatch explicitly.Tradeoff: a subcommand name shadows a positional value with the same text. ov eval cdp always dispatches to the cdp subcommand — if an image is literally named cdp, use the explicit ov eval live cdp form.
LoadConfig must NOT read deploy.ymlOCI labels are written exclusively from image.yml + layer.yml at ov image build / ov image generate time. deploy.yml is deploy-mode state and must never bleed into the baked image. The key guarantee lives in ov/config.go:LoadConfig — it calls LoadConfigRaw only, with no MergeDeployOverlay.
The rule: every build-mode command (anything under ov image …) calls LoadConfig. If you ever re-introduce MergeDeployOverlay inside LoadConfig, you will silently contaminate OCI labels with whatever is in the user's local deploy.yml — exactly the bug that made images bake ports: ["5900:5900","9250:9222"] from a stale deploy.yml entry instead of the image.yml-declared ["5900:5900","9222:9222","9224:9224"].
Deploy-mode commands (ov config, ov start, ov stop, ov update, ov deploy add, ov deploy del, ov shell, ov cmd, ov service, ov vm create, …) read labels via ExtractMetadata and then apply the deploy overlay explicitly via MergeDeployOntoMetadata(meta, dc, instance). This split is load-bearing — never collapse it.
Host-deploy specifics: ov deploy add host is deploy mode (reads both image.yml and deploy.yml), not build mode — despite looking like "install on host, not into an image". The compiler (BuildDeployPlan in install_build.go) is pure and shared with build mode, but the invocation path reads deploy.yml for add_layer: and install_opts: like every other deploy-mode command.
ov image build, pod deploys, and local-target deploys all route through a shared IR. Flow:
Layer + ResolvedImage + HostContext
→ BuildDeployPlan (install_build.go) [pure]
→ InstallPlan (install_plan.go)
→ DeployTarget.Emit
├── OCITarget (build_target_oci.go) → Containerfile text
├── PodDeployTarget (deploy_target_pod.go) → overlay + quadlet
├── LocalDeployTarget (deploy_target_local.go) → local shell execution
├── VmDeployTarget (deploy_target_vm.go) → SSH-wrapped shell in the guest
└── K8sDeployTarget (k8s_target.go) → Kustomize base/overlays tree
Full reference lives in /ov-internals:install-plan — go there before touching any of those files. Supporting Go files (ledger, builder_run, shell_profile, reverse_ops, service_render, deploy_ref, hostdistro, migrate_services_tool) are covered in /ov-internals:local-infra.
The VM path spans the following module topology:
| File | Role |
|---|---|
| ov/vm_spec.go | VmSpec + VmSource discriminated union (cloud_image / bootc) + VmChecksum + VmNetwork + VmSSH + VmKeyInjection |
| ov/cloud_init_types.go | VmCloudInit + VmCloudInitUser/File/Network/Mirrors + VmOvInstall (auto/scp/url/skip state machine) |
| ov/libvirt_schema.go | LibvirtConfig + 30+ sub-types (features, CPU, clock, memory backing, numatune, cputune, devices, seclabel, launch security, resource, sysinfo) |
| ov/libvirt_render.go + libvirt_render_devices.go | RenderDomain pure function + device emission (passt backend, portForward attribute order, virtio-gpu default, SMBIOS credentials) |
| ov/qemu_render.go | RenderQemuArgv for direct-QEMU backend |
| ov/cloud_init_render.go + cloud_init_iso.go | RenderCloudInit + ResolveKeyInjectionChannels + composeUsers (adopt-merge) + WriteSeedISO via xorriso/genisoimage/mkisofs |
| ov/vm_cloud_image.go + http_fetch.go | BuildCloudImage pipeline: fetch URL + sha256 sidecar + resize + seed ISO render |
| ov/ov_install.go | EnsureOvInGuest — strategy state machine for installing the ov binary in the guest |
| ov/ovmf_paths.go | ResolveOvmfPaths (per-distro OVMF_CODE/VARS paths) + EnsurePerVmNvram + ResolveOvmfForSpec (bios-sentinel returning empty strings) |
| ov/libvirt_validate.go | ValidateVmSpec + ValidateLibvirtConfig |
| ov/deploy_executor*.go | DeployExecutor interface + ShellExecutor + SSHExecutor with WaitForSSH + WaitForCloudInit |
| ov/deploy_target_vm.go | VmDeployTarget.Emit |
| ov/deploy_add_cmd_vm.go | VM-only deploy helpers (deployNestedPodsInGuest, buildVmReverseRunner, vmNameFromDeployName); ov deploy add vm:<name> itself dispatches through dispatchNode → ResolveTarget → VmUnifiedTarget.Add |
| ov/vm_create_spec.go + vm_build.go | CLI command wiring for ov vm build/create reading kind: vm entities |
| ov/migrate_vm_spec.go | ov migrate one-shot conversion from legacy image.bootc/image.vm/image.libvirt |
unified.go VM support: "vm" is in the entityKind enum, with a VmDoc loader struct, a mergeVmMap merger, and "vm" in rootShapeKeys (so vm.yml is recognized as a valid entity-shape include file).
Full subsystem references: /ov-internals:vm-spec, /ov-internals:libvirt-renderer, /ov-internals:cloud-init-renderer, /ov-internals:vm-deploy-target, /ov-internals:ovmf, /ov-internals:cutover-policy.
The ov binary self-execs in two distinct directions.
Host → container — the host ov delegates to a container-baked ov via exec … ov <subcommand>. Three sites today:
ov/notify.go:20 — best-effort desktop notification via in-container ov eval dbus notify.ov/dbus.go:195 — strict in-container ov eval dbus notify with gdbus fallback.ov/dbus.go:229 — generic D-Bus call via in-container ov eval dbus call.Host → host — the test runner spawns the same host ov binary as a subprocess to execute cdp/wl/dbus/vnc declarative verbs:
ov/testrun_ov_verbs.go — the runOvVerb dispatcher builds ov eval <verb> <method> <image> [args…] argv and runs it via exec.CommandContext, feeding stdout/stderr through the existing matcher pipeline. findOvBinary() prefers os.Executable() so tests invoke the same build that collected them, falling back to $PATH.The rule: whenever you rename a subcommand path crossed by any of these self-exec sites, edit the host-side invocation strings AND plan a coordinated rebuild of every image that bakes the ov layer (affected images: grep image.yml for - ov$). For host→host sites the rebuild doesn't matter — it's the same binary — but the method-name allowlists in testrun_ov_verbs.go must stay in lockstep with the actual ov eval cdp|wl|dbus|vnc subcommand tree.
| Action | Command | Description |
|--------|---------|-------------|
| Build | task build:ov | Compile to bin/ov and install as Arch package |
| Install | task build:install | Install ov as Arch package (uses pre-built binary) |
| Run tests | cd ov && go test ./... | Run all tests |
| Run specific test | cd ov && go test -run TestName ./... | Run single test |
| Vet | cd ov && go vet ./... | Static analysis |
| Format | cd ov && gofmt -w . | Format code |
project/
├── bin/ov # Built by `task build:ov` (gitignored)
├── ov/ # Go module (go 1.25.3, kong CLI, go-containerregistry)
├── build.yml # Unified build-time config: distro: bootstrap + formats,
│ # builder: multi-stage defs, init: supervisord/systemd
│ # (referenced via image.yml format_config)
├── .build/ # Generated Containerfiles (gitignored)
├── image.yml # Image definitions
├── Taskfile.yml # Bootstrap tasks only
├── taskfiles/ # Build.yml, Setup.yml
├── layers/<name>/ # Layer directories (160 layers)
├── plugins/ # Git submodule (overthink-plugins, 5 plugins, 244 skills)
└── templates/ # supervisord.header.conf (referenced by init.supervisord.header_file)
Submodule convention: plugins/ is a submodule rooted at the
overthink-plugins repo. Clone with --recurse-submodules or run
git submodule update --init after a plain clone. See
/ov-internals:skills for the skill-authoring and sync conventions.
| File | Purpose |
|------|---------|
| main.go | CLI entry point (Kong framework). CLI struct carries two global path fields: Dir (-C / --dir / env OV_PROJECT_DIR) and Repo (--repo / env OV_PROJECT_REPO). When Repo is set, main() resolves it via ResolveProjectRepo and assigns the cache path back into Dir; when Dir is non-empty (after that resolution), main() calls os.Chdir(Dir) before ctx.Run() — one-line intervention that propagates to every os.Getwd() call site throughout build-mode commands without requiring per-command plumbing. --repo and --dir are mutually exclusive (fast-fail). Covered by TestOvDir_FlagChdir, TestOvDir_Errors, TestOvRepo_FlagChdir, TestOvRepo_DirConflict, TestOvRepo_DefaultExpansion in main_dir_test.go + main_repo_test.go. Load-bearing for ov mcp serve inside a container where cwd resolves to /workspace (the ov-mcp layer default) — either bind-mounted with the project, or empty in which case bootstrapProject() auto-falls back to the upstream repo. |
| main_repo.go | --repo resolver. DefaultProjectRepo = "github.com/overthinkos/overthink". normalizeRepoSpec(spec) handles four spec shapes: "default" literal, bare owner/repo (auto-prefix github.com/ when first segment has no dot), bare owner/repo@ref, host-qualified host.tld/owner/repo[@ref]. ResolveProjectRepo(spec) reuses EnsureRepoDownloaded from refs.go so the project-repo cache shares ~/.cache/ov/repos/ (override OV_REPO_CACHE) with the existing remote-layer cache. Empty version triggers GitDefaultBranch resolution. |
| config.go | image.yml parsing, inheritance resolution. BuildFormats type. Distro field. ResolvedImage.Tags (union). SupportsTag(), SupportsBuild() methods |
| format_config.go | DistroConfig (with per-distro Formats), BuilderConfig types. BuildFile loader struct matches the three top-level sections of build.yml (distro:, builder:, init:). LoadBuildConfigForImage resolves a single format_config: build.yml ref and splits it into DistroConfig / BuilderConfig / InitConfig views. Per-image config resolution with remote ref support |
| format_template.go | Go text/template rendering engine. Template helpers: cacheMounts, cacheMountsOwned, quote, default, splitFirst, replace, join. InstallContext, BuildStageContext types |
| layers.go | Layer scanning, file detection, parseLayerYAML(). LayerYAML struct with Vars + Tasks fields. Task struct + Kind() method (exactly-one-verb). PackageSection generic format sections. TagSections for distro overrides. |
| tasks.go | All task emission logic — per-verb emitters (emitMkdirBatch, emitCopy, emitWrite, emitLinkBatch, emitDownload, emitSetcapBatch, emitCmd, emitBuild), emitTasks orchestrator, stageInlineContent (content-addressed), resolveUserSpec, taskSubstPath, taskUnresolvedRefs. Adjacent-coalescing (taskCoalescesWith). Shell-quoting helpers: shellSingleQuote(s) for standard '...' escaping (used by LABEL values + emitDownload env entries) and shellAnsiQuote(s) for bash ANSI-C $'...' quoting (used by emitCmd so multi-line script bodies survive podman's line-oriented Dockerfile parser). emitDownload env rule: uses export VAR=val; (semicolon-terminated) not VAR=val cmd, because bash expands ${VAR} in URL arguments before the cmd-prefix environment is assembled. ~430 lines, single home for install-task codegen. |
| generate.go | Containerfile generation. writeLayerSteps orchestrates per-layer: packages → emitTasks (from tasks.go) → builders → USER reset. Config-driven format install, builder stages, and bootstrap from build.yml (distro: + builder: sections). writeLabels is called at the END of the final stage (after the final USER directive) — the volatile LabelEval value would otherwise invalidate every downstream RUN/COPY on a test edit; with LABELs-at-end, only the LABEL steps themselves re-emit (cache preserves all install work). writeJSONLabel routes every JSON label value through shellSingleQuote so embedded ' chars in test commands (awk '{print $1}') don't break podman's key=value LABEL parser. |
| validate.go | All validation rules. validateLayerTasks enforces exactly-one-verb, per-verb required modifiers, vars key rules, path/mode/caps format, ${VAR} resolution checks. Format/builder validation against config definitions (not hardcoded maps) |
| version.go | CalVer computation |
| scaffold.go | new layer scaffolding (single-layer dir creation with stub layer.yml) |
| scaffold_project.go | new project scaffolding + image.yml mutation helpers (ScaffoldProject, AddImage, AddLayerToImage, RemoveLayerFromImage). All YAML round-trips go through the yaml.v3 Node API so comments + key order are preserved. Tested in scaffold_project_test.go. |
| scaffold_cmds.go | All Kong command structs for the MCP-first authoring surface: NewProjectCmd, NewImageCmd, ImageSetCmd, ImageAddLayerCmd, ImageRmLayerCmd, ImageFetchCmd, ImageRefreshCmd, ImageWriteCmd, ImageCatCmd, LayerCmd (with Set + four add-{rpm,deb,pac,aur} aliases), LayerAddPkgCmd. Houses resolveProjectFile() — the path-traversal guard for image write / image cat. Houses detectPkgSection(os.Args) — the workaround for Kong not exposing "which alias triggered me" to a shared struct. Houses appendLayerPackages() with the scaffold's null-package: → sequence upgrade. |
| yaml_setter.go | SetByDotPath(path, dotpath, valueYAML) — generic comment-preserving YAML setter used by ov image set and ov layer set. Walks *yaml.Node trees; creates intermediate mappings on demand; rejects descent into scalars. Tested in yaml_setter_test.go (comment preservation, list values, intermediate-mapping creation, scalar-descent error path). |
| File | Purpose |
|------|---------|
| graph.go | Topological sort (layers + images), ResolveImageOrder() |
| intermediates.go | Auto-intermediate image computation (trie analysis). createIntermediate() inherits Distro and BuildFormats from the parent image first, falling back to cfg.Defaults.* only when the parent is external or empty. Inverting this (defaults winning over the explicit parent) mis-tags every arch-rooted intermediate as build: [rpm], so every layer section keyed on pac: emits an empty RUN step (symptom: arch-ssh-client ships without direnv / gnupg / openssh). Regression guard: TestComputeIntermediates_InheritDistroFromParent uses defaults.Build=[rpm] but expects arch-rooted intermediates to come out [pac]. |
| File | Purpose |
|------|---------|
| build.go | build command (sequential image building, retry logic) |
| merge.go | merge command (post-build layer merging) |
| shell.go | shell command (execs engine run) |
| start.go | start/stop commands |
| status.go | status command (structured table/detail view, live tool probing, --json) |
| commands.go | enable/disable/logs/update/remove |
| service.go | service command (init system service management inside containers) |
| seed.go | seed command (bind-backed volume data seeding) |
| hooks.go | Lifecycle hooks (post_enable, pre_remove) collection and execution |
| remote_image.go | Remote image ref resolution, pull-or-build |
| vm.go | VM lifecycle: create, start, stop, destroy, list, console, ssh |
| vm_build.go | VM disk image builds (qcow2, raw via bootc install) |
| vm_libvirt.go | Libvirt backend: VM operations via session-level libvirt |
| vm_qemu.go | QEMU backend: direct VM operations via qemu-system |
| smbios_credentials.go | SSH key injection via SMBIOS/systemd credentials at VM boot |
| libvirt.go | Libvirt XML snippet collection and injection |
| browser.go | Browser automation commands (open, list, close, text, html, url, screenshot, click, type, eval, wait, cdp) |
| browser_cdp.go | CDPClient -- lightweight Chrome DevTools Protocol WebSocket client (golang.org/x/net/websocket) |
| cdp.go | CDP commands + CdpCmd struct. cdpGetWindowOffset, cdpDispatchKeyEvent, deepQueryJS |
| cdp_spa.go | SPA-aware remote desktop interaction: CdpSpaCmd (click, type, key, key-combo, mouse, status). spaDetect, spaEnsureFocus, spaKeyMap, spaModifierMap, coordinate scaling via --scale |
| browser_test.go | Browser command and CDP client tests |
| wl.go | Wayland desktop commands (screenshot, click, type, key, mouse, status, windows, focus). --from-x11 flag + FindX11WindowGeometry() for XWayland coordinate translation |
| vnc.go | VNC desktop commands (screenshot, click, type, key, mouse, status, passwd, rfb). --from-x11 flag for XWayland coordinate translation |
| sway.go | Sway compositor commands. swayNode has Focused/FullscreenMode fields; searchSwayNode prefers focused/fullscreen nodes; XWayland class matching via swayWindowProperties |
| File | Purpose |
|------|---------|
| engine.go | Docker/Podman abstraction, ResolveImageEngineForDeploy() |
| registry.go | Remote image inspection (go-containerregistry) |
| transfer.go | Cross-engine image transfer |
| runtime_config.go | ~/.config/ov/config.yml, secret_backend key, credential maps |
| network.go | Shared "ov" container network management |
| machine.go | Podman machine management (rootful VM builds) |
Key types — user_policy + exclude_distros architecture:
| Type / Field | File | Purpose |
|---|---|---|
| DistroDef.BaseUser *BaseUserDef | format_config.go | Pointer to a declared pre-existing uid-1000 account in the upstream base image. Nil when not declared (fedora/arch/debian); set for ubuntu ({ubuntu, 1000, 1000, /home/ubuntu}). Inherited via resolveInherits so a child distro with no base_user: inherits the parent's |
| BaseUserDef | format_config.go | Four required fields: Name, UID, GID, Home. Parsed from build.yml distro.<name>.base_user: |
| ImageConfig.UserPolicy string | config.go:130 | YAML field user_policy. Values: auto (default) / adopt / create. Drives the reconciliation switch in ResolveImage |
| ResolvedImage.UserAdopted bool | config.go:194 | True when the policy reconciliation adopted a distro's BaseUser (User/UID/GID/Home overwritten). Consumed by writeBootstrap in generate.go to skip the useradd step |
| Check.ExcludeDistros []string | testspec.go | Per-test filter — test runner in testrun.go:runOne skips the check when any of the image's distro tags intersects with this list. Reason reported as excluded on distro "<tag>" |
| TagPkgConfig.Raw map[string]any | layers.go | Captures the full YAML map for a tag section (e.g. debian:13:), not just package:. Enables repos:, keys:, options: inside tag sections. Read by the generator's install-template emission path |
Policy reconciliation flow (ov/config.go:ResolveImage, after distroDef loaded):
policy := img.UserPolicy
if policy == "" { policy = c.Defaults.UserPolicy }
if policy == "" { policy = "auto" }
baseUser := (*BaseUserDef)(nil)
if resolved.DistroDef != nil { baseUser = resolved.DistroDef.BaseUser }
userExplicitlySet := img.User != "" || c.Defaults.User != ""
switch policy {
case "adopt":
if baseUser == nil { return nil, fmt.Errorf(...) }
// overwrite User/UID/GID/Home
resolved.UserAdopted = true
case "auto":
if baseUser != nil && !userExplicitlySet {
// overwrite User/UID/GID/Home
resolved.UserAdopted = true
}
case "create":
// no-op
}
See /ov-image:image "user_policy" for the user-facing decision matrix, /ov-build:build "base_user:" for the declarative side, and /ov-build:generate "writeBootstrap" for the consumer side.
| File | Purpose |
|------|---------|
| env.go | ENV merging, path expansion |
| envfile.go | .env file parsing (ParseEnvFile, ParseEnvBytes), runtime env var resolution/merging |
| security.go | Container security config collection, CLI args generation. Merges Mounts from layer security configs |
| labels.go | OCI label constants. LabelEval carries the three-section {layer, image, deploy} test manifest; ImageMetadata.Tests *LabelEvalSet is populated by ExtractMetadata when present |
| volumes.go | Named volume collection/mounting |
| alias.go | Command aliases (wrapper scripts) |
| deploy.go | Per-deployment config overlay, DeployVolumeConfig, ResolveVolumeBacking(), saveDeployState(), cleanDeployEntry() (instance-aware provides cleanup) |
| provides.go | Env/MCP provides injection, removeBySource(), removeByExactSource() (instance-specific cleanup), podAwareMCPProvides() |
| enc.go | Encrypted volumes (gocryptfs via systemd-run --scope --user --unit=ov-enc-<image>-<volume>), ResolvedBindMount. -allow_other required for rootless podman keep-id. encUnmount() stops scope units after fusermount. Stale scope retry on mount failure |
| devices.go | Host device auto-detection. DetectedDevices struct with RenderNode field (first /dev/dri/renderD*). appendAutoDetectedEnv() centralizes injection of HSA_OVERRIDE_GFX_VERSION, DRINODE, DRI_NODE — called at 10 sites across config_image.go, start.go, shell.go. Uses appendEnvUnique so user -e flags always override |
| tunnel.go | Tunnel providers (Tailscale, Cloudflare), backend scheme helpers (schemeTarget, tailscaleFlag, isTCPFamily), scheme validation maps (validTailscaleSchemes, validCloudflareSchemes), start/stop for each provider |
| quadlet.go | Quadlet .container file generation, Secret= directives |
| credential_store.go | CredentialStore interface, ResolveCredential(), DefaultCredentialStore(), ConfigMigrateSecretsCmd |
| credential_keyring.go | System keyring backend (go-keyring: GNOME Keyring, KDE Wallet, KeePassXC via FdoSecrets / Secret Service) |
| credential_config.go | Config file credential backend (plaintext fallback for headless) |
| migrate_secrets_kdbx.go | ov migrate — strips residual secret_backend: kdbx + secrets_kdbx_* keys from ~/.config/ov/config.yml |
| secrets.go | Container secret collection from labels, Podman secret provisioning, SecretArgs() |
| secrets_cmd.go | ov secrets CLI commands (list, get, set, delete, import, export — all retargeted to DefaultCredentialStore()) + the gpg subgroup |
| secrets_gpg.go | ov secrets gpg commands (show, env, edit, encrypt, decrypt, set, unset, add-recipient, recipients) |
| File | Purpose |
|------|---------|
| refs.go | Remote ref types, parsing, cache management |
| refs_git.go | Git operations: clone, resolve ref, tag resolution |
Implements the ov eval live / ov eval image commands and the
org.overthinkos.eval OCI label. User-facing authoring, verb catalog,
runtime variables, and deploy.yml overlay rules live in /ov-eval:eval — this
section is the Go-implementation map.
| File | Purpose |
|------|---------|
| evalspec.go | Check struct (20 verb discriminators; Kind() enforces exactly-one). Original 15 built-in verbs (file/port/command/http/package/service/process/dns/user/group/interface/kernel-param/mount/addr/matching) plus 9 live-container verbs (cdp/wl/dbus/vnc/mcp/record/spice/libvirt/k8s) dispatched via evalrun_ov_verbs.go. Status on the http verb is a plain int — not a MatcherList. One expected code per test; no [200, 302] list shorthand. Matcher + MatcherList with custom YAML and JSON unmarshalers for scalar/list/map shorthand — symmetry between layer.yml authoring and hand-crafted OCI labels. LabelEvalSet with {Layer, Image, Deploy} sections. Extended ${NAME[:arg]} regex (evalVarRefPattern) — backward-compatible widening of taskVarRefPattern in tasks.go. No bash-style defaults: ${VAR:-fallback} is unsupported; only ${IDENT}. ExpandEvalVars, EvalVarRefs, IsRuntimeOnlyVar, Check.ExpandVars. |
| evalvars.go | ResolveEvalVarsBuild / ResolveEvalVarsRuntime. InspectContainer is a swappable package-level var (test-friendly pattern matching InspectLabels in labels.go). Maps podman inspect output into HOST_PORT:<N>, VOLUME_PATH:<name>, VOLUME_CONTAINER_PATH:<name>, CONTAINER_IP, CONTAINER_NAME, ENV_<NAME>. |
| evalrun.go | Runner, Executor interface, ContainerExecutor (via podman exec), ImageExecutor (via podman run --rm). EvalStatus/EvalResult types (named to avoid collision with doctor.go). Per-verb dispatch for file/port/command/http. Matcher evaluation: matchOne + matchNumeric (lt/le/gt/ge). validMatcherOps allowlist kept in lockstep with the runner switch by TestMatcher_AllowlistRunnerSync. Output formatters: text, JSON, TAP. |
| evalrun_verbs.go | Dispatch for the remaining verbs: package (rpm/dpkg/pacman), service (supervisorctl + systemctl), process (pgrep), dns (host-side net.LookupIP or in-container getent), user/group (getent passwd/group), interface (ip -o addr show + MTU), kernel-param (sysctl -n), mount (findmnt), addr (host-side net.DialTimeout or in-container nc -z), matching (pure in-process value matching). resolvePackageName(c, distros) implements the distro-aware package-map: when Check.PackageMap is non-empty, the first entry in Runner.Distros that matches a key wins; otherwise Check.Package is used as-is. Covered by TestResolvePackageName (6 sub-cases including empty-map fallback, first-matching-tag-wins priority, and empty-string-map-value fall-through). Runner.Distros is populated from meta.Distro at both entry points in eval_cmd.go. |
| evalrun_ov_verbs.go | Dispatch for the nine live-container verbs (cdp/wl/dbus/vnc/mcp/record/spice/libvirt/k8s). Hand-enumerated method allowlists (cdpMethods/wlMethods/dbusMethods/vncMethods/mcpMethods etc.) map each method name to its ov eval <verb> <method> subcommand path, required modifier fields, and positional-arg builder. The runOvVerb dispatcher handles skip (RunModeImage → skip with message; empty r.Image → skip), required-modifier enforcement (via checkRequiredFields + isZeroField), subprocess exec via findOvBinary(), matcher pipeline through matchAll, and post-run artifact_min_bytes size assertions for screenshot methods. |
| mcp.go | ov eval mcp … Kong subcommand tree. McpCmd parent + 7 leaves (ping, servers, list-tools, list-resources, list-prompts, call, read). Each leaf resolves the image's mcp_provides metadata via ExtractMetadata, opens a *mcp.ClientSession through the swappable mcpOpenSession helper, and exercises one SDK operation. Output is tab-separated plaintext by default; --json emits the SDK's native result struct. Uses github.com/modelcontextprotocol/go-sdk/mcp v1.5.0. |
| mcp_client.go | SDK wrapper layer: resolveMCPEntry / pickMCPEntry (disambiguator), resolveContainerNameTemplate ({{.ContainerName}} substitution), rewriteMCPURLForHost (container-network hostname → 127.0.0.1:<published-host-port> via the same NetworkSettings.Ports data that mergeRuntimeVars in testvars.go:200-213 reads for HOST_PORT:N), buildMCPTransport (http → StreamableClientTransport, sse → SSEClientTransport), plus formatters (formatTool / formatResource / formatPrompt / extractToolText). The rewriter is the load-bearing piece that keeps MCP URLs reachable from the host without authors having to carry separate "internal" vs "external" URL declarations. |
| mcp_server.go | ov mcp serve — turns the entire ov CLI into an MCP server. McpCmdGroup + McpServeCmd (flags: --listen, --path, --stdio, --read-only, --no-default-repo). buildMcpServer(readOnly) constructs a fresh kong.New(&modelCLI), walks k.Model.Leaves(true) to enumerate every leaf command, and calls server.AddTool(kongLeafToTool(...), makeToolHandler(...)). Result: 190 tools auto-generated from Kong struct tags with zero hand-written schema — --long flags become properties, positionals become required properties, enum:"..." surfaces as JSON-schema enum, default:"..." as default, and every schema has additionalProperties: false (LLM-honest: unknown keys are rejected by the SDK's input validation before the handler runs). The mcpDestructivePaths map flags 63 entries with DestructiveHint: true (including the MCP-first authoring verbs that mutate image.yml / layer.yml / write files); --read-only filters these out at registration time (not runtime gating). bootstrapProject() runs before the server starts: chains through env vars (OV_PROJECT_DIR, OV_PROJECT_REPO) → local image.yml → auto-fallback to overthinkos/overthink; --no-default-repo opts out of the fallback. The env-var check is the only way to detect parent-flag state from a subcommand receiver (Kong does not expose CLI upward). Tool invocation goes through captureAndRun(argv) which redirects os.Stdout / os.Stderr (package-level vars, not fd-level via syscall.Dup2 — see the commentary block for why: the SDK's stdio transport captures os.Stdout by pointer at Connect time, and dup2 would silently route JSON-RPC responses into the tool-output buffer). Transport: mcp.NewStreamableHTTPHandler for HTTP mode, &mcp.StdioTransport{} for stdio. runMu serialises calls because os-level stream redirects are global. Test coverage: mcp_server_test.go (schema presence, destructive-hint annotation, --read-only filter — currently 127 read-only + 63 destructive = 190 — positional/flag schema shape, enum/default surfacing, additionalProperties: false, TestMcpServer_VersionRoundTrip round-trip) + mcp_serve_default_repo_test.go (auto-fallback behaviour, hermetic via OV_REPO_CACHE pre-seeding). |
| main_dir_test.go | Integration tests for the -C / --dir / OV_PROJECT_DIR global: spawns a freshly-compiled ov binary from /tmp with a scratch project, verifies all three flag forms make ov image list images resolve the scratch image.yml. Error cases: missing dir, file-not-dir. |
| local_image.go | resolveLocalImageRef(engine, input) — test-mode-only image resolution that never reads image.yml. Full refs pass through with a LocalImageExists check; short names match against ListLocalImages() output using label-preferred matching (org.overthinkos.image=<name>) with a repo-name trailing-component fallback. Returns ErrImageNotLocal on no-match so FormatCLIError renders the "ov image pull / ov image build" recommendation. Used by EvalImageCmd.Run() to keep ov eval image purely OCI-labels-driven. |
| evalcollect.go | CollectEval(cfg, layers, imageName) *LabelEvalSet walks the base-image chain — mirror of CollectHooks in hooks.go:18-68 — with a visited-image guard so pathological cycles reported by validateImageDAG can't hang the collector. Bucketizes checks into layer/image/deploy by source + scope, stamps Origin for reporting. MergeDeployEval(baked, local) implements id-based replace, append, and {id: X, skip: true} disable semantics. |
| eval_cmd.go | EvalCmd — the top-level ov eval command tree with three primary verbs (Image EvalImageCmd, Live EvalLiveCmd, Run EvalRunCmd) plus 9 live-container probe sub-Cmds (cdp/wl/dbus/vnc/mcp/record/spice/libvirt/k8s) plus the eval-run management subcommands (list-ai/list-recipe/list-score/list/sync-credential/report/scope/last-tag/note/run-local/self-evaluate). ov eval live flow: resolveContainer → containerImageRef → ExtractMetadata → load DeployImageConfig.Eval overlay → MergeDeployEval → ResolveEvalVarsRuntime → populate Runner.Image/Instance (so cdp/wl/dbus/vnc verbs can build CLI invocations) → Runner.Run → format results. ov eval image flow: resolveLocalImageRef (never reads image.yml — see local_image.go) → ExtractMetadata → ResolveEvalVarsBuild → disposable ImageExecutor → run ONLY layer+image sections (deploy-scope skipped — for full-stack live eval use ov eval live <name>). |
| eval_runner_cmd.go | The eval-run management Cmds: EvalRunCmd, EvalRunLocalCmd, EvalListAICmd, EvalListRecipeCmd, EvalListScoreCmd, EvalListRunsCmd, EvalSyncCredCmd, EvalScopeCmd, EvalLastTagCmd, EvalSelfEvalCmd, EvalReportCmd, EvalNoteCmd. The orchestrator's preflight (runWithPhaseResync) restarts the disposable harness sandbox (the score pod: target), syncs credentials, and dispatches ov eval run-local inside it via podman exec. |
| eval_loop.go | The AI iteration loop core: per-iter dispatch, scoring, plateau bookkeeping, watchdog integration, NOTES.md memory, commitIterationBestEffort (where the orphan-bash defense kills issue-52328 deadlock leftovers between iterations), result-file emission. |
| eval_runner_live.go | RunEvalLive — the live scenario-by-scenario probe driver invoked at iter end by the harness scorer. Buckets scenarios by pod:, resolves chains via ResolveDeployChain for dotted paths, dispatches to the right DeployExecutor. Same code path used by ov eval self-evaluate (the AI-side mid-iter sanity check). |
| eval_watchdog.go | ProgressWatchdog — per-iteration scoring-progress monitor. Every progress_check_interval (default 5m), runs RunEvalLive against in-scope scenarios, records a WatchdogSample, emits a harness: progress [phase X/N iter Y] elapsed Nm — current score A/B stderr line. Cancels the AI runner's context if progress_no_improvement_timeout (default 30m) of zero score delta passes. |
| migrate_eval.go | ov migrate — strict forward-only migrator from the legacy harness.yml shape to eval.yml (file/dir/labels/tokens/env renames, tests:→eval: rewriting in layer.yml/image.yml/deploy.yml/overthink.yml, well-known bench/fixture project-data renames). Idempotent. NO chain-aware handling of legacy benchmark: blocks. |
| validate_eval.go | validateEval(cfg, layers, errs) hooked into Validate in validate.go. Enforces: exactly-one-verb per Check, attribute types, port range (1-65535), time.Duration parse on timeout, scope ∈ {build,deploy}, build-scope checks can't reference runtime-only variables (via IsRuntimeOnlyVar), id: uniqueness per section (including cross-layer collisions via validateCollectedIDUniqueness → CollectEval), matcher-op allowlist (kept in lockstep with matchOne), per-verb method-allowlist and required-modifier checks for cdp/wl/dbus/vnc/mcp (via validateOvVerb — deploy-scope-only enforcement, method validation against cdpMethods/wlMethods/dbusMethods/vncMethods/mcpMethods maps in evalrun_ov_verbs.go). |
Related skill: /ov-eval:eval is the authoring-facing reference.
kong (CLI), go-containerregistry (OCI), go-keyring (Secret Service API)ov/go.modmain.goRun() method*_test.gocd ov && go test ./... && go build -o ../bin/ov .Add to ov/validate.go. All validation rules are centralized there.
# Generate Containerfiles without building
bin/ov image generate
# Inspect generated output
cat .build/<image>/Containerfile
# Validate configuration
bin/ov image validate
# Inspect resolved image config
bin/ov image inspect <image>
ov image build auto-generates intermediate images (e.g., ghcr.io/overthinkos/fedora-ov-2-dbus-nodejs) that bundle the ov layer plus common layers for cache reuse across many downstream images. These intermediates are aggressively podman-cached. Updating layers/ov/bin/ov does invalidate the COPY step inside the intermediate, but if the intermediate tag already exists locally, ov image build may reuse it without re-running the build chain. To force a fresh binary propagation after a manual bin/ov update:
podman rmi 'ghcr.io/overthinkos/fedora-ov-2*' 2>/dev/null || true
ov image build <image>
This also interacts with the dual-path gotcha documented in /ov-tools:ov: bin/ov (repo-root, used by host-side invocations) and layers/ov/bin/ov (what the ov layer actually copies into images) must stay in sync. The canonical task build:ov path does both; a manual go build -o bin/ov ./ov needs an explicit cp bin/ov layers/ov/bin/ov follow-up.
These are hard-won lessons that shape the Go-side architecture. They're not obvious from reading the source cold; skim this list before making structural changes.
Top-level flags and subcommand flags share one global namespace. Declaring Repo on both CLI (ov/main.go) and McpServeCmd (ov/mcp_server.go) panics with duplicate flag --repo at Kong parse time. Resolution: drop the subcommand flag entirely; users write ov --repo … mcp serve. Only keep subcommand flags when they have no top-level twin (e.g. --no-default-repo on McpServeCmd has no collision and stays).
From inside a McpServeCmd.Run() receiver, you can't reach the parent CLI struct. To detect "did the user set the top-level --dir / --repo" at command-run time, read os.Getenv("OV_PROJECT_DIR") / os.Getenv("OV_PROJECT_REPO"). Kong populates env vars from flags before main() runs, so the env is a reliable proxy whether the user passed the flag or the env var. Implementation: McpServeCmd.bootstrapProject() at ov/mcp_server.go.
detectPkgSection(os.Args) — sharing one struct across four Kong aliasesKong dispatches four distinct subcommand aliases (add-rpm, add-deb, add-pac, add-aur) to the same LayerAddPkgCmd struct. Kong does not expose "which alias triggered me" to the struct's Run() method. Workaround: derive the section name from os.Args (ov/scaffold_cmds.go's detectPkgSection). Ugly but contained — the alternative is four almost-identical structs + four dispatchers.
yaml.v3 Node API is the single reason edits preserve commentsUnmarshal-to-value + re-marshal scrambles comments, key order, and node styles. Every image.yml / layer.yml editor in the authoring surface (SetByDotPath in ov/yaml_setter.go; AddImage / AddLayerToImage / RemoveLayerFromImage in ov/scaffold_project.go; appendLayerPackages in ov/scaffold_cmds.go) navigates *yaml.Node trees directly and only serializes with yaml.Marshal(root) at the very end. Tests (ov/yaml_setter_test.go, ov/scaffold_project_test.go) explicitly verify that leading file comments, sibling keys, and per-key inline comments all survive round trips.
package: null)The layer scaffold writes rpm:\n packages:\n # Add RPM packages here\n — the value of package: parses as scalar-null, not a sequence. Naively calling layersNode.Content = append(...) silently no-ops. appendLayerPackages (ov/scaffold_cmds.go) checks pkgsNode.Kind != yaml.SequenceNode and upgrades in place (Kind = yaml.SequenceNode; Tag = "!!seq"; Value = ""; Content = nil). This preserves the key+comment association on serialization. Any other "upgrade a null scalar to a collection" path needs the same pattern.
image write / image cat escape hatchresolveProjectFile(projectDir, relPath) in ov/scaffold_cmds.go is the single safety boundary for agent-driven file writes. It rejects absolute paths, calls filepath.Clean, then uses filepath.Rel + a prefix check to confirm the result stays inside the project root. Any future "free-form file read/write" verb must go through the same helper.
ov/main.go resolves the project dir in two steps: --repo resolves to a cache path first (ov/main_repo.go calls ResolveProjectRepo → EnsureRepoDownloaded), then falls through into the os.Chdir(cli.Dir) block. The two paths are mutually exclusive (fast-fail if both are set). Downstream code just reads os.Getwd() — no per-command plumbing. Tested in ov/main_repo_test.go (hermetic via OV_REPO_CACHE pre-seeding) + ov/mcp_serve_default_repo_test.go.
foo.go -> foo_test.go)./ov-internals:generate-source — Understanding generated Containerfiles + deep dive on the task emission pipeline (ov/tasks.go)./ov-image:layer — Canonical author-facing reference for the task verb catalog that ov/tasks.go implements./ov-build:validate — Validation rules and error handling (validateLayerTasks in ov/validate.go)./ov-build:build — Using the built CLI./ov-eval:eval — Author-facing reference for the declarative-testing feature that testspec.go / testvars.go / testrun.go / testrun_verbs.go / testrun_ov_verbs.go / testcollect.go / test_cmd.go / local_image.go / validate_tests.go / mcp.go / mcp_client.go implement./ov-build:ov-mcp-cmd — Author-facing reference for both (a) the ov eval mcp client verb (method catalog, URL-rewrite behavior, port-publishing gotcha, transport dispatch — pair with the file table's mcp.go + mcp_client.go rows above) and (b) the ov mcp serve server (190 tools auto-generated from Kong reflection including the MCP-first authoring surface, destructive-hint + --read-only filter, Streamable-HTTP + stdio transports, auto-fallback to overthinkos/overthink — pair with mcp_server.go + main_repo.go + scaffold_cmds.go + scaffold_project.go + yaml_setter.go above)./ov-coder:ov-mcp — The layer that deploys ov mcp serve inside a container: bind-mount volume NAME project at the container PATH /workspace, OV_PROJECT_DIR=/workspace so build-mode MCP tools (image.list.images, image.inspect, etc.) reach image.yml from outside the project checkout — or auto-fall back to overthinkos/overthink when /workspace is empty (the fallback fires on absence of image.yml, not absence of OV_PROJECT_DIR)./ov-eval:cdp, /ov-eval:wl, /ov-eval:dbus, /ov-eval:vnc — the four sibling live-container verbs.ov/ directory (~79 source + ~55 test .go files).MUST be invoked before reading or modifying Go source files. Invoke this skill BEFORE launching Explore agents on ov/ code.
/ov-eval:eval 10 standards)Changes that touch this verb's output must reach a healthy deployment on a target explicitly marked disposable: true (see /ov-internals:disposable). Use ov update <name> to destroy + rebuild unattended on any disposable target. Never experiment on a non-disposable deploy — set up a disposable one first with ov deploy add <name> <ref> --disposable or mark a VM under vm: in vm.yml.
After committing the source-level fix, ov update the disposable target ONCE MORE from clean and re-run the full verification. A fix that passes only on a hand-patched target is not a real fix — it's a regression waiting for the next unrelated rebuild. Paste BOTH the exploratory-pass output and the fresh-rebuild-pass output into the conversation.
Unit tests + a clean compile are necessary but not sufficient. See CLAUDE.md R1–R10.
development
Claude Code multi-agent support in Overthink — sub-agents, dynamic workflows, and agent teams, and how each drives the existing `ov eval` disposable beds to test and verify. MUST be invoked before authoring or invoking an ov sub-agent / dynamic workflow / agent team, wiring agent-lifecycle hooks, or asking "which primitive should drive the R10 beds?".
tools
Mounts a virtiofs share tagged `workspace` at /workspace inside a VM guest via a systemd .mount unit. Use when a kind:vm entity shares a host directory into the guest and you need it auto-mounted (and re-mounted at every boot).
development
MUST be invoked before any work involving: the `kind: android` schema kind, a `target: android` deploy, the `apk:` layer package format (installing Android apps declaratively), AndroidDeployTarget, an in-pod emulator OR a remote/physical adb-endpoint device, or nested `pod → android` deployment. The first-class Android device + app surface that sits above `ov eval adb`/`appium`.
tools
Use when committing, branching, pushing, merging, tagging, creating PRs, or approving/merging PRs with gh — the feat/-branch, R10-gated, never-force-push landing workflow across the main repo + the plugins submodule + image/<distro> submodules. Covers sync-to-upstream, branch/worktree pruning, the fork+PR path for contributors without write access, and cross-repo @github landing order.