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

CommandWhat it does
mainark startCreate/link Linear issue; rename branch to feat/ful-<n>-<slug> (before push)
mainark publishPR title FUL-n: …, Linear comment with PR URL, optional In Review
mainarkFull loop: validate → Claude review → fix → commit/push (default mode: local)
mainark validatepnpm mainark:validate in target repo
mainark reviewDefault — full loop in chosen mode
mainark review --mode cloudClaude review + Codex fix (Cursor fallback)
mainark review --mode localClaude review + local Cursor fix (+ Codex SDK fallback)
mainark review --executor <name>Override fix backend: auto, codex, cursor, or local
mainark review --review-onlyClaude review + checklist only (no fix)
mainark review-claudeSame as review --review-only
mainark review --postAlso post full review as PR comment
mainark fixFix only from existing .reviews/ (retry after failure)
mainark fix --re-reviewRefresh 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 readyDerived merge queue: PR ∧ CI green ∧ latest review attempt PASS (MAI-7)
mainark ledger-cancel --run-id NClear any stuck queued/running agent_run (implement/review/fix); does not change work_status
mainark ledger-cancel --pr N --role review|fixClear stuck review/fix row by PR (after kill/timeout)
mainark status 529Terminal checklist (like your paste)
mainark status-post 529Upsert PR checklist comment
mainark devStart Linear orchestrator (watch mode)
mainark serveOrchestrator 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:

  1. Dispatches via CursorCloudAgentExecutor (same as mainark dispatch)
  2. Polls until the cloud run is terminal with a PR (bounded by MAINARK_IMPLEMENT_TIMEOUT_MS, falling back to MAINARK_DAILY_TIMEOUT_MS / 4h)
  3. 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
Resultagent_runs.statusoutcome / outcome_reason
Success + PRsucceededneeds_human / pr_ready
Success, no PRsucceededneeds_human / no_pr
Agent failedfailedneeds_human / failed
Cancelledcancelledneeds_human / cancelled
Supervise timeoutstays runningneeds_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 dispatch does not touch slots.
  • Never merges. Daily-triggered runs are skipped by coordinate --auto-review.
  • Slack notifies when SLACK_WEBHOOK_URL is set (webhook errors do not fail the job).
  • Blocking supervision is transitional — later daily will exit after dispatch and coordinate / Hetzner 5a will own handoff.

LaunchAgent example: ops/launchd/com.mainark.daily.plist.example.

PathUse when
mainark implement …Local leaf hook + review (default; laptop stays)
implement --executor cloud / MAINARK_AGENT_EXECUTOR=cursor-cloudBlocking cloud: dispatch → wait for PR → review→fix until clean
dispatch + coordinate --watchCloud implement only; you run review yourself
dispatch + coordinate --watch --auto-reviewFire-and-forget cloud hands-off (implement → PR → review → fix)
mainark dailyOnce/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:

  1. mainark -C /path/to/repo …
  2. MAINARK_TARGET=/path/to/repo
  3. Current directory (if it has .git and .ai/reviewer.md or mainark:validate)
  4. REPO_PATH in 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.

ModeReviewFix (default --executor auto)
local (--mode local, default)Claude CLILocal Cursor agent → Codex SDK fallback on failure
cloud (--mode cloud)Claude CLICodex 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

ModeCommandUse when
Local PR loopmainarkYou have a branch + PR; want review/fix on your machine
Linear orchestratormainark devTickets 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).

HookPurpose
validateWithLogCustom validate runner (default: pnpm mainark:validate + log)
validateFailureArtifactsBuild .reviews from validate failures
postFixPR description / SaaS template after fix
resolveWorkspaceOverride PR worktree resolution
consensusCheckImplement 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--resume verifies PRs merged via gh
  • checkpoint: approve — human sign-off; --resume without merge check
  • stack: true + base: stack — PR chains on previous issue branch (within-track only)
  • Parallel roots in a phase — multiple base: main lines, no stack: true
  • State: .reviews/epic-<EPIC>.state.json in 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).

StepPrimaryFallback
Pre-push reviewClaudeCodex review-local
Pre-push fixesCursor agentCodex fix-local
PR reviewClaudeCodex reviewer
Ticket implementCursor agentCodex developer
PR review fixesCursor agentCodex 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:

StageMeaning
implementingCursor implement agent
prepush · reviewingClaude local review
prepush · fixingCursor local review fixes
prepush · blockedvalidate failed, no review, or pending must_fix/missing_tests
prepush · local-cleanpassed local-is-clean (ready to push)
shippedPR 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:

  1. Local — validate → (on validate fail: compare vs merge-base; baseline-only debt skips block → else scoped validate_failure artifacts → Cursor fix → retry) → Claude review on git diff vs stackBase / default branch → Cursor fixes → loop until clean. Artifacts: .reviews/local-FUL-407.json|.md (no PR yet).
  2. Push + publish — stacked PRs open only after local pass.
  3. Promote — copies local review to .reviews/pr-N.* and posts the PR checklist comment.
  4. Optional PR loop — set MAINARK_PR_REVIEW_AFTER_PUSH=1 to run the full until-clean on GitHub after push (off by default; enable in Mainark .env for 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-push or mainark 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_requestdraft — skip CI on drafts; run when marked ready for review.
  • Label gate — e.g. only run full CI when label ready-for-ci is present (mainark can add it in publish later).
  • Do not rely on push to main for feature work; stacked PRs still use pull_request per 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/mainarkscripts/mainark.sh. To use this package as the source of truth:

  1. bun link from Mainark (or add Mainark as a devDependency).
  2. In Fulreach package.json, set "mainark": "mainark" in bin or scripts to delegate to the linked CLI.
  3. Keep Fulreach-specific .ai/fulreach-standards.md etc.; the reviewer reads any .ai/*-patterns.md and *-standards.md automatically.

Legacy pnpm review scripts can stay as thin wrappers calling mainark.