panopticon-cli 0.4.33 → 0.5.0
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 +96 -210
- package/dist/{agents-VLK4BMVA.js → agents-E43Y3HNU.js} +5 -5
- package/dist/{chunk-ASY7T35E.js → chunk-AAFQANKW.js} +231 -76
- package/dist/chunk-AAFQANKW.js.map +1 -0
- package/dist/{chunk-KJ2TRXNK.js → chunk-FTCPTHIJ.js} +47 -420
- package/dist/chunk-FTCPTHIJ.js.map +1 -0
- package/dist/{chunk-PI7Y3PSN.js → chunk-GR6ZZMCX.js} +25 -6
- package/dist/chunk-GR6ZZMCX.js.map +1 -0
- package/dist/chunk-HJSM6E6U.js +1038 -0
- package/dist/chunk-HJSM6E6U.js.map +1 -0
- package/dist/{chunk-BKCWRMUX.js → chunk-HZT2AOPN.js} +81 -9
- package/dist/chunk-HZT2AOPN.js.map +1 -0
- package/dist/{chunk-XFR2DLMR.js → chunk-NTO3EDB3.js} +3 -3
- package/dist/{chunk-XFR2DLMR.js.map → chunk-NTO3EDB3.js.map} +1 -1
- package/dist/{chunk-RBUO57TC.js → chunk-PPRFKTVC.js} +2 -2
- package/dist/chunk-PPRFKTVC.js.map +1 -0
- package/dist/{chunk-XKT5MHPT.js → chunk-WQG2TYCB.js} +2 -2
- package/dist/cli/index.js +1383 -880
- package/dist/cli/index.js.map +1 -1
- package/dist/dashboard/prompts/work-agent.md +2 -0
- package/dist/dashboard/public/assets/{index-UjZq6ykz.css → index-BxpjweAL.css} +1 -1
- package/dist/dashboard/public/assets/index-DQHkwvvJ.js +743 -0
- package/dist/dashboard/public/index.html +2 -2
- package/dist/dashboard/server.js +3593 -2052
- package/dist/index.d.ts +10 -1
- package/dist/index.js +5 -3
- package/dist/index.js.map +1 -1
- package/dist/{specialist-context-T3NBMCIE.js → specialist-context-ZC6A4M3I.js} +4 -4
- package/dist/{specialist-logs-CVKD3YJ3.js → specialist-logs-KLGJCEUL.js} +4 -4
- package/dist/{specialists-TKAP6T6Z.js → specialists-O4HWDJL5.js} +4 -4
- package/dist/{traefik-QX4ZV4YG.js → traefik-QN7R5I6V.js} +2 -2
- package/dist/{workspace-manager-KLHUCIZV.js → workspace-manager-IE4JL2JP.js} +2 -2
- package/package.json +1 -1
- package/scripts/stop-hook +7 -0
- package/scripts/work-agent-stop-hook +137 -0
- package/skills/myn-standards/SKILL.md +351 -0
- package/skills/write-spec/SKILL.md +138 -0
- package/dist/chunk-7XNJJBH6.js +0 -538
- package/dist/chunk-7XNJJBH6.js.map +0 -1
- package/dist/chunk-ASY7T35E.js.map +0 -1
- package/dist/chunk-BKCWRMUX.js.map +0 -1
- package/dist/chunk-KJ2TRXNK.js.map +0 -1
- package/dist/chunk-PI7Y3PSN.js.map +0 -1
- package/dist/chunk-RBUO57TC.js.map +0 -1
- package/dist/dashboard/public/assets/index-kAJqtLDO.js +0 -708
- /package/dist/{agents-VLK4BMVA.js.map → agents-E43Y3HNU.js.map} +0 -0
- /package/dist/{chunk-XKT5MHPT.js.map → chunk-WQG2TYCB.js.map} +0 -0
- /package/dist/{specialist-context-T3NBMCIE.js.map → specialist-context-ZC6A4M3I.js.map} +0 -0
- /package/dist/{specialist-logs-CVKD3YJ3.js.map → specialist-logs-KLGJCEUL.js.map} +0 -0
- /package/dist/{specialists-TKAP6T6Z.js.map → specialists-O4HWDJL5.js.map} +0 -0
- /package/dist/{traefik-QX4ZV4YG.js.map → traefik-QN7R5I6V.js.map} +0 -0
- /package/dist/{workspace-manager-KLHUCIZV.js.map → workspace-manager-IE4JL2JP.js.map} +0 -0
|
@@ -1,25 +1,29 @@
|
|
|
1
1
|
import {
|
|
2
|
+
capturePaneAsync,
|
|
2
3
|
checkHook,
|
|
4
|
+
confirmDelivery,
|
|
3
5
|
getModelId,
|
|
4
6
|
init_hooks,
|
|
5
7
|
init_tmux,
|
|
6
8
|
init_work_type_router,
|
|
7
9
|
popFromHook,
|
|
8
10
|
pushToHook,
|
|
9
|
-
sendKeysAsync
|
|
10
|
-
|
|
11
|
+
sendKeysAsync,
|
|
12
|
+
waitForClaudePrompt
|
|
13
|
+
} from "./chunk-FTCPTHIJ.js";
|
|
11
14
|
import {
|
|
12
15
|
init_pipeline_notifier,
|
|
13
16
|
notifyPipeline
|
|
14
17
|
} from "./chunk-JQBV3Q2W.js";
|
|
15
18
|
import {
|
|
19
|
+
clearCredentialFileAuth,
|
|
16
20
|
getProviderEnv,
|
|
17
21
|
getProviderForModel,
|
|
18
22
|
init_providers,
|
|
19
23
|
init_settings,
|
|
20
24
|
loadSettings,
|
|
21
25
|
setupCredentialFileAuth
|
|
22
|
-
} from "./chunk-
|
|
26
|
+
} from "./chunk-HJSM6E6U.js";
|
|
23
27
|
import {
|
|
24
28
|
init_projects,
|
|
25
29
|
projects_exports
|
|
@@ -393,6 +397,40 @@ import { homedir as homedir2 } from "os";
|
|
|
393
397
|
import { exec } from "child_process";
|
|
394
398
|
import { promisify } from "util";
|
|
395
399
|
import { randomUUID } from "crypto";
|
|
400
|
+
async function resolveWorkspaceGitInfo(workspace, taskBranch) {
|
|
401
|
+
const gitDirs = [];
|
|
402
|
+
let branch = taskBranch || "unknown";
|
|
403
|
+
if (!workspace || workspace === "unknown") {
|
|
404
|
+
return { gitDirs, branch, isPolyrepo: false };
|
|
405
|
+
}
|
|
406
|
+
if (existsSync3(join4(workspace, ".git"))) {
|
|
407
|
+
gitDirs.push(workspace);
|
|
408
|
+
} else {
|
|
409
|
+
try {
|
|
410
|
+
const entries = readdirSync2(workspace, { withFileTypes: true });
|
|
411
|
+
for (const entry of entries) {
|
|
412
|
+
if (entry.isDirectory() && existsSync3(join4(workspace, entry.name, ".git"))) {
|
|
413
|
+
gitDirs.push(join4(workspace, entry.name));
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
} catch {
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
if (branch === "unknown" && gitDirs.length > 0) {
|
|
420
|
+
try {
|
|
421
|
+
const { stdout } = await execAsync(
|
|
422
|
+
`cd "${gitDirs[0]}" && git branch --show-current`,
|
|
423
|
+
{ encoding: "utf-8", timeout: 5e3 }
|
|
424
|
+
);
|
|
425
|
+
const detected = stdout.trim();
|
|
426
|
+
if (detected) {
|
|
427
|
+
branch = detected;
|
|
428
|
+
}
|
|
429
|
+
} catch {
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
return { gitDirs, branch, isPolyrepo: gitDirs.length > 1 };
|
|
433
|
+
}
|
|
396
434
|
function getProviderEnvForModel(model) {
|
|
397
435
|
const provider = getProviderForModel(model);
|
|
398
436
|
if (provider.name === "anthropic") return {};
|
|
@@ -584,9 +622,9 @@ function recordWake(name, sessionId) {
|
|
|
584
622
|
}
|
|
585
623
|
async function spawnEphemeralSpecialist(projectKey, specialistType, task) {
|
|
586
624
|
ensureProjectSpecialistDir(projectKey, specialistType);
|
|
587
|
-
const { loadContextDigest } = await import("./specialist-context-
|
|
625
|
+
const { loadContextDigest } = await import("./specialist-context-ZC6A4M3I.js");
|
|
588
626
|
const contextDigest = loadContextDigest(projectKey, specialistType);
|
|
589
|
-
const { createRunLog: createRunLog2 } = await import("./specialist-logs-
|
|
627
|
+
const { createRunLog: createRunLog2 } = await import("./specialist-logs-KLGJCEUL.js");
|
|
590
628
|
const { runId, filePath: logFilePath } = createRunLog2(
|
|
591
629
|
projectKey,
|
|
592
630
|
specialistType,
|
|
@@ -611,6 +649,8 @@ async function spawnEphemeralSpecialist(projectKey, specialistType, task) {
|
|
|
611
649
|
const providerConfig = getProviderForModel(model);
|
|
612
650
|
if (providerConfig.authType === "credential-file") {
|
|
613
651
|
setupCredentialFileAuth(providerConfig, cwd);
|
|
652
|
+
} else {
|
|
653
|
+
clearCredentialFileAuth(cwd);
|
|
614
654
|
}
|
|
615
655
|
const permissionFlags = specialistType === "merge-agent" ? "--dangerously-skip-permissions --permission-mode bypassPermissions" : "--dangerously-skip-permissions";
|
|
616
656
|
const agentDir = join4(homedir2(), ".panopticon", "agents", tmuxSession);
|
|
@@ -633,7 +673,7 @@ echo "## Specialist completed task"
|
|
|
633
673
|
`tmux new-session -d -s "${tmuxSession}"${envFlags} "bash '${launcherScript}'"`,
|
|
634
674
|
{ encoding: "utf-8" }
|
|
635
675
|
);
|
|
636
|
-
const { saveAgentRuntimeState } = await import("./agents-
|
|
676
|
+
const { saveAgentRuntimeState } = await import("./agents-E43Y3HNU.js");
|
|
637
677
|
saveAgentRuntimeState(tmuxSession, {
|
|
638
678
|
state: "active",
|
|
639
679
|
lastActivity: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -718,14 +758,23 @@ Update status via API:
|
|
|
718
758
|
- If tests pass: POST to /api/workspaces/${task.issueId}/review-status with {"testStatus":"passed"}
|
|
719
759
|
- If tests fail: POST with {"testStatus":"failed","testNotes":"..."}`;
|
|
720
760
|
break;
|
|
721
|
-
case "merge-agent":
|
|
761
|
+
case "merge-agent": {
|
|
762
|
+
const bInfo = await resolveWorkspaceGitInfo(task.workspace, task.branch);
|
|
763
|
+
if (bInfo.isPolyrepo) {
|
|
764
|
+
prompt += `This is a POLYREPO project with ${bInfo.gitDirs.length} repos: ${bInfo.gitDirs.map((d) => basename2(d)).join(", ")}.
|
|
765
|
+
You must merge each repo separately.
|
|
766
|
+
|
|
767
|
+
`;
|
|
768
|
+
}
|
|
722
769
|
prompt += `Your task:
|
|
723
770
|
1. Fetch the latest main branch
|
|
724
|
-
2. Attempt to merge ${
|
|
771
|
+
2. Attempt to merge ${bInfo.branch} into main
|
|
725
772
|
3. Resolve conflicts intelligently if needed
|
|
726
773
|
4. Run tests to verify merge is clean
|
|
727
|
-
5. Complete merge if tests pass
|
|
774
|
+
5. Complete merge if tests pass
|
|
775
|
+
6. NEVER use git push --force`;
|
|
728
776
|
break;
|
|
777
|
+
}
|
|
729
778
|
}
|
|
730
779
|
prompt += `
|
|
731
780
|
|
|
@@ -818,7 +867,7 @@ async function terminateSpecialist(projectKey, specialistType) {
|
|
|
818
867
|
console.error(`[specialist] Failed to kill tmux session ${tmuxSession}:`, error);
|
|
819
868
|
}
|
|
820
869
|
if (metadata.currentRun) {
|
|
821
|
-
const { finalizeRunLog: finalizeRunLog2 } = await import("./specialist-logs-
|
|
870
|
+
const { finalizeRunLog: finalizeRunLog2 } = await import("./specialist-logs-KLGJCEUL.js");
|
|
822
871
|
try {
|
|
823
872
|
finalizeRunLog2(projectKey, specialistType, metadata.currentRun, {
|
|
824
873
|
status: metadata.lastRunStatus || "incomplete",
|
|
@@ -831,19 +880,19 @@ async function terminateSpecialist(projectKey, specialistType) {
|
|
|
831
880
|
}
|
|
832
881
|
const key = `${projectKey}-${specialistType}`;
|
|
833
882
|
gracePeriodStates.delete(key);
|
|
834
|
-
const { saveAgentRuntimeState } = await import("./agents-
|
|
883
|
+
const { saveAgentRuntimeState } = await import("./agents-E43Y3HNU.js");
|
|
835
884
|
saveAgentRuntimeState(tmuxSession, {
|
|
836
885
|
state: "suspended",
|
|
837
886
|
lastActivity: (/* @__PURE__ */ new Date()).toISOString()
|
|
838
887
|
});
|
|
839
|
-
const { scheduleDigestGeneration } = await import("./specialist-context-
|
|
888
|
+
const { scheduleDigestGeneration } = await import("./specialist-context-ZC6A4M3I.js");
|
|
840
889
|
scheduleDigestGeneration(projectKey, specialistType);
|
|
841
890
|
scheduleLogCleanup(projectKey, specialistType);
|
|
842
891
|
}
|
|
843
892
|
function scheduleLogCleanup(projectKey, specialistType) {
|
|
844
893
|
Promise.resolve().then(async () => {
|
|
845
894
|
try {
|
|
846
|
-
const { cleanupOldLogs: cleanupOldLogs2 } = await import("./specialist-logs-
|
|
895
|
+
const { cleanupOldLogs: cleanupOldLogs2 } = await import("./specialist-logs-KLGJCEUL.js");
|
|
847
896
|
const { getSpecialistRetention } = await import("./projects-JEIVIYC6.js");
|
|
848
897
|
const retention = getSpecialistRetention(projectKey);
|
|
849
898
|
const deleted = cleanupOldLogs2(projectKey, specialistType, { maxDays: retention.max_days, maxRuns: retention.max_runs });
|
|
@@ -1022,7 +1071,7 @@ async function getSpecialistStatus(name, projectKey) {
|
|
|
1022
1071
|
const sessionId = getSessionId(name);
|
|
1023
1072
|
const running = await isRunning(name, projectKey);
|
|
1024
1073
|
const contextTokens = countContextTokens(name);
|
|
1025
|
-
const { getAgentRuntimeState } = await import("./agents-
|
|
1074
|
+
const { getAgentRuntimeState } = await import("./agents-E43Y3HNU.js");
|
|
1026
1075
|
const tmuxSession = getTmuxSessionName(name, projectKey);
|
|
1027
1076
|
const runtimeState = getAgentRuntimeState(tmuxSession);
|
|
1028
1077
|
let state;
|
|
@@ -1100,6 +1149,8 @@ Say: "I am the ${name} specialist, ready and waiting for tasks."`;
|
|
|
1100
1149
|
const providerCfg = getProviderForModel(model);
|
|
1101
1150
|
if (providerCfg.authType === "credential-file") {
|
|
1102
1151
|
setupCredentialFileAuth(providerCfg, cwd);
|
|
1152
|
+
} else {
|
|
1153
|
+
clearCredentialFileAuth(cwd);
|
|
1103
1154
|
}
|
|
1104
1155
|
const agentDir = join4(homedir2(), ".panopticon", "agents", tmuxSession);
|
|
1105
1156
|
await execAsync(`mkdir -p "${agentDir}"`, { encoding: "utf-8" });
|
|
@@ -1162,9 +1213,7 @@ async function resetSpecialist(name) {
|
|
|
1162
1213
|
const tmuxSession = getTmuxSessionName(name);
|
|
1163
1214
|
try {
|
|
1164
1215
|
await execAsync(`tmux send-keys -t "${tmuxSession}" C-c`, { encoding: "utf-8" });
|
|
1165
|
-
await new Promise((resolve) => setTimeout(resolve,
|
|
1166
|
-
await sendKeysAsync(tmuxSession, "cd ~");
|
|
1167
|
-
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
1216
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1168
1217
|
await execAsync(`tmux send-keys -t "${tmuxSession}" C-u`, { encoding: "utf-8" });
|
|
1169
1218
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
1170
1219
|
} catch (error) {
|
|
@@ -1200,6 +1249,8 @@ async function wakeSpecialist(name, taskPrompt, options = {}) {
|
|
|
1200
1249
|
const provCfg = getProviderForModel(model);
|
|
1201
1250
|
if (provCfg.authType === "credential-file") {
|
|
1202
1251
|
setupCredentialFileAuth(provCfg, cwd);
|
|
1252
|
+
} else {
|
|
1253
|
+
clearCredentialFileAuth(cwd);
|
|
1203
1254
|
}
|
|
1204
1255
|
const permissionFlags = name === "merge-agent" ? "--dangerously-skip-permissions --permission-mode bypassPermissions" : "--dangerously-skip-permissions";
|
|
1205
1256
|
let claudeCmd;
|
|
@@ -1215,7 +1266,10 @@ async function wakeSpecialist(name, taskPrompt, options = {}) {
|
|
|
1215
1266
|
{ encoding: "utf-8" }
|
|
1216
1267
|
);
|
|
1217
1268
|
if (waitForReady) {
|
|
1218
|
-
|
|
1269
|
+
const ready = await waitForClaudePrompt(tmuxSession, 15e3);
|
|
1270
|
+
if (!ready) {
|
|
1271
|
+
console.warn(`[specialist] ${name}: prompt not detected within 15s, proceeding anyway`);
|
|
1272
|
+
}
|
|
1219
1273
|
}
|
|
1220
1274
|
} catch (error) {
|
|
1221
1275
|
const msg = error instanceof Error ? error.message : String(error);
|
|
@@ -1228,21 +1282,43 @@ async function wakeSpecialist(name, taskPrompt, options = {}) {
|
|
|
1228
1282
|
}
|
|
1229
1283
|
}
|
|
1230
1284
|
await resetSpecialist(name);
|
|
1285
|
+
const promptReady = await waitForClaudePrompt(tmuxSession, wasAlreadyRunning ? 5e3 : 15e3);
|
|
1286
|
+
if (!promptReady) {
|
|
1287
|
+
console.warn(`[specialist] ${name}: prompt not detected after reset, proceeding anyway`);
|
|
1288
|
+
}
|
|
1231
1289
|
try {
|
|
1232
1290
|
const isLargePrompt = taskPrompt.length > 500 || taskPrompt.includes("\n");
|
|
1291
|
+
let messageToSend;
|
|
1233
1292
|
if (isLargePrompt) {
|
|
1234
1293
|
if (!existsSync3(TASKS_DIR)) {
|
|
1235
1294
|
mkdirSync2(TASKS_DIR, { recursive: true });
|
|
1236
1295
|
}
|
|
1237
1296
|
const taskFile = join4(TASKS_DIR, `${name}-${Date.now()}.md`);
|
|
1238
1297
|
writeFileSync(taskFile, taskPrompt, "utf-8");
|
|
1239
|
-
|
|
1240
|
-
await sendKeysAsync(tmuxSession, shortMessage);
|
|
1298
|
+
messageToSend = `Read and execute the task in: ${taskFile}`;
|
|
1241
1299
|
} else {
|
|
1242
|
-
|
|
1300
|
+
messageToSend = taskPrompt;
|
|
1301
|
+
}
|
|
1302
|
+
const outputBefore = await capturePaneAsync(tmuxSession, 50);
|
|
1303
|
+
await sendKeysAsync(tmuxSession, messageToSend);
|
|
1304
|
+
const delivered = await confirmDelivery(tmuxSession, outputBefore, 1e4);
|
|
1305
|
+
if (!delivered) {
|
|
1306
|
+
console.warn(`[specialist] ${name}: no activity detected after task send, retrying...`);
|
|
1307
|
+
const retryBefore = await capturePaneAsync(tmuxSession, 50);
|
|
1308
|
+
await sendKeysAsync(tmuxSession, messageToSend);
|
|
1309
|
+
const retryDelivered = await confirmDelivery(tmuxSession, retryBefore, 1e4);
|
|
1310
|
+
if (!retryDelivered) {
|
|
1311
|
+
return {
|
|
1312
|
+
success: false,
|
|
1313
|
+
message: `Task message not received by specialist ${name} after retry`,
|
|
1314
|
+
tmuxSession,
|
|
1315
|
+
wasAlreadyRunning,
|
|
1316
|
+
error: "delivery_failed"
|
|
1317
|
+
};
|
|
1318
|
+
}
|
|
1243
1319
|
}
|
|
1244
1320
|
recordWake(name, sessionId || void 0);
|
|
1245
|
-
const { saveAgentRuntimeState } = await import("./agents-
|
|
1321
|
+
const { saveAgentRuntimeState } = await import("./agents-E43Y3HNU.js");
|
|
1246
1322
|
saveAgentRuntimeState(tmuxSession, {
|
|
1247
1323
|
state: "active",
|
|
1248
1324
|
lastActivity: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -1270,46 +1346,65 @@ async function wakeSpecialistWithTask(name, task) {
|
|
|
1270
1346
|
const apiUrl = process.env.DASHBOARD_URL || `http://localhost:${apiPort}`;
|
|
1271
1347
|
let prompt;
|
|
1272
1348
|
switch (name) {
|
|
1273
|
-
case "merge-agent":
|
|
1349
|
+
case "merge-agent": {
|
|
1350
|
+
const mergeWorkspace = task.workspace || "unknown";
|
|
1351
|
+
const mergeInfo = await resolveWorkspaceGitInfo(task.workspace, task.branch);
|
|
1352
|
+
const mergeBranch = mergeInfo.branch;
|
|
1353
|
+
const mergeRepoInstructions = mergeInfo.isPolyrepo ? `
|
|
1354
|
+
IMPORTANT: This is a POLYREPO project. There are ${mergeInfo.gitDirs.length} separate git repositories to merge:
|
|
1355
|
+
${mergeInfo.gitDirs.map((d, i) => `${i + 1}. ${basename2(d)}: ${d}`).join("\n")}
|
|
1356
|
+
|
|
1357
|
+
The workspace root is NOT a git repo. You must cd into each subdirectory to run git commands.
|
|
1358
|
+
You MUST complete the merge for ALL repos.
|
|
1359
|
+
` : "";
|
|
1274
1360
|
prompt = `New merge task for ${task.issueId}:
|
|
1275
1361
|
|
|
1276
|
-
Branch: ${
|
|
1277
|
-
Workspace: ${
|
|
1362
|
+
Branch: ${mergeBranch}
|
|
1363
|
+
Workspace: ${mergeWorkspace}
|
|
1364
|
+
${mergeInfo.isPolyrepo ? `Polyrepo: git repos in subdirectories: ${mergeInfo.gitDirs.map((d) => basename2(d)).join(", ")}` : ""}
|
|
1278
1365
|
${task.prUrl ? `PR URL: ${task.prUrl}` : ""}
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1366
|
+
${mergeRepoInstructions}
|
|
1367
|
+
For ${mergeInfo.isPolyrepo ? "EACH repo" : "the repo"}, perform these steps:
|
|
1368
|
+
|
|
1369
|
+
PHASE 1 \u2014 SYNC & BASELINE (before merge):
|
|
1370
|
+
1. ${mergeInfo.isPolyrepo ? "cd into the repo directory" : `cd ${mergeWorkspace}`}
|
|
1371
|
+
2. git checkout main
|
|
1372
|
+
3. git fetch origin main
|
|
1373
|
+
4. Sync local main with origin/main:
|
|
1374
|
+
Run: git rev-list --left-right --count main...origin/main
|
|
1375
|
+
If REMOTE_AHEAD > 0: git rebase origin/main
|
|
1376
|
+
If rebase conflicts: abort and report failure.
|
|
1377
|
+
5. Run tests on main to establish a baseline. Record BASELINE_PASS and BASELINE_FAIL.
|
|
1378
|
+
|
|
1379
|
+
PHASE 2 \u2014 MERGE:
|
|
1380
|
+
6. git merge ${mergeBranch} --no-edit
|
|
1381
|
+
7. If conflicts: resolve them intelligently, then git add and git commit
|
|
1382
|
+
8. If clean merge: the merge commit is auto-created (or fast-forward)
|
|
1383
|
+
|
|
1384
|
+
PHASE 3 \u2014 VERIFY:
|
|
1385
|
+
9. Run tests again. Record MERGE_PASS and MERGE_FAIL.
|
|
1386
|
+
|
|
1387
|
+
PHASE 4 \u2014 DECIDE:
|
|
1388
|
+
10. Compare results:
|
|
1389
|
+
- If MERGE_FAIL > BASELINE_FAIL (NEW test failures): ROLLBACK with git reset --hard ORIG_HEAD
|
|
1390
|
+
- If MERGE_FAIL <= BASELINE_FAIL (no new failures): PUSH with git push origin main
|
|
1391
|
+
- Pre-existing failures on main are NOT a reason to rollback
|
|
1392
|
+
|
|
1393
|
+
PHASE 5 \u2014 REPORT:
|
|
1394
|
+
11. Call the Panopticon API to report results:
|
|
1395
|
+
curl -s -X POST ${apiUrl}/api/specialists/done \\
|
|
1396
|
+
-H "Content-Type: application/json" \\
|
|
1397
|
+
-d '{"specialist":"merge","issueId":"${task.issueId}","status":"passed|failed","notes":"<summary>"}'
|
|
1398
|
+
|
|
1399
|
+
CRITICAL: You MUST call the /api/specialists/done endpoint whether you succeed or fail.
|
|
1400
|
+
CRITICAL: NEVER use git push --force.
|
|
1401
|
+
CRITICAL: Do NOT delete the feature branch.`;
|
|
1294
1402
|
break;
|
|
1403
|
+
}
|
|
1295
1404
|
case "review-agent": {
|
|
1296
1405
|
const workspace = task.workspace || "unknown";
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
if (existsSync3(join4(workspace, ".git"))) {
|
|
1300
|
-
gitDirs = [workspace];
|
|
1301
|
-
} else {
|
|
1302
|
-
try {
|
|
1303
|
-
const entries = readdirSync2(workspace, { withFileTypes: true });
|
|
1304
|
-
for (const entry of entries) {
|
|
1305
|
-
if (entry.isDirectory() && existsSync3(join4(workspace, entry.name, ".git"))) {
|
|
1306
|
-
gitDirs.push(join4(workspace, entry.name));
|
|
1307
|
-
}
|
|
1308
|
-
}
|
|
1309
|
-
} catch {
|
|
1310
|
-
}
|
|
1311
|
-
}
|
|
1312
|
-
}
|
|
1406
|
+
const reviewGitInfo = await resolveWorkspaceGitInfo(task.workspace, task.branch);
|
|
1407
|
+
const gitDirs = reviewGitInfo.gitDirs;
|
|
1313
1408
|
const gitDir = gitDirs[0] || workspace;
|
|
1314
1409
|
let staleBranch = false;
|
|
1315
1410
|
if (workspace !== "unknown" && gitDirs.length > 0) {
|
|
@@ -1332,7 +1427,7 @@ Use the send-feedback-to-agent skill to report findings back to the issue agent.
|
|
|
1332
1427
|
});
|
|
1333
1428
|
console.log(`[specialist] review-agent: auto-passed ${task.issueId} (stale branch)`);
|
|
1334
1429
|
const tmuxSession = getTmuxSessionName("review-agent");
|
|
1335
|
-
const { saveAgentRuntimeState } = await import("./agents-
|
|
1430
|
+
const { saveAgentRuntimeState } = await import("./agents-E43Y3HNU.js");
|
|
1336
1431
|
saveAgentRuntimeState(tmuxSession, {
|
|
1337
1432
|
state: "idle",
|
|
1338
1433
|
lastActivity: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -1433,14 +1528,62 @@ curl -s -X POST ${apiUrl}/api/specialists/test-agent/queue -H "Content-Type: app
|
|
|
1433
1528
|
\u26A0\uFE0F VERIFICATION: After running each curl, confirm you see valid JSON output. If you get an error, report it.`;
|
|
1434
1529
|
break;
|
|
1435
1530
|
}
|
|
1436
|
-
case "test-agent":
|
|
1531
|
+
case "test-agent": {
|
|
1532
|
+
const testWorkspace = task.workspace || "unknown";
|
|
1533
|
+
const testGitInfo = await resolveWorkspaceGitInfo(task.workspace, task.branch);
|
|
1534
|
+
const testIsPolyrepo = testGitInfo.isPolyrepo;
|
|
1535
|
+
const { extractTeamPrefix, findProjectByTeam } = await import("./projects-JEIVIYC6.js");
|
|
1536
|
+
const testTeamPrefix = extractTeamPrefix(task.issueId);
|
|
1537
|
+
const testProjectConfig = testTeamPrefix ? findProjectByTeam(testTeamPrefix) : null;
|
|
1538
|
+
const testConfigs = testProjectConfig?.tests;
|
|
1539
|
+
let testCommands = "";
|
|
1540
|
+
let baselineCommands = "";
|
|
1541
|
+
const featureName = task.issueId.toLowerCase();
|
|
1542
|
+
const mainWorkspacePath = testWorkspace.replace(/workspaces\/feature-[^/]+/, "workspaces/main");
|
|
1543
|
+
const projectRootPath = testProjectConfig?.path || testWorkspace.replace(/\/workspaces\/.*/, "");
|
|
1544
|
+
if (testConfigs && Object.keys(testConfigs).length > 0) {
|
|
1545
|
+
const testEntries = Object.entries(testConfigs);
|
|
1546
|
+
const testSuites = [];
|
|
1547
|
+
const baselineSuites = [];
|
|
1548
|
+
for (const [name2, cfg] of testEntries) {
|
|
1549
|
+
const testDir = testIsPolyrepo ? `${testWorkspace}/${cfg.path}` : cfg.path === "." ? testWorkspace : `${testWorkspace}/${cfg.path}`;
|
|
1550
|
+
const baseDir = testIsPolyrepo ? `${mainWorkspacePath}/${cfg.path}` : cfg.path === "." ? mainWorkspacePath : `${mainWorkspacePath}/${cfg.path}`;
|
|
1551
|
+
const fallbackDir = cfg.path === "." ? projectRootPath : `${projectRootPath}/${cfg.path}`;
|
|
1552
|
+
testSuites.push(`echo "\\n=== Test suite: ${name2} (${cfg.type}) ===" && cd "${testDir}" && ${cfg.command} 2>&1; echo "EXIT_CODE_${name2}: $?"`);
|
|
1553
|
+
baselineSuites.push(`echo "\\n=== Baseline: ${name2} (${cfg.type}) ===" && cd "${baseDir}" 2>/dev/null && ${cfg.command} 2>&1 || (cd "${fallbackDir}" 2>/dev/null && ${cfg.command} 2>&1) || echo "BASELINE_SKIP_${name2}: could not run baseline"; echo "EXIT_CODE_${name2}: $?"`);
|
|
1554
|
+
}
|
|
1555
|
+
testCommands = testSuites.map((cmd, i) => `# Suite ${i + 1}
|
|
1556
|
+
${cmd}`).join("\n");
|
|
1557
|
+
baselineCommands = baselineSuites.map((cmd, i) => `# Suite ${i + 1}
|
|
1558
|
+
${cmd}`).join("\n");
|
|
1559
|
+
} else if (testIsPolyrepo) {
|
|
1560
|
+
const testSuites = [];
|
|
1561
|
+
const baselineSuites = [];
|
|
1562
|
+
for (const gitDir of testGitInfo.gitDirs) {
|
|
1563
|
+
const repoName = basename2(gitDir);
|
|
1564
|
+
testSuites.push(`echo "\\n=== ${repoName} ===" && cd "${gitDir}" && if [ -f pom.xml ]; then ./mvnw test 2>&1; elif [ -f package.json ]; then npm test 2>&1; else echo "No test runner found"; fi; echo "EXIT_CODE_${repoName}: $?"`);
|
|
1565
|
+
const baseDir = `${mainWorkspacePath}/${repoName}`;
|
|
1566
|
+
baselineSuites.push(`echo "\\n=== Baseline: ${repoName} ===" && cd "${baseDir}" 2>/dev/null && if [ -f pom.xml ]; then ./mvnw test 2>&1; elif [ -f package.json ]; then npm test 2>&1; else echo "No test runner found"; fi; echo "EXIT_CODE_${repoName}: $?"`);
|
|
1567
|
+
}
|
|
1568
|
+
testCommands = testSuites.join("\n");
|
|
1569
|
+
baselineCommands = baselineSuites.join("\n");
|
|
1570
|
+
} else {
|
|
1571
|
+
testCommands = `cd "${testWorkspace}" && npm test 2>&1; echo "EXIT_CODE: $?"`;
|
|
1572
|
+
baselineCommands = `cd "${mainWorkspacePath}" 2>/dev/null && npm test 2>&1 || (cd "${projectRootPath}" && npm test 2>&1); echo "EXIT_CODE: $?"`;
|
|
1573
|
+
}
|
|
1574
|
+
const testConfigSummary = testConfigs ? Object.entries(testConfigs).map(([name2, cfg]) => `- **${name2}** (${cfg.type}): \`${cfg.command}\` in \`${cfg.path}/\``).join("\n") : testIsPolyrepo ? testGitInfo.gitDirs.map((d) => `- **${basename2(d)}**: auto-detected`).join("\n") : "- Single test suite at workspace root";
|
|
1437
1575
|
prompt = `New test task for ${task.issueId}:
|
|
1438
1576
|
|
|
1439
1577
|
Branch: ${task.branch || "unknown"}
|
|
1440
|
-
Workspace: ${
|
|
1578
|
+
Workspace: ${testWorkspace}
|
|
1579
|
+
${testIsPolyrepo ? `Polyrepo: git repos in subdirectories: ${testGitInfo.gitDirs.map((d) => basename2(d)).join(", ")}` : ""}
|
|
1580
|
+
|
|
1581
|
+
## Test Suites
|
|
1582
|
+
|
|
1583
|
+
${testConfigSummary}
|
|
1441
1584
|
|
|
1442
1585
|
Your task:
|
|
1443
|
-
1. Run
|
|
1586
|
+
1. Run ALL test suites \u2014 redirect output to file, read only summaries
|
|
1444
1587
|
2. If ALL pass, skip baseline and report PASS
|
|
1445
1588
|
3. If failures, run baseline on main and compare
|
|
1446
1589
|
4. Only fail for NEW regressions (not pre-existing)
|
|
@@ -1454,32 +1597,43 @@ Raw test output from large suites (1000+ tests) WILL fill your context and cause
|
|
|
1454
1597
|
## CRITICAL: Bash Timeout for Test Commands
|
|
1455
1598
|
|
|
1456
1599
|
**ALWAYS use timeout: 300000 (5 minutes) when running test commands.**
|
|
1600
|
+
For Maven/Spring Boot tests, use timeout: 600000 (10 minutes) \u2014 they take longer.
|
|
1457
1601
|
|
|
1458
1602
|
## Step 1: Run Feature Branch Tests
|
|
1459
1603
|
|
|
1604
|
+
${testIsPolyrepo || testConfigs && Object.keys(testConfigs).length > 1 ? `**Run ALL test suites** \u2014 each suite is a separate repo/runner. Redirect ALL output to one file.` : ""}
|
|
1605
|
+
|
|
1460
1606
|
\`\`\`bash
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1607
|
+
(
|
|
1608
|
+
${testCommands}
|
|
1609
|
+
) > /tmp/test-feature.txt 2>&1
|
|
1610
|
+
# Use timeout: ${testConfigs && Object.values(testConfigs).some((c) => c.type === "maven") ? "600000" : "300000"} for this command
|
|
1611
|
+
echo "--- Feature test output tail ---"
|
|
1612
|
+
tail -40 /tmp/test-feature.txt
|
|
1613
|
+
grep "EXIT_CODE" /tmp/test-feature.txt
|
|
1464
1614
|
\`\`\`
|
|
1465
1615
|
|
|
1466
1616
|
## Step 2: Check Results
|
|
1467
1617
|
|
|
1468
|
-
- If ALL
|
|
1469
|
-
- If failures
|
|
1618
|
+
- If ALL exit codes are 0 \u2192 skip baseline, go to "Update Status"
|
|
1619
|
+
- If any failures \u2192 continue to Step 3
|
|
1470
1620
|
|
|
1471
1621
|
## Step 3: Baseline Comparison (ONLY if failures found)
|
|
1472
1622
|
|
|
1473
1623
|
\`\`\`bash
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1624
|
+
(
|
|
1625
|
+
${baselineCommands}
|
|
1626
|
+
) > /tmp/test-main.txt 2>&1
|
|
1627
|
+
# Use timeout: ${testConfigs && Object.values(testConfigs).some((c) => c.type === "maven") ? "600000" : "300000"} for this command
|
|
1628
|
+
echo "--- Baseline test output tail ---"
|
|
1629
|
+
tail -40 /tmp/test-main.txt
|
|
1630
|
+
grep "EXIT_CODE" /tmp/test-main.txt
|
|
1477
1631
|
\`\`\`
|
|
1478
1632
|
|
|
1479
1633
|
Then compare failures (targeted, NOT full output):
|
|
1480
1634
|
\`\`\`bash
|
|
1481
|
-
grep -E "FAIL|\u2717|Error|failed" /tmp/test-feature.txt | head -30
|
|
1482
|
-
grep -E "FAIL|\u2717|Error|failed" /tmp/test-main.txt | head -30
|
|
1635
|
+
grep -E "FAIL|\u2717|Error|failed|BUILD FAILURE" /tmp/test-feature.txt | head -30
|
|
1636
|
+
grep -E "FAIL|\u2717|Error|failed|BUILD FAILURE" /tmp/test-main.txt | head -30
|
|
1483
1637
|
\`\`\`
|
|
1484
1638
|
|
|
1485
1639
|
Tests that fail on BOTH = pre-existing (don't block). Tests that fail ONLY on feature = NEW regression (block).
|
|
@@ -1493,12 +1647,12 @@ You MUST execute the appropriate curl command and verify it succeeds. Do NOT jus
|
|
|
1493
1647
|
|
|
1494
1648
|
If NO new regressions (tests PASS):
|
|
1495
1649
|
\`\`\`bash
|
|
1496
|
-
curl -s -X POST ${apiUrl}/api/workspaces/${task.issueId}/review-status -H "Content-Type: application/json" -d '{"testStatus":"passed","testNotes":"[summary including pre-existing failures if any]"}' | jq .
|
|
1650
|
+
curl -s -X POST ${apiUrl}/api/workspaces/${task.issueId}/review-status -H "Content-Type: application/json" -d '{"testStatus":"passed","testNotes":"[summary including pre-existing failures if any, and which suites were tested]"}' | jq .
|
|
1497
1651
|
\`\`\`
|
|
1498
1652
|
|
|
1499
1653
|
If NEW regressions found (tests FAIL):
|
|
1500
1654
|
\`\`\`bash
|
|
1501
|
-
curl -s -X POST ${apiUrl}/api/workspaces/${task.issueId}/review-status -H "Content-Type: application/json" -d '{"testStatus":"failed","testNotes":"[describe NEW failures only]"}' | jq .
|
|
1655
|
+
curl -s -X POST ${apiUrl}/api/workspaces/${task.issueId}/review-status -H "Content-Type: application/json" -d '{"testStatus":"failed","testNotes":"[describe NEW failures only \u2014 specify which suite/repo]"}' | jq .
|
|
1502
1656
|
\`\`\`
|
|
1503
1657
|
Then use send-feedback-to-agent skill to notify issue agent of NEW failures only.
|
|
1504
1658
|
|
|
@@ -1508,6 +1662,7 @@ Then use send-feedback-to-agent skill to notify issue agent of NEW failures only
|
|
|
1508
1662
|
|
|
1509
1663
|
IMPORTANT: Do NOT hand off to merge-agent. Human clicks Merge button when ready.`;
|
|
1510
1664
|
break;
|
|
1665
|
+
}
|
|
1511
1666
|
default:
|
|
1512
1667
|
prompt = `Task for ${task.issueId}: Please process this task and report findings.`;
|
|
1513
1668
|
}
|
|
@@ -1516,7 +1671,7 @@ IMPORTANT: Do NOT hand off to merge-agent. Human clicks Merge button when ready.
|
|
|
1516
1671
|
async function wakeSpecialistOrQueue(name, task, options = {}) {
|
|
1517
1672
|
const { priority = "normal", source = "handoff" } = options;
|
|
1518
1673
|
const running = await isRunning(name);
|
|
1519
|
-
const { getAgentRuntimeState } = await import("./agents-
|
|
1674
|
+
const { getAgentRuntimeState } = await import("./agents-E43Y3HNU.js");
|
|
1520
1675
|
const tmuxSession = getTmuxSessionName(name);
|
|
1521
1676
|
const runtimeState = getAgentRuntimeState(tmuxSession);
|
|
1522
1677
|
const idle = runtimeState?.state === "idle" || runtimeState?.state === "suspended";
|
|
@@ -1547,7 +1702,7 @@ async function wakeSpecialistOrQueue(name, task, options = {}) {
|
|
|
1547
1702
|
};
|
|
1548
1703
|
}
|
|
1549
1704
|
}
|
|
1550
|
-
const { saveAgentRuntimeState } = await import("./agents-
|
|
1705
|
+
const { saveAgentRuntimeState } = await import("./agents-E43Y3HNU.js");
|
|
1551
1706
|
saveAgentRuntimeState(tmuxSession, {
|
|
1552
1707
|
state: "active",
|
|
1553
1708
|
lastActivity: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -1667,7 +1822,7 @@ async function sendFeedbackToAgent(feedback) {
|
|
|
1667
1822
|
return false;
|
|
1668
1823
|
}
|
|
1669
1824
|
try {
|
|
1670
|
-
const { messageAgent } = await import("./agents-
|
|
1825
|
+
const { messageAgent } = await import("./agents-E43Y3HNU.js");
|
|
1671
1826
|
const msg = `SPECIALIST FEEDBACK: ${fromSpecialist} reported ${feedback.feedbackType.toUpperCase()} for ${toIssueId}.
|
|
1672
1827
|
Read and address: ${fileResult.relativePath}`;
|
|
1673
1828
|
await messageAgent(agentSession, msg);
|
|
@@ -2161,4 +2316,4 @@ export {
|
|
|
2161
2316
|
getFeedbackStats,
|
|
2162
2317
|
init_specialists
|
|
2163
2318
|
};
|
|
2164
|
-
//# sourceMappingURL=chunk-
|
|
2319
|
+
//# sourceMappingURL=chunk-AAFQANKW.js.map
|