Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/agent-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ jobs:
- name: Install pnpm
run: npm install -g pnpm

- name: Setup Bun
uses: oven-sh/setup-bun@v2

- name: Install dependencies
run: pnpm install --frozen-lockfile

Expand Down
134 changes: 134 additions & 0 deletions .github/workflows/array-stack-check.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
# Generated by Array CLI - https://github.com/posthog/array
# Blocks stacked PRs until their downstack dependencies are merged
# Only runs for PRs managed by Array (detected via stack comment marker)

name: Stack Check

on:
pull_request:
types: [opened, synchronize, reopened, edited]
pull_request_target:
types: [closed]

permissions:
contents: read

jobs:
check:
runs-on: ubuntu-latest
if: github.event_name == 'pull_request'
permissions:
pull-requests: read
issues: read
steps:
- name: Check stack dependencies
uses: actions/github-script@v7
with:
script: |
const pr = context.payload.pull_request;

// Check if this is an Array-managed PR by looking for stack comment
const { data: comments } = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pr.number
});

const isArrayPR = comments.some(c =>
c.body.includes('<!-- array-stack-comment -->')
);

if (!isArrayPR) {
console.log('Not an Array PR, skipping');
return;
}

const baseBranch = pr.base.ref;
const trunk = ['main', 'master', 'develop'];

if (trunk.includes(baseBranch)) {
console.log('Base is trunk, no dependencies');
return;
}

async function getBlockers(base, visited = new Set()) {
if (trunk.includes(base) || visited.has(base)) {
return [];
}
visited.add(base);

const { data: prs } = await github.rest.pulls.list({
owner: context.repo.owner,
repo: context.repo.repo,
state: 'open',
head: `${context.repo.owner}:${base}`
});

if (prs.length === 0) {
return [];
}

const blocker = prs[0];
const upstream = await getBlockers(blocker.base.ref, visited);
return [{ number: blocker.number, title: blocker.title }, ...upstream];
}

const blockers = await getBlockers(baseBranch);

if (blockers.length > 0) {
const list = blockers.map(b => `#${b.number} (${b.title})`).join('\n - ');
core.setFailed(`Blocked by:\n - ${list}\n\nMerge these PRs first (bottom to top).`);
} else {
console.log('All dependencies merged, ready to merge');
}

recheck-dependents:
runs-on: ubuntu-latest
if: >-
github.event_name == 'pull_request_target' &&
github.event.action == 'closed' &&
github.event.pull_request.merged == true
permissions:
pull-requests: write
issues: read
steps:
- name: Trigger recheck of dependent PRs
uses: actions/github-script@v7
with:
script: |
const pr = context.payload.pull_request;

// Check if this is an Array-managed PR
const { data: comments } = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pr.number
});

const isArrayPR = comments.some(c =>
c.body.includes('<!-- array-stack-comment -->')
);

if (!isArrayPR) {
console.log('Not an Array PR, skipping');
return;
}

const mergedBranch = pr.head.ref;

const { data: dependentPRs } = await github.rest.pulls.list({
owner: context.repo.owner,
repo: context.repo.repo,
base: mergedBranch,
state: 'open'
});

for (const dependentPR of dependentPRs) {
console.log(`Re-checking PR #${dependentPR.number}`);
await github.rest.pulls.update({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: dependentPR.number,
base: 'main'
});
}
17 changes: 6 additions & 11 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,9 @@ name: Build

on:
pull_request:

concurrency:
group: build-${{ github.head_ref || github.ref }}
cancel-in-progress: true
push:
branches:
- main

jobs:
build:
Expand All @@ -17,24 +16,20 @@ jobs:
uses: actions/checkout@v5
with:
persist-credentials: false

- name: Setup pnpm
uses: pnpm/action-setup@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 22
cache: "pnpm"

cache: 'pnpm'
- name: Setup Bun
uses: oven-sh/setup-bun@v2
- name: Install dependencies
run: pnpm install --frozen-lockfile

- name: Build electron-trpc
run: pnpm --filter @posthog/electron-trpc build

- name: Build agent
run: pnpm --filter agent build

- name: Build array
run: pnpm --filter array build
3 changes: 3 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ jobs:
node-version: 22
cache: "pnpm"

- name: Setup Bun
uses: oven-sh/setup-bun@v2

- name: Compute version from git tags
id: version
run: |
Expand Down
10 changes: 2 additions & 8 deletions .github/workflows/typecheck.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,6 @@ name: Typecheck
on:
pull_request:

concurrency:
group: typecheck-${{ github.head_ref || github.ref }}
cancel-in-progress: true

jobs:
typecheck:
runs-on: ubuntu-latest
Expand All @@ -17,18 +13,16 @@ jobs:
uses: actions/checkout@v5
with:
persist-credentials: false

- name: Setup pnpm
uses: pnpm/action-setup@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 22
cache: "pnpm"

- name: Setup Bun
uses: oven-sh/setup-bun@v2
- name: Install dependencies
run: pnpm install --frozen-lockfile

- name: Run type check
run: pnpm run typecheck
21 changes: 17 additions & 4 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,12 @@

### Avoid Barrel Files

- Do not make use of index.ts

Barrel files:

- Break tree-shaking
- Create circular dependency risks
- Create circular dependency risks
- Hide the true source of imports
- Make refactoring harder

Expand All @@ -74,6 +77,17 @@ See [ARCHITECTURE.md](./ARCHITECTURE.md) for detailed patterns (DI, services, tR
- PostHog API integration in `posthog-api.ts`
- Task execution and session management

### CLI Package (packages/cli)

- **Dumb shell, imperative core**: CLI commands should be thin wrappers that call `@array/core`
- All business logic belongs in `@array/core`, not in CLI command files
- CLI only handles: argument parsing, calling core, formatting output
- No data transformation, tree building, or complex logic in CLI

### Core Package (packages/core)

- Shared business logic for jj/GitHub operations

## Key Libraries

- React 18, Radix UI Themes, Tailwind CSS
Expand All @@ -91,6 +105,5 @@ TODO: Update me

## Testing

- Tests use vitest with jsdom environment
- Test helpers in `src/test/`
- Run specific test: `pnpm --filter array test -- path/to/test`
- `pnpm test` - Run tests across all packages
- Array app: Vitest with jsdom, helpers in `apps/array/src/test/`
117 changes: 117 additions & 0 deletions apps/cli/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
> [!IMPORTANT] > `arr` is still in development and not production-ready. Interested? Email [email protected]

# arr

arr is CLI for stacked PR management using Jujutsu (`jj`).

Split your work into small changes, push them as a PR stack, and keep everything in sync.

## Features

- Stacked PRs synced to GitHub
- Simpler interface compared to `jj` for managing your work.
- Visual stack log with PR status.
- Comes with a GitHub Action to enforce merge order
- Unknown commands pass through to `jj`

## Why

Stacked PRs keep reviews small and manageable. Managing them with `git` is painful, this involves rebasing, force-pushing, updating PR bases and describing the PR stack via comments.

`arr` makes it easy to create, manage and submit (stacked) PRs by using `jj` under the hood.

## Install

Requires [Bun](https://bun.sh).

```
git clone https://github.com/posthog/array
cd array
pnpm install
pnpm --filter @array/core build
```

Then install the `arr` command (symlinked to `~/bin/arr`):

```
./apps/cli/arr.sh install
```

## Usage

```
arr init # set up arr in a git repo
arr create "message" # new change on stack
arr submit # push stack, create PRs
arr merge # merge PR via GitHub
arr sync # fetch, rebase, cleanup merged
arr up / arr down # navigate stack
arr log # show stack
arr exit # back to git
arr help --all # show all commands
```

## Example

```
$ echo "user model" >> user_model.ts
$ arr create "Add user model"
✓ Created add-user-model-qtrsqm

$ echo "user api" >> user_api.ts
$ arr create "Add user API"
✓ Created add-user-api-nnmzrt

$ arr log
◉ (working copy)
│ Empty
○ 12-23-add-user-api nnmzrtzz (+1, 1 file)
│ Not submitted
○ 12-23-add-user-model qtrsqmmy (+1, 1 file)
│ Not submitted
○ main

$ arr submit
Created PR #8: 12-23-add-user-model
https://github.com/username/your-repo/pull/8
Created PR #9: 12-23-add-user-api
https://github.com/username/your-repo/pull/9

$ arr merge
...

$ arr sync
```

Each change becomes a PR. PRs are stacked so reviewers see the dependency.

## CI

```
arr ci
```

Adds a GitHub Action that blocks merging a PR if its parent PR hasn't merged yet, which helps keep your stack in order.

## FAQ

**Can I use this with an existing `git` repo?**

Yes, do so by using `arr init` in any `git` repo. `jj` works alongside `git`.

**Do my teammates need to use `arr` or `jj`?**

No, your PRs are normal GitHub PRs. Teammates review and merge them as usual. `jj` has full support for `git`.

**What if I want to stop using `arr`?**

Run `arr exit` to switch back to `git`. Your repo, branches, and PRs stay exactly as they are.

**How is `arr` related to Array?**

`arr` is the CLI component of Array, an agentic development environment.

## Learn more

- [`jj` documentation](https://jj-vcs.github.io/jj/latest/) - full `jj` reference
- [`jj` tutorial](https://jj-vcs.github.io/jj/latest/tutorial/) - getting started with `jj`
Loading
Loading