squadrant 0.16.5 → 0.16.6
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/index.js +108 -34
- package/dist/index.js.map +1 -1
- package/dist/squadrantd.js +91 -33
- package/dist/squadrantd.js.map +1 -1
- package/package.json +1 -1
- package/plugin/skills/captain-ops/SKILL.md +9 -3
package/dist/index.js
CHANGED
|
@@ -1323,6 +1323,11 @@ function nextPendingTool(current, ev, now) {
|
|
|
1323
1323
|
return void 0;
|
|
1324
1324
|
return current;
|
|
1325
1325
|
}
|
|
1326
|
+
function nextPendingMonitor(current, ev, now) {
|
|
1327
|
+
if (ev.note === "agent.hook.PreToolUse" && ev.tool === "Monitor")
|
|
1328
|
+
return { since: now };
|
|
1329
|
+
return current;
|
|
1330
|
+
}
|
|
1326
1331
|
function reduce(rec, ev, now) {
|
|
1327
1332
|
if (ev.type === "task.reopened") {
|
|
1328
1333
|
return { ...rec, state: "working", question: void 0, error: void 0, lastHeartbeat: now, lastEvent: ev.type };
|
|
@@ -1339,14 +1344,17 @@ function reduce(rec, ev, now) {
|
|
|
1339
1344
|
sessionId: ev.sessionId ?? rec.sessionId,
|
|
1340
1345
|
question: void 0,
|
|
1341
1346
|
// resuming after a blocked→reply clears the question
|
|
1342
|
-
pendingTool: void 0
|
|
1347
|
+
pendingTool: void 0,
|
|
1343
1348
|
// #354: a new turn closes any prior tool window
|
|
1349
|
+
pendingMonitor: void 0
|
|
1350
|
+
// #594a: same reset — a new turn moots any prior watch
|
|
1344
1351
|
};
|
|
1345
1352
|
case "task.progress": {
|
|
1346
1353
|
const pendingTool = nextPendingTool(rec.pendingTool, ev, now);
|
|
1354
|
+
const pendingMonitor = nextPendingMonitor(rec.pendingMonitor, ev, now);
|
|
1347
1355
|
if (isStickyAttention(rec.state))
|
|
1348
|
-
return { ...rec, lastHeartbeat: now, lastEvent: ev.type, pendingTool };
|
|
1349
|
-
const b = { ...base, pendingTool };
|
|
1356
|
+
return { ...rec, lastHeartbeat: now, lastEvent: ev.type, pendingTool, pendingMonitor };
|
|
1357
|
+
const b = { ...base, pendingTool, pendingMonitor };
|
|
1350
1358
|
if (rec.state === "awaiting-input" || rec.state === "stalled")
|
|
1351
1359
|
return { ...stampAttempt(b, {}, now), state: "working" };
|
|
1352
1360
|
return stampAttempt(b, {}, now);
|
|
@@ -1360,9 +1368,9 @@ function reduce(rec, ev, now) {
|
|
|
1360
1368
|
case "task.blocked":
|
|
1361
1369
|
if (rec.state === "blocked")
|
|
1362
1370
|
return { ...rec, lastHeartbeat: now, lastEvent: ev.type };
|
|
1363
|
-
return { ...base, state: "blocked", question: ev.question, pendingTool: void 0 };
|
|
1371
|
+
return { ...base, state: "blocked", question: ev.question, pendingTool: void 0, pendingMonitor: void 0 };
|
|
1364
1372
|
case "task.review":
|
|
1365
|
-
return { ...base, state: "review", reviewNote: ev.message, pendingTool: void 0 };
|
|
1373
|
+
return { ...base, state: "review", reviewNote: ev.message, pendingTool: void 0, pendingMonitor: void 0 };
|
|
1366
1374
|
case "task.done":
|
|
1367
1375
|
if (rec.state === "review" && ev.source !== "approve") {
|
|
1368
1376
|
return { ...rec, lastHeartbeat: now, lastEvent: ev.type };
|
|
@@ -1377,19 +1385,19 @@ function reduce(rec, ev, now) {
|
|
|
1377
1385
|
case "task.session":
|
|
1378
1386
|
return stampAttempt(base, { resumeRef: ev.resumeRef }, now);
|
|
1379
1387
|
case "task.turn.started":
|
|
1380
|
-
return { ...stampAttempt(base, {}, now), state: "working", pendingTool: void 0 };
|
|
1388
|
+
return { ...stampAttempt(base, {}, now), state: "working", pendingTool: void 0, pendingMonitor: void 0 };
|
|
1381
1389
|
case "task.turn.completed":
|
|
1382
1390
|
if (isStickyAttention(rec.state))
|
|
1383
1391
|
return { ...rec, lastHeartbeat: now, lastEvent: ev.type };
|
|
1384
|
-
if (rec.pendingTool)
|
|
1392
|
+
if (rec.pendingTool || rec.pendingMonitor)
|
|
1385
1393
|
return stampAttempt(base, {}, now);
|
|
1386
|
-
return { ...stampAttempt(base, {}, now), state: "awaiting-input", pendingTool: void 0 };
|
|
1394
|
+
return { ...stampAttempt(base, {}, now), state: "awaiting-input", pendingTool: void 0, pendingMonitor: void 0 };
|
|
1387
1395
|
case "task.delta":
|
|
1388
1396
|
return stampAttempt(base, {}, now);
|
|
1389
1397
|
// heartbeat-only
|
|
1390
1398
|
case "task.input.requested":
|
|
1391
1399
|
case "task.approval.requested":
|
|
1392
|
-
return { ...stampAttempt(base, {}, now), state: "blocked", question: ev.question, pendingTool: void 0 };
|
|
1400
|
+
return { ...stampAttempt(base, {}, now), state: "blocked", question: ev.question, pendingTool: void 0, pendingMonitor: void 0 };
|
|
1393
1401
|
case "task.reattached":
|
|
1394
1402
|
return stampAttempt(base, {}, now);
|
|
1395
1403
|
case "task.first-turn.confirmed":
|
|
@@ -1414,15 +1422,21 @@ var init_state_machine = __esm({
|
|
|
1414
1422
|
});
|
|
1415
1423
|
|
|
1416
1424
|
// packages/core/dist/watchdog.js
|
|
1417
|
-
function evaluateStall(rec, now, toolStallMs = TOOL_STALL_BUDGET_MS) {
|
|
1425
|
+
function evaluateStall(rec, now, toolStallMs = TOOL_STALL_BUDGET_MS, monitorStallMs = MONITOR_STALL_BUDGET_MS) {
|
|
1418
1426
|
if (rec.state !== "working")
|
|
1419
1427
|
return null;
|
|
1420
1428
|
if (rec.mode === "interactive") {
|
|
1421
|
-
if (
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
return
|
|
1425
|
-
|
|
1429
|
+
if (rec.pendingTool) {
|
|
1430
|
+
if (now - rec.pendingTool.since <= toolStallMs)
|
|
1431
|
+
return null;
|
|
1432
|
+
return { ...rec, state: "stalled", lastEvent: "watchdog.tool-stall" };
|
|
1433
|
+
}
|
|
1434
|
+
if (rec.pendingMonitor) {
|
|
1435
|
+
if (now - rec.pendingMonitor.since <= monitorStallMs)
|
|
1436
|
+
return null;
|
|
1437
|
+
return { ...rec, state: "stalled", lastEvent: "watchdog.monitor-stall" };
|
|
1438
|
+
}
|
|
1439
|
+
return null;
|
|
1426
1440
|
}
|
|
1427
1441
|
const liveness = rec.attempts.at(-1)?.lastHeartbeatAt ?? rec.lastHeartbeat;
|
|
1428
1442
|
if (now - liveness <= rec.heartbeatBudgetMs)
|
|
@@ -1432,12 +1446,13 @@ function evaluateStall(rec, now, toolStallMs = TOOL_STALL_BUDGET_MS) {
|
|
|
1432
1446
|
function recoverStall(rec, now) {
|
|
1433
1447
|
if (rec.state !== "stalled")
|
|
1434
1448
|
return null;
|
|
1435
|
-
return { ...rec, state: "working", lastHeartbeat: now, lastEvent: "watchdog.recover", pendingTool: void 0 };
|
|
1449
|
+
return { ...rec, state: "working", lastHeartbeat: now, lastEvent: "watchdog.recover", pendingTool: void 0, pendingMonitor: void 0 };
|
|
1436
1450
|
}
|
|
1437
|
-
var TOOL_STALL_BUDGET_MS;
|
|
1451
|
+
var TOOL_STALL_BUDGET_MS, MONITOR_STALL_BUDGET_MS;
|
|
1438
1452
|
var init_watchdog = __esm({
|
|
1439
1453
|
"packages/core/dist/watchdog.js"() {
|
|
1440
1454
|
TOOL_STALL_BUDGET_MS = 10 * 60 * 1e3;
|
|
1455
|
+
MONITOR_STALL_BUDGET_MS = 60 * 60 * 1e3;
|
|
1441
1456
|
}
|
|
1442
1457
|
});
|
|
1443
1458
|
|
|
@@ -1711,7 +1726,7 @@ function createDaemon(deps) {
|
|
|
1711
1726
|
store.delete(r.project, r.id);
|
|
1712
1727
|
continue;
|
|
1713
1728
|
}
|
|
1714
|
-
if (!TERMINAL_STATES.has(r.state)) {
|
|
1729
|
+
if (!TERMINAL_STATES.has(r.state) && !isStickyAttention(r.state)) {
|
|
1715
1730
|
const ceiling = deps.taskTimeoutMs ?? DEFAULT_TASK_TIMEOUT_MS;
|
|
1716
1731
|
if (t - r.createdAt > ceiling) {
|
|
1717
1732
|
const prevState = r.state;
|
|
@@ -1756,7 +1771,7 @@ function createDaemon(deps) {
|
|
|
1756
1771
|
const idle = evaluateStall(r, t);
|
|
1757
1772
|
if (idle) {
|
|
1758
1773
|
store.put(idle);
|
|
1759
|
-
const synthEvent = idle.pendingTool ? { type: "task.stalled", id: r.id, heartbeatBudgetMs: r.heartbeatBudgetMs, tool: idle.pendingTool.name, elapsedMs: t - idle.pendingTool.since } : { type: "task.stalled", id: r.id, heartbeatBudgetMs: r.heartbeatBudgetMs };
|
|
1774
|
+
const synthEvent = idle.pendingTool ? { type: "task.stalled", id: r.id, heartbeatBudgetMs: r.heartbeatBudgetMs, tool: idle.pendingTool.name, elapsedMs: t - idle.pendingTool.since } : idle.pendingMonitor ? { type: "task.stalled", id: r.id, heartbeatBudgetMs: r.heartbeatBudgetMs, tool: "Monitor", elapsedMs: t - idle.pendingMonitor.since } : { type: "task.stalled", id: r.id, heartbeatBudgetMs: r.heartbeatBudgetMs };
|
|
1760
1775
|
firePush(deps, r.project, r.state, idle, synthEvent, lastCaptainTurnAt.get(r.id));
|
|
1761
1776
|
continue;
|
|
1762
1777
|
}
|
|
@@ -3319,9 +3334,11 @@ var init_defer_delivery = __esm({
|
|
|
3319
3334
|
"packages/core/dist/delivery/defer-delivery.js"() {
|
|
3320
3335
|
DeferDelivery = class extends Error {
|
|
3321
3336
|
draft;
|
|
3322
|
-
|
|
3337
|
+
reason;
|
|
3338
|
+
constructor(draft = null, reason = "draft") {
|
|
3323
3339
|
super("deferred: captain composing");
|
|
3324
3340
|
this.draft = draft;
|
|
3341
|
+
this.reason = reason;
|
|
3325
3342
|
this.name = "DeferDelivery";
|
|
3326
3343
|
}
|
|
3327
3344
|
};
|
|
@@ -3345,6 +3362,7 @@ var init_captain_delivery = __esm({
|
|
|
3345
3362
|
deferCounts = /* @__PURE__ */ new Map();
|
|
3346
3363
|
lastContent = /* @__PURE__ */ new Map();
|
|
3347
3364
|
stableCounts = /* @__PURE__ */ new Map();
|
|
3365
|
+
lastReason = /* @__PURE__ */ new Map();
|
|
3348
3366
|
constructor(opts) {
|
|
3349
3367
|
this.opts = opts;
|
|
3350
3368
|
}
|
|
@@ -3367,29 +3385,40 @@ var init_captain_delivery = __esm({
|
|
|
3367
3385
|
this.deferCounts.delete(seq);
|
|
3368
3386
|
this.stableCounts.delete(seq);
|
|
3369
3387
|
this.lastContent.delete(seq);
|
|
3388
|
+
this.lastReason.delete(seq);
|
|
3370
3389
|
return { delivered: true };
|
|
3371
3390
|
} catch (e) {
|
|
3372
3391
|
if (e instanceof DeferDelivery) {
|
|
3373
3392
|
this.deferCounts.set(seq, deferCount + 1);
|
|
3374
3393
|
const content = e.draft;
|
|
3394
|
+
let stableCount;
|
|
3375
3395
|
if (content && content === this.lastContent.get(seq)) {
|
|
3376
|
-
|
|
3396
|
+
stableCount = (this.stableCounts.get(seq) ?? 0) + 1;
|
|
3397
|
+
this.stableCounts.set(seq, stableCount);
|
|
3377
3398
|
} else {
|
|
3399
|
+
stableCount = 0;
|
|
3378
3400
|
this.stableCounts.set(seq, 0);
|
|
3379
3401
|
}
|
|
3380
3402
|
this.lastContent.set(seq, content);
|
|
3381
|
-
|
|
3403
|
+
const reason = e.reason !== "draft" ? e.reason : stableCount >= this.opts.stableProbePolls ? "stable" : "draft";
|
|
3404
|
+
this.lastReason.set(seq, reason);
|
|
3405
|
+
return { deferred: true, reason };
|
|
3382
3406
|
}
|
|
3383
|
-
|
|
3407
|
+
this.lastReason.set(seq, "unknown");
|
|
3408
|
+
return { deferred: true, reason: "unknown" };
|
|
3384
3409
|
}
|
|
3385
3410
|
}
|
|
3386
3411
|
/** Read-only. Never mutates — safe to poll from the snapshot assembler every tick. */
|
|
3387
3412
|
stats() {
|
|
3388
3413
|
let maxDeferCount = 0;
|
|
3389
|
-
|
|
3390
|
-
|
|
3414
|
+
let reason;
|
|
3415
|
+
for (const [seq, c] of this.deferCounts) {
|
|
3416
|
+
if (c > maxDeferCount) {
|
|
3391
3417
|
maxDeferCount = c;
|
|
3392
|
-
|
|
3418
|
+
reason = this.lastReason.get(seq);
|
|
3419
|
+
}
|
|
3420
|
+
}
|
|
3421
|
+
return { maxDeferCount, stuck: maxDeferCount >= this.opts.maxDefers, reason };
|
|
3393
3422
|
}
|
|
3394
3423
|
};
|
|
3395
3424
|
}
|
|
@@ -3489,6 +3518,10 @@ function createDelivery(ctx, daemonCmux) {
|
|
|
3489
3518
|
const notifyFault = ctx.notifyFault ?? (() => {
|
|
3490
3519
|
});
|
|
3491
3520
|
const defaultNotify = async (args) => {
|
|
3521
|
+
const fresh = store.get(args.project, args.record.id);
|
|
3522
|
+
if (fresh && TERMINAL_STATES.has(fresh.state) && fresh.state !== args.record.state) {
|
|
3523
|
+
return;
|
|
3524
|
+
}
|
|
3492
3525
|
try {
|
|
3493
3526
|
await appendToMailbox({
|
|
3494
3527
|
stateRoot,
|
|
@@ -3578,16 +3611,19 @@ function createDelivery(ctx, daemonCmux) {
|
|
|
3578
3611
|
log(`delivery seq=${entry.seq} kind=${entry.kind} outcome=delivered`);
|
|
3579
3612
|
await writeCursor({ stateRoot, project, subscriber: CURSOR_SUBSCRIBER, lastAckedSeq: entry.seq });
|
|
3580
3613
|
} else {
|
|
3581
|
-
|
|
3614
|
+
const { maxDeferCount } = d.stats();
|
|
3615
|
+
if (maxDeferCount === 1 || maxDeferCount % 30 === 0) {
|
|
3616
|
+
log(`delivery seq=${entry.seq} kind=${entry.kind} outcome=deferred project=${project} reason=${result.reason}`);
|
|
3617
|
+
}
|
|
3582
3618
|
break;
|
|
3583
3619
|
}
|
|
3584
3620
|
}
|
|
3585
3621
|
const stuck = d.stats().stuck;
|
|
3586
3622
|
if (stuck && !stuckNotified.has(project)) {
|
|
3587
3623
|
stuckNotified.add(project);
|
|
3588
|
-
const { maxDeferCount } = d.stats();
|
|
3589
|
-
log(`delivery stuck project=${project} deferCount=${maxDeferCount}`);
|
|
3590
|
-
const text = `\u26A0\uFE0F DELIVERY STUCK: an in-progress draft (or ghost text) in your input box has blocked pending notification(s) for ${maxDeferCount}+ retries. Your input is never touched \u2014 this keeps retrying safely and will deliver automatically once you submit or clear it.`;
|
|
3624
|
+
const { maxDeferCount, reason } = d.stats();
|
|
3625
|
+
log(`delivery stuck project=${project} deferCount=${maxDeferCount} reason=${reason ?? "unknown"}`);
|
|
3626
|
+
const text = reason === "modal" ? `\u26A0\uFE0F DELIVERY STUCK: a modal question is open in your captain pane and has blocked pending notification(s) for ${maxDeferCount}+ retries. This keeps retrying safely and will deliver automatically once you answer or dismiss it.` : `\u26A0\uFE0F DELIVERY STUCK: an in-progress draft (or ghost text) in your input box has blocked pending notification(s) for ${maxDeferCount}+ retries. Your input is never touched \u2014 this keeps retrying safely and will deliver automatically once you submit or clear it.`;
|
|
3591
3627
|
appendCaptainMessage({ stateRoot, project, text, source: "daemon" }).catch((e) => log(`delivery stuck alert failed project=${project}: ${e.message}`));
|
|
3592
3628
|
Promise.resolve(notifyFault(project, text)).catch((e) => log(`delivery stuck fault-notify failed project=${project}: ${e.message}`));
|
|
3593
3629
|
telegramBridge?.pushRaw(project, text);
|
|
@@ -6079,6 +6115,7 @@ __export(dist_exports2, {
|
|
|
6079
6115
|
GROUP_DISPATCH_WARMUP_TIMEOUT_MS: () => GROUP_DISPATCH_WARMUP_TIMEOUT_MS,
|
|
6080
6116
|
IDLE_DEBOUNCE_MS: () => IDLE_DEBOUNCE_MS,
|
|
6081
6117
|
LABEL: () => LABEL,
|
|
6118
|
+
MONITOR_STALL_BUDGET_MS: () => MONITOR_STALL_BUDGET_MS,
|
|
6082
6119
|
PROBE_QUIET_MS: () => PROBE_QUIET_MS,
|
|
6083
6120
|
PROTOCOL_VERSION: () => PROTOCOL_VERSION,
|
|
6084
6121
|
STALE_THRESHOLD_MS: () => STALE_THRESHOLD_MS,
|
|
@@ -6148,6 +6185,7 @@ __export(dist_exports2, {
|
|
|
6148
6185
|
isDaemonSocketLive: () => isDaemonSocketLive,
|
|
6149
6186
|
isNotifyActive: () => isNotifyActive,
|
|
6150
6187
|
isSideTitle: () => isSideTitle,
|
|
6188
|
+
isStickyAttention: () => isStickyAttention,
|
|
6151
6189
|
isTurnAccepted: () => isTurnAccepted,
|
|
6152
6190
|
kickstartArgv: () => kickstartArgv,
|
|
6153
6191
|
launchOneWorkspace: () => launchOneWorkspace,
|
|
@@ -6666,9 +6704,9 @@ function createCmuxDriver() {
|
|
|
6666
6704
|
}
|
|
6667
6705
|
const draft = parseDraftFromScreen(screen);
|
|
6668
6706
|
if (draft === null)
|
|
6669
|
-
throw new DeferDelivery(null);
|
|
6707
|
+
throw new DeferDelivery(null, "no-box");
|
|
6670
6708
|
if (hasModalOptionList(screen))
|
|
6671
|
-
throw new DeferDelivery(null);
|
|
6709
|
+
throw new DeferDelivery(null, "modal");
|
|
6672
6710
|
if (draft === "") {
|
|
6673
6711
|
await deliver();
|
|
6674
6712
|
return;
|
|
@@ -7493,6 +7531,7 @@ function installClaudeHooks(opts = {}) {
|
|
|
7493
7531
|
});
|
|
7494
7532
|
let settings = {};
|
|
7495
7533
|
const raw = readFile6(settingsPath);
|
|
7534
|
+
const hadExistingSettings = raw !== void 0;
|
|
7496
7535
|
if (raw) {
|
|
7497
7536
|
try {
|
|
7498
7537
|
settings = JSON.parse(raw);
|
|
@@ -7505,6 +7544,7 @@ function installClaudeHooks(opts = {}) {
|
|
|
7505
7544
|
}
|
|
7506
7545
|
const hooks = settings.hooks;
|
|
7507
7546
|
let changed = false;
|
|
7547
|
+
const repaired = [];
|
|
7508
7548
|
for (const [eventName, sub, matcher] of CLAUDE_HOOK_EVENTS) {
|
|
7509
7549
|
if (!Array.isArray(hooks[eventName])) {
|
|
7510
7550
|
hooks[eventName] = [];
|
|
@@ -7516,6 +7556,26 @@ function installClaudeHooks(opts = {}) {
|
|
|
7516
7556
|
if (!alreadyPresent) {
|
|
7517
7557
|
entries.push({ matcher: hookMatcher, hooks: [{ type: "command", command, timeout: 10 }] });
|
|
7518
7558
|
changed = true;
|
|
7559
|
+
repaired.push(`${eventName}/${sub}`);
|
|
7560
|
+
}
|
|
7561
|
+
}
|
|
7562
|
+
if (repaired.length > 0 && hadExistingSettings) {
|
|
7563
|
+
log(`native-hook: repaired ${repaired.length} missing squadrant hook(s) in ${settingsPath} [${repaired.join(", ")}] \u2014 WARNING: blocked-signalling or lifecycle tracking may have been broken until this run`);
|
|
7564
|
+
}
|
|
7565
|
+
if (opts.claudeEnv && Object.keys(opts.claudeEnv).length > 0) {
|
|
7566
|
+
if (typeof settings.env !== "object" || settings.env === null || Array.isArray(settings.env)) {
|
|
7567
|
+
settings.env = {};
|
|
7568
|
+
}
|
|
7569
|
+
const env = settings.env;
|
|
7570
|
+
for (const [key, value] of Object.entries(opts.claudeEnv)) {
|
|
7571
|
+
if (key in env) {
|
|
7572
|
+
if (env[key] !== value) {
|
|
7573
|
+
log(`native-hook: claudeEnv key '${key}' already set to '${String(env[key])}' in ${settingsPath} \u2014 not overwriting with '${value}'`);
|
|
7574
|
+
}
|
|
7575
|
+
continue;
|
|
7576
|
+
}
|
|
7577
|
+
env[key] = value;
|
|
7578
|
+
changed = true;
|
|
7519
7579
|
}
|
|
7520
7580
|
}
|
|
7521
7581
|
if (changed) {
|
|
@@ -7590,9 +7650,9 @@ var init_native_hook_source = __esm({
|
|
|
7590
7650
|
cache = /* @__PURE__ */ new Map();
|
|
7591
7651
|
active = false;
|
|
7592
7652
|
constructor(opts = {}) {
|
|
7593
|
-
this.hookInstall = opts.hookInstall ?? {};
|
|
7594
7653
|
this.log = opts.log ?? (() => {
|
|
7595
7654
|
});
|
|
7655
|
+
this.hookInstall = { log: this.log, ...opts.hookInstall };
|
|
7596
7656
|
}
|
|
7597
7657
|
start(deps) {
|
|
7598
7658
|
this.deps = deps;
|
|
@@ -11484,6 +11544,16 @@ function defaultCreatePr(cwd, o) {
|
|
|
11484
11544
|
{ cwd, encoding: "utf-8" }
|
|
11485
11545
|
).trim();
|
|
11486
11546
|
}
|
|
11547
|
+
function defaultGetCommitSubject(cwd) {
|
|
11548
|
+
try {
|
|
11549
|
+
return execFileSync6("git", ["-C", cwd, "log", "-1", "--format=%s"], {
|
|
11550
|
+
encoding: "utf-8",
|
|
11551
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
11552
|
+
}).trim();
|
|
11553
|
+
} catch {
|
|
11554
|
+
return "";
|
|
11555
|
+
}
|
|
11556
|
+
}
|
|
11487
11557
|
async function runCrewApprove(project, crew, deps) {
|
|
11488
11558
|
const config = loadConfig();
|
|
11489
11559
|
const proj = config.projects[project];
|
|
@@ -11499,8 +11569,12 @@ async function runCrewApprove(project, crew, deps) {
|
|
|
11499
11569
|
const cwd = task.cwd ?? proj.path;
|
|
11500
11570
|
const base = resolveWorktreeBase(proj.path);
|
|
11501
11571
|
const branch = crewBranch(crew);
|
|
11502
|
-
const title = (task.task ?? branch).split(/\r?\n/)[0].trim().slice(0, 100);
|
|
11503
11572
|
const body = (task.reviewNote ?? task.task ?? "").trim();
|
|
11573
|
+
const getCommitSubject = deps.getCommitSubject ?? defaultGetCommitSubject;
|
|
11574
|
+
const commitSubject = getCommitSubject(cwd).trim();
|
|
11575
|
+
const reviewNoteFirstLine = (task.reviewNote ?? "").split(/\r?\n/)[0].trim();
|
|
11576
|
+
const taskFirstLine = (task.task ?? branch).split(/\r?\n/)[0].trim();
|
|
11577
|
+
const title = (commitSubject || reviewNoteFirstLine || taskFirstLine).slice(0, 100);
|
|
11504
11578
|
const pushBranch = deps.pushBranch ?? defaultPushBranch;
|
|
11505
11579
|
const createPr = deps.createPr ?? defaultCreatePr;
|
|
11506
11580
|
pushBranch(cwd, branch);
|