Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .runseal/wrappers/antlr
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#!/usr/bin/env sh
set -eu
exec "$(dirname -- "$0")/../lib/python-module" "$@"
22 changes: 12 additions & 10 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,13 @@ management.
scripts there.
- `runseal.toml` and `.runseal/wrappers/` are the repo-local operator entrypoints
for support tasks that do not belong in the installable `flavor` product
binary. Current support commands are `runseal :cloudflare`, `runseal :pr`,
and `runseal :release`.
binary. Current support commands are `runseal :antlr`, `runseal :cloudflare`,
`runseal :pr`, and `runseal :release`.
- `grammars/` contains the repo-visible `.g4` grammar source of truth plus
`metadata.json` contract metadata. Parser backends, facts, diagnostics, and
harnesses should align to these files.
- `scripts/` contains both developer helpers (`scripts/dev/`) and the
repo-local uv-managed Python support command tree used by runseal wrappers.
- `scripts/` contains the repo-local uv-managed Python support command tree
used by runseal wrappers.
- `.local/` is repo-local private operator state (for example Cloudflare
secrets). It must stay gitignored and must not become a source of truth for
product behavior.
Expand Down Expand Up @@ -86,17 +86,19 @@ cargo fmt --all --check
cargo clippy --locked --workspace --all-targets -- -D warnings
cargo test --locked --workspace
cargo run --locked -p flavor-cli -- check --root . --config flavor.toml
python3 scripts/dev/antlr.py check
runseal :antlr init
runseal :antlr check
runseal :pr --help
```

`python3 scripts/init.py` is the default post-clone command. It requires
`runseal` so repo-local support commands have one entrypoint shape. Use
`--force` only when intentionally replacing existing non-init hooks; the script
backs them up first.
`python3 scripts/dev/antlr.py check` is an optional Dockerized ANTLR validation
helper. It lazily builds its Docker image when needed, checks `.g4` files under
`grammars/` in ANTLR dependency mode, and does not generate Java artifacts.
`runseal :antlr init` builds the pinned local ANTLR Docker image.
`runseal :antlr check` is an optional Dockerized ANTLR validation helper. It
requires the image prepared by init, checks `.g4` files under `grammars/` in
ANTLR dependency mode, and does not generate Java artifacts.

## Standard Workflow

Expand Down Expand Up @@ -233,8 +235,8 @@ and `manage.ps1`. Release and smoke scripts should reference those root files.
### Where Do Workflow Helper Scripts Go?

Workflow-only helpers belong under `.github/scripts/`. The repository
initialization entrypoint is `scripts/init.py`; additional local developer
harness helpers, if added, belong under `scripts/dev/`.
initialization entrypoint is `scripts/init.py`; additional local support
commands, if added, should use runseal wrappers plus `scripts/cli/`.

### Does Init Replace Existing Hooks?

Expand Down
File renamed without changes.
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -189,13 +189,16 @@ cargo fmt --all --check
cargo clippy --locked --workspace --all-targets -- -D warnings
cargo test --locked --workspace
cargo run --locked -p flavor-cli -- check --root . --config flavor.toml
python3 scripts/dev/antlr.py check # optional Dockerized G4 validation
runseal :antlr init # build the local ANTLR image once
runseal :antlr check # optional Dockerized G4 validation
```

Repo-local operator commands use runseal wrappers rather than the installable
`flavor` binary:

```bash
runseal :antlr init [options]
runseal :antlr check [grammars/.../*.g4]
runseal :cloudflare <command> [args]
runseal :pr [options]
runseal :release --channel=stable|beta [options]
Expand Down
6 changes: 4 additions & 2 deletions grammars/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,16 @@ files for plugin-facing grammar contracts.

```bash
cargo test --locked -p flavor-grammar
python3 scripts/dev/antlr.py check
runseal :antlr init
runseal :antlr check
```

## Standard Workflow

- Define or update grammar shape in `.g4`; keep metadata focused on owner,
entrypoint, facts, diagnostics, span mapping, recovery, and backend bindings.
- Use `scripts/dev/antlr.py check` as the Dockerized ANTLR helper. It accepts
- Use `runseal :antlr init` to build the pinned local ANTLR Docker image, then
`runseal :antlr check` as the Dockerized ANTLR helper. The check accepts
optional `.g4` files under `grammars/`, groups them by path relative to
`grammars/`, and runs ANTLR in dependency mode without Java artifact output.
- Harnesses should prove conformance to this contract rather than merely
Expand Down
99 changes: 59 additions & 40 deletions scripts/dev/antlr.py → scripts/cli/antlr.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,22 +22,46 @@ def main() -> int:
image = args.image or f"flavor-antlr:{args.antlr_version}"

require_docker()
ensure_image(
root,
image,
antlr_version=args.antlr_version,
antlr_sha256=args.antlr_sha256,
rebuild=args.rebuild_image,
require_image=args.require_image,
)

if args.command == "init":
ensure_image(
root,
image,
antlr_version=args.antlr_version,
antlr_sha256=args.antlr_sha256,
rebuild=args.rebuild_image,
)
print(f"antlr Docker image ready: {image}")
return 0

require_image(image, args.antlr_version)
return check_grammars(root, image, args.g4_files)


def parse_args(argv: list[str]) -> argparse.Namespace:
parser = argparse.ArgumentParser(description=__doc__)
parser = argparse.ArgumentParser(prog="runseal :antlr", description=__doc__)
subparsers = parser.add_subparsers(dest="command", required=True)

init = subparsers.add_parser(
"init",
help="build the local ANTLR Docker image",
description=(
"Build the local ANTLR Docker image used by `runseal :antlr check`. "
"The image downloads a pinned ANTLR complete jar and verifies its "
"SHA256 digest during build."
),
)
add_image_args(init)
init.add_argument(
"--antlr-sha256",
default=os.environ.get("ANTLR_SHA256", DEFAULT_ANTLR_SHA256),
help="expected SHA256 of the ANTLR complete jar",
)
init.add_argument(
"--rebuild-image",
action="store_true",
help="rebuild the Docker image even if it already exists",
)

check = subparsers.add_parser(
"check",
help="validate repo grammar files",
Expand All @@ -53,36 +77,22 @@ def parse_args(argv: list[str]) -> argparse.Namespace:
nargs="*",
help="optional .g4 files under grammars/",
)
check.add_argument(
add_image_args(check)

return parser.parse_args(argv)


def add_image_args(parser: argparse.ArgumentParser) -> None:
parser.add_argument(
"--antlr-version",
default=os.environ.get("ANTLR_VERSION", DEFAULT_ANTLR_VERSION),
help=f"ANTLR version to download into the Docker image (default: {DEFAULT_ANTLR_VERSION})",
help=f"ANTLR version for the Docker image tag (default: {DEFAULT_ANTLR_VERSION})",
)
check.add_argument(
"--antlr-sha256",
default=os.environ.get("ANTLR_SHA256", DEFAULT_ANTLR_SHA256),
help="expected SHA256 of the ANTLR complete jar",
)
check.add_argument(
parser.add_argument(
"--image",
default=os.environ.get("ANTLR_IMAGE"),
help="Docker image tag to build/use (default: flavor-antlr:<version>)",
help="Docker image tag to use (default: flavor-antlr:<version>)",
)
check.add_argument(
"--rebuild-image",
action="store_true",
help="rebuild the Docker image even if it already exists",
)
check.add_argument(
"--require-image",
action="store_true",
help="do not build the Docker image; fail if it is missing",
)

args = parser.parse_args(argv)
if args.rebuild_image and args.require_image:
raise SystemExit("--rebuild-image and --require-image cannot be combined")
return args


def repo_root() -> Path:
Expand Down Expand Up @@ -117,15 +127,25 @@ def ensure_image(
antlr_version: str,
antlr_sha256: str,
rebuild: bool,
require_image: bool,
) -> None:
if not rebuild and image_exists(image):
return
if require_image:
raise SystemExit(f"Docker image {image!r} is missing")
build_image(root, image, antlr_version, antlr_sha256)


def require_image(image: str, antlr_version: str) -> None:
if image_exists(image):
return
command = ["runseal :antlr init"]
if antlr_version != DEFAULT_ANTLR_VERSION:
command.append(f"--antlr-version {antlr_version}")
if image != f"flavor-antlr:{antlr_version}":
command.append(f"--image {image}")
raise SystemExit(
f"Docker image {image!r} is missing; run `{' '.join(command)}` before `runseal :antlr check`"
)


def image_exists(image: str) -> bool:
result = subprocess.run(
["docker", "image", "inspect", image],
Expand All @@ -136,8 +156,7 @@ def image_exists(image: str) -> bool:


def build_image(root: Path, image: str, antlr_version: str, antlr_sha256: str) -> None:
dockerfile = root / "scripts" / "dev" / "antlr.Dockerfile"
context_dir = root / "scripts" / "dev"
dockerfile = root / "Dockerfile.antlr4"
run_checked(
[
"docker",
Expand All @@ -150,7 +169,7 @@ def build_image(root: Path, image: str, antlr_version: str, antlr_sha256: str) -
image,
"-f",
str(dockerfile),
str(context_dir),
str(root),
],
label=f"building {image}",
attempts=3,
Expand Down
7 changes: 5 additions & 2 deletions scripts/init.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
"manage.ps1",
"runseal.toml",
".runseal/lib/python-module",
".runseal/wrappers/antlr",
".runseal/wrappers/cloudflare",
".runseal/wrappers/pr",
".runseal/wrappers/release",
Expand All @@ -49,7 +50,8 @@
"scripts/cli/cloudflare.py",
"scripts/cli/pr.py",
"scripts/cli/release.py",
"scripts/dev/antlr.py",
"Dockerfile.antlr4",
"scripts/cli/antlr.py",
"scripts/init.py",
)

Expand All @@ -70,6 +72,7 @@

echo "==> shell syntax"
sh -n .runseal/lib/python-module
sh -n .runseal/wrappers/antlr
sh -n .runseal/wrappers/cloudflare
sh -n .runseal/wrappers/pr
sh -n .runseal/wrappers/release
Expand All @@ -79,7 +82,7 @@

echo "==> python syntax"
python3 -m py_compile scripts/init.py
python3 -m py_compile scripts/dev/antlr.py
python3 -m py_compile scripts/cli/antlr.py
python3 -m py_compile scripts/cli/cloudflare.py
python3 -m py_compile scripts/cli/pr.py
python3 -m py_compile scripts/cli/release.py
Expand Down