pi-loop: Jira issue → Draft GitLab MR orchestrator built on the pi SDK
  • TypeScript 99.9%
  • JavaScript 0.1%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
2026-09-05 20:20:12 +00:00
docs documentation: add ADR-021 no-code-change comment and implementation plan (#312) 2026-09-05 18:48:29 +00:00
src 307: Surface the posted comment in the run summary and CLI outcome line 2026-09-06 05:54:29 +10:00
.gitignore documentation: commit orphaned implementation-plan docs, drop stale gitignore rules (#294) 2026-08-19 04:31:02 +00:00
.prettierignore M0: project scaffold & config (#10) 2026-07-24 01:28:02 +00:00
.prettierrc.json M0: project scaffold & config (#10) 2026-07-24 01:28:02 +00:00
AGENTS.md 311: Doc updates and validation for the no-code-change comment feature 2026-09-06 06:06:01 +10:00
CHANGELOG.md 282: Fix config precedence: piloop-config.yaml should outrank env vars (CLI > YAML > env > defaults) 2026-08-19 10:44:28 +10:00
CODING_STANDARDS.md Remove gitExclude and assertCleanWorkingTree (#53) 2026-07-30 11:39:22 +00:00
DESIGN.md 311: Doc updates and validation for the no-code-change comment feature 2026-09-06 06:13:55 +10:00
DOMAIN.md 267: Network-level fetch failures produce opaque "fetch failed" errors and skip bounded retries 2026-08-18 17:02:56 +10:00
eslint.config.js M0: project scaffold & config (#10) 2026-07-24 01:28:02 +00:00
IMPLEMENTATION_PLAN.md 282: Fix config precedence: piloop-config.yaml should outrank env vars (CLI > YAML > env > defaults) 2026-08-19 10:39:17 +10:00
NOTICE issue-249: add claude-sonnet-5/claude-opus-5 to Vertex catalog + document adding new Vertex models (#250) 2026-08-18 00:01:38 +00:00
package-lock.json issue-251: loosen @types/js-yaml version pin to a caret range (#252) 2026-08-18 00:33:36 +00:00
package.json issue-251: loosen @types/js-yaml version pin to a caret range (#252) 2026-08-18 00:33:36 +00:00
piloop-config.example.yaml 282: Fix config precedence: piloop-config.yaml should outrank env vars (CLI > YAML > env > defaults) 2026-08-19 10:39:17 +10:00
piloop-config.yaml chore: add piloop-config.yaml (#288) 2026-08-19 02:30:40 +00:00
README.md 311: Doc updates and validation for the no-code-change comment feature 2026-09-06 06:13:55 +10:00
tsconfig.build.json Make pi-loop an installable CLI (Phase 1) (#26) 2026-07-27 02:50:29 +00:00
tsconfig.json M0: project scaffold & config (#10) 2026-07-24 01:28:02 +00:00
vitest.config.ts 222: docs stage: tests — unit seams + commit-model/resume e2e updates + dedicated docs-stage e2e 2026-08-17 10:06:56 +10:00

pi-loop

Turn an issue (Jira, GitHub, Forgejo, or GitLab) into a reviewed, remediated Draft MR/PR on whatever platform hosts your repo — automatically.

pi-loop is a Node.js/TypeScript app built on the pi SDK (@earendil-works/pi-coding-agent) that runs a five-stage agent loop:

issue fetch → implement → review → remediate → docs → MR/PR creation → issue write-back

You run it from inside your target repo with a single issue ref; it produces a Draft MR/PR and writes a comment back to the source issue. The Draft MR/PR is the only human checkpoint — there is no human gate mid-run.

Issue source and MR platform are independent axes: fetch from any supported provider, open the MR/PR via the right mechanism for the detected origin (glab for GitLab, gh for GitHub, or the Forgejo REST API for Forgejo). See docs/design-multi-provider.md.

See DESIGN.md for architecture and decisions, and IMPLEMENTATION_PLAN.md for the build plan. Contributors should also read AGENTS.md.

How it works

Each stage runs as a fresh pi agent session with a least-privilege tool allowlist. Stages communicate through on-disk artifacts (not shared conversation memory), which makes every hand-off explicit, inspectable, and re-runnable.

Stage Does Produces
0. Issue fetch Fetch + normalize the issue via REST (plain code, no LLM) issue-context.json
1. Implement Implement the ticket in the working tree implementation.diff
2. Review Run build/test/lint + static review vs project standards review-findings.md
3. Remediate Single pass to fix findings final.diff
3.5 Docs Sync README.md + docs/**/*.md to the implemented behavior docs.diff
4. MR creation Branch, commit, push, open a Draft MR/PR (origin platform) mr-result.json
— Write-back Comment the MR/PR link on the source issue (issue updated)

All artifacts are written under .pi-loop/runs/<run-id>/ and kept out of the captured diff. The docs stage is non-fatal: if it fails, pi-loop warns and continues to MR creation; "no doc updates needed" is a success outcome.

Prerequisites

  • Node.js >= 22.19.0 and npm (a TypeScript toolchain is only needed for local development, not to run an installed build).
  • git and the CLI for CLI-driven platforms: glab (GitLab) or gh (GitHub). Forgejo MR creation uses the Forgejo REST API and does not need the fj CLI. pi-loop detects the platform from the git origin and fails fast at preflight when a required CLI is missing.
  • Credentials for the providers used in the run (see Configuration): Jira creds for Jira issues, GITHUB_TOKEN for GitHub issues, FORGEJO_TOKEN for Forgejo issues, GITLAB_TOKEN for GitLab issues/MRs.
  • A model/auth configured for the pi SDK (ModelRuntime).
  • The target repo in a clean git state.
  • The target repo has a .gitignore containing a .pi-loop/ rule (enforced at startup — pi-loop aborts with instructions otherwise).
  • When the docs stage is enabled (default), none of its in-scope doc files (default README.md + docs/**/*.md) may be covered by a git ignore source (.gitignore, .git/info/exclude, global excludes) — enforced at startup; pass --no-docs to skip this check and disable the docs stage for the run.

Install

pi-loop installs as a global pi-loop command (Phase 1 is a maintainer-only local install; publishing to a registry is deferred to Phase 2 — see Roadmap).

npm prefix setup (required)

install:local uses npm link, which writes to npm's global prefix. If your prefix is /usr/local (the default on many systems), you need a user-level prefix to avoid EACCES: permission denied:

mkdir -p ~/.npm-global
npm config set prefix ~/.npm-global
echo 'export PATH="$HOME/.npm-global/bin:$PATH"' >> ~/.bashrc
source ~/.bashrc

Verify with npm config get prefix — it should show ~/.npm-global (or your chosen path).

Install from source

git clone <this-repo>
cd pi-loop
npm install          # installs deps and builds dist/ via the prepare hook
npm run install:local   # build + npm link → global `pi-loop` command

Verify the install and run it from any target repo (no tsx/npx needed):

pi-loop --help

Updating

There is no auto-update. To pick up new changes, pull and reinstall:

git pull
npm run install:local   # rebuilds dist/ and reinstalls the global binary

Configuration

Set these environment variables (e.g. in a .env or your shell):

Variable Required Description
JIRA_BASE_URL yes Jira Cloud base URL, e.g. https://acme.atlassian.net
JIRA_EMAIL yes Jira account email (Basic auth)
JIRA_API_TOKEN yes Jira API token (read + comment)
GITLAB_TOKEN yes GitLab token used by glab
GITHUB_TOKEN source-dependent GitHub token (GitHub-as-source + write-back)
FORGEJO_TOKEN source-dependent Forgejo token (Forgejo-as-source + MR creation + write-back)
FORGEJO_BASE_URL no Forgejo base URL (inferred from origin host)
GITLAB_BASE_URL no GitLab base URL (inferred from origin host)
PILOOP_MODEL yes Model id, e.g. anthropic/claude-opus-4-5required for deterministic model pinning; unset/empty aborts the run (fail-fast) to keep pi-loop independent of the interactive pi session's default model. Overridable per run with --model <id> (the flag wins when both are set)
PILOOP_THINKING_LEVEL no Thinking level for the model
PILOOP_VERTEX_ANTHROPIC no Set to 1 to enable the Claude-on-Vertex provider (opt-in)
PILOOP_AUTO_MERGE no Set to 1 or true to auto-merge the PR/MR after creation
PILOOP_AUTO_MERGE_TIMEOUT_MS no Max wait for mergeability (default: 600000 = 10 minutes)
PILOOP_AUTO_MERGE_POLL_MS no Poll interval in ms (default: 15000 = 15 seconds)
PILOOP_E2E no Set to 1 to enable the gated end-to-end test
PILOOP_BASH_DEFAULT_TIMEOUT_S no Default bash timeout in seconds (default: 120)
PILOOP_BASH_MAX_TIMEOUT_S no Hard cap for bash timeouts in seconds (default: 600)

Each stage's skill body is bundled in src/skills/ (edit the TypeScript modules there), so there are no skill-path env vars. The retired IMPLEMENT_SKILL_PATH, REVIEW_SKILL_PATH, REMEDIATE_SKILL_PATH, and MR_SKILL_PATH vars are ignored; the CLI warns when they are still set.

piloop-config.yaml (per-repo config)

For stable per-repo settings, create a committed piloop-config.yaml in the repo root (the directory you run pi-loop from). It is optional — when absent, behavior is unchanged. It covers every non-secret setting; credentials and Jira email always stay in the environment.

A fully annotated example is committed at piloop-config.example.yaml. Start from it:

cp piloop-config.example.yaml piloop-config.yaml

Then edit the copy to match your repo. A condensed example:

# piloop-config.yaml — example
source: jira # issue source (jira|github|forgejo|gitlab)
git-host: forgejo # MR platform (github|forgejo|gitlab)
label: pi-loop # scan label filter (default: pi-loop)
max-issues: 50 # scan cap (default: 50)
model: anthropic/claude-opus-4-5
thinking-level: high
auto-merge:
  enabled: true # equivalent to --auto-merge / PILOOP_AUTO_MERGE
  timeout-ms: 600000 # max wait for mergeability (default: 600000)
  poll-ms: 15000 # poll interval (default: 15000)
bash:
  default-timeout: 120 # PILOOP_BASH_DEFAULT_TIMEOUT_S (default: 120)
  max-timeout: 600 # PILOOP_BASH_MAX_TIMEOUT_S (default: 600)
jira:
  acceptance-criteria-field-id: customfield_10001
gitlab:
  base-url: https://gitlab.example.com # inferred from origin when unset
forgejo:
  base-url: https://code.example.com # inferred from origin when unset
docs:
  enabled: true # default: true — set false to disable the docs stage
  paths: # default: [README.md, docs/**/*.md]
    - README.md
    - docs/**/*.md
  exclude: # default: [docs/reference/**] — applied on top of any paths
    - docs/reference/**

The docs stage can also be disabled per run with the --no-docs CLI flag; it overrides docs.enabled for that invocation only (CLI flag > YAML).

Precedence: CLI flag > YAML > env var > built-in defaults. The file is the repo's committed baseline; it overrides environment variables (e.g. model: in the file beats PILOOP_MODEL), and a CLI flag overrides both (e.g. --model beats model: in the file). Env vars only fill keys the file leaves unset.

Validation: strict — unknown keys, wrong types, or malformed YAML abort the run with exit code 1 (kind: config). Fix the file and re-run. Per-invocation switches (issue refs, --fresh, --scan, --yes, --list-models, --help) are not configurable from the file.

Claude on Vertex (optional)

pi-loop can run its stage sessions on Claude models served via Google Cloud Vertex AI. This is opt-in and off by default — when disabled, no provider is registered and behaviour is unchanged.

Enable it by setting the flag and selecting a vertex-anthropic/... model:

Variable Description
PILOOP_VERTEX_ANTHROPIC Set to 1 to register the provider
PILOOP_MODEL e.g. vertex-anthropic/claude-opus-4-8
GOOGLE_CLOUD_PROJECT GCP project (or ANTHROPIC_VERTEX_PROJECT_ID)
region (one of below) CLOUD_ML_REGION, VERTEX_LOCATION, VERTEXAI_LOCATION, GOOGLE_CLOUD_LOCATION
GOOGLE_APPLICATION_CREDENTIALS Service-account key path (optional; ADC / gcloud are used otherwise)

Auth resolves in order: service-account JSON → Application Default Credentials (gcloud auth application-default login) → gcloud CLI. Tokens are never logged.

Working model/region caveat: the vendored default region is us-east5, but for some projects only global works (e.g. global + claude-opus-4-8). Set one of the region vars to global if you hit 429/400/404 errors. Example:

PILOOP_VERTEX_ANTHROPIC=1 \
PILOOP_MODEL="vertex-anthropic/claude-opus-4-8" \
GOOGLE_CLOUD_PROJECT=<your-project> \
VERTEX_LOCATION=global \
pi-loop <ISSUE-REF>

The provider code under src/model/vertexAnthropic/ is vendored (MIT) from pi-provider-vertex-anthropic; see NOTICE for attribution.

⚠️ Adding a new Vertex model (important)

The Vertex-Anthropic model catalog is hardcoded in src/model/vertexAnthropic/models.ts (VERTEX_MODELS) — it is a vendored snapshot, not a live lookup. When Anthropic ships a new Claude model on Vertex AI, pi-loop does not pick it up automatically; the catalog must be updated by hand or the model id will fail to resolve (--model vertex-anthropic/<new-id> errors as unknown).

To add one:

  1. Add an entry to the VERTEX_MODELS array in models.ts with the model's id (the exact Vertex publisher-model id, e.g. claude-opus-5-1), a human-readable name, reasoning, input, contextWindow, maxTokens, and per-1M-token cost fields — copy the shape of an existing entry.
  2. Update NOTICE's "Model catalog deviation" note: this file is vendored from upstream pi-provider-vertex-anthropic, so any model added here ahead of (or diverging from) what upstream ships must be recorded there, and removed/reconciled once upstream picks it up.
  3. Add/extend a test in registerVertexAnthropic.test.ts asserting the new id is present with the expected reasoning/limits (see the existing claude-sonnet-5/claude-opus-5 cases for the pattern).
  4. Run npm test and npm run lint, then verify end-to-end with PILOOP_MODEL="vertex-anthropic/<new-id>" (see the working model/region caveat above — some ids only resolve in the global region).

Model ids and limits are otherwise not validated against a live Vertex API — an incorrect id, contextWindow, or maxTokens will not fail fast; it surfaces as a runtime 429/400/404 from Vertex or truncated output.

Usage

Run the installed pi-loop command from inside the target repository (there is no --repo argument):

cd /path/to/your/target/repo
pi-loop ABC-123              # Jira issue key
pi-loop 42                   # issue #42 on the detected origin platform
pi-loop owner/repo#42        # issue #42 on that repo's platform
pi-loop issue-42             # alias for issue #42 on the origin platform
pi-loop 42 --source github   # explicit provider override
pi-loop 42 --fresh           # force clean start (ignore incomplete runs)
pi-loop 42 --model anthropic/claude-opus-4-5   # per-run model override (flag wins over PILOOP_MODEL)

During local development you can run the CLI without installing via npm start -- <ISSUE-REF> (see Development).

The MR/PR platform is detected from git remote get-url origin; --source overrides provider inference for the issue ref (ambiguous refs fail with a usage error).

pi-loop will:

  1. Fetch and validate the issue from the resolved provider.
  2. Implement it, review it, and remediate it (single pass).
  3. Open a Draft MR/PR on the origin platform (branch feature/<ISSUE-KEY>/<slug> for Jira, feature/issue-<number>/<slug> for GitHub/Forgejo/GitLab; target = default branch) via glab, gh, or the Forgejo REST API.
  4. Write back to the source issue: a comment with the MR/PR link (uniform across providers).

Inspect run artifacts under .pi-loop/runs/<run-id>/.

Auto-merge

By default pi-loop creates a Draft MR/PR. With --auto-merge (or PILOOP_AUTO_MERGE=1), it creates a non-draft MR/PR, polls until the CI checks pass and the PR/MR is mergeable, then merges it automatically. After a successful merge the source issue is closed (Forgejo, GitHub, GitLab; Jira is a no-op — Jira issues are not auto-closed).

pi-loop 42 --auto-merge          # enable via CLI flag
PILOOP_AUTO_MERGE=1 pi-loop 42   # enable via environment

Tuning:

Variable Default Description
PILOOP_AUTO_MERGE_TIMEOUT_MS 600000 Total timeout waiting for mergeability (ms)
PILOOP_AUTO_MERGE_POLL_MS 15000 Interval between mergeability checks (ms)

When auto-merge times out or the merge API fails, the run fails with auto-merge-timeout or auto-merge-failed respectively. The write-back comment reflects the actual outcome (draft, merged, or merge-failed).

Resume

When pi-loop exits unexpectedly (agent crash, operator kill, network outage), re-running the same command automatically resumes from the failed stage instead of starting over.

How it works:

  1. The CLI scans .pi-loop/runs/ for the most recent incomplete run matching the issue key.
  2. A run is incomplete when error.json exists and no successful mr-result.json or mr-skipped.json exists.
  3. Artifact existence determines which stages are skipped — completed stages are reused, failed stages are re-run.
  4. The working tree is trusted as-is (no git manipulation).
pi-loop ABC-123              # auto-resumes if incomplete run exists
pi-loop ABC-123 --fresh      # force clean start, ignore incomplete runs

Working tree warnings: During resume, if the working tree has uncommitted changes, a warning is logged but the run continues (expected after implement/ remediate stages). Use --fresh to start over on a clean tree.

Escaping resume: --fresh creates a completely new run with a new timestamp and run directory, ignoring any incomplete runs.

Resuming with a different model: pass --model <id> on the resume command to re-run the interrupted stage with a different model (e.g. after a model-related failure). Nothing pins the model to the original run; the per-stage model banner lines in run.log show which model each stage actually used.

Batch mode

Batch mode processes multiple issues serially in a single invocation. It is opt-in and requires --auto-merge (each issue is auto-merged before the next starts).

# Manual batch: multiple refs
pi-loop ABC-1 DEF-2 --auto-merge

# Scan mode (interactive — select issues from a label)
pi-loop --scan --auto-merge

# Scan mode (non-interactive, accept all)
pi-loop --scan --auto-merge --yes

Scan mode (--scan) discovers open issues by label on the origin platform (Forgejo and GitLab). Use --label <label> to change the filter (default: pi-loop) and --max-issues <n> to cap results (default: 50). Issues are ordered by priority (critical > high > medium > low > none), then by creation date (oldest first).

Batch resume: re-running the same batch command skips completed issues and resumes from the failed one.

Console output

Agent output streams live by default — no flags needed, in single-issue and batch mode alike (see ADR-011):

  • Full model thinking streams to the terminal as an indented block — one thinking: marker line, continuation lines indented under it, no truncation. This is the narrative of why the agent acts at every step.
  • Visible assistant text streams live as it is produced — deltas append to the current line (no per-delta timestamps) and embedded newlines start continuation lines.
  • Tool call lines and results keep their existing compact format (errors always in full). Structural events (message/turn/agent boundaries, compaction, auto-retry, queue) stay suppressed.
  • Colors (dim thinking, normal assistant text, colored tool lines, red errors, blue prefix brackets) are enabled only when stdout is a TTY and NO_COLOR is not set, so redirected output and CI logs stay plain and free of ANSI escape codes. Partial lines are flushed at message/turn/tool boundaries and session end, so > file redirects stay line-readable.

In batch mode, every new line carries a per-issue [i/N issue-KEY] prefix (prefixes are applied on new lines only — continuation lines are indented, so a long thinking block never becomes thousands of prefixed lines):

[2/3 issue-ABC-2][12:00 implement][bash] npm test
[2/3 issue-ABC-2][12:00 implement] thinking: The issue requires adding a timeout to the fetch
                  call. Let me locate the fetch site and check the tests.
[2/3 issue-ABC-2][12:00 implement] INFO implementing

Every agent line carries a [HH:mm <stage>] prefix; tool-start lines add a [tool] bracket. Logger lines use the same [HH:mm <stage>] prefix (console and run.log), and the whole prefix bracket group is colored blue on a TTY (see ADR-012). There are no stage header separator lines anymore — the stage is always visible in the prefix.

  • Batch status lines (Batch [i/N]: …) keep their existing format and are not prefixed — they are self-identifying:

    Batch [1/3]: starting forgejo:ABC-1
    Batch [1/3]: ✓ forgejo:ABC-1
    

A batch-manifest.json is written under .pi-loop/runs/<batch-id>/ after the batch completes, recording the plan, selections, and per-issue outcomes.

Exit codes & error handling

Exit code Meaning
0 Success — a Draft MR was created, or a no-code-change run completed (the deterministic comment on the source issue is the deliverable of that path)
1 Pipeline failure — a stage failed hard; see error.json
2 Usage error — bad/missing Jira key or unknown flag

The orchestrator enforces a per-stage error-handling policy:

  • Fail-fast on invalid setup/config, missing creds, or Jira 401/403/404 (before any agent session starts). This includes a docs-scope preflight: if the docs stage is enabled and any in-scope doc file is covered by a git ignore source, pi-loop aborts before any session starts (see Prerequisites).
  • Bounded retries (3 attempts, exponential backoff) on transient Jira/GitLab/model errors (429/5xx/network).
  • Bundled skill bodies (in src/skills/) are always present, so stages never degrade for a missing skill; degradedStages is retained for compatibility and stays empty.
  • Abort + persist on a hard agent-stage failure: the session is disposed, artifacts produced so far are kept, and error.json (kind: agent-failure) is written.
  • Non-fatal write-back: a write-back failure after the MR is created never fails the run (the MR is the deliverable); it is surfaced in the final summary.
  • Fatal no-code-change comment: on the no-code-change short-circuit there is no MR — the deterministic comment on the source issue is the sole deliverable, so a comment-post failure aborts the run: error.json attributed to the write-back stage, exit 1, and resumable. Explicit exception to the non-fatal write-back bullet above (ADR-021 §5).

On any hard failure the orchestrator writes error.json to the run directory:

{
  "stage": "setup|issue-fetch|implement|review|remediate|docs|mr|write-back",
  "kind": "config|auth|not-found|rate-limit|transient|network|agent-failure|git|auto-merge-timeout|auto-merge-failed|unknown",
  "message": "string",
  "retriable": false,
  "attempts": 1,
  "occurredAt": "ISO-8601",
}

On success, a final summary (MR URL, remaining findings, degraded stages, the friction aggregate, and the Jira write-back outcome) is printed and appended to run.log.

MR / VCS conventions (AGENTS.md-first)

The MR stage checks your target repo's AGENTS.md for VCS/MR tooling and conventions and follows them if present. Otherwise it defaults per origin platform: glab (GitLab, via GITLAB_TOKEN), gh (GitHub, via GITHUB_TOKEN), or the Forgejo REST API (Forgejo, via FORGEJO_TOKEN); branch feature/<ISSUE-KEY>/<slug> (Jira) or feature/issue-<number>/<slug> (GitHub/Forgejo/GitLab); target = the repo default branch; state = Draft.

Development

For local development you don't need to install the global binary — run the CLI directly from source with tsx:

npm install                     # install deps (also builds dist/ via prepare)
npm start -- <ISSUE-REF>        # run the CLI from source via tsx
npm start -- --help

Common scripts:

Script Does
npm start -- <args> Run the CLI from source (tsx src/cli.ts) — dev workflow
npm run build Clean + compile src/dist/ and mark dist/cli.js executable
npm run typecheck Type-check only (tsc --noEmit), no emit
npm run install:local Build then npm link → install the global pi-loop
npm run lint / npm run format eslint / prettier
npm test Deterministic unit tests

npm run build compiles with tsconfig.build.json (emits dist/, excludes test files); npm pack --dry-run ships only dist/ and README.md (never src/ or .pi-loop/).

Testing

npm test               # deterministic unit tests (normal CI)
PILOOP_E2E=1 npm run e2e   # gated end-to-end test (live creds + LLM required)
  • Unit tests cover every non-LLM seam (Jira client, prompt/skill composer, artifact I/O + schemas, config loader, diff capture, git-exclude, MR-description builder, transition resolver, retry helper, error classifier, final-summary builder, and the orchestrator itself with all stages injected as fakes).
  • The gated E2E runs the full pipeline against a sandbox repo + throwaway ticket and asserts on artifacts/exit conditions — never on exact LLM text.

The full-pipeline E2E needs a provisioned sandbox in addition to the normal config; when these are unset it is skipped (so npm run e2e still passes):

Variable Description
PILOOP_E2E Set to 1 to enable gated E2E specs
PILOOP_E2E_JIRA_KEY A throwaway Jira issue key to implement
PILOOP_E2E_REPO Path to a clean sandbox git repo to run the loop in

Scope (v1)

In: single Jira key → single Draft GitLab MR; five-stage loop; single review + single remediation pass; Jira write-back; multi-issue batch mode (opt-in via --auto-merge with multiple refs); scan mode (--scan).

Out (non-goals): re-review loop / iteration cap, multi-repo, CI trigger, rollback/undo.

Project status

v1 pipeline complete. The codebase was developed milestone by milestone (M0M8) per IMPLEMENTATION_PLAN.md; M8 wires all stages into one autonomous run with the error-handling policy, error.json persistence, and a final summary, validated by deterministic unit tests plus the gated E2E.

Roadmap

Packaging — Phase 1 (done): pi-loop builds to dist/ and installs as a global pi-loop command via a maintainer-only local install (npm run install:local).

Packaging — Phase 2 (planned): publish pi-loop to a private registry (Verdaccio) so it can be installed with a plain npm i -g — covering the package scope/name, publish automation (manual vs CI), and registry .npmrc/auth. Until then, install from source as described in Install.