From 9cd5ec00a857858a7bc4f9aa1cbcc36bed7111ef Mon Sep 17 00:00:00 2001 From: Riyan Dhiman Date: Fri, 21 Aug 2026 10:54:37 +0530 Subject: [PATCH 01/61] docs: document reproducible UI stack installs --- README.md | 7 +++++++ scripts/install.sh | 15 +++++++++++---- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index a812a6f..db8d629 100644 --- a/README.md +++ b/README.md @@ -90,6 +90,13 @@ and lays them out as: Re-running `install.sh` updates the checkouts in place. Requirements: `git`, `python3` (3.10+), and Go 1.24+ to build the binary. +For reproducible deployments, pin the engine and catalog before installing (use +reviewed tags or commit SHAs rather than mutable branches): + +```sh +LACHESIS_REF= ATROPOS_REF= ./scripts/install.sh +``` + Then put the binary on your PATH: ```sh diff --git a/scripts/install.sh b/scripts/install.sh index 917a6f4..979eae5 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -33,6 +33,8 @@ VENV="$LACHESIS_HOME/venv" BIN="$LACHESIS_HOME/bin" GRAPHS="$LACHESIS_HOME/graphs" PYTHON="${PYTHON:-python3}" +LACHESIS_REF="${LACHESIS_REF:-main}" +ATROPOS_REF="${ATROPOS_REF:-main}" LACHESIS_REPO="https://github.com/UnboundCompute/lachesis.git" ATROPOS_REPO="https://github.com/UnboundCompute/atropos.git" @@ -52,17 +54,22 @@ mkdir -p "$SRC" "$BIN" "$GRAPHS" # ---- 1 + 2. clone/update engine and catalog (side by side) ---------------- clone_or_update() { - local url="$1" dir="$2" name="$3" + local url="$1" dir="$2" name="$3" ref="$4" if [ -d "$dir/.git" ]; then info "updating $name" git -C "$dir" pull --ff-only --quiet || warn "$name: could not fast-forward (local changes?), leaving as is" else info "cloning $name" - git clone --depth 1 --quiet "$url" "$dir" + git clone --depth 1 --quiet "$url" "$dir" \ + || die "$name: could not clone repository" + git -C "$dir" fetch --depth 1 origin "$ref" \ + || die "$name: could not fetch ref '$ref'" + git -C "$dir" checkout --detach --quiet FETCH_HEAD \ + || die "$name: could not check out ref '$ref'" fi } -clone_or_update "$LACHESIS_REPO" "$SRC/lachesis" "engine (lachesis)" -clone_or_update "$ATROPOS_REPO" "$SRC/atropos" "catalog (atropos)" +clone_or_update "$LACHESIS_REPO" "$SRC/lachesis" "engine (lachesis)" "$LACHESIS_REF" +clone_or_update "$ATROPOS_REPO" "$SRC/atropos" "catalog (atropos)" "$ATROPOS_REF" # ---- 3. engine virtualenv ------------------------------------------------- if [ ! -x "$VENV/bin/python" ]; then From e78d9c00acb091a8f39231ffea9355c99505d023 Mon Sep 17 00:00:00 2001 From: Riyan Dhiman Date: Fri, 21 Aug 2026 11:07:16 +0530 Subject: [PATCH 02/61] fix: honor pinned refs on UI reinstall --- README.md | 4 +++- scripts/install.sh | 11 +++++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index db8d629..e64a135 100644 --- a/README.md +++ b/README.md @@ -91,7 +91,9 @@ Re-running `install.sh` updates the checkouts in place. Requirements: `git`, `python3` (3.10+), and Go 1.24+ to build the binary. For reproducible deployments, pin the engine and catalog before installing (use -reviewed tags or commit SHAs rather than mutable branches): +reviewed tags or commit SHAs rather than mutable branches). Re-running the +installer applies those refs to existing clean checkouts and refuses to touch a +checkout with local edits: ```sh LACHESIS_REF= ATROPOS_REF= ./scripts/install.sh diff --git a/scripts/install.sh b/scripts/install.sh index 979eae5..9df255a 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -19,7 +19,8 @@ # ~/.lachesis/bin/lachesis-ui the built UI binary # ~/.lachesis/build-graph.sh helper: build a graph from any source tree # -# Re-running is safe: it updates existing checkouts instead of recloning. +# Re-running is safe: it updates existing clean checkouts to the requested refs +# instead of silently retaining an older branch or commit. # # Env overrides: # LACHESIS_HOME install root (default: ~/.lachesis) @@ -57,7 +58,13 @@ clone_or_update() { local url="$1" dir="$2" name="$3" ref="$4" if [ -d "$dir/.git" ]; then info "updating $name" - git -C "$dir" pull --ff-only --quiet || warn "$name: could not fast-forward (local changes?), leaving as is" + if ! git -C "$dir" diff --quiet || ! git -C "$dir" diff --cached --quiet; then + die "$name: local changes found; commit or remove them before changing refs" + fi + git -C "$dir" fetch --depth 1 --quiet origin "$ref" \ + || die "$name: could not fetch ref '$ref'" + git -C "$dir" checkout --detach --quiet FETCH_HEAD \ + || die "$name: could not check out ref '$ref'" else info "cloning $name" git clone --depth 1 --quiet "$url" "$dir" \ From b27ef6c2bf57677fc817fea7e4feda74c744e882 Mon Sep 17 00:00:00 2001 From: Riyan Dhiman Date: Fri, 21 Aug 2026 11:08:41 +0530 Subject: [PATCH 03/61] chore: add dependency update automation --- .github/dependabot.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..51a9367 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,10 @@ +version: 2 +updates: + - package-ecosystem: gomod + directory: "/" + schedule: + interval: weekly + - package-ecosystem: github-actions + directory: "/" + schedule: + interval: weekly From 392c04687c6b8801d5358660023538f135ba76c2 Mon Sep 17 00:00:00 2001 From: Riyan Dhiman Date: Fri, 21 Aug 2026 11:09:38 +0530 Subject: [PATCH 04/61] ci: cancel superseded UI checks --- .github/workflows/ci.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f519425..2113305 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,6 +9,10 @@ on: permissions: contents: read +concurrency: + group: ui-ci-${{ github.ref }} + cancel-in-progress: true + jobs: build: name: build and check From 84b13fa6a5486e4258059154f25b712fade75dbe Mon Sep 17 00:00:00 2001 From: Riyan Dhiman Date: Fri, 21 Aug 2026 11:12:04 +0530 Subject: [PATCH 05/61] ci: build checksummed UI release binaries --- .github/workflows/release.yml | 49 +++++++++++++++++++++++++++++++++++ CHANGELOG.md | 1 + README.md | 4 ++- 3 files changed, 53 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..9cb159d --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,49 @@ +name: release binaries + +on: + push: + tags: ["v*"] + +permissions: + contents: read + +jobs: + build: + name: ${{ matrix.goos }}-${{ matrix.goarch }} + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + include: + - goos: linux + goarch: amd64 + - goos: linux + goarch: arm64 + - goos: darwin + goarch: amd64 + - goos: darwin + goarch: arm64 + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version: "1.24" + cache: true + - name: Build and package + env: + GOOS: ${{ matrix.goos }} + GOARCH: ${{ matrix.goarch }} + CGO_ENABLED: "0" + run: | + set -euo pipefail + name="lachesis-ui-${GITHUB_REF_NAME}-${GOOS}-${GOARCH}" + mkdir -p dist + go build -trimpath -ldflags='-s -w' -o "dist/$name/lachesis-ui" . + cp README.md LICENSE "dist/$name/" + tar -C dist -czf "dist/$name.tar.gz" "$name" + sha256sum "dist/$name.tar.gz" >> dist/SHA256SUMS + - name: Upload binary artifact + uses: actions/upload-artifact@v4 + with: + name: lachesis-ui-${{ github.ref_name }}-${{ matrix.goos }}-${{ matrix.goarch }} + path: dist/ diff --git a/CHANGELOG.md b/CHANGELOG.md index f1a88ca..5f97581 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project aims to follow [Semantic Versioning](https://semver.org/spec/v2 ### Added +- Tagged releases now build checksummed Linux and macOS binaries for amd64 and arm64. - Nested, collapsible source tree on the Tree screen. Folders expand in place and load their children lazily, with child-count badges, so a large repo opens as a handful of top-level folders rather than a flat wall of files. diff --git a/README.md b/README.md index e64a135..4e6da44 100644 --- a/README.md +++ b/README.md @@ -118,7 +118,9 @@ go install github.com/UnboundCompute/lachesis-ui@latest `lachesis-ui` is a static Go binary, so it is **not** a pip or npm package. The engine it drives is Python (`pip install -e` from the checkout, which -`install.sh` does for you). Prebuilt binaries and a Homebrew tap are planned. +`install.sh` does for you). Tagged releases build Linux and macOS binaries for +amd64 and arm64 in the `release binaries` workflow. Download the matching archive +and verify its SHA-256 checksum; a Homebrew tap is still planned. --- From 7d6392965af91e19f8c446a6f5414c7d96782cdc Mon Sep 17 00:00:00 2001 From: Riyan Dhiman Date: Fri, 21 Aug 2026 11:14:32 +0530 Subject: [PATCH 06/61] docs: show UI artifact checksum verification --- README.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 4e6da44..122bfb0 100644 --- a/README.md +++ b/README.md @@ -120,7 +120,13 @@ go install github.com/UnboundCompute/lachesis-ui@latest engine it drives is Python (`pip install -e` from the checkout, which `install.sh` does for you). Tagged releases build Linux and macOS binaries for amd64 and arm64 in the `release binaries` workflow. Download the matching archive -and verify its SHA-256 checksum; a Homebrew tap is still planned. +and verify its SHA-256 checksum before unpacking it: + +```sh +sha256sum -c SHA256SUMS --ignore-missing +``` + +A Homebrew tap is still planned. --- From 5993edc6b837b87396abec16f984692bf834247f Mon Sep 17 00:00:00 2001 From: Riyan Dhiman Date: Fri, 21 Aug 2026 11:14:55 +0530 Subject: [PATCH 07/61] docs: add UI release runbook --- README.md | 2 ++ RELEASING.md | 25 +++++++++++++++++++++++++ 2 files changed, 27 insertions(+) create mode 100644 RELEASING.md diff --git a/README.md b/README.md index 122bfb0..dc0cf93 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,8 @@ A keyboard-driven terminal UI for navigating a [lachesis](https://github.com/UnboundCompute/lachesis) code-property-graph, the way a developer reads a codebase rather than as a raw graph. +Release and rollback guidance is in [`RELEASING.md`](RELEASING.md). + You point it at a graph built from any Python, TypeScript, JavaScript, or C tree and it opens a persistent, k9s-style screen: subsystems and entry points up front, a source tree with per-file symbol outlines, and a symbol's neighborhood diff --git a/RELEASING.md b/RELEASING.md new file mode 100644 index 0000000..f5ac653 --- /dev/null +++ b/RELEASING.md @@ -0,0 +1,25 @@ +# Releasing lachesis-ui + +The UI is a Go binary released independently from the Lachesis engine and Atropos +catalog. Keep the UI tag and the engine/catalog refs it discovers separate. + +Before tagging: + +```bash +gofmt -l . # must print nothing +go vet ./... +go test ./... +go build ./... +``` + +Create an annotated `vMAJOR.MINOR.PATCH` tag from a clean commit. The tag workflow +builds Linux and macOS binaries for amd64 and arm64 with CGO disabled, packages the +README and license, and writes `SHA256SUMS`. Downloaded archives must pass: + +```bash +sha256sum -c SHA256SUMS --ignore-missing +``` + +The workflow uploads artifacts but does not publish a GitHub release or modify a +package registry. Promote the reviewed artifacts explicitly and retain the prior +version for rollback. Never overwrite a published tag; cut a new patch release. From f1e84694ceca2fbf3e0b3fe05aa5811e4abc4a8e Mon Sep 17 00:00:00 2001 From: Riyan Dhiman Date: Fri, 21 Aug 2026 11:28:31 +0530 Subject: [PATCH 08/61] docs: recommend pinned UI releases --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index dc0cf93..39cbe78 100644 --- a/README.md +++ b/README.md @@ -113,9 +113,12 @@ If you already have the engine installed (`~/.lachesis/venv`, or `lachesis` on your PATH), you only need the UI: ```sh -go install github.com/UnboundCompute/lachesis-ui@latest +go install github.com/UnboundCompute/lachesis-ui@v0.1.0 ``` +Replace `v0.1.0` with the reviewed release tag you intend to deploy; avoid +`@latest` in production automation. + ### Not pip or npm `lachesis-ui` is a static Go binary, so it is **not** a pip or npm package. The From 5df27883569ce14afac13b8e19a3144ba88eef84 Mon Sep 17 00:00:00 2001 From: Riyan Dhiman Date: Fri, 21 Aug 2026 11:51:26 +0530 Subject: [PATCH 09/61] Bound UI CI and release runtimes --- .github/workflows/ci.yml | 1 + .github/workflows/release.yml | 1 + 2 files changed, 2 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2113305..a2a9a03 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,6 +17,7 @@ jobs: build: name: build and check runs-on: ubuntu-latest + timeout-minutes: 10 steps: - name: Check out uses: actions/checkout@v4 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 9cb159d..bf8b647 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -11,6 +11,7 @@ jobs: build: name: ${{ matrix.goos }}-${{ matrix.goarch }} runs-on: ubuntu-latest + timeout-minutes: 15 strategy: fail-fast: false matrix: From 39cb70278e18d6b6074845ab696d8fea505bd8bb Mon Sep 17 00:00:00 2001 From: Riyan Dhiman Date: Fri, 21 Aug 2026 11:57:55 +0530 Subject: [PATCH 10/61] Pin UI installer fallback releases --- README.md | 3 +++ scripts/install.sh | 4 +++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 39cbe78..e64f397 100644 --- a/README.md +++ b/README.md @@ -101,6 +101,9 @@ checkout with local edits: LACHESIS_REF= ATROPOS_REF= ./scripts/install.sh ``` +The installer pins its binary fallback to `v0.1.0`; set `LACHESIS_UI_REF` to a +reviewed tag or commit when selecting another UI release. + Then put the binary on your PATH: ```sh diff --git a/scripts/install.sh b/scripts/install.sh index 9df255a..10c9892 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -25,6 +25,7 @@ # Env overrides: # LACHESIS_HOME install root (default: ~/.lachesis) # PYTHON python to build the venv (default: python3) +# LACHESIS_UI_REF release tag/commit for the go-install fallback (default: v0.1.0) # set -euo pipefail @@ -36,6 +37,7 @@ GRAPHS="$LACHESIS_HOME/graphs" PYTHON="${PYTHON:-python3}" LACHESIS_REF="${LACHESIS_REF:-main}" ATROPOS_REF="${ATROPOS_REF:-main}" +LACHESIS_UI_REF="${LACHESIS_UI_REF:-v0.1.0}" LACHESIS_REPO="https://github.com/UnboundCompute/lachesis.git" ATROPOS_REPO="https://github.com/UnboundCompute/atropos.git" @@ -112,7 +114,7 @@ if command -v go >/dev/null 2>&1; then (cd "$HERE" && go build -o "$BIN/lachesis-ui" .) else info "installing lachesis-ui via go install" - GOBIN="$BIN" go install github.com/UnboundCompute/lachesis-ui@latest + GOBIN="$BIN" go install "github.com/UnboundCompute/lachesis-ui@$LACHESIS_UI_REF" fi else warn "skipped building the UI (no go). Install Go and run: go build -o $BIN/lachesis-ui ." From b1305b3d86f084288edcccf09a33835a3a81b2f4 Mon Sep 17 00:00:00 2001 From: Riyan Dhiman Date: Fri, 21 Aug 2026 12:08:20 +0530 Subject: [PATCH 11/61] Pin UI builds to the declared Go toolchain --- .github/workflows/ci.yml | 3 +-- .github/workflows/release.yml | 2 +- CONTRIBUTING.md | 2 +- README.md | 2 +- scripts/install.sh | 2 +- 5 files changed, 5 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a2a9a03..3b6f9f7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -25,8 +25,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v5 with: - go-version: '1.24' - check-latest: true + go-version: '1.24.2' - name: Verify gofmt run: | diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index bf8b647..a6365cc 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -28,7 +28,7 @@ jobs: - uses: actions/checkout@v4 - uses: actions/setup-go@v5 with: - go-version: "1.24" + go-version: "1.24.2" cache: true - name: Build and package env: diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index abce16b..40c6cc3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -19,7 +19,7 @@ about how that information is shown or navigated, it belongs here. ## Getting set up -You need Go 1.24+ and, to run the UI against a real graph, the engine. +You need Go 1.24.2+ and, to run the UI against a real graph, the engine. ```sh git clone https://github.com/UnboundCompute/lachesis-ui diff --git a/README.md b/README.md index e64f397..b1f5ea9 100644 --- a/README.md +++ b/README.md @@ -90,7 +90,7 @@ and lays them out as: ``` Re-running `install.sh` updates the checkouts in place. Requirements: `git`, -`python3` (3.10+), and Go 1.24+ to build the binary. +`python3` (3.10+), and Go 1.24.2+ to build the binary. For reproducible deployments, pin the engine and catalog before installing (use reviewed tags or commit SHAs rather than mutable branches). Re-running the diff --git a/scripts/install.sh b/scripts/install.sh index 10c9892..949a1a4 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -51,7 +51,7 @@ need() { command -v "$1" >/dev/null 2>&1 || die "missing required tool: $1"; } # ---- preflight ------------------------------------------------------------ need git need "$PYTHON" -command -v go >/dev/null 2>&1 || warn "go not found. Install Go 1.24+ to build the UI (a 'go install' fallback is tried only if you have go)" +command -v go >/dev/null 2>&1 || warn "go not found. Install Go 1.24.2+ to build the UI (a 'go install' fallback is tried only if you have go)" mkdir -p "$SRC" "$BIN" "$GRAPHS" From f2e1ab920f1ecdc01791f9049267eddb348b7ea6 Mon Sep 17 00:00:00 2001 From: Riyan Dhiman Date: Fri, 21 Aug 2026 12:09:38 +0530 Subject: [PATCH 12/61] Make UI release archives reproducible --- .github/workflows/release.yml | 4 +++- RELEASING.md | 3 +++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a6365cc..d9c517b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -37,11 +37,13 @@ jobs: CGO_ENABLED: "0" run: | set -euo pipefail + export SOURCE_DATE_EPOCH="$(git log -1 --format=%ct)" name="lachesis-ui-${GITHUB_REF_NAME}-${GOOS}-${GOARCH}" mkdir -p dist go build -trimpath -ldflags='-s -w' -o "dist/$name/lachesis-ui" . cp README.md LICENSE "dist/$name/" - tar -C dist -czf "dist/$name.tar.gz" "$name" + tar -C dist --sort=name --mtime="@${SOURCE_DATE_EPOCH}" \ + --owner=0 --group=0 --numeric-owner -czf "dist/$name.tar.gz" "$name" sha256sum "dist/$name.tar.gz" >> dist/SHA256SUMS - name: Upload binary artifact uses: actions/upload-artifact@v4 diff --git a/RELEASING.md b/RELEASING.md index f5ac653..be43828 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -20,6 +20,9 @@ README and license, and writes `SHA256SUMS`. Downloaded archives must pass: sha256sum -c SHA256SUMS --ignore-missing ``` +Archive ordering, timestamps, and ownership metadata are normalized from the tagged +commit, so rebuilding the same tag produces identical archive bytes. + The workflow uploads artifacts but does not publish a GitHub release or modify a package registry. Promote the reviewed artifacts explicitly and retain the prior version for rollback. Never overwrite a published tag; cut a new patch release. From f5c8716dc4e588c980cb39be94b70ada68f552ae Mon Sep 17 00:00:00 2001 From: Riyan Dhiman Date: Fri, 21 Aug 2026 12:10:19 +0530 Subject: [PATCH 13/61] Make UI release checksums artifact-relative --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d9c517b..7dd6952 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -44,7 +44,7 @@ jobs: cp README.md LICENSE "dist/$name/" tar -C dist --sort=name --mtime="@${SOURCE_DATE_EPOCH}" \ --owner=0 --group=0 --numeric-owner -czf "dist/$name.tar.gz" "$name" - sha256sum "dist/$name.tar.gz" >> dist/SHA256SUMS + (cd dist && sha256sum "$name.tar.gz") >> dist/SHA256SUMS - name: Upload binary artifact uses: actions/upload-artifact@v4 with: From e91343dd5313047755868685d42a57decc6ec3b5 Mon Sep 17 00:00:00 2001 From: Riyan Dhiman Date: Fri, 21 Aug 2026 12:12:38 +0530 Subject: [PATCH 14/61] Protect UI installer from untracked checkout state --- README.md | 2 +- scripts/install.sh | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index b1f5ea9..ab07b93 100644 --- a/README.md +++ b/README.md @@ -95,7 +95,7 @@ Re-running `install.sh` updates the checkouts in place. Requirements: `git`, For reproducible deployments, pin the engine and catalog before installing (use reviewed tags or commit SHAs rather than mutable branches). Re-running the installer applies those refs to existing clean checkouts and refuses to touch a -checkout with local edits: +checkout with local edits or untracked files: ```sh LACHESIS_REF= ATROPOS_REF= ./scripts/install.sh diff --git a/scripts/install.sh b/scripts/install.sh index 949a1a4..249e613 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -60,8 +60,8 @@ clone_or_update() { local url="$1" dir="$2" name="$3" ref="$4" if [ -d "$dir/.git" ]; then info "updating $name" - if ! git -C "$dir" diff --quiet || ! git -C "$dir" diff --cached --quiet; then - die "$name: local changes found; commit or remove them before changing refs" + if [ -n "$(git -C "$dir" status --porcelain --untracked-files=all)" ]; then + die "$name: local changes found (including untracked files); commit or remove them before changing refs" fi git -C "$dir" fetch --depth 1 --quiet origin "$ref" \ || die "$name: could not fetch ref '$ref'" From a2a3ef57ad1954a27e057c74bfc7269aa0d52bb2 Mon Sep 17 00:00:00 2001 From: Riyan Dhiman Date: Fri, 21 Aug 2026 12:14:04 +0530 Subject: [PATCH 15/61] Stamp UI release versions into binaries --- .github/workflows/release.yml | 3 ++- Makefile | 4 +++- internal/mcp/tools.go | 6 ++++-- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7dd6952..e4ab1f0 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -38,9 +38,10 @@ jobs: run: | set -euo pipefail export SOURCE_DATE_EPOCH="$(git log -1 --format=%ct)" + version="${GITHUB_REF_NAME#v}" name="lachesis-ui-${GITHUB_REF_NAME}-${GOOS}-${GOARCH}" mkdir -p dist - go build -trimpath -ldflags='-s -w' -o "dist/$name/lachesis-ui" . + go build -trimpath -ldflags="-s -w -X github.com/UnboundCompute/lachesis-ui/internal/mcp.Version=$version" -o "dist/$name/lachesis-ui" . cp README.md LICENSE "dist/$name/" tar -C dist --sort=name --mtime="@${SOURCE_DATE_EPOCH}" \ --owner=0 --group=0 --numeric-owner -czf "dist/$name.tar.gz" "$name" diff --git a/Makefile b/Makefile index fdb095a..369f200 100644 --- a/Makefile +++ b/Makefile @@ -1,11 +1,13 @@ BIN ?= lachesis-ui PREFIX ?= $(HOME)/.lachesis/bin +VERSION ?= 0.1.0 +LDFLAGS ?= -X github.com/UnboundCompute/lachesis-ui/internal/mcp.Version=$(VERSION) .PHONY: build install run tidy fmt vet clean stack ## build the binary into the working directory build: - go build -o $(BIN) . + go build -ldflags "$(LDFLAGS)" -o $(BIN) . ## build and drop the binary into ~/.lachesis/bin install: build diff --git a/internal/mcp/tools.go b/internal/mcp/tools.go index e372361..e23c647 100644 --- a/internal/mcp/tools.go +++ b/internal/mcp/tools.go @@ -6,8 +6,10 @@ import ( "strings" ) -// Version is stamped into the client handshake and the --version flag. -const Version = "0.1.0" +// Version is stamped into the client handshake and the --version flag. Release +// builds replace it with the tag version via -ldflags; source builds retain the +// current development default. +var Version = "0.1.0" // ---- shared row shapes (as the engine emits them) ------------------------- From 375db5dcb011be79af50b28f34b9f3b5778fbd9d Mon Sep 17 00:00:00 2001 From: Riyan Dhiman Date: Fri, 21 Aug 2026 12:14:14 +0530 Subject: [PATCH 16/61] Document UI binary version verification --- RELEASING.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/RELEASING.md b/RELEASING.md index be43828..b24e7a5 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -20,6 +20,9 @@ README and license, and writes `SHA256SUMS`. Downloaded archives must pass: sha256sum -c SHA256SUMS --ignore-missing ``` +The workflow stamps each binary with the tag version; verify a downloaded binary with +`./lachesis-ui --version` before promoting it. + Archive ordering, timestamps, and ownership metadata are normalized from the tagged commit, so rebuilding the same tag produces identical archive bytes. From 56833a539b5ee37de25f8c0cba390eaade7bd583 Mon Sep 17 00:00:00 2001 From: Riyan Dhiman Date: Fri, 21 Aug 2026 12:15:35 +0530 Subject: [PATCH 17/61] Require UI release notes for tags --- .github/workflows/release.yml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e4ab1f0..5e8bf00 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -30,6 +30,18 @@ jobs: with: go-version: "1.24.2" cache: true + - name: Verify tag has changelog entry + run: | + python3 - <<'PY' + import os + from pathlib import Path + + version = os.environ["GITHUB_REF_NAME"].removeprefix("v") + heading = f"## [{version}]" + if heading not in Path("CHANGELOG.md").read_text(encoding="utf-8"): + raise SystemExit(f"CHANGELOG.md has no release heading for {version}") + print(f"changelog entry: {heading}") + PY - name: Build and package env: GOOS: ${{ matrix.goos }} From c9abeafb7554035c06c08e4e86b0329dbf910630 Mon Sep 17 00:00:00 2001 From: Riyan Dhiman Date: Fri, 21 Aug 2026 12:17:42 +0530 Subject: [PATCH 18/61] Validate UI installer Python floor early --- scripts/install.sh | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/scripts/install.sh b/scripts/install.sh index 249e613..0a20942 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -51,6 +51,14 @@ need() { command -v "$1" >/dev/null 2>&1 || die "missing required tool: $1"; } # ---- preflight ------------------------------------------------------------ need git need "$PYTHON" +"$PYTHON" - <<'PY' +import sys + +if sys.version_info < (3, 10): + raise SystemExit( + f"Python 3.10+ is required (found {sys.version_info.major}.{sys.version_info.minor})" + ) +PY command -v go >/dev/null 2>&1 || warn "go not found. Install Go 1.24.2+ to build the UI (a 'go install' fallback is tried only if you have go)" mkdir -p "$SRC" "$BIN" "$GRAPHS" From 916f6c1e3f976a65cf25b96e577d3287c63b856f Mon Sep 17 00:00:00 2001 From: Riyan Dhiman Date: Fri, 21 Aug 2026 12:18:13 +0530 Subject: [PATCH 19/61] Validate UI installer Go floor early --- scripts/install.sh | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/scripts/install.sh b/scripts/install.sh index 0a20942..e30bfdf 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -59,7 +59,23 @@ if sys.version_info < (3, 10): f"Python 3.10+ is required (found {sys.version_info.major}.{sys.version_info.minor})" ) PY -command -v go >/dev/null 2>&1 || warn "go not found. Install Go 1.24.2+ to build the UI (a 'go install' fallback is tried only if you have go)" +if command -v go >/dev/null 2>&1; then + "$PYTHON" - "$(go version)" <<'PY' +import re +import sys + +match = re.search(r"go(\d+)\.(\d+)(?:\.(\d+))?", sys.argv[1]) +if match is None: + raise SystemExit(f"could not determine Go version from: {sys.argv[1]}") +found = tuple(int(part or 0) for part in match.groups()) +if found < (1, 24, 2): + raise SystemExit( + f"Go 1.24.2+ is required (found {found[0]}.{found[1]}.{found[2]})" + ) +PY +else + warn "go not found. Install Go 1.24.2+ to build the UI (a 'go install' fallback is tried only if you have go)" +fi mkdir -p "$SRC" "$BIN" "$GRAPHS" From 7ade1dac4db702d6fd78aa87211fd4e940e97272 Mon Sep 17 00:00:00 2001 From: Riyan Dhiman Date: Fri, 21 Aug 2026 12:18:56 +0530 Subject: [PATCH 20/61] Vendor TypeScript in UI stack installer --- scripts/install.sh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/scripts/install.sh b/scripts/install.sh index e30bfdf..e5b0610 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -111,6 +111,9 @@ if [ ! -x "$VENV/bin/python" ]; then fi info "installing the engine into the virtualenv" "$VENV/bin/python" -m pip install --quiet --upgrade pip +info "vendoring the pinned TypeScript compiler" +"$PYTHON" "$SRC/lachesis/tools/vendor_typescript.py" +"$PYTHON" "$SRC/lachesis/tools/vendor_typescript.py" --check "$VENV/bin/python" -m pip install --quiet -e "$SRC/lachesis" # ---- 4. graph-build helper ------------------------------------------------ From e3478e8c3b1f92ccb6b3c5a675b25e9a49a0c861 Mon Sep 17 00:00:00 2001 From: Riyan Dhiman Date: Fri, 21 Aug 2026 12:19:06 +0530 Subject: [PATCH 21/61] Document vendored TypeScript in UI installer --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index ab07b93..e682afe 100644 --- a/README.md +++ b/README.md @@ -89,6 +89,9 @@ and lays them out as: ~/.lachesis/build-graph.sh helper to build a graph from any source tree ``` +The installer also vendors Lachesis's pinned TypeScript compiler, so the resulting +stack can analyze TypeScript without a separate Node/npm setup. + Re-running `install.sh` updates the checkouts in place. Requirements: `git`, `python3` (3.10+), and Go 1.24.2+ to build the binary. From 4bc56d8a70be122b145f64beacf48276dd45c0bd Mon Sep 17 00:00:00 2001 From: Riyan Dhiman Date: Fri, 21 Aug 2026 12:19:30 +0530 Subject: [PATCH 22/61] Add safe UI installer help handling --- scripts/install.sh | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/scripts/install.sh b/scripts/install.sh index e5b0610..f9044c0 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -29,6 +29,22 @@ # set -euo pipefail +if [ "${1:-}" = "--help" ] || [ "${1:-}" = "-h" ]; then + sed -n '2,/^set -euo pipefail$/p' "$0" + cat <<'USAGE' + +Usage: ./scripts/install.sh + +Configure refs with LACHESIS_REF, ATROPOS_REF, and LACHESIS_UI_REF, and the +installation root with LACHESIS_HOME. The script has no positional arguments. +USAGE + exit 0 +fi +if [ "$#" -ne 0 ]; then + die() { printf '\033[31merror:\033[0m %s\n' "$*" >&2; exit 1; } + die "unknown argument '$1' (try --help)" +fi + LACHESIS_HOME="${LACHESIS_HOME:-$HOME/.lachesis}" SRC="$LACHESIS_HOME/src" VENV="$LACHESIS_HOME/venv" From f954b81b01ebf276f0f1b327359b06321eafa1a5 Mon Sep 17 00:00:00 2001 From: Riyan Dhiman Date: Fri, 21 Aug 2026 12:19:58 +0530 Subject: [PATCH 23/61] Document macOS release checksum verification --- README.md | 2 ++ RELEASING.md | 6 ++++++ 2 files changed, 8 insertions(+) diff --git a/README.md b/README.md index e682afe..86e44e3 100644 --- a/README.md +++ b/README.md @@ -137,6 +137,8 @@ and verify its SHA-256 checksum before unpacking it: sha256sum -c SHA256SUMS --ignore-missing ``` +On macOS, use `shasum -a 256 -c SHA256SUMS` instead. + A Homebrew tap is still planned. --- diff --git a/RELEASING.md b/RELEASING.md index b24e7a5..4c5c3e7 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -20,6 +20,12 @@ README and license, and writes `SHA256SUMS`. Downloaded archives must pass: sha256sum -c SHA256SUMS --ignore-missing ``` +On macOS, the equivalent verification command is: + +```bash +shasum -a 256 -c SHA256SUMS +``` + The workflow stamps each binary with the tag version; verify a downloaded binary with `./lachesis-ui --version` before promoting it. From 4f75519fc4dd87c0c5383198203d22fa2763fdf5 Mon Sep 17 00:00:00 2001 From: Riyan Dhiman Date: Fri, 21 Aug 2026 12:26:01 +0530 Subject: [PATCH 24/61] Make UI release archives reproducible --- .github/workflows/release.yml | 5 ++++- RELEASING.md | 4 ++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5e8bf00..0ea9030 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -55,8 +55,11 @@ jobs: mkdir -p dist go build -trimpath -ldflags="-s -w -X github.com/UnboundCompute/lachesis-ui/internal/mcp.Version=$version" -o "dist/$name/lachesis-ui" . cp README.md LICENSE "dist/$name/" + # Normalize tar metadata and the gzip header so the same tag rebuilds + # to byte-identical artifacts. tar -C dist --sort=name --mtime="@${SOURCE_DATE_EPOCH}" \ - --owner=0 --group=0 --numeric-owner -czf "dist/$name.tar.gz" "$name" + --owner=0 --group=0 --numeric-owner -cf - "$name" \ + | gzip -n > "dist/$name.tar.gz" (cd dist && sha256sum "$name.tar.gz") >> dist/SHA256SUMS - name: Upload binary artifact uses: actions/upload-artifact@v4 diff --git a/RELEASING.md b/RELEASING.md index 4c5c3e7..d8f1061 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -29,8 +29,8 @@ shasum -a 256 -c SHA256SUMS The workflow stamps each binary with the tag version; verify a downloaded binary with `./lachesis-ui --version` before promoting it. -Archive ordering, timestamps, and ownership metadata are normalized from the tagged -commit, so rebuilding the same tag produces identical archive bytes. +Archive ordering, timestamps, ownership metadata, and the gzip header are normalized +from the tagged commit, so rebuilding the same tag produces identical archive bytes. The workflow uploads artifacts but does not publish a GitHub release or modify a package registry. Promote the reviewed artifacts explicitly and retain the prior From 8138f861ad3c296e37d05b38756a789e2668731a Mon Sep 17 00:00:00 2001 From: Riyan Dhiman Date: Fri, 21 Aug 2026 12:30:40 +0530 Subject: [PATCH 25/61] Honor custom install roots in graph helper --- README.md | 7 +++++++ scripts/install.sh | 18 +++++++++--------- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 86e44e3..be70961 100644 --- a/README.md +++ b/README.md @@ -95,6 +95,13 @@ stack can analyze TypeScript without a separate Node/npm setup. Re-running `install.sh` updates the checkouts in place. Requirements: `git`, `python3` (3.10+), and Go 1.24.2+ to build the binary. +To keep the stack outside your home directory (for example, on a CI volume), set +`LACHESIS_HOME`; the generated graph helper uses the same root: + +```sh +LACHESIS_HOME=/var/cache/lachesis ./scripts/install.sh +``` + For reproducible deployments, pin the engine and catalog before installing (use reviewed tags or commit SHAs rather than mutable branches). Re-running the installer applies those refs to existing clean checkouts and refuses to touch a diff --git a/scripts/install.sh b/scripts/install.sh index f9044c0..26674a4 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -134,18 +134,18 @@ info "vendoring the pinned TypeScript compiler" # ---- 4. graph-build helper ------------------------------------------------ info "writing $LACHESIS_HOME/build-graph.sh" -cat > "$LACHESIS_HOME/build-graph.sh" <<'HELPER' +cat > "$LACHESIS_HOME/build-graph.sh" < [name] -# Builds a lachesis graph and prints the store path (~/.lachesis/graphs/.kuzu). +# Builds a lachesis graph and prints the store path. set -euo pipefail -SRC="${1:?usage: build-graph.sh [name]}" -SRC="$(cd "$SRC" && pwd)" -NAME="${2:-$(basename "$SRC")}" -OUT="$HOME/.lachesis/graphs/$NAME.kuzu" -export ATROPOS_ROOT="${ATROPOS_ROOT:-$HOME/.lachesis/src/atropos}" -"$HOME/.lachesis/venv/bin/lachesis-analyze" "$SRC" "$OUT" --prune --timeout 3600 -echo "$OUT" +SOURCE_DIR="\${1:?usage: build-graph.sh [name]}" +SOURCE_DIR="\$(cd "\$SOURCE_DIR" && pwd)" +NAME="\${2:-\$(basename "\$SOURCE_DIR")}" +OUT="$GRAPHS/\$NAME.kuzu" +export ATROPOS_ROOT="\${ATROPOS_ROOT:-$SRC/atropos}" +"$VENV/bin/lachesis-analyze" "\$SOURCE_DIR" "\$OUT" --prune --timeout 3600 +echo "\$OUT" HELPER chmod +x "$LACHESIS_HOME/build-graph.sh" From a52ed410cd5ba5d4dfe2f672db190c2b77219256 Mon Sep 17 00:00:00 2001 From: Riyan Dhiman Date: Fri, 21 Aug 2026 12:37:17 +0530 Subject: [PATCH 26/61] Verify UI archive reproducibility in release CI --- .github/workflows/release.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0ea9030..8f1735e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -60,6 +60,11 @@ jobs: tar -C dist --sort=name --mtime="@${SOURCE_DATE_EPOCH}" \ --owner=0 --group=0 --numeric-owner -cf - "$name" \ | gzip -n > "dist/$name.tar.gz" + sha256sum "dist/$name.tar.gz" > "/tmp/${name}.first.sha256" + tar -C dist --sort=name --mtime="@${SOURCE_DATE_EPOCH}" \ + --owner=0 --group=0 --numeric-owner -cf - "$name" \ + | gzip -n > "dist/$name.tar.gz" + sha256sum -c "/tmp/${name}.first.sha256" (cd dist && sha256sum "$name.tar.gz") >> dist/SHA256SUMS - name: Upload binary artifact uses: actions/upload-artifact@v4 From 9d9f5549cbd262e414c040618293136abc93b70e Mon Sep 17 00:00:00 2001 From: Riyan Dhiman Date: Fri, 21 Aug 2026 12:38:45 +0530 Subject: [PATCH 27/61] Correct UI engine install guidance --- main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.go b/main.go index 0887230..0b8181c 100644 --- a/main.go +++ b/main.go @@ -51,7 +51,7 @@ func main() { if err != nil { fatal(fmt.Sprintf("could not start the engine: %v\n\n"+ "The UI needs the lachesis engine (Python). Install it, or point at it:\n"+ - " pip install lachesis # or your project's install\n"+ + " pip install lachesis-cpg # or your project's install\n"+ " lachesis-ui --python /path/to/python\n"+ " LACHESIS_PYTHON=/path/to/python lachesis-ui", err)) } From e7e40f611be4b3e5a046db41a270ac9e3f9c11ba Mon Sep 17 00:00:00 2001 From: Riyan Dhiman Date: Fri, 21 Aug 2026 12:40:08 +0530 Subject: [PATCH 28/61] Honor custom Lachesis roots in UI discovery --- README.md | 2 +- internal/mcp/engine.go | 13 +++++++++++-- internal/mcp/engine_test.go | 39 +++++++++++++++++++++++++++++++++++++ 3 files changed, 51 insertions(+), 3 deletions(-) create mode 100644 internal/mcp/engine_test.go diff --git a/README.md b/README.md index be70961..f0741ed 100644 --- a/README.md +++ b/README.md @@ -96,7 +96,7 @@ Re-running `install.sh` updates the checkouts in place. Requirements: `git`, `python3` (3.10+), and Go 1.24.2+ to build the binary. To keep the stack outside your home directory (for example, on a CI volume), set -`LACHESIS_HOME`; the generated graph helper uses the same root: +`LACHESIS_HOME`; the generated graph helper and UI discovery use the same root: ```sh LACHESIS_HOME=/var/cache/lachesis ./scripts/install.sh diff --git a/internal/mcp/engine.go b/internal/mcp/engine.go index a77d9c1..42a1628 100644 --- a/internal/mcp/engine.go +++ b/internal/mcp/engine.go @@ -18,8 +18,14 @@ func DiscoverPython() string { if p := os.Getenv("LACHESIS_PYTHON"); p != "" { return p } - if home, err := os.UserHomeDir(); err == nil { - venv := filepath.Join(home, ".lachesis", "venv", "bin", "python") + installRoot := os.Getenv("LACHESIS_HOME") + if installRoot == "" { + if home, err := os.UserHomeDir(); err == nil { + installRoot = filepath.Join(home, ".lachesis") + } + } + if installRoot != "" { + venv := filepath.Join(installRoot, "venv", "bin", "python") if fileExists(venv) { return venv } @@ -37,6 +43,9 @@ func GraphsDir() string { if d := os.Getenv("LACHESIS_GRAPHS_DIR"); d != "" { return d } + if root := os.Getenv("LACHESIS_HOME"); root != "" { + return filepath.Join(root, "graphs") + } home, err := os.UserHomeDir() if err != nil { return ".lachesis/graphs" diff --git a/internal/mcp/engine_test.go b/internal/mcp/engine_test.go new file mode 100644 index 0000000..089bb22 --- /dev/null +++ b/internal/mcp/engine_test.go @@ -0,0 +1,39 @@ +package mcp + +import ( + "os" + "path/filepath" + "testing" +) + +func TestGraphsDirHonorsInstallRoot(t *testing.T) { + t.Setenv("LACHESIS_GRAPHS_DIR", "") + t.Setenv("LACHESIS_HOME", "/tmp/lachesis-install") + if got, want := GraphsDir(), filepath.Join("/tmp/lachesis-install", "graphs"); got != want { + t.Fatalf("GraphsDir() = %q, want %q", got, want) + } +} + +func TestGraphsDirExplicitOverrideWins(t *testing.T) { + t.Setenv("LACHESIS_HOME", "/tmp/lachesis-install") + t.Setenv("LACHESIS_GRAPHS_DIR", "/tmp/custom-graphs") + if got, want := GraphsDir(), "/tmp/custom-graphs"; got != want { + t.Fatalf("GraphsDir() = %q, want %q", got, want) + } +} + +func TestDiscoverPythonHonorsInstallRoot(t *testing.T) { + root := t.TempDir() + venvPython := filepath.Join(root, "venv", "bin", "python") + if err := os.MkdirAll(filepath.Dir(venvPython), 0o755); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(venvPython, []byte(""), 0o755); err != nil { + t.Fatal(err) + } + t.Setenv("LACHESIS_PYTHON", "") + t.Setenv("LACHESIS_HOME", root) + if got := DiscoverPython(); got != venvPython { + t.Fatalf("DiscoverPython() = %q, want %q", got, venvPython) + } +} From 508dee1bda681df6e8b3f8ef65f1875008185fd3 Mon Sep 17 00:00:00 2001 From: Riyan Dhiman Date: Fri, 21 Aug 2026 12:43:39 +0530 Subject: [PATCH 29/61] Ignore non-directory graph artifacts in UI --- internal/mcp/engine.go | 3 +++ internal/mcp/engine_test.go | 15 +++++++++++++++ 2 files changed, 18 insertions(+) diff --git a/internal/mcp/engine.go b/internal/mcp/engine.go index 42a1628..e81d684 100644 --- a/internal/mcp/engine.go +++ b/internal/mcp/engine.go @@ -77,6 +77,9 @@ func ListGraphs() []Graph { if !strings.HasSuffix(name, ".kuzu") { continue // skips ".kuzu.enriched" and everything else } + if !e.IsDir() { + continue // ignore partial files and other non-store artifacts + } info, err := e.Info() var mod int64 if err == nil { diff --git a/internal/mcp/engine_test.go b/internal/mcp/engine_test.go index 089bb22..7e7e391 100644 --- a/internal/mcp/engine_test.go +++ b/internal/mcp/engine_test.go @@ -37,3 +37,18 @@ func TestDiscoverPythonHonorsInstallRoot(t *testing.T) { t.Fatalf("DiscoverPython() = %q, want %q", got, venvPython) } } + +func TestListGraphsSkipsNonStoreArtifacts(t *testing.T) { + root := t.TempDir() + if err := os.Mkdir(filepath.Join(root, "valid.kuzu"), 0o755); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(filepath.Join(root, "partial.kuzu"), []byte("incomplete"), 0o644); err != nil { + t.Fatal(err) + } + t.Setenv("LACHESIS_GRAPHS_DIR", root) + graphs := ListGraphs() + if len(graphs) != 1 || graphs[0].Name != "valid" { + t.Fatalf("ListGraphs() = %#v, want only valid.kuzu", graphs) + } +} From 9d9b06fc75583b72a4438ae805b82a3bca3a05fc Mon Sep 17 00:00:00 2001 From: Riyan Dhiman Date: Fri, 21 Aug 2026 12:44:03 +0530 Subject: [PATCH 30/61] Reject non-directory graph paths early --- main.go | 6 +++++- main_test.go | 19 +++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) create mode 100644 main_test.go diff --git a/main.go b/main.go index 0b8181c..631294b 100644 --- a/main.go +++ b/main.go @@ -75,9 +75,13 @@ func resolveGraph(flagVal string, args []string) (path, name string, err error) if aerr != nil { return "", "", aerr } - if _, serr := os.Stat(abs); serr != nil { + info, serr := os.Stat(abs) + if serr != nil { return "", "", fmt.Errorf("graph not found: %s", candidate) } + if !info.IsDir() { + return "", "", fmt.Errorf("graph store is not a directory: %s", candidate) + } return abs, graphNameOf(abs), nil } graphs := mcp.ListGraphs() diff --git a/main_test.go b/main_test.go new file mode 100644 index 0000000..130cb31 --- /dev/null +++ b/main_test.go @@ -0,0 +1,19 @@ +package main + +import ( + "os" + "path/filepath" + "strings" + "testing" +) + +func TestResolveGraphRejectsRegularFiles(t *testing.T) { + path := filepath.Join(t.TempDir(), "partial.kuzu") + if err := os.WriteFile(path, []byte("partial"), 0o644); err != nil { + t.Fatal(err) + } + _, _, err := resolveGraph(path, nil) + if err == nil || !strings.Contains(err.Error(), "not a directory") { + t.Fatalf("resolveGraph() error = %v, want a directory diagnostic", err) + } +} From bd0bac943e9ab46c353a24ad07218f9ab4a60a3c Mon Sep 17 00:00:00 2001 From: Riyan Dhiman Date: Fri, 21 Aug 2026 12:45:22 +0530 Subject: [PATCH 31/61] Bound UI engine startup time --- README.md | 5 +++++ internal/mcp/client.go | 28 ++++++++++++++++++++++++++-- internal/mcp/engine_test.go | 12 ++++++++++++ 3 files changed, 43 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index f0741ed..4a78885 100644 --- a/README.md +++ b/README.md @@ -216,6 +216,11 @@ lachesis-ui [flags] [graph.kuzu] **Engine discovery** order: `$LACHESIS_PYTHON`, then `~/.lachesis/venv/bin/python`, then `python3` or `python` on PATH. +Engine startup is bounded to five minutes by default while a large graph loads. Set +`LACHESIS_UI_STARTUP_TIMEOUT` to a Go duration such as `10m` when a larger codebase +needs more time; an expired startup terminates the child and reports an actionable +error. + --- ## Contributing diff --git a/internal/mcp/client.go b/internal/mcp/client.go index 74cf2ed..ac13db7 100644 --- a/internal/mcp/client.go +++ b/internal/mcp/client.go @@ -13,6 +13,7 @@ import ( "encoding/json" "fmt" "io" + "os" "os/exec" "strings" "sync" @@ -78,11 +79,34 @@ func Spawn(python, graphPath string) (*Client, error) { } go c.drainStderr(stderr) - if err := c.initialize(); err != nil { + timeout := startupTimeout() + ready := make(chan error, 1) + go func() { ready <- c.initialize() }() + select { + case err := <-ready: + if err == nil { + return c, nil + } _ = c.Close() return nil, err + case <-time.After(timeout): + _ = c.Close() + return nil, fmt.Errorf("engine initialization timed out after %s", timeout) + } +} + +const defaultStartupTimeout = 5 * time.Minute + +func startupTimeout() time.Duration { + value := os.Getenv("LACHESIS_UI_STARTUP_TIMEOUT") + if value == "" { + return defaultStartupTimeout + } + timeout, err := time.ParseDuration(value) + if err != nil || timeout <= 0 { + return defaultStartupTimeout } - return c, nil + return timeout } func (c *Client) initialize() error { diff --git a/internal/mcp/engine_test.go b/internal/mcp/engine_test.go index 7e7e391..5c36ffa 100644 --- a/internal/mcp/engine_test.go +++ b/internal/mcp/engine_test.go @@ -4,6 +4,7 @@ import ( "os" "path/filepath" "testing" + "time" ) func TestGraphsDirHonorsInstallRoot(t *testing.T) { @@ -52,3 +53,14 @@ func TestListGraphsSkipsNonStoreArtifacts(t *testing.T) { t.Fatalf("ListGraphs() = %#v, want only valid.kuzu", graphs) } } + +func TestStartupTimeoutIsConfigurable(t *testing.T) { + t.Setenv("LACHESIS_UI_STARTUP_TIMEOUT", "17s") + if got, want := startupTimeout(), 17*time.Second; got != want { + t.Fatalf("startupTimeout() = %s, want %s", got, want) + } + t.Setenv("LACHESIS_UI_STARTUP_TIMEOUT", "not-a-duration") + if got := startupTimeout(); got != defaultStartupTimeout { + t.Fatalf("invalid timeout = %s, want default %s", got, defaultStartupTimeout) + } +} From d64d4636a5a59e75dacd2c44a2d9690e036be512 Mon Sep 17 00:00:00 2001 From: Riyan Dhiman Date: Fri, 21 Aug 2026 12:48:39 +0530 Subject: [PATCH 32/61] Document UI runtime safeguards --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5f97581..f9454a9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,10 @@ and this project aims to follow [Semantic Versioning](https://semver.org/spec/v2 - The Tree keys now match a nested tree: right expands a folder, left collapses it or jumps to its parent, and enter opens a folder or a symbol's neighborhood. +- Custom `LACHESIS_HOME` installs now work end-to-end, including UI engine and graph + discovery. +- Engine startup is bounded by a configurable timeout, and invalid graph artifacts + are rejected with actionable diagnostics. ## [0.1.0] From b26596bdff376e39d76dd27296a3d96e3acc8f42 Mon Sep 17 00:00:00 2001 From: Riyan Dhiman Date: Fri, 21 Aug 2026 12:50:26 +0530 Subject: [PATCH 33/61] Stamp version into installer-built UI --- README.md | 7 +++++++ scripts/install.sh | 6 +++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 4a78885..f8a9a97 100644 --- a/README.md +++ b/README.md @@ -114,6 +114,13 @@ LACHESIS_REF= ATROPOS_REF= ./scripts/in The installer pins its binary fallback to `v0.1.0`; set `LACHESIS_UI_REF` to a reviewed tag or commit when selecting another UI release. +When building from the checkout, set `LACHESIS_UI_VERSION` if the binary should report +a version other than the default `0.1.0`: + +```sh +LACHESIS_UI_VERSION=1.2.0 ./scripts/install.sh +``` + Then put the binary on your PATH: ```sh diff --git a/scripts/install.sh b/scripts/install.sh index 26674a4..d301d90 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -26,6 +26,7 @@ # LACHESIS_HOME install root (default: ~/.lachesis) # PYTHON python to build the venv (default: python3) # LACHESIS_UI_REF release tag/commit for the go-install fallback (default: v0.1.0) +# LACHESIS_UI_VERSION version stamped into a source-built binary (default: 0.1.0) # set -euo pipefail @@ -54,6 +55,7 @@ PYTHON="${PYTHON:-python3}" LACHESIS_REF="${LACHESIS_REF:-main}" ATROPOS_REF="${ATROPOS_REF:-main}" LACHESIS_UI_REF="${LACHESIS_UI_REF:-v0.1.0}" +LACHESIS_UI_VERSION="${LACHESIS_UI_VERSION:-0.1.0}" LACHESIS_REPO="https://github.com/UnboundCompute/lachesis.git" ATROPOS_REPO="https://github.com/UnboundCompute/atropos.git" @@ -154,7 +156,9 @@ HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" if command -v go >/dev/null 2>&1; then if [ -f "$HERE/main.go" ]; then info "building lachesis-ui from source checkout" - (cd "$HERE" && go build -o "$BIN/lachesis-ui" .) + (cd "$HERE" && go build -trimpath \ + -ldflags="-s -w -X github.com/UnboundCompute/lachesis-ui/internal/mcp.Version=$LACHESIS_UI_VERSION" \ + -o "$BIN/lachesis-ui" .) else info "installing lachesis-ui via go install" GOBIN="$BIN" go install "github.com/UnboundCompute/lachesis-ui@$LACHESIS_UI_REF" From f22e718e6c24be7b35a7ca8bd0db0ccf13fac0f2 Mon Sep 17 00:00:00 2001 From: Riyan Dhiman Date: Fri, 21 Aug 2026 12:51:03 +0530 Subject: [PATCH 34/61] Fail fast when UI build toolchain is missing --- scripts/install.sh | 25 +++++++++---------------- 1 file changed, 9 insertions(+), 16 deletions(-) diff --git a/scripts/install.sh b/scripts/install.sh index d301d90..1eb6fe8 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -77,8 +77,8 @@ if sys.version_info < (3, 10): f"Python 3.10+ is required (found {sys.version_info.major}.{sys.version_info.minor})" ) PY -if command -v go >/dev/null 2>&1; then - "$PYTHON" - "$(go version)" <<'PY' +need go +"$PYTHON" - "$(go version)" <<'PY' import re import sys @@ -91,9 +91,6 @@ if found < (1, 24, 2): f"Go 1.24.2+ is required (found {found[0]}.{found[1]}.{found[2]})" ) PY -else - warn "go not found. Install Go 1.24.2+ to build the UI (a 'go install' fallback is tried only if you have go)" -fi mkdir -p "$SRC" "$BIN" "$GRAPHS" @@ -153,18 +150,14 @@ chmod +x "$LACHESIS_HOME/build-graph.sh" # ---- 5. build the UI ------------------------------------------------------ HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" -if command -v go >/dev/null 2>&1; then - if [ -f "$HERE/main.go" ]; then - info "building lachesis-ui from source checkout" - (cd "$HERE" && go build -trimpath \ - -ldflags="-s -w -X github.com/UnboundCompute/lachesis-ui/internal/mcp.Version=$LACHESIS_UI_VERSION" \ - -o "$BIN/lachesis-ui" .) - else - info "installing lachesis-ui via go install" - GOBIN="$BIN" go install "github.com/UnboundCompute/lachesis-ui@$LACHESIS_UI_REF" - fi +if [ -f "$HERE/main.go" ]; then + info "building lachesis-ui from source checkout" + (cd "$HERE" && go build -trimpath \ + -ldflags="-s -w -X github.com/UnboundCompute/lachesis-ui/internal/mcp.Version=$LACHESIS_UI_VERSION" \ + -o "$BIN/lachesis-ui" .) else - warn "skipped building the UI (no go). Install Go and run: go build -o $BIN/lachesis-ui ." + info "installing lachesis-ui via go install" + GOBIN="$BIN" go install "github.com/UnboundCompute/lachesis-ui@$LACHESIS_UI_REF" fi # ---- done ----------------------------------------------------------------- From 2816d53145ad8d1ea654e9d374bb5138738f9577 Mon Sep 17 00:00:00 2001 From: Riyan Dhiman Date: Fri, 21 Aug 2026 12:51:28 +0530 Subject: [PATCH 35/61] Serialize UI installer runs --- README.md | 2 ++ scripts/install.sh | 8 ++++++++ 2 files changed, 10 insertions(+) diff --git a/README.md b/README.md index f8a9a97..df2e712 100644 --- a/README.md +++ b/README.md @@ -94,6 +94,8 @@ stack can analyze TypeScript without a separate Node/npm setup. Re-running `install.sh` updates the checkouts in place. Requirements: `git`, `python3` (3.10+), and Go 1.24.2+ to build the binary. +The installer takes an atomic lock, so concurrent invocations fail safely rather than +mutating the shared virtualenv and checkouts at the same time. To keep the stack outside your home directory (for example, on a CI volume), set `LACHESIS_HOME`; the generated graph helper and UI discovery use the same root: diff --git a/scripts/install.sh b/scripts/install.sh index 1eb6fe8..59ebe22 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -94,6 +94,14 @@ PY mkdir -p "$SRC" "$BIN" "$GRAPHS" +# A second installer can otherwise race a checkout update or pip install and leave +# the shared stack half-written. mkdir is atomic and available on macOS and Linux. +INSTALL_LOCK="$LACHESIS_HOME/.install.lock" +if ! mkdir "$INSTALL_LOCK" 2>/dev/null; then + die "another Lachesis installation is already running at $LACHESIS_HOME" +fi +trap 'rmdir "$INSTALL_LOCK" 2>/dev/null || true' EXIT + # ---- 1 + 2. clone/update engine and catalog (side by side) ---------------- clone_or_update() { local url="$1" dir="$2" name="$3" ref="$4" From c6b181b15b876bdd08380886806534596c340ea3 Mon Sep 17 00:00:00 2001 From: Riyan Dhiman Date: Fri, 21 Aug 2026 12:52:12 +0530 Subject: [PATCH 36/61] Recover stale UI installer locks safely --- scripts/install.sh | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/scripts/install.sh b/scripts/install.sh index 59ebe22..9db3f10 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -98,9 +98,20 @@ mkdir -p "$SRC" "$BIN" "$GRAPHS" # the shared stack half-written. mkdir is atomic and available on macOS and Linux. INSTALL_LOCK="$LACHESIS_HOME/.install.lock" if ! mkdir "$INSTALL_LOCK" 2>/dev/null; then - die "another Lachesis installation is already running at $LACHESIS_HOME" + owner_pid="" + if [ -f "$INSTALL_LOCK/pid" ]; then + read -r owner_pid < "$INSTALL_LOCK/pid" || true + fi + if [ -n "$owner_pid" ] && ! kill -0 "$owner_pid" 2>/dev/null; then + rm -f "$INSTALL_LOCK/pid" + rmdir "$INSTALL_LOCK" 2>/dev/null || true + fi + if ! mkdir "$INSTALL_LOCK" 2>/dev/null; then + die "another Lachesis installation is already running at $LACHESIS_HOME" + fi fi -trap 'rmdir "$INSTALL_LOCK" 2>/dev/null || true' EXIT +printf '%s\n' "$$" > "$INSTALL_LOCK/pid" +trap 'rm -f "$INSTALL_LOCK/pid"; rmdir "$INSTALL_LOCK" 2>/dev/null || true' EXIT # ---- 1 + 2. clone/update engine and catalog (side by side) ---------------- clone_or_update() { From 400cfdbf06c0cca3cd53e3247b7766d8ca9a5b16 Mon Sep 17 00:00:00 2001 From: Riyan Dhiman Date: Fri, 21 Aug 2026 12:55:07 +0530 Subject: [PATCH 37/61] Pin UI CI actions to immutable SHAs --- .github/workflows/ci.yml | 4 ++-- .github/workflows/release.yml | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3b6f9f7..5b16733 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,10 +20,10 @@ jobs: timeout-minutes: 10 steps: - name: Check out - uses: actions/checkout@v4 + uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 - name: Set up Go - uses: actions/setup-go@v5 + uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5 with: go-version: '1.24.2' diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 8f1735e..a081bf7 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -25,8 +25,8 @@ jobs: - goos: darwin goarch: arm64 steps: - - uses: actions/checkout@v4 - - uses: actions/setup-go@v5 + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 + - uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5 with: go-version: "1.24.2" cache: true @@ -67,7 +67,7 @@ jobs: sha256sum -c "/tmp/${name}.first.sha256" (cd dist && sha256sum "$name.tar.gz") >> dist/SHA256SUMS - name: Upload binary artifact - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: lachesis-ui-${{ github.ref_name }}-${{ matrix.goos }}-${{ matrix.goarch }} path: dist/ From 4ddc22623c64b0e8f678b40cdcdcc7e7b31a283d Mon Sep 17 00:00:00 2001 From: Riyan Dhiman Date: Fri, 21 Aug 2026 13:04:42 +0530 Subject: [PATCH 38/61] Keep generated graph paths inside graph root --- CHANGELOG.md | 3 +++ README.md | 6 +++++- scripts/install.sh | 6 ++++++ 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f9454a9..c89e53a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,9 @@ and this project aims to follow [Semantic Versioning](https://semver.org/spec/v2 ### Changed +- The generated `build-graph.sh` helper rejects path-like graph names so output + cannot escape the configured graph directory. + - The Tree keys now match a nested tree: right expands a folder, left collapses it or jumps to its parent, and enter opens a folder or a symbol's neighborhood. diff --git a/README.md b/README.md index df2e712..db59db0 100644 --- a/README.md +++ b/README.md @@ -165,7 +165,7 @@ Build a graph from any source tree, then launch: ```sh # 1. build a graph (prints the store path) -~/.lachesis/build-graph.sh /path/to/some/repo +~/.lachesis/build-graph.sh /path/to/some/repo [name] # 2. open it. With no args, the UI picks the newest graph you've built lachesis-ui @@ -174,6 +174,10 @@ lachesis-ui lachesis-ui --graph ~/.lachesis/graphs/somerepo.kuzu ``` +The optional `name` is a single graph-name component; `/`, `\\`, `.`, and `..` are +rejected so output stays under `~/.lachesis/graphs` (or your configured +`LACHESIS_HOME`). + The first screen you touch triggers a one-time graph load (a few seconds for a large tree); after that every move is instant. diff --git a/scripts/install.sh b/scripts/install.sh index 9db3f10..28c9765 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -160,6 +160,12 @@ set -euo pipefail SOURCE_DIR="\${1:?usage: build-graph.sh [name]}" SOURCE_DIR="\$(cd "\$SOURCE_DIR" && pwd)" NAME="\${2:-\$(basename "\$SOURCE_DIR")}" +case "\$NAME" in + ""|"."|".."|*/*|*\\*) + echo "error: graph name must be a single path component (no '/' or '\\')" >&2 + exit 2 + ;; +esac OUT="$GRAPHS/\$NAME.kuzu" export ATROPOS_ROOT="\${ATROPOS_ROOT:-$SRC/atropos}" "$VENV/bin/lachesis-analyze" "\$SOURCE_DIR" "\$OUT" --prune --timeout 3600 From d03f50dc78e0d6f464152ef896054f0743c74bb6 Mon Sep 17 00:00:00 2001 From: Riyan Dhiman Date: Fri, 21 Aug 2026 13:13:33 +0530 Subject: [PATCH 39/61] Add one-command UI developer gate --- .github/workflows/ci.yml | 20 ++------------------ CHANGELOG.md | 3 +++ CONTRIBUTING.md | 9 ++++----- Makefile | 15 ++++++++++++++- 4 files changed, 23 insertions(+), 24 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5b16733..699e647 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -27,21 +27,5 @@ jobs: with: go-version: '1.24.2' - - name: Verify gofmt - run: | - unformatted=$(gofmt -l .) - if [ -n "$unformatted" ]; then - echo "These files are not gofmt-clean:" - echo "$unformatted" - echo "Run 'make fmt' and commit the result." - exit 1 - fi - - - name: go vet - run: go vet ./... - - - name: Build - run: go build ./... - - - name: Test - run: go test ./... + - name: Run the local gate + run: make check diff --git a/CHANGELOG.md b/CHANGELOG.md index c89e53a..19d147a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,9 @@ and this project aims to follow [Semantic Versioning](https://semver.org/spec/v2 ### Changed +- Added a single `make check` developer/CI gate covering formatting, vet, build, + and tests. + - The generated `build-graph.sh` helper rejects path-like graph names so output cannot escape the configured graph directory. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 40c6cc3..b340be2 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -64,15 +64,14 @@ in the screen's own file. ## Before you open a pull request -Run the local checks. CI runs the same ones and will block a merge if any fail. +Run the local gate. CI runs the same command and will block a merge if any check fails. ```sh -make fmt # gofmt: formatting must be clean -make vet # go vet: no reported problems -make build # the binary must build -go test ./... # tests must pass +make check # gofmt check, vet, build, and tests ``` +Use `make fmt` when the formatting check reports files that need rewriting. + Guidelines: - Keep formatting gofmt-clean. CI fails on any file gofmt would rewrite. diff --git a/Makefile b/Makefile index 369f200..8f2b819 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ PREFIX ?= $(HOME)/.lachesis/bin VERSION ?= 0.1.0 LDFLAGS ?= -X github.com/UnboundCompute/lachesis-ui/internal/mcp.Version=$(VERSION) -.PHONY: build install run tidy fmt vet clean stack +.PHONY: build install run tidy fmt vet check clean stack ## build the binary into the working directory build: @@ -31,5 +31,18 @@ fmt: vet: go vet ./... +## run the complete local gate used by CI +check: + @unformatted="$$(gofmt -l .)"; \ + if [ -n "$$unformatted" ]; then \ + echo "These files are not gofmt-clean:"; \ + echo "$$unformatted"; \ + echo "Run 'make fmt' and commit the result."; \ + exit 1; \ + fi + go vet ./... + go build ./... + go test ./... + clean: rm -f $(BIN) From f1e1d9d311b734e0bf6ea061c93e8a49fe100afb Mon Sep 17 00:00:00 2001 From: Riyan Dhiman Date: Fri, 21 Aug 2026 13:17:39 +0530 Subject: [PATCH 40/61] Document UI developer gate --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index db59db0..e63b1a1 100644 --- a/README.md +++ b/README.md @@ -242,6 +242,8 @@ Contributions are welcome. See [CONTRIBUTING.md](CONTRIBUTING.md) for how to set up the stack, the code layout, and what CI checks before a merge. By participating you agree to the [Code of Conduct](CODE_OF_CONDUCT.md). +From a checkout, `make check` runs the same formatting, vet, build, and test gate as CI. + --- ## License From 547d1d1943bba7e4bd7f2ea135d2c6c548faa0b6 Mon Sep 17 00:00:00 2001 From: Riyan Dhiman Date: Fri, 21 Aug 2026 13:32:21 +0530 Subject: [PATCH 41/61] Surface MCP engine diagnostics in UI --- CHANGELOG.md | 4 ++++ internal/mcp/client.go | 23 +++++++++++++++++++++-- internal/mcp/engine_test.go | 23 +++++++++++++++++++++++ 3 files changed, 48 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 19d147a..df7432d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,10 @@ and this project aims to follow [Semantic Versioning](https://semver.org/spec/v2 ### Changed +- MCP startup and request failures now include the bounded recent engine stderr + tail, making wrong-interpreter, missing-graph, and import failures visible in + the UI instead of looking like a silent server exit. + - Added a single `make check` developer/CI gate covering formatting, vet, build, and tests. diff --git a/internal/mcp/client.go b/internal/mcp/client.go index ac13db7..db0c5af 100644 --- a/internal/mcp/client.go +++ b/internal/mcp/client.go @@ -87,14 +87,33 @@ func Spawn(python, graphPath string) (*Client, error) { if err == nil { return c, nil } + err = withEngineLogs(err, c.Logs()) _ = c.Close() return nil, err case <-time.After(timeout): + diagnostic := withEngineLogs( + fmt.Errorf("engine initialization timed out after %s", timeout), + c.Logs(), + ) _ = c.Close() - return nil, fmt.Errorf("engine initialization timed out after %s", timeout) + return nil, diagnostic } } +// withEngineLogs keeps startup failures actionable without putting stderr on the +// MCP stdout channel. In particular, a wrong interpreter/cwd and an unreadable +// graph otherwise look identical to a client as a silent process exit. +func withEngineLogs(err error, logs []string) error { + if len(logs) == 0 { + return err + } + const maxLines = 20 + if len(logs) > maxLines { + logs = logs[len(logs)-maxLines:] + } + return fmt.Errorf("%w\nengine stderr:\n%s", err, strings.Join(logs, "\n")) +} + const defaultStartupTimeout = 5 * time.Minute func startupTimeout() time.Duration { @@ -195,7 +214,7 @@ func (c *Client) request(method string, params any) (json.RawMessage, error) { line, err := c.stdout.ReadBytes('\n') if err != nil { if len(line) == 0 { - return nil, fmt.Errorf("read %s: %w", method, err) + return nil, withEngineLogs(fmt.Errorf("read %s: %w", method, err), c.Logs()) } } line = trimLine(line) diff --git a/internal/mcp/engine_test.go b/internal/mcp/engine_test.go index 5c36ffa..e77f000 100644 --- a/internal/mcp/engine_test.go +++ b/internal/mcp/engine_test.go @@ -1,8 +1,10 @@ package mcp import ( + "errors" "os" "path/filepath" + "strings" "testing" "time" ) @@ -64,3 +66,24 @@ func TestStartupTimeoutIsConfigurable(t *testing.T) { t.Fatalf("invalid timeout = %s, want default %s", got, defaultStartupTimeout) } } + +func TestWithEngineLogsIncludesRecentDiagnostics(t *testing.T) { + err := withEngineLogs(errors.New("startup failed"), []string{"[lachesis-mcp] starting", "graph missing"}) + if got := err.Error(); got != "startup failed\nengine stderr:\n[lachesis-mcp] starting\ngraph missing" { + t.Fatalf("withEngineLogs() = %q", got) + } +} + +func TestWithEngineLogsBoundsOutput(t *testing.T) { + logs := make([]string, 25) + for i := range logs { + logs[i] = filepath.Base(t.TempDir()) + } + got := withEngineLogs(os.ErrClosed, logs).Error() + if len(got) == 0 { + t.Fatal("withEngineLogs returned an empty error") + } + if count := len(strings.Split(got, "\n")); count > 22 { + t.Fatalf("diagnostic grew beyond bounded stderr tail: %d lines", count) + } +} From 7f25cc81759cfc55af78e24a983de31148a0ef01 Mon Sep 17 00:00:00 2001 From: Riyan Dhiman Date: Fri, 21 Aug 2026 13:33:03 +0530 Subject: [PATCH 42/61] Clarify missing source errors in graph helper --- CHANGELOG.md | 3 +++ scripts/install.sh | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index df7432d..5b8e2d4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,9 @@ and this project aims to follow [Semantic Versioning](https://semver.org/spec/v2 - The generated `build-graph.sh` helper rejects path-like graph names so output cannot escape the configured graph directory. +- The generated graph helper now reports a clear error for a missing source + directory before attempting to resolve it. + - The Tree keys now match a nested tree: right expands a folder, left collapses it or jumps to its parent, and enter opens a folder or a symbol's neighborhood. diff --git a/scripts/install.sh b/scripts/install.sh index 28c9765..4b503f1 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -158,6 +158,10 @@ cat > "$LACHESIS_HOME/build-graph.sh" <&2 + exit 2 +fi SOURCE_DIR="\$(cd "\$SOURCE_DIR" && pwd)" NAME="\${2:-\$(basename "\$SOURCE_DIR")}" case "\$NAME" in From 5937296bc84914c98a5fd71635f0e3fbcb86e252 Mon Sep 17 00:00:00 2001 From: Riyan Dhiman Date: Fri, 21 Aug 2026 13:43:15 +0530 Subject: [PATCH 43/61] Clarify UI engine interpreter setup --- CHANGELOG.md | 3 +++ README.md | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5b8e2d4..fc97817 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,9 @@ and this project aims to follow [Semantic Versioning](https://semver.org/spec/v2 ### Changed +- The install documentation now uses `python -m pip` so engine dependencies + stay in the interpreter the UI discovers. + - MCP startup and request failures now include the bounded recent engine stderr tail, making wrong-interpreter, missing-graph, and import failures visible in the UI instead of looking like a silent server exit. diff --git a/README.md b/README.md index e63b1a1..9ce5eaf 100644 --- a/README.md +++ b/README.md @@ -144,7 +144,7 @@ Replace `v0.1.0` with the reviewed release tag you intend to deploy; avoid ### Not pip or npm `lachesis-ui` is a static Go binary, so it is **not** a pip or npm package. The -engine it drives is Python (`pip install -e` from the checkout, which +engine it drives is Python (`python -m pip install -e` from the checkout, which `install.sh` does for you). Tagged releases build Linux and macOS binaries for amd64 and arm64 in the `release binaries` workflow. Download the matching archive and verify its SHA-256 checksum before unpacking it: From c0fe085e9d07e2a7678d4d4a01181bfe9f25c949 Mon Sep 17 00:00:00 2001 From: Riyan Dhiman Date: Fri, 21 Aug 2026 13:48:23 +0530 Subject: [PATCH 44/61] Validate UI installer refs early --- CHANGELOG.md | 3 +++ scripts/install.sh | 13 +++++++++++++ 2 files changed, 16 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index fc97817..c74aefd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,9 @@ and this project aims to follow [Semantic Versioning](https://semver.org/spec/v2 - The install documentation now uses `python -m pip` so engine dependencies stay in the interpreter the UI discovers. +- The installer rejects empty, option-like, or whitespace-containing engine, + catalog, and UI refs before invoking Git. + - MCP startup and request failures now include the bounded recent engine stderr tail, making wrong-interpreter, missing-graph, and import failures visible in the UI instead of looking like a silent server exit. diff --git a/scripts/install.sh b/scripts/install.sh index 4b503f1..b0e23e0 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -64,6 +64,19 @@ info() { printf '\033[36m==>\033[0m %s\n' "$*"; } warn() { printf '\033[33m warn:\033[0m %s\n' "$*" >&2; } die() { printf '\033[31merror:\033[0m %s\n' "$*" >&2; exit 1; } +validate_ref() { + local label="$1" ref="$2" + case "$ref" in + ""|[-]*|*[[:space:]]*) + die "$label must be a single Git ref (no leading '-' or whitespace)" + ;; + esac +} + +validate_ref LACHESIS_REF "$LACHESIS_REF" +validate_ref ATROPOS_REF "$ATROPOS_REF" +validate_ref LACHESIS_UI_REF "$LACHESIS_UI_REF" + need() { command -v "$1" >/dev/null 2>&1 || die "missing required tool: $1"; } # ---- preflight ------------------------------------------------------------ From 70540f5aedcd94cbdfe455dd0f0a6ca2494a4637 Mon Sep 17 00:00:00 2001 From: Riyan Dhiman Date: Fri, 21 Aug 2026 13:52:13 +0530 Subject: [PATCH 45/61] Use selected interpreter in UI recovery hint --- CHANGELOG.md | 3 +++ main.go | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c74aefd..53e1430 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,9 @@ and this project aims to follow [Semantic Versioning](https://semver.org/spec/v2 - The install documentation now uses `python -m pip` so engine dependencies stay in the interpreter the UI discovers. +- UI startup diagnostics now use `python -m pip` in their engine-install hint, + avoiding interpreter mismatches during recovery. + - The installer rejects empty, option-like, or whitespace-containing engine, catalog, and UI refs before invoking Git. diff --git a/main.go b/main.go index 631294b..8794d66 100644 --- a/main.go +++ b/main.go @@ -51,7 +51,7 @@ func main() { if err != nil { fatal(fmt.Sprintf("could not start the engine: %v\n\n"+ "The UI needs the lachesis engine (Python). Install it, or point at it:\n"+ - " pip install lachesis-cpg # or your project's install\n"+ + " python -m pip install lachesis-cpg # or your project's install\n"+ " lachesis-ui --python /path/to/python\n"+ " LACHESIS_PYTHON=/path/to/python lachesis-ui", err)) } From 4352cc3e8f56be8c3189d2a14d916336826a0a99 Mon Sep 17 00:00:00 2001 From: Riyan Dhiman Date: Fri, 21 Aug 2026 14:05:37 +0530 Subject: [PATCH 46/61] Bound UI MCP tool requests --- CHANGELOG.md | 4 ++++ README.md | 5 ++++ internal/mcp/client.go | 47 +++++++++++++++++++++++++++++++++++-- internal/mcp/engine_test.go | 11 +++++++++ 4 files changed, 65 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 53e1430..44d8f12 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,10 @@ and this project aims to follow [Semantic Versioning](https://semver.org/spec/v2 ### Changed +- UI MCP tool requests now have a two-minute default bound, configurable with + `LACHESIS_UI_REQUEST_TIMEOUT`, and report recent engine diagnostics when a request + times out instead of leaving the terminal waiting indefinitely. + - The install documentation now uses `python -m pip` so engine dependencies stay in the interpreter the UI discovers. diff --git a/README.md b/README.md index 9ce5eaf..e23ffa3 100644 --- a/README.md +++ b/README.md @@ -97,6 +97,11 @@ Re-running `install.sh` updates the checkouts in place. Requirements: `git`, The installer takes an atomic lock, so concurrent invocations fail safely rather than mutating the shared virtualenv and checkouts at the same time. +The UI bounds each MCP tool request to two minutes so a stalled engine cannot leave the +terminal waiting forever. For unusually expensive local graphs, override it with a Go +duration such as `LACHESIS_UI_REQUEST_TIMEOUT=5m`; a timed-out request terminates the +engine and shows the recent engine diagnostics. + To keep the stack outside your home directory (for example, on a CI volume), set `LACHESIS_HOME`; the generated graph helper and UI discovery use the same root: diff --git a/internal/mcp/client.go b/internal/mcp/client.go index db0c5af..cf53bab 100644 --- a/internal/mcp/client.go +++ b/internal/mcp/client.go @@ -160,10 +160,10 @@ func (c *Client) Call(tool string, args map[string]any) (json.RawMessage, error) if _, ok := args["format"]; !ok { args["format"] = "json" } - raw, err := c.request("tools/call", map[string]any{ + raw, err := c.requestBounded("tools/call", map[string]any{ "name": tool, "arguments": args, - }) + }, tool) if err != nil { return nil, err } @@ -188,6 +188,49 @@ func (c *Client) Call(tool string, args map[string]any) (json.RawMessage, error) return json.RawMessage(text), nil } +const defaultRequestTimeout = 2 * time.Minute + +func requestTimeout() time.Duration { + value := os.Getenv("LACHESIS_UI_REQUEST_TIMEOUT") + if value == "" { + return defaultRequestTimeout + } + timeout, err := time.ParseDuration(value) + if err != nil || timeout <= 0 { + return defaultRequestTimeout + } + return timeout +} + +// requestBounded prevents a stalled engine query from leaving the terminal UI +// permanently waiting. Once a request exceeds the bound, the engine is closed so +// queued calls cannot continue against an unresponsive process; the stderr tail is +// retained in the returned error for an actionable diagnosis. +func (c *Client) requestBounded(method string, params any, label string) (json.RawMessage, error) { + result := make(chan struct { + raw json.RawMessage + err error + }, 1) + go func() { + raw, err := c.request(method, params) + result <- struct { + raw json.RawMessage + err error + }{raw: raw, err: err} + }() + timeout := requestTimeout() + timer := time.NewTimer(timeout) + defer timer.Stop() + select { + case r := <-result: + return r.raw, r.err + case <-timer.C: + err := withEngineLogs(fmt.Errorf("%s timed out after %s", label, timeout), c.Logs()) + _ = c.Close() + return nil, err + } +} + // request performs one synchronous JSON-RPC round trip. func (c *Client) request(method string, params any) (json.RawMessage, error) { c.mu.Lock() diff --git a/internal/mcp/engine_test.go b/internal/mcp/engine_test.go index e77f000..6932670 100644 --- a/internal/mcp/engine_test.go +++ b/internal/mcp/engine_test.go @@ -67,6 +67,17 @@ func TestStartupTimeoutIsConfigurable(t *testing.T) { } } +func TestRequestTimeoutIsConfigurable(t *testing.T) { + t.Setenv("LACHESIS_UI_REQUEST_TIMEOUT", "17s") + if got, want := requestTimeout(), 17*time.Second; got != want { + t.Fatalf("requestTimeout() = %s, want %s", got, want) + } + t.Setenv("LACHESIS_UI_REQUEST_TIMEOUT", "not-a-duration") + if got := requestTimeout(); got != defaultRequestTimeout { + t.Fatalf("invalid timeout = %s, want default %s", got, defaultRequestTimeout) + } +} + func TestWithEngineLogsIncludesRecentDiagnostics(t *testing.T) { err := withEngineLogs(errors.New("startup failed"), []string{"[lachesis-mcp] starting", "graph missing"}) if got := err.Error(); got != "startup failed\nengine stderr:\n[lachesis-mcp] starting\ngraph missing" { From 156baa5be180d6e22b606d2cb210b154b486a3fb Mon Sep 17 00:00:00 2001 From: Riyan Dhiman Date: Fri, 21 Aug 2026 14:16:02 +0530 Subject: [PATCH 47/61] Make UI installer Git noninteractive --- CHANGELOG.md | 3 +++ scripts/install.sh | 5 +++++ 2 files changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 44d8f12..e112655 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,9 @@ and this project aims to follow [Semantic Versioning](https://semver.org/spec/v2 ### Changed +- The stack installer now disables Git terminal prompts so unattended setup fails + clearly when a checkout cannot authenticate or reach its repository. + - UI MCP tool requests now have a two-minute default bound, configurable with `LACHESIS_UI_REQUEST_TIMEOUT`, and report recent engine diagnostics when a request times out instead of leaving the terminal waiting indefinitely. diff --git a/scripts/install.sh b/scripts/install.sh index b0e23e0..bde0a31 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -30,6 +30,11 @@ # set -euo pipefail +# The installer is intended for terminals and CI alike. Never leave an unattended +# setup waiting for Git credentials; the public repositories below should fail fast +# when the runner has no network or credentials rather than hanging indefinitely. +export GIT_TERMINAL_PROMPT=0 + if [ "${1:-}" = "--help" ] || [ "${1:-}" = "-h" ]; then sed -n '2,/^set -euo pipefail$/p' "$0" cat <<'USAGE' From 7d1a1d306222cd023715ebc18cd0d2354d07c519 Mon Sep 17 00:00:00 2001 From: Riyan Dhiman Date: Fri, 21 Aug 2026 14:19:27 +0530 Subject: [PATCH 48/61] Smoke test native UI release --- .github/workflows/release.yml | 11 +++++++++++ CHANGELOG.md | 3 +++ RELEASING.md | 4 ++++ 3 files changed, 18 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a081bf7..6dc43e1 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -66,6 +66,17 @@ jobs: | gzip -n > "dist/$name.tar.gz" sha256sum -c "/tmp/${name}.first.sha256" (cd dist && sha256sum "$name.tar.gz") >> dist/SHA256SUMS + - name: Smoke-test native binary + if: matrix.goos == 'linux' && matrix.goarch == 'amd64' + run: | + version="${GITHUB_REF_NAME#v}" + name="lachesis-ui-${GITHUB_REF_NAME}-${{ matrix.goos }}-${{ matrix.goarch }}" + expected="lachesis-ui ${version}" + actual="$("dist/$name/lachesis-ui" --version)" + test "$actual" = "$expected" || { + echo "version mismatch: got '$actual', want '$expected'" >&2 + exit 1 + } - name: Upload binary artifact uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: diff --git a/CHANGELOG.md b/CHANGELOG.md index e112655..80f1395 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,9 @@ and this project aims to follow [Semantic Versioning](https://semver.org/spec/v2 ### Changed +- Release verification now executes the native Linux binary and checks its stamped + version before uploading cross-platform archives. + - The stack installer now disables Git terminal prompts so unattended setup fails clearly when a checkout cannot authenticate or reach its repository. diff --git a/RELEASING.md b/RELEASING.md index d8f1061..91f1609 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -29,6 +29,10 @@ shasum -a 256 -c SHA256SUMS The workflow stamps each binary with the tag version; verify a downloaded binary with `./lachesis-ui --version` before promoting it. +The release workflow performs that version smoke test automatically for the native +Linux amd64 artifact; the other targets are cross-compiled and verified by their +reproducible archive hashes. + Archive ordering, timestamps, ownership metadata, and the gzip header are normalized from the tagged commit, so rebuilding the same tag produces identical archive bytes. From 5adc4c54ebfbe6af347b324670bb77588422f62a Mon Sep 17 00:00:00 2001 From: Riyan Dhiman Date: Fri, 21 Aug 2026 14:22:06 +0530 Subject: [PATCH 49/61] Smoke-test extracted UI release archive --- .github/workflows/release.yml | 7 ++++++- CHANGELOG.md | 2 +- RELEASING.md | 6 +++--- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6dc43e1..974d0fd 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -69,10 +69,15 @@ jobs: - name: Smoke-test native binary if: matrix.goos == 'linux' && matrix.goarch == 'amd64' run: | + set -euo pipefail version="${GITHUB_REF_NAME#v}" name="lachesis-ui-${GITHUB_REF_NAME}-${{ matrix.goos }}-${{ matrix.goarch }}" expected="lachesis-ui ${version}" - actual="$("dist/$name/lachesis-ui" --version)" + verify_dir="$(mktemp -d)" + trap 'rm -rf "$verify_dir"' EXIT + tar -xzf "dist/$name.tar.gz" -C "$verify_dir" + test -f "$verify_dir/$name/lachesis-ui" + actual="$("$verify_dir/$name/lachesis-ui" --version)" test "$actual" = "$expected" || { echo "version mismatch: got '$actual', want '$expected'" >&2 exit 1 diff --git a/CHANGELOG.md b/CHANGELOG.md index 80f1395..185fe20 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,7 +20,7 @@ and this project aims to follow [Semantic Versioning](https://semver.org/spec/v2 ### Changed - Release verification now executes the native Linux binary and checks its stamped - version before uploading cross-platform archives. + version from the extracted archive before uploading cross-platform archives. - The stack installer now disables Git terminal prompts so unattended setup fails clearly when a checkout cannot authenticate or reach its repository. diff --git a/RELEASING.md b/RELEASING.md index 91f1609..e50df8d 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -29,9 +29,9 @@ shasum -a 256 -c SHA256SUMS The workflow stamps each binary with the tag version; verify a downloaded binary with `./lachesis-ui --version` before promoting it. -The release workflow performs that version smoke test automatically for the native -Linux amd64 artifact; the other targets are cross-compiled and verified by their -reproducible archive hashes. +The release workflow extracts the native Linux amd64 archive and performs that version +smoke test against the extracted binary; the other targets are cross-compiled and +verified by their reproducible archive hashes. Archive ordering, timestamps, ownership metadata, and the gzip header are normalized from the tagged commit, so rebuilding the same tag produces identical archive bytes. From dc9cbed3a3349dc6c224b5b5ccd5f959299969c7 Mon Sep 17 00:00:00 2001 From: Riyan Dhiman Date: Fri, 21 Aug 2026 14:33:27 +0530 Subject: [PATCH 50/61] Bound UI installer network waits --- CHANGELOG.md | 3 +++ scripts/install.sh | 9 +++++++++ 2 files changed, 12 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 185fe20..84948d0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,9 @@ and this project aims to follow [Semantic Versioning](https://semver.org/spec/v2 - The stack installer now disables Git terminal prompts so unattended setup fails clearly when a checkout cannot authenticate or reach its repository. +- The stack installer now applies Git low-speed and pip network bounds, so stalled + repository or dependency transfers fail clearly instead of hanging unattended. + - UI MCP tool requests now have a two-minute default bound, configurable with `LACHESIS_UI_REQUEST_TIMEOUT`, and report recent engine diagnostics when a request times out instead of leaving the terminal waiting indefinitely. diff --git a/scripts/install.sh b/scripts/install.sh index bde0a31..2437dab 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -34,6 +34,15 @@ set -euo pipefail # setup waiting for Git credentials; the public repositories below should fail fast # when the runner has no network or credentials rather than hanging indefinitely. export GIT_TERMINAL_PROMPT=0 +# Fail slow or stalled HTTPS transfers instead of leaving an unattended bootstrap +# waiting forever. These are Git's documented low-speed guards and do not affect +# local checkouts or SSH refs. +export GIT_HTTP_LOW_SPEED_LIMIT="${GIT_HTTP_LOW_SPEED_LIMIT:-1000}" +export GIT_HTTP_LOW_SPEED_TIME="${GIT_HTTP_LOW_SPEED_TIME:-60}" +# Keep pip noninteractive and bound its index/download wait during the engine setup. +export PIP_NO_INPUT=1 +export PIP_DISABLE_PIP_VERSION_CHECK=1 +export PIP_DEFAULT_TIMEOUT="${PIP_DEFAULT_TIMEOUT:-60}" if [ "${1:-}" = "--help" ] || [ "${1:-}" = "-h" ]; then sed -n '2,/^set -euo pipefail$/p' "$0" From 9b9494364249ba790441a331edcf5a02d85f5d2c Mon Sep 17 00:00:00 2001 From: Riyan Dhiman Date: Fri, 21 Aug 2026 14:34:16 +0530 Subject: [PATCH 51/61] Document UI installer network bounds --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index e23ffa3..15ea70e 100644 --- a/README.md +++ b/README.md @@ -97,6 +97,11 @@ Re-running `install.sh` updates the checkouts in place. Requirements: `git`, The installer takes an atomic lock, so concurrent invocations fail safely rather than mutating the shared virtualenv and checkouts at the same time. +Unattended installs fail fast on stalled network transfers: Git aborts after 60 seconds +below its low-speed threshold, and pip uses a 60-second default index/download timeout +with prompts disabled. Override `GIT_HTTP_LOW_SPEED_LIMIT`, `GIT_HTTP_LOW_SPEED_TIME`, +or `PIP_DEFAULT_TIMEOUT` when a slower private mirror requires it. + The UI bounds each MCP tool request to two minutes so a stalled engine cannot leave the terminal waiting forever. For unusually expensive local graphs, override it with a Go duration such as `LACHESIS_UI_REQUEST_TIMEOUT=5m`; a timed-out request terminates the From 8e70cbab8e2029d3f89c18b2850c810e73182ea0 Mon Sep 17 00:00:00 2001 From: Riyan Dhiman Date: Fri, 21 Aug 2026 14:40:47 +0530 Subject: [PATCH 52/61] Clarify UI engine install recovery --- CHANGELOG.md | 3 +++ main.go | 3 ++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 84948d0..4ea5b61 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,6 +35,9 @@ and this project aims to follow [Semantic Versioning](https://semver.org/spec/v2 - The install documentation now uses `python -m pip` so engine dependencies stay in the interpreter the UI discovers. +- Engine startup recovery now points to the supported source-checkout install and + labels the PyPI command as conditional until a release is published. + - UI startup diagnostics now use `python -m pip` in their engine-install hint, avoiding interpreter mismatches during recovery. diff --git a/main.go b/main.go index 8794d66..de1223f 100644 --- a/main.go +++ b/main.go @@ -51,7 +51,8 @@ func main() { if err != nil { fatal(fmt.Sprintf("could not start the engine: %v\n\n"+ "The UI needs the lachesis engine (Python). Install it, or point at it:\n"+ - " python -m pip install lachesis-cpg # or your project's install\n"+ + " python -m pip install -e /path/to/lachesis # source checkout\n"+ + " python -m pip install lachesis-cpg # once the release is published\n"+ " lachesis-ui --python /path/to/python\n"+ " LACHESIS_PYTHON=/path/to/python lachesis-ui", err)) } From c3d40bd595a2e581ba550702677da0b01044aa79 Mon Sep 17 00:00:00 2001 From: Riyan Dhiman Date: Fri, 21 Aug 2026 14:41:35 +0530 Subject: [PATCH 53/61] Clarify UI binary release install path --- CHANGELOG.md | 3 +++ README.md | 8 +++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4ea5b61..5546d60 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,6 +38,9 @@ and this project aims to follow [Semantic Versioning](https://semver.org/spec/v2 - Engine startup recovery now points to the supported source-checkout install and labels the PyPI command as conditional until a release is published. +- The binary-only install section now distinguishes the working source build from + the `go install` command reserved for a published UI tag. + - UI startup diagnostics now use `python -m pip` in their engine-install hint, avoiding interpreter mismatches during recovery. diff --git a/README.md b/README.md index 15ea70e..f4b9a59 100644 --- a/README.md +++ b/README.md @@ -142,7 +142,13 @@ export PATH="$HOME/.lachesis/bin:$PATH" ### Just the binary If you already have the engine installed (`~/.lachesis/venv`, or `lachesis` on -your PATH), you only need the UI: +your PATH), you only need the UI. From this checkout, build it with: + +```sh +go build -trimpath -o "$HOME/.lachesis/bin/lachesis-ui" . +``` + +For a tagged release published to the Go module proxy, use: ```sh go install github.com/UnboundCompute/lachesis-ui@v0.1.0 From a52e1d008578f53653e84b853042c609c1f33e06 Mon Sep 17 00:00:00 2001 From: Riyan Dhiman Date: Fri, 21 Aug 2026 14:44:39 +0530 Subject: [PATCH 54/61] Unify UI version metadata --- .github/workflows/release.yml | 4 ++++ CHANGELOG.md | 3 +++ RELEASING.md | 5 +++-- VERSION | 1 + scripts/install.sh | 9 +++++++-- 5 files changed, 18 insertions(+), 4 deletions(-) create mode 100644 VERSION diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 974d0fd..342a833 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -40,7 +40,11 @@ jobs: heading = f"## [{version}]" if heading not in Path("CHANGELOG.md").read_text(encoding="utf-8"): raise SystemExit(f"CHANGELOG.md has no release heading for {version}") + declared = Path("VERSION").read_text(encoding="utf-8").strip() + if declared != version: + raise SystemExit(f"VERSION declares {declared}, but tag is {version}") print(f"changelog entry: {heading}") + print(f"UI version: {declared}") PY - name: Build and package env: diff --git a/CHANGELOG.md b/CHANGELOG.md index 5546d60..5e1bd22 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,9 @@ and this project aims to follow [Semantic Versioning](https://semver.org/spec/v2 ## [Unreleased] +- Added a machine-readable `VERSION` source used by the installer’s source-build + default; release CI now rejects tags that do not match it. + ### Added - Tagged releases now build checksummed Linux and macOS binaries for amd64 and arm64. diff --git a/RELEASING.md b/RELEASING.md index e50df8d..2b91e41 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -1,6 +1,6 @@ # Releasing lachesis-ui -The UI is a Go binary released independently from the Lachesis engine and Atropos +The UI version is recorded in [`VERSION`](VERSION). The UI is a Go binary released independently from the Lachesis engine and Atropos catalog. Keep the UI tag and the engine/catalog refs it discovers separate. Before tagging: @@ -12,7 +12,8 @@ go test ./... go build ./... ``` -Create an annotated `vMAJOR.MINOR.PATCH` tag from a clean commit. The tag workflow +Update `VERSION` and the matching changelog heading, then create an annotated +`vMAJOR.MINOR.PATCH` tag from a clean commit. The tag workflow builds Linux and macOS binaries for amd64 and arm64 with CGO disabled, packages the README and license, and writes `SHA256SUMS`. Downloaded archives must pass: diff --git a/VERSION b/VERSION new file mode 100644 index 0000000..6e8bf73 --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +0.1.0 diff --git a/scripts/install.sh b/scripts/install.sh index 2437dab..3f22e29 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -44,6 +44,8 @@ export PIP_NO_INPUT=1 export PIP_DISABLE_PIP_VERSION_CHECK=1 export PIP_DEFAULT_TIMEOUT="${PIP_DEFAULT_TIMEOUT:-60}" +HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" + if [ "${1:-}" = "--help" ] || [ "${1:-}" = "-h" ]; then sed -n '2,/^set -euo pipefail$/p' "$0" cat <<'USAGE' @@ -69,7 +71,11 @@ PYTHON="${PYTHON:-python3}" LACHESIS_REF="${LACHESIS_REF:-main}" ATROPOS_REF="${ATROPOS_REF:-main}" LACHESIS_UI_REF="${LACHESIS_UI_REF:-v0.1.0}" -LACHESIS_UI_VERSION="${LACHESIS_UI_VERSION:-0.1.0}" +DEFAULT_UI_VERSION="0.1.0" +if [ -f "$HERE/VERSION" ]; then + DEFAULT_UI_VERSION="$(tr -d '[:space:]' < "$HERE/VERSION")" +fi +LACHESIS_UI_VERSION="${LACHESIS_UI_VERSION:-$DEFAULT_UI_VERSION}" LACHESIS_REPO="https://github.com/UnboundCompute/lachesis.git" ATROPOS_REPO="https://github.com/UnboundCompute/atropos.git" @@ -205,7 +211,6 @@ HELPER chmod +x "$LACHESIS_HOME/build-graph.sh" # ---- 5. build the UI ------------------------------------------------------ -HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" if [ -f "$HERE/main.go" ]; then info "building lachesis-ui from source checkout" (cd "$HERE" && go build -trimpath \ From 671d717ba5e78fb7853247f3ed0b44233064eb8e Mon Sep 17 00:00:00 2001 From: Riyan Dhiman Date: Fri, 21 Aug 2026 14:50:12 +0530 Subject: [PATCH 55/61] Warn on mutable UI stack refs --- CHANGELOG.md | 3 +++ scripts/install.sh | 7 +++++++ 2 files changed, 10 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5e1bd22..7c9e017 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -44,6 +44,9 @@ and this project aims to follow [Semantic Versioning](https://semver.org/spec/v2 - The binary-only install section now distinguishes the working source build from the `go install` command reserved for a published UI tag. +- The stack installer now warns when engine or catalog refs remain on mutable `main`, + while preserving those defaults for development use. + - UI startup diagnostics now use `python -m pip` in their engine-install hint, avoiding interpreter mismatches during recovery. diff --git a/scripts/install.sh b/scripts/install.sh index 3f22e29..8b7daa5 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -97,6 +97,13 @@ validate_ref LACHESIS_REF "$LACHESIS_REF" validate_ref ATROPOS_REF "$ATROPOS_REF" validate_ref LACHESIS_UI_REF "$LACHESIS_UI_REF" +if [ "$LACHESIS_REF" = "main" ]; then + warn "LACHESIS_REF=main is mutable; pin a reviewed engine tag or commit for reproducible installs" +fi +if [ "$ATROPOS_REF" = "main" ]; then + warn "ATROPOS_REF=main is mutable; pin a reviewed catalog tag or commit for reproducible installs" +fi + need() { command -v "$1" >/dev/null 2>&1 || die "missing required tool: $1"; } # ---- preflight ------------------------------------------------------------ From 84044d8e215d5f262a12c53f70770b69806f40f8 Mon Sep 17 00:00:00 2001 From: Riyan Dhiman Date: Fri, 21 Aug 2026 14:53:17 +0530 Subject: [PATCH 56/61] Terminate UI engine process groups on timeout --- CHANGELOG.md | 3 +++ internal/mcp/client.go | 3 ++- internal/mcp/process_other.go | 16 ++++++++++++++++ internal/mcp/process_posix.go | 24 ++++++++++++++++++++++++ 4 files changed, 45 insertions(+), 1 deletion(-) create mode 100644 internal/mcp/process_other.go create mode 100644 internal/mcp/process_posix.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c9e017..db912c3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -47,6 +47,9 @@ and this project aims to follow [Semantic Versioning](https://semver.org/spec/v2 - The stack installer now warns when engine or catalog refs remain on mutable `main`, while preserving those defaults for development use. +- UI engine shutdown now uses a dedicated POSIX process group, so startup or request + timeouts terminate compiler/helper children along with the MCP parent. + - UI startup diagnostics now use `python -m pip` in their engine-install hint, avoiding interpreter mismatches during recovery. diff --git a/internal/mcp/client.go b/internal/mcp/client.go index cf53bab..e767206 100644 --- a/internal/mcp/client.go +++ b/internal/mcp/client.go @@ -54,6 +54,7 @@ type rpcError struct { // python is the interpreter that can import lachesis (see engine.Discover). func Spawn(python, graphPath string) (*Client, error) { cmd := exec.Command(python, "-m", "lachesis.nav.mcp_server", graphPath) + configureProcess(cmd) stdin, err := cmd.StdinPipe() if err != nil { @@ -318,7 +319,7 @@ func (c *Client) Close() error { case err := <-done: return err case <-time.After(2 * time.Second): - _ = c.cmd.Process.Kill() + _ = killProcessTree(c.cmd) return nil } } diff --git a/internal/mcp/process_other.go b/internal/mcp/process_other.go new file mode 100644 index 0000000..df48b28 --- /dev/null +++ b/internal/mcp/process_other.go @@ -0,0 +1,16 @@ +//go:build !(aix || darwin || dragonfly || freebsd || illumos || linux || netbsd || openbsd || solaris) + +package mcp + +import "os/exec" + +// Non-POSIX runners do not expose a portable process-group API here. The parent +// process is still bounded and killed, matching the pre-group behavior. +func configureProcess(_ *exec.Cmd) {} + +func killProcessTree(cmd *exec.Cmd) error { + if cmd == nil || cmd.Process == nil { + return nil + } + return cmd.Process.Kill() +} diff --git a/internal/mcp/process_posix.go b/internal/mcp/process_posix.go new file mode 100644 index 0000000..17eef6f --- /dev/null +++ b/internal/mcp/process_posix.go @@ -0,0 +1,24 @@ +//go:build aix || darwin || dragonfly || freebsd || illumos || linux || netbsd || openbsd || solaris + +package mcp + +import ( + "os/exec" + "syscall" +) + +// configureProcess puts the engine in its own process group so a timeout cannot +// leave a compiler or helper subprocess running after the UI closes the parent. +func configureProcess(cmd *exec.Cmd) { + cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true} +} + +func killProcessTree(cmd *exec.Cmd) error { + if cmd == nil || cmd.Process == nil { + return nil + } + if err := syscall.Kill(-cmd.Process.Pid, syscall.SIGKILL); err != nil { + return cmd.Process.Kill() + } + return nil +} From 598e4cc9ca8aec860110cc71ed1c052230efff14 Mon Sep 17 00:00:00 2001 From: Riyan Dhiman Date: Fri, 21 Aug 2026 14:58:26 +0530 Subject: [PATCH 57/61] Test UI process group configuration --- internal/mcp/process_posix_test.go | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 internal/mcp/process_posix_test.go diff --git a/internal/mcp/process_posix_test.go b/internal/mcp/process_posix_test.go new file mode 100644 index 0000000..6b7a262 --- /dev/null +++ b/internal/mcp/process_posix_test.go @@ -0,0 +1,16 @@ +//go:build aix || darwin || dragonfly || freebsd || illumos || linux || netbsd || openbsd || solaris + +package mcp + +import ( + "os/exec" + "testing" +) + +func TestConfigureProcessUsesDedicatedGroup(t *testing.T) { + cmd := exec.Command("sh", "-c", "true") + configureProcess(cmd) + if cmd.SysProcAttr == nil || !cmd.SysProcAttr.Setpgid { + t.Fatal("engine command is not configured with a dedicated process group") + } +} From d3f6a5272ca7d2e426bb75e78e827b252bebe25a Mon Sep 17 00:00:00 2001 From: Riyan Dhiman Date: Fri, 21 Aug 2026 14:59:22 +0530 Subject: [PATCH 58/61] Configure UI graph build timeout --- CHANGELOG.md | 2 ++ README.md | 4 ++++ scripts/install.sh | 8 +++++++- 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index db912c3..c30bc1b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -49,6 +49,8 @@ and this project aims to follow [Semantic Versioning](https://semver.org/spec/v2 - UI engine shutdown now uses a dedicated POSIX process group, so startup or request timeouts terminate compiler/helper children along with the MCP parent. +- The stack installer now exposes the generated graph helper's frontend timeout as + `LACHESIS_BUILD_TIMEOUT` instead of requiring edits to generated shell code. - UI startup diagnostics now use `python -m pip` in their engine-install hint, avoiding interpreter mismatches during recovery. diff --git a/README.md b/README.md index f4b9a59..2dd1a87 100644 --- a/README.md +++ b/README.md @@ -126,6 +126,10 @@ LACHESIS_REF= ATROPOS_REF= ./scripts/in The installer pins its binary fallback to `v0.1.0`; set `LACHESIS_UI_REF` to a reviewed tag or commit when selecting another UI release. +The generated `build-graph.sh` helper bounds each frontend invocation to 3,600 +seconds by default. Set `LACHESIS_BUILD_TIMEOUT` before running the installer to +use a different positive-integer limit. + When building from the checkout, set `LACHESIS_UI_VERSION` if the binary should report a version other than the default `0.1.0`: diff --git a/scripts/install.sh b/scripts/install.sh index 8b7daa5..10f34b3 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -27,6 +27,7 @@ # PYTHON python to build the venv (default: python3) # LACHESIS_UI_REF release tag/commit for the go-install fallback (default: v0.1.0) # LACHESIS_UI_VERSION version stamped into a source-built binary (default: 0.1.0) +# LACHESIS_BUILD_TIMEOUT maximum seconds for one generated graph build (default: 3600) # set -euo pipefail @@ -76,6 +77,7 @@ if [ -f "$HERE/VERSION" ]; then DEFAULT_UI_VERSION="$(tr -d '[:space:]' < "$HERE/VERSION")" fi LACHESIS_UI_VERSION="${LACHESIS_UI_VERSION:-$DEFAULT_UI_VERSION}" +LACHESIS_BUILD_TIMEOUT="${LACHESIS_BUILD_TIMEOUT:-3600}" LACHESIS_REPO="https://github.com/UnboundCompute/lachesis.git" ATROPOS_REPO="https://github.com/UnboundCompute/atropos.git" @@ -84,6 +86,10 @@ info() { printf '\033[36m==>\033[0m %s\n' "$*"; } warn() { printf '\033[33m warn:\033[0m %s\n' "$*" >&2; } die() { printf '\033[31merror:\033[0m %s\n' "$*" >&2; exit 1; } +case "$LACHESIS_BUILD_TIMEOUT" in + ''|*[!0-9]*|0) die "LACHESIS_BUILD_TIMEOUT must be a positive integer" ;; +esac + validate_ref() { local label="$1" ref="$2" case "$ref" in @@ -212,7 +218,7 @@ case "\$NAME" in esac OUT="$GRAPHS/\$NAME.kuzu" export ATROPOS_ROOT="\${ATROPOS_ROOT:-$SRC/atropos}" -"$VENV/bin/lachesis-analyze" "\$SOURCE_DIR" "\$OUT" --prune --timeout 3600 +"$VENV/bin/lachesis-analyze" "\$SOURCE_DIR" "\$OUT" --prune --timeout $LACHESIS_BUILD_TIMEOUT echo "\$OUT" HELPER chmod +x "$LACHESIS_HOME/build-graph.sh" From 689bb5faaaa47430c1f2b5bed3fc2c0d3ccc9bbd Mon Sep 17 00:00:00 2001 From: Riyan Dhiman Date: Fri, 21 Aug 2026 15:00:02 +0530 Subject: [PATCH 59/61] Document UI engine Python support --- CHANGELOG.md | 1 + README.md | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c30bc1b..20fdcfd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -51,6 +51,7 @@ and this project aims to follow [Semantic Versioning](https://semver.org/spec/v2 timeouts terminate compiler/helper children along with the MCP parent. - The stack installer now exposes the generated graph helper's frontend timeout as `LACHESIS_BUILD_TIMEOUT` instead of requiring edits to generated shell code. +- Documented the engine's Python 3.10–3.12 release-tested window in the UI setup guide. - UI startup diagnostics now use `python -m pip` in their engine-install hint, avoiding interpreter mismatches during recovery. diff --git a/README.md b/README.md index 2dd1a87..b57cfa1 100644 --- a/README.md +++ b/README.md @@ -93,7 +93,9 @@ The installer also vendors Lachesis's pinned TypeScript compiler, so the resulti stack can analyze TypeScript without a separate Node/npm setup. Re-running `install.sh` updates the checkouts in place. Requirements: `git`, -`python3` (3.10+), and Go 1.24.2+ to build the binary. +`python3` (3.10–3.12 are the engine's release-tested versions), and Go 1.24.2+ to +build the binary. Verify newer Python versions against the engine/Kùzu dependency set +before using them in production. The installer takes an atomic lock, so concurrent invocations fail safely rather than mutating the shared virtualenv and checkouts at the same time. From 511187902093d54ad12c34d541b4330616bb4c42 Mon Sep 17 00:00:00 2001 From: Riyan Dhiman Date: Fri, 21 Aug 2026 15:00:55 +0530 Subject: [PATCH 60/61] Derive UI build version from VERSION --- CHANGELOG.md | 2 ++ Makefile | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 20fdcfd..2d5685a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -52,6 +52,8 @@ and this project aims to follow [Semantic Versioning](https://semver.org/spec/v2 - The stack installer now exposes the generated graph helper's frontend timeout as `LACHESIS_BUILD_TIMEOUT` instead of requiring edits to generated shell code. - Documented the engine's Python 3.10–3.12 release-tested window in the UI setup guide. +- The Makefile now derives its default binary version from `VERSION`, matching the + installer and release workflow while retaining explicit `VERSION=...` overrides. - UI startup diagnostics now use `python -m pip` in their engine-install hint, avoiding interpreter mismatches during recovery. diff --git a/Makefile b/Makefile index 8f2b819..9ee37fc 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ BIN ?= lachesis-ui PREFIX ?= $(HOME)/.lachesis/bin -VERSION ?= 0.1.0 +VERSION ?= $(shell tr -d '[:space:]' < VERSION 2>/dev/null || printf '0.1.0') LDFLAGS ?= -X github.com/UnboundCompute/lachesis-ui/internal/mcp.Version=$(VERSION) .PHONY: build install run tidy fmt vet check clean stack From a7767ed1c488d1a7e12dd1e37c0f8058397682ca Mon Sep 17 00:00:00 2001 From: Riyan Dhiman Date: Fri, 21 Aug 2026 15:18:37 +0530 Subject: [PATCH 61/61] Use version tags in UI workflows --- .github/workflows/ci.yml | 4 ++-- .github/workflows/release.yml | 6 +++--- README.md | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 699e647..de33aec 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,10 +20,10 @@ jobs: timeout-minutes: 10 steps: - name: Check out - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 + uses: actions/checkout@v4 - name: Set up Go - uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5 + uses: actions/setup-go@v5 with: go-version: '1.24.2' diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 342a833..a379c72 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -25,8 +25,8 @@ jobs: - goos: darwin goarch: arm64 steps: - - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 - - uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5 + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 with: go-version: "1.24.2" cache: true @@ -87,7 +87,7 @@ jobs: exit 1 } - name: Upload binary artifact - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + uses: actions/upload-artifact@v4 with: name: lachesis-ui-${{ github.ref_name }}-${{ matrix.goos }}-${{ matrix.goarch }} path: dist/ diff --git a/README.md b/README.md index b57cfa1..af1f22c 100644 --- a/README.md +++ b/README.md @@ -117,12 +117,12 @@ LACHESIS_HOME=/var/cache/lachesis ./scripts/install.sh ``` For reproducible deployments, pin the engine and catalog before installing (use -reviewed tags or commit SHAs rather than mutable branches). Re-running the +reviewed release tags rather than mutable branches). Re-running the installer applies those refs to existing clean checkouts and refuses to touch a checkout with local edits or untracked files: ```sh -LACHESIS_REF= ATROPOS_REF= ./scripts/install.sh +LACHESIS_REF= ATROPOS_REF= ./scripts/install.sh ``` The installer pins its binary fallback to `v0.1.0`; set `LACHESIS_UI_REF` to a