Modules and imports
A Kinmokusei file is a module with its own declaration scope. File layout controls name visibility explicitly; directory proximity never creates an implicit shared namespace.
One file, one scope
Consider two files:
application/
├── main.km
└── users.kmDeclarations in users.km are not visible in main.km until imported. Passing both files as compiler roots does not merge their source scopes.
Relative imports
Import selected declarations with braces:
import { User, findUser } from "./users";The path resolves relative to the importing file. The .km extension may be omitted or written explicitly. The list cannot be empty, repeat a local binding, or request a declaration the target does not contain or export. Different aliases may select the same declaration.
Every imported name becomes one binding in the caller's module scope. It may refer to a function, class, struct, interface, enum, defined type, alias, or top-level value supported by the compiler.
Imports are selective
If users.km declares both findUser and normalizeID, this import exposes only findUser:
import { User, findUser } from "./users";
function load(id: string): User | null {
return findUser(id);
}Calling normalizeID without importing it is an undefined-name diagnostic. This controls which bindings the caller sees, not whether another caller may import the helper. Use explicit source exports to make helpers private.
Local import aliases
Use as to choose a file-local name for a selected source or Go declaration. This source module exports a mutable value, a generic class and a function:
export let count: int = 0
export class Box<T> {
constructor(public value: T) {}
}
export function bump(): int {
count++
return count
}The caller can rename each selection, including selecting one declaration twice:
import { count as first, count as second, Box as Container, bump as increment } from "./import-aliases-library"
import go { Println as print, Sprint as render } from "fmt"
import go { Duration as Span, Second as unit } from "time"
const main = () => {
first = 40
const next = increment()
const box: Container<int> = new Container<int>(next)
print(second, &first == &second, box.value, render(Span(2) * unit))
}It prints 41 true 41 2s. first and second are two names for the same mutable storage, not copied values; their addresses compare equal. Container retains the generic class's identity, and Span remains Go's time.Duration. Renaming a function does not introduce a wrapper or change its signature.
Only the local name is introduced: Println as print binds print, not Println. Using the original name without a separate import is an error:
import go { Println as print } from "fmt"
const main = () => {
Println("Only print was imported")
}Local import names cannot collide with another module-level binding or reserved compiler names/types, and _ is not an import alias. Local variables and parameters can shadow imported values normally. To publish a source alias, select it explicitly with export { localName }; imports alone do not re-export. Editor rename keeps the local alias separate from the selected public name.
This is different from a manifest [imports] alias: as renames a declaration in one file, while [imports] shortens a package path throughout the importing project. See external packages.
Explicit source exports
A module can choose its public declarations. Prefix a named top-level declaration with export, or select local declarations in a list:
export class Box {
constructor(public value: int) {}
}
export const offset = 2
function normalize(value: int): int {
return value + offset
}
function makeBox(value: int): Box {
return new Box(normalize(value))
}
export { makeBox }The list can appear before or after the declarations. Functions, classes, structs, interfaces, constraints, enums, defined types, aliases, and const/let bindings all support declaration exports. Export lists may select local declarations or explicitly imported Kinmokusei bindings, and each name can be exported only once. Lists allow a trailing comma and follow the ordinary semicolon-omission rules.
Once a file contains any source export, only its selected declarations can be imported. Its functions and methods can still use its private helpers. Write export {} to keep every declaration private to that source module.
For compatibility, a file with no source exports keeps the earlier behavior: all its top-level declarations can be selected by an import. Adding the first source export therefore changes that file's public surface; list every name its callers still need. This choice is per file, including when compiling several root files together. export c(...) alone does not change source visibility.
A caller can use the checked example above like this:
import { Box, makeBox } from "./source-exports-library"
import go { Println } from "fmt"
function main(): void {
const box: Box = makeBox(40)
Println(box.value)
}Running it prints 42. Editor definition/hover, references, and rename include named export lists and their imported uses.
Named re-exports
A public entry module can gather selected names from other modules:
export { Box } from "./source-exports-library"
import { makeBox } from "./source-exports-library"
export { makeBox }export { Box } from "./source-exports-library" makes Box available to callers without introducing Box into this module's local scope. Import a name first when this module also needs to use it. Both forms retain the original function, type, or variable: a re-export does not copy mutable state or wrap a function.
import { Box, makeBox } from "./named-reexports-library"
import go { Println } from "fmt"
function main(): void {
const box: Box = makeBox(40)
Println(box.value)
}Running this prints 42. Re-export chains and multiple paths to the same module retain declaration identity. Import and export-from dependencies are visited in source order, with each module initialized once. Cycles remain errors, and re-exporting a private or nonexistent name is rejected:
export { normalize } from "../snippets/source-exports-library"Imports alone are not re-exported. Each public name must be selected explicitly; two export declarations cannot publish the same name, even from the same origin.
Export aliases
Use as to choose a public name independently of the implementation name:
export { Box as Container, makeBox as createBox } from "./source-exports-library"
import { offset } from "./source-exports-library"
export { offset as baseOffset, offset as initialOffset }The same syntax works for local declarations: export { local as publicName }. An export alias creates a public name, not a new local variable or type. It keeps the original declaration's identity; two aliases of one mutable variable share the same storage, and two aliases of one defined type remain the same type.
import { Container, createBox } from "./export-aliases-library"
import go { Println } from "fmt"
function main(): void {
const box: Container = createBox(40)
Println(box.value)
}This prints 42. Callers import the selected public names; selecting an alias does not also make the original name available. Public names must be unique:
const first = 1
const second = 2
export { first as value, second as value }Editor rename treats both sides of as independently. Renaming the implementation updates the left side without changing the public name. Renaming a public alias updates uses of that name in the analyzed dependency graph, stopping at the next explicit alias boundary. Definition on an imported alias leads to its export clause; the left side leads to the selected declaration or upstream alias.
Aliases do not rename generated Go declarations or C ABI symbols.
Source imports versus Go exports
Relative Kinmokusei imports select an available declaration by its written name, regardless of whether that name begins with a lower- or uppercase letter. Source exports control availability; named imports select the caller's bindings.
An emitted Go package follows Go's export rule instead: a top-level function, type, or value whose written name begins with an uppercase Unicode letter is visible to external Go packages. For example, function Add(...) emits exported Add, while function add(...) remains package-local. Class/struct member visibility is separate—write public for a member that belongs to the public source and generated-Go contract.
Choose uppercase top-level names only for the API you intend Go consumers to use, then test that package from an external or same-package Go test. The testing guide provides a checked example.
Source export does not change the emitted Go name: export function add(...) is available to Kinmokusei importers but stays package-local in Go. Conversely, an uppercase declaration remains Go-exported even if a source export list omits it. Source-module privacy is not a separate access barrier for Go consumers.
Imports are not transitive
Suppose repository.km imports connect from database.km, and main.km imports load from repository.km. main.km receives load; it does not receive connect.
database.km -- connect
↑
repository.km -- imports connect, declares load
↑
main.km -- imports load onlyThis prevents distant implementation details from leaking whenever an intermediate module changes its own imports.
Duplicate private spellings across modules
Two dependency modules may each declare a local Helper or normalize without colliding. The compiler links declarations by module identity and generates stable private names independent of the checkout's absolute path.
Import binding collisions occur when the caller selects two declarations under the same local name, conflicts with its own top-level declaration/Go alias, or uses a reserved type/compiler name. Names also undergo generated-Go collision checks; a diagnostic asks for a rename when a reference cannot be preserved.
Import cycles
Relative imports form a directed module graph. Cycles are rejected:
first.km imports second.km
second.km imports first.kmBreak a cycle by moving the shared contract/value into a third lower-level module, or by reversing dependency through an interface/callback supplied by the caller.
Go package imports
Use a package alias and literal Go import path:
import go fmt from "fmt";
import go http from "net/http";
function main(): void {
fmt.Println(http.StatusOK);
}The alias is the qualifier for exported Go declarations. Kinmokusei loads the package for the locked/effective target and retains its original named types, functions, constants, variables, methods, interfaces, and generic information where supported.
An alias cannot use a built-in type or an existing module binding.
Named Go imports
Selected Go exports can also be used without a source qualifier:
import go { Println } from "fmt"
import go { Compare } from "cmp"
import go { Duration, Second } from "time"
function main(): void {
const delay: Duration = Duration(2) * Second
Println(delay, Compare(3, 1))
}The names must be exported Go functions, types, constants, variables, or supported compiler-recognized Go built-ins. Their original signatures, generic constraints, constant values, and storage identities are retained. Generated Go uses ordinary qualified references, not copied variables, dot imports, or wrapper functions. Package initialization is unchanged.
Named lists may have trailing commas and may accompany a package-qualified import of the same path. Each name is visible only in its importing file; duplicate import bindings and conflicts with module declarations are rejected. Local bindings can shadow an imported value. Type parameters can shadow an imported type. Go package variables remain assignable and addressable; Go constants and function declarations do not become assignable.
The checked example is website/snippets/named-go-imports.km in the repository. Editor navigation leads to the import binding, and Go export names are read-only for rename. Dependency locking and unsafe interop policies apply identically to both import forms.
Compiler-managed standard modules
Reserved kinmokusei/* modules use the named import form:
import { App, Context, fetch } from "kinmokusei/http";They are embedded, compiler-versioned Kinmokusei source modules—not remote packages. Unknown, differently cased, or noncanonical reserved paths are rejected. The implemented module is kinmokusei/http.
External Kinmokusei packages
Use ordinary named imports for a locked source package, not import go:
import { greet } from "example.com/greeting"
import { format } from "example.com/greeting/format"The package's [package].entry selects its root source file; [exports] selects the public submodule files. Source export statements within each file then select the public declarations. These are distinct levels of visibility: exporting a function does not publish an arbitrary source file as a submodule.
keika deps add locks the dependency and registers a short import path, such as greeting. Canonical and short paths retain the same declaration/type/storage identity. Each package must declare its own direct dependencies; a transitive package is not automatically importable by its consumer. Imports and re-exports are resolved using the importing package's own aliases and dependency edges.
The external package guide provides tagged distribution, local replacement and offline restoration workflows.
External Go modules
An import does not download a dependency. Project code first records and locks an exact module version:
keika install --go-module github.com/google/uuid@v1.6.0Then source imports a package from that locked module:
import go uuid from "github.com/google/uuid";Normal check, build, run, LSP, and emit operations validate and consume locked dependency state without modifying it. This separates source compilation from network-dependent graph resolution.
Project root and target
kinmokusei.toml establishes project identity, Go module path/version, optional target, unsafe policy, dependencies, and replacements. kinmokusei.lock records the canonical resolved graph and target-sensitive module state.
Relative source paths resolve from their importing files; external source paths resolve through the locked package graph and its explicit submodule mappings. Go package availability may depend on the locked GOOS, GOARCH, build tags, CGO mode, and Go version. A package that works on the host can still be unavailable for a cross target.
Organizing a medium project
A practical layout groups by responsibility while preserving explicit imports:
service/
├── kinmokusei.toml
├── kinmokusei.lock
├── main.km
├── domain/
│ ├── user.km
│ └── account.km
├── application/
│ └── registration.km
└── transport/
└── http.kmmain.km should assemble concrete implementations. Domain modules can expose defined types, value structs, interfaces, and validation functions without importing transport details. Application modules depend on those contracts; transport modules translate external HTTP/Go values at the edge.
Diagnosing module failures
| Diagnostic family | Check |
|---|---|
| Cannot load relative module | Path spelling, location, .km, import cycle |
| Module does not declare a name | Named import list and target declaration spelling |
| Module does not export a name | Source export list, public alias spelling, re-export chain |
| Duplicate import binding | Relative names, Go aliases, and local declarations |
| Source package/submodule unavailable | Direct dependency, automatic path alias, package entry and [exports] mapping |
| Go package load failure | Lock state, target, build tags, CGO, dependency version |
| Standard package unavailable | Exact supported kinmokusei/* path and compiler version |
Use keika deps check for project graph integrity and keika interop audit --json package/path for Go API connectivity.
Complete example
Split code across modules is a runnable two-file example. Add an external Go module covers the transactional dependency workflow.
After resolving names across files and packages, Types and values develops the copy, aliasing, and identity model behind those declarations.