Catalog · How the catalog works
lwpt
Lightweight Pascal toolkit
- ★ 3 on GitHub
Anyone may press Interesting, signed in or not. It helps others find this project.
README
LWPT — lightweight Pascal toolkit
A small, dependency-light toolkit for FreePascal / Delphi projects.
One executable, fifteen command families, driven by a single lwpt.toml
manifest. Zero-install by default — git clone && fpc @lwpt.cfg
builds a project without running lwpt install first.
lwpt init scaffold a new project or adopt an existing manifest [--adopt]
lwpt install resolve + fetch dependencies, write lwpt.lock + lwpt.cfg
lwpt add add a dependency to lwpt.toml + install it [--name <name>]
lwpt remove remove dependencies from lwpt.toml + prune their modules
lwpt outdated compare locked git-host deps to advertised tags [--json]
lwpt update bump constraints + reinstall newer git-host deps [name ...]
lwpt build compile manifest build entries [--mode dev|release] [--clean] [--jobs N]
lwpt format format uses-clauses + identifiers [--check]
lwpt duplication report manifest-scoped Pascal token clones [--json]
lwpt test discover, compile and run *.Test.pas files [--jobs N] [--bail N]
lwpt repair reclaim install, build-session, and worker-lease residue, and upgrade a v3 lockfile to v4
lwpt registry run a self-hosted registry origin or verified mirror, or publish to one <init|sync|verify|rotate-key|publish|issue-token|revoke-token|serve>
lwpt run invoke a user-declared run task (or alias a subcommand)
lwpt health report Pascal complexity and optional Git hotspots [--json] [--hotspots]
lwpt agents write/verify the agent-facing command reference in AGENTS.md [--check]Status
LWPT is pre-1.0. The package model, install pipeline, formatter, test runner,
duplication analysis, codebase-health report, and release flow are in place.
The project-only release architecture check remains separate from the customer
commands originally deferred by
ADR-0006. See the consumer guide for using LWPT,
AGENTS.md for contributing to LWPT, and
docs/adr/ for the architectural decisions that shape
the v1 design.
The project's durable direction and delivery gates live in
VISION.md,
DEFINITION_OF_READY.md, and
DEFINITION_OF_DONE.md.
Quick start
Install the released toolkit on macOS, then scaffold in your own project:
brew install fpc frostney/tap/lwpt
mkdir my-project
cd my-project
lwpt --help
lwpt init --yes
lwpt buildContinue with Use LWPT in your project for a
complete CLI, native test, dependency manifest, run task, and quality commands.
It also covers release installation on other platforms. Each subcommand's help
links back to lwpt --help so its other capabilities remain discoverable.
Contributing to LWPT itself? Follow the contributor quick start for the source bootstrap and self-host build. The bootstrap belongs to the LWPT repository; a consumer uses the installed executable.
Documentation for agents: llms.txt links directly to the same canonical Markdown guides and references.
Architecture
The package manager is the foundation. install resolves the
dependency graph and emits lwpt.cfg (an FPC response fragment of
-Fu search paths). Every other subcommand consumes that same cfg.
The manifest is the single source of truth. This through-line is
deliberate — see docs/adr/0002-lwpt-namespace-zero-install.md
for the full rationale.
| File | Origin | Role |
|---|---|---|
source/lwpt.pas |
new | program entry: registers subcommands |
source/LWPT.Core.pas |
new | project identity, error hierarchy, and shared low-level helpers |
source/LWPT.Manifest.pas |
new | manifest model, intake, source/version parsing, and manifest path context |
source/LWPT.Install.pas |
new | install transaction: resolve, fetch or restore, extract, lockfile/cfg, frozen verification |
source/LWPT.Command.*.pas |
new | command-level behavior for each subcommand |
source/LWPT.Formatter.pas |
converted from GocciaScript format.pas |
formatter engine used by LWPT.Command.Format |
source/LWPT.GitProtocol.pas |
new | git smart-HTTP tag listing for @<spec> resolution |
source/Platform.pas |
LWPT-canonical | host OS / CPU detection for {platform.*} placeholders (extraction candidate for packages/platform/) |
source/Shared.inc |
LWPT-canonical | include file ({$mode delphi} {$H+} baseline; each packages/<name>/source/ has its own bundled copy) |
packages/httpclient/ |
LWPT-canonical workspace package | HTTP/1.1 + HTTPS client + byte-safety accumulator |
packages/cli/ |
LWPT-canonical workspace package | option parser + subcommand dispatch + interactive prompts |
packages/semver/ |
LWPT-canonical workspace package | full node-semver port |
packages/toml/ |
LWPT-canonical workspace package | TOML 1.1 parser |
packages/testing/ |
LWPT-canonical workspace package | TestingPascalLibrary — assertion + suite + runner framework for *.Test.pas files |
The five workspace packages live under packages/<name>/ (per
ADR-0014 +
ADR-0015);
the root manifest auto-discovers them via [workspaces] include = ["packages/*"]. Per
ADR-0017, LWPT is the
canonical source for every package — and GocciaScript (a sister project
under the same owner) is the first named consumer, committed to Path A
adoption (full toolchain migration to lwpt build / install / test / format). Phase 2 graduates individual packages to standalone repos
when warranted; the per-package roadmap lives in
docs/contributor/packages.md.
On-disk layout
my-project/
├── lwpt.toml # manifest (single source of truth)
├── lwpt.lock # lockfile (committed)
├── lwpt.cfg # FPC response fragment (committed)
├── .lwpt/ # toolkit state
│ ├── modules/ # extracted deps — COMMITTED, source of truth
│ │ ├── horse/
│ │ └── jhonson/
│ ├── archives/ # *.tar.gz per dep — COMMITTED (verification)
│ ├── tmp/ # install workspace — GITIGNORED
│ ├── sessions/ # default private build-session staging
│ └── session-roots # relocated-root ledger — GITIGNORED
├── build/ # FPC output — GITIGNORED
└── src/
└── main.pasManifest
[package]
name = "myapp"
version = "1.4.2"
units = ["src"]
[dependencies]
# Bare-string shorthand: "<source>@<spec>" — see ADR-0009.
horse = "HashLoad/horse@^4.0.0" # GitHub by default, SemVer range
hello = "octocat/Hello-World@1.0.0" # exact SemVer (matches tag `1.0.0` or `v1.0.0`)
ci-debug = "gitlab:gitlab-examples/ci-debug-trace@dd648b2e48ce6518303b0bb580b2ee32fadaf045" # GitLab via prefix, commit SHA
atlaskit = "bitbucket:atlassian/atlaskit@d7ac1acad54ed82e3fc244398cd29044f9bf1775" # Bitbucket via prefix, full commit SHA (must be on a branch or tag)
custom = "https://example.com/custom-1.0.0.tar.gz" # arbitrary HTTPS tarball
leaf = "../leaf" # local sibling path
# Inline-table form for advanced options (include / exclude filters,
# formatter-mirror semantics — see ADR-0009):
horse-mw = { source = "HashLoad/horse", version = "^4.0.0", include = ["src/middleware/**"] }
horse-no-tests = { source = "HashLoad/horse", version = "^4.0.0", exclude = ["tests/**", "examples/**"] }
# Custom hosts via [sources.<name>] — gitea/forgejo/self-hosted/etc.
mylib = "gitea:team/mylib@^1.0.0" # uses the [sources.gitea] entry below
json = "registry:json@^1.2.0" # signed registry record (ADR-0051); see [registries] below
[sources]
# Per-project custom prefix definitions. Each entry is an inline
# table with `archive` + `git` URL templates. Placeholders are
# {user} / {repository} / {ref}. The smart-HTTP tag listing uses
# the `git` URL; the archive download uses `archive`.
# See ADR-0009 §"Custom hosts".
gitea = { archive = "https://git.example.com/{user}/{repository}/archive/{ref}.tar.gz", git = "https://git.example.com/{user}/{repository}.git" }
[registries]
# Registry origins that `registry:` dependencies resolve through (ADR-0051).
# Root manifest only. `default` is optional with exactly one registry.
default = "corp"
[registries.corp]
identity = "https://packages.example.com" # optional; advertised by the contacts, then locked
# The trust pin: the origin's root key. EXAMPLE VALUES ONLY: a valid,
# throwaway pair whose private key was discarded. Replace both with your
# origin's root pin, its `keys/ed25519-*.toml` record's key_id and public_key
# (see docs/user/registry-deployment.md); key-id is "ed25519:" + the SHA-256 of
# the raw 32-byte public key.
key-id = "ed25519:77790c39520108490b51dd825c9c84dc009f92ac5d1b94d68f19c9d11cca3675"
public-key = "hex:fd49e4bc086e9b5203d162066dc459ca66478324dc3b39d4e202460c0bc3e724"
origin = "https://packages.example.com" # origin contact; defaults to identity
mirrors = ["https://mirror.example.net/lwpt"] # optional; tried first, in this order
[build]
# Single-binary shorthand: `[build] source = "..."` defaults the
# entry name to [package].name and the output to build/<entry-name>.
# Multi-binary form (used here): one inline table per entry.
cli = { source = "src/cli.pas", output = "bin/cli",
target = { os = "linux", architecture = "aarch64",
abi = "", environment = "" } }
tool = { source = "src/tool.pas", output = "bin/tool", compiler = "custom" }
delphi-tool = { source = "src/tool.dpr", output = "bin/tool.exe", compiler = "delphi-win64" }
[prebuild]
generate = { command = "tools/generate", args = ["--output", "src/Generated.inc"], inputs = ["schemas/**/*.json"], output = "src/Generated.inc" }
[deploy]
command = "tools/deploy"
args = ["--environment", "staging"]
[compiler]
# Optional root-owned policy. Without it, build and test use built-in FPC.
default = "native"
[compiler.profiles.native]
driver = "fpc"
version = "^3.2.0"
[compiler.profiles.wasm]
# Opt-in adapter for the released frostney/lakon compiler.
driver = "lakon"
command = "tools/lakon" # optional; otherwise resolved from PATH
version = ">=0.1.0"
[compiler.profiles.blaise]
# Built-in opt-in adapter for graemeg/blaise v0.13.0 or newer.
driver = "blaise"
command = "tools/blaise" # optional; otherwise resolved from PATH
version = ">=0.13.0"
[compiler.profiles.custom]
# External drivers receive versioned canonical TOML on stdin/stdout.
driver = "my-compiler"
command = "tools/my-compiler-driver"
args = ["--protocol=lwpt"]
version = "^1.0.0"
[compiler.profiles.delphi-win64]
# Built-in, opt-in consumer driver; LWPT itself remains FPC-built.
driver = "delphi"
# Replace this placeholder with the installed compiler path.
command = "C:/path/to/dcc64.exe"
version = ">=36.0.0"
[version]
output = "src/Version.Generated.inc"
prefix = "APP" # generates APP_VERSION, APP_BUILD_DATE
[lwpt]
# Toolkit-state overrides. Defaults shown; you almost never need these.
# modules-dir = ".lwpt/modules"
# archives-dir = ".lwpt/archives"
# tmp-dir = ".lwpt/tmp"
# sessions-dir = ".lwpt/sessions" # LWPT_SESSION_DIR overrides this
# cfg-file = "lwpt.cfg"
[format]
# include = additive glob list on top of [package].units;
# exclude = glob list subtracted from the resolved set.
# Plain dir names are top-level shorthand; recursion via ** is explicit.
# See ADR-0007 + docs/contributor/code-style.md for the full algorithm.
include = ["tests/**/*.pas"]
exclude = ["src/legacy/Vendored.pas"]Source kinds: skGitHost (default github, with gitlab: / bitbucket: / any user-declared [sources.<name>] prefix), skURL (any https://...), skLocal (any path or local: prefix), skWorkspace (workspace:* or workspace:^X.Y.Z, naming a member discovered through [workspaces]), and skRegistry (registry:[<alias>/]<package>, selected from the registry snapshot named by the signed checkpoint of an origin declared in the root [registries] table; see ADR-0051). Version specs go through the LWPT-canonical Semver unit (a node-semver port adapted from GocciaScript's earlier copy) for ranges + exact matches, then fall through to literal Git tag / commit-SHA lookup. Tag listing uses git smart-HTTP info/refs?service=git-upload-pack — works against any git host with one URL pattern, no JSON, no auth tokens. Custom hosts (Gitea, Forgejo, self-hosted GitHub Enterprise / GitLab / Bitbucket Server) plug in via the [sources] table — no code change needed. See ADR-0009.
Writing tests
For a consumer dependency manifest and a runnable example, start with the consumer guide. The workspace auto-discovery below describes LWPT’s own monorepo.
TestingPascalLibrary lives in the testing workspace package and is
auto-discovered via [workspaces] include = ["packages/*"] in the
root manifest — lwpt install publishes its validated snapshot into
.lwpt/modules/testing/, and the cfg emitter wires the -Fu / -Fi paths so
every *.Test.pas file resolves uses TestingPascalLibrary; with no
further setup.
Then a *.Test.pas file is a self-contained program:
program Math.Test;
{$mode delphi}{$H+}
uses TestingPascalLibrary;
type
TMathTests = class(TTestSuite)
public
procedure SetupTests; override;
procedure TestAddition;
end;
procedure TMathTests.TestAddition;
begin
Expect<Integer>(2 + 2).ToBe(4);
end;
procedure TMathTests.SetupTests;
begin
Test('addition works', TestAddition);
end;
begin
TestRunnerProgram.AddSuite(TMathTests.Create('Math'));
TestRunnerProgram.Run;
ExitCode := TestResultToExitCode;
end.lwpt test discovers *.Test.pas files and compiles/runs independent
programs concurrently within the shared machine worker budget. --jobs=N
sets a lower per-invocation ceiling. --bail=N stops after N compile or
runtime failures, terminates and reaps active children, and leaves later
programs unstarted; zero runs the complete queue. The project default is
configured with [test] bail = N and defaults to zero. Results are printed
in source-path order, and the command exits 1 if any test or compile fails.
Every subcommand accepts a long-only --silent option after the command name.
On success it suppresses progress and child output and prints only the final
lwpt <command>: completed in ... line on stdout. On failure it replays the
retained diagnostic and failed child output before one final failure line on
stderr. --silent cannot be combined with --verbose; aliases such as
lwpt run build --silent report the resolved build identity. Because silent
mode cannot display prompts, lwpt init --silent additionally requires
--yes or --adopt.
Notable canonical-version choices
Per ADR-0017, LWPT is the canonical source for every workspace package; the older GocciaScript copies of these units are frozen pending Path A adoption. The places where the LWPT-canonical version meaningfully differs from GocciaScript's older copy:
packages/httpclient/source/HTTPClient.pas— byte-safeAppendRawBytesaccumulator on the header-recv path and the chunked-body seed-buffer.Copy(PAnsiChar(...))truncates response bytes at the first#0, corrupting binary downloads; the byte-safe accumulator avoids the issue.packages/cli/source/CLI.Parser.pas— valued short options support separated values (-o output, including values beginning with-) and opt-in attached values (-Fusource,-dDEBUG) with longest-prefix matching. Space-separated long values (--mode release) work for plain string/integer options, not only repeatable ones. Plus theAStartArgparameter forlwpt run <subcommand>aliasing.packages/cli/source/CLI.Options.pas—TGoccia*type-prefix stripped from every public type; GocciaScript-engine-specific option groups removed as dead code.packages/semver/source/Semver.pas— renamed fromGoccia.Semver;MAX_SAFE_INTEGERinlined.packages/toml/source/TOML.pas— renamed fromGoccia.TOML; parser refactored to a class-based AST shape.source/Platform.pas— renamed fromGoccia.Platform.
See docs/contributor/packages.md for the complete
package set + per-file divergence table + bootstrap chicken-and-egg
story.
Documentation
- Consumer guide — install LWPT and use its capabilities in your project.
- Documentation index — topic-by-topic navigation.
- llms.txt — compact index with direct Markdown links for agents.
AGENTS.md— contributor instructions for AI assistants changing LWPT itself.docs/adr/— architectural decision records.docs/spikes/— point-in-time snapshots of investigations (e.g. the archived HTTP registry spike, prior art for the self-hosted registry that shipped under issue #29).- User guide — using LWPT in a project: the consumer guide, build system, health, and registry use and deployment.
- Contributor guide — changing LWPT itself: architecture, quick start, tooling, code style, deployment, testing, packages, and CI.

