phoebe-agent 0.0.0 → 0.1.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/README.md +77 -8
- package/bootstrap/bin.mjs +42 -0
- package/bootstrap/boot.ts +383 -0
- package/bootstrap/cli.ts +29 -0
- package/bootstrap/crash-loop.ts +391 -0
- package/bootstrap/define-config.ts +20 -0
- package/bootstrap/engine-source.ts +83 -0
- package/bootstrap/github-engine.ts +169 -0
- package/bootstrap/index.mjs +9 -0
- package/bootstrap/index.ts +24 -0
- package/bootstrap/materialize.mjs +53 -0
- package/bootstrap/reconcile.ts +314 -0
- package/bootstrap/spawn-engine.mjs +78 -0
- package/package.json +26 -24
- package/prompts/checks-prompt.md +21 -6
- package/prompts/conflict-prompt.md +12 -1
- package/prompts/research-prompt.md +61 -0
- package/src/agent-env.ts +32 -0
- package/src/branded.ts +19 -0
- package/src/cli.ts +187 -0
- package/src/config-schema.ts +278 -0
- package/src/drain.ts +74 -0
- package/src/execution-gate.ts +27 -0
- package/src/git-model.ts +113 -0
- package/src/init.ts +277 -0
- package/src/load-config.ts +168 -0
- package/src/main.ts +1636 -0
- package/src/orchestrator.ts +953 -0
- package/src/prompt.ts +98 -0
- package/src/providers/providers.ts +222 -0
- package/src/providers/run-agent.ts +90 -0
- package/src/providers/types.ts +26 -0
- package/src/resolved-config.ts +55 -0
- package/templates/.env.example +11 -5
- package/templates/container/Dockerfile +41 -19
- package/templates/container/compose.local.yml +37 -0
- package/templates/container/compose.yml +34 -13
- package/templates/phoebe.config.ts +30 -6
- package/dist/phoebe.config.d.ts +0 -2
- package/dist/phoebe.config.js +0 -43
- package/dist/src/agent-env.d.ts +0 -6
- package/dist/src/agent-env.js +0 -24
- package/dist/src/cli.d.ts +0 -25
- package/dist/src/cli.js +0 -161
- package/dist/src/config-schema.d.ts +0 -163
- package/dist/src/config-schema.js +0 -140
- package/dist/src/execution-gate.d.ts +0 -9
- package/dist/src/execution-gate.js +0 -17
- package/dist/src/git-model.d.ts +0 -30
- package/dist/src/git-model.js +0 -71
- package/dist/src/index.d.ts +0 -2
- package/dist/src/index.js +0 -12
- package/dist/src/init.d.ts +0 -82
- package/dist/src/init.js +0 -207
- package/dist/src/load-config.d.ts +0 -88
- package/dist/src/load-config.js +0 -153
- package/dist/src/main.d.ts +0 -7
- package/dist/src/main.js +0 -1180
- package/dist/src/orchestrator.d.ts +0 -275
- package/dist/src/orchestrator.js +0 -579
- package/dist/src/prompt.d.ts +0 -13
- package/dist/src/prompt.js +0 -55
- package/dist/src/providers/providers.d.ts +0 -3
- package/dist/src/providers/providers.js +0 -210
- package/dist/src/providers/run-agent.d.ts +0 -34
- package/dist/src/providers/run-agent.js +0 -57
- package/dist/src/providers/types.d.ts +0 -27
- package/dist/src/providers/types.js +0 -6
- package/dist/src/resolved-config.d.ts +0 -8
- package/dist/src/resolved-config.js +0 -49
- package/dist/src/supervisor-decision.d.ts +0 -70
- package/dist/src/supervisor-decision.js +0 -94
- package/templates/container/compose.daemon.yml +0 -16
- package/templates/container/supervisor.sh +0 -50
- /package/prompts/{prompt.md → issues-prompt.md} +0 -0
|
@@ -1,41 +1,61 @@
|
|
|
1
1
|
# Phoebe compose file — scaffolded by `phoebe init`, consumer-owned.
|
|
2
2
|
#
|
|
3
|
-
#
|
|
4
|
-
# the engine
|
|
5
|
-
# runs
|
|
6
|
-
#
|
|
3
|
+
# One long-lived container. Its main process is `phoebe boot` (pinned into the
|
|
4
|
+
# image's ENTRYPOINT), which materializes the engine named by `engine` in
|
|
5
|
+
# phoebe.config.ts, runs it, and keeps supervising it for the life of the
|
|
6
|
+
# container — so this file describes a daemon, not a one-shot:
|
|
7
7
|
#
|
|
8
|
-
# docker compose
|
|
8
|
+
# docker compose up -d # run it
|
|
9
|
+
# docker compose logs -f # watch it
|
|
10
|
+
# docker compose stop # SIGTERM → the engine drains, finishing its
|
|
11
|
+
# # current work unit and starting no new one
|
|
9
12
|
#
|
|
10
|
-
#
|
|
11
|
-
#
|
|
13
|
+
# For a scoped, non-daemon invocation, pass engine flags — the image's `CMD []`
|
|
14
|
+
# means anything here is forwarded straight through `boot` to the engine:
|
|
15
|
+
#
|
|
16
|
+
# docker compose run --rm phoebe --dry-run --run-once # selection preview
|
|
17
|
+
# docker compose run --rm phoebe --run-once # work exactly one unit
|
|
18
|
+
#
|
|
19
|
+
# To upgrade the engine, edit `engine.ref` in phoebe.config.ts — `boot` notices
|
|
20
|
+
# within a poll interval and relaunches at the next work-unit boundary. No
|
|
21
|
+
# rebuild and no restart; rebuild only when this image's toolchain changes.
|
|
12
22
|
|
|
13
23
|
services:
|
|
14
24
|
phoebe:
|
|
15
25
|
build:
|
|
16
26
|
context: .
|
|
17
27
|
args:
|
|
18
|
-
PHOEBE_VERSION: "${PHOEBE_VERSION:-latest}"
|
|
19
28
|
PHOEBE_INSTALL_COMMAND: "{{INSTALL_COMMAND}}"
|
|
20
|
-
image: phoebe-runtime
|
|
21
|
-
|
|
29
|
+
image: phoebe-runtime:latest
|
|
30
|
+
restart: unless-stopped
|
|
22
31
|
environment:
|
|
23
|
-
|
|
24
|
-
#
|
|
32
|
+
# Secrets — populate via `.env` (see `.env.example`). GH_TOKEN also
|
|
33
|
+
# authenticates the engine checkout when `engine.source` is "github".
|
|
25
34
|
GH_TOKEN: "${GH_TOKEN:?GH_TOKEN is required}"
|
|
26
35
|
ANTHROPIC_API_KEY: "${ANTHROPIC_API_KEY:-}"
|
|
27
36
|
CURSOR_API_KEY: "${CURSOR_API_KEY:-}"
|
|
28
37
|
OPENAI_KEY: "${OPENAI_KEY:-}"
|
|
38
|
+
# Where `boot` keeps engine checkouts. Pointed at a named volume below so
|
|
39
|
+
# a restart fetches into an existing clone instead of cloning again.
|
|
40
|
+
PHOEBE_ENGINE_DIR: /data/engine
|
|
29
41
|
# Optional runtime toggles.
|
|
30
42
|
PHOEBE_AGENT: "${PHOEBE_AGENT:-}"
|
|
31
43
|
PHOEBE_MODEL: "${PHOEBE_MODEL:-}"
|
|
32
44
|
PHOEBE_POLL_INTERVAL_MS: "${PHOEBE_POLL_INTERVAL_MS:-}"
|
|
45
|
+
PHOEBE_RECONCILE_INTERVAL_MS: "${PHOEBE_RECONCILE_INTERVAL_MS:-}"
|
|
33
46
|
volumes:
|
|
34
47
|
- phoebe-repo:/data/repo
|
|
35
48
|
- phoebe-worktrees:/data/worktrees
|
|
36
49
|
- phoebe-state:/data/state
|
|
50
|
+
- phoebe-engine:/data/engine
|
|
37
51
|
# Consumer config + prompt overrides live in-repo; mount them read-only
|
|
38
|
-
# so
|
|
52
|
+
# so `boot` can re-read the config without a rebuild.
|
|
53
|
+
#
|
|
54
|
+
# Caveat worth knowing: a single-file bind mount pins the host *inode*, so
|
|
55
|
+
# a save that writes a new file and renames it over the old one — most
|
|
56
|
+
# editors' atomic save, and what `git pull` does — is invisible in here,
|
|
57
|
+
# and `boot`'s config watch will never fire for it. Either edit in place,
|
|
58
|
+
# or follow the write with `docker compose up -d --force-recreate`.
|
|
39
59
|
- ../phoebe.config.ts:/etc/phoebe/phoebe.config.ts:ro
|
|
40
60
|
- ../prompts:/etc/phoebe/prompts:ro
|
|
41
61
|
working_dir: /etc/phoebe
|
|
@@ -44,3 +64,4 @@ volumes:
|
|
|
44
64
|
phoebe-repo:
|
|
45
65
|
phoebe-worktrees:
|
|
46
66
|
phoebe-state:
|
|
67
|
+
phoebe-engine:
|
|
@@ -1,15 +1,39 @@
|
|
|
1
1
|
// Phoebe consumer config — scaffolded by `phoebe init`.
|
|
2
2
|
//
|
|
3
|
-
// Only
|
|
4
|
-
// (see the `PhoebeUserConfig` type)
|
|
5
|
-
// them
|
|
3
|
+
// Only the five fields at the top are required. Everything else has a shipped
|
|
4
|
+
// default (see the `PhoebeUserConfig` type); add overrides here only when you
|
|
5
|
+
// need them, and engine upgrades pick up new defaults automatically.
|
|
6
|
+
//
|
|
7
|
+
// This is a *type-only* import on purpose. The container mounts this file at
|
|
8
|
+
// /etc/phoebe and `phoebe boot` imports it before the engine exists, from a
|
|
9
|
+
// directory with no reachable `node_modules` — a value import of `{{CLI_BIN}}`
|
|
10
|
+
// (the `defineConfig` helper) could not resolve there under ESM. A type-only
|
|
11
|
+
// import is erased at runtime, so it type-checks in your editor and costs
|
|
12
|
+
// nothing in the container.
|
|
6
13
|
|
|
7
|
-
import {
|
|
14
|
+
import type { PhoebeUserConfig } from "{{CLI_BIN}}";
|
|
8
15
|
|
|
9
|
-
|
|
16
|
+
const config: PhoebeUserConfig = {
|
|
10
17
|
repoSlug: "your-org/your-repo",
|
|
11
18
|
repoUrl: "https://github.com/your-org/your-repo.git",
|
|
12
19
|
installCommand: "{{INSTALL_COMMAND}}",
|
|
13
20
|
checkCommand: "npm run check",
|
|
14
21
|
testCommand: "npm test",
|
|
15
|
-
|
|
22
|
+
|
|
23
|
+
// Which engine `phoebe boot` runs. This is the upgrade knob: edit it and the
|
|
24
|
+
// running container drains the engine and relaunches on the new code at the
|
|
25
|
+
// next work-unit boundary — no rebuild, no restart.
|
|
26
|
+
//
|
|
27
|
+
// ref: "main" follow the tip — every push lands here. Guarded: a
|
|
28
|
+
// commit that will not boot is quarantined after a few
|
|
29
|
+
// fast crashes and `boot` pins back to the last one that
|
|
30
|
+
// ran healthily, until the branch moves past it.
|
|
31
|
+
// ref: "v1.2.3" pin a tag (or a full SHA). Exactly this commit, always.
|
|
32
|
+
// No fallback — pinning means pinning.
|
|
33
|
+
//
|
|
34
|
+
// `repo` defaults to the upstream engine repo; set it to run a fork.
|
|
35
|
+
// For a checkout on your own machine, see container/compose.local.yml.
|
|
36
|
+
engine: { source: "github", ref: "main" },
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export default config;
|
package/dist/phoebe.config.d.ts
DELETED
package/dist/phoebe.config.js
DELETED
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
// All repo-specific configuration for the Phoebe engine lives here — repo slug,
|
|
2
|
-
// labels, branch prefix, toolchain commands, prompts, work order, provider keys.
|
|
3
|
-
// Engine code under src/ never mentions any concrete repository (enforced by
|
|
4
|
-
// src/config-seam.test.ts), so pointing Phoebe at a repo is a matter of editing
|
|
5
|
-
// this one file. These are the engine's shipped defaults; consumers replace the
|
|
6
|
-
// repo/toolchain values with their own.
|
|
7
|
-
export const config = {
|
|
8
|
-
repoSlug: "your-org/your-repo",
|
|
9
|
-
repoUrl: "https://github.com/your-org/your-repo.git",
|
|
10
|
-
defaultBranch: "main",
|
|
11
|
-
branchPrefix: "phoebe/",
|
|
12
|
-
readyLabel: "ready-for-agent",
|
|
13
|
-
prScope: "phoebe",
|
|
14
|
-
draftPrs: "skip-non-phoebe",
|
|
15
|
-
prOptOutLabel: "ready-for-human",
|
|
16
|
-
installCommand: "npm ci",
|
|
17
|
-
checkCommand: "npm run check",
|
|
18
|
-
testCommand: "npm test",
|
|
19
|
-
promptFiles: {
|
|
20
|
-
issue: "prompts/prompt.md",
|
|
21
|
-
conflict: "prompts/conflict-prompt.md",
|
|
22
|
-
checks: "prompts/checks-prompt.md",
|
|
23
|
-
reviews: "prompts/reviews-prompt.md",
|
|
24
|
-
},
|
|
25
|
-
workOrder: ["conflicts", "checks", "reviews", "issues"],
|
|
26
|
-
defaultProvider: "cursor",
|
|
27
|
-
defaultModels: {
|
|
28
|
-
cursor: "composer-2.5",
|
|
29
|
-
claude: "claude-sonnet-4-6",
|
|
30
|
-
codex: "gpt-5.4-mini",
|
|
31
|
-
},
|
|
32
|
-
providerEnv: {
|
|
33
|
-
cursor: "CURSOR_API_KEY",
|
|
34
|
-
claude: "ANTHROPIC_API_KEY",
|
|
35
|
-
codex: "OPENAI_KEY",
|
|
36
|
-
},
|
|
37
|
-
selfUpdatePaths: ["package.json", "package-lock.json"],
|
|
38
|
-
paths: {
|
|
39
|
-
repoDir: "/data/repo",
|
|
40
|
-
worktreesDir: "/data/worktrees",
|
|
41
|
-
stateDir: "/data/state",
|
|
42
|
-
},
|
|
43
|
-
};
|
package/dist/src/agent-env.d.ts
DELETED
package/dist/src/agent-env.js
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
// Explicit env allowlist for agent child processes. The agent sees PATH, HOME,
|
|
2
|
-
// git identity, the GitHub token, and the *active* provider's API key — never
|
|
3
|
-
// the other providers' keys, so a prompt-injected agent can't exfiltrate the
|
|
4
|
-
// whole keyring.
|
|
5
|
-
const BASE_ALLOWLIST = [
|
|
6
|
-
"PATH",
|
|
7
|
-
"HOME",
|
|
8
|
-
"GH_TOKEN",
|
|
9
|
-
"GIT_AUTHOR_NAME",
|
|
10
|
-
"GIT_AUTHOR_EMAIL",
|
|
11
|
-
"GIT_COMMITTER_NAME",
|
|
12
|
-
"GIT_COMMITTER_EMAIL",
|
|
13
|
-
];
|
|
14
|
-
export function buildAgentEnv(opts) {
|
|
15
|
-
const { parentEnv, provider, providerEnv } = opts;
|
|
16
|
-
const env = { CI: "true" };
|
|
17
|
-
for (const key of [...BASE_ALLOWLIST, providerEnv[provider]]) {
|
|
18
|
-
const value = parentEnv[key];
|
|
19
|
-
if (value !== undefined && value !== "") {
|
|
20
|
-
env[key] = value;
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
return env;
|
|
24
|
-
}
|
package/dist/src/cli.d.ts
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
type ParsedArgs = {
|
|
3
|
-
configPath: string | undefined;
|
|
4
|
-
help: boolean;
|
|
5
|
-
forward: string[];
|
|
6
|
-
};
|
|
7
|
-
/**
|
|
8
|
-
* Extract `--config <path>` / `--config=<path>` / `-c <path>` and `--help`/`-h`
|
|
9
|
-
* from argv, forwarding everything else to `runEngine`. A minimal parser is
|
|
10
|
-
* enough — the engine handles its own boolean flags (`--run-once`, `--dry-run`)
|
|
11
|
-
* from the forwarded array.
|
|
12
|
-
*/
|
|
13
|
-
export declare function parseCliArgs(argv: readonly string[]): ParsedArgs;
|
|
14
|
-
export type ParsedInitArgs = {
|
|
15
|
-
targetDir: string;
|
|
16
|
-
help: boolean;
|
|
17
|
-
};
|
|
18
|
-
/**
|
|
19
|
-
* Parse argv left after the leading `init` token has been consumed. Supports
|
|
20
|
-
* an optional positional target directory (`phoebe init ./my-agent`) and
|
|
21
|
-
* `--help`. Extra flags are rejected loudly so a typo like `--forcee` fails
|
|
22
|
-
* fast instead of being silently ignored.
|
|
23
|
-
*/
|
|
24
|
-
export declare function parseInitArgs(argv: readonly string[]): ParsedInitArgs;
|
|
25
|
-
export {};
|
package/dist/src/cli.js
DELETED
|
@@ -1,161 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// `phoebe` bin — the packaged CLI consumers invoke via
|
|
3
|
-
// `npx phoebe-agent [flags]` (or a pinned `phoebe` script). Recognises two
|
|
4
|
-
// modes:
|
|
5
|
-
//
|
|
6
|
-
// phoebe init [dir] Scaffold a consumer-owned runtime (config, prompts,
|
|
7
|
-
// .env.example, container templates, gitignore).
|
|
8
|
-
// Skips existing files — safe to re-run.
|
|
9
|
-
// phoebe [flags] Run the engine. Loads the consumer's
|
|
10
|
-
// `phoebe.config.ts`, overlays `PHOEBE_*` env vars,
|
|
11
|
-
// installs the resolved config, then hands off to main.
|
|
12
|
-
//
|
|
13
|
-
// This is the only supported v1 programmatic surface: there is no exported
|
|
14
|
-
// `run(config)` — CLI-only. That keeps every consumer on the same load/resolve/
|
|
15
|
-
// install pipeline and leaves the door open to CLI-only concerns (init/pin
|
|
16
|
-
// scaffolding, log formatting) without breaking a library API.
|
|
17
|
-
import { realpathSync } from "node:fs";
|
|
18
|
-
import { pathToFileURL } from "node:url";
|
|
19
|
-
import { resolveConfig } from "./config-schema.js";
|
|
20
|
-
import { formatInitReport, runInit } from "./init.js";
|
|
21
|
-
import { applyEnvOverlay, loadUserConfig, resolveConfigPath } from "./load-config.js";
|
|
22
|
-
import { setResolvedConfig } from "./resolved-config.js";
|
|
23
|
-
/**
|
|
24
|
-
* Extract `--config <path>` / `--config=<path>` / `-c <path>` and `--help`/`-h`
|
|
25
|
-
* from argv, forwarding everything else to `runEngine`. A minimal parser is
|
|
26
|
-
* enough — the engine handles its own boolean flags (`--run-once`, `--dry-run`)
|
|
27
|
-
* from the forwarded array.
|
|
28
|
-
*/
|
|
29
|
-
export function parseCliArgs(argv) {
|
|
30
|
-
const forward = [];
|
|
31
|
-
let configPath;
|
|
32
|
-
let help = false;
|
|
33
|
-
for (let i = 0; i < argv.length; i++) {
|
|
34
|
-
const arg = argv[i];
|
|
35
|
-
if (arg === "--help" || arg === "-h") {
|
|
36
|
-
help = true;
|
|
37
|
-
continue;
|
|
38
|
-
}
|
|
39
|
-
if (arg === "--config" || arg === "-c") {
|
|
40
|
-
const next = argv[i + 1];
|
|
41
|
-
if (next === undefined) {
|
|
42
|
-
throw new Error(`${arg} requires a path argument (e.g. --config phoebe.config.ts).`);
|
|
43
|
-
}
|
|
44
|
-
configPath = next;
|
|
45
|
-
i += 1;
|
|
46
|
-
continue;
|
|
47
|
-
}
|
|
48
|
-
if (arg !== undefined && arg.startsWith("--config=")) {
|
|
49
|
-
configPath = arg.slice("--config=".length);
|
|
50
|
-
continue;
|
|
51
|
-
}
|
|
52
|
-
if (arg !== undefined) {
|
|
53
|
-
forward.push(arg);
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
return { configPath, help, forward };
|
|
57
|
-
}
|
|
58
|
-
/**
|
|
59
|
-
* Parse argv left after the leading `init` token has been consumed. Supports
|
|
60
|
-
* an optional positional target directory (`phoebe init ./my-agent`) and
|
|
61
|
-
* `--help`. Extra flags are rejected loudly so a typo like `--forcee` fails
|
|
62
|
-
* fast instead of being silently ignored.
|
|
63
|
-
*/
|
|
64
|
-
export function parseInitArgs(argv) {
|
|
65
|
-
let targetDir;
|
|
66
|
-
let help = false;
|
|
67
|
-
for (const arg of argv) {
|
|
68
|
-
if (arg === "--help" || arg === "-h") {
|
|
69
|
-
help = true;
|
|
70
|
-
continue;
|
|
71
|
-
}
|
|
72
|
-
if (arg.startsWith("-")) {
|
|
73
|
-
throw new Error(`Unknown flag \`${arg}\` for \`phoebe init\`. See \`phoebe init --help\`.`);
|
|
74
|
-
}
|
|
75
|
-
if (targetDir !== undefined) {
|
|
76
|
-
throw new Error(`\`phoebe init\` takes at most one target directory (got \`${targetDir}\` and \`${arg}\`).`);
|
|
77
|
-
}
|
|
78
|
-
targetDir = arg;
|
|
79
|
-
}
|
|
80
|
-
return { targetDir: targetDir ?? ".", help };
|
|
81
|
-
}
|
|
82
|
-
const HELP_TEXT = `phoebe — AFK coding agent
|
|
83
|
-
|
|
84
|
-
Usage:
|
|
85
|
-
phoebe init [dir] Scaffold a consumer-owned runtime
|
|
86
|
-
phoebe [--config <path>] [flags] Run the engine
|
|
87
|
-
|
|
88
|
-
Options (engine mode):
|
|
89
|
-
--config, -c <path> Path to phoebe.config.ts (default: ./phoebe.config.ts)
|
|
90
|
-
--run-once Work one unit of the first one-shot-eligible kind, then exit
|
|
91
|
-
--dry-run Print the selected unit without executing it
|
|
92
|
-
--help, -h Show this message
|
|
93
|
-
|
|
94
|
-
Environment overlays (each replaces the corresponding config field):
|
|
95
|
-
PHOEBE_REPO_SLUG, PHOEBE_REPO_URL, PHOEBE_DEFAULT_BRANCH, PHOEBE_BRANCH_PREFIX,
|
|
96
|
-
PHOEBE_READY_LABEL, PHOEBE_PROCESSING_LABEL, PHOEBE_PR_OPT_OUT_LABEL,
|
|
97
|
-
PHOEBE_INSTALL_COMMAND, PHOEBE_CHECK_COMMAND, PHOEBE_TEST_COMMAND,
|
|
98
|
-
PHOEBE_READY_COMMAND, PHOEBE_BLOCKED_BY_PATTERN, PHOEBE_REVIEWS_SUCCESS_HEADING,
|
|
99
|
-
PHOEBE_PR_SCOPE, PHOEBE_DRAFT_PRS, PHOEBE_DEFAULT_PROVIDER
|
|
100
|
-
|
|
101
|
-
Runtime toggles (read directly by the engine, not overlaid onto the config):
|
|
102
|
-
PHOEBE_AGENT Provider name to use for this run (cursor|claude|codex)
|
|
103
|
-
PHOEBE_MODEL Model to use for this run
|
|
104
|
-
PHOEBE_POLL_INTERVAL_MS Persistent-mode poll interval (default 300000)
|
|
105
|
-
PHOEBE_DEFAULT_BRANCH Branch the supervisor keeps the clone on (overrides tracked branch only)
|
|
106
|
-
`;
|
|
107
|
-
const INIT_HELP_TEXT = `phoebe init — scaffold a consumer-owned runtime
|
|
108
|
-
|
|
109
|
-
Usage:
|
|
110
|
-
phoebe init [dir]
|
|
111
|
-
|
|
112
|
-
Writes into [dir] (default: current directory):
|
|
113
|
-
phoebe.config.ts Consumer config starter (edit the five required fields)
|
|
114
|
-
prompts/ Copies of the shipped agent prompts (edit to override)
|
|
115
|
-
.env.example Documented environment variables to copy to .env
|
|
116
|
-
.gitignore Additive — appends Phoebe entries only
|
|
117
|
-
container/Dockerfile Runtime image (Node + git + gh + pinned phoebe-agent)
|
|
118
|
-
container/compose.yml Base one-shot compose config
|
|
119
|
-
container/compose.daemon.yml Overlay to run Phoebe as a persistent daemon
|
|
120
|
-
container/supervisor.sh Warm-install + engine-restart loop (chmod +x)
|
|
121
|
-
|
|
122
|
-
Existing files are left untouched, so re-running is safe. To regenerate a
|
|
123
|
-
scaffolded file, delete it first and re-run \`phoebe init\`.
|
|
124
|
-
`;
|
|
125
|
-
async function main() {
|
|
126
|
-
const args = process.argv.slice(2);
|
|
127
|
-
if (args[0] === "init") {
|
|
128
|
-
const parsed = parseInitArgs(args.slice(1));
|
|
129
|
-
if (parsed.help) {
|
|
130
|
-
process.stdout.write(INIT_HELP_TEXT);
|
|
131
|
-
return;
|
|
132
|
-
}
|
|
133
|
-
const report = runInit({ targetDir: parsed.targetDir });
|
|
134
|
-
process.stdout.write(formatInitReport(report, parsed.targetDir));
|
|
135
|
-
return;
|
|
136
|
-
}
|
|
137
|
-
const parsed = parseCliArgs(args);
|
|
138
|
-
if (parsed.help) {
|
|
139
|
-
process.stdout.write(HELP_TEXT);
|
|
140
|
-
return;
|
|
141
|
-
}
|
|
142
|
-
const configPath = resolveConfigPath(parsed.configPath, process.cwd());
|
|
143
|
-
const userConfig = await loadUserConfig(configPath);
|
|
144
|
-
const overlaid = applyEnvOverlay(userConfig, process.env);
|
|
145
|
-
setResolvedConfig(resolveConfig(overlaid));
|
|
146
|
-
// Import after the config is installed — main.ts's module-level constants
|
|
147
|
-
// read `config` at import time via the Proxy in resolved-config.ts.
|
|
148
|
-
const { runEngine } = await import("./main.js");
|
|
149
|
-
await runEngine(parsed.forward);
|
|
150
|
-
}
|
|
151
|
-
// Only run when invoked as the entry point — tests import `parseCliArgs` from
|
|
152
|
-
// this module without triggering the whole CLI pipeline. `argv[1]` is realpath'd
|
|
153
|
-
// so a bin symlink (`node_modules/.bin/phoebe -> ../phoebe-agent/dist/src/cli.js`)
|
|
154
|
-
// still matches `import.meta.url`, which Node resolves through symlinks.
|
|
155
|
-
if (process.argv[1] && import.meta.url === pathToFileURL(realpathSync(process.argv[1])).href) {
|
|
156
|
-
main().catch((error) => {
|
|
157
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
158
|
-
console.error(`[phoebe] ${message}`);
|
|
159
|
-
process.exit(1);
|
|
160
|
-
});
|
|
161
|
-
}
|
|
@@ -1,163 +0,0 @@
|
|
|
1
|
-
export declare const PROVIDER_NAMES: readonly ["cursor", "claude", "codex"];
|
|
2
|
-
export type ProviderName = (typeof PROVIDER_NAMES)[number];
|
|
3
|
-
export type PromptFilesConfig = {
|
|
4
|
-
issue: string;
|
|
5
|
-
conflict: string;
|
|
6
|
-
checks: string;
|
|
7
|
-
reviews: string;
|
|
8
|
-
};
|
|
9
|
-
export type PathsConfig = {
|
|
10
|
-
/** The private clone (origin hub). */
|
|
11
|
-
repoDir: string;
|
|
12
|
-
/** Per-unit git worktrees. */
|
|
13
|
-
worktreesDir: string;
|
|
14
|
-
/** Lock, markers, logs. */
|
|
15
|
-
stateDir: string;
|
|
16
|
-
};
|
|
17
|
-
export type PhoebeConfig = {
|
|
18
|
-
/** GitHub `owner/repo` slug, passed to every `gh -R` call. */
|
|
19
|
-
repoSlug: string;
|
|
20
|
-
/** HTTPS clone URL for the container's private clone. */
|
|
21
|
-
repoUrl: string;
|
|
22
|
-
/** Branch PRs target and worktrees base off (usually `main`). */
|
|
23
|
-
defaultBranch: string;
|
|
24
|
-
/** Prefix for agent branches; issue branches are `<prefix>issue-<n>`. */
|
|
25
|
-
branchPrefix: string;
|
|
26
|
-
/** Label marking issues Phoebe may pick up. */
|
|
27
|
-
readyLabel: string;
|
|
28
|
-
/** Label the agent applies to an issue it has claimed and is working. */
|
|
29
|
-
processingLabel: string;
|
|
30
|
-
/** Which open PRs the conflicts/checks/reviews work-kinds scan.
|
|
31
|
-
* "phoebe" = only branchPrefix branches. "all" = any same-repo PR. */
|
|
32
|
-
prScope: "phoebe" | "all";
|
|
33
|
-
/** Draft PR handling: "skip-non-phoebe" = drafts on non-Phoebe branches are
|
|
34
|
-
* off-limits; "skip-all" = never touch drafts; "include" = drafts are fair game. */
|
|
35
|
-
draftPrs: "skip-non-phoebe" | "skip-all" | "include";
|
|
36
|
-
/** PRs carrying this label are excluded from the PR scan in every mode. */
|
|
37
|
-
prOptOutLabel: string;
|
|
38
|
-
/** Shell command strings — toolchains differ per repo, so these are data. */
|
|
39
|
-
installCommand: string;
|
|
40
|
-
checkCommand: string;
|
|
41
|
-
testCommand: string;
|
|
42
|
-
/** The all-in-one gate the agent runs before pushing (e.g. `npm run ready`).
|
|
43
|
-
* Substituted into default prompts as `{{READY_COMMAND}}`. */
|
|
44
|
-
readyCommand: string;
|
|
45
|
-
/**
|
|
46
|
-
* JavaScript-compatible regex source that matches an issue-blocker reference
|
|
47
|
-
* in issue body text. Must expose the blocker issue number as capture group 1.
|
|
48
|
-
* Compiled with the `gi` flags.
|
|
49
|
-
*/
|
|
50
|
-
blockedByPattern: string;
|
|
51
|
-
/**
|
|
52
|
-
* Markdown heading the reviews agent must include when it posts its summary
|
|
53
|
-
* comment. The orchestrator detects the summary by substring match on this
|
|
54
|
-
* exact string, so it must be unique enough not to collide with other
|
|
55
|
-
* comments. Substituted into the default reviews prompt as
|
|
56
|
-
* `{{REVIEWS_SUCCESS_HEADING}}`.
|
|
57
|
-
*/
|
|
58
|
-
reviewsSuccessHeading: string;
|
|
59
|
-
/** Prompt template paths, relative to the package root (apps/phoebe). */
|
|
60
|
-
promptFiles: PromptFilesConfig;
|
|
61
|
-
/** Ordered work kinds, validated by the orchestrator at startup. */
|
|
62
|
-
workOrder: readonly string[];
|
|
63
|
-
defaultProvider: ProviderName;
|
|
64
|
-
defaultModels: Record<ProviderName, string>;
|
|
65
|
-
/** Env var holding each provider's API key — the only key the agent child inherits. */
|
|
66
|
-
providerEnv: Record<ProviderName, string>;
|
|
67
|
-
/**
|
|
68
|
-
* Repo paths that mean "Phoebe's own code changed" — a fetch of the default
|
|
69
|
-
* branch touching these makes the orchestrator exit for a supervisor
|
|
70
|
-
* reinstall + re-exec. Directory entries must end with `/`.
|
|
71
|
-
*/
|
|
72
|
-
selfUpdatePaths: readonly string[];
|
|
73
|
-
/** Container filesystem layout (named volumes). */
|
|
74
|
-
paths: PathsConfig;
|
|
75
|
-
};
|
|
76
|
-
/**
|
|
77
|
-
* User-facing shape of `phoebe.config.ts`. Only the five fields with no sane
|
|
78
|
-
* cross-repo default are required; everything else is optional and filled from
|
|
79
|
-
* `CONFIG_DEFAULTS` by `resolveConfig()`. Nested objects (`promptFiles`,
|
|
80
|
-
* `paths`, `defaultModels`, `providerEnv`) are merged key-by-key, so overriding
|
|
81
|
-
* one provider's model or one prompt file does not force the caller to supply
|
|
82
|
-
* the rest.
|
|
83
|
-
*/
|
|
84
|
-
export type PhoebeUserConfig = {
|
|
85
|
-
repoSlug: string;
|
|
86
|
-
repoUrl: string;
|
|
87
|
-
installCommand: string;
|
|
88
|
-
checkCommand: string;
|
|
89
|
-
testCommand: string;
|
|
90
|
-
defaultBranch?: string;
|
|
91
|
-
branchPrefix?: string;
|
|
92
|
-
readyLabel?: string;
|
|
93
|
-
processingLabel?: string;
|
|
94
|
-
prScope?: PhoebeConfig["prScope"];
|
|
95
|
-
draftPrs?: PhoebeConfig["draftPrs"];
|
|
96
|
-
prOptOutLabel?: string;
|
|
97
|
-
readyCommand?: string;
|
|
98
|
-
blockedByPattern?: string;
|
|
99
|
-
reviewsSuccessHeading?: string;
|
|
100
|
-
promptFiles?: Partial<PromptFilesConfig>;
|
|
101
|
-
workOrder?: readonly string[];
|
|
102
|
-
defaultProvider?: ProviderName;
|
|
103
|
-
defaultModels?: Partial<Record<ProviderName, string>>;
|
|
104
|
-
providerEnv?: Partial<Record<ProviderName, string>>;
|
|
105
|
-
selfUpdatePaths?: readonly string[];
|
|
106
|
-
paths?: Partial<PathsConfig>;
|
|
107
|
-
};
|
|
108
|
-
/**
|
|
109
|
-
* Engine defaults for every optional user field. These land in the resolved
|
|
110
|
-
* config whenever the consumer's `phoebe.config.ts` omits them, so a minimal
|
|
111
|
-
* consumer config only has to name the repo and its three toolchain commands.
|
|
112
|
-
*/
|
|
113
|
-
export declare const CONFIG_DEFAULTS: {
|
|
114
|
-
readonly defaultBranch: "main";
|
|
115
|
-
readonly branchPrefix: "phoebe/";
|
|
116
|
-
readonly readyLabel: "ready-for-agent";
|
|
117
|
-
readonly processingLabel: "processing";
|
|
118
|
-
readonly prScope: "phoebe";
|
|
119
|
-
readonly draftPrs: "skip-non-phoebe";
|
|
120
|
-
readonly prOptOutLabel: "ready-for-human";
|
|
121
|
-
readonly readyCommand: "npm run ready";
|
|
122
|
-
readonly blockedByPattern: string;
|
|
123
|
-
readonly reviewsSuccessHeading: "## Review feedback addressed";
|
|
124
|
-
readonly promptFiles: {
|
|
125
|
-
issue: string;
|
|
126
|
-
conflict: string;
|
|
127
|
-
checks: string;
|
|
128
|
-
reviews: string;
|
|
129
|
-
};
|
|
130
|
-
readonly workOrder: readonly string[];
|
|
131
|
-
readonly defaultProvider: ProviderName;
|
|
132
|
-
readonly defaultModels: {
|
|
133
|
-
cursor: string;
|
|
134
|
-
claude: string;
|
|
135
|
-
codex: string;
|
|
136
|
-
};
|
|
137
|
-
readonly providerEnv: {
|
|
138
|
-
cursor: string;
|
|
139
|
-
claude: string;
|
|
140
|
-
codex: string;
|
|
141
|
-
};
|
|
142
|
-
readonly selfUpdatePaths: readonly string[];
|
|
143
|
-
readonly paths: {
|
|
144
|
-
repoDir: string;
|
|
145
|
-
worktreesDir: string;
|
|
146
|
-
stateDir: string;
|
|
147
|
-
};
|
|
148
|
-
};
|
|
149
|
-
/**
|
|
150
|
-
* Throw when a required field is missing or blank, or when `blockedByPattern`
|
|
151
|
-
* is not a valid regex or fails to expose the blocker issue number as capture
|
|
152
|
-
* group 1. `parseBlockedBy` reads `match[1]`, so a pattern without a capture
|
|
153
|
-
* group would silently break the entire blocker-detection path — reject it up
|
|
154
|
-
* front. Kept separate from `resolveConfig` so consumers or tests can validate
|
|
155
|
-
* a config independent of the defaults merge.
|
|
156
|
-
*/
|
|
157
|
-
export declare function validateUserConfig(user: PhoebeUserConfig): void;
|
|
158
|
-
/**
|
|
159
|
-
* Merge a user config with `CONFIG_DEFAULTS` and return the fully-populated
|
|
160
|
-
* shape the engine runs against. Nested records are shallow-merged so partial
|
|
161
|
-
* overrides (one prompt file, one provider's env var, etc.) work as expected.
|
|
162
|
-
*/
|
|
163
|
-
export declare function resolveConfig(user: PhoebeUserConfig): PhoebeConfig;
|