Mainark local CLI
Terminal workflow for the PR loop you run today in Fulreach: validate → Claude review → GitHub checklist comment → Cursor fixes.
This lives in the Mainark repo; it runs in your target app repo (Fulreach, etc.) via MAINARK_TARGET, -C, REPO_PATH in Mainark .env, or your current directory.
Install
From the Mainark repo:
bun install
bun link # puts `mainark` on your PATH
Or without linking:
/path/to/mainark/bin/mainark --help
Target repo setup
In the app repo (once):
cd ~/fulreach
mainark init # copies default .ai/reviewer*.md if missing
Ensure package.json has a validation script the loop can call:
"scripts": {
"mainark:validate": "nx affected -t typecheck,lint,test,build --base=origin/main --head=HEAD --parallel=3"
}
Requires: gh (authenticated), claude (claude login), cursor agent (cursor agent login or CURSOR_API_KEY), pnpm.
Commands
| Command | What it does |
|---|---|
mainark start | Create/link Linear issue; rename branch to feat/ful-<n>-<slug> (before push) |
mainark publish | PR title FUL-n: …, Linear comment with PR URL, optional In Review |
mainark | Full loop: validate → Claude review → fix → commit/push (default mode: local) |
mainark validate | pnpm mainark:validate in target repo |
mainark review | Default — full loop in chosen mode |
mainark review --mode cloud | Claude review + Codex fix (Cursor fallback) |
mainark review --mode local | Claude review + local Cursor fix (+ Codex SDK fallback) |
mainark review --executor <name> | Override fix backend: auto, codex, cursor, or local |
mainark review --review-only | Claude review + checklist only (no fix) |
mainark review-claude | Same as review --review-only |
mainark review --post | Also post full review as PR comment |
mainark fix | Fix only from existing .reviews/ (retry after failure) |
mainark fix --re-review | Refresh Claude review, then run fix |
mainark dispatch [ID…] [-j N] | Implement via agent_runs; no IDs auto-picks up to N unblocked todos. Cloud returns while still running. cursor-local rejects concurrent multi-issue -j (shared worktree) — use cursor-cloud. |
mainark coordinate [--once|--watch] | Refresh inflight Cursor Cloud agent_runs (MAI-9). Laptop can leave after dispatch. |
mainark runs [--issue ID] | List recent agent_runs |
mainark ready | Derived merge queue: PR ∧ CI green ∧ latest review attempt PASS (MAI-7) |
mainark ledger-cancel --run-id N | Clear any stuck queued/running agent_run (implement/review/fix); does not change work_status |
mainark ledger-cancel --pr N --role review|fix | Clear stuck review/fix row by PR (after kill/timeout) |
mainark status 529 | Terminal checklist (like your paste) |
mainark status-post 529 | Upsert PR checklist comment |
mainark dev | Start Linear orchestrator (watch mode) |
mainark serve | Orchestrator one-shot |
Examples:
cd ~/fulreach
mainark start --title "Unified voices library" # before first push
git push -u origin HEAD
mainark publish # PR + Linear link
mainark review --pr 606 # full loop (local Cursor fix)
MAINARK_MODE=cloud mainark review --pr 606 # Codex-primary fix (hands off laptop)
mainark review --review-only # Claude verdict only (no fix)
mainark fix 529 # retry fix from .reviews
Requires in Mainark .env: LINEAR_API_KEY, and optionally LINEAR_TEAM_NAME=fulreach, MAINARK_BRANCH_PREFIX=feat/ful-.
Hands-off cloud dispatch (MAI-9)
With MAINARK_AGENT_EXECUTOR=cursor-cloud, mainark dispatch persists external_run_id and returns while the Cursor Cloud run is queued/running (exit 0). Exit 1 only if the run is already failed. The laptop can leave; the cloud VM keeps working.
Refresh the ledger later (Get Run → applyCloudRunResult). Does not dispatch the next issue or merge. Review/fix stays off by default:
mainark coordinate --once
mainark coordinate --watch --interval-ms 30000
Hands-off implement → PR → Claude review → fix (same loop as mainark review --pr N / until-clean). Opt-in only — no surprise review spend:
mainark dispatch FUL-999 FUL-1001 -j 2
mainark coordinate --watch --auto-review
# or: MAINARK_COORDINATE_AUTO_REVIEW=1 mainark coordinate --watch
When an implement run reaches succeeded with a PR number, coordinate ensures a worktree on that branch, then starts review→fix until clean. Fix backends follow MAINARK_MODE (local vs cloud), same as mainark review.
Blocking cloud implement (same outcome, waits)
With MAINARK_AGENT_EXECUTOR=cursor-cloud (or mainark implement … --executor cloud), leaf implement does not run the target repo’s scripts/mainark-implement.sh. It:
- Dispatches via
CursorCloudAgentExecutor(same asmainark dispatch) - Polls until the cloud run is terminal with a PR (bounded by
MAINARK_IMPLEMENT_TIMEOUT_MS, falling back toMAINARK_DAILY_TIMEOUT_MS/ 4h) - Runs the review→fix until-clean loop in the foreground (same path as
coordinate --auto-review)
This keeps epic checkpoints (FUL-845 --resume) meaningful: each leaf still blocks until consensus before the next stacked issue.
MAINARK_AGENT_EXECUTOR=cursor-cloud mainark implement FUL-978
# or:
mainark implement FUL-978 --executor cloud
Local remains the default: omit the env/--executor, or pass --executor local.
Daily unattended slot (mainark daily)
Once per Europe/Zurich calendar day, Mainark atomically claims a row in scheduled_dispatch_slots (UNIQUE(graph, trigger, slot_date)), picks ≤1 dispatch-eligible leaf, dispatches Cursor Cloud, and (transitionally) polls until the run is terminal or MAINARK_DAILY_TIMEOUT_MS (default 4h).
# Prerequisites: one successful manual cloud → FUL PR; GIT_DEFAULT_BRANCH=develop
MAINARK_AGENT_EXECUTOR=cursor-cloud mainark daily --dry-run
MAINARK_AGENT_EXECUTOR=cursor-cloud mainark daily
| Result | agent_runs.status | outcome / outcome_reason |
|---|---|---|
| Success + PR | succeeded | needs_human / pr_ready |
| Success, no PR | succeeded | needs_human / no_pr |
| Agent failed | failed | needs_human / failed |
| Cancelled | cancelled | needs_human / cancelled |
| Supervise timeout | stays running | needs_human / supervision_timeout |
| Dispatch throws after slot claim | (no run, or failed row) | Slack dispatch failed — slot stays consumed |
- Empty queue consumes the daily slot (one evaluation per day).
- Manual
mainark dispatchdoes not touch slots. - Never merges. Daily-triggered runs are skipped by
coordinate --auto-review. - Slack notifies when
SLACK_WEBHOOK_URLis set (webhook errors do not fail the job). - Blocking supervision is transitional — later
dailywill exit after dispatch andcoordinate/ Hetzner 5a will own handoff.
LaunchAgent example: ops/launchd/com.mainark.daily.plist.example.
| Path | Use when |
|---|---|
mainark implement … | Local leaf hook + review (default; laptop stays) |
implement --executor cloud / MAINARK_AGENT_EXECUTOR=cursor-cloud | Blocking cloud: dispatch → wait for PR → review→fix until clean |
dispatch + coordinate --watch | Cloud implement only; you run review yourself |
dispatch + coordinate --watch --auto-review | Fire-and-forget cloud hands-off (implement → PR → review → fix) |
mainark daily | Once/day autonomous pick → cloud PR → needs_human |
Watch interval defaults to MAINARK_COORDINATE_INTERVAL_MS (30000). Requires CURSOR_API_KEY. Auto-review also needs the same Claude/Cursor auth as mainark review (loaded from Mainark .env into the coordinate process — do not wrap in bare nohup without env).
Pointing Mainark at a repo
Priority:
mainark -C /path/to/repo …MAINARK_TARGET=/path/to/repo- Current directory (if it has
.gitand.ai/reviewer.mdormainark:validate) REPO_PATHin Mainark's.env(same path the orchestrator clones)
Shell alias (optional):
alias mainark='MAINARK_TARGET=~/fulreach mainark'
Outputs (in target repo)
.reviews/pr-<N>.json— structured verdict.reviews/pr-<N>.md— human review.reviews/pr-<N>.cursor-fix.log— Cursor agent log.reviews/pr-<N>.status-comment.md— checklist body posted to GitHub.mainark/handoffs/<ID>/<runId>.json— immutable implement → review handoff (MAI-36).mainark/handoff-<ID>.json— latest handoff pointer (convenience).mainark/handoff-<ID>.partial.json— implementer-authored handoff input
See Agent handoff.
PR comments use <!-- mainark-review-status --> (also updates legacy Fulreach markers).
Note: mainark review --review-only exits with code 1 when the verdict is FAIL (for scripts/CI). The full loop passes --continue-on-fail-verdict internally so fixes still run when there are MUST FIX items.
ReviewPipeline modes
One command runs all phases internally (validate → review → fix). Use flags or env to pick the fix backend; Claude review stays on the local CLI for now.
| Mode | Review | Fix (default --executor auto) |
|---|---|---|
local (--mode local, default) | Claude CLI | Local Cursor agent → Codex SDK fallback on failure |
cloud (--mode cloud) | Claude CLI | Codex SDK primary → Cursor fallback |
mainark review --pr 606 # local mode (default)
mainark review --pr 606 --mode cloud # cloud fix chain
mainark review --pr 606 --executor codex # force Codex fix only
MAINARK_MODE=cloud mainark review --pr 606 # env override
Escape hatches: mainark review-claude (review only), mainark fix (fix only / retry).
PR3: The fix phase routes through src/cli/reviewPipeline.ts and typed executors, but still delegates to the same bash scripts (cursor-fix-pr-review.sh, codexFallback.ts) so local behavior is unchanged. Set MAINARK_FIX_TS=0 to use the pre-PR3 inline bash router.
Orchestrator vs local loop
| Mode | Command | Use when |
|---|---|---|
| Local PR loop | mainark | You have a branch + PR; want review/fix on your machine |
| Linear orchestrator | mainark dev | Tickets labeled orchestrator-managed → agents → PRs in REPO_PATH |
Both can share the same REPO_PATH / Fulreach clone.
Note: When the target repo has scripts/mainark-hooks/manifest.json (e.g. Fulreach), Mainark loads optional hooks for validate-with-log, post-fix PR description, and workspace resolution. Mainark owns review/fix; the target repo owns pnpm mainark:validate and hooks only.
Set MAINARK_LEGACY_REVIEW=1 to restore delegation to $MAINARK_ROOT/scripts/mainark.sh (deprecated).
Target repo hooks (Fulreach)
Optional manifest: $MAINARK_ROOT/scripts/mainark-hooks/manifest.json (see cli/lib/hooks/manifest.schema.json).
| Hook | Purpose |
|---|---|
validateWithLog | Custom validate runner (default: pnpm mainark:validate + log) |
validateFailureArtifacts | Build .reviews from validate failures |
postFix | PR description / SaaS template after fix |
resolveWorkspace | Override PR worktree resolution |
consensusCheck | Implement consensus gate |
Repo-only scripts (via manifest repoScripts): mainark-update-pr-description.mjs, etc.
Phased epic implement (Linear plan)
Epics with ## Mainark plan in the Linear description run phase-by-phase with human checkpoints (checkpoint: merge or checkpoint: approve). See Human checkpoints and merge vs stack.
mainark plan FUL-515 # parse plan, no side effects
mainark implement FUL-515 --dry-run # same
mainark implement FUL-515 # run current phase (sequential default)
mainark implement FUL-515 --resume # after merges, advance phase
mainark status FUL-515 # phase progress + PR stack
mainark status 626 # PR checklist — unchanged
mainark implement FUL-521 # leaf issue (no plan) — unchanged
Plan format (in epic Linear description)
## Mainark plan
### Phase 1 — Foundation
checkpoint: merge
- FUL-516 base: main
- FUL-517 base: main
### Phase 2 — Gated module
checkpoint: approve
- FUL-518 base: main
### Phase 3 — Setup
checkpoint: merge
stack: true
- FUL-521 base: main
- FUL-522 base: stack
checkpoint: merge—--resumeverifies PRs merged viaghcheckpoint: approve— human sign-off;--resumewithout merge checkstack: true+base: stack— PR chains on previous issue branch (within-track only)- Parallel roots in a phase — multiple
base: mainlines, nostack: true - State:
.reviews/epic-<EPIC>.state.jsonin target repo --parallel(opt-in): run current phase via worktree stack bootstrap
Coexists with .mainark/epics.json queue (mainark queue / mainark next).
Parallel features (worktrees)
Run several spec’d features at once without branch mix-ups. Stay in the Fulreach repo — Mainark CLI resolves via bun link; issue SoT follows Mainark .env (TASK_BACKEND=sqlite or linear). Relative SQLITE_PATH is resolved against MAINARK_HOME, not the Fulreach cwd.
After creating issues (mainark start / SQLite provider)
cd ~/fulreach
# Full pipeline: worktrees → agents → local review → push → stacked PRs → promote to PR
# No ids: every issue in Todo (optional MAINARK_IMPLEMENT_LABEL to filter)
mainark implement
mainark implement FUL-407 FUL-409 FUL-410 FUL-411 FUL-408
# Preview queue without running agents (no worktrees, registry writes, or ship)
mainark implement --plan
mainark implement --plan FUL-407 FUL-409
# Skip push/review (inspect diffs first)
mainark implement --no-ship FUL-407 …
# Optional plan preview
mainark parallel --plan FUL-407 FUL-411 FUL-408
# Partial steps
mainark parallel FUL-407 … # setup only
mainark parallel agents FUL-407 FUL-409
mainark parallel ship FUL-407 FUL-409 --yes-push
# or: mainark parallel ship --append --yes-push
Set blocking relations on the task backend (FUL-411 blocked by FUL-407, etc.) so stack order is correct. Legacy merge-first: mainark implement --no-stack ….
Codex fallback (safety layer)
If Claude review or Cursor implement/fix fails, the CLI retries with Codex SDK when OPENAI_API_KEY is in Mainark .env (on by default; MAINARK_CODEX_FALLBACK=0 to disable).
| Step | Primary | Fallback |
|---|---|---|
| Pre-push review | Claude | Codex review-local |
| Pre-push fixes | Cursor agent | Codex fix-local |
| PR review | Claude | Codex reviewer |
| Ticket implement | Cursor agent | Codex developer |
| PR review fixes | Cursor agent | Codex fix |
Cursor model: Local fix/implement defaults to --model Auto (MAINARK_CURSOR_MODEL, default Auto) so Pro premium quota is not burned. Set MAINARK_CURSOR_MODEL=default to omit --model (CLI default). Lowercase auto is normalized to Auto.
Usage limits: If Cursor logs a Pro usage/quota limit (“Switch to Auto”, etc.), soft --continue retries are skipped and Codex runs immediately (same as exhausting retries). Auth failures still do not fall through to Codex.
Extra logs: .reviews/local-FUL-N.codex-fix.log, .reviews/pr-N.codex-fix.log, .mainark/logs/implement-FUL-N.codex.log.
Manual / PR-based
# Register worktrees (existing PRs or Linear ids)
mainark parallel add 529 530
mainark parallel add FUL-123 FUL-124
mainark parallel add --new FUL-125 --title "Reconnect landing"
# 2) Open each tree in Cursor (or separate terminals)
mainark parallel paths
# 3) After you implement in each tree — push, open PRs, review until clean
mainark parallel push --yes
mainark parallel publish
mainark parallel until-clean --jobs 2
# Or one-shot tail (after implementation):
mainark parallel ship --yes-push
Registry: .mainark/parallel.json in the target repo (one batch for the clone — mainark implement refuses leftover slots). Worktrees default to ../wt-<repo-name>/ next to the clone (MAINARK_PARALLEL_BASE to override).
Implement progress (second terminal)
While mainark implement runs Cursor agents in parallel:
mainark parallel agents-status # one-shot snapshot
mainark parallel agents-status --watch # refresh every 3s until all done/failed
mainark parallel agents-status FUL-431 # filter slots
Per slot, shows pipeline stage:
| Stage | Meaning |
|---|---|
implementing | Cursor implement agent |
prepush · reviewing | Claude local review |
prepush · fixing | Cursor local review fixes |
prepush · blocked | validate failed, no review, or pending must_fix/missing_tests |
prepush · local-clean | passed local-is-clean (ready to push) |
shipped | PR exists on registry slot |
Also: orchestrator line (implement vs ship), commit, pending counts, browse/tail hints. Files: .mainark/implement-FUL-N.status.json, .mainark/logs/parallel-local-FUL-N.log, .reviews/local-FUL-N.json.
Pre-push review (hybrid)
mainark implement / mainark parallel ship now runs local review before the first push:
- Local — validate → (on validate fail: compare vs merge-base; baseline-only debt skips block → else scoped
validate_failureartifacts → Cursor fix → retry) → Claude review ongit diffvsstackBase/ default branch → Cursor fixes → loop until clean. Artifacts:.reviews/local-FUL-407.json|.md(no PR yet). - Push + publish — stacked PRs open only after local pass.
- Promote — copies local review to
.reviews/pr-N.*and posts the PR checklist comment. - Optional PR loop — set
MAINARK_PR_REVIEW_AFTER_PUSH=1to run the fulluntil-cleanon GitHub after push (off by default; enable in Mainark.envfor hands-off ship).
Manual step: mainark parallel local-until-clean (same as ship step 1).
until-clean (PR-based) still applies when you already have PRs or want a second pass on CI.
Logs: .mainark/logs/parallel-local-<key>.log (pre-push), .mainark/logs/parallel-<key>.log (PR loop).
Baseline-aware validate (pre-existing suite debt)
When pnpm mainark:validate fails, Mainark compares failing tests/targets against a merge-base baseline (cached under .mainark/cache/validate-baseline-<sha>.log). If every failure already exists on the base branch, ship continues to Claude review instead of burning review cycles on unrelated red suites (e.g. api:test already failing on develop while you only changed web).
- Disable:
MAINARK_VALIDATE_BASELINE_AWARE=0 - Escape hatch:
mainark parallel ship --skip-validate --yes-pushormainark parallel local-until-clean --skip-validate - Scoped failures only: when some failures are new, Cursor auto-fix sees new failures only in
must_fix
Cursor: ask the agent to run mainark parallel paths and implement each listed cd path, then mainark parallel ship --yes-push when done.
GitHub Actions (Fulreach)
Keep pull_request triggers for opened and synchronize. That is the right hook: CI runs when the PR exists and updates after push — which now happens after local review, not on raw agent output.
Avoid (or narrow) workflows that run on every push to feat/** before a PR exists, if those duplicate expensive checks on unreviewed commits.
Suggested pattern:
on:
pull_request:
types: [opened, synchronize, reopened, ready_for_review]
# Optional: drop bare push to feature branches, or gate with:
# push:
# branches: ['feat/**']
# paths-ignore: ...
Optional extras:
pull_request→draft— skip CI on drafts; run when marked ready for review.- Label gate — e.g. only run full CI when label
ready-for-ciis present (mainark can add it inpublishlater). - Do not rely on
pushtomainfor feature work; stacked PRs still usepull_requestper branch.
After ship, GitHub gets a checklist comment from the promoted local review; you do not need a separate “review bot” Action unless you want server-side re-review.
Fulreach migration
Fulreach already ships bin/mainark → scripts/mainark.sh. To use this package as the source of truth:
bun linkfrom Mainark (or add Mainark as a devDependency).- In Fulreach
package.json, set"mainark": "mainark"inbinor scripts to delegate to the linked CLI. - Keep Fulreach-specific
.ai/fulreach-standards.mdetc.; the reviewer reads any.ai/*-patterns.mdand*-standards.mdautomatically.
Legacy pnpm review scripts can stay as thin wrappers calling mainark.