phoebe-agent 0.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +55 -0
- package/dist/phoebe.config.d.ts +2 -0
- package/dist/phoebe.config.js +43 -0
- package/dist/src/agent-env.d.ts +6 -0
- package/dist/src/agent-env.js +24 -0
- package/dist/src/cli.d.ts +25 -0
- package/dist/src/cli.js +161 -0
- package/dist/src/config-schema.d.ts +163 -0
- package/dist/src/config-schema.js +140 -0
- package/dist/src/execution-gate.d.ts +9 -0
- package/dist/src/execution-gate.js +17 -0
- package/dist/src/git-model.d.ts +30 -0
- package/dist/src/git-model.js +71 -0
- package/dist/src/index.d.ts +2 -0
- package/dist/src/index.js +12 -0
- package/dist/src/init.d.ts +82 -0
- package/dist/src/init.js +207 -0
- package/dist/src/load-config.d.ts +88 -0
- package/dist/src/load-config.js +153 -0
- package/dist/src/main.d.ts +7 -0
- package/dist/src/main.js +1180 -0
- package/dist/src/orchestrator.d.ts +275 -0
- package/dist/src/orchestrator.js +579 -0
- package/dist/src/prompt.d.ts +13 -0
- package/dist/src/prompt.js +55 -0
- package/dist/src/providers/providers.d.ts +3 -0
- package/dist/src/providers/providers.js +210 -0
- package/dist/src/providers/run-agent.d.ts +34 -0
- package/dist/src/providers/run-agent.js +57 -0
- package/dist/src/providers/types.d.ts +27 -0
- package/dist/src/providers/types.js +6 -0
- package/dist/src/resolved-config.d.ts +8 -0
- package/dist/src/resolved-config.js +49 -0
- package/dist/src/supervisor-decision.d.ts +70 -0
- package/dist/src/supervisor-decision.js +94 -0
- package/package.json +53 -0
- package/prompts/checks-prompt.md +53 -0
- package/prompts/conflict-prompt.md +53 -0
- package/prompts/prompt.md +55 -0
- package/prompts/reviews-prompt.md +106 -0
- package/templates/.env.example +21 -0
- package/templates/container/Dockerfile +54 -0
- package/templates/container/compose.daemon.yml +16 -0
- package/templates/container/compose.yml +46 -0
- package/templates/container/supervisor.sh +50 -0
- package/templates/phoebe.config.ts +15 -0
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# Context
|
|
2
|
+
|
|
3
|
+
## Assigned task
|
|
4
|
+
|
|
5
|
+
Phoebe's host detected that **PR #{{PR_NUMBER}}** (`{{PR_BRANCH}}`) conflicts with `{{DEFAULT_BRANCH}}`. Your job is to reconcile this branch with `{{DEFAULT_BRANCH}}`, resolve any conflicts, verify, and push — or abort and leave a PR comment if you cannot fix it cleanly.
|
|
6
|
+
|
|
7
|
+
!`gh pr view {{PR_NUMBER}} --json number,title,body,headRefName,baseRefName,mergeable,mergeStateStatus`
|
|
8
|
+
|
|
9
|
+
# Task
|
|
10
|
+
|
|
11
|
+
You are Phoebe — resolving a **merge conflict** on an existing PR branch in this repository.
|
|
12
|
+
|
|
13
|
+
**Before anything else, read `AGENTS.md` at the repo root, if present.** It is the single source of project guidance — toolchain, conventions, and any compliance requirements — and it overrides your defaults.
|
|
14
|
+
|
|
15
|
+
Your worktree already attempted the merge sequence below. If conflicts remain, resolve them now.
|
|
16
|
+
|
|
17
|
+
## Merge order (stacked follow-ups)
|
|
18
|
+
|
|
19
|
+
When `{{BLOCKER_PR_NUMBERS}}` is non-empty, this PR is a stacked follow-up whose blocker(s) already merged. Reconcile **blocker-first, then base** — never merge `origin/{{DEFAULT_BRANCH}}` alone first:
|
|
20
|
+
|
|
21
|
+
1. For each blocker PR number `N` in `{{BLOCKER_PR_NUMBERS}}` (comma-separated, in order):
|
|
22
|
+
`git fetch origin pull/N/head && git merge FETCH_HEAD`
|
|
23
|
+
2. Then: `git fetch origin {{DEFAULT_BRANCH}} && git merge origin/{{DEFAULT_BRANCH}}`
|
|
24
|
+
|
|
25
|
+
When `{{BLOCKER_PR_NUMBERS}}` is empty, merge `origin/{{DEFAULT_BRANCH}}` only (standard conflict fix).
|
|
26
|
+
|
|
27
|
+
## Workflow
|
|
28
|
+
|
|
29
|
+
1. **Assess** — run `git status`. If no merge is in progress, run the merge order above from scratch.
|
|
30
|
+
2. **Resolve** — fix every conflicted file. Prefer preserving both sides' intent; do not drop unrelated changes from `{{DEFAULT_BRANCH}}` or this branch.
|
|
31
|
+
3. **Verify** — run `{{CHECK_COMMAND}}` and `{{TEST_COMMAND}}` (or `{{READY_COMMAND}}` if the project ships an all-in-one gate). Fix any failures before proceeding.
|
|
32
|
+
4. **Commit** — stage the resolution and commit each merge (e.g. `Phoebe: merge blocker PR #N into {{PR_BRANCH}}`, then `Phoebe: merge {{DEFAULT_BRANCH}} into {{PR_BRANCH}}`). Do **not** force-push.
|
|
33
|
+
5. **Push** — `git push origin {{PR_BRANCH}}`.
|
|
34
|
+
6. **Comment** — only if you **cannot** resolve cleanly or tests still fail after resolving:
|
|
35
|
+
- Run `git merge --abort` (or `git reset --hard` to the pre-merge state if needed).
|
|
36
|
+
- Leave a PR comment explaining what conflicts and that auto-resolution failed:
|
|
37
|
+
```
|
|
38
|
+
gh pr comment {{PR_NUMBER}} --body "<explanation>"
|
|
39
|
+
```
|
|
40
|
+
- Do **not** push a broken merge.
|
|
41
|
+
|
|
42
|
+
## Rules
|
|
43
|
+
|
|
44
|
+
- Work on **this PR only** (#{{PR_NUMBER}}). Do not pick up issues or other PRs.
|
|
45
|
+
- Do not leave commented-out code or TODO comments in committed code.
|
|
46
|
+
- Never force-push (`git push --force`).
|
|
47
|
+
- If you are blocked, abort the merge, comment on the PR, and finish — do not leave the branch in a conflicted state.
|
|
48
|
+
|
|
49
|
+
# Done
|
|
50
|
+
|
|
51
|
+
When the merge is resolved and pushed, or you have aborted and commented, output:
|
|
52
|
+
|
|
53
|
+
<promise>COMPLETE</promise>
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# Context
|
|
2
|
+
|
|
3
|
+
## Assigned issue
|
|
4
|
+
|
|
5
|
+
You are working **exactly** issue **#{{ISSUE_NUMBER}}** — the orchestrator selected it before this run started. Do not pick a different issue.
|
|
6
|
+
|
|
7
|
+
!`gh issue view {{ISSUE_NUMBER}} --json number,title,body,labels,comments --jq '{number, title, body, labels: [.labels[].name], comments: [.comments[].body]}'`
|
|
8
|
+
|
|
9
|
+
## Recent Phoebe commits (last 10)
|
|
10
|
+
|
|
11
|
+
!`git log --oneline --grep="Phoebe" -10`
|
|
12
|
+
|
|
13
|
+
# Task
|
|
14
|
+
|
|
15
|
+
You are Phoebe — an autonomous coding agent working on issue **#{{ISSUE_NUMBER}}** in this repository.
|
|
16
|
+
|
|
17
|
+
**Before anything else, read `AGENTS.md` at the repo root, if present.** It is the single source of project guidance — toolchain, conventions, and any compliance requirements — and it overrides your defaults.
|
|
18
|
+
|
|
19
|
+
## Workflow
|
|
20
|
+
|
|
21
|
+
1. **Claim** — immediately label the issue `{{PROCESSING_LABEL}}` so others know it is in flight:
|
|
22
|
+
```
|
|
23
|
+
gh issue edit {{ISSUE_NUMBER}} --add-label "{{PROCESSING_LABEL}}" --remove-label "{{READY_LABEL}}"
|
|
24
|
+
```
|
|
25
|
+
2. **Explore** — read the issue carefully. Pull in the parent PRD if referenced. Read the relevant source files and tests before writing any code.
|
|
26
|
+
3. **Plan** — decide what to change and why. Keep the change as small as possible.
|
|
27
|
+
4. **Implement** — make the change, treating issue #{{ISSUE_NUMBER}} as the spec. Write or update tests alongside code when a behaviour change warrants coverage. If this repo ships an `implement` (or equivalent) workflow skill under `.claude/skills/`, read and follow it; otherwise apply your own tight edit → test loop.
|
|
28
|
+
5. **Verify** — run the project's ready gate: `{{READY_COMMAND}}`. If the ready gate is not available, fall back to `{{CHECK_COMMAND}}` and `{{TEST_COMMAND}}`. Fix any failures before proceeding.
|
|
29
|
+
6. **Commit** — make a single git commit. The message MUST:
|
|
30
|
+
- Start with the `Phoebe:` prefix
|
|
31
|
+
- Name the task completed and any PRD reference
|
|
32
|
+
- List key decisions made
|
|
33
|
+
- List files changed
|
|
34
|
+
- Note any blockers for the next iteration
|
|
35
|
+
7. **PR** — open a pull request targeting `{{DEFAULT_BRANCH}}`. The body MUST include `Closes #{{ISSUE_NUMBER}}` so the issue closes automatically on merge:
|
|
36
|
+
```
|
|
37
|
+
gh pr create --base {{DEFAULT_BRANCH}} --title "Phoebe: <title>" --body "Closes #{{ISSUE_NUMBER}}\n\n<summary>"
|
|
38
|
+
```
|
|
39
|
+
8. **Address** — remove the processing label and leave a pointer comment:
|
|
40
|
+
```
|
|
41
|
+
gh issue edit {{ISSUE_NUMBER}} --remove-label "{{PROCESSING_LABEL}}" && gh issue comment {{ISSUE_NUMBER}} --body "Addressed by Phoebe: <PR URL>"
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Rules
|
|
45
|
+
|
|
46
|
+
- Work on **this issue only** (#{{ISSUE_NUMBER}}). Do not attempt other issues in this run.
|
|
47
|
+
- Do not open the PR until you have committed the fix and the project's check and test gates pass.
|
|
48
|
+
- Do not leave commented-out code or TODO comments in committed code.
|
|
49
|
+
- If you are blocked (missing context, failing tests you cannot fix, external dependency), leave a comment on the issue and move on — do not close it.
|
|
50
|
+
|
|
51
|
+
# Done
|
|
52
|
+
|
|
53
|
+
When the work for issue #{{ISSUE_NUMBER}} is complete (or you are blocked), output the completion signal:
|
|
54
|
+
|
|
55
|
+
<promise>COMPLETE</promise>
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# Context
|
|
2
|
+
|
|
3
|
+
## Assigned task
|
|
4
|
+
|
|
5
|
+
Phoebe's host detected **new unresolved review-thread feedback** on **PR #{{PR_NUMBER}}** (`{{PR_BRANCH}}`). Your job is to triage every unresolved thread, address the ones that need code changes, and post the summary comment.
|
|
6
|
+
|
|
7
|
+
!`gh pr view {{PR_NUMBER}} --json number,title,body,headRefName,baseRefName`
|
|
8
|
+
|
|
9
|
+
# Task
|
|
10
|
+
|
|
11
|
+
You are Phoebe — handling **PR review feedback** on an existing branch in this repository.
|
|
12
|
+
|
|
13
|
+
**Before anything else, read `AGENTS.md` at the repo root, if present.** It is the single source of project guidance — toolchain, conventions, and any compliance requirements — and it overrides your defaults.
|
|
14
|
+
|
|
15
|
+
## Workflow
|
|
16
|
+
|
|
17
|
+
1. **Fetch unresolved threads** — page through GraphQL `reviewThreads` until `pageInfo.hasNextPage` is `false`, feeding the previous page's `endCursor` back as `$cursor`. **Do not stop after the first page** — a PR with more threads than one page holds would silently lose feedback. Accumulate nodes across pages and then filter to `isResolved === false && isOutdated === false`.
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
OWNER=<OWNER> REPO=<REPO> PR={{PR_NUMBER}}
|
|
21
|
+
CURSOR="" # start unset
|
|
22
|
+
while :; do
|
|
23
|
+
PAGE=$(gh api graphql \
|
|
24
|
+
-F owner="$OWNER" -F repo="$REPO" -F pr="$PR" -F cursor="$CURSOR" \
|
|
25
|
+
-f query='
|
|
26
|
+
query($owner:String!,$repo:String!,$pr:Int!,$cursor:String) {
|
|
27
|
+
repository(owner:$owner,name:$repo) {
|
|
28
|
+
pullRequest(number:$pr) {
|
|
29
|
+
reviewThreads(first:50, after:$cursor) {
|
|
30
|
+
pageInfo { hasNextPage endCursor }
|
|
31
|
+
nodes {
|
|
32
|
+
id isResolved isOutdated
|
|
33
|
+
path line
|
|
34
|
+
comments(first:50) {
|
|
35
|
+
nodes { id databaseId author{login} body createdAt }
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}')
|
|
42
|
+
# append PAGE.data.repository.pullRequest.reviewThreads.nodes to your list
|
|
43
|
+
HAS_NEXT=$(echo "$PAGE" | jq -r '.data.repository.pullRequest.reviewThreads.pageInfo.hasNextPage')
|
|
44
|
+
[ "$HAS_NEXT" = "true" ] || break
|
|
45
|
+
CURSOR=$(echo "$PAGE" | jq -r '.data.repository.pullRequest.reviewThreads.pageInfo.endCursor')
|
|
46
|
+
done
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
If your runtime prefers a single call, pass `-F cursor=""` on the first call and re-invoke with the returned `endCursor` until `hasNextPage` is `false`.
|
|
50
|
+
|
|
51
|
+
2. **Triage each unresolved thread** into one of four buckets. Read the code the thread points at before deciding:
|
|
52
|
+
- **fix** — the feedback is right; apply the reviewer's suggested change.
|
|
53
|
+
- **fix-adjusted** — the feedback identifies a real problem, but a different fix is better; apply your alternative and note the deviation in the inline reply.
|
|
54
|
+
- **challenge** — you disagree; explain why in an inline reply and leave the thread open for the reviewer.
|
|
55
|
+
- **skip** — outdated, already fixed, or not actionable; note it in the summary but leave no reply.
|
|
56
|
+
|
|
57
|
+
3. **Look for holistic fixes** — if several threads point at the same underlying issue, address them together with one change rather than N patchwork edits.
|
|
58
|
+
|
|
59
|
+
4. **Implement** — make the smallest correct changes. Update or add tests alongside code when a behaviour change warrants coverage.
|
|
60
|
+
|
|
61
|
+
5. **Verify** — run `{{CHECK_COMMAND}}` and `{{TEST_COMMAND}}` (or `{{READY_COMMAND}}` if the project ships an all-in-one gate). Fix any failures before pushing. Do not push a red branch.
|
|
62
|
+
|
|
63
|
+
6. **Commit** — one or more commits with the `Phoebe:` prefix. Do **not** force-push.
|
|
64
|
+
|
|
65
|
+
7. **Push** — `git push origin {{PR_BRANCH}}` (non-force).
|
|
66
|
+
|
|
67
|
+
8. **Resolve fixed threads** — for every thread in the **fix** or **fix-adjusted** buckets:
|
|
68
|
+
|
|
69
|
+
```
|
|
70
|
+
gh api graphql -f query='
|
|
71
|
+
mutation($threadId:ID!) {
|
|
72
|
+
resolveReviewThread(input:{threadId:$threadId}) { thread { id isResolved } }
|
|
73
|
+
}' -F threadId=<THREAD_ID>
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
9. **Reply to challenges** — for every **challenge** thread, post an inline reply explaining your reasoning. Reply to the thread's first comment (its `databaseId` from step 1) so the reply threads correctly:
|
|
77
|
+
|
|
78
|
+
```
|
|
79
|
+
gh api repos/<OWNER>/<REPO>/pulls/{{PR_NUMBER}}/comments/<COMMENT_ID>/replies \
|
|
80
|
+
-f body="<explanation>"
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
10. **Post the summary comment** — always post one PR comment, even if zero commits were pushed. Its **first line must be exactly** `{{REVIEWS_SUCCESS_HEADING}}` — the orchestrator matches on that heading to know the run finished cleanly. Follow it with a short breakdown, for example:
|
|
84
|
+
|
|
85
|
+
```
|
|
86
|
+
gh pr comment {{PR_NUMBER}} --body "{{REVIEWS_SUCCESS_HEADING}}
|
|
87
|
+
|
|
88
|
+
**Fixed:** <n> thread(s)
|
|
89
|
+
**Challenged:** <n> thread(s) — see inline replies
|
|
90
|
+
**Skipped:** <n> thread(s) (outdated / already addressed)
|
|
91
|
+
"
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## Rules
|
|
95
|
+
|
|
96
|
+
- Work on **this PR only** (#{{PR_NUMBER}}). Do not pick up issues or other PRs.
|
|
97
|
+
- Do not leave commented-out code or TODO comments in committed code.
|
|
98
|
+
- Never force-push (`git push --force`).
|
|
99
|
+
- Zero commits is a valid success when every thread is challenged, skipped, or already outdated — but you **must** still post the summary comment.
|
|
100
|
+
- If you cannot fetch threads, cannot verify, or otherwise get stuck, post the best summary you can (still with the required heading) and finish.
|
|
101
|
+
|
|
102
|
+
# Done
|
|
103
|
+
|
|
104
|
+
When the summary comment is posted (and any fixes are pushed), output:
|
|
105
|
+
|
|
106
|
+
<promise>COMPLETE</promise>
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Phoebe environment — scaffolded by `phoebe init`. Copy to `.env` and fill in.
|
|
2
|
+
#
|
|
3
|
+
# GH_TOKEN is required; every `gh` call in the engine reads it. The provider
|
|
4
|
+
# key is required only for whichever agent you actually run — set the one that
|
|
5
|
+
# matches your `defaultProvider` (or `PHOEBE_AGENT` at runtime).
|
|
6
|
+
|
|
7
|
+
# --- Required ---
|
|
8
|
+
GH_TOKEN=
|
|
9
|
+
|
|
10
|
+
# --- Provider keys (set the one you use) ---
|
|
11
|
+
ANTHROPIC_API_KEY=
|
|
12
|
+
CURSOR_API_KEY=
|
|
13
|
+
OPENAI_KEY=
|
|
14
|
+
|
|
15
|
+
# --- Runtime toggles (optional) ---
|
|
16
|
+
# PHOEBE_AGENT=claude
|
|
17
|
+
# PHOEBE_MODEL=claude-sonnet-4-6
|
|
18
|
+
# PHOEBE_POLL_INTERVAL_MS=300000
|
|
19
|
+
|
|
20
|
+
# --- Engine version pin (compose.yml reads this) ---
|
|
21
|
+
PHOEBE_VERSION=latest
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# Phoebe runtime image — scaffolded by `phoebe init`.
|
|
2
|
+
#
|
|
3
|
+
# Consumer-owned: commit this file, tweak it as you need. Phoebe treats the
|
|
4
|
+
# resulting image as both orchestrator and execution environment. The engine
|
|
5
|
+
# itself is installed from npm (`{{CLI_BIN}}`) — the source is not vendored.
|
|
6
|
+
#
|
|
7
|
+
# What ships in this image:
|
|
8
|
+
# * Node.js (LTS) + git + GitHub CLI (`gh`) — the runtime the engine drives.
|
|
9
|
+
# * A globally installed `{{CLI_BIN}}` pinned by the compose file.
|
|
10
|
+
# * A `supervisor.sh` that keeps the engine restarted and self-updating.
|
|
11
|
+
#
|
|
12
|
+
# What lives on named volumes (see compose.yml):
|
|
13
|
+
# * /data/repo — Phoebe's private clone of the target repo.
|
|
14
|
+
# * /data/worktrees — Per-work-unit git worktrees.
|
|
15
|
+
# * /data/state — Lock, watermarks, logs.
|
|
16
|
+
|
|
17
|
+
FROM node:22-bookworm-slim
|
|
18
|
+
|
|
19
|
+
# gh CLI + git + tini for signal handling; keep the layer small.
|
|
20
|
+
RUN apt-get update \
|
|
21
|
+
&& apt-get install -y --no-install-recommends \
|
|
22
|
+
ca-certificates curl git gnupg tini \
|
|
23
|
+
&& install -m 0755 -d /etc/apt/keyrings \
|
|
24
|
+
&& curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg \
|
|
25
|
+
| gpg --dearmor -o /etc/apt/keyrings/githubcli-archive-keyring.gpg \
|
|
26
|
+
&& chmod go+r /etc/apt/keyrings/githubcli-archive-keyring.gpg \
|
|
27
|
+
&& echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" \
|
|
28
|
+
> /etc/apt/sources.list.d/github-cli.list \
|
|
29
|
+
&& apt-get update \
|
|
30
|
+
&& apt-get install -y --no-install-recommends gh \
|
|
31
|
+
&& rm -rf /var/lib/apt/lists/*
|
|
32
|
+
|
|
33
|
+
# PHOEBE_VERSION is set by the compose file so consumers can pin the engine
|
|
34
|
+
# without editing the Dockerfile. `latest` is only a build-time default; the
|
|
35
|
+
# compose files should always pass an explicit version.
|
|
36
|
+
ARG PHOEBE_VERSION=latest
|
|
37
|
+
RUN npm install -g {{CLI_BIN}}@${PHOEBE_VERSION}
|
|
38
|
+
|
|
39
|
+
# Consumer install command, kept as an ARG so it lands in the image metadata
|
|
40
|
+
# and can be inspected with `docker image inspect`. The engine re-runs the
|
|
41
|
+
# command inside each worktree; this is just documentation for operators.
|
|
42
|
+
ARG PHOEBE_INSTALL_COMMAND="{{INSTALL_COMMAND}}"
|
|
43
|
+
LABEL phoebe.install-command="${PHOEBE_INSTALL_COMMAND}"
|
|
44
|
+
|
|
45
|
+
COPY supervisor.sh /usr/local/bin/phoebe-supervisor
|
|
46
|
+
RUN chmod +x /usr/local/bin/phoebe-supervisor
|
|
47
|
+
|
|
48
|
+
# Compose's `command:` fully replaces `CMD` (it does not append to
|
|
49
|
+
# `ENTRYPOINT`), so the supervisor path must live in `ENTRYPOINT` — otherwise
|
|
50
|
+
# compose.yml's `command: ["--run-once"]` (or the daemon overlay's `command: []`)
|
|
51
|
+
# strips the supervisor away and tini exits with no child to exec. Keeping
|
|
52
|
+
# `CMD []` here means the compose files only ever contribute engine flags.
|
|
53
|
+
ENTRYPOINT ["/usr/bin/tini", "--", "/usr/local/bin/phoebe-supervisor"]
|
|
54
|
+
CMD []
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Persistent-daemon overlay — scaffolded by `phoebe init`, consumer-owned.
|
|
2
|
+
#
|
|
3
|
+
# Layer on top of compose.yml to run Phoebe as a long-lived poller:
|
|
4
|
+
#
|
|
5
|
+
# docker compose -f compose.yml -f compose.daemon.yml up -d
|
|
6
|
+
#
|
|
7
|
+
# Drops the `--run-once` flag so the engine enters its polling loop, and
|
|
8
|
+
# adds a restart policy so the container comes back after a crash or host
|
|
9
|
+
# reboot. The engine's own self-update path uses `SELF_UPDATE_EXIT_CODE`
|
|
10
|
+
# (75), which the supervisor handles inside the container without needing
|
|
11
|
+
# a restart.
|
|
12
|
+
|
|
13
|
+
services:
|
|
14
|
+
phoebe:
|
|
15
|
+
command: []
|
|
16
|
+
restart: unless-stopped
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# Phoebe compose file — scaffolded by `phoebe init`, consumer-owned.
|
|
2
|
+
#
|
|
3
|
+
# Base configuration: builds the runtime image, mounts the three named volumes
|
|
4
|
+
# the engine expects (`/data/repo`, `/data/worktrees`, `/data/state`), and
|
|
5
|
+
# runs the CLI once with `--run-once`. For the persistent daemon topology,
|
|
6
|
+
# stack the daemon overlay:
|
|
7
|
+
#
|
|
8
|
+
# docker compose -f compose.yml -f compose.daemon.yml up -d
|
|
9
|
+
#
|
|
10
|
+
# The image ships a pinned {{CLI_BIN}} — bump PHOEBE_VERSION here (not in the
|
|
11
|
+
# Dockerfile) to upgrade the engine.
|
|
12
|
+
|
|
13
|
+
services:
|
|
14
|
+
phoebe:
|
|
15
|
+
build:
|
|
16
|
+
context: .
|
|
17
|
+
args:
|
|
18
|
+
PHOEBE_VERSION: "${PHOEBE_VERSION:-latest}"
|
|
19
|
+
PHOEBE_INSTALL_COMMAND: "{{INSTALL_COMMAND}}"
|
|
20
|
+
image: phoebe-runtime:${PHOEBE_VERSION:-latest}
|
|
21
|
+
command: ["--run-once"]
|
|
22
|
+
environment:
|
|
23
|
+
PHOEBE_VERSION: "${PHOEBE_VERSION:-latest}"
|
|
24
|
+
# Secrets — populate via `.env` (see `.env.example`).
|
|
25
|
+
GH_TOKEN: "${GH_TOKEN:?GH_TOKEN is required}"
|
|
26
|
+
ANTHROPIC_API_KEY: "${ANTHROPIC_API_KEY:-}"
|
|
27
|
+
CURSOR_API_KEY: "${CURSOR_API_KEY:-}"
|
|
28
|
+
OPENAI_KEY: "${OPENAI_KEY:-}"
|
|
29
|
+
# Optional runtime toggles.
|
|
30
|
+
PHOEBE_AGENT: "${PHOEBE_AGENT:-}"
|
|
31
|
+
PHOEBE_MODEL: "${PHOEBE_MODEL:-}"
|
|
32
|
+
PHOEBE_POLL_INTERVAL_MS: "${PHOEBE_POLL_INTERVAL_MS:-}"
|
|
33
|
+
volumes:
|
|
34
|
+
- phoebe-repo:/data/repo
|
|
35
|
+
- phoebe-worktrees:/data/worktrees
|
|
36
|
+
- phoebe-state:/data/state
|
|
37
|
+
# Consumer config + prompt overrides live in-repo; mount them read-only
|
|
38
|
+
# so a `docker compose restart` picks up edits without a rebuild.
|
|
39
|
+
- ../phoebe.config.ts:/etc/phoebe/phoebe.config.ts:ro
|
|
40
|
+
- ../prompts:/etc/phoebe/prompts:ro
|
|
41
|
+
working_dir: /etc/phoebe
|
|
42
|
+
|
|
43
|
+
volumes:
|
|
44
|
+
phoebe-repo:
|
|
45
|
+
phoebe-worktrees:
|
|
46
|
+
phoebe-state:
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Phoebe supervisor — scaffolded by `phoebe init`, consumer-owned.
|
|
3
|
+
#
|
|
4
|
+
# Keeps the engine (`{{CLI_BIN}}`) restarted when it exits deliberately for a
|
|
5
|
+
# self-update, and re-runs the consumer's install command (`{{INSTALL_COMMAND}}`)
|
|
6
|
+
# after every self-update so the private clone stays warm. Exits 0 only when
|
|
7
|
+
# the engine returns 0 in one-shot mode.
|
|
8
|
+
|
|
9
|
+
set -euo pipefail
|
|
10
|
+
|
|
11
|
+
: "${PHOEBE_REPO_DIR:=/data/repo}"
|
|
12
|
+
: "${PHOEBE_STATE_DIR:=/data/state}"
|
|
13
|
+
|
|
14
|
+
# Exit code the engine uses to request a supervisor re-exec after a self-update
|
|
15
|
+
# (kept in sync with SELF_UPDATE_EXIT_CODE in src/supervisor-decision.ts).
|
|
16
|
+
SELF_UPDATE_EXIT_CODE=75
|
|
17
|
+
|
|
18
|
+
mkdir -p "${PHOEBE_REPO_DIR}" "${PHOEBE_STATE_DIR}"
|
|
19
|
+
|
|
20
|
+
warm_install() {
|
|
21
|
+
# Best-effort: on first boot the clone doesn't exist yet, and the engine will
|
|
22
|
+
# populate it on its first tick. Skip silently when there's nothing to install.
|
|
23
|
+
if [ -f "${PHOEBE_REPO_DIR}/package.json" ]; then
|
|
24
|
+
( cd "${PHOEBE_REPO_DIR}" && {{INSTALL_COMMAND}} ) || true
|
|
25
|
+
fi
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
while true; do
|
|
29
|
+
warm_install
|
|
30
|
+
set +e
|
|
31
|
+
{{CLI_BIN}} "$@"
|
|
32
|
+
status=$?
|
|
33
|
+
set -e
|
|
34
|
+
|
|
35
|
+
if [ "${status}" -eq "${SELF_UPDATE_EXIT_CODE}" ]; then
|
|
36
|
+
echo "[phoebe-supervisor] Engine exited ${SELF_UPDATE_EXIT_CODE} — reinstalling and re-execing."
|
|
37
|
+
npm install -g "{{CLI_BIN}}@${PHOEBE_VERSION:-latest}" || true
|
|
38
|
+
continue
|
|
39
|
+
fi
|
|
40
|
+
|
|
41
|
+
# Non-self-update exit — one-shot runs propagate the exit code, persistent
|
|
42
|
+
# runs treat any exit as unexpected and get restarted after a short backoff.
|
|
43
|
+
case " $* " in
|
|
44
|
+
*" --run-once "*|*" --dry-run "*)
|
|
45
|
+
exit "${status}"
|
|
46
|
+
;;
|
|
47
|
+
esac
|
|
48
|
+
echo "[phoebe-supervisor] Engine exited ${status} — restarting in 10s."
|
|
49
|
+
sleep 10
|
|
50
|
+
done
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// Phoebe consumer config — scaffolded by `phoebe init`.
|
|
2
|
+
//
|
|
3
|
+
// Only these five fields are required. Everything else has a shipped default
|
|
4
|
+
// (see the `PhoebeUserConfig` type). Add overrides here only when you need
|
|
5
|
+
// them; `phoebe-agent` upgrades pick up new defaults automatically.
|
|
6
|
+
|
|
7
|
+
import { defineConfig } from "{{CLI_BIN}}";
|
|
8
|
+
|
|
9
|
+
export default defineConfig({
|
|
10
|
+
repoSlug: "your-org/your-repo",
|
|
11
|
+
repoUrl: "https://github.com/your-org/your-repo.git",
|
|
12
|
+
installCommand: "{{INSTALL_COMMAND}}",
|
|
13
|
+
checkCommand: "npm run check",
|
|
14
|
+
testCommand: "npm test",
|
|
15
|
+
});
|