Packages and Package Source
This chapter defines package identity, package-root source, dependency declarations, and executable and test target declarations. Modules, Use Declarations, and Source Visibility defines the directory modules selected by those declarations. The Command-Line Interface defines how commands consume and update this source contract.
Package Root
A package root is a directory whose index.nct contains exactly one top-level #package directive. The same file is both the package declaration source and the root module's root source. There is no separate manifest file and no source-root concept.
project/
index.nct
search.nct
parser/
index.nct
lexer.nct
tests/
unit/
index.nct
The root index.nct contains package documentation, a directive prefix, and ordinary root-module code:
//! Example application package.
#package: {
name: "example",
version: "0.1.0",
}
#executable: {
name: "example",
}
#test: {
name: "unit",
module: "./tests/unit",
}
use std/io
use ./parser.Parser
func main(): i32! {
let parser = Parser.new()
io.print("ready\n")?
return 0
}
Package-root rules:
- file documentation precedes the package directive prefix
- that file documentation belongs to the package; the root module does not register a second copy
#packageis required and requires string fieldsnameandversion#dependencies,#executable, and#testfollow in the same directive prefix- every package directive precedes
see,use, and ordinary declarations - package directives are invalid outside the package root
index.nct - a descendant
index.nctcontaining#packagestarts a nested package; one without#packagestarts a child module
The compiler does not discover a package target by probing main.nct or another conventional filename.
Directive Data
Directive records and lists use the data notation recognized by Syntactic Grammar. List elements and record fields are comma-delimited and may use one trailing comma on any layout under Comma-Delimited Lists.
The package directive names form this closed set:
package
dependencies
executable
test
Unknown directive names are invalid. In particular, #lock is not a package directive and no separate lockfile exists.
Dependencies
Each dependency record owns both authored source intent and its optional generated exact selection:
#dependencies: {
json: {
git: "https://github.com/example/json.git",
revision: "main",
commit: "7db21c1000000000000000000000000000000000",
},
http: {
archive: "https://nocter.dev/lib/http-v1.0.0.tar.gz",
sha256: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
},
local_math: {
path: "./packages/math",
},
}
Dependency aliases are package-local names. Reserved alias std cannot be declared because the active toolchain supplies it implicitly.
Exactly one source form is selected by each dependency entry:
gitrequires authoredrevision;commitis its optional exact selectionarchiveacceptssha256as its optional exact selectionpathselects a mutable local package and accepts no exact-selection field
git, archive, and path are mutually exclusive. Fields belonging to a different source form, unknown fields, and wrong value kinds are invalid.
Git builds use only an exact lowercase 40-hex commit. Archive builds use only content with the exact lowercase 64-hex sha256. Path dependencies remain mutable development inputs. Dependency acquisition and exact-field updates are command behavior defined by nocter fetch.
Every exact dependency selection has one canonical, Windows-safe package identity:
- a Git
commitbecomesgit-<lowercase-40-hex-commit> - an archive
sha256becomessha256-<lowercase-64-hex-digest> - a path package becomes
path-<64-lowercase-hex>, where the digest is SHA-256 over the UTF-8 bytes of its canonical absolute path
The Git URL and archive URL are acquisition metadata, not identity input. Two declarations that select the same exact commit or archive content select the same package even when they use different mirrors. Symlinks in a path dependency are resolved before its identity is computed. Display names and versions never participate in identity.
Executable Targets
#executable is repeatable. Each record requires a package-local name unique among executable targets and accepts an optional module:
#executable: {
name: "server",
module: "./src/server",
}
An omitted module selects .. Target module paths are . or package-relative directory paths beginning with ./:
.selects the package rootindex.nct./src/serverselectssrc/server/index.nct- paths omit
.nct, cannot escape the package, and cannot cross a nested package - targets never select ordinary implementation sources
Executable and test names occupy separate target namespaces. The CLI defines executable selection, while Executable Entry defines the required main contract.
Test Targets
#test is repeatable. Each record requires a package-local name unique among test targets and a required module:
#test: {
name: "unit",
module: "./tests/unit",
}
Test target module paths follow the same directory-module rules as executable targets. Test modules are never discovered from directory names or filenames. Test execution behavior is defined by Native Testing.
Implicit Standard-Library Package
The active Nocter home contributes one immutable package at <Nocter-home>/std. Its root index.nct contains #package; the package name and version must match the toolchain installation. Every compilation graph binds reserved dependency alias std to this exact package, including imports written inside std itself and single-file mode.
A package named std, a directory with that spelling, or a dependency alias cannot shadow the compiler-selected package or gain its primitive authority. Single-file mode uses the toolchain package without creating a package declaration for the source file.
The standard package follows ordinary directory-module, visibility, use, and see rules. Its special authority is limited to the trusted declarations and primitive boundaries specified in Standard Library Primitives and OS Boundaries.