prloom 0.1.3 → 0.1.5
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 +249 -46
- 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
|
@@ -7421,7 +7421,7 @@ var init_adapters = __esm(() => {
|
|
|
7421
7421
|
import { join as join2, resolve as resolve5 } from "path";
|
|
7422
7422
|
import { existsSync, readFileSync as readFileSync6 } from "fs";
|
|
7423
7423
|
function loadConfig(repoRoot) {
|
|
7424
|
-
const configPath = join2(repoRoot, "prloom
|
|
7424
|
+
const configPath = join2(repoRoot, "prloom", "config.json");
|
|
7425
7425
|
if (!existsSync(configPath)) {
|
|
7426
7426
|
return { ...DEFAULTS };
|
|
7427
7427
|
}
|
|
@@ -7458,7 +7458,7 @@ var init_config = __esm(() => {
|
|
|
7458
7458
|
agents: {
|
|
7459
7459
|
default: "opencode"
|
|
7460
7460
|
},
|
|
7461
|
-
worktrees_dir: "
|
|
7461
|
+
worktrees_dir: "prloom/.local/worktrees",
|
|
7462
7462
|
poll_interval_ms: 60000,
|
|
7463
7463
|
base_branch: "main"
|
|
7464
7464
|
};
|
|
@@ -7479,7 +7479,9 @@ async function runInit(cwd, opts = {}) {
|
|
|
7479
7479
|
await ensureGhAuthed();
|
|
7480
7480
|
await ensureGhRepoResolvable(repoRoot);
|
|
7481
7481
|
const defaultBranch = await detectDefaultBranch(repoRoot);
|
|
7482
|
-
const configPath = join3(repoRoot, "prloom
|
|
7482
|
+
const configPath = join3(repoRoot, "prloom", "config.json");
|
|
7483
|
+
const prloomDir = join3(repoRoot, "prloom");
|
|
7484
|
+
mkdirSync(prloomDir, { recursive: true });
|
|
7483
7485
|
if (!existsSync2(configPath) || opts.force) {
|
|
7484
7486
|
const existing = loadConfig(repoRoot);
|
|
7485
7487
|
const config = {
|
|
@@ -7494,12 +7496,14 @@ async function runInit(cwd, opts = {}) {
|
|
|
7494
7496
|
} else {
|
|
7495
7497
|
console.log(`Found existing ${configPath} (leaving unchanged)`);
|
|
7496
7498
|
}
|
|
7497
|
-
const
|
|
7498
|
-
const inboxDir = join3(
|
|
7499
|
-
const
|
|
7499
|
+
const localDir = join3(prloomDir, ".local");
|
|
7500
|
+
const inboxDir = join3(localDir, "inbox");
|
|
7501
|
+
const plansStateDir = join3(localDir, "plans");
|
|
7500
7502
|
mkdirSync(inboxDir, { recursive: true });
|
|
7503
|
+
mkdirSync(plansStateDir, { recursive: true });
|
|
7504
|
+
const plansDir = join3(prloomDir, "plans");
|
|
7501
7505
|
mkdirSync(plansDir, { recursive: true });
|
|
7502
|
-
await ensureGitignoreEntry(repoRoot, "
|
|
7506
|
+
await ensureGitignoreEntry(repoRoot, "prloom/.local/");
|
|
7503
7507
|
console.log("✅ prloom initialized");
|
|
7504
7508
|
console.log(`Base branch: ${defaultBranch}`);
|
|
7505
7509
|
if (!opts.yes) {
|
|
@@ -11202,7 +11206,7 @@ ${plan.progressLog}`;
|
|
|
11202
11206
|
function generatePlanSkeleton(id, agent, baseBranch) {
|
|
11203
11207
|
const frontmatter = {
|
|
11204
11208
|
id,
|
|
11205
|
-
status: "
|
|
11209
|
+
status: "draft"
|
|
11206
11210
|
};
|
|
11207
11211
|
if (agent) {
|
|
11208
11212
|
frontmatter.agent = agent;
|
|
@@ -16898,11 +16902,10 @@ You are implementing exactly ONE task from this plan.
|
|
|
16898
16902
|
|
|
16899
16903
|
You are processing PR feedback for an active plan. Your job is to:
|
|
16900
16904
|
|
|
16901
|
-
1.
|
|
16902
|
-
2.
|
|
16903
|
-
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
|
|
16904
16908
|
4. Detect if a rebase was requested
|
|
16905
|
-
5. Compose a reply message for the reviewer(s)
|
|
16906
16909
|
|
|
16907
16910
|
## Feedback to Process
|
|
16908
16911
|
|
|
@@ -16912,9 +16915,36 @@ You are processing PR feedback for an active plan. Your job is to:
|
|
|
16912
16915
|
|
|
16913
16916
|
{{plan}}
|
|
16914
16917
|
|
|
16915
|
-
|
|
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
|
|
16941
|
+
|
|
16942
|
+
Example question: "Why did you use a polling approach instead of webhooks?"
|
|
16943
|
+
→ Reply with the actual reasoning, don't create a TODO.
|
|
16916
16944
|
|
|
16917
|
-
###
|
|
16945
|
+
### Change Requests → Create TODOs
|
|
16946
|
+
|
|
16947
|
+
For explicit requests to modify code:
|
|
16918
16948
|
|
|
16919
16949
|
- Edit the plan file directly to add actionable TODOs
|
|
16920
16950
|
- Add them at the end of the ## TODO section
|
|
@@ -16924,13 +16954,23 @@ You are processing PR feedback for an active plan. Your job is to:
|
|
|
16924
16954
|
- "Update function X to handle null input"
|
|
16925
16955
|
- "Add test for Y edge case"
|
|
16926
16956
|
- "Rename Z for clarity"
|
|
16927
|
-
|
|
16928
|
-
- Group related comments into single tasks when logical
|
|
16957
|
+
- Group related requests into single tasks when logical
|
|
16929
16958
|
- If the plan status is \`done\` and you add TODOs, change status to \`active\`
|
|
16930
16959
|
|
|
16931
|
-
###
|
|
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
|
|
16932
16970
|
|
|
16933
|
-
|
|
16971
|
+
## Step 3: Write the Result File
|
|
16972
|
+
|
|
16973
|
+
After processing, you MUST write \`prloom/.local/triage-result.json\`:
|
|
16934
16974
|
|
|
16935
16975
|
\`\`\`json
|
|
16936
16976
|
{
|
|
@@ -16941,22 +16981,23 @@ After editing the plan, you MUST write \`.prloom/triage-result.json\` with:
|
|
|
16941
16981
|
|
|
16942
16982
|
**Required fields:**
|
|
16943
16983
|
|
|
16944
|
-
- \`reply_markdown\`: Your response to post on the PR (ALWAYS required
|
|
16945
|
-
- \`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
|
|
16946
16986
|
|
|
16947
16987
|
### Reply Guidelines
|
|
16948
16988
|
|
|
16949
16989
|
- Be polite and professional
|
|
16950
|
-
-
|
|
16951
|
-
- Explain what TODOs were created
|
|
16952
|
-
- If
|
|
16953
|
-
- 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
|
|
16954
16994
|
|
|
16955
16995
|
## Critical Rules
|
|
16956
16996
|
|
|
16957
|
-
1. You MUST write
|
|
16997
|
+
1. You MUST write \`prloom/.local/triage-result.json\` even if you add no TODOs
|
|
16958
16998
|
2. The result file must contain valid JSON only, no markdown wrapper
|
|
16959
16999
|
3. Failure to write the result file will mark the plan as blocked
|
|
17000
|
+
4. **Questions should be answered, not converted to TODOs**
|
|
16960
17001
|
`
|
|
16961
17002
|
};
|
|
16962
17003
|
});
|
|
@@ -16967,32 +17008,72 @@ import { join as join4 } from "path";
|
|
|
16967
17008
|
function loadTemplate(_repoRoot, name) {
|
|
16968
17009
|
return BUILTIN_PROMPTS[name];
|
|
16969
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
|
+
}
|
|
16970
17018
|
function renderWorkerPrompt(repoRoot, plan, todo) {
|
|
16971
17019
|
const template = loadTemplate(repoRoot, "worker");
|
|
16972
17020
|
const compiled = import_handlebars.default.compile(template);
|
|
16973
|
-
|
|
17021
|
+
let prompt = compiled({
|
|
16974
17022
|
current_todo: `TODO #${todo.index}: ${todo.text}`,
|
|
16975
17023
|
plan: plan.raw
|
|
16976
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;
|
|
16977
17036
|
}
|
|
16978
17037
|
function renderDesignerNewPrompt(repoPath, planPath, baseBranch, workerAgent, userDescription) {
|
|
16979
17038
|
const template = BUILTIN_PROMPTS["designer_new"];
|
|
16980
17039
|
const compiled = import_handlebars.default.compile(template);
|
|
16981
|
-
|
|
17040
|
+
let prompt = compiled({
|
|
16982
17041
|
repo_path: repoPath,
|
|
16983
17042
|
plan_path: planPath,
|
|
16984
17043
|
base_branch: baseBranch,
|
|
16985
17044
|
worker_agent: workerAgent,
|
|
16986
17045
|
user_description: userDescription ?? ""
|
|
16987
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;
|
|
16988
17058
|
}
|
|
16989
|
-
function renderDesignerEditPrompt(planPath, existingPlan) {
|
|
17059
|
+
function renderDesignerEditPrompt(repoPath, planPath, existingPlan) {
|
|
16990
17060
|
const template = BUILTIN_PROMPTS["designer_edit"];
|
|
16991
17061
|
const compiled = import_handlebars.default.compile(template);
|
|
16992
|
-
|
|
17062
|
+
let prompt = compiled({
|
|
16993
17063
|
plan_path: planPath,
|
|
16994
17064
|
existing_plan: existingPlan
|
|
16995
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;
|
|
16996
17077
|
}
|
|
16997
17078
|
function renderTriagePrompt(repoRoot, plan, feedback) {
|
|
16998
17079
|
const template = loadTemplate(repoRoot, "review_triage");
|
|
@@ -17008,6 +17089,10 @@ ${f.body}`;
|
|
|
17008
17089
|
entry += `
|
|
17009
17090
|
|
|
17010
17091
|
*Review: ${f.reviewState}*`;
|
|
17092
|
+
if (f.inReplyToId)
|
|
17093
|
+
entry += `
|
|
17094
|
+
|
|
17095
|
+
*In reply to comment #${f.inReplyToId}*`;
|
|
17011
17096
|
return entry;
|
|
17012
17097
|
}).join(`
|
|
17013
17098
|
|
|
@@ -17039,7 +17124,7 @@ function readTriageResultFile(worktreePath) {
|
|
|
17039
17124
|
rebase_requested: result.rebase_requested
|
|
17040
17125
|
};
|
|
17041
17126
|
}
|
|
17042
|
-
var import_handlebars, TRIAGE_RESULT_FILE = "
|
|
17127
|
+
var import_handlebars, TRIAGE_RESULT_FILE = "prloom/.local/triage-result.json";
|
|
17043
17128
|
var init_template2 = __esm(() => {
|
|
17044
17129
|
init_prompt_sources();
|
|
17045
17130
|
import_handlebars = __toESM(require_lib(), 1);
|
|
@@ -17174,7 +17259,7 @@ function deleteInboxPlan(repoRoot, planId) {
|
|
|
17174
17259
|
unlinkSync2(inboxPath);
|
|
17175
17260
|
}
|
|
17176
17261
|
}
|
|
17177
|
-
var SWARM_DIR = "
|
|
17262
|
+
var SWARM_DIR = "prloom/.local", STATE_FILE = "state.json", LOCK_FILE = "lock", PLANS_DIR = "plans", INBOX_DIR = "inbox";
|
|
17178
17263
|
var init_state = () => {};
|
|
17179
17264
|
|
|
17180
17265
|
// node_modules/nanoid/url-alphabet/index.js
|
|
@@ -17301,7 +17386,7 @@ function copyFileToWorktree(srcPath, worktreePath, destRelPath) {
|
|
|
17301
17386
|
copyFileSync(srcPath, destPath);
|
|
17302
17387
|
}
|
|
17303
17388
|
function ensureWorktreePrloomDir(worktreePath) {
|
|
17304
|
-
const prloomDir = join6(worktreePath, "
|
|
17389
|
+
const prloomDir = join6(worktreePath, "prloom", ".local");
|
|
17305
17390
|
if (!existsSync5(prloomDir)) {
|
|
17306
17391
|
mkdirSync3(prloomDir, { recursive: true });
|
|
17307
17392
|
}
|
|
@@ -17311,6 +17396,23 @@ var init_git = __esm(() => {
|
|
|
17311
17396
|
init_nanoid();
|
|
17312
17397
|
});
|
|
17313
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
|
+
|
|
17314
17416
|
// src/cli/new.ts
|
|
17315
17417
|
var exports_new = {};
|
|
17316
17418
|
__export(exports_new, {
|
|
@@ -17348,8 +17450,8 @@ async function runNew(repoRoot, planId, agentOverride, noDesigner) {
|
|
|
17348
17450
|
console.log(`Worker agent: ${workerAgent}`);
|
|
17349
17451
|
if (noDesigner) {
|
|
17350
17452
|
console.log("");
|
|
17351
|
-
console.log("Plan skeleton created
|
|
17352
|
-
console.log("
|
|
17453
|
+
console.log("Plan skeleton created (status: draft).");
|
|
17454
|
+
console.log("Use 'prloom edit' to design, then queue for dispatch.");
|
|
17353
17455
|
return;
|
|
17354
17456
|
}
|
|
17355
17457
|
const adapter = getAdapter(designerAgent);
|
|
@@ -17358,8 +17460,15 @@ async function runNew(repoRoot, planId, agentOverride, noDesigner) {
|
|
|
17358
17460
|
console.log("Starting Designer session to fill in the plan...");
|
|
17359
17461
|
const prompt = renderDesignerNewPrompt(repoRoot, planPath, baseBranch, workerAgent);
|
|
17360
17462
|
await adapter.interactive({ cwd: repoRoot, prompt });
|
|
17463
|
+
console.log("");
|
|
17361
17464
|
console.log("Designer session ended.");
|
|
17362
|
-
|
|
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
|
+
}
|
|
17363
17472
|
}
|
|
17364
17473
|
var init_new = __esm(() => {
|
|
17365
17474
|
init_config();
|
|
@@ -17368,6 +17477,7 @@ var init_new = __esm(() => {
|
|
|
17368
17477
|
init_template2();
|
|
17369
17478
|
init_state();
|
|
17370
17479
|
init_git();
|
|
17480
|
+
init_prompt();
|
|
17371
17481
|
});
|
|
17372
17482
|
|
|
17373
17483
|
// src/cli/edit.ts
|
|
@@ -17383,9 +17493,11 @@ async function runEdit(repoRoot, planId, agentOverride, noDesigner) {
|
|
|
17383
17493
|
const inboxPath = getInboxPath(repoRoot, planId);
|
|
17384
17494
|
let planPath;
|
|
17385
17495
|
let cwd;
|
|
17496
|
+
let isInbox = false;
|
|
17386
17497
|
if (existsSync7(inboxPath)) {
|
|
17387
17498
|
planPath = inboxPath;
|
|
17388
17499
|
cwd = repoRoot;
|
|
17500
|
+
isInbox = true;
|
|
17389
17501
|
console.log(`Editing inbox plan: ${planId}`);
|
|
17390
17502
|
} else {
|
|
17391
17503
|
const ps = state.plans[planId];
|
|
@@ -17413,15 +17525,30 @@ async function runEdit(repoRoot, planId, agentOverride, noDesigner) {
|
|
|
17413
17525
|
const agentName = agentOverride ?? config.agents.designer ?? config.agents.default;
|
|
17414
17526
|
const adapter = getAdapter(agentName);
|
|
17415
17527
|
console.log(`Agent: ${agentName}`);
|
|
17416
|
-
const prompt = renderDesignerEditPrompt(planPath, existingPlan);
|
|
17528
|
+
const prompt = renderDesignerEditPrompt(cwd, planPath, existingPlan);
|
|
17417
17529
|
await adapter.interactive({ cwd, prompt });
|
|
17530
|
+
console.log("");
|
|
17418
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
|
+
}
|
|
17419
17544
|
}
|
|
17420
17545
|
var init_edit = __esm(() => {
|
|
17421
17546
|
init_config();
|
|
17422
17547
|
init_adapters();
|
|
17423
17548
|
init_template2();
|
|
17424
17549
|
init_state();
|
|
17550
|
+
init_plan();
|
|
17551
|
+
init_prompt();
|
|
17425
17552
|
});
|
|
17426
17553
|
|
|
17427
17554
|
// src/lib/ipc.ts
|
|
@@ -17439,10 +17566,10 @@ import {
|
|
|
17439
17566
|
mkdirSync as mkdirSync4
|
|
17440
17567
|
} from "fs";
|
|
17441
17568
|
function getControlPath(repoRoot) {
|
|
17442
|
-
return join8(repoRoot, "
|
|
17569
|
+
return join8(repoRoot, "prloom", ".local", CONTROL_FILE);
|
|
17443
17570
|
}
|
|
17444
17571
|
function enqueue(repoRoot, cmd) {
|
|
17445
|
-
const prloomDir = join8(repoRoot, "
|
|
17572
|
+
const prloomDir = join8(repoRoot, "prloom", ".local");
|
|
17446
17573
|
if (!existsSync8(prloomDir)) {
|
|
17447
17574
|
mkdirSync4(prloomDir, { recursive: true });
|
|
17448
17575
|
}
|
|
@@ -17572,7 +17699,7 @@ async function getPRReviewComments(repoRoot, prNumber) {
|
|
|
17572
17699
|
"api",
|
|
17573
17700
|
`repos/{owner}/{repo}/pulls/${prNumber}/comments`,
|
|
17574
17701
|
"--jq",
|
|
17575
|
-
".[] | {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}"
|
|
17576
17703
|
], { cwd: repoRoot });
|
|
17577
17704
|
if (!stdout.trim())
|
|
17578
17705
|
return [];
|
|
@@ -17586,7 +17713,8 @@ async function getPRReviewComments(repoRoot, prNumber) {
|
|
|
17586
17713
|
body: obj.body,
|
|
17587
17714
|
path: obj.path,
|
|
17588
17715
|
line: obj.line,
|
|
17589
|
-
createdAt: obj.createdAt
|
|
17716
|
+
createdAt: obj.createdAt,
|
|
17717
|
+
inReplyToId: obj.inReplyToId || undefined
|
|
17590
17718
|
};
|
|
17591
17719
|
});
|
|
17592
17720
|
}
|
|
@@ -17693,11 +17821,14 @@ async function ingestInboxPlans(repoRoot, worktreesDir, config, state) {
|
|
|
17693
17821
|
console.error(`ID mismatch: file ${planId}.md has id: ${plan.frontmatter.id}, skipping`);
|
|
17694
17822
|
continue;
|
|
17695
17823
|
}
|
|
17824
|
+
if (plan.frontmatter.status === "draft") {
|
|
17825
|
+
continue;
|
|
17826
|
+
}
|
|
17696
17827
|
console.log(`\uD83D\uDCE5 Ingesting inbox plan: ${planId}`);
|
|
17697
17828
|
const baseBranch = plan.frontmatter.base_branch ?? config.base_branch;
|
|
17698
17829
|
const branch = await createBranchName(planId);
|
|
17699
17830
|
const worktreePath = await createWorktree(repoRoot, worktreesDir, branch, baseBranch);
|
|
17700
|
-
const planRelpath = `plans/${planId}.md`;
|
|
17831
|
+
const planRelpath = `prloom/plans/${planId}.md`;
|
|
17701
17832
|
copyFileToWorktree(inboxPath, worktreePath, planRelpath);
|
|
17702
17833
|
const worktreePlanPath = join9(worktreePath, planRelpath);
|
|
17703
17834
|
setStatus(worktreePlanPath, "active");
|
|
@@ -17785,6 +17916,30 @@ async function processActivePlans(repoRoot, config, state, botLogin, options2 =
|
|
|
17785
17916
|
if (!isManualAgent && plan.frontmatter.status !== "blocked" && plan.frontmatter.status !== "done") {
|
|
17786
17917
|
const todo = findNextUnchecked(plan);
|
|
17787
17918
|
if (todo) {
|
|
17919
|
+
const MAX_TODO_RETRIES = 3;
|
|
17920
|
+
if (ps.lastTodoIndex === todo.index) {
|
|
17921
|
+
ps.todoRetryCount = (ps.todoRetryCount ?? 0) + 1;
|
|
17922
|
+
console.log(` [retry ${ps.todoRetryCount}/${MAX_TODO_RETRIES} for TODO #${todo.index}]`);
|
|
17923
|
+
if (ps.todoRetryCount >= MAX_TODO_RETRIES) {
|
|
17924
|
+
console.error(`❌ TODO #${todo.index} failed ${MAX_TODO_RETRIES} times, blocking plan`);
|
|
17925
|
+
const planPath2 = join9(ps.worktree, ps.planRelpath);
|
|
17926
|
+
setStatus(planPath2, "blocked");
|
|
17927
|
+
ps.lastError = `TODO #${todo.index} failed after ${MAX_TODO_RETRIES} retries - worker did not mark it complete`;
|
|
17928
|
+
if (ps.pr) {
|
|
17929
|
+
await postPRComment(repoRoot, ps.pr, `❌ **Plan blocked**: TODO #${todo.index} failed after ${MAX_TODO_RETRIES} attempts.
|
|
17930
|
+
|
|
17931
|
+
The worker did not mark this TODO as complete. Please investigate manually.
|
|
17932
|
+
|
|
17933
|
+
\`\`\`
|
|
17934
|
+
prloom unpause ${planId}
|
|
17935
|
+
\`\`\``);
|
|
17936
|
+
}
|
|
17937
|
+
continue;
|
|
17938
|
+
}
|
|
17939
|
+
} else {
|
|
17940
|
+
ps.lastTodoIndex = todo.index;
|
|
17941
|
+
ps.todoRetryCount = 0;
|
|
17942
|
+
}
|
|
17788
17943
|
console.log(`\uD83D\uDD27 Running TODO #${todo.index} for ${planId}: ${todo.text}`);
|
|
17789
17944
|
const prompt = renderWorkerPrompt(repoRoot, plan, todo);
|
|
17790
17945
|
const agentName = plan.frontmatter.agent ?? config.agents.default;
|
|
@@ -17794,10 +17949,26 @@ async function processActivePlans(repoRoot, config, state, botLogin, options2 =
|
|
|
17794
17949
|
ps.tmuxSession = tmuxConfig.sessionName;
|
|
17795
17950
|
console.log(` [spawned in tmux session: ${tmuxConfig.sessionName}]`);
|
|
17796
17951
|
}
|
|
17797
|
-
await adapter.execute({
|
|
17952
|
+
const execResult = await adapter.execute({
|
|
17953
|
+
cwd: ps.worktree,
|
|
17954
|
+
prompt,
|
|
17955
|
+
tmux: tmuxConfig
|
|
17956
|
+
});
|
|
17798
17957
|
if (tmuxConfig) {
|
|
17799
17958
|
ps.tmuxSession = undefined;
|
|
17800
17959
|
}
|
|
17960
|
+
if (execResult.exitCode !== 0) {
|
|
17961
|
+
console.warn(` ⚠️ Worker exited with code ${execResult.exitCode}`);
|
|
17962
|
+
}
|
|
17963
|
+
const updatedPlan = parsePlan(planPath);
|
|
17964
|
+
const updatedTodo = updatedPlan.todos[todo.index];
|
|
17965
|
+
if (!updatedTodo?.done) {
|
|
17966
|
+
console.warn(` ⚠️ TODO #${todo.index} was NOT marked complete by worker`);
|
|
17967
|
+
continue;
|
|
17968
|
+
}
|
|
17969
|
+
ps.lastTodoIndex = undefined;
|
|
17970
|
+
ps.todoRetryCount = undefined;
|
|
17971
|
+
console.log(` ✓ TODO #${todo.index} marked complete`);
|
|
17801
17972
|
const committed = await commitAll(ps.worktree, `[prloom] ${planId}: TODO #${todo.index}`);
|
|
17802
17973
|
if (committed) {
|
|
17803
17974
|
await push(ps.worktree, ps.branch);
|
|
@@ -17862,12 +18033,44 @@ async function runTriage(repoRoot, config, ps, plan, feedback, options2 = {}) {
|
|
|
17862
18033
|
const planPath = join9(ps.worktree, ps.planRelpath);
|
|
17863
18034
|
setStatus(planPath, "blocked");
|
|
17864
18035
|
ps.lastError = `Rebase conflict: ${rebaseResult.conflictFiles?.join(", ")}`;
|
|
17865
|
-
await postPRComment(repoRoot, ps.pr, `⚠️ Rebase conflict detected
|
|
18036
|
+
await postPRComment(repoRoot, ps.pr, `⚠️ **Rebase conflict detected**
|
|
18037
|
+
|
|
18038
|
+
The following files have conflicts:
|
|
17866
18039
|
\`\`\`
|
|
17867
18040
|
${rebaseResult.conflictFiles?.join(`
|
|
17868
18041
|
`)}
|
|
17869
18042
|
\`\`\`
|
|
17870
|
-
|
|
18043
|
+
|
|
18044
|
+
**To resolve:**
|
|
18045
|
+
|
|
18046
|
+
1. Navigate to the worktree:
|
|
18047
|
+
\`\`\`
|
|
18048
|
+
cd ${ps.worktree}
|
|
18049
|
+
\`\`\`
|
|
18050
|
+
|
|
18051
|
+
2. Fetch and rebase manually:
|
|
18052
|
+
\`\`\`
|
|
18053
|
+
git fetch origin ${ps.baseBranch}
|
|
18054
|
+
git rebase origin/${ps.baseBranch}
|
|
18055
|
+
\`\`\`
|
|
18056
|
+
|
|
18057
|
+
3. Resolve conflicts in your editor, then:
|
|
18058
|
+
\`\`\`
|
|
18059
|
+
git add .
|
|
18060
|
+
git rebase --continue
|
|
18061
|
+
\`\`\`
|
|
18062
|
+
|
|
18063
|
+
4. Force push the resolved branch:
|
|
18064
|
+
\`\`\`
|
|
18065
|
+
git push --force-with-lease
|
|
18066
|
+
\`\`\`
|
|
18067
|
+
|
|
18068
|
+
5. Unblock the plan:
|
|
18069
|
+
\`\`\`
|
|
18070
|
+
prloom unpause ${plan.frontmatter.id}
|
|
18071
|
+
\`\`\`
|
|
18072
|
+
|
|
18073
|
+
The plan is now **blocked** until conflicts are resolved.`);
|
|
17871
18074
|
} else if (rebaseResult.success) {
|
|
17872
18075
|
await forcePush(ps.worktree, ps.branch);
|
|
17873
18076
|
console.log(` Rebased and force-pushed`);
|
|
@@ -18241,8 +18444,8 @@ async function runClean(repoRoot) {
|
|
|
18241
18444
|
console.log("");
|
|
18242
18445
|
console.log(`Found ${planIds.length} plan(s) in inbox.`);
|
|
18243
18446
|
console.log("");
|
|
18244
|
-
const
|
|
18245
|
-
const rl =
|
|
18447
|
+
const readline2 = await import("readline");
|
|
18448
|
+
const rl = readline2.createInterface({
|
|
18246
18449
|
input: process.stdin,
|
|
18247
18450
|
output: process.stdout
|
|
18248
18451
|
});
|
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**
|