phoebe-agent 0.0.0 → 0.1.1
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 +431 -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 +145 -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 +17 -5
- package/templates/container/Dockerfile +128 -19
- package/templates/container/compose.local.yml +37 -0
- package/templates/container/compose.yml +43 -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,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;
|
|
@@ -1,140 +0,0 @@
|
|
|
1
|
-
// Shape of the repo-specific configuration the Phoebe engine runs against.
|
|
2
|
-
// The values live in ../phoebe.config.ts — the single file allowed to mention
|
|
3
|
-
// this repository. Engine modules (everything under src/) import the resolved
|
|
4
|
-
// config from ./resolved-config.ts and stay repo-agnostic;
|
|
5
|
-
// src/config-seam.test.ts enforces it.
|
|
6
|
-
//
|
|
7
|
-
// Two shapes live here. `PhoebeUserConfig` is what a consumer writes: only the
|
|
8
|
-
// unavoidable repo/toolchain fields are required; everything else is optional
|
|
9
|
-
// and filled from `CONFIG_DEFAULTS` by `resolveConfig()`. `PhoebeConfig` is the
|
|
10
|
-
// fully-resolved shape the engine sees at runtime — every field populated.
|
|
11
|
-
export const PROVIDER_NAMES = ["cursor", "claude", "codex"];
|
|
12
|
-
/**
|
|
13
|
-
* Engine defaults for every optional user field. These land in the resolved
|
|
14
|
-
* config whenever the consumer's `phoebe.config.ts` omits them, so a minimal
|
|
15
|
-
* consumer config only has to name the repo and its three toolchain commands.
|
|
16
|
-
*/
|
|
17
|
-
export const CONFIG_DEFAULTS = {
|
|
18
|
-
defaultBranch: "main",
|
|
19
|
-
branchPrefix: "phoebe/",
|
|
20
|
-
readyLabel: "ready-for-agent",
|
|
21
|
-
processingLabel: "processing",
|
|
22
|
-
prScope: "phoebe",
|
|
23
|
-
draftPrs: "skip-non-phoebe",
|
|
24
|
-
prOptOutLabel: "ready-for-human",
|
|
25
|
-
readyCommand: "npm run ready",
|
|
26
|
-
blockedByPattern: String.raw `Blocked by\s+#(\d+)`,
|
|
27
|
-
reviewsSuccessHeading: "## Review feedback addressed",
|
|
28
|
-
promptFiles: {
|
|
29
|
-
issue: "prompts/prompt.md",
|
|
30
|
-
conflict: "prompts/conflict-prompt.md",
|
|
31
|
-
checks: "prompts/checks-prompt.md",
|
|
32
|
-
reviews: "prompts/reviews-prompt.md",
|
|
33
|
-
},
|
|
34
|
-
workOrder: ["conflicts", "checks", "reviews", "issues"],
|
|
35
|
-
defaultProvider: "cursor",
|
|
36
|
-
defaultModels: {
|
|
37
|
-
cursor: "composer-2.5",
|
|
38
|
-
claude: "claude-sonnet-4-6",
|
|
39
|
-
codex: "gpt-5.4-mini",
|
|
40
|
-
},
|
|
41
|
-
providerEnv: {
|
|
42
|
-
cursor: "CURSOR_API_KEY",
|
|
43
|
-
claude: "ANTHROPIC_API_KEY",
|
|
44
|
-
codex: "OPENAI_KEY",
|
|
45
|
-
},
|
|
46
|
-
selfUpdatePaths: ["package.json", "package-lock.json"],
|
|
47
|
-
paths: {
|
|
48
|
-
repoDir: "/data/repo",
|
|
49
|
-
worktreesDir: "/data/worktrees",
|
|
50
|
-
stateDir: "/data/state",
|
|
51
|
-
},
|
|
52
|
-
};
|
|
53
|
-
const REQUIRED_USER_FIELDS = [
|
|
54
|
-
"repoSlug",
|
|
55
|
-
"repoUrl",
|
|
56
|
-
"installCommand",
|
|
57
|
-
"checkCommand",
|
|
58
|
-
"testCommand",
|
|
59
|
-
];
|
|
60
|
-
/**
|
|
61
|
-
* Count the numbered capture groups defined by a regex source. We compile it
|
|
62
|
-
* with an added empty alternative (`|`) so the resulting regex always matches
|
|
63
|
-
* the empty string; the match array's length minus one then equals the number
|
|
64
|
-
* of capture groups, regardless of whether the original pattern would have
|
|
65
|
-
* matched anything on its own. Escaped parens, non-capturing groups (`(?:…)`),
|
|
66
|
-
* lookarounds, and named groups are handled correctly because we're asking
|
|
67
|
-
* the engine's own group count, not parsing the source ourselves.
|
|
68
|
-
*/
|
|
69
|
-
function countCaptureGroups(source) {
|
|
70
|
-
const compiled = new RegExp(`${source}|`);
|
|
71
|
-
const match = compiled.exec("");
|
|
72
|
-
// The extra `|` guarantees a match against ""; TS still narrows to nullable.
|
|
73
|
-
if (!match) {
|
|
74
|
-
return 0;
|
|
75
|
-
}
|
|
76
|
-
return match.length - 1;
|
|
77
|
-
}
|
|
78
|
-
/**
|
|
79
|
-
* Throw when a required field is missing or blank, or when `blockedByPattern`
|
|
80
|
-
* is not a valid regex or fails to expose the blocker issue number as capture
|
|
81
|
-
* group 1. `parseBlockedBy` reads `match[1]`, so a pattern without a capture
|
|
82
|
-
* group would silently break the entire blocker-detection path — reject it up
|
|
83
|
-
* front. Kept separate from `resolveConfig` so consumers or tests can validate
|
|
84
|
-
* a config independent of the defaults merge.
|
|
85
|
-
*/
|
|
86
|
-
export function validateUserConfig(user) {
|
|
87
|
-
const missing = REQUIRED_USER_FIELDS.filter((key) => {
|
|
88
|
-
const value = user[key];
|
|
89
|
-
return typeof value !== "string" || value.trim().length === 0;
|
|
90
|
-
});
|
|
91
|
-
if (missing.length > 0) {
|
|
92
|
-
throw new Error(`phoebe.config.ts is missing required field(s): ${missing.join(", ")}. ` +
|
|
93
|
-
`Only these five fields are required — the engine fills the rest from its defaults.`);
|
|
94
|
-
}
|
|
95
|
-
if (user.blockedByPattern !== undefined) {
|
|
96
|
-
try {
|
|
97
|
-
new RegExp(user.blockedByPattern, "gi");
|
|
98
|
-
}
|
|
99
|
-
catch (err) {
|
|
100
|
-
throw new Error(`phoebe.config.ts blockedByPattern is not a valid regex: ${err.message}`);
|
|
101
|
-
}
|
|
102
|
-
if (countCaptureGroups(user.blockedByPattern) < 1) {
|
|
103
|
-
throw new Error(`phoebe.config.ts blockedByPattern must define capture group 1 for the ` +
|
|
104
|
-
`blocker issue number (parseBlockedBy reads match[1]). Wrap the number ` +
|
|
105
|
-
`portion in parentheses, e.g. String.raw\`Blocked by\\s+#(\\d+)\`.`);
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
/**
|
|
110
|
-
* Merge a user config with `CONFIG_DEFAULTS` and return the fully-populated
|
|
111
|
-
* shape the engine runs against. Nested records are shallow-merged so partial
|
|
112
|
-
* overrides (one prompt file, one provider's env var, etc.) work as expected.
|
|
113
|
-
*/
|
|
114
|
-
export function resolveConfig(user) {
|
|
115
|
-
validateUserConfig(user);
|
|
116
|
-
return {
|
|
117
|
-
repoSlug: user.repoSlug,
|
|
118
|
-
repoUrl: user.repoUrl,
|
|
119
|
-
installCommand: user.installCommand,
|
|
120
|
-
checkCommand: user.checkCommand,
|
|
121
|
-
testCommand: user.testCommand,
|
|
122
|
-
defaultBranch: user.defaultBranch ?? CONFIG_DEFAULTS.defaultBranch,
|
|
123
|
-
branchPrefix: user.branchPrefix ?? CONFIG_DEFAULTS.branchPrefix,
|
|
124
|
-
readyLabel: user.readyLabel ?? CONFIG_DEFAULTS.readyLabel,
|
|
125
|
-
processingLabel: user.processingLabel ?? CONFIG_DEFAULTS.processingLabel,
|
|
126
|
-
prScope: user.prScope ?? CONFIG_DEFAULTS.prScope,
|
|
127
|
-
draftPrs: user.draftPrs ?? CONFIG_DEFAULTS.draftPrs,
|
|
128
|
-
prOptOutLabel: user.prOptOutLabel ?? CONFIG_DEFAULTS.prOptOutLabel,
|
|
129
|
-
readyCommand: user.readyCommand ?? CONFIG_DEFAULTS.readyCommand,
|
|
130
|
-
blockedByPattern: user.blockedByPattern ?? CONFIG_DEFAULTS.blockedByPattern,
|
|
131
|
-
reviewsSuccessHeading: user.reviewsSuccessHeading ?? CONFIG_DEFAULTS.reviewsSuccessHeading,
|
|
132
|
-
promptFiles: { ...CONFIG_DEFAULTS.promptFiles, ...user.promptFiles },
|
|
133
|
-
workOrder: user.workOrder ?? CONFIG_DEFAULTS.workOrder,
|
|
134
|
-
defaultProvider: user.defaultProvider ?? CONFIG_DEFAULTS.defaultProvider,
|
|
135
|
-
defaultModels: { ...CONFIG_DEFAULTS.defaultModels, ...user.defaultModels },
|
|
136
|
-
providerEnv: { ...CONFIG_DEFAULTS.providerEnv, ...user.providerEnv },
|
|
137
|
-
selfUpdatePaths: user.selfUpdatePaths ?? CONFIG_DEFAULTS.selfUpdatePaths,
|
|
138
|
-
paths: { ...CONFIG_DEFAULTS.paths, ...user.paths },
|
|
139
|
-
};
|
|
140
|
-
}
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
export declare const CONTAINER_MARKER_PATH = "/.phoebe-container";
|
|
2
|
-
export declare function isInsideContainer(exists?: (path: string) => boolean): boolean;
|
|
3
|
-
export type ExecutionDecision = "execute" | "dry-run" | "refuse";
|
|
4
|
-
/** Pure decision: may this process execute the selected work unit? */
|
|
5
|
-
export declare function executionDecision(opts: {
|
|
6
|
-
dryRun: boolean;
|
|
7
|
-
inContainer: boolean;
|
|
8
|
-
}): ExecutionDecision;
|
|
9
|
-
export declare const EXECUTION_REFUSED_MESSAGE: string;
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
// Work-unit execution is gated to the Phoebe container. Selection logic and
|
|
2
|
-
// --dry-run stay host-runnable for fast iteration; anything that mutates a
|
|
3
|
-
// clone, launches an agent CLI, or pushes runs only where the container
|
|
4
|
-
// marker exists (created by the Dockerfile).
|
|
5
|
-
import { existsSync } from "node:fs";
|
|
6
|
-
export const CONTAINER_MARKER_PATH = "/.phoebe-container";
|
|
7
|
-
export function isInsideContainer(exists = existsSync) {
|
|
8
|
-
return exists(CONTAINER_MARKER_PATH);
|
|
9
|
-
}
|
|
10
|
-
/** Pure decision: may this process execute the selected work unit? */
|
|
11
|
-
export function executionDecision(opts) {
|
|
12
|
-
if (opts.dryRun)
|
|
13
|
-
return "dry-run";
|
|
14
|
-
return opts.inContainer ? "execute" : "refuse";
|
|
15
|
-
}
|
|
16
|
-
export const EXECUTION_REFUSED_MESSAGE = "[phoebe] Refusing to execute a work unit outside the Phoebe container. " +
|
|
17
|
-
"Use --dry-run to preview selection on the host, or start the container loop.";
|
package/dist/src/git-model.d.ts
DELETED
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
export type GitRunner = (args: string[], opts?: {
|
|
2
|
-
cwd?: string;
|
|
3
|
-
stdio?: "inherit" | "ignore" | "pipe";
|
|
4
|
-
}) => string;
|
|
5
|
-
export declare const defaultGit: GitRunner;
|
|
6
|
-
/** Clone the repo into `repoDir` unless a clone already exists there. */
|
|
7
|
-
export declare function ensureClone(opts: {
|
|
8
|
-
repoUrl: string;
|
|
9
|
-
repoDir: string;
|
|
10
|
-
}, git?: GitRunner): void;
|
|
11
|
-
export declare function fetchOrigin(repoDir: string, git?: GitRunner): void;
|
|
12
|
-
export declare function originBranchSha(repoDir: string, branch: string, git?: GitRunner): string;
|
|
13
|
-
/** Filesystem-safe worktree directory name for a branch. */
|
|
14
|
-
export declare function worktreeDirForBranch(worktreesDir: string, branch: string): string;
|
|
15
|
-
/** Create a worktree on a (possibly new) branch reset to `baseRef`. */
|
|
16
|
-
export declare function addWorktreeForNewBranch(opts: {
|
|
17
|
-
repoDir: string;
|
|
18
|
-
worktreeDir: string;
|
|
19
|
-
branch: string;
|
|
20
|
-
baseRef: string;
|
|
21
|
-
}, git?: GitRunner): void;
|
|
22
|
-
/** Create a worktree on an existing branch (local first, then origin/<branch>). */
|
|
23
|
-
export declare function addWorktreeForExistingBranch(opts: {
|
|
24
|
-
repoDir: string;
|
|
25
|
-
worktreeDir: string;
|
|
26
|
-
branch: string;
|
|
27
|
-
}, git?: GitRunner): void;
|
|
28
|
-
export declare function removeWorktree(repoDir: string, worktreeDir: string, git?: GitRunner): void;
|
|
29
|
-
export declare function commitCount(worktreeDir: string, range: string, git?: GitRunner): number;
|
|
30
|
-
export declare function pushBranch(worktreeDir: string, branch: string, git?: GitRunner): void;
|
package/dist/src/git-model.js
DELETED
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
// Origin-hub git model: a private clone owns all local git state, work units
|
|
2
|
-
// run in worktrees off it, finished branches push straight to origin. Every
|
|
3
|
-
// function takes the clone directory explicitly so tests can run against a
|
|
4
|
-
// temp clone.
|
|
5
|
-
import { execFileSync } from "node:child_process";
|
|
6
|
-
import { existsSync, mkdirSync, rmSync } from "node:fs";
|
|
7
|
-
import { join } from "node:path";
|
|
8
|
-
export const defaultGit = (args, opts) => execFileSync("git", args, {
|
|
9
|
-
encoding: "utf8",
|
|
10
|
-
...(opts?.cwd ? { cwd: opts.cwd } : {}),
|
|
11
|
-
...(opts?.stdio ? { stdio: opts.stdio } : {}),
|
|
12
|
-
});
|
|
13
|
-
/** Clone the repo into `repoDir` unless a clone already exists there. */
|
|
14
|
-
export function ensureClone(opts, git = defaultGit) {
|
|
15
|
-
if (existsSync(join(opts.repoDir, ".git")))
|
|
16
|
-
return;
|
|
17
|
-
mkdirSync(opts.repoDir, { recursive: true });
|
|
18
|
-
git(["clone", opts.repoUrl, opts.repoDir], { stdio: "inherit" });
|
|
19
|
-
}
|
|
20
|
-
export function fetchOrigin(repoDir, git = defaultGit) {
|
|
21
|
-
git(["fetch", "origin"], { cwd: repoDir, stdio: "inherit" });
|
|
22
|
-
}
|
|
23
|
-
export function originBranchSha(repoDir, branch, git = defaultGit) {
|
|
24
|
-
return git(["rev-parse", `origin/${branch}`], { cwd: repoDir }).trim();
|
|
25
|
-
}
|
|
26
|
-
/** Filesystem-safe worktree directory name for a branch. */
|
|
27
|
-
export function worktreeDirForBranch(worktreesDir, branch) {
|
|
28
|
-
return join(worktreesDir, branch.toLowerCase().replace(/[^a-z0-9]/g, "-"));
|
|
29
|
-
}
|
|
30
|
-
/** Create a worktree on a (possibly new) branch reset to `baseRef`. */
|
|
31
|
-
export function addWorktreeForNewBranch(opts, git = defaultGit) {
|
|
32
|
-
git(["worktree", "add", "-B", opts.branch, opts.worktreeDir, opts.baseRef], {
|
|
33
|
-
cwd: opts.repoDir,
|
|
34
|
-
stdio: "inherit",
|
|
35
|
-
});
|
|
36
|
-
}
|
|
37
|
-
/** Create a worktree on an existing branch (local first, then origin/<branch>). */
|
|
38
|
-
export function addWorktreeForExistingBranch(opts, git = defaultGit) {
|
|
39
|
-
try {
|
|
40
|
-
git(["worktree", "add", opts.worktreeDir, opts.branch], {
|
|
41
|
-
cwd: opts.repoDir,
|
|
42
|
-
stdio: "inherit",
|
|
43
|
-
});
|
|
44
|
-
}
|
|
45
|
-
catch {
|
|
46
|
-
git(["worktree", "add", "-B", opts.branch, opts.worktreeDir, `origin/${opts.branch}`], {
|
|
47
|
-
cwd: opts.repoDir,
|
|
48
|
-
stdio: "inherit",
|
|
49
|
-
});
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
export function removeWorktree(repoDir, worktreeDir, git = defaultGit) {
|
|
53
|
-
try {
|
|
54
|
-
git(["worktree", "remove", "--force", worktreeDir], { cwd: repoDir, stdio: "ignore" });
|
|
55
|
-
}
|
|
56
|
-
catch {
|
|
57
|
-
rmSync(worktreeDir, { recursive: true, force: true });
|
|
58
|
-
}
|
|
59
|
-
try {
|
|
60
|
-
git(["worktree", "prune"], { cwd: repoDir, stdio: "ignore" });
|
|
61
|
-
}
|
|
62
|
-
catch {
|
|
63
|
-
// Best-effort.
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
export function commitCount(worktreeDir, range, git = defaultGit) {
|
|
67
|
-
return Number(git(["rev-list", "--count", range], { cwd: worktreeDir }).trim());
|
|
68
|
-
}
|
|
69
|
-
export function pushBranch(worktreeDir, branch, git = defaultGit) {
|
|
70
|
-
git(["push", "origin", branch], { cwd: worktreeDir, stdio: "inherit" });
|
|
71
|
-
}
|
package/dist/src/index.d.ts
DELETED
package/dist/src/index.js
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
// Public consumer surface for `phoebe-agent`. Deliberately minimal — the
|
|
2
|
-
// packaged v1 has no programmatic runtime API, only the `phoebe` bin. Import
|
|
3
|
-
// this module for type-checked config authoring:
|
|
4
|
-
//
|
|
5
|
-
// ```ts
|
|
6
|
-
// import { defineConfig, type PhoebeUserConfig } from "phoebe-agent";
|
|
7
|
-
// export default defineConfig({ repoSlug: "...", ... });
|
|
8
|
-
// ```
|
|
9
|
-
//
|
|
10
|
-
// Types are structurally identical to their source in ./config-schema.ts;
|
|
11
|
-
// `defineConfig` is the identity typing helper from ./load-config.ts.
|
|
12
|
-
export { defineConfig } from "./load-config.js";
|
package/dist/src/init.d.ts
DELETED
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
/** Placeholder tokens rendered into every scaffolded file. */
|
|
2
|
-
export type TemplateParams = {
|
|
3
|
-
/** The consumer's install command (also written into the config). */
|
|
4
|
-
installCommand: string;
|
|
5
|
-
/** The npm package name of the CLI — normally `phoebe-agent`. */
|
|
6
|
-
cliBin: string;
|
|
7
|
-
};
|
|
8
|
-
export declare const DEFAULT_TEMPLATE_PARAMS: TemplateParams;
|
|
9
|
-
/**
|
|
10
|
-
* One consumer-owned output produced by init. Sources are one of:
|
|
11
|
-
* - `template`: a file under the shipped `templates/` tree, rendered with
|
|
12
|
-
* placeholder substitution.
|
|
13
|
-
* - `shipped-prompt`: a file under the shipped `prompts/` tree, copied
|
|
14
|
-
* verbatim (prompts already carry their own `{{…}}` markers that the
|
|
15
|
-
* engine expands at run time — do NOT substitute those here).
|
|
16
|
-
* - `gitignore`: an additive-merge marker; `entries` are appended to any
|
|
17
|
-
* existing `.gitignore`, deduped line-wise.
|
|
18
|
-
*/
|
|
19
|
-
export type PlannedOutput = {
|
|
20
|
-
destRelPath: string;
|
|
21
|
-
source: {
|
|
22
|
-
kind: "template";
|
|
23
|
-
templateRelPath: string;
|
|
24
|
-
executable?: boolean;
|
|
25
|
-
} | {
|
|
26
|
-
kind: "shipped-prompt";
|
|
27
|
-
promptRelPath: string;
|
|
28
|
-
} | {
|
|
29
|
-
kind: "gitignore";
|
|
30
|
-
entries: readonly string[];
|
|
31
|
-
};
|
|
32
|
-
};
|
|
33
|
-
/**
|
|
34
|
-
* Enumerate every file init will produce. The prompt list is derived from
|
|
35
|
-
* `CONFIG_DEFAULTS.promptFiles` so adding a new prompt kind to the engine
|
|
36
|
-
* automatically gets scaffolded — no drift between the two lists.
|
|
37
|
-
*/
|
|
38
|
-
export declare function planInitOutputs(): PlannedOutput[];
|
|
39
|
-
/**
|
|
40
|
-
* Substitute `{{KEY}}` tokens with the matching value. Unknown tokens throw
|
|
41
|
-
* so a typo in a template surfaces during tests rather than silently landing
|
|
42
|
-
* in a consumer's repo. Iteration is over the params (not a regex over the
|
|
43
|
-
* source) so a value that happens to contain a `{{…}}`-shaped substring is
|
|
44
|
-
* never re-scanned.
|
|
45
|
-
*/
|
|
46
|
-
export declare function renderTemplate(source: string, params: TemplateParams): string;
|
|
47
|
-
/**
|
|
48
|
-
* Additive `.gitignore` merge — append any missing entries under a `# Phoebe`
|
|
49
|
-
* header, leaving existing lines untouched. An empty file becomes a bare list
|
|
50
|
-
* (no header) because there's nothing else to distinguish it from.
|
|
51
|
-
*/
|
|
52
|
-
export declare function mergeGitignore(existing: string, entries: readonly string[]): string;
|
|
53
|
-
export type InitReport = {
|
|
54
|
-
/** New files written (destination paths, relative to the target dir). */
|
|
55
|
-
created: string[];
|
|
56
|
-
/** `.gitignore` entries appended in-place (destination paths). */
|
|
57
|
-
updated: string[];
|
|
58
|
-
/** Existing files left alone (destination paths). */
|
|
59
|
-
skipped: string[];
|
|
60
|
-
};
|
|
61
|
-
export type RunInitOptions = {
|
|
62
|
-
/** Directory the scaffolded files land under. Created if missing. */
|
|
63
|
-
targetDir: string;
|
|
64
|
-
/** Override template params (`installCommand`, `cliBin`). */
|
|
65
|
-
params?: Partial<TemplateParams>;
|
|
66
|
-
/** Root for shipped `templates/` and `prompts/` (test seam). Defaults to
|
|
67
|
-
* the walk-up from this module. */
|
|
68
|
-
packageRoot?: string;
|
|
69
|
-
};
|
|
70
|
-
/**
|
|
71
|
-
* Execute the plan: create missing files, additively update `.gitignore`, and
|
|
72
|
-
* leave every existing file alone. Returns a report so the CLI (and tests)
|
|
73
|
-
* can render a summary without re-walking the filesystem.
|
|
74
|
-
*
|
|
75
|
-
* Not idempotent in the "produces the same output twice" sense — running init
|
|
76
|
-
* twice on a directory the consumer has edited must not change their files.
|
|
77
|
-
* That's the entire guarded-re-run contract. A second run into an empty
|
|
78
|
-
* directory *does* reproduce the first-run output.
|
|
79
|
-
*/
|
|
80
|
-
export declare function runInit(opts: RunInitOptions): InitReport;
|
|
81
|
-
/** Human-readable summary suitable for the CLI to stdout after init runs. */
|
|
82
|
-
export declare function formatInitReport(report: InitReport, targetDir: string): string;
|