pure-point-guard 0.3.0 → 0.3.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/dist/cli.js +244 -181
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
- package/skills/ppg-conductor/SKILL.md +5 -0
- package/skills/ppg-conductor/references/commands.md +32 -0
- package/skills/ppg-conductor/references/conductor.md +52 -5
- package/skills/ppg-conductor/references/modes.md +9 -0
package/dist/cli.js
CHANGED
|
@@ -95,7 +95,7 @@ var init_errors = __esm({
|
|
|
95
95
|
constructor(names) {
|
|
96
96
|
const list = names.map((n) => ` ${n}`).join("\n");
|
|
97
97
|
super(
|
|
98
|
-
`${names.length} worktree(s) have
|
|
98
|
+
`${names.length} worktree(s) have unmerged work that hasn't been PR'd:
|
|
99
99
|
${list}
|
|
100
100
|
|
|
101
101
|
Use --force to reset anyway, or create PRs first with: ppg pr <worktree-id>`,
|
|
@@ -163,7 +163,7 @@ function success(message) {
|
|
|
163
163
|
function warn(message) {
|
|
164
164
|
console.log(`${YELLOW}\u26A0${RESET} ${message}`);
|
|
165
165
|
}
|
|
166
|
-
var RESET, BOLD, DIM, RED, GREEN, YELLOW, BLUE,
|
|
166
|
+
var RESET, BOLD, DIM, RED, GREEN, YELLOW, BLUE, CYAN, GRAY, STATUS_COLORS;
|
|
167
167
|
var init_output = __esm({
|
|
168
168
|
"src/lib/output.ts"() {
|
|
169
169
|
"use strict";
|
|
@@ -174,20 +174,17 @@ var init_output = __esm({
|
|
|
174
174
|
GREEN = "\x1B[32m";
|
|
175
175
|
YELLOW = "\x1B[33m";
|
|
176
176
|
BLUE = "\x1B[34m";
|
|
177
|
-
MAGENTA = "\x1B[35m";
|
|
178
177
|
CYAN = "\x1B[36m";
|
|
179
178
|
GRAY = "\x1B[90m";
|
|
180
179
|
STATUS_COLORS = {
|
|
181
|
-
spawning: YELLOW,
|
|
182
180
|
running: GREEN,
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
killed: MAGENTA,
|
|
187
|
-
lost: RED + BOLD,
|
|
181
|
+
idle: CYAN,
|
|
182
|
+
exited: BLUE,
|
|
183
|
+
gone: GRAY,
|
|
188
184
|
active: GREEN,
|
|
189
185
|
merging: YELLOW,
|
|
190
186
|
merged: BLUE,
|
|
187
|
+
failed: RED,
|
|
191
188
|
cleaned: GRAY
|
|
192
189
|
};
|
|
193
190
|
}
|
|
@@ -325,29 +322,17 @@ var init_config = __esm({
|
|
|
325
322
|
claude: {
|
|
326
323
|
name: "claude",
|
|
327
324
|
command: "claude --dangerously-skip-permissions",
|
|
328
|
-
interactive: true
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
"## PR",
|
|
340
|
-
"<the PR URL from step 3>",
|
|
341
|
-
"",
|
|
342
|
-
"## Summary",
|
|
343
|
-
"<what you accomplished>",
|
|
344
|
-
"",
|
|
345
|
-
"## Changes",
|
|
346
|
-
"<list of files changed>",
|
|
347
|
-
"",
|
|
348
|
-
"## Notes",
|
|
349
|
-
"<any important observations>"
|
|
350
|
-
].join("\n")
|
|
325
|
+
interactive: true
|
|
326
|
+
},
|
|
327
|
+
codex: {
|
|
328
|
+
name: "codex",
|
|
329
|
+
command: "codex --yolo",
|
|
330
|
+
interactive: true
|
|
331
|
+
},
|
|
332
|
+
opencode: {
|
|
333
|
+
name: "opencode",
|
|
334
|
+
command: "opencode --yolo",
|
|
335
|
+
interactive: true
|
|
351
336
|
}
|
|
352
337
|
},
|
|
353
338
|
envFiles: [".env", ".env.local"],
|
|
@@ -977,11 +962,19 @@ var init_init = __esm({
|
|
|
977
962
|
|
|
978
963
|
You are operating on the master branch of a ppg-managed project.
|
|
979
964
|
|
|
980
|
-
##
|
|
981
|
-
|
|
965
|
+
## When to Use ppg
|
|
966
|
+
Use \`ppg spawn\` whenever you want work to appear in the **ppg dashboard** \u2014 parallel tasks, code reviews,
|
|
967
|
+
batch issue work, multi-agent swarms. Agents spawned through ppg run in tmux panes the user can monitor,
|
|
968
|
+
interact with, and manage. Available agent types: \`claude\` (default), \`codex\`, \`opencode\` via \`--agent\`.
|
|
969
|
+
|
|
970
|
+
Direct edits, quick commands, and research are fine to do yourself \u2014 not everything needs an agent.
|
|
971
|
+
Never run \`claude\`, \`codex\`, or \`opencode\` directly as bash commands \u2014 they won't appear in the dashboard.
|
|
982
972
|
|
|
983
973
|
## Quick Reference
|
|
984
974
|
- \`ppg spawn --name <name> --prompt "<task>" --json\` \u2014 Spawn worktree + agent
|
|
975
|
+
- \`ppg spawn --name <name> --agent codex --prompt "<task>" --json\` \u2014 Use Codex agent
|
|
976
|
+
- \`ppg spawn --name <name> --agent opencode --prompt "<task>" --json\` \u2014 Use OpenCode agent
|
|
977
|
+
- \`ppg spawn --worktree <id> --agent codex --prompt "review --base main" --json\` \u2014 Codex review
|
|
985
978
|
- \`ppg status --json\` \u2014 Check statuses
|
|
986
979
|
- \`ppg aggregate --all --json\` \u2014 Collect results (includes PR URLs)
|
|
987
980
|
- \`ppg kill --agent <id> --json\` \u2014 Kill agent
|
|
@@ -1046,6 +1039,11 @@ async function createWorktree(repoRoot, id, options) {
|
|
|
1046
1039
|
await execa3("git", args, { ...execaEnv, cwd: repoRoot });
|
|
1047
1040
|
return wtPath;
|
|
1048
1041
|
}
|
|
1042
|
+
async function adoptWorktree(repoRoot, id, branch) {
|
|
1043
|
+
const wtPath = worktreePath(repoRoot, id);
|
|
1044
|
+
await execa3("git", ["worktree", "add", wtPath, branch], { ...execaEnv, cwd: repoRoot });
|
|
1045
|
+
return wtPath;
|
|
1046
|
+
}
|
|
1049
1047
|
async function removeWorktree(repoRoot, wtPath, options) {
|
|
1050
1048
|
const args = ["worktree", "remove", wtPath];
|
|
1051
1049
|
if (options?.force) {
|
|
@@ -1169,31 +1167,12 @@ async function spawnAgent(options) {
|
|
|
1169
1167
|
agentId: agentId2,
|
|
1170
1168
|
agentConfig,
|
|
1171
1169
|
prompt,
|
|
1172
|
-
worktreePath: worktreePath2,
|
|
1173
1170
|
tmuxTarget,
|
|
1174
|
-
projectRoot
|
|
1175
|
-
branch
|
|
1171
|
+
projectRoot
|
|
1176
1172
|
} = options;
|
|
1177
|
-
const resFile = resultFile(projectRoot, agentId2);
|
|
1178
|
-
let fullPrompt = prompt;
|
|
1179
|
-
if (agentConfig.resultInstructions && !options.skipResultInstructions) {
|
|
1180
|
-
const ctx = {
|
|
1181
|
-
WORKTREE_PATH: worktreePath2,
|
|
1182
|
-
BRANCH: branch,
|
|
1183
|
-
AGENT_ID: agentId2,
|
|
1184
|
-
RESULT_FILE: resFile,
|
|
1185
|
-
PROJECT_ROOT: projectRoot
|
|
1186
|
-
};
|
|
1187
|
-
const instructions = renderTemplate(agentConfig.resultInstructions, ctx);
|
|
1188
|
-
fullPrompt += `
|
|
1189
|
-
|
|
1190
|
-
---
|
|
1191
|
-
|
|
1192
|
-
${instructions}`;
|
|
1193
|
-
}
|
|
1194
1173
|
const pFile = agentPromptFile(projectRoot, agentId2);
|
|
1195
1174
|
await fs6.mkdir(agentPromptsDir(projectRoot), { recursive: true });
|
|
1196
|
-
await fs6.writeFile(pFile,
|
|
1175
|
+
await fs6.writeFile(pFile, prompt, "utf-8");
|
|
1197
1176
|
const command = buildAgentCommand(agentConfig, pFile, options.sessionId);
|
|
1198
1177
|
await sendKeys(tmuxTarget, command);
|
|
1199
1178
|
return {
|
|
@@ -1204,7 +1183,6 @@ ${instructions}`;
|
|
|
1204
1183
|
tmuxTarget,
|
|
1205
1184
|
prompt: prompt.slice(0, 500),
|
|
1206
1185
|
// Truncate for manifest storage
|
|
1207
|
-
resultFile: resFile,
|
|
1208
1186
|
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1209
1187
|
...options.sessionId ? { sessionId: options.sessionId } : {}
|
|
1210
1188
|
};
|
|
@@ -1219,32 +1197,16 @@ function buildAgentCommand(agentConfig, promptFilePath, sessionId2) {
|
|
|
1219
1197
|
}
|
|
1220
1198
|
return `${envPrefix} ${command}${sessionFlag} ${catExpr}`;
|
|
1221
1199
|
}
|
|
1222
|
-
async function checkAgentStatus(agent,
|
|
1223
|
-
if (["completed", "failed", "killed", "lost"].includes(agent.status)) {
|
|
1224
|
-
return { status: agent.status, exitCode: agent.exitCode };
|
|
1225
|
-
}
|
|
1226
|
-
const hasResult = await fileExists(agent.resultFile);
|
|
1227
|
-
if (hasResult) {
|
|
1228
|
-
return { status: "completed" };
|
|
1229
|
-
}
|
|
1200
|
+
async function checkAgentStatus(agent, _projectRoot, paneMap) {
|
|
1230
1201
|
const paneInfo = paneMap ? paneMap.get(agent.tmuxTarget) ?? null : await getPaneInfo(agent.tmuxTarget);
|
|
1231
1202
|
if (!paneInfo) {
|
|
1232
|
-
return { status: "
|
|
1203
|
+
return { status: "gone" };
|
|
1233
1204
|
}
|
|
1234
1205
|
if (paneInfo.isDead) {
|
|
1235
|
-
|
|
1236
|
-
const hasResultNow = await fileExists(agent.resultFile);
|
|
1237
|
-
if (hasResultNow || exitCode === 0) {
|
|
1238
|
-
return { status: "completed", exitCode: exitCode ?? 0 };
|
|
1239
|
-
}
|
|
1240
|
-
return { status: "failed", exitCode };
|
|
1206
|
+
return { status: "exited", exitCode: paneInfo.deadStatus };
|
|
1241
1207
|
}
|
|
1242
1208
|
if (SHELL_COMMANDS.has(paneInfo.currentCommand)) {
|
|
1243
|
-
|
|
1244
|
-
if (hasResultNow) {
|
|
1245
|
-
return { status: "completed", exitCode: 0 };
|
|
1246
|
-
}
|
|
1247
|
-
return { status: "failed", exitCode: void 0 };
|
|
1209
|
+
return { status: "idle" };
|
|
1248
1210
|
}
|
|
1249
1211
|
return { status: "running" };
|
|
1250
1212
|
}
|
|
@@ -1260,27 +1222,18 @@ async function refreshAllAgentStatuses(manifest, projectRoot) {
|
|
|
1260
1222
|
}
|
|
1261
1223
|
}
|
|
1262
1224
|
const results = await Promise.all(checks.map((c) => c.promise));
|
|
1263
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1264
1225
|
for (let i = 0; i < checks.length; i++) {
|
|
1265
1226
|
const { agent } = checks[i];
|
|
1266
1227
|
const { status, exitCode } = results[i];
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
if (exitCode !== void 0) agent.exitCode = exitCode;
|
|
1270
|
-
if (["completed", "failed", "lost"].includes(status) && !agent.completedAt) {
|
|
1271
|
-
agent.completedAt = now;
|
|
1272
|
-
}
|
|
1273
|
-
}
|
|
1228
|
+
agent.status = status;
|
|
1229
|
+
if (exitCode !== void 0) agent.exitCode = exitCode;
|
|
1274
1230
|
}
|
|
1275
1231
|
const wtChecks = Object.values(manifest.worktrees).filter((wt) => wt.status === "active").map(async (wt) => {
|
|
1276
1232
|
const exists = await fileExists(wt.path);
|
|
1277
1233
|
if (!exists) {
|
|
1278
1234
|
wt.status = "cleaned";
|
|
1279
1235
|
for (const agent of Object.values(wt.agents)) {
|
|
1280
|
-
|
|
1281
|
-
agent.status = "lost";
|
|
1282
|
-
if (!agent.completedAt) agent.completedAt = now;
|
|
1283
|
-
}
|
|
1236
|
+
agent.status = "gone";
|
|
1284
1237
|
}
|
|
1285
1238
|
}
|
|
1286
1239
|
});
|
|
@@ -1356,7 +1309,6 @@ var init_agent = __esm({
|
|
|
1356
1309
|
init_tmux();
|
|
1357
1310
|
init_manifest();
|
|
1358
1311
|
init_errors();
|
|
1359
|
-
init_template();
|
|
1360
1312
|
init_tmux();
|
|
1361
1313
|
SHELL_COMMANDS = /* @__PURE__ */ new Set(["bash", "zsh", "sh", "fish", "dash", "tcsh", "csh"]);
|
|
1362
1314
|
}
|
|
@@ -1478,6 +1430,12 @@ async function spawnCommand(options) {
|
|
|
1478
1430
|
const count = options.count ?? 1;
|
|
1479
1431
|
const userVars = parseVars(options.var ?? []);
|
|
1480
1432
|
const promptText = await resolvePrompt(options, projectRoot);
|
|
1433
|
+
if (options.branch && options.worktree) {
|
|
1434
|
+
throw new PpgError("--branch and --worktree are mutually exclusive", "INVALID_ARGS");
|
|
1435
|
+
}
|
|
1436
|
+
if (options.branch && options.base) {
|
|
1437
|
+
throw new PpgError("--branch and --base are mutually exclusive (--base is for new branches)", "INVALID_ARGS");
|
|
1438
|
+
}
|
|
1481
1439
|
if (options.worktree) {
|
|
1482
1440
|
await spawnIntoExistingWorktree(
|
|
1483
1441
|
projectRoot,
|
|
@@ -1489,6 +1447,17 @@ async function spawnCommand(options) {
|
|
|
1489
1447
|
options,
|
|
1490
1448
|
userVars
|
|
1491
1449
|
);
|
|
1450
|
+
} else if (options.branch) {
|
|
1451
|
+
await spawnOnExistingBranch(
|
|
1452
|
+
projectRoot,
|
|
1453
|
+
config,
|
|
1454
|
+
agentConfig,
|
|
1455
|
+
options.branch,
|
|
1456
|
+
promptText,
|
|
1457
|
+
count,
|
|
1458
|
+
options,
|
|
1459
|
+
userVars
|
|
1460
|
+
);
|
|
1492
1461
|
} else {
|
|
1493
1462
|
await spawnNewWorktree(
|
|
1494
1463
|
projectRoot,
|
|
@@ -1558,7 +1527,6 @@ async function spawnNewWorktree(projectRoot, config, agentConfig, promptText, co
|
|
|
1558
1527
|
WORKTREE_PATH: wtPath,
|
|
1559
1528
|
BRANCH: branchName,
|
|
1560
1529
|
AGENT_ID: aId,
|
|
1561
|
-
RESULT_FILE: resultFile(projectRoot, aId),
|
|
1562
1530
|
PROJECT_ROOT: projectRoot,
|
|
1563
1531
|
TASK_NAME: name,
|
|
1564
1532
|
PROMPT: promptText
|
|
@@ -1611,6 +1579,102 @@ async function spawnNewWorktree(projectRoot, config, agentConfig, promptText, co
|
|
|
1611
1579
|
info(`Attach: ppg attach ${wtId}`);
|
|
1612
1580
|
}
|
|
1613
1581
|
}
|
|
1582
|
+
async function spawnOnExistingBranch(projectRoot, config, agentConfig, branch, promptText, count, options, userVars) {
|
|
1583
|
+
const baseBranch = await getCurrentBranch(projectRoot);
|
|
1584
|
+
const wtId = worktreeId();
|
|
1585
|
+
const derivedName = branch.startsWith("ppg/") ? branch.slice(4) : branch;
|
|
1586
|
+
const name = options.name ? normalizeName(options.name, wtId) : normalizeName(derivedName, wtId);
|
|
1587
|
+
info(`Creating worktree ${wtId} from existing branch ${branch}`);
|
|
1588
|
+
const wtPath = await adoptWorktree(projectRoot, wtId, branch);
|
|
1589
|
+
await setupWorktreeEnv(projectRoot, wtPath, config);
|
|
1590
|
+
const manifest = await readManifest(projectRoot);
|
|
1591
|
+
const sessionName = manifest.sessionName;
|
|
1592
|
+
await ensureSession(sessionName);
|
|
1593
|
+
const windowTarget = await createWindow(sessionName, name, wtPath);
|
|
1594
|
+
const worktreeEntry = {
|
|
1595
|
+
id: wtId,
|
|
1596
|
+
name,
|
|
1597
|
+
path: wtPath,
|
|
1598
|
+
branch,
|
|
1599
|
+
baseBranch,
|
|
1600
|
+
status: "active",
|
|
1601
|
+
tmuxWindow: windowTarget,
|
|
1602
|
+
agents: {},
|
|
1603
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1604
|
+
};
|
|
1605
|
+
await updateManifest(projectRoot, (m) => {
|
|
1606
|
+
m.worktrees[wtId] = worktreeEntry;
|
|
1607
|
+
return m;
|
|
1608
|
+
});
|
|
1609
|
+
const agents = [];
|
|
1610
|
+
for (let i = 0; i < count; i++) {
|
|
1611
|
+
const aId = agentId();
|
|
1612
|
+
let target;
|
|
1613
|
+
if (i === 0) {
|
|
1614
|
+
target = windowTarget;
|
|
1615
|
+
} else if (options.split) {
|
|
1616
|
+
const direction = i % 2 === 1 ? "horizontal" : "vertical";
|
|
1617
|
+
const pane = await splitPane(windowTarget, direction, wtPath);
|
|
1618
|
+
target = pane.target;
|
|
1619
|
+
} else {
|
|
1620
|
+
target = await createWindow(sessionName, `${name}-${i}`, wtPath);
|
|
1621
|
+
}
|
|
1622
|
+
const ctx = {
|
|
1623
|
+
WORKTREE_PATH: wtPath,
|
|
1624
|
+
BRANCH: branch,
|
|
1625
|
+
AGENT_ID: aId,
|
|
1626
|
+
PROJECT_ROOT: projectRoot,
|
|
1627
|
+
TASK_NAME: name,
|
|
1628
|
+
PROMPT: promptText
|
|
1629
|
+
};
|
|
1630
|
+
Object.assign(ctx, userVars);
|
|
1631
|
+
const renderedPrompt = renderTemplate(promptText, ctx);
|
|
1632
|
+
const agentEntry = await spawnAgent({
|
|
1633
|
+
agentId: aId,
|
|
1634
|
+
agentConfig,
|
|
1635
|
+
prompt: renderedPrompt,
|
|
1636
|
+
worktreePath: wtPath,
|
|
1637
|
+
tmuxTarget: target,
|
|
1638
|
+
projectRoot,
|
|
1639
|
+
branch,
|
|
1640
|
+
sessionId: sessionId()
|
|
1641
|
+
});
|
|
1642
|
+
agents.push(agentEntry);
|
|
1643
|
+
await updateManifest(projectRoot, (m) => {
|
|
1644
|
+
if (m.worktrees[wtId]) {
|
|
1645
|
+
m.worktrees[wtId].agents[agentEntry.id] = agentEntry;
|
|
1646
|
+
}
|
|
1647
|
+
return m;
|
|
1648
|
+
});
|
|
1649
|
+
}
|
|
1650
|
+
if (options.open === true) {
|
|
1651
|
+
openTerminalWindow(sessionName, windowTarget, name).catch(() => {
|
|
1652
|
+
});
|
|
1653
|
+
}
|
|
1654
|
+
if (options.json) {
|
|
1655
|
+
output({
|
|
1656
|
+
success: true,
|
|
1657
|
+
worktree: {
|
|
1658
|
+
id: wtId,
|
|
1659
|
+
name,
|
|
1660
|
+
branch,
|
|
1661
|
+
path: wtPath,
|
|
1662
|
+
tmuxWindow: windowTarget
|
|
1663
|
+
},
|
|
1664
|
+
agents: agents.map((a) => ({
|
|
1665
|
+
id: a.id,
|
|
1666
|
+
tmuxTarget: a.tmuxTarget,
|
|
1667
|
+
sessionId: a.sessionId
|
|
1668
|
+
}))
|
|
1669
|
+
}, true);
|
|
1670
|
+
} else {
|
|
1671
|
+
success(`Spawned worktree ${wtId} from branch ${branch} with ${agents.length} agent(s)`);
|
|
1672
|
+
for (const a of agents) {
|
|
1673
|
+
info(` Agent ${a.id} \u2192 ${a.tmuxTarget}`);
|
|
1674
|
+
}
|
|
1675
|
+
info(`Attach: ppg attach ${wtId}`);
|
|
1676
|
+
}
|
|
1677
|
+
}
|
|
1614
1678
|
async function spawnIntoExistingWorktree(projectRoot, config, agentConfig, worktreeRef, promptText, count, options, userVars) {
|
|
1615
1679
|
const manifest = await readManifest(projectRoot);
|
|
1616
1680
|
const wt = resolveWorktree(manifest, worktreeRef);
|
|
@@ -1637,7 +1701,6 @@ async function spawnIntoExistingWorktree(projectRoot, config, agentConfig, workt
|
|
|
1637
1701
|
WORKTREE_PATH: wt.path,
|
|
1638
1702
|
BRANCH: wt.branch,
|
|
1639
1703
|
AGENT_ID: aId,
|
|
1640
|
-
RESULT_FILE: resultFile(projectRoot, aId),
|
|
1641
1704
|
PROJECT_ROOT: projectRoot,
|
|
1642
1705
|
TASK_NAME: wt.name,
|
|
1643
1706
|
PROMPT: promptText
|
|
@@ -1713,6 +1776,7 @@ var init_spawn = __esm({
|
|
|
1713
1776
|
// src/commands/status.ts
|
|
1714
1777
|
var status_exports = {};
|
|
1715
1778
|
__export(status_exports, {
|
|
1779
|
+
computeLifecycle: () => computeLifecycle,
|
|
1716
1780
|
statusCommand: () => statusCommand
|
|
1717
1781
|
});
|
|
1718
1782
|
async function statusCommand(worktreeFilter, options) {
|
|
@@ -1731,7 +1795,7 @@ async function statusCommand(worktreeFilter, options) {
|
|
|
1731
1795
|
if (options?.json) {
|
|
1732
1796
|
output({
|
|
1733
1797
|
session: manifest.sessionName,
|
|
1734
|
-
worktrees: Object.fromEntries(worktrees.map((wt) => [wt.id, wt]))
|
|
1798
|
+
worktrees: Object.fromEntries(worktrees.map((wt) => [wt.id, { ...wt, lifecycle: computeLifecycle(wt) }]))
|
|
1735
1799
|
}, true);
|
|
1736
1800
|
return;
|
|
1737
1801
|
}
|
|
@@ -1770,12 +1834,6 @@ async function statusCommand(worktreeFilter, options) {
|
|
|
1770
1834
|
}
|
|
1771
1835
|
function printWorktreeStatus(wt) {
|
|
1772
1836
|
const agents = Object.values(wt.agents);
|
|
1773
|
-
const statusCounts = {
|
|
1774
|
-
running: agents.filter((a) => a.status === "running").length,
|
|
1775
|
-
completed: agents.filter((a) => a.status === "completed").length,
|
|
1776
|
-
failed: agents.filter((a) => a.status === "failed").length,
|
|
1777
|
-
lost: agents.filter((a) => a.status === "lost").length
|
|
1778
|
-
};
|
|
1779
1837
|
console.log(
|
|
1780
1838
|
`
|
|
1781
1839
|
${wt.name} (${wt.id}) [${formatStatus(wt.status)}] branch:${wt.branch}`
|
|
@@ -1799,6 +1857,14 @@ ${wt.name} (${wt.id}) [${formatStatus(wt.status)}] branch:${wt.branch}`
|
|
|
1799
1857
|
const table = formatTable(agents, columns);
|
|
1800
1858
|
console.log(table.split("\n").map((l) => ` ${l}`).join("\n"));
|
|
1801
1859
|
}
|
|
1860
|
+
function computeLifecycle(wt) {
|
|
1861
|
+
if (wt.status === "merged") return "merged";
|
|
1862
|
+
if (wt.status === "cleaned") return "cleaned";
|
|
1863
|
+
const agents = Object.values(wt.agents);
|
|
1864
|
+
if (agents.some((a) => a.status === "running")) return "busy";
|
|
1865
|
+
if (wt.prUrl) return "shipped";
|
|
1866
|
+
return "idle";
|
|
1867
|
+
}
|
|
1802
1868
|
function formatTime(iso) {
|
|
1803
1869
|
if (!iso) return "\u2014";
|
|
1804
1870
|
const d = new Date(iso);
|
|
@@ -1999,7 +2065,7 @@ async function killSingleAgent(projectRoot, agentId2, options, selfPaneId, paneM
|
|
|
1999
2065
|
const found = findAgent(manifest, agentId2);
|
|
2000
2066
|
if (!found) throw new AgentNotFoundError(agentId2);
|
|
2001
2067
|
const { agent } = found;
|
|
2002
|
-
const isTerminal =
|
|
2068
|
+
const isTerminal = agent.status !== "running";
|
|
2003
2069
|
if (selfPaneId && paneMap) {
|
|
2004
2070
|
const { skipped } = excludeSelf([agent], selfPaneId, paneMap);
|
|
2005
2071
|
if (skipped.length > 0) {
|
|
@@ -2042,8 +2108,7 @@ async function killSingleAgent(projectRoot, agentId2, options, selfPaneId, paneM
|
|
|
2042
2108
|
await updateManifest(projectRoot, (m) => {
|
|
2043
2109
|
const f = findAgent(m, agentId2);
|
|
2044
2110
|
if (f) {
|
|
2045
|
-
f.agent.status = "
|
|
2046
|
-
f.agent.completedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
2111
|
+
f.agent.status = "gone";
|
|
2047
2112
|
}
|
|
2048
2113
|
return m;
|
|
2049
2114
|
});
|
|
@@ -2058,7 +2123,7 @@ async function killWorktreeAgents(projectRoot, worktreeRef, options, selfPaneId,
|
|
|
2058
2123
|
const manifest = await readManifest(projectRoot);
|
|
2059
2124
|
const wt = resolveWorktree(manifest, worktreeRef);
|
|
2060
2125
|
if (!wt) throw new WorktreeNotFoundError(worktreeRef);
|
|
2061
|
-
let toKill = Object.values(wt.agents).filter((a) =>
|
|
2126
|
+
let toKill = Object.values(wt.agents).filter((a) => a.status === "running");
|
|
2062
2127
|
const skippedIds = [];
|
|
2063
2128
|
if (selfPaneId && paneMap) {
|
|
2064
2129
|
const { safe, skipped } = excludeSelf(toKill, selfPaneId, paneMap);
|
|
@@ -2076,8 +2141,7 @@ async function killWorktreeAgents(projectRoot, worktreeRef, options, selfPaneId,
|
|
|
2076
2141
|
if (mWt) {
|
|
2077
2142
|
for (const agent of Object.values(mWt.agents)) {
|
|
2078
2143
|
if (killedIds.includes(agent.id)) {
|
|
2079
|
-
agent.status = "
|
|
2080
|
-
agent.completedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
2144
|
+
agent.status = "gone";
|
|
2081
2145
|
}
|
|
2082
2146
|
}
|
|
2083
2147
|
}
|
|
@@ -2127,7 +2191,7 @@ async function killAllAgents(projectRoot, options, selfPaneId, paneMap) {
|
|
|
2127
2191
|
let toKill = [];
|
|
2128
2192
|
for (const wt of Object.values(manifest.worktrees)) {
|
|
2129
2193
|
for (const agent of Object.values(wt.agents)) {
|
|
2130
|
-
if (
|
|
2194
|
+
if (agent.status === "running") {
|
|
2131
2195
|
toKill.push(agent);
|
|
2132
2196
|
}
|
|
2133
2197
|
}
|
|
@@ -2149,8 +2213,7 @@ async function killAllAgents(projectRoot, options, selfPaneId, paneMap) {
|
|
|
2149
2213
|
for (const wt of Object.values(m.worktrees)) {
|
|
2150
2214
|
for (const agent of Object.values(wt.agents)) {
|
|
2151
2215
|
if (killedIds.includes(agent.id)) {
|
|
2152
|
-
agent.status = "
|
|
2153
|
-
agent.completedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
2216
|
+
agent.status = "gone";
|
|
2154
2217
|
}
|
|
2155
2218
|
}
|
|
2156
2219
|
}
|
|
@@ -2396,13 +2459,12 @@ async function aggregateCommand(worktreeId2, options) {
|
|
|
2396
2459
|
worktrees = [wt];
|
|
2397
2460
|
} else {
|
|
2398
2461
|
worktrees = Object.values(manifest.worktrees).filter(
|
|
2399
|
-
(wt) => Object.values(wt.agents).some((a) => a.status
|
|
2462
|
+
(wt) => Object.values(wt.agents).some((a) => a.status !== "running")
|
|
2400
2463
|
);
|
|
2401
2464
|
}
|
|
2402
2465
|
const results = [];
|
|
2403
2466
|
for (const wt of worktrees) {
|
|
2404
2467
|
for (const agent of Object.values(wt.agents)) {
|
|
2405
|
-
if (agent.status !== "completed" && agent.status !== "failed") continue;
|
|
2406
2468
|
const result = await collectAgentResult(agent, projectRoot);
|
|
2407
2469
|
results.push({
|
|
2408
2470
|
agentId: agent.id,
|
|
@@ -2418,7 +2480,7 @@ async function aggregateCommand(worktreeId2, options) {
|
|
|
2418
2480
|
if (options?.json) {
|
|
2419
2481
|
output({ results: [] }, true);
|
|
2420
2482
|
} else {
|
|
2421
|
-
console.log("No
|
|
2483
|
+
console.log("No agent results to aggregate.");
|
|
2422
2484
|
}
|
|
2423
2485
|
return;
|
|
2424
2486
|
}
|
|
@@ -2447,20 +2509,18 @@ async function aggregateCommand(worktreeId2, options) {
|
|
|
2447
2509
|
}
|
|
2448
2510
|
}
|
|
2449
2511
|
async function collectAgentResult(agent, projectRoot) {
|
|
2450
|
-
try {
|
|
2451
|
-
const content = await fs9.readFile(agent.resultFile, "utf-8");
|
|
2452
|
-
return content;
|
|
2453
|
-
} catch {
|
|
2454
|
-
}
|
|
2455
2512
|
try {
|
|
2456
2513
|
const paneContent = await capturePane(agent.tmuxTarget, 500);
|
|
2457
|
-
return
|
|
2458
|
-
|
|
2459
|
-
\`\`\`
|
|
2514
|
+
return `\`\`\`
|
|
2460
2515
|
${paneContent}
|
|
2461
2516
|
\`\`\``;
|
|
2462
2517
|
} catch {
|
|
2463
|
-
|
|
2518
|
+
}
|
|
2519
|
+
try {
|
|
2520
|
+
const content = await fs9.readFile(resultFile(projectRoot, agent.id), "utf-8");
|
|
2521
|
+
return content;
|
|
2522
|
+
} catch {
|
|
2523
|
+
return "*[Pane not available and no legacy result file]*";
|
|
2464
2524
|
}
|
|
2465
2525
|
}
|
|
2466
2526
|
var init_aggregate = __esm({
|
|
@@ -2470,6 +2530,7 @@ var init_aggregate = __esm({
|
|
|
2470
2530
|
init_agent();
|
|
2471
2531
|
init_worktree();
|
|
2472
2532
|
init_tmux();
|
|
2533
|
+
init_paths();
|
|
2473
2534
|
init_errors();
|
|
2474
2535
|
init_output();
|
|
2475
2536
|
}
|
|
@@ -2490,7 +2551,7 @@ async function mergeCommand(worktreeId2, options) {
|
|
|
2490
2551
|
const wt = resolveWorktree(manifest, worktreeId2);
|
|
2491
2552
|
if (!wt) throw new WorktreeNotFoundError(worktreeId2);
|
|
2492
2553
|
const agents = Object.values(wt.agents);
|
|
2493
|
-
const incomplete = agents.filter((a) =>
|
|
2554
|
+
const incomplete = agents.filter((a) => a.status === "running");
|
|
2494
2555
|
if (incomplete.length > 0 && !options.force) {
|
|
2495
2556
|
const ids = incomplete.map((a) => a.id).join(", ");
|
|
2496
2557
|
throw new PpgError(
|
|
@@ -2753,7 +2814,6 @@ async function spawnSwarmAgent(opts) {
|
|
|
2753
2814
|
WORKTREE_PATH: wtPath,
|
|
2754
2815
|
BRANCH: branchName,
|
|
2755
2816
|
AGENT_ID: aId,
|
|
2756
|
-
RESULT_FILE: resultFile(projectRoot, aId),
|
|
2757
2817
|
PROJECT_ROOT: projectRoot,
|
|
2758
2818
|
TASK_NAME: taskName,
|
|
2759
2819
|
...swarmAgent.vars ?? {},
|
|
@@ -3213,7 +3273,7 @@ async function restartCommand(agentRef, options) {
|
|
|
3213
3273
|
const found = findAgent(manifest, agentRef);
|
|
3214
3274
|
if (!found) throw new AgentNotFoundError(agentRef);
|
|
3215
3275
|
const { worktree: wt, agent: oldAgent } = found;
|
|
3216
|
-
if (
|
|
3276
|
+
if (oldAgent.status === "running") {
|
|
3217
3277
|
info(`Killing existing agent ${oldAgent.id}`);
|
|
3218
3278
|
await killAgent(oldAgent);
|
|
3219
3279
|
}
|
|
@@ -3239,7 +3299,6 @@ async function restartCommand(agentRef, options) {
|
|
|
3239
3299
|
WORKTREE_PATH: wt.path,
|
|
3240
3300
|
BRANCH: wt.branch,
|
|
3241
3301
|
AGENT_ID: newAgentId,
|
|
3242
|
-
RESULT_FILE: resultFile(projectRoot, newAgentId),
|
|
3243
3302
|
PROJECT_ROOT: projectRoot,
|
|
3244
3303
|
TASK_NAME: wt.name,
|
|
3245
3304
|
PROMPT: promptText
|
|
@@ -3254,16 +3313,14 @@ async function restartCommand(agentRef, options) {
|
|
|
3254
3313
|
tmuxTarget: windowTarget,
|
|
3255
3314
|
projectRoot,
|
|
3256
3315
|
branch: wt.branch,
|
|
3257
|
-
sessionId: newSessionId
|
|
3258
|
-
skipResultInstructions: !options.prompt
|
|
3316
|
+
sessionId: newSessionId
|
|
3259
3317
|
});
|
|
3260
3318
|
await updateManifest(projectRoot, (m) => {
|
|
3261
3319
|
const mWt = m.worktrees[wt.id];
|
|
3262
3320
|
if (mWt) {
|
|
3263
3321
|
const mOldAgent = mWt.agents[oldAgent.id];
|
|
3264
|
-
if (mOldAgent &&
|
|
3265
|
-
mOldAgent.status = "
|
|
3266
|
-
mOldAgent.completedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
3322
|
+
if (mOldAgent && mOldAgent.status === "running") {
|
|
3323
|
+
mOldAgent.status = "gone";
|
|
3267
3324
|
}
|
|
3268
3325
|
mWt.agents[newAgentId] = agentEntry;
|
|
3269
3326
|
}
|
|
@@ -3366,7 +3423,6 @@ __export(pr_exports, {
|
|
|
3366
3423
|
prCommand: () => prCommand,
|
|
3367
3424
|
truncateBody: () => truncateBody
|
|
3368
3425
|
});
|
|
3369
|
-
import fs15 from "fs/promises";
|
|
3370
3426
|
import { execa as execa8 } from "execa";
|
|
3371
3427
|
async function prCommand(worktreeRef, options) {
|
|
3372
3428
|
const projectRoot = await getRepoRoot();
|
|
@@ -3441,16 +3497,11 @@ async function prCommand(worktreeRef, options) {
|
|
|
3441
3497
|
}
|
|
3442
3498
|
}
|
|
3443
3499
|
async function buildBodyFromResults(agents) {
|
|
3444
|
-
|
|
3445
|
-
|
|
3446
|
-
|
|
3447
|
-
|
|
3448
|
-
|
|
3449
|
-
}
|
|
3450
|
-
});
|
|
3451
|
-
const contents = (await Promise.all(reads)).filter((c) => c !== null);
|
|
3452
|
-
if (contents.length === 0) return "";
|
|
3453
|
-
return truncateBody(contents.join("\n\n---\n\n"));
|
|
3500
|
+
if (agents.length === 0) return "";
|
|
3501
|
+
const sections = agents.map((a) => `## Agent: ${a.id}
|
|
3502
|
+
|
|
3503
|
+
${a.prompt}`);
|
|
3504
|
+
return truncateBody(sections.join("\n\n---\n\n"));
|
|
3454
3505
|
}
|
|
3455
3506
|
function truncateBody(body) {
|
|
3456
3507
|
if (body.length <= MAX_BODY_LENGTH) return body;
|
|
@@ -3480,7 +3531,7 @@ function findAtRiskWorktrees(worktrees) {
|
|
|
3480
3531
|
return worktrees.filter((wt) => {
|
|
3481
3532
|
if (wt.status === "merged" || wt.status === "cleaned") return false;
|
|
3482
3533
|
if (wt.prUrl) return false;
|
|
3483
|
-
return Object.values(wt.agents).some((a) => a.status === "
|
|
3534
|
+
return Object.values(wt.agents).some((a) => a.status === "idle" || a.status === "exited");
|
|
3484
3535
|
});
|
|
3485
3536
|
}
|
|
3486
3537
|
async function resetCommand(options) {
|
|
@@ -3517,7 +3568,7 @@ async function resetCommand(options) {
|
|
|
3517
3568
|
const allAgents = [];
|
|
3518
3569
|
for (const wt of worktrees) {
|
|
3519
3570
|
for (const agent of Object.values(wt.agents)) {
|
|
3520
|
-
if (
|
|
3571
|
+
if (agent.status === "running") {
|
|
3521
3572
|
allAgents.push(agent);
|
|
3522
3573
|
}
|
|
3523
3574
|
}
|
|
@@ -3539,12 +3590,10 @@ async function resetCommand(options) {
|
|
|
3539
3590
|
}
|
|
3540
3591
|
if (killedIds.length > 0) {
|
|
3541
3592
|
await updateManifest(projectRoot, (m) => {
|
|
3542
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3543
3593
|
for (const wt of Object.values(m.worktrees)) {
|
|
3544
3594
|
for (const agent of Object.values(wt.agents)) {
|
|
3545
3595
|
if (killedIds.includes(agent.id)) {
|
|
3546
|
-
agent.status = "
|
|
3547
|
-
agent.completedAt = now;
|
|
3596
|
+
agent.status = "gone";
|
|
3548
3597
|
}
|
|
3549
3598
|
}
|
|
3550
3599
|
}
|
|
@@ -3847,9 +3896,9 @@ async function waitCommand(worktreeRef, options) {
|
|
|
3847
3896
|
}
|
|
3848
3897
|
const manifest = await refreshAndGet(projectRoot);
|
|
3849
3898
|
const agents = collectAgents(manifest, worktreeRef, options.all);
|
|
3850
|
-
const
|
|
3851
|
-
if (
|
|
3852
|
-
const anyFailed = agents.some((a) =>
|
|
3899
|
+
const allDone = agents.every((a) => a.status !== "running");
|
|
3900
|
+
if (allDone) {
|
|
3901
|
+
const anyFailed = agents.some((a) => a.status === "exited" && a.exitCode !== void 0 && a.exitCode !== 0);
|
|
3853
3902
|
if (options.json) {
|
|
3854
3903
|
output({
|
|
3855
3904
|
timedOut: false,
|
|
@@ -3892,11 +3941,9 @@ function formatAgent(a) {
|
|
|
3892
3941
|
status: a.status,
|
|
3893
3942
|
agentType: a.agentType,
|
|
3894
3943
|
exitCode: a.exitCode,
|
|
3895
|
-
startedAt: a.startedAt
|
|
3896
|
-
completedAt: a.completedAt
|
|
3944
|
+
startedAt: a.startedAt
|
|
3897
3945
|
};
|
|
3898
3946
|
}
|
|
3899
|
-
var TERMINAL_STATUSES;
|
|
3900
3947
|
var init_wait = __esm({
|
|
3901
3948
|
"src/commands/wait.ts"() {
|
|
3902
3949
|
"use strict";
|
|
@@ -3905,7 +3952,6 @@ var init_wait = __esm({
|
|
|
3905
3952
|
init_worktree();
|
|
3906
3953
|
init_errors();
|
|
3907
3954
|
init_output();
|
|
3908
|
-
TERMINAL_STATUSES = ["completed", "failed", "killed", "lost"];
|
|
3909
3955
|
}
|
|
3910
3956
|
});
|
|
3911
3957
|
|
|
@@ -3915,18 +3961,34 @@ __export(worktree_exports, {
|
|
|
3915
3961
|
worktreeCreateCommand: () => worktreeCreateCommand
|
|
3916
3962
|
});
|
|
3917
3963
|
async function worktreeCreateCommand(options) {
|
|
3964
|
+
if (options.branch && options.base) {
|
|
3965
|
+
throw new PpgError("--branch and --base are mutually exclusive (--base is for new branches)", "INVALID_ARGS");
|
|
3966
|
+
}
|
|
3918
3967
|
const projectRoot = await getRepoRoot();
|
|
3919
3968
|
const config = await loadConfig(projectRoot);
|
|
3920
3969
|
await requireManifest(projectRoot);
|
|
3921
|
-
const baseBranch = options.base ?? await getCurrentBranch(projectRoot);
|
|
3922
3970
|
const wtId = worktreeId();
|
|
3923
|
-
|
|
3924
|
-
|
|
3925
|
-
|
|
3926
|
-
|
|
3927
|
-
|
|
3928
|
-
|
|
3929
|
-
|
|
3971
|
+
let name;
|
|
3972
|
+
let branchName;
|
|
3973
|
+
let baseBranch;
|
|
3974
|
+
let wtPath;
|
|
3975
|
+
if (options.branch) {
|
|
3976
|
+
branchName = options.branch;
|
|
3977
|
+
const derivedName = branchName.startsWith("ppg/") ? branchName.slice(4) : branchName;
|
|
3978
|
+
name = options.name ? normalizeName(options.name, wtId) : normalizeName(derivedName, wtId);
|
|
3979
|
+
baseBranch = await getCurrentBranch(projectRoot);
|
|
3980
|
+
info(`Creating worktree ${wtId} from existing branch ${branchName}`);
|
|
3981
|
+
wtPath = await adoptWorktree(projectRoot, wtId, branchName);
|
|
3982
|
+
} else {
|
|
3983
|
+
baseBranch = options.base ?? await getCurrentBranch(projectRoot);
|
|
3984
|
+
name = options.name ? normalizeName(options.name, wtId) : wtId;
|
|
3985
|
+
branchName = `ppg/${name}`;
|
|
3986
|
+
info(`Creating worktree ${wtId} on branch ${branchName}`);
|
|
3987
|
+
wtPath = await createWorktree(projectRoot, wtId, {
|
|
3988
|
+
branch: branchName,
|
|
3989
|
+
base: baseBranch
|
|
3990
|
+
});
|
|
3991
|
+
}
|
|
3930
3992
|
await setupWorktreeEnv(projectRoot, wtPath, config);
|
|
3931
3993
|
const worktreeEntry = {
|
|
3932
3994
|
id: wtId,
|
|
@@ -3968,6 +4030,7 @@ var init_worktree2 = __esm({
|
|
|
3968
4030
|
init_worktree();
|
|
3969
4031
|
init_env2();
|
|
3970
4032
|
init_id();
|
|
4033
|
+
init_errors();
|
|
3971
4034
|
init_output();
|
|
3972
4035
|
init_name();
|
|
3973
4036
|
}
|
|
@@ -4162,14 +4225,14 @@ var init_install_dashboard = __esm({
|
|
|
4162
4225
|
});
|
|
4163
4226
|
|
|
4164
4227
|
// src/core/schedule.ts
|
|
4165
|
-
import
|
|
4228
|
+
import fs15 from "fs/promises";
|
|
4166
4229
|
import YAML3 from "yaml";
|
|
4167
4230
|
import { CronExpressionParser } from "cron-parser";
|
|
4168
4231
|
async function loadSchedules(projectRoot) {
|
|
4169
4232
|
const filePath = schedulesPath(projectRoot);
|
|
4170
4233
|
let raw;
|
|
4171
4234
|
try {
|
|
4172
|
-
raw = await
|
|
4235
|
+
raw = await fs15.readFile(filePath, "utf-8");
|
|
4173
4236
|
} catch (err) {
|
|
4174
4237
|
if (err.code === "ENOENT") {
|
|
4175
4238
|
throw new PpgError("No schedules file found. Create .ppg/schedules.yaml first.", "INVALID_ARGS");
|
|
@@ -4254,16 +4317,16 @@ var init_schedule = __esm({
|
|
|
4254
4317
|
});
|
|
4255
4318
|
|
|
4256
4319
|
// src/core/cron.ts
|
|
4257
|
-
import
|
|
4320
|
+
import fs16 from "fs/promises";
|
|
4258
4321
|
import { createReadStream } from "fs";
|
|
4259
4322
|
import path11 from "path";
|
|
4260
4323
|
import readline from "readline";
|
|
4261
4324
|
import { execa as execa11 } from "execa";
|
|
4262
4325
|
async function runCronDaemon(projectRoot) {
|
|
4263
4326
|
const pidPath = cronPidPath(projectRoot);
|
|
4264
|
-
await
|
|
4265
|
-
await
|
|
4266
|
-
await
|
|
4327
|
+
await fs16.mkdir(path11.dirname(pidPath), { recursive: true });
|
|
4328
|
+
await fs16.writeFile(pidPath, String(process.pid), "utf-8");
|
|
4329
|
+
await fs16.mkdir(logsDir(projectRoot), { recursive: true });
|
|
4267
4330
|
await logCron(projectRoot, "Cron daemon starting");
|
|
4268
4331
|
let states = await loadScheduleStates(projectRoot);
|
|
4269
4332
|
let lastConfigMtime = await getFileMtime(schedulesPath(projectRoot));
|
|
@@ -4274,7 +4337,7 @@ async function runCronDaemon(projectRoot) {
|
|
|
4274
4337
|
const cleanup = async () => {
|
|
4275
4338
|
await logCron(projectRoot, "Cron daemon stopping");
|
|
4276
4339
|
try {
|
|
4277
|
-
await
|
|
4340
|
+
await fs16.unlink(pidPath);
|
|
4278
4341
|
} catch {
|
|
4279
4342
|
}
|
|
4280
4343
|
process.exit(0);
|
|
@@ -4318,7 +4381,7 @@ async function loadScheduleStates(projectRoot) {
|
|
|
4318
4381
|
}
|
|
4319
4382
|
async function getFileMtime(filePath) {
|
|
4320
4383
|
try {
|
|
4321
|
-
const stat = await
|
|
4384
|
+
const stat = await fs16.stat(filePath);
|
|
4322
4385
|
return stat.mtimeMs;
|
|
4323
4386
|
} catch {
|
|
4324
4387
|
return 0;
|
|
@@ -4373,10 +4436,10 @@ async function logCron(projectRoot, message) {
|
|
|
4373
4436
|
`;
|
|
4374
4437
|
process.stdout.write(line);
|
|
4375
4438
|
try {
|
|
4376
|
-
await
|
|
4439
|
+
await fs16.appendFile(logPath, line, "utf-8");
|
|
4377
4440
|
} catch {
|
|
4378
|
-
await
|
|
4379
|
-
await
|
|
4441
|
+
await fs16.mkdir(logsDir(projectRoot), { recursive: true });
|
|
4442
|
+
await fs16.appendFile(logPath, line, "utf-8");
|
|
4380
4443
|
}
|
|
4381
4444
|
}
|
|
4382
4445
|
async function isCronRunning(projectRoot) {
|
|
@@ -4386,7 +4449,7 @@ async function getCronPid(projectRoot) {
|
|
|
4386
4449
|
const pidPath = cronPidPath(projectRoot);
|
|
4387
4450
|
let raw;
|
|
4388
4451
|
try {
|
|
4389
|
-
raw = await
|
|
4452
|
+
raw = await fs16.readFile(pidPath, "utf-8");
|
|
4390
4453
|
} catch {
|
|
4391
4454
|
return null;
|
|
4392
4455
|
}
|
|
@@ -4405,14 +4468,14 @@ async function getCronPid(projectRoot) {
|
|
|
4405
4468
|
}
|
|
4406
4469
|
async function cleanupPidFile(pidPath) {
|
|
4407
4470
|
try {
|
|
4408
|
-
await
|
|
4471
|
+
await fs16.unlink(pidPath);
|
|
4409
4472
|
} catch {
|
|
4410
4473
|
}
|
|
4411
4474
|
}
|
|
4412
4475
|
async function readCronLog(projectRoot, lines = 20) {
|
|
4413
4476
|
const logPath = cronLogPath(projectRoot);
|
|
4414
4477
|
try {
|
|
4415
|
-
await
|
|
4478
|
+
await fs16.access(logPath);
|
|
4416
4479
|
} catch {
|
|
4417
4480
|
return [];
|
|
4418
4481
|
}
|
|
@@ -4449,7 +4512,7 @@ __export(cron_exports, {
|
|
|
4449
4512
|
cronStatusCommand: () => cronStatusCommand,
|
|
4450
4513
|
cronStopCommand: () => cronStopCommand
|
|
4451
4514
|
});
|
|
4452
|
-
import
|
|
4515
|
+
import fs17 from "fs/promises";
|
|
4453
4516
|
async function cronStartCommand(options) {
|
|
4454
4517
|
const projectRoot = await getRepoRoot();
|
|
4455
4518
|
await requireInit(projectRoot);
|
|
@@ -4500,7 +4563,7 @@ async function cronStopCommand(options) {
|
|
|
4500
4563
|
} catch {
|
|
4501
4564
|
}
|
|
4502
4565
|
try {
|
|
4503
|
-
await
|
|
4566
|
+
await fs17.unlink(cronPidPath(projectRoot));
|
|
4504
4567
|
} catch {
|
|
4505
4568
|
}
|
|
4506
4569
|
try {
|
|
@@ -4586,7 +4649,7 @@ async function cronDaemonCommand() {
|
|
|
4586
4649
|
}
|
|
4587
4650
|
async function requireInit(projectRoot) {
|
|
4588
4651
|
try {
|
|
4589
|
-
await
|
|
4652
|
+
await fs17.access(manifestPath(projectRoot));
|
|
4590
4653
|
} catch {
|
|
4591
4654
|
throw new NotInitializedError(projectRoot);
|
|
4592
4655
|
}
|
|
@@ -4620,7 +4683,7 @@ program.command("init").description("Initialize Point Guard in the current git r
|
|
|
4620
4683
|
const { initCommand: initCommand2 } = await Promise.resolve().then(() => (init_init(), init_exports));
|
|
4621
4684
|
await initCommand2(options);
|
|
4622
4685
|
});
|
|
4623
|
-
program.command("spawn").description("Spawn a new worktree and agent(s), or add agents to an existing worktree").option("-n, --name <name>", "Name for the worktree/task").option("-a, --agent <type>", "Agent type to use (default: claude)").option("-p, --prompt <text>", "Prompt text for the agent").option("-f, --prompt-file <path>", "File containing the prompt").option("-t, --template <name>", "Template name from .ppg/templates/").option("--var <key=value...>", "Template variables", collectVars, []).option("-b, --base <branch>", "Base branch for the worktree").option("-w, --worktree <id>", "Add agent to existing worktree").option("-c, --count <n>", "Number of agents to spawn", parsePositiveInt("count"), 1).option("--split", "Put all agents in one window as split panes").option("--open", "Open a Terminal window for the spawned agents").option("--json", "Output as JSON").action(async (options) => {
|
|
4686
|
+
program.command("spawn").description("Spawn a new worktree and agent(s), or add agents to an existing worktree").option("-n, --name <name>", "Name for the worktree/task").option("-a, --agent <type>", "Agent type to use (default: claude)").option("-p, --prompt <text>", "Prompt text for the agent").option("-f, --prompt-file <path>", "File containing the prompt").option("-t, --template <name>", "Template name from .ppg/templates/").option("--var <key=value...>", "Template variables", collectVars, []).option("-b, --base <branch>", "Base branch for the worktree").option("--branch <name>", "Check out an existing branch into a new worktree").option("-w, --worktree <id>", "Add agent to existing worktree").option("-c, --count <n>", "Number of agents to spawn", parsePositiveInt("count"), 1).option("--split", "Put all agents in one window as split panes").option("--open", "Open a Terminal window for the spawned agents").option("--json", "Output as JSON").action(async (options) => {
|
|
4624
4687
|
const { spawnCommand: spawnCommand2 } = await Promise.resolve().then(() => (init_spawn(), spawn_exports));
|
|
4625
4688
|
await spawnCommand2(options);
|
|
4626
4689
|
});
|
|
@@ -4640,11 +4703,11 @@ program.command("logs").description("View agent pane output").argument("<agent-i
|
|
|
4640
4703
|
const { logsCommand: logsCommand2 } = await Promise.resolve().then(() => (init_logs(), logs_exports));
|
|
4641
4704
|
await logsCommand2(agentId2, options);
|
|
4642
4705
|
});
|
|
4643
|
-
program.command("aggregate").description("Aggregate results from
|
|
4706
|
+
program.command("aggregate").description("Aggregate results from agents (captures pane output)").argument("[worktree-id]", "Worktree ID to aggregate results from").option("--all", "Aggregate from all worktrees").option("-o, --output <file>", "Write output to file").option("--json", "Output as JSON").action(async (worktreeId2, options) => {
|
|
4644
4707
|
const { aggregateCommand: aggregateCommand2 } = await Promise.resolve().then(() => (init_aggregate(), aggregate_exports));
|
|
4645
4708
|
await aggregateCommand2(worktreeId2, options);
|
|
4646
4709
|
});
|
|
4647
|
-
program.command("merge").description("Merge a worktree branch back into base").argument("<worktree-id>", "Worktree ID to merge").option("-s, --strategy <strategy>", "Merge strategy: squash or no-ff", "squash").option("--no-cleanup", "Do not remove worktree after merge").option("--dry-run", "Show what would be done without doing it").option("--force", "Merge even if agents are
|
|
4710
|
+
program.command("merge").description("Merge a worktree branch back into base").argument("<worktree-id>", "Worktree ID to merge").option("-s, --strategy <strategy>", "Merge strategy: squash or no-ff", "squash").option("--no-cleanup", "Do not remove worktree after merge").option("--dry-run", "Show what would be done without doing it").option("--force", "Merge even if agents are still running").option("--json", "Output as JSON").action(async (worktreeId2, options) => {
|
|
4648
4711
|
const { mergeCommand: mergeCommand2 } = await Promise.resolve().then(() => (init_merge(), merge_exports));
|
|
4649
4712
|
await mergeCommand2(worktreeId2, options);
|
|
4650
4713
|
});
|
|
@@ -4660,7 +4723,7 @@ program.command("list").description("List available templates, swarms, or prompt
|
|
|
4660
4723
|
const { listCommand: listCommand2 } = await Promise.resolve().then(() => (init_list(), list_exports));
|
|
4661
4724
|
await listCommand2(type, options);
|
|
4662
4725
|
});
|
|
4663
|
-
program.command("restart").description("Restart
|
|
4726
|
+
program.command("restart").description("Restart an agent in the same worktree").argument("<agent-id>", "Agent ID to restart").option("-p, --prompt <text>", "Override the original prompt").option("-a, --agent <type>", "Override the agent type").option("--open", "Open a Terminal window for the restarted agent").option("--json", "Output as JSON").action(async (agentId2, options) => {
|
|
4664
4727
|
const { restartCommand: restartCommand2 } = await Promise.resolve().then(() => (init_restart(), restart_exports));
|
|
4665
4728
|
await restartCommand2(agentId2, options);
|
|
4666
4729
|
});
|
|
@@ -4689,7 +4752,7 @@ program.command("wait").description("Wait for agents to reach terminal state").a
|
|
|
4689
4752
|
await waitCommand2(worktreeId2, options);
|
|
4690
4753
|
});
|
|
4691
4754
|
var worktreeCmd = program.command("worktree").description("Manage worktrees");
|
|
4692
|
-
worktreeCmd.command("create").description("Create a standalone worktree without spawning agents").option("-n, --name <name>", "Name for the worktree").option("-b, --base <branch>", "Base branch for the worktree").option("--json", "Output as JSON").action(async (options) => {
|
|
4755
|
+
worktreeCmd.command("create").description("Create a standalone worktree without spawning agents").option("-n, --name <name>", "Name for the worktree").option("-b, --base <branch>", "Base branch for the worktree").option("--branch <name>", "Check out an existing branch into a new worktree").option("--json", "Output as JSON").action(async (options) => {
|
|
4693
4756
|
const { worktreeCreateCommand: worktreeCreateCommand2 } = await Promise.resolve().then(() => (init_worktree2(), worktree_exports));
|
|
4694
4757
|
await worktreeCreateCommand2(options);
|
|
4695
4758
|
});
|