Skip to content
frostneyPublic

Latest commit

 

History

233 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

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 build

Continue 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 <source>@<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/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.pas

Manifest

[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/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/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-safe AppendRawBytes accumulator 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 the AStartArg parameter for lwpt 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 from Goccia.Semver; MAX_SAFE_INTEGER inlined.
  • packages/toml/source/TOML.pas — renamed from Goccia.TOML; parser refactored to a class-based AST shape.
  • source/Platform.pas — renamed from Goccia.Platform.

See docs/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).

  • docs/ — full set of canonical docs: architecture.md, quick-start.md, tooling.md, code-style.md, build-system.md, deployment.md, testing.md, packages.md, ci.md. Each opens with an Executive Summary.