panopticon-cli 0.5.8 → 0.5.10

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.
Files changed (100) hide show
  1. package/README.md +29 -83
  2. package/dist/{agents-I6RAEGL5.js → agents-MOMDECON.js} +8 -6
  3. package/dist/{archive-planning-U3AZAKWI.js → archive-planning-54J6EP6A.js} +3 -3
  4. package/dist/{chunk-UKSGE6RH.js → chunk-3KYTNMSE.js} +1 -2
  5. package/dist/{chunk-UKSGE6RH.js.map → chunk-3KYTNMSE.js.map} +1 -1
  6. package/dist/{chunk-M6ZVVKZ3.js → chunk-4OQ4SXQZ.js} +219 -107
  7. package/dist/chunk-4OQ4SXQZ.js.map +1 -0
  8. package/dist/{chunk-ZMJFEHGF.js → chunk-7ZB5D46Y.js} +2 -2
  9. package/dist/{chunk-ZMJFEHGF.js.map → chunk-7ZB5D46Y.js.map} +1 -1
  10. package/dist/{chunk-BYWVPPAZ.js → chunk-BHRMW7BY.js} +31 -4
  11. package/dist/chunk-BHRMW7BY.js.map +1 -0
  12. package/dist/{chunk-WEQW3EAT.js → chunk-F4XS2FQN.js} +3 -2
  13. package/dist/chunk-F4XS2FQN.js.map +1 -0
  14. package/dist/{chunk-OJF4QS3S.js → chunk-GIW2TUWI.js} +2 -2
  15. package/dist/{chunk-SUM2WVPF.js → chunk-H7T35QDO.js} +30 -12
  16. package/dist/chunk-H7T35QDO.js.map +1 -0
  17. package/dist/{chunk-MJXYTGK5.js → chunk-JZWCL5S5.js} +2 -2
  18. package/dist/{chunk-ZN5RHWGR.js → chunk-PFA5XE2V.js} +5 -41
  19. package/dist/chunk-PFA5XE2V.js.map +1 -0
  20. package/dist/{chunk-6OYUJ4AJ.js → chunk-R47UJWF6.js} +2 -2
  21. package/dist/{chunk-NYOGHGIW.js → chunk-RCYJK3ZC.js} +10 -9
  22. package/dist/chunk-RCYJK3ZC.js.map +1 -0
  23. package/dist/{chunk-R4KPLLRB.js → chunk-SFX3BG6N.js} +1 -1
  24. package/dist/chunk-SFX3BG6N.js.map +1 -0
  25. package/dist/{chunk-IZIXJYXZ.js → chunk-TA5X4QYQ.js} +6 -2
  26. package/dist/{chunk-IZIXJYXZ.js.map → chunk-TA5X4QYQ.js.map} +1 -1
  27. package/dist/{chunk-43F4LDZ4.js → chunk-VVTAPQOI.js} +2 -2
  28. package/dist/{chunk-YAAT66RT.js → chunk-WP6ZLWU3.js} +28 -3
  29. package/dist/chunk-WP6ZLWU3.js.map +1 -0
  30. package/dist/clean-planning-V4SSVU26.js +9 -0
  31. package/dist/cli/index.js +1654 -1056
  32. package/dist/cli/index.js.map +1 -1
  33. package/dist/close-issue-5OMOP2FU.js +9 -0
  34. package/dist/compact-beads-YQDVF6FQ.js +9 -0
  35. package/dist/dashboard/prompts/inspect-agent.md +157 -0
  36. package/dist/dashboard/prompts/merge-agent.md +11 -0
  37. package/dist/dashboard/prompts/review-agent.md +9 -0
  38. package/dist/dashboard/prompts/test-agent.md +9 -0
  39. package/dist/dashboard/prompts/uat-agent.md +215 -0
  40. package/dist/dashboard/prompts/work-agent.md +53 -5
  41. package/dist/dashboard/public/assets/index-5hYjhhGn.js +826 -0
  42. package/dist/dashboard/public/assets/index-DIFh3T1V.css +32 -0
  43. package/dist/dashboard/public/index.html +3 -6
  44. package/dist/dashboard/server.js +3338 -2033
  45. package/dist/factory-KKT7324R.js +20 -0
  46. package/dist/{feedback-writer-T2WCT6EZ.js → feedback-writer-IPPIUPDX.js} +2 -2
  47. package/dist/feedback-writer-IPPIUPDX.js.map +1 -0
  48. package/dist/index.d.ts +8 -3
  49. package/dist/index.js +19 -19
  50. package/dist/{label-cleanup-4HJVX6NP.js → label-cleanup-4IVZIPGK.js} +2 -2
  51. package/dist/{merge-agent-ZITLVF2B.js → merge-agent-6YOMGQMX.js} +16 -16
  52. package/dist/{projects-3CRF57ZU.js → projects-BPGM6IFB.js} +2 -2
  53. package/dist/{remote-workspace-M4IULGFZ.js → remote-workspace-LKRDGYEB.js} +2 -2
  54. package/dist/{review-status-J2YJGL3E.js → review-status-E77PZZWG.js} +2 -2
  55. package/dist/{specialist-context-W25PPWM4.js → specialist-context-GVF4DV3M.js} +5 -5
  56. package/dist/{specialist-logs-KPC45SZN.js → specialist-logs-W47SAAIU.js} +5 -5
  57. package/dist/{specialists-H4LGYR7R.js → specialists-SIXRWCZ3.js} +5 -5
  58. package/dist/{traefik-QXLZ4PO2.js → traefik-X2IWTUHO.js} +3 -3
  59. package/dist/{workspace-manager-G6TTBPC3.js → workspace-manager-Z57ROWBQ.js} +2 -2
  60. package/dist/workspace-manager-Z57ROWBQ.js.map +1 -0
  61. package/package.json +1 -1
  62. package/scripts/inspect-on-bead-close +73 -0
  63. package/scripts/stop-hook +17 -0
  64. package/skills/pan-new-project/SKILL.md +1 -1
  65. package/skills/pan-oversee/SKILL.md +45 -10
  66. package/skills/plan/SKILL.md +336 -0
  67. package/dist/chunk-BYWVPPAZ.js.map +0 -1
  68. package/dist/chunk-M6ZVVKZ3.js.map +0 -1
  69. package/dist/chunk-NYOGHGIW.js.map +0 -1
  70. package/dist/chunk-R4KPLLRB.js.map +0 -1
  71. package/dist/chunk-SUM2WVPF.js.map +0 -1
  72. package/dist/chunk-WEQW3EAT.js.map +0 -1
  73. package/dist/chunk-YAAT66RT.js.map +0 -1
  74. package/dist/chunk-ZN5RHWGR.js.map +0 -1
  75. package/dist/clean-planning-7Z5YY64X.js +0 -9
  76. package/dist/close-issue-CTZK777I.js +0 -9
  77. package/dist/compact-beads-72SHALOL.js +0 -9
  78. package/dist/dashboard/public/assets/index-Bx4NCn9A.css +0 -32
  79. package/dist/dashboard/public/assets/index-C7hJ5-o1.js +0 -756
  80. package/dist/feedback-writer-T2WCT6EZ.js.map +0 -1
  81. package/skills/opus-plan/SKILL.md +0 -400
  82. /package/dist/{agents-I6RAEGL5.js.map → agents-MOMDECON.js.map} +0 -0
  83. /package/dist/{archive-planning-U3AZAKWI.js.map → archive-planning-54J6EP6A.js.map} +0 -0
  84. /package/dist/{chunk-OJF4QS3S.js.map → chunk-GIW2TUWI.js.map} +0 -0
  85. /package/dist/{chunk-MJXYTGK5.js.map → chunk-JZWCL5S5.js.map} +0 -0
  86. /package/dist/{chunk-6OYUJ4AJ.js.map → chunk-R47UJWF6.js.map} +0 -0
  87. /package/dist/{chunk-43F4LDZ4.js.map → chunk-VVTAPQOI.js.map} +0 -0
  88. /package/dist/{clean-planning-7Z5YY64X.js.map → clean-planning-V4SSVU26.js.map} +0 -0
  89. /package/dist/{close-issue-CTZK777I.js.map → close-issue-5OMOP2FU.js.map} +0 -0
  90. /package/dist/{compact-beads-72SHALOL.js.map → compact-beads-YQDVF6FQ.js.map} +0 -0
  91. /package/dist/{projects-3CRF57ZU.js.map → factory-KKT7324R.js.map} +0 -0
  92. /package/dist/{label-cleanup-4HJVX6NP.js.map → label-cleanup-4IVZIPGK.js.map} +0 -0
  93. /package/dist/{merge-agent-ZITLVF2B.js.map → merge-agent-6YOMGQMX.js.map} +0 -0
  94. /package/dist/{review-status-J2YJGL3E.js.map → projects-BPGM6IFB.js.map} +0 -0
  95. /package/dist/{remote-workspace-M4IULGFZ.js.map → remote-workspace-LKRDGYEB.js.map} +0 -0
  96. /package/dist/{specialist-logs-KPC45SZN.js.map → review-status-E77PZZWG.js.map} +0 -0
  97. /package/dist/{specialist-context-W25PPWM4.js.map → specialist-context-GVF4DV3M.js.map} +0 -0
  98. /package/dist/{specialists-H4LGYR7R.js.map → specialist-logs-W47SAAIU.js.map} +0 -0
  99. /package/dist/{traefik-QXLZ4PO2.js.map → specialists-SIXRWCZ3.js.map} +0 -0
  100. /package/dist/{workspace-manager-G6TTBPC3.js.map → traefik-X2IWTUHO.js.map} +0 -0
@@ -9,7 +9,7 @@ import {
9
9
  init_work_type_router,
10
10
  popFromHook,
11
11
  pushToHook
12
- } from "./chunk-ZN5RHWGR.js";
12
+ } from "./chunk-PFA5XE2V.js";
13
13
  import {
14
14
  clearCredentialFileAuth,
15
15
  getProviderEnv,
@@ -27,7 +27,7 @@ import {
27
27
  getProject,
28
28
  init_projects,
29
29
  projects_exports
30
- } from "./chunk-ZMJFEHGF.js";
30
+ } from "./chunk-7ZB5D46Y.js";
31
31
  import {
32
32
  capturePaneAsync,
33
33
  confirmDelivery,
@@ -552,6 +552,80 @@ var init_specialist_handoff_logger = __esm({
552
552
  }
553
553
  });
554
554
 
555
+ // src/lib/vbrief/io.ts
556
+ import { existsSync as existsSync4, readFileSync as readFileSync4, renameSync, writeFileSync as writeFileSync2 } from "fs";
557
+ import { join as join4 } from "path";
558
+ function findPlan(workspacePath) {
559
+ const planPath = join4(workspacePath, ".planning", PLAN_FILENAME);
560
+ return existsSync4(planPath) ? planPath : null;
561
+ }
562
+ function readPlan(planPath) {
563
+ const raw = readFileSync4(planPath, "utf-8");
564
+ return JSON.parse(raw);
565
+ }
566
+ function readWorkspacePlan(workspacePath) {
567
+ const planPath = findPlan(workspacePath);
568
+ if (!planPath) return null;
569
+ return readPlan(planPath);
570
+ }
571
+ function updateItemStatus(workspacePath, itemId, status) {
572
+ const planPath = findPlan(workspacePath);
573
+ if (!planPath) return;
574
+ const doc = readPlan(planPath);
575
+ const item = doc.plan.items.find((i) => i.id === itemId);
576
+ if (!item) return;
577
+ item.status = status;
578
+ const tempPath = planPath + ".tmp";
579
+ writeFileSync2(tempPath, JSON.stringify(doc, null, 2), "utf-8");
580
+ renameSync(tempPath, planPath);
581
+ }
582
+ function updateSubItemStatus(workspacePath, itemId, subItemId, status) {
583
+ const planPath = findPlan(workspacePath);
584
+ if (!planPath) return;
585
+ const doc = readPlan(planPath);
586
+ const item = doc.plan.items.find((i) => i.id === itemId);
587
+ if (!item?.subItems) return;
588
+ const subItem = item.subItems.find((s) => s.id === subItemId);
589
+ if (!subItem) return;
590
+ subItem.status = status;
591
+ const tempPath = planPath + ".tmp";
592
+ writeFileSync2(tempPath, JSON.stringify(doc, null, 2), "utf-8");
593
+ renameSync(tempPath, planPath);
594
+ }
595
+ var PLAN_FILENAME;
596
+ var init_io = __esm({
597
+ "src/lib/vbrief/io.ts"() {
598
+ "use strict";
599
+ init_esm_shims();
600
+ PLAN_FILENAME = "plan.vbrief.json";
601
+ }
602
+ });
603
+
604
+ // src/lib/cloister/task-readiness.ts
605
+ function isTaskReady(itemId, workspacePath) {
606
+ const doc = readWorkspacePlan(workspacePath);
607
+ if (!doc) return true;
608
+ const itemExists = doc.plan.items.some((i) => i.id === itemId);
609
+ if (!itemExists) return true;
610
+ const blockerIds = doc.plan.edges.filter((e) => e.type === "blocks" && e.to === itemId).map((e) => e.from);
611
+ if (blockerIds.length === 0) return true;
612
+ const itemById = new Map(doc.plan.items.map((i) => [i.id, i]));
613
+ return blockerIds.every((blockerId) => {
614
+ const blocker = itemById.get(blockerId);
615
+ if (!blocker) return true;
616
+ return TERMINAL_STATUSES.includes(blocker.status);
617
+ });
618
+ }
619
+ var TERMINAL_STATUSES;
620
+ var init_task_readiness = __esm({
621
+ "src/lib/cloister/task-readiness.ts"() {
622
+ "use strict";
623
+ init_esm_shims();
624
+ init_io();
625
+ TERMINAL_STATUSES = ["completed", "cancelled"];
626
+ }
627
+ });
628
+
555
629
  // src/lib/cloister/specialists.ts
556
630
  var specialists_exports = {};
557
631
  __export(specialists_exports, {
@@ -611,8 +685,8 @@ __export(specialists_exports, {
611
685
  wakeSpecialistOrQueue: () => wakeSpecialistOrQueue,
612
686
  wakeSpecialistWithTask: () => wakeSpecialistWithTask
613
687
  });
614
- import { readFileSync as readFileSync4, writeFileSync as writeFileSync2, existsSync as existsSync4, mkdirSync as mkdirSync3, readdirSync as readdirSync3, unlinkSync, appendFileSync as appendFileSync3 } from "fs";
615
- import { join as join4, basename as basename2 } from "path";
688
+ import { readFileSync as readFileSync5, writeFileSync as writeFileSync3, existsSync as existsSync5, mkdirSync as mkdirSync3, readdirSync as readdirSync3, unlinkSync, appendFileSync as appendFileSync3 } from "fs";
689
+ import { join as join5, basename as basename2 } from "path";
616
690
  import { homedir as homedir2 } from "os";
617
691
  import { exec } from "child_process";
618
692
  import { promisify } from "util";
@@ -623,14 +697,14 @@ async function resolveWorkspaceGitInfo(workspace, taskBranch) {
623
697
  if (!workspace || workspace === "unknown") {
624
698
  return { gitDirs, branch, isPolyrepo: false };
625
699
  }
626
- if (existsSync4(join4(workspace, ".git"))) {
700
+ if (existsSync5(join5(workspace, ".git"))) {
627
701
  gitDirs.push(workspace);
628
702
  } else {
629
703
  try {
630
704
  const entries = readdirSync3(workspace, { withFileTypes: true });
631
705
  for (const entry of entries) {
632
- if (entry.isDirectory() && existsSync4(join4(workspace, entry.name, ".git"))) {
633
- gitDirs.push(join4(workspace, entry.name));
706
+ if (entry.isDirectory() && existsSync5(join5(workspace, entry.name, ".git"))) {
707
+ gitDirs.push(join5(workspace, entry.name));
634
708
  }
635
709
  }
636
710
  } catch {
@@ -670,10 +744,10 @@ function buildTmuxEnvFlags(env) {
670
744
  return flags;
671
745
  }
672
746
  function initSpecialistsDirectory() {
673
- if (!existsSync4(SPECIALISTS_DIR)) {
747
+ if (!existsSync5(SPECIALISTS_DIR)) {
674
748
  mkdirSync3(SPECIALISTS_DIR, { recursive: true });
675
749
  }
676
- if (!existsSync4(REGISTRY_FILE)) {
750
+ if (!existsSync5(REGISTRY_FILE)) {
677
751
  const registry = {
678
752
  version: "2.0",
679
753
  // Updated for per-project structure
@@ -697,7 +771,7 @@ function initSpecialistsDirectory() {
697
771
  }
698
772
  function migrateRegistryIfNeeded() {
699
773
  try {
700
- const content = readFileSync4(REGISTRY_FILE, "utf-8");
774
+ const content = readFileSync5(REGISTRY_FILE, "utf-8");
701
775
  const registry = JSON.parse(content);
702
776
  if (registry.version === "2.0" || registry.projects) {
703
777
  return;
@@ -727,7 +801,7 @@ function migrateRegistryIfNeeded() {
727
801
  function loadRegistry() {
728
802
  initSpecialistsDirectory();
729
803
  try {
730
- const content = readFileSync4(REGISTRY_FILE, "utf-8");
804
+ const content = readFileSync5(REGISTRY_FILE, "utf-8");
731
805
  return JSON.parse(content);
732
806
  } catch (error) {
733
807
  console.error("Failed to load specialist registry:", error);
@@ -745,13 +819,13 @@ function loadRegistry() {
745
819
  }
746
820
  }
747
821
  function saveRegistry(registry) {
748
- if (!existsSync4(SPECIALISTS_DIR)) {
822
+ if (!existsSync5(SPECIALISTS_DIR)) {
749
823
  mkdirSync3(SPECIALISTS_DIR, { recursive: true });
750
824
  }
751
825
  registry.lastUpdated = (/* @__PURE__ */ new Date()).toISOString();
752
826
  try {
753
827
  const content = JSON.stringify(registry, null, 2);
754
- writeFileSync2(REGISTRY_FILE, content, "utf-8");
828
+ writeFileSync3(REGISTRY_FILE, content, "utf-8");
755
829
  } catch (error) {
756
830
  console.error("Failed to save specialist registry:", error);
757
831
  throw error;
@@ -763,17 +837,17 @@ function deterministicUUID(input) {
763
837
  }
764
838
  function getSessionFilePath(name, projectKey) {
765
839
  if (projectKey) {
766
- return join4(SPECIALISTS_DIR, "projects", projectKey, `${name}.session`);
840
+ return join5(SPECIALISTS_DIR, "projects", projectKey, `${name}.session`);
767
841
  }
768
- return join4(SPECIALISTS_DIR, `${name}.session`);
842
+ return join5(SPECIALISTS_DIR, `${name}.session`);
769
843
  }
770
844
  function getSessionId(name, projectKey) {
771
845
  const sessionFile = getSessionFilePath(name, projectKey);
772
- if (!existsSync4(sessionFile)) {
846
+ if (!existsSync5(sessionFile)) {
773
847
  return null;
774
848
  }
775
849
  try {
776
- const sessionId = readFileSync4(sessionFile, "utf-8").trim();
850
+ const sessionId = readFileSync5(sessionFile, "utf-8").trim();
777
851
  const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
778
852
  if (!uuidRegex.test(sessionId)) {
779
853
  console.warn(`[specialist] Invalid session ID format for ${name} (${projectKey ?? "global"}): ${sessionId} \u2014 discarding`);
@@ -788,12 +862,12 @@ function getSessionId(name, projectKey) {
788
862
  }
789
863
  function setSessionId(name, sessionId, projectKey) {
790
864
  const sessionFile = getSessionFilePath(name, projectKey);
791
- const dir = projectKey ? join4(SPECIALISTS_DIR, "projects", projectKey) : SPECIALISTS_DIR;
792
- if (!existsSync4(dir)) {
865
+ const dir = projectKey ? join5(SPECIALISTS_DIR, "projects", projectKey) : SPECIALISTS_DIR;
866
+ if (!existsSync5(dir)) {
793
867
  mkdirSync3(dir, { recursive: true });
794
868
  }
795
869
  try {
796
- writeFileSync2(sessionFile, sessionId.trim(), "utf-8");
870
+ writeFileSync3(sessionFile, sessionId.trim(), "utf-8");
797
871
  } catch (error) {
798
872
  console.error(`Failed to write session file for ${name} (${projectKey ?? "global"}):`, error);
799
873
  throw error;
@@ -801,7 +875,7 @@ function setSessionId(name, sessionId, projectKey) {
801
875
  }
802
876
  function clearSessionId(name, projectKey) {
803
877
  const sessionFile = getSessionFilePath(name, projectKey);
804
- if (!existsSync4(sessionFile)) {
878
+ if (!existsSync5(sessionFile)) {
805
879
  return false;
806
880
  }
807
881
  try {
@@ -859,9 +933,9 @@ function recordWake(name, sessionId) {
859
933
  }
860
934
  async function spawnEphemeralSpecialist(projectKey, specialistType, task) {
861
935
  ensureProjectSpecialistDir(projectKey, specialistType);
862
- const { loadContextDigest } = await import("./specialist-context-W25PPWM4.js");
936
+ const { loadContextDigest } = await import("./specialist-context-GVF4DV3M.js");
863
937
  const contextDigest = loadContextDigest(projectKey, specialistType);
864
- const { createRunLog: createRunLog2 } = await import("./specialist-logs-KPC45SZN.js");
938
+ const { createRunLog: createRunLog2 } = await import("./specialist-logs-W47SAAIU.js");
865
939
  const { runId, filePath: logFilePath } = createRunLog2(
866
940
  projectKey,
867
941
  specialistType,
@@ -881,7 +955,7 @@ ${basePrompt}`;
881
955
  const project = getProject(projectKey);
882
956
  const cwd = project?.path || getDevrootPath() || homedir2();
883
957
  try {
884
- const { preTrustDirectory } = await import("./workspace-manager-G6TTBPC3.js");
958
+ const { preTrustDirectory } = await import("./workspace-manager-Z57ROWBQ.js");
885
959
  preTrustDirectory(cwd);
886
960
  } catch {
887
961
  }
@@ -889,7 +963,7 @@ ${basePrompt}`;
889
963
  try {
890
964
  const { stdout: sessions } = await execAsync('tmux list-sessions -F "#{session_name}" 2>/dev/null || echo ""', { encoding: "utf-8" });
891
965
  if (sessions.split("\n").map((s) => s.trim()).includes(tmuxSession)) {
892
- const { getAgentRuntimeState } = await import("./agents-I6RAEGL5.js");
966
+ const { getAgentRuntimeState } = await import("./agents-MOMDECON.js");
893
967
  const existingState = getAgentRuntimeState(tmuxSession);
894
968
  if (existingState?.state === "active") {
895
969
  return {
@@ -926,17 +1000,17 @@ ${basePrompt}`;
926
1000
  clearCredentialFileAuth(cwd);
927
1001
  }
928
1002
  const permissionFlags = specialistType === "merge-agent" ? "--dangerously-skip-permissions --permission-mode bypassPermissions" : "--dangerously-skip-permissions";
929
- const agentDir = join4(homedir2(), ".panopticon", "agents", tmuxSession);
1003
+ const agentDir = join5(homedir2(), ".panopticon", "agents", tmuxSession);
930
1004
  await execAsync(`mkdir -p "${agentDir}"`, { encoding: "utf-8" });
931
- const promptFile = join4(agentDir, "task-prompt.md");
932
- writeFileSync2(promptFile, taskPrompt);
1005
+ const promptFile = join5(agentDir, "task-prompt.md");
1006
+ writeFileSync3(promptFile, taskPrompt);
933
1007
  const sessionName = `specialist-${projectKey}-${specialistType}`;
934
1008
  const sessionId = deterministicUUID(sessionName);
935
1009
  setSessionId(specialistType, sessionId, projectKey);
936
1010
  console.log(`[specialist] Dispatching ${specialistType} for ${projectKey}/${task.issueId} (session: ${sessionId.slice(0, 8)}...)`);
937
- const launcherScript = join4(agentDir, "launcher.sh");
938
- const innerScript = join4(agentDir, "run-claude.sh");
939
- writeFileSync2(innerScript, `#!/bin/bash
1011
+ const launcherScript = join5(agentDir, "launcher.sh");
1012
+ const innerScript = join5(agentDir, "run-claude.sh");
1013
+ writeFileSync3(innerScript, `#!/bin/bash
940
1014
  set -o pipefail
941
1015
  cd "${cwd}"
942
1016
  export PANOPTICON_AGENT_ID="${tmuxSession}"
@@ -958,14 +1032,14 @@ fi
958
1032
  echo ""
959
1033
  echo "## Specialist completed task"
960
1034
  `, { mode: 493 });
961
- writeFileSync2(launcherScript, `#!/bin/bash
1035
+ writeFileSync3(launcherScript, `#!/bin/bash
962
1036
  script -qfec "bash '${innerScript}'" /dev/null 2>&1 | tee -a "${logFilePath}"
963
1037
  `, { mode: 493 });
964
1038
  await execAsync(
965
- `tmux new-session -d -s "${tmuxSession}"${envFlags} "bash '${launcherScript}'"`,
1039
+ `tmux new-session -d -s "${tmuxSession}" -c "${cwd}"${envFlags} "bash '${launcherScript}'"`,
966
1040
  { encoding: "utf-8" }
967
1041
  );
968
- const { saveAgentRuntimeState } = await import("./agents-I6RAEGL5.js");
1042
+ const { saveAgentRuntimeState } = await import("./agents-MOMDECON.js");
969
1043
  saveAgentRuntimeState(tmuxSession, {
970
1044
  state: "active",
971
1045
  lastActivity: (/* @__PURE__ */ new Date()).toISOString(),
@@ -989,7 +1063,7 @@ script -qfec "bash '${innerScript}'" /dev/null 2>&1 | tee -a "${logFilePath}"
989
1063
  }
990
1064
  }
991
1065
  async function buildTaskPrompt(projectKey, specialistType, task, contextDigest) {
992
- const { getSpecialistPromptOverride } = await import("./projects-3CRF57ZU.js");
1066
+ const { getSpecialistPromptOverride } = await import("./projects-BPGM6IFB.js");
993
1067
  const customPrompt = getSpecialistPromptOverride(projectKey, specialistType);
994
1068
  let prompt = `# ${specialistType} Task - ${task.issueId}
995
1069
 
@@ -1039,17 +1113,8 @@ Update status via API:
1039
1113
  - If issues found: POST to /api/workspaces/${task.issueId}/review-status with {"reviewStatus":"blocked","reviewNotes":"..."}
1040
1114
  - If review passes: POST with {"reviewStatus":"passed"} then queue test-agent`;
1041
1115
  break;
1042
- case "test-agent":
1043
- prompt += `Your task:
1044
- 1. Run the full test suite
1045
- 2. Analyze any failures in detail
1046
- 3. Identify root causes
1047
- 4. Update status via API when done
1048
-
1049
- Update status via API:
1050
- - If tests pass: POST to /api/workspaces/${task.issueId}/review-status with {"testStatus":"passed"}
1051
- - If tests fail: POST with {"testStatus":"failed","testNotes":"..."}`;
1052
- break;
1116
+ // test-agent: detailed prompt is in the second switch block below (line ~2173)
1117
+ // Do NOT add an inline stub here — it would shadow the detailed template.
1053
1118
  case "merge-agent": {
1054
1119
  const bInfo = await resolveWorkspaceGitInfo(task.workspace, task.branch);
1055
1120
  if (bInfo.isPolyrepo) {
@@ -1159,7 +1224,7 @@ async function terminateSpecialist(projectKey, specialistType) {
1159
1224
  console.error(`[specialist] Failed to kill tmux session ${tmuxSession}:`, error);
1160
1225
  }
1161
1226
  if (metadata.currentRun) {
1162
- const { finalizeRunLog: finalizeRunLog2 } = await import("./specialist-logs-KPC45SZN.js");
1227
+ const { finalizeRunLog: finalizeRunLog2 } = await import("./specialist-logs-W47SAAIU.js");
1163
1228
  try {
1164
1229
  finalizeRunLog2(projectKey, specialistType, metadata.currentRun, {
1165
1230
  status: metadata.lastRunStatus || "incomplete",
@@ -1172,20 +1237,20 @@ async function terminateSpecialist(projectKey, specialistType) {
1172
1237
  }
1173
1238
  const key = `${projectKey}-${specialistType}`;
1174
1239
  gracePeriodStates.delete(key);
1175
- const { saveAgentRuntimeState } = await import("./agents-I6RAEGL5.js");
1240
+ const { saveAgentRuntimeState } = await import("./agents-MOMDECON.js");
1176
1241
  saveAgentRuntimeState(tmuxSession, {
1177
1242
  state: "suspended",
1178
1243
  lastActivity: (/* @__PURE__ */ new Date()).toISOString()
1179
1244
  });
1180
- const { scheduleDigestGeneration } = await import("./specialist-context-W25PPWM4.js");
1245
+ const { scheduleDigestGeneration } = await import("./specialist-context-GVF4DV3M.js");
1181
1246
  scheduleDigestGeneration(projectKey, specialistType);
1182
1247
  scheduleLogCleanup(projectKey, specialistType);
1183
1248
  }
1184
1249
  function scheduleLogCleanup(projectKey, specialistType) {
1185
1250
  Promise.resolve().then(async () => {
1186
1251
  try {
1187
- const { cleanupOldLogs: cleanupOldLogs2 } = await import("./specialist-logs-KPC45SZN.js");
1188
- const { getSpecialistRetention } = await import("./projects-3CRF57ZU.js");
1252
+ const { cleanupOldLogs: cleanupOldLogs2 } = await import("./specialist-logs-W47SAAIU.js");
1253
+ const { getSpecialistRetention } = await import("./projects-BPGM6IFB.js");
1189
1254
  const retention = getSpecialistRetention(projectKey);
1190
1255
  const deleted = cleanupOldLogs2(projectKey, specialistType, { maxDays: retention.max_days, maxRuns: retention.max_runs });
1191
1256
  if (deleted > 0) {
@@ -1197,16 +1262,16 @@ function scheduleLogCleanup(projectKey, specialistType) {
1197
1262
  });
1198
1263
  }
1199
1264
  function getProjectSpecialistDir(projectKey, specialistType) {
1200
- return join4(SPECIALISTS_DIR, projectKey, specialistType);
1265
+ return join5(SPECIALISTS_DIR, projectKey, specialistType);
1201
1266
  }
1202
1267
  function ensureProjectSpecialistDir(projectKey, specialistType) {
1203
1268
  const specialistDir = getProjectSpecialistDir(projectKey, specialistType);
1204
- const runsDir = join4(specialistDir, "runs");
1205
- const contextDir = join4(specialistDir, "context");
1206
- if (!existsSync4(runsDir)) {
1269
+ const runsDir = join5(specialistDir, "runs");
1270
+ const contextDir = join5(specialistDir, "context");
1271
+ if (!existsSync5(runsDir)) {
1207
1272
  mkdirSync3(runsDir, { recursive: true });
1208
1273
  }
1209
- if (!existsSync4(contextDir)) {
1274
+ if (!existsSync5(contextDir)) {
1210
1275
  mkdirSync3(contextDir, { recursive: true });
1211
1276
  }
1212
1277
  }
@@ -1373,7 +1438,7 @@ async function getSpecialistStatus(name, projectKey) {
1373
1438
  const sessionId = getSessionId(name, projectKey);
1374
1439
  const running = await isRunning(name, projectKey);
1375
1440
  const contextTokens = countContextTokens(name);
1376
- const { getAgentRuntimeState } = await import("./agents-I6RAEGL5.js");
1441
+ const { getAgentRuntimeState } = await import("./agents-MOMDECON.js");
1377
1442
  const tmuxSession = getTmuxSessionName(name, projectKey);
1378
1443
  const runtimeState = getAgentRuntimeState(tmuxSession);
1379
1444
  let state;
@@ -1454,20 +1519,20 @@ Say: "I am the ${name} specialist, ready and waiting for tasks."`;
1454
1519
  } else {
1455
1520
  clearCredentialFileAuth(cwd);
1456
1521
  }
1457
- const agentDir = join4(homedir2(), ".panopticon", "agents", tmuxSession);
1522
+ const agentDir = join5(homedir2(), ".panopticon", "agents", tmuxSession);
1458
1523
  await execAsync(`mkdir -p "${agentDir}"`, { encoding: "utf-8" });
1459
- const promptFile = join4(agentDir, "identity-prompt.md");
1460
- const launcherScript = join4(agentDir, "launcher.sh");
1461
- writeFileSync2(promptFile, identityPrompt);
1524
+ const promptFile = join5(agentDir, "identity-prompt.md");
1525
+ const launcherScript = join5(agentDir, "launcher.sh");
1526
+ writeFileSync3(promptFile, identityPrompt);
1462
1527
  const newSessionId = randomUUID();
1463
- writeFileSync2(launcherScript, `#!/bin/bash
1528
+ writeFileSync3(launcherScript, `#!/bin/bash
1464
1529
  cd "${cwd}"
1465
1530
  prompt=$(cat "${promptFile}")
1466
1531
  exec claude --dangerously-skip-permissions --session-id "${newSessionId}" --model ${model} "$prompt"
1467
1532
  `, { mode: 493 });
1468
1533
  setSessionId(name, newSessionId);
1469
1534
  await execAsync(
1470
- `tmux new-session -d -s "${tmuxSession}"${envFlags} "bash '${launcherScript}'"`,
1535
+ `tmux new-session -d -s "${tmuxSession}" -c "${cwd}"${envFlags} "bash '${launcherScript}'"`,
1471
1536
  { encoding: "utf-8" }
1472
1537
  );
1473
1538
  recordWake(name);
@@ -1528,7 +1593,7 @@ async function wakeSpecialist(name, taskPrompt, options = {}) {
1528
1593
  const sessionId = getSessionId(name);
1529
1594
  const wasAlreadyRunning = await isRunning(name);
1530
1595
  if (wasAlreadyRunning && !options.skipBusyGuard) {
1531
- const { getAgentRuntimeState } = await import("./agents-I6RAEGL5.js");
1596
+ const { getAgentRuntimeState } = await import("./agents-MOMDECON.js");
1532
1597
  const runtimeState = getAgentRuntimeState(tmuxSession);
1533
1598
  if (runtimeState?.state === "active") {
1534
1599
  console.warn(`[specialist] ${name} is busy (working on ${runtimeState.currentIssue}), refusing to interrupt`);
@@ -1550,9 +1615,9 @@ async function wakeSpecialist(name, taskPrompt, options = {}) {
1550
1615
  error: "not_running"
1551
1616
  };
1552
1617
  }
1553
- const cwd = getDevrootPath() || join4(process.env.HOME || "/home/eltmon", "Projects");
1618
+ const cwd = getDevrootPath() || join5(process.env.HOME || "/home/eltmon", "Projects");
1554
1619
  try {
1555
- const { preTrustDirectory } = await import("./workspace-manager-G6TTBPC3.js");
1620
+ const { preTrustDirectory } = await import("./workspace-manager-Z57ROWBQ.js");
1556
1621
  preTrustDirectory(cwd);
1557
1622
  } catch {
1558
1623
  }
@@ -1619,11 +1684,11 @@ async function wakeSpecialist(name, taskPrompt, options = {}) {
1619
1684
  const isLargePrompt = taskPrompt.length > 500 || taskPrompt.includes("\n");
1620
1685
  let messageToSend;
1621
1686
  if (isLargePrompt) {
1622
- if (!existsSync4(TASKS_DIR)) {
1687
+ if (!existsSync5(TASKS_DIR)) {
1623
1688
  mkdirSync3(TASKS_DIR, { recursive: true });
1624
1689
  }
1625
- const taskFile = join4(TASKS_DIR, `${name}-${Date.now()}.md`);
1626
- writeFileSync2(taskFile, taskPrompt, "utf-8");
1690
+ const taskFile = join5(TASKS_DIR, `${name}-${Date.now()}.md`);
1691
+ writeFileSync3(taskFile, taskPrompt, "utf-8");
1627
1692
  messageToSend = `Read and execute the task in: ${taskFile}`;
1628
1693
  } else {
1629
1694
  messageToSend = taskPrompt;
@@ -1647,7 +1712,7 @@ async function wakeSpecialist(name, taskPrompt, options = {}) {
1647
1712
  }
1648
1713
  }
1649
1714
  recordWake(name, sessionId || void 0);
1650
- const { saveAgentRuntimeState } = await import("./agents-I6RAEGL5.js");
1715
+ const { saveAgentRuntimeState } = await import("./agents-MOMDECON.js");
1651
1716
  saveAgentRuntimeState(tmuxSession, {
1652
1717
  state: "active",
1653
1718
  lastActivity: (/* @__PURE__ */ new Date()).toISOString(),
@@ -1705,7 +1770,7 @@ PHASE 1 \u2014 SYNC & BASELINE (before merge):
1705
1770
  If rebase conflicts: abort and report failure.
1706
1771
  5. Run tests on main to establish a baseline. Record BASELINE_PASS and BASELINE_FAIL.
1707
1772
 
1708
- PHASE 2 \u2014 MERGE:
1773
+ PHASE 2 \u2014 MERGE (dry run):
1709
1774
  6. git merge ${mergeBranch} --no-edit
1710
1775
  7. If conflicts: resolve them intelligently, then git add and git commit
1711
1776
  8. If clean merge: the merge commit is auto-created (or fast-forward)
@@ -1715,10 +1780,13 @@ PHASE 3 \u2014 VERIFY:
1715
1780
 
1716
1781
  PHASE 4 \u2014 DECIDE:
1717
1782
  10. Compare results:
1718
- - If MERGE_FAIL > BASELINE_FAIL (NEW test failures): ROLLBACK with git reset --hard ORIG_HEAD
1719
- - If MERGE_FAIL <= BASELINE_FAIL (no new failures): PUSH with git push origin main
1783
+ - If MERGE_FAIL > BASELINE_FAIL (NEW test failures): ROLLBACK with git reset --hard ORIG_HEAD and report FAILED
1784
+ - If MERGE_FAIL <= BASELINE_FAIL (no new failures): Report PASSED (merge is validated)
1720
1785
  - Pre-existing failures on main are NOT a reason to rollback
1721
1786
 
1787
+ CRITICAL: Do NOT push to main. Do NOT run git push origin main.
1788
+ The merge validation stays LOCAL. A human will click Merge in the dashboard to push.
1789
+
1722
1790
  PHASE 5 \u2014 REPORT:
1723
1791
  11. Call the Panopticon API to report results:
1724
1792
  curl -s -X POST ${apiUrl}/api/specialists/done \\
@@ -1726,6 +1794,7 @@ PHASE 5 \u2014 REPORT:
1726
1794
  -d '{"specialist":"merge","issueId":"${task.issueId}","status":"passed|failed","notes":"<summary>"}'
1727
1795
 
1728
1796
  CRITICAL: You MUST call the /api/specialists/done endpoint whether you succeed or fail.
1797
+ CRITICAL: NEVER push to main \u2014 only humans merge. Your job is to VALIDATE the merge, not execute it.
1729
1798
  CRITICAL: NEVER use git push --force.
1730
1799
  CRITICAL: Do NOT delete the feature branch.`;
1731
1800
  break;
@@ -1749,14 +1818,14 @@ CRITICAL: Do NOT delete the feature branch.`;
1749
1818
  if (totalChangedFiles === 0) {
1750
1819
  staleBranch = true;
1751
1820
  console.log(`[specialist] review-agent: stale branch detected for ${task.issueId} \u2014 0 files changed vs main`);
1752
- const { setReviewStatus } = await import("./review-status-J2YJGL3E.js");
1821
+ const { setReviewStatus } = await import("./review-status-E77PZZWG.js");
1753
1822
  setReviewStatus(task.issueId.toUpperCase(), {
1754
1823
  reviewStatus: "passed",
1755
1824
  reviewNotes: "No changes to review \u2014 branch identical to main (already merged or stale)"
1756
1825
  });
1757
1826
  console.log(`[specialist] review-agent: auto-passed ${task.issueId} (stale branch)`);
1758
1827
  const tmuxSession = getTmuxSessionName("review-agent");
1759
- const { saveAgentRuntimeState } = await import("./agents-I6RAEGL5.js");
1828
+ const { saveAgentRuntimeState } = await import("./agents-MOMDECON.js");
1760
1829
  saveAgentRuntimeState(tmuxSession, {
1761
1830
  state: "idle",
1762
1831
  lastActivity: (/* @__PURE__ */ new Date()).toISOString()
@@ -1861,7 +1930,7 @@ curl -s -X POST ${apiUrl}/api/specialists/test-agent/queue -H "Content-Type: app
1861
1930
  const testWorkspace = task.workspace || "unknown";
1862
1931
  const testGitInfo = await resolveWorkspaceGitInfo(task.workspace, task.branch);
1863
1932
  const testIsPolyrepo = testGitInfo.isPolyrepo;
1864
- const { extractTeamPrefix, findProjectByTeam } = await import("./projects-3CRF57ZU.js");
1933
+ const { extractTeamPrefix, findProjectByTeam } = await import("./projects-BPGM6IFB.js");
1865
1934
  const testTeamPrefix = extractTeamPrefix(task.issueId);
1866
1935
  const testProjectConfig = testTeamPrefix ? findProjectByTeam(testTeamPrefix) : null;
1867
1936
  const testConfigs = testProjectConfig?.tests;
@@ -1989,6 +2058,29 @@ Then use send-feedback-to-agent skill to notify issue agent of NEW failures only
1989
2058
 
1990
2059
  **NEVER run test commands without redirecting to a file.** This is not optional.
1991
2060
 
2061
+ ## REQUIRED: Container Smoke Test
2062
+
2063
+ After unit tests pass, verify the Docker workspace frontend is accessible.
2064
+ This is NOT optional \u2014 UI changes that pass unit tests but break in containers must be caught.
2065
+
2066
+ \`\`\`bash
2067
+ # Check if containers are running for this workspace
2068
+ docker ps --filter "name=${featureName}" --format "{{.Names}} {{.Status}}" 2>/dev/null
2069
+ \`\`\`
2070
+
2071
+ If containers are running, test these URLs:
2072
+ - **Frontend**: \`curl -sk https://feature-${featureName}.${testProjectConfig?.workspace?.dns?.domain || "pan.localhost"}/ | head -5\`
2073
+ - **API proxy**: \`curl -sk https://feature-${featureName}.${testProjectConfig?.workspace?.dns?.domain || "pan.localhost"}/api/health\`
2074
+ - **API issues**: \`curl -sk https://feature-${featureName}.${testProjectConfig?.workspace?.dns?.domain || "pan.localhost"}/api/issues | head -100\`
2075
+
2076
+ **Pass criteria:**
2077
+ 1. Frontend returns HTML containing \`<div id="root">\`
2078
+ 2. \`/api/health\` returns JSON with \`"status":"ok"\`
2079
+ 3. \`/api/issues\` returns JSON array (not an error)
2080
+
2081
+ **If ANY of these fail, the test FAILS** \u2014 report via the API with details about which check failed.
2082
+ If containers are NOT running, note it but don't fail (containers may not be configured for this project).
2083
+
1992
2084
  IMPORTANT: Do NOT hand off to merge-agent. Human clicks Merge button when ready.`;
1993
2085
  break;
1994
2086
  }
@@ -1999,8 +2091,23 @@ IMPORTANT: Do NOT hand off to merge-agent. Human clicks Merge button when ready.
1999
2091
  }
2000
2092
  async function wakeSpecialistOrQueue(name, task, options = {}) {
2001
2093
  const { priority = "normal", source = "handoff" } = options;
2094
+ const vbriefItemId = task.context?.vbriefItemId;
2095
+ const workspacePath = task.workspace || task.context?.workspace;
2096
+ if (vbriefItemId && workspacePath) {
2097
+ try {
2098
+ if (!isTaskReady(vbriefItemId, workspacePath)) {
2099
+ return {
2100
+ success: false,
2101
+ queued: false,
2102
+ message: `Task "${vbriefItemId}" has incomplete blocking dependencies \u2014 not ready to schedule`
2103
+ };
2104
+ }
2105
+ } catch (readinessErr) {
2106
+ console.warn(`[specialist] Task readiness check failed for ${vbriefItemId}: ${readinessErr.message}`);
2107
+ }
2108
+ }
2002
2109
  const running = await isRunning(name);
2003
- const { getAgentRuntimeState } = await import("./agents-I6RAEGL5.js");
2110
+ const { getAgentRuntimeState } = await import("./agents-MOMDECON.js");
2004
2111
  const tmuxSession = getTmuxSessionName(name);
2005
2112
  const runtimeState = getAgentRuntimeState(tmuxSession);
2006
2113
  const idle = runtimeState?.state === "idle" || runtimeState?.state === "suspended";
@@ -2031,7 +2138,7 @@ async function wakeSpecialistOrQueue(name, task, options = {}) {
2031
2138
  };
2032
2139
  }
2033
2140
  }
2034
- const { saveAgentRuntimeState } = await import("./agents-I6RAEGL5.js");
2141
+ const { saveAgentRuntimeState } = await import("./agents-MOMDECON.js");
2035
2142
  saveAgentRuntimeState(tmuxSession, {
2036
2143
  state: "active",
2037
2144
  lastActivity: (/* @__PURE__ */ new Date()).toISOString(),
@@ -2115,7 +2222,7 @@ function getNextSpecialistTask(specialistName) {
2115
2222
  }
2116
2223
  async function sendFeedbackToAgent(feedback) {
2117
2224
  const { fromSpecialist, toIssueId, summary, details } = feedback;
2118
- if (!existsSync4(FEEDBACK_DIR)) {
2225
+ if (!existsSync5(FEEDBACK_DIR)) {
2119
2226
  mkdirSync3(FEEDBACK_DIR, { recursive: true });
2120
2227
  }
2121
2228
  const fullFeedback = {
@@ -2131,7 +2238,7 @@ async function sendFeedbackToAgent(feedback) {
2131
2238
  }
2132
2239
  const agentSession = `agent-${toIssueId.toLowerCase()}`;
2133
2240
  const feedbackMessage = formatFeedbackForAgent(fullFeedback);
2134
- const { writeFeedbackFile } = await import("./feedback-writer-T2WCT6EZ.js");
2241
+ const { writeFeedbackFile } = await import("./feedback-writer-IPPIUPDX.js");
2135
2242
  const specialistMap = {
2136
2243
  "review-agent": "review-agent",
2137
2244
  "test-agent": "test-agent",
@@ -2151,7 +2258,7 @@ async function sendFeedbackToAgent(feedback) {
2151
2258
  return false;
2152
2259
  }
2153
2260
  try {
2154
- const { messageAgent } = await import("./agents-I6RAEGL5.js");
2261
+ const { messageAgent } = await import("./agents-MOMDECON.js");
2155
2262
  const msg = `SPECIALIST FEEDBACK: ${fromSpecialist} reported ${feedback.feedbackType.toUpperCase()} for ${toIssueId}.
2156
2263
  Read and address: ${fileResult.relativePath}`;
2157
2264
  await messageAgent(agentSession, msg);
@@ -2210,11 +2317,11 @@ ${details}
2210
2317
  return message;
2211
2318
  }
2212
2319
  function getPendingFeedback(issueId) {
2213
- if (!existsSync4(FEEDBACK_LOG)) {
2320
+ if (!existsSync5(FEEDBACK_LOG)) {
2214
2321
  return [];
2215
2322
  }
2216
2323
  try {
2217
- const content = readFileSync4(FEEDBACK_LOG, "utf-8");
2324
+ const content = readFileSync5(FEEDBACK_LOG, "utf-8");
2218
2325
  const lines = content.trim().split("\n").filter((l) => l.length > 0);
2219
2326
  const allFeedback = lines.map((line) => JSON.parse(line));
2220
2327
  return allFeedback.filter((f) => f.toIssueId.toLowerCase() === issueId.toLowerCase());
@@ -2233,11 +2340,11 @@ function getFeedbackStats() {
2233
2340
  byType: {},
2234
2341
  total: 0
2235
2342
  };
2236
- if (!existsSync4(FEEDBACK_LOG)) {
2343
+ if (!existsSync5(FEEDBACK_LOG)) {
2237
2344
  return stats;
2238
2345
  }
2239
2346
  try {
2240
- const content = readFileSync4(FEEDBACK_LOG, "utf-8");
2347
+ const content = readFileSync5(FEEDBACK_LOG, "utf-8");
2241
2348
  const lines = content.trim().split("\n").filter((l) => l.length > 0);
2242
2349
  for (const line of lines) {
2243
2350
  const feedback = JSON.parse(line);
@@ -2264,11 +2371,12 @@ var init_specialists = __esm({
2264
2371
  init_providers();
2265
2372
  init_tmux();
2266
2373
  init_pipeline_notifier();
2374
+ init_task_readiness();
2267
2375
  init_hooks();
2268
2376
  execAsync = promisify(exec);
2269
- SPECIALISTS_DIR = join4(PANOPTICON_HOME, "specialists");
2270
- REGISTRY_FILE = join4(SPECIALISTS_DIR, "registry.json");
2271
- TASKS_DIR = join4(SPECIALISTS_DIR, "tasks");
2377
+ SPECIALISTS_DIR = join5(PANOPTICON_HOME, "specialists");
2378
+ REGISTRY_FILE = join5(SPECIALISTS_DIR, "registry.json");
2379
+ TASKS_DIR = join5(SPECIALISTS_DIR, "tasks");
2272
2380
  DEFAULT_SPECIALISTS = [
2273
2381
  {
2274
2382
  name: "merge-agent",
@@ -2293,8 +2401,8 @@ var init_specialists = __esm({
2293
2401
  }
2294
2402
  ];
2295
2403
  gracePeriodStates = /* @__PURE__ */ new Map();
2296
- FEEDBACK_DIR = join4(PANOPTICON_HOME, "specialists", "feedback");
2297
- FEEDBACK_LOG = join4(FEEDBACK_DIR, "feedback.jsonl");
2404
+ FEEDBACK_DIR = join5(PANOPTICON_HOME, "specialists", "feedback");
2405
+ FEEDBACK_LOG = join5(FEEDBACK_DIR, "feedback.jsonl");
2298
2406
  }
2299
2407
  });
2300
2408
 
@@ -2318,13 +2426,13 @@ __export(specialist_logs_exports, {
2318
2426
  listRunLogs: () => listRunLogs,
2319
2427
  parseLogMetadata: () => parseLogMetadata
2320
2428
  });
2321
- import { existsSync as existsSync5, mkdirSync as mkdirSync4, writeFileSync as writeFileSync3, appendFileSync as appendFileSync4, readFileSync as readFileSync5, readdirSync as readdirSync4, statSync as statSync2, unlinkSync as unlinkSync2 } from "fs";
2322
- import { join as join5, basename as basename3 } from "path";
2429
+ import { existsSync as existsSync6, mkdirSync as mkdirSync4, writeFileSync as writeFileSync4, appendFileSync as appendFileSync4, readFileSync as readFileSync6, readdirSync as readdirSync4, statSync as statSync2, unlinkSync as unlinkSync2 } from "fs";
2430
+ import { join as join6, basename as basename3 } from "path";
2323
2431
  function getSpecialistsDir() {
2324
- return join5(getPanopticonHome(), "specialists");
2432
+ return join6(getPanopticonHome(), "specialists");
2325
2433
  }
2326
2434
  function getRunsDirectory(projectKey, specialistType) {
2327
- return join5(getSpecialistsDir(), projectKey, specialistType, "runs");
2435
+ return join6(getSpecialistsDir(), projectKey, specialistType, "runs");
2328
2436
  }
2329
2437
  function generateRunId(issueId) {
2330
2438
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").substring(0, 19);
@@ -2332,11 +2440,11 @@ function generateRunId(issueId) {
2332
2440
  }
2333
2441
  function getRunLogPath(projectKey, specialistType, runId) {
2334
2442
  const runsDir = getRunsDirectory(projectKey, specialistType);
2335
- return join5(runsDir, `${runId}.log`);
2443
+ return join6(runsDir, `${runId}.log`);
2336
2444
  }
2337
2445
  function ensureRunsDirectory(projectKey, specialistType) {
2338
2446
  const runsDir = getRunsDirectory(projectKey, specialistType);
2339
- if (!existsSync5(runsDir)) {
2447
+ if (!existsSync6(runsDir)) {
2340
2448
  mkdirSync4(runsDir, { recursive: true });
2341
2449
  }
2342
2450
  }
@@ -2356,22 +2464,22 @@ ${contextSeed ? contextSeed : "[No context digest available]"}
2356
2464
 
2357
2465
  ## Session Transcript
2358
2466
  `;
2359
- writeFileSync3(filePath, header, "utf-8");
2467
+ writeFileSync4(filePath, header, "utf-8");
2360
2468
  return { runId, filePath };
2361
2469
  }
2362
2470
  function appendToRunLog(projectKey, specialistType, runId, content) {
2363
2471
  const filePath = getRunLogPath(projectKey, specialistType, runId);
2364
- if (!existsSync5(filePath)) {
2472
+ if (!existsSync6(filePath)) {
2365
2473
  throw new Error(`Run log not found: ${filePath}`);
2366
2474
  }
2367
2475
  appendFileSync4(filePath, content, "utf-8");
2368
2476
  }
2369
2477
  function finalizeRunLog(projectKey, specialistType, runId, result) {
2370
2478
  const filePath = getRunLogPath(projectKey, specialistType, runId);
2371
- if (!existsSync5(filePath)) {
2479
+ if (!existsSync6(filePath)) {
2372
2480
  throw new Error(`Run log not found: ${filePath}`);
2373
2481
  }
2374
- const content = readFileSync5(filePath, "utf-8");
2482
+ const content = readFileSync6(filePath, "utf-8");
2375
2483
  const startMatch = content.match(/^Started: (.+)$/m);
2376
2484
  const startedAt = startMatch ? new Date(startMatch[1]) : /* @__PURE__ */ new Date();
2377
2485
  const finishedAt = /* @__PURE__ */ new Date();
@@ -2392,11 +2500,11 @@ Finished: ${finishedAt.toISOString()}
2392
2500
  }
2393
2501
  function getRunLog(projectKey, specialistType, runId) {
2394
2502
  const filePath = getRunLogPath(projectKey, specialistType, runId);
2395
- if (!existsSync5(filePath)) {
2503
+ if (!existsSync6(filePath)) {
2396
2504
  return null;
2397
2505
  }
2398
2506
  try {
2399
- return readFileSync5(filePath, "utf-8");
2507
+ return readFileSync6(filePath, "utf-8");
2400
2508
  } catch (error) {
2401
2509
  console.error(`Failed to read run log ${runId}:`, error);
2402
2510
  return null;
@@ -2431,15 +2539,15 @@ function parseLogMetadata(logContent) {
2431
2539
  }
2432
2540
  function listRunLogs(projectKey, specialistType, options = {}) {
2433
2541
  const runsDir = getRunsDirectory(projectKey, specialistType);
2434
- if (!existsSync5(runsDir)) {
2542
+ if (!existsSync6(runsDir)) {
2435
2543
  return [];
2436
2544
  }
2437
2545
  try {
2438
2546
  const files = readdirSync4(runsDir).filter((f) => f.endsWith(".log")).map((f) => {
2439
- const filePath = join5(runsDir, f);
2547
+ const filePath = join6(runsDir, f);
2440
2548
  const stats = statSync2(filePath);
2441
2549
  const runId = basename3(f, ".log");
2442
- const content = readFileSync5(filePath, "utf-8");
2550
+ const content = readFileSync6(filePath, "utf-8");
2443
2551
  const metadata = parseLogMetadata(content);
2444
2552
  return {
2445
2553
  runId,
@@ -2512,7 +2620,7 @@ function isRunLogActive(projectKey, specialistType, runId) {
2512
2620
  }
2513
2621
  function getRunLogSize(projectKey, specialistType, runId) {
2514
2622
  const filePath = getRunLogPath(projectKey, specialistType, runId);
2515
- if (!existsSync5(filePath)) {
2623
+ if (!existsSync6(filePath)) {
2516
2624
  return null;
2517
2625
  }
2518
2626
  try {
@@ -2569,6 +2677,10 @@ var init_specialist_logs = __esm({
2569
2677
  });
2570
2678
 
2571
2679
  export {
2680
+ readWorkspacePlan,
2681
+ updateItemStatus,
2682
+ updateSubItemStatus,
2683
+ init_io,
2572
2684
  readTodayCosts,
2573
2685
  readIssueCosts,
2574
2686
  summarizeCosts,
@@ -2660,4 +2772,4 @@ export {
2660
2772
  getFeedbackStats,
2661
2773
  init_specialists
2662
2774
  };
2663
- //# sourceMappingURL=chunk-M6ZVVKZ3.js.map
2775
+ //# sourceMappingURL=chunk-4OQ4SXQZ.js.map