prloom 0.1.2 → 0.1.4
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 +29 -10
- package/dist/cli/index.js +332 -60
- package/package.json +1 -1
- package/prompts/review_triage.md +54 -17
package/README.md
CHANGED
|
@@ -4,12 +4,12 @@
|
|
|
4
4
|
|
|
5
5
|
You write a plan (a Markdown checklist), `prloom` turns it into a dedicated git worktree + branch, opens a draft PR, and then iterates one TODO at a time using a configurable coding agent. Review happens in GitHub: comments and review submissions are triaged into new TODOs and pushed back onto the same PR.
|
|
6
6
|
|
|
7
|
-
`prloom` is designed to be safe to run from multiple clones: all runtime state lives in
|
|
7
|
+
`prloom` is designed to be safe to run from multiple clones: all runtime state lives in `prloom/.local/` (gitignored), so each developer can run their own dispatcher against the PRs they create/track in their local state.
|
|
8
8
|
|
|
9
9
|
## How It Works
|
|
10
10
|
|
|
11
|
-
- Plans start locally in
|
|
12
|
-
- The dispatcher ingests a plan into a new branch/worktree at `plans/<id>.md` and opens a draft PR.
|
|
11
|
+
- Plans start locally in `prloom/.local/inbox/` (gitignored; clean `git status`).
|
|
12
|
+
- The dispatcher ingests a plan into a new branch/worktree at `prloom/plans/<id>.md` and opens a draft PR.
|
|
13
13
|
- The worker agent executes exactly one TODO per iteration and updates the plan in-branch.
|
|
14
14
|
- PR comments/reviews trigger a triage agent which updates the plan with new TODOs and posts a reply.
|
|
15
15
|
- When all TODOs are complete, the PR is marked ready; you merge when satisfied.
|
|
@@ -88,13 +88,13 @@ bun run build
|
|
|
88
88
|
- `npx -y prloom new my-feature`
|
|
89
89
|
3. Start the dispatcher:
|
|
90
90
|
- `npx -y prloom start`
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
91
|
+
4. Review the draft PR in GitHub.
|
|
92
|
+
5. Leave PR comments or a review; `prloom` triages feedback into TODOs.
|
|
93
|
+
6. When the PR is ready, merge it.
|
|
94
94
|
|
|
95
95
|
## Configuration
|
|
96
96
|
|
|
97
|
-
Create `prloom
|
|
97
|
+
Create `prloom/config.json`:
|
|
98
98
|
|
|
99
99
|
```json
|
|
100
100
|
{
|
|
@@ -102,13 +102,32 @@ Create `prloom.config.json` in the repo root:
|
|
|
102
102
|
"default": "opencode",
|
|
103
103
|
"designer": "codex"
|
|
104
104
|
},
|
|
105
|
-
"worktrees_dir": "
|
|
105
|
+
"worktrees_dir": "prloom/.local/worktrees",
|
|
106
106
|
"poll_interval_ms": 60000,
|
|
107
107
|
"base_branch": "main"
|
|
108
108
|
}
|
|
109
109
|
```
|
|
110
110
|
|
|
111
|
+
## Repository Context
|
|
112
|
+
|
|
113
|
+
You can provide repository-specific context to agents by creating markdown files in the `prloom/` directory:
|
|
114
|
+
|
|
115
|
+
```
|
|
116
|
+
repo/
|
|
117
|
+
├── prloom/
|
|
118
|
+
│ ├── config.json # Configuration
|
|
119
|
+
│ ├── plans/ # Committed plans (on PR branches)
|
|
120
|
+
│ ├── planner.md # Appended to designer prompts
|
|
121
|
+
│ ├── worker.md # Appended to worker prompts
|
|
122
|
+
│ └── .local/ # Gitignored (runtime state)
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
- **`prloom/planner.md`**: Architecture info, coding conventions, design patterns
|
|
126
|
+
- **`prloom/worker.md`**: Build commands, test patterns, implementation guidelines
|
|
127
|
+
|
|
128
|
+
These files are appended to the respective agent prompts automatically.
|
|
129
|
+
|
|
111
130
|
## Notes
|
|
112
131
|
|
|
113
|
-
- Runtime state is stored under
|
|
114
|
-
- The plan file is committed on the PR branch at `plans/<id>.md` and lands on the configured `base_branch`
|
|
132
|
+
- Runtime state is stored under `prloom/.local/` (gitignored).
|
|
133
|
+
- The plan file is committed on the PR branch at `prloom/plans/<id>.md` and lands on the configured `base_branch` when you merge the PR.
|
package/dist/cli/index.js
CHANGED
|
@@ -7238,14 +7238,46 @@ var init_execa = __esm(() => {
|
|
|
7238
7238
|
} = getIpcExport());
|
|
7239
7239
|
});
|
|
7240
7240
|
|
|
7241
|
+
// src/lib/adapters/tmux.ts
|
|
7242
|
+
async function waitForTmuxSession(sessionName) {
|
|
7243
|
+
while (true) {
|
|
7244
|
+
const { exitCode } = await execa("tmux", ["has-session", "-t", sessionName], {
|
|
7245
|
+
reject: false
|
|
7246
|
+
});
|
|
7247
|
+
if (exitCode !== 0) {
|
|
7248
|
+
return;
|
|
7249
|
+
}
|
|
7250
|
+
await new Promise((resolve5) => setTimeout(resolve5, 1000));
|
|
7251
|
+
}
|
|
7252
|
+
}
|
|
7253
|
+
var init_tmux = __esm(() => {
|
|
7254
|
+
init_execa();
|
|
7255
|
+
});
|
|
7256
|
+
|
|
7241
7257
|
// src/lib/adapters/codex.ts
|
|
7242
7258
|
var codexAdapter;
|
|
7243
7259
|
var init_codex = __esm(() => {
|
|
7244
7260
|
init_execa();
|
|
7261
|
+
init_tmux();
|
|
7245
7262
|
codexAdapter = {
|
|
7246
7263
|
name: "codex",
|
|
7247
|
-
async execute({ cwd, prompt }) {
|
|
7248
|
-
const
|
|
7264
|
+
async execute({ cwd, prompt, tmux }) {
|
|
7265
|
+
const args = ["exec", prompt, "--full-auto"];
|
|
7266
|
+
if (tmux) {
|
|
7267
|
+
await execa("tmux", [
|
|
7268
|
+
"new-session",
|
|
7269
|
+
"-d",
|
|
7270
|
+
"-s",
|
|
7271
|
+
tmux.sessionName,
|
|
7272
|
+
"-c",
|
|
7273
|
+
cwd,
|
|
7274
|
+
"codex",
|
|
7275
|
+
...args
|
|
7276
|
+
], { reject: false });
|
|
7277
|
+
await waitForTmuxSession(tmux.sessionName);
|
|
7278
|
+
return { exitCode: 0 };
|
|
7279
|
+
}
|
|
7280
|
+
const result = await execa("codex", args, {
|
|
7249
7281
|
cwd,
|
|
7250
7282
|
timeout: 0,
|
|
7251
7283
|
reject: false
|
|
@@ -7266,9 +7298,25 @@ var init_codex = __esm(() => {
|
|
|
7266
7298
|
var opencodeAdapter;
|
|
7267
7299
|
var init_opencode = __esm(() => {
|
|
7268
7300
|
init_execa();
|
|
7301
|
+
init_tmux();
|
|
7269
7302
|
opencodeAdapter = {
|
|
7270
7303
|
name: "opencode",
|
|
7271
|
-
async execute({ cwd, prompt }) {
|
|
7304
|
+
async execute({ cwd, prompt, tmux }) {
|
|
7305
|
+
if (tmux) {
|
|
7306
|
+
await execa("tmux", [
|
|
7307
|
+
"new-session",
|
|
7308
|
+
"-d",
|
|
7309
|
+
"-s",
|
|
7310
|
+
tmux.sessionName,
|
|
7311
|
+
"-c",
|
|
7312
|
+
cwd,
|
|
7313
|
+
"opencode",
|
|
7314
|
+
"run",
|
|
7315
|
+
prompt
|
|
7316
|
+
], { reject: false });
|
|
7317
|
+
await waitForTmuxSession(tmux.sessionName);
|
|
7318
|
+
return { exitCode: 0 };
|
|
7319
|
+
}
|
|
7272
7320
|
const result = await execa("opencode", ["run", prompt], {
|
|
7273
7321
|
cwd,
|
|
7274
7322
|
timeout: 0,
|
|
@@ -7290,10 +7338,26 @@ var init_opencode = __esm(() => {
|
|
|
7290
7338
|
var claudeAdapter;
|
|
7291
7339
|
var init_claude = __esm(() => {
|
|
7292
7340
|
init_execa();
|
|
7341
|
+
init_tmux();
|
|
7293
7342
|
claudeAdapter = {
|
|
7294
7343
|
name: "claude",
|
|
7295
|
-
async execute({ cwd, prompt }) {
|
|
7296
|
-
const
|
|
7344
|
+
async execute({ cwd, prompt, tmux }) {
|
|
7345
|
+
const args = ["-p", prompt, "--dangerously-skip-permissions"];
|
|
7346
|
+
if (tmux) {
|
|
7347
|
+
await execa("tmux", [
|
|
7348
|
+
"new-session",
|
|
7349
|
+
"-d",
|
|
7350
|
+
"-s",
|
|
7351
|
+
tmux.sessionName,
|
|
7352
|
+
"-c",
|
|
7353
|
+
cwd,
|
|
7354
|
+
"claude",
|
|
7355
|
+
...args
|
|
7356
|
+
], { reject: false });
|
|
7357
|
+
await waitForTmuxSession(tmux.sessionName);
|
|
7358
|
+
return { exitCode: 0 };
|
|
7359
|
+
}
|
|
7360
|
+
const result = await execa("claude", args, {
|
|
7297
7361
|
cwd,
|
|
7298
7362
|
timeout: 0,
|
|
7299
7363
|
reject: false
|
|
@@ -7314,7 +7378,7 @@ var manualAdapter;
|
|
|
7314
7378
|
var init_manual = __esm(() => {
|
|
7315
7379
|
manualAdapter = {
|
|
7316
7380
|
name: "manual",
|
|
7317
|
-
async execute({ cwd, prompt }) {
|
|
7381
|
+
async execute({ cwd, prompt, tmux }) {
|
|
7318
7382
|
console.log("⚠️ Manual agent: execute() called but should be skipped by dispatcher.");
|
|
7319
7383
|
console.log(" This plan is intended for IDE-driven execution.");
|
|
7320
7384
|
return { exitCode: 0 };
|
|
@@ -7357,7 +7421,7 @@ var init_adapters = __esm(() => {
|
|
|
7357
7421
|
import { join as join2, resolve as resolve5 } from "path";
|
|
7358
7422
|
import { existsSync, readFileSync as readFileSync6 } from "fs";
|
|
7359
7423
|
function loadConfig(repoRoot) {
|
|
7360
|
-
const configPath = join2(repoRoot, "prloom
|
|
7424
|
+
const configPath = join2(repoRoot, "prloom", "config.json");
|
|
7361
7425
|
if (!existsSync(configPath)) {
|
|
7362
7426
|
return { ...DEFAULTS };
|
|
7363
7427
|
}
|
|
@@ -7394,7 +7458,7 @@ var init_config = __esm(() => {
|
|
|
7394
7458
|
agents: {
|
|
7395
7459
|
default: "opencode"
|
|
7396
7460
|
},
|
|
7397
|
-
worktrees_dir: "
|
|
7461
|
+
worktrees_dir: "prloom/.local/worktrees",
|
|
7398
7462
|
poll_interval_ms: 60000,
|
|
7399
7463
|
base_branch: "main"
|
|
7400
7464
|
};
|
|
@@ -7415,7 +7479,9 @@ async function runInit(cwd, opts = {}) {
|
|
|
7415
7479
|
await ensureGhAuthed();
|
|
7416
7480
|
await ensureGhRepoResolvable(repoRoot);
|
|
7417
7481
|
const defaultBranch = await detectDefaultBranch(repoRoot);
|
|
7418
|
-
const configPath = join3(repoRoot, "prloom
|
|
7482
|
+
const configPath = join3(repoRoot, "prloom", "config.json");
|
|
7483
|
+
const prloomDir = join3(repoRoot, "prloom");
|
|
7484
|
+
mkdirSync(prloomDir, { recursive: true });
|
|
7419
7485
|
if (!existsSync2(configPath) || opts.force) {
|
|
7420
7486
|
const existing = loadConfig(repoRoot);
|
|
7421
7487
|
const config = {
|
|
@@ -7430,12 +7496,14 @@ async function runInit(cwd, opts = {}) {
|
|
|
7430
7496
|
} else {
|
|
7431
7497
|
console.log(`Found existing ${configPath} (leaving unchanged)`);
|
|
7432
7498
|
}
|
|
7433
|
-
const
|
|
7434
|
-
const inboxDir = join3(
|
|
7435
|
-
const
|
|
7499
|
+
const localDir = join3(prloomDir, ".local");
|
|
7500
|
+
const inboxDir = join3(localDir, "inbox");
|
|
7501
|
+
const plansStateDir = join3(localDir, "plans");
|
|
7436
7502
|
mkdirSync(inboxDir, { recursive: true });
|
|
7503
|
+
mkdirSync(plansStateDir, { recursive: true });
|
|
7504
|
+
const plansDir = join3(prloomDir, "plans");
|
|
7437
7505
|
mkdirSync(plansDir, { recursive: true });
|
|
7438
|
-
await ensureGitignoreEntry(repoRoot, "
|
|
7506
|
+
await ensureGitignoreEntry(repoRoot, "prloom/.local/");
|
|
7439
7507
|
console.log("✅ prloom initialized");
|
|
7440
7508
|
console.log(`Base branch: ${defaultBranch}`);
|
|
7441
7509
|
if (!opts.yes) {
|
|
@@ -11138,7 +11206,7 @@ ${plan.progressLog}`;
|
|
|
11138
11206
|
function generatePlanSkeleton(id, agent, baseBranch) {
|
|
11139
11207
|
const frontmatter = {
|
|
11140
11208
|
id,
|
|
11141
|
-
status: "
|
|
11209
|
+
status: "draft"
|
|
11142
11210
|
};
|
|
11143
11211
|
if (agent) {
|
|
11144
11212
|
frontmatter.agent = agent;
|
|
@@ -16834,11 +16902,10 @@ You are implementing exactly ONE task from this plan.
|
|
|
16834
16902
|
|
|
16835
16903
|
You are processing PR feedback for an active plan. Your job is to:
|
|
16836
16904
|
|
|
16837
|
-
1.
|
|
16838
|
-
2.
|
|
16839
|
-
3.
|
|
16905
|
+
1. **Classify** each feedback item by type
|
|
16906
|
+
2. **Respond** appropriately based on the type
|
|
16907
|
+
3. **Update** the plan if changes are needed
|
|
16840
16908
|
4. Detect if a rebase was requested
|
|
16841
|
-
5. Compose a reply message for the reviewer(s)
|
|
16842
16909
|
|
|
16843
16910
|
## Feedback to Process
|
|
16844
16911
|
|
|
@@ -16848,9 +16915,36 @@ You are processing PR feedback for an active plan. Your job is to:
|
|
|
16848
16915
|
|
|
16849
16916
|
{{plan}}
|
|
16850
16917
|
|
|
16851
|
-
|
|
16918
|
+
---
|
|
16919
|
+
|
|
16920
|
+
## Step 1: Classify Each Feedback Item
|
|
16921
|
+
|
|
16922
|
+
Before acting, identify the type of each feedback item:
|
|
16923
|
+
|
|
16924
|
+
| Type | Description | Action |
|
|
16925
|
+
| ------------------- | --------------------------------------------- | ------------------------ |
|
|
16926
|
+
| **Question** | Asking "why", "how", or seeking clarification | Answer in reply, NO TODO |
|
|
16927
|
+
| **Change Request** | Asking for code modifications | Create specific TODO |
|
|
16928
|
+
| **Approval/Praise** | Positive feedback, LGTM, approval | Acknowledge briefly |
|
|
16929
|
+
| **Process Request** | Rebase, update branch, etc. | Set flag, acknowledge |
|
|
16930
|
+
|
|
16931
|
+
## Step 2: Handle Each Type
|
|
16932
|
+
|
|
16933
|
+
### Questions → Answer Directly
|
|
16934
|
+
|
|
16935
|
+
For questions, **answer them substantively in your reply**:
|
|
16936
|
+
|
|
16937
|
+
- Explore the codebase to find the answer if needed
|
|
16938
|
+
- Explain the reasoning behind implementation decisions
|
|
16939
|
+
- Reference specific code locations or commits if helpful
|
|
16940
|
+
- Do NOT create a TODO like "answer the question" - just answer it
|
|
16852
16941
|
|
|
16853
|
-
|
|
16942
|
+
Example question: "Why did you use a polling approach instead of webhooks?"
|
|
16943
|
+
→ Reply with the actual reasoning, don't create a TODO.
|
|
16944
|
+
|
|
16945
|
+
### Change Requests → Create TODOs
|
|
16946
|
+
|
|
16947
|
+
For explicit requests to modify code:
|
|
16854
16948
|
|
|
16855
16949
|
- Edit the plan file directly to add actionable TODOs
|
|
16856
16950
|
- Add them at the end of the ## TODO section
|
|
@@ -16860,13 +16954,23 @@ You are processing PR feedback for an active plan. Your job is to:
|
|
|
16860
16954
|
- "Update function X to handle null input"
|
|
16861
16955
|
- "Add test for Y edge case"
|
|
16862
16956
|
- "Rename Z for clarity"
|
|
16863
|
-
|
|
16864
|
-
- Group related comments into single tasks when logical
|
|
16957
|
+
- Group related requests into single tasks when logical
|
|
16865
16958
|
- If the plan status is \`done\` and you add TODOs, change status to \`active\`
|
|
16866
16959
|
|
|
16867
|
-
###
|
|
16960
|
+
### Approval/Praise → Acknowledge
|
|
16961
|
+
|
|
16962
|
+
Simply thank the reviewer in your reply. No TODO needed.
|
|
16963
|
+
|
|
16964
|
+
### Process Requests → Set Flag
|
|
16965
|
+
|
|
16966
|
+
If any comment mentions rebase, update branch, or similar:
|
|
16967
|
+
|
|
16968
|
+
- Set \`rebase_requested: true\` in the result file
|
|
16969
|
+
- Acknowledge in your reply
|
|
16868
16970
|
|
|
16869
|
-
|
|
16971
|
+
## Step 3: Write the Result File
|
|
16972
|
+
|
|
16973
|
+
After processing, you MUST write \`prloom/.local/triage-result.json\`:
|
|
16870
16974
|
|
|
16871
16975
|
\`\`\`json
|
|
16872
16976
|
{
|
|
@@ -16877,22 +16981,23 @@ After editing the plan, you MUST write \`.prloom/triage-result.json\` with:
|
|
|
16877
16981
|
|
|
16878
16982
|
**Required fields:**
|
|
16879
16983
|
|
|
16880
|
-
- \`reply_markdown\`: Your response to post on the PR (ALWAYS required
|
|
16881
|
-
- \`rebase_requested\`: Set to \`true\` if
|
|
16984
|
+
- \`reply_markdown\`: Your response to post on the PR (ALWAYS required)
|
|
16985
|
+
- \`rebase_requested\`: Set to \`true\` if rebase was requested
|
|
16882
16986
|
|
|
16883
16987
|
### Reply Guidelines
|
|
16884
16988
|
|
|
16885
16989
|
- Be polite and professional
|
|
16886
|
-
-
|
|
16887
|
-
- Explain what TODOs were created
|
|
16888
|
-
- If
|
|
16889
|
-
- Keep it concise
|
|
16990
|
+
- **Answer questions directly** - this is the most important part
|
|
16991
|
+
- Explain what TODOs were created (if any)
|
|
16992
|
+
- If a request doesn't require changes, explain why
|
|
16993
|
+
- Keep it concise but complete
|
|
16890
16994
|
|
|
16891
16995
|
## Critical Rules
|
|
16892
16996
|
|
|
16893
|
-
1. You MUST write
|
|
16997
|
+
1. You MUST write \`prloom/.local/triage-result.json\` even if you add no TODOs
|
|
16894
16998
|
2. The result file must contain valid JSON only, no markdown wrapper
|
|
16895
16999
|
3. Failure to write the result file will mark the plan as blocked
|
|
17000
|
+
4. **Questions should be answered, not converted to TODOs**
|
|
16896
17001
|
`
|
|
16897
17002
|
};
|
|
16898
17003
|
});
|
|
@@ -16903,32 +17008,72 @@ import { join as join4 } from "path";
|
|
|
16903
17008
|
function loadTemplate(_repoRoot, name) {
|
|
16904
17009
|
return BUILTIN_PROMPTS[name];
|
|
16905
17010
|
}
|
|
17011
|
+
function loadAgentContext(repoRoot, agentType) {
|
|
17012
|
+
const contextPath = join4(repoRoot, "prloom", `${agentType}.md`);
|
|
17013
|
+
if (!existsSync3(contextPath)) {
|
|
17014
|
+
return "";
|
|
17015
|
+
}
|
|
17016
|
+
return readFileSync9(contextPath, "utf-8");
|
|
17017
|
+
}
|
|
16906
17018
|
function renderWorkerPrompt(repoRoot, plan, todo) {
|
|
16907
17019
|
const template = loadTemplate(repoRoot, "worker");
|
|
16908
17020
|
const compiled = import_handlebars.default.compile(template);
|
|
16909
|
-
|
|
17021
|
+
let prompt = compiled({
|
|
16910
17022
|
current_todo: `TODO #${todo.index}: ${todo.text}`,
|
|
16911
17023
|
plan: plan.raw
|
|
16912
17024
|
});
|
|
17025
|
+
const context = loadAgentContext(repoRoot, "worker");
|
|
17026
|
+
if (context) {
|
|
17027
|
+
prompt += `
|
|
17028
|
+
|
|
17029
|
+
---
|
|
17030
|
+
|
|
17031
|
+
# Repository Context
|
|
17032
|
+
|
|
17033
|
+
${context}`;
|
|
17034
|
+
}
|
|
17035
|
+
return prompt;
|
|
16913
17036
|
}
|
|
16914
17037
|
function renderDesignerNewPrompt(repoPath, planPath, baseBranch, workerAgent, userDescription) {
|
|
16915
17038
|
const template = BUILTIN_PROMPTS["designer_new"];
|
|
16916
17039
|
const compiled = import_handlebars.default.compile(template);
|
|
16917
|
-
|
|
17040
|
+
let prompt = compiled({
|
|
16918
17041
|
repo_path: repoPath,
|
|
16919
17042
|
plan_path: planPath,
|
|
16920
17043
|
base_branch: baseBranch,
|
|
16921
17044
|
worker_agent: workerAgent,
|
|
16922
17045
|
user_description: userDescription ?? ""
|
|
16923
17046
|
});
|
|
17047
|
+
const context = loadAgentContext(repoPath, "planner");
|
|
17048
|
+
if (context) {
|
|
17049
|
+
prompt += `
|
|
17050
|
+
|
|
17051
|
+
---
|
|
17052
|
+
|
|
17053
|
+
# Repository Context
|
|
17054
|
+
|
|
17055
|
+
${context}`;
|
|
17056
|
+
}
|
|
17057
|
+
return prompt;
|
|
16924
17058
|
}
|
|
16925
|
-
function renderDesignerEditPrompt(planPath, existingPlan) {
|
|
17059
|
+
function renderDesignerEditPrompt(repoPath, planPath, existingPlan) {
|
|
16926
17060
|
const template = BUILTIN_PROMPTS["designer_edit"];
|
|
16927
17061
|
const compiled = import_handlebars.default.compile(template);
|
|
16928
|
-
|
|
17062
|
+
let prompt = compiled({
|
|
16929
17063
|
plan_path: planPath,
|
|
16930
17064
|
existing_plan: existingPlan
|
|
16931
17065
|
});
|
|
17066
|
+
const context = loadAgentContext(repoPath, "planner");
|
|
17067
|
+
if (context) {
|
|
17068
|
+
prompt += `
|
|
17069
|
+
|
|
17070
|
+
---
|
|
17071
|
+
|
|
17072
|
+
# Repository Context
|
|
17073
|
+
|
|
17074
|
+
${context}`;
|
|
17075
|
+
}
|
|
17076
|
+
return prompt;
|
|
16932
17077
|
}
|
|
16933
17078
|
function renderTriagePrompt(repoRoot, plan, feedback) {
|
|
16934
17079
|
const template = loadTemplate(repoRoot, "review_triage");
|
|
@@ -16944,6 +17089,10 @@ ${f.body}`;
|
|
|
16944
17089
|
entry += `
|
|
16945
17090
|
|
|
16946
17091
|
*Review: ${f.reviewState}*`;
|
|
17092
|
+
if (f.inReplyToId)
|
|
17093
|
+
entry += `
|
|
17094
|
+
|
|
17095
|
+
*In reply to comment #${f.inReplyToId}*`;
|
|
16947
17096
|
return entry;
|
|
16948
17097
|
}).join(`
|
|
16949
17098
|
|
|
@@ -16975,7 +17124,7 @@ function readTriageResultFile(worktreePath) {
|
|
|
16975
17124
|
rebase_requested: result.rebase_requested
|
|
16976
17125
|
};
|
|
16977
17126
|
}
|
|
16978
|
-
var import_handlebars, TRIAGE_RESULT_FILE = "
|
|
17127
|
+
var import_handlebars, TRIAGE_RESULT_FILE = "prloom/.local/triage-result.json";
|
|
16979
17128
|
var init_template2 = __esm(() => {
|
|
16980
17129
|
init_prompt_sources();
|
|
16981
17130
|
import_handlebars = __toESM(require_lib(), 1);
|
|
@@ -17110,7 +17259,7 @@ function deleteInboxPlan(repoRoot, planId) {
|
|
|
17110
17259
|
unlinkSync2(inboxPath);
|
|
17111
17260
|
}
|
|
17112
17261
|
}
|
|
17113
|
-
var SWARM_DIR = "
|
|
17262
|
+
var SWARM_DIR = "prloom/.local", STATE_FILE = "state.json", LOCK_FILE = "lock", PLANS_DIR = "plans", INBOX_DIR = "inbox";
|
|
17114
17263
|
var init_state = () => {};
|
|
17115
17264
|
|
|
17116
17265
|
// node_modules/nanoid/url-alphabet/index.js
|
|
@@ -17237,7 +17386,7 @@ function copyFileToWorktree(srcPath, worktreePath, destRelPath) {
|
|
|
17237
17386
|
copyFileSync(srcPath, destPath);
|
|
17238
17387
|
}
|
|
17239
17388
|
function ensureWorktreePrloomDir(worktreePath) {
|
|
17240
|
-
const prloomDir = join6(worktreePath, "
|
|
17389
|
+
const prloomDir = join6(worktreePath, "prloom", ".local");
|
|
17241
17390
|
if (!existsSync5(prloomDir)) {
|
|
17242
17391
|
mkdirSync3(prloomDir, { recursive: true });
|
|
17243
17392
|
}
|
|
@@ -17247,6 +17396,23 @@ var init_git = __esm(() => {
|
|
|
17247
17396
|
init_nanoid();
|
|
17248
17397
|
});
|
|
17249
17398
|
|
|
17399
|
+
// src/cli/prompt.ts
|
|
17400
|
+
import * as readline from "readline";
|
|
17401
|
+
function confirm(message) {
|
|
17402
|
+
const rl = readline.createInterface({
|
|
17403
|
+
input: process.stdin,
|
|
17404
|
+
output: process.stdout
|
|
17405
|
+
});
|
|
17406
|
+
return new Promise((resolve6) => {
|
|
17407
|
+
rl.question(`${message} (y/N) `, (answer) => {
|
|
17408
|
+
rl.close();
|
|
17409
|
+
const normalized = answer.trim().toLowerCase();
|
|
17410
|
+
resolve6(normalized === "y" || normalized === "yes");
|
|
17411
|
+
});
|
|
17412
|
+
});
|
|
17413
|
+
}
|
|
17414
|
+
var init_prompt = () => {};
|
|
17415
|
+
|
|
17250
17416
|
// src/cli/new.ts
|
|
17251
17417
|
var exports_new = {};
|
|
17252
17418
|
__export(exports_new, {
|
|
@@ -17284,8 +17450,8 @@ async function runNew(repoRoot, planId, agentOverride, noDesigner) {
|
|
|
17284
17450
|
console.log(`Worker agent: ${workerAgent}`);
|
|
17285
17451
|
if (noDesigner) {
|
|
17286
17452
|
console.log("");
|
|
17287
|
-
console.log("Plan skeleton created
|
|
17288
|
-
console.log("
|
|
17453
|
+
console.log("Plan skeleton created (status: draft).");
|
|
17454
|
+
console.log("Use 'prloom edit' to design, then queue for dispatch.");
|
|
17289
17455
|
return;
|
|
17290
17456
|
}
|
|
17291
17457
|
const adapter = getAdapter(designerAgent);
|
|
@@ -17294,8 +17460,15 @@ async function runNew(repoRoot, planId, agentOverride, noDesigner) {
|
|
|
17294
17460
|
console.log("Starting Designer session to fill in the plan...");
|
|
17295
17461
|
const prompt = renderDesignerNewPrompt(repoRoot, planPath, baseBranch, workerAgent);
|
|
17296
17462
|
await adapter.interactive({ cwd: repoRoot, prompt });
|
|
17463
|
+
console.log("");
|
|
17297
17464
|
console.log("Designer session ended.");
|
|
17298
|
-
|
|
17465
|
+
const shouldQueue = await confirm("Queue this plan for the dispatcher?");
|
|
17466
|
+
if (shouldQueue) {
|
|
17467
|
+
setStatus(planPath, "queued");
|
|
17468
|
+
console.log("Plan queued. Run 'prloom start' to dispatch.");
|
|
17469
|
+
} else {
|
|
17470
|
+
console.log("Plan left as draft. Use 'prloom edit' to continue later.");
|
|
17471
|
+
}
|
|
17299
17472
|
}
|
|
17300
17473
|
var init_new = __esm(() => {
|
|
17301
17474
|
init_config();
|
|
@@ -17304,6 +17477,7 @@ var init_new = __esm(() => {
|
|
|
17304
17477
|
init_template2();
|
|
17305
17478
|
init_state();
|
|
17306
17479
|
init_git();
|
|
17480
|
+
init_prompt();
|
|
17307
17481
|
});
|
|
17308
17482
|
|
|
17309
17483
|
// src/cli/edit.ts
|
|
@@ -17319,9 +17493,11 @@ async function runEdit(repoRoot, planId, agentOverride, noDesigner) {
|
|
|
17319
17493
|
const inboxPath = getInboxPath(repoRoot, planId);
|
|
17320
17494
|
let planPath;
|
|
17321
17495
|
let cwd;
|
|
17496
|
+
let isInbox = false;
|
|
17322
17497
|
if (existsSync7(inboxPath)) {
|
|
17323
17498
|
planPath = inboxPath;
|
|
17324
17499
|
cwd = repoRoot;
|
|
17500
|
+
isInbox = true;
|
|
17325
17501
|
console.log(`Editing inbox plan: ${planId}`);
|
|
17326
17502
|
} else {
|
|
17327
17503
|
const ps = state.plans[planId];
|
|
@@ -17349,15 +17525,30 @@ async function runEdit(repoRoot, planId, agentOverride, noDesigner) {
|
|
|
17349
17525
|
const agentName = agentOverride ?? config.agents.designer ?? config.agents.default;
|
|
17350
17526
|
const adapter = getAdapter(agentName);
|
|
17351
17527
|
console.log(`Agent: ${agentName}`);
|
|
17352
|
-
const prompt = renderDesignerEditPrompt(planPath, existingPlan);
|
|
17528
|
+
const prompt = renderDesignerEditPrompt(cwd, planPath, existingPlan);
|
|
17353
17529
|
await adapter.interactive({ cwd, prompt });
|
|
17530
|
+
console.log("");
|
|
17354
17531
|
console.log("Designer session ended.");
|
|
17532
|
+
if (isInbox) {
|
|
17533
|
+
const plan = parsePlan(planPath);
|
|
17534
|
+
if (plan.frontmatter.status === "draft") {
|
|
17535
|
+
const shouldQueue = await confirm("Queue this plan for the dispatcher?");
|
|
17536
|
+
if (shouldQueue) {
|
|
17537
|
+
setStatus(planPath, "queued");
|
|
17538
|
+
console.log("Plan queued. Run 'prloom start' to dispatch.");
|
|
17539
|
+
} else {
|
|
17540
|
+
console.log("Plan left as draft.");
|
|
17541
|
+
}
|
|
17542
|
+
}
|
|
17543
|
+
}
|
|
17355
17544
|
}
|
|
17356
17545
|
var init_edit = __esm(() => {
|
|
17357
17546
|
init_config();
|
|
17358
17547
|
init_adapters();
|
|
17359
17548
|
init_template2();
|
|
17360
17549
|
init_state();
|
|
17550
|
+
init_plan();
|
|
17551
|
+
init_prompt();
|
|
17361
17552
|
});
|
|
17362
17553
|
|
|
17363
17554
|
// src/lib/ipc.ts
|
|
@@ -17375,10 +17566,10 @@ import {
|
|
|
17375
17566
|
mkdirSync as mkdirSync4
|
|
17376
17567
|
} from "fs";
|
|
17377
17568
|
function getControlPath(repoRoot) {
|
|
17378
|
-
return join8(repoRoot, "
|
|
17569
|
+
return join8(repoRoot, "prloom", ".local", CONTROL_FILE);
|
|
17379
17570
|
}
|
|
17380
17571
|
function enqueue(repoRoot, cmd) {
|
|
17381
|
-
const prloomDir = join8(repoRoot, "
|
|
17572
|
+
const prloomDir = join8(repoRoot, "prloom", ".local");
|
|
17382
17573
|
if (!existsSync8(prloomDir)) {
|
|
17383
17574
|
mkdirSync4(prloomDir, { recursive: true });
|
|
17384
17575
|
}
|
|
@@ -17508,7 +17699,7 @@ async function getPRReviewComments(repoRoot, prNumber) {
|
|
|
17508
17699
|
"api",
|
|
17509
17700
|
`repos/{owner}/{repo}/pulls/${prNumber}/comments`,
|
|
17510
17701
|
"--jq",
|
|
17511
|
-
".[] | {id: .id, author: .user.login, body: .body, path: .path, line: .line, createdAt: .created_at}"
|
|
17702
|
+
".[] | {id: .id, author: .user.login, body: .body, path: .path, line: .line, createdAt: .created_at, inReplyToId: .in_reply_to_id}"
|
|
17512
17703
|
], { cwd: repoRoot });
|
|
17513
17704
|
if (!stdout.trim())
|
|
17514
17705
|
return [];
|
|
@@ -17522,7 +17713,8 @@ async function getPRReviewComments(repoRoot, prNumber) {
|
|
|
17522
17713
|
body: obj.body,
|
|
17523
17714
|
path: obj.path,
|
|
17524
17715
|
line: obj.line,
|
|
17525
|
-
createdAt: obj.createdAt
|
|
17716
|
+
createdAt: obj.createdAt,
|
|
17717
|
+
inReplyToId: obj.inReplyToId || undefined
|
|
17526
17718
|
};
|
|
17527
17719
|
});
|
|
17528
17720
|
}
|
|
@@ -17582,7 +17774,7 @@ __export(exports_dispatcher, {
|
|
|
17582
17774
|
});
|
|
17583
17775
|
import { join as join9 } from "path";
|
|
17584
17776
|
import { existsSync as existsSync9, statSync as statSync5 } from "fs";
|
|
17585
|
-
async function runDispatcher(repoRoot) {
|
|
17777
|
+
async function runDispatcher(repoRoot, options2 = {}) {
|
|
17586
17778
|
const config = loadConfig(repoRoot);
|
|
17587
17779
|
const worktreesDir = resolveWorktreesDir(repoRoot, config);
|
|
17588
17780
|
acquireLock(repoRoot);
|
|
@@ -17610,7 +17802,7 @@ async function runDispatcher(repoRoot) {
|
|
|
17610
17802
|
await handleCommand2(state, cmd);
|
|
17611
17803
|
}
|
|
17612
17804
|
await ingestInboxPlans(repoRoot, worktreesDir, config, state);
|
|
17613
|
-
await processActivePlans(repoRoot, config, state, botLogin);
|
|
17805
|
+
await processActivePlans(repoRoot, config, state, botLogin, options2);
|
|
17614
17806
|
saveState(repoRoot, state);
|
|
17615
17807
|
await sleepUntilIpcOrTimeout(repoRoot, state.control_cursor, config.poll_interval_ms);
|
|
17616
17808
|
} catch (error) {
|
|
@@ -17629,11 +17821,14 @@ async function ingestInboxPlans(repoRoot, worktreesDir, config, state) {
|
|
|
17629
17821
|
console.error(`ID mismatch: file ${planId}.md has id: ${plan.frontmatter.id}, skipping`);
|
|
17630
17822
|
continue;
|
|
17631
17823
|
}
|
|
17824
|
+
if (plan.frontmatter.status === "draft") {
|
|
17825
|
+
continue;
|
|
17826
|
+
}
|
|
17632
17827
|
console.log(`\uD83D\uDCE5 Ingesting inbox plan: ${planId}`);
|
|
17633
17828
|
const baseBranch = plan.frontmatter.base_branch ?? config.base_branch;
|
|
17634
17829
|
const branch = await createBranchName(planId);
|
|
17635
17830
|
const worktreePath = await createWorktree(repoRoot, worktreesDir, branch, baseBranch);
|
|
17636
|
-
const planRelpath = `plans/${planId}.md`;
|
|
17831
|
+
const planRelpath = `prloom/plans/${planId}.md`;
|
|
17637
17832
|
copyFileToWorktree(inboxPath, worktreePath, planRelpath);
|
|
17638
17833
|
const worktreePlanPath = join9(worktreePath, planRelpath);
|
|
17639
17834
|
setStatus(worktreePlanPath, "active");
|
|
@@ -17667,7 +17862,7 @@ function getFeedbackPollDecision(opts) {
|
|
|
17667
17862
|
shouldUpdateLastPolledAt: !pollOnce && shouldPoll
|
|
17668
17863
|
};
|
|
17669
17864
|
}
|
|
17670
|
-
async function processActivePlans(repoRoot, config, state, botLogin) {
|
|
17865
|
+
async function processActivePlans(repoRoot, config, state, botLogin, options2 = {}) {
|
|
17671
17866
|
for (const [planId, ps] of Object.entries(state.plans)) {
|
|
17672
17867
|
try {
|
|
17673
17868
|
const planPath = join9(ps.worktree, ps.planRelpath);
|
|
@@ -17702,7 +17897,7 @@ async function processActivePlans(repoRoot, config, state, botLogin) {
|
|
|
17702
17897
|
if (newFeedback.length > 0) {
|
|
17703
17898
|
console.log(`\uD83D\uDCAC ${newFeedback.length} new feedback for ${planId}`);
|
|
17704
17899
|
if (!isManualAgent) {
|
|
17705
|
-
await runTriage(repoRoot, config, ps, plan, newFeedback);
|
|
17900
|
+
await runTriage(repoRoot, config, ps, plan, newFeedback, options2);
|
|
17706
17901
|
}
|
|
17707
17902
|
plan = parsePlan(planPath);
|
|
17708
17903
|
const maxIds = getMaxFeedbackIds(newFeedback);
|
|
@@ -17725,7 +17920,15 @@ async function processActivePlans(repoRoot, config, state, botLogin) {
|
|
|
17725
17920
|
const prompt = renderWorkerPrompt(repoRoot, plan, todo);
|
|
17726
17921
|
const agentName = plan.frontmatter.agent ?? config.agents.default;
|
|
17727
17922
|
const adapter = getAdapter(agentName);
|
|
17728
|
-
|
|
17923
|
+
const tmuxConfig = options2.tmux ? { sessionName: `prloom-${planId}` } : undefined;
|
|
17924
|
+
if (tmuxConfig) {
|
|
17925
|
+
ps.tmuxSession = tmuxConfig.sessionName;
|
|
17926
|
+
console.log(` [spawned in tmux session: ${tmuxConfig.sessionName}]`);
|
|
17927
|
+
}
|
|
17928
|
+
await adapter.execute({ cwd: ps.worktree, prompt, tmux: tmuxConfig });
|
|
17929
|
+
if (tmuxConfig) {
|
|
17930
|
+
ps.tmuxSession = undefined;
|
|
17931
|
+
}
|
|
17729
17932
|
const committed = await commitAll(ps.worktree, `[prloom] ${planId}: TODO #${todo.index}`);
|
|
17730
17933
|
if (committed) {
|
|
17731
17934
|
await push(ps.worktree, ps.branch);
|
|
@@ -17773,13 +17976,14 @@ async function pollNewFeedback(repoRoot, ps, botLogin) {
|
|
|
17773
17976
|
};
|
|
17774
17977
|
return filterNewFeedback(allFeedback, cursors, botLogin);
|
|
17775
17978
|
}
|
|
17776
|
-
async function runTriage(repoRoot, config, ps, plan, feedback) {
|
|
17979
|
+
async function runTriage(repoRoot, config, ps, plan, feedback, options2 = {}) {
|
|
17777
17980
|
ensureWorktreePrloomDir(ps.worktree);
|
|
17778
17981
|
const triageAgent = config.agents.designer ?? config.agents.default;
|
|
17779
17982
|
const adapter = getAdapter(triageAgent);
|
|
17780
17983
|
const prompt = renderTriagePrompt(repoRoot, plan, feedback);
|
|
17781
17984
|
console.log(`\uD83D\uDD0D Running triage for ${plan.frontmatter.id}...`);
|
|
17782
|
-
|
|
17985
|
+
const tmuxConfig = options2.tmux ? { sessionName: `prloom-triage-${plan.frontmatter.id}` } : undefined;
|
|
17986
|
+
await adapter.execute({ cwd: ps.worktree, prompt, tmux: tmuxConfig });
|
|
17783
17987
|
try {
|
|
17784
17988
|
const result = readTriageResultFile(ps.worktree);
|
|
17785
17989
|
if (result.rebase_requested) {
|
|
@@ -17789,12 +17993,44 @@ async function runTriage(repoRoot, config, ps, plan, feedback) {
|
|
|
17789
17993
|
const planPath = join9(ps.worktree, ps.planRelpath);
|
|
17790
17994
|
setStatus(planPath, "blocked");
|
|
17791
17995
|
ps.lastError = `Rebase conflict: ${rebaseResult.conflictFiles?.join(", ")}`;
|
|
17792
|
-
await postPRComment(repoRoot, ps.pr, `⚠️ Rebase conflict detected
|
|
17996
|
+
await postPRComment(repoRoot, ps.pr, `⚠️ **Rebase conflict detected**
|
|
17997
|
+
|
|
17998
|
+
The following files have conflicts:
|
|
17793
17999
|
\`\`\`
|
|
17794
18000
|
${rebaseResult.conflictFiles?.join(`
|
|
17795
18001
|
`)}
|
|
17796
18002
|
\`\`\`
|
|
17797
|
-
|
|
18003
|
+
|
|
18004
|
+
**To resolve:**
|
|
18005
|
+
|
|
18006
|
+
1. Navigate to the worktree:
|
|
18007
|
+
\`\`\`
|
|
18008
|
+
cd ${ps.worktree}
|
|
18009
|
+
\`\`\`
|
|
18010
|
+
|
|
18011
|
+
2. Fetch and rebase manually:
|
|
18012
|
+
\`\`\`
|
|
18013
|
+
git fetch origin ${ps.baseBranch}
|
|
18014
|
+
git rebase origin/${ps.baseBranch}
|
|
18015
|
+
\`\`\`
|
|
18016
|
+
|
|
18017
|
+
3. Resolve conflicts in your editor, then:
|
|
18018
|
+
\`\`\`
|
|
18019
|
+
git add .
|
|
18020
|
+
git rebase --continue
|
|
18021
|
+
\`\`\`
|
|
18022
|
+
|
|
18023
|
+
4. Force push the resolved branch:
|
|
18024
|
+
\`\`\`
|
|
18025
|
+
git push --force-with-lease
|
|
18026
|
+
\`\`\`
|
|
18027
|
+
|
|
18028
|
+
5. Unblock the plan:
|
|
18029
|
+
\`\`\`
|
|
18030
|
+
prloom unpause ${plan.frontmatter.id}
|
|
18031
|
+
\`\`\`
|
|
18032
|
+
|
|
18033
|
+
The plan is now **blocked** until conflicts are resolved.`);
|
|
17798
18034
|
} else if (rebaseResult.success) {
|
|
17799
18035
|
await forcePush(ps.worktree, ps.branch);
|
|
17800
18036
|
console.log(` Rebased and force-pushed`);
|
|
@@ -17993,6 +18229,35 @@ var init_open = __esm(() => {
|
|
|
17993
18229
|
init_adapters();
|
|
17994
18230
|
});
|
|
17995
18231
|
|
|
18232
|
+
// src/cli/watch.ts
|
|
18233
|
+
var exports_watch = {};
|
|
18234
|
+
__export(exports_watch, {
|
|
18235
|
+
runWatch: () => runWatch
|
|
18236
|
+
});
|
|
18237
|
+
async function runWatch(repoRoot, planId) {
|
|
18238
|
+
const state = loadState(repoRoot);
|
|
18239
|
+
const ps = state.plans[planId];
|
|
18240
|
+
if (!ps) {
|
|
18241
|
+
console.error(`Plan not found in state: ${planId}`);
|
|
18242
|
+
console.error("Make sure the plan has been dispatched at least once.");
|
|
18243
|
+
process.exit(1);
|
|
18244
|
+
}
|
|
18245
|
+
if (!ps.tmuxSession) {
|
|
18246
|
+
console.error(`No active tmux session for ${planId}`);
|
|
18247
|
+
console.error("This plan may not be running, or dispatcher wasn't started with --tmux");
|
|
18248
|
+
process.exit(1);
|
|
18249
|
+
}
|
|
18250
|
+
console.log(`Attaching to ${ps.tmuxSession} (read-only)...`);
|
|
18251
|
+
console.log("Press Ctrl+B D to detach without interrupting the worker.");
|
|
18252
|
+
await execa("tmux", ["attach", "-t", ps.tmuxSession, "-r"], {
|
|
18253
|
+
stdio: "inherit"
|
|
18254
|
+
});
|
|
18255
|
+
}
|
|
18256
|
+
var init_watch = __esm(() => {
|
|
18257
|
+
init_execa();
|
|
18258
|
+
init_state();
|
|
18259
|
+
});
|
|
18260
|
+
|
|
17996
18261
|
// src/cli/logs.ts
|
|
17997
18262
|
var exports_logs = {};
|
|
17998
18263
|
__export(exports_logs, {
|
|
@@ -18139,8 +18404,8 @@ async function runClean(repoRoot) {
|
|
|
18139
18404
|
console.log("");
|
|
18140
18405
|
console.log(`Found ${planIds.length} plan(s) in inbox.`);
|
|
18141
18406
|
console.log("");
|
|
18142
|
-
const
|
|
18143
|
-
const rl =
|
|
18407
|
+
const readline2 = await import("readline");
|
|
18408
|
+
const rl = readline2.createInterface({
|
|
18144
18409
|
input: process.stdin,
|
|
18145
18410
|
output: process.stdout
|
|
18146
18411
|
});
|
|
@@ -23508,9 +23773,13 @@ yargs_default(hideBin(process.argv)).scriptName("prloom").usage("$0 <command> [o
|
|
|
23508
23773
|
}), async (argv) => {
|
|
23509
23774
|
const { runEdit: runEdit2 } = await Promise.resolve().then(() => (init_edit(), exports_edit));
|
|
23510
23775
|
await runEdit2(await getRepoRoot(), argv["plan-id"], argv.agent, argv["no-designer"]);
|
|
23511
|
-
}).command("start", "Start the dispatcher", () =>
|
|
23776
|
+
}).command("start", "Start the dispatcher", (yargs) => yargs.option("tmux", {
|
|
23777
|
+
type: "boolean",
|
|
23778
|
+
describe: "Run workers in tmux sessions for observation",
|
|
23779
|
+
default: false
|
|
23780
|
+
}), async (argv) => {
|
|
23512
23781
|
const { runDispatcher: runDispatcher2 } = await Promise.resolve().then(() => (init_dispatcher(), exports_dispatcher));
|
|
23513
|
-
await runDispatcher2(await getRepoRoot());
|
|
23782
|
+
await runDispatcher2(await getRepoRoot(), { tmux: argv.tmux });
|
|
23514
23783
|
}).command("status", "Show plan states", () => {}, async () => {
|
|
23515
23784
|
const { runStatus: runStatus2 } = await Promise.resolve().then(() => (init_status(), exports_status));
|
|
23516
23785
|
await runStatus2(await getRepoRoot());
|
|
@@ -23523,6 +23792,9 @@ yargs_default(hideBin(process.argv)).scriptName("prloom").usage("$0 <command> [o
|
|
|
23523
23792
|
}).command("open <plan-id>", "Open TUI for manual work (requires paused)", (yargs) => yargs.positional("plan-id", { type: "string", demandOption: true }), async (argv) => {
|
|
23524
23793
|
const { runOpen: runOpen2 } = await Promise.resolve().then(() => (init_open(), exports_open));
|
|
23525
23794
|
await runOpen2(await getRepoRoot(), argv["plan-id"]);
|
|
23795
|
+
}).command("watch <plan-id>", "Observe a running worker (requires --tmux mode)", (yargs) => yargs.positional("plan-id", { type: "string", demandOption: true }), async (argv) => {
|
|
23796
|
+
const { runWatch: runWatch2 } = await Promise.resolve().then(() => (init_watch(), exports_watch));
|
|
23797
|
+
await runWatch2(await getRepoRoot(), argv["plan-id"]);
|
|
23526
23798
|
}).command("logs <plan-id>", "Show session ID for a plan", (yargs) => yargs.positional("plan-id", { type: "string", demandOption: true }), async (argv) => {
|
|
23527
23799
|
const { runLogs: runLogs2 } = await Promise.resolve().then(() => (init_logs(), exports_logs));
|
|
23528
23800
|
await runLogs2(await getRepoRoot(), argv["plan-id"]);
|
package/package.json
CHANGED
package/prompts/review_triage.md
CHANGED
|
@@ -2,11 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
You are processing PR feedback for an active plan. Your job is to:
|
|
4
4
|
|
|
5
|
-
1.
|
|
6
|
-
2.
|
|
7
|
-
3.
|
|
5
|
+
1. **Classify** each feedback item by type
|
|
6
|
+
2. **Respond** appropriately based on the type
|
|
7
|
+
3. **Update** the plan if changes are needed
|
|
8
8
|
4. Detect if a rebase was requested
|
|
9
|
-
5. Compose a reply message for the reviewer(s)
|
|
10
9
|
|
|
11
10
|
## Feedback to Process
|
|
12
11
|
|
|
@@ -16,9 +15,36 @@ You are processing PR feedback for an active plan. Your job is to:
|
|
|
16
15
|
|
|
17
16
|
{{plan}}
|
|
18
17
|
|
|
19
|
-
|
|
18
|
+
---
|
|
20
19
|
|
|
21
|
-
|
|
20
|
+
## Step 1: Classify Each Feedback Item
|
|
21
|
+
|
|
22
|
+
Before acting, identify the type of each feedback item:
|
|
23
|
+
|
|
24
|
+
| Type | Description | Action |
|
|
25
|
+
| ------------------- | --------------------------------------------- | ------------------------ |
|
|
26
|
+
| **Question** | Asking "why", "how", or seeking clarification | Answer in reply, NO TODO |
|
|
27
|
+
| **Change Request** | Asking for code modifications | Create specific TODO |
|
|
28
|
+
| **Approval/Praise** | Positive feedback, LGTM, approval | Acknowledge briefly |
|
|
29
|
+
| **Process Request** | Rebase, update branch, etc. | Set flag, acknowledge |
|
|
30
|
+
|
|
31
|
+
## Step 2: Handle Each Type
|
|
32
|
+
|
|
33
|
+
### Questions → Answer Directly
|
|
34
|
+
|
|
35
|
+
For questions, **answer them substantively in your reply**:
|
|
36
|
+
|
|
37
|
+
- Explore the codebase to find the answer if needed
|
|
38
|
+
- Explain the reasoning behind implementation decisions
|
|
39
|
+
- Reference specific code locations or commits if helpful
|
|
40
|
+
- Do NOT create a TODO like "answer the question" - just answer it
|
|
41
|
+
|
|
42
|
+
Example question: "Why did you use a polling approach instead of webhooks?"
|
|
43
|
+
→ Reply with the actual reasoning, don't create a TODO.
|
|
44
|
+
|
|
45
|
+
### Change Requests → Create TODOs
|
|
46
|
+
|
|
47
|
+
For explicit requests to modify code:
|
|
22
48
|
|
|
23
49
|
- Edit the plan file directly to add actionable TODOs
|
|
24
50
|
- Add them at the end of the ## TODO section
|
|
@@ -28,13 +54,23 @@ You are processing PR feedback for an active plan. Your job is to:
|
|
|
28
54
|
- "Update function X to handle null input"
|
|
29
55
|
- "Add test for Y edge case"
|
|
30
56
|
- "Rename Z for clarity"
|
|
31
|
-
|
|
32
|
-
- Group related comments into single tasks when logical
|
|
57
|
+
- Group related requests into single tasks when logical
|
|
33
58
|
- If the plan status is `done` and you add TODOs, change status to `active`
|
|
34
59
|
|
|
35
|
-
###
|
|
60
|
+
### Approval/Praise → Acknowledge
|
|
61
|
+
|
|
62
|
+
Simply thank the reviewer in your reply. No TODO needed.
|
|
63
|
+
|
|
64
|
+
### Process Requests → Set Flag
|
|
65
|
+
|
|
66
|
+
If any comment mentions rebase, update branch, or similar:
|
|
67
|
+
|
|
68
|
+
- Set `rebase_requested: true` in the result file
|
|
69
|
+
- Acknowledge in your reply
|
|
70
|
+
|
|
71
|
+
## Step 3: Write the Result File
|
|
36
72
|
|
|
37
|
-
After
|
|
73
|
+
After processing, you MUST write `prloom/.local/triage-result.json`:
|
|
38
74
|
|
|
39
75
|
```json
|
|
40
76
|
{
|
|
@@ -45,19 +81,20 @@ After editing the plan, you MUST write `.prloom/triage-result.json` with:
|
|
|
45
81
|
|
|
46
82
|
**Required fields:**
|
|
47
83
|
|
|
48
|
-
- `reply_markdown`: Your response to post on the PR (ALWAYS required
|
|
49
|
-
- `rebase_requested`: Set to `true` if
|
|
84
|
+
- `reply_markdown`: Your response to post on the PR (ALWAYS required)
|
|
85
|
+
- `rebase_requested`: Set to `true` if rebase was requested
|
|
50
86
|
|
|
51
87
|
### Reply Guidelines
|
|
52
88
|
|
|
53
89
|
- Be polite and professional
|
|
54
|
-
-
|
|
55
|
-
- Explain what TODOs were created
|
|
56
|
-
- If
|
|
57
|
-
- Keep it concise
|
|
90
|
+
- **Answer questions directly** - this is the most important part
|
|
91
|
+
- Explain what TODOs were created (if any)
|
|
92
|
+
- If a request doesn't require changes, explain why
|
|
93
|
+
- Keep it concise but complete
|
|
58
94
|
|
|
59
95
|
## Critical Rules
|
|
60
96
|
|
|
61
|
-
1. You MUST write
|
|
97
|
+
1. You MUST write `prloom/.local/triage-result.json` even if you add no TODOs
|
|
62
98
|
2. The result file must contain valid JSON only, no markdown wrapper
|
|
63
99
|
3. Failure to write the result file will mark the plan as blocked
|
|
100
|
+
4. **Questions should be answered, not converted to TODOs**
|