Skip to content

ci: enforce RLMX release channel contract#103

Merged
namastex888 merged 2 commits into
mainfrom
drogo/prod-rlmx
May 31, 2026
Merged

ci: enforce RLMX release channel contract#103
namastex888 merged 2 commits into
mainfrom
drogo/prod-rlmx

Conversation

@namastex888
Copy link
Copy Markdown
Collaborator

@namastex888 namastex888 commented May 31, 2026

Summary

Implements the RLMX release-channel contract:

  • scripts/install.sh + rlmx update are the canonical CLI/application install/update path.
  • npm is SDK-only and no longer exposes a CLI bin.
  • merging to main is documented as the application release boundary.
  • GitHub Releases are metadata-only and package-version coherent.
  • CI now smokes scripts/install.sh, rlmx update dirty-check refusal, and update happy path against a temporary local main remote.

Verification

  • npm run build
  • npm run check
  • npm test — 377 passing, 0 failing
  • bash scripts/smoke-install-update.sh
  • npm pack --json --dry-runhasBin=false
  • staged public-safety scan — no private/KHAL/customer/IP/secret hits
  • independent review found one blocker in the smoke script SDK-only check; fixed and reran smoke successfully

Summary by CodeRabbit

  • New Features

    • Added rlmx update command to update managed installations with optional force flag to override safety checks.
  • Documentation

    • Added comprehensive release contract documentation outlining installation, update, and version management behavior.
  • Chores

    • Updated package metadata to reflect SDK designation.
    • Enhanced CI/CD workflows with improved documentation and validation steps.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 31, 2026

Review Change Stack

📝 Walkthrough

Walkthrough

This PR establishes RLMX as a git-installed CLI distributed via a source repository while publishing only the SDK to npm. It adds rlmx update for self-updates, provides installer/updater scripts, documents the release contract governing channel responsibilities, and validates the complete flow through smoke tests.

Changes

Git-Based CLI Distribution with Self-Update

Layer / File(s) Summary
Release Contract and Workflow Documentation
docs/release-contract.md, .github/workflows/release.yml, .github/workflows/rolling-pr.yml, .github/workflows/version.yml
Full release-contract documentation defines CLI vs npm channel boundaries, coherence invariants (git tags match package contents), and operational semantics. Workflow files reframed as SDK-only npm publishing with git main as the authoritative CLI release boundary.
Package Distribution Model Shift
package.json
Package converted from CLI-first (with bin entry for rlmx executable) to SDK-only model. Description, keywords, and scripts updated; install:local script added for developer setup.
CLI Update Command Implementation
src/cli.ts, src/schema.ts
Adds rlmx update [--force] command. Schema defines --force flag (alias -f) to bypass dirty-state checks. Implementation validates git context, fetches origin/main, hard-resets on divergence, runs npm ci and build, and reports before/after revisions.
Git-Based Installer Script
scripts/install.sh
Bash installer clones or refreshes RLMX git checkout from configurable repo/branch, installs dependencies, builds, symlinks compiled CLI into user bin directory, and verifies via rlmx --version.
Smoke Testing and CI Integration
scripts/smoke-install-update.sh, .github/workflows/ci.yml
Smoke test validates installer and update flows: HEAD alignment, binary execution, package SDK-only constraint (npm pack --dry-run has no bin), dirty-state refusal with forced reset, and update advancement. CI pipeline integrates smoke test after Test job.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • automagik-dev/rlmx#101: Adds foundational CLI schema support (RLMX_CLI_SCHEMA) and command argument parsing wiring that this PR extends with the update command and --force flag definitions.

Poem

🐰 A rabbit hops through update flows so clean,
Git branches fetched, with npm in between!
No npm CLI—just SDK to share,
Install from source, with self-update care.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 12.50% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'ci: enforce RLMX release channel contract' directly and clearly describes the main objective of the changeset: implementing CI checks to enforce the RLMX release-channel contract across multiple workflow files and supporting scripts.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch drogo/prod-rlmx

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a new rlmx update command to update git-installed checkouts, transitions the npm package to be SDK-only by removing the bin field, and adds a canonical installer script (scripts/install.sh) along with smoke tests. Feedback on the changes suggests using checkout -f in the installer script to ensure that refreshing an existing checkout with local modifications does not cause the installation to fail.

Comment thread scripts/install.sh
if [ -d "$RLMX_INSTALL_DIR/.git" ]; then
echo "==> Existing checkout found; refreshing"
git -C "$RLMX_INSTALL_DIR" fetch origin "$RLMX_BRANCH" --tags
git -C "$RLMX_INSTALL_DIR" checkout "$RLMX_BRANCH"
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

If the existing checkout has any local modifications or untracked files that conflict, git checkout without the force flag will fail and abort the installation. Using checkout -f ensures that the checkout succeeds by discarding local changes, which is the expected behavior for a clean managed refresh.

Suggested change
git -C "$RLMX_INSTALL_DIR" checkout "$RLMX_BRANCH"
git -C "$RLMX_INSTALL_DIR" checkout -f "$RLMX_BRANCH"

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 62f2a0be57

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread scripts/install.sh
Comment on lines +33 to +37
echo "==> Installing dependencies"
npm ci

echo "==> Building"
npm run build
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Install dev dependencies before building

When this installer runs in an environment with NODE_ENV=production, npm ci omits dev dependencies by default (checked with NODE_ENV=production npm config get omit, which returns dev), so the following npm run build cannot find dev-only tools like typescript/tsc. That makes fresh installs fail on production-configured hosts even though the committed dist/ exists; the same pattern also appears in rlmx update, so force dev dependencies for the managed build path (for example npm ci --include=dev or clearing omit).

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@scripts/install.sh`:
- Around line 17-21: The refresh path uses the existing origin instead of the
requested RLMX_REPO_URL; update the script to read the current origin URL (git
-C "$RLMX_INSTALL_DIR" remote get-url origin), compare it to $RLMX_REPO_URL, and
if they differ either set the origin to the requested URL (git -C
"$RLMX_INSTALL_DIR" remote set-url origin "$RLMX_REPO_URL") before
fetch/checkout/reset or exit with a clear error; ensure this logic surrounds the
existing git -C "$RLMX_INSTALL_DIR" fetch/checkout/reset calls that reference
origin and uses the RLMX_* variables named in the diff.

In `@scripts/smoke-install-update.sh`:
- Around line 53-56: The npm pack check depends on the current working
directory; update the script so the npm pack --json --dry-run invocation runs
from the repository root instead of the caller's cwd. Specifically, wrap or
prefix the existing npm pack --json --dry-run | node ... pipeline (the line
containing "npm pack --json --dry-run" and the inline node check) so it executes
in "$ROOT" (for example by cd'ing into "$ROOT" before running the pipeline or
using a subshell that changes directory), ensuring the SDK-only bin assertion
always inspects the repo root package.

In `@src/cli.ts`:
- Around line 929-945: The update flow currently runs runGit(root, ["reset",
"--hard", "origin/main"]) but doesn't remove untracked files, so using --force
won't produce a fully clean checkout; call the git clean step (e.g.,
runGit(root, ["clean", "-fdx"]) or at minimum ["clean", "-fd"]) before the reset
when force is true (or when you decide to override a dirty state) to remove
untracked files and dirs; locate the update logic around the dirty/force checks
and add the runGit("clean", ...) call before runGit(... "reset", "--hard",
"origin/main") so the working tree truly matches origin/main before
runCommand(root, "npm", ["ci"]).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f5d4c191-ecc0-4d51-99e7-fe3799ca29ae

📥 Commits

Reviewing files that changed from the base of the PR and between de101ee and 62f2a0b.

⛔ Files ignored due to path filters (3)
  • dist/src/cli.js is excluded by !**/dist/**
  • dist/src/schema.js is excluded by !**/dist/**
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (10)
  • .github/workflows/ci.yml
  • .github/workflows/release.yml
  • .github/workflows/rolling-pr.yml
  • .github/workflows/version.yml
  • docs/release-contract.md
  • package.json
  • scripts/install.sh
  • scripts/smoke-install-update.sh
  • src/cli.ts
  • src/schema.ts

Comment thread scripts/install.sh
Comment on lines +17 to +21
if [ -d "$RLMX_INSTALL_DIR/.git" ]; then
echo "==> Existing checkout found; refreshing"
git -C "$RLMX_INSTALL_DIR" fetch origin "$RLMX_BRANCH" --tags
git -C "$RLMX_INSTALL_DIR" checkout "$RLMX_BRANCH"
git -C "$RLMX_INSTALL_DIR" reset --hard "origin/$RLMX_BRANCH"
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Existing installs ignore RLMX_REPO_URL.

On the refresh path, the script always fetches from the checkout’s current origin, so rerunning the installer with a different RLMX_REPO_URL silently keeps using the old remote while the banner says otherwise. Either update origin first or fail if it does not match the requested repo.

Suggested fix
 if [ -d "$RLMX_INSTALL_DIR/.git" ]; then
   echo "==> Existing checkout found; refreshing"
+  git -C "$RLMX_INSTALL_DIR" remote set-url origin "$RLMX_REPO_URL"
   git -C "$RLMX_INSTALL_DIR" fetch origin "$RLMX_BRANCH" --tags
   git -C "$RLMX_INSTALL_DIR" checkout "$RLMX_BRANCH"
   git -C "$RLMX_INSTALL_DIR" reset --hard "origin/$RLMX_BRANCH"
 else
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if [ -d "$RLMX_INSTALL_DIR/.git" ]; then
echo "==> Existing checkout found; refreshing"
git -C "$RLMX_INSTALL_DIR" fetch origin "$RLMX_BRANCH" --tags
git -C "$RLMX_INSTALL_DIR" checkout "$RLMX_BRANCH"
git -C "$RLMX_INSTALL_DIR" reset --hard "origin/$RLMX_BRANCH"
if [ -d "$RLMX_INSTALL_DIR/.git" ]; then
echo "==> Existing checkout found; refreshing"
git -C "$RLMX_INSTALL_DIR" remote set-url origin "$RLMX_REPO_URL"
git -C "$RLMX_INSTALL_DIR" fetch origin "$RLMX_BRANCH" --tags
git -C "$RLMX_INSTALL_DIR" checkout "$RLMX_BRANCH"
git -C "$RLMX_INSTALL_DIR" reset --hard "origin/$RLMX_BRANCH"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/install.sh` around lines 17 - 21, The refresh path uses the existing
origin instead of the requested RLMX_REPO_URL; update the script to read the
current origin URL (git -C "$RLMX_INSTALL_DIR" remote get-url origin), compare
it to $RLMX_REPO_URL, and if they differ either set the origin to the requested
URL (git -C "$RLMX_INSTALL_DIR" remote set-url origin "$RLMX_REPO_URL") before
fetch/checkout/reset or exit with a clear error; ensure this logic surrounds the
existing git -C "$RLMX_INSTALL_DIR" fetch/checkout/reset calls that reference
origin and uses the RLMX_* variables named in the diff.

Comment on lines +53 to +56
npm pack --json --dry-run | node -e 'let s=""; process.stdin.on("data",d=>s+=d); process.stdin.on("end",()=>{ const p=JSON.parse(s)[0]; if (p.bin) { console.error("npm package exposes bin unexpectedly", p.bin); process.exit(1); } });' || {
echo "::error::npm package must remain SDK-only and expose no bin" >&2
exit 1
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Run the SDK-only check from "$ROOT".

This assertion currently depends on the caller’s working directory. If someone invokes the script from outside the repo root, npm pack --dry-run inspects the wrong package.

Suggested fix
-npm pack --json --dry-run | node -e 'let s=""; process.stdin.on("data",d=>s+=d); process.stdin.on("end",()=>{ const p=JSON.parse(s)[0]; if (p.bin) { console.error("npm package exposes bin unexpectedly", p.bin); process.exit(1); } });' || {
+(cd "$ROOT" && npm pack --json --dry-run) | node -e 'let s=""; process.stdin.on("data",d=>s+=d); process.stdin.on("end",()=>{ const p=JSON.parse(s)[0]; if (p.bin) { console.error("npm package exposes bin unexpectedly", p.bin); process.exit(1); } });' || {
   echo "::error::npm package must remain SDK-only and expose no bin" >&2
   exit 1
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
npm pack --json --dry-run | node -e 'let s=""; process.stdin.on("data",d=>s+=d); process.stdin.on("end",()=>{ const p=JSON.parse(s)[0]; if (p.bin) { console.error("npm package exposes bin unexpectedly", p.bin); process.exit(1); } });' || {
echo "::error::npm package must remain SDK-only and expose no bin" >&2
exit 1
}
(cd "$ROOT" && npm pack --json --dry-run) | node -e 'let s=""; process.stdin.on("data",d=>s+=d); process.stdin.on("end",()=>{ const p=JSON.parse(s)[0]; if (p.bin) { console.error("npm package exposes bin unexpectedly", p.bin); process.exit(1); } });' || {
echo "::error::npm package must remain SDK-only and expose no bin" >&2
exit 1
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/smoke-install-update.sh` around lines 53 - 56, The npm pack check
depends on the current working directory; update the script so the npm pack
--json --dry-run invocation runs from the repository root instead of the
caller's cwd. Specifically, wrap or prefix the existing npm pack --json
--dry-run | node ... pipeline (the line containing "npm pack --json --dry-run"
and the inline node check) so it executes in "$ROOT" (for example by cd'ing into
"$ROOT" before running the pipeline or using a subshell that changes directory),
ensuring the SDK-only bin assertion always inspects the repo root package.

Comment thread src/cli.ts
Comment on lines +929 to +945
if (dirty && !force) {
throw new Error("Refusing to update with local changes. Commit/stash them or rerun with --force for managed installs.");
}

console.log(`rlmx update: ${root}`);
console.log(`before: ${before}`);
runGit(root, ["fetch", "origin", "main", "--tags"]);
const target = runGit(root, ["rev-parse", "origin/main"]);
console.log(`target: ${target}`);

if (before === target && !dirty) {
console.log("Already up to date.");
return;
}

runGit(root, ["reset", "--hard", "origin/main"]);
runCommand(root, "npm", ["ci"]);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

--force does not fully restore a clean checkout.

This path only does git reset --hard, which drops tracked edits but leaves untracked files behind. A checkout that is dirty because of untracked files will still not match origin/main after rlmx update --force, despite the help/schema text saying it resets the managed checkout. Add a clean step before rebuilding.

Suggested fix
   if (before === target && !dirty) {
     console.log("Already up to date.");
     return;
   }

   runGit(root, ["reset", "--hard", "origin/main"]);
+  runGit(root, ["clean", "-fd"]);
   runCommand(root, "npm", ["ci"]);
   runCommand(root, "npm", ["run", "build"]);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (dirty && !force) {
throw new Error("Refusing to update with local changes. Commit/stash them or rerun with --force for managed installs.");
}
console.log(`rlmx update: ${root}`);
console.log(`before: ${before}`);
runGit(root, ["fetch", "origin", "main", "--tags"]);
const target = runGit(root, ["rev-parse", "origin/main"]);
console.log(`target: ${target}`);
if (before === target && !dirty) {
console.log("Already up to date.");
return;
}
runGit(root, ["reset", "--hard", "origin/main"]);
runCommand(root, "npm", ["ci"]);
if (dirty && !force) {
throw new Error("Refusing to update with local changes. Commit/stash them or rerun with --force for managed installs.");
}
console.log(`rlmx update: ${root}`);
console.log(`before: ${before}`);
runGit(root, ["fetch", "origin", "main", "--tags"]);
const target = runGit(root, ["rev-parse", "origin/main"]);
console.log(`target: ${target}`);
if (before === target && !dirty) {
console.log("Already up to date.");
return;
}
runGit(root, ["reset", "--hard", "origin/main"]);
runGit(root, ["clean", "-fd"]);
runCommand(root, "npm", ["ci"]);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/cli.ts` around lines 929 - 945, The update flow currently runs
runGit(root, ["reset", "--hard", "origin/main"]) but doesn't remove untracked
files, so using --force won't produce a fully clean checkout; call the git clean
step (e.g., runGit(root, ["clean", "-fdx"]) or at minimum ["clean", "-fd"])
before the reset when force is true (or when you decide to override a dirty
state) to remove untracked files and dirs; locate the update logic around the
dirty/force checks and add the runGit("clean", ...) call before runGit(...
"reset", "--hard", "origin/main") so the working tree truly matches origin/main
before runCommand(root, "npm", ["ci"]).

@namastex888 namastex888 merged commit 5143666 into main May 31, 2026
8 checks passed
@namastex888
Copy link
Copy Markdown
Collaborator Author

Follow-up to valid automated review comments is in #104. #103 merged before the follow-up commit landed, so the fixes were moved onto a fresh branch from current main.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants