panopticon-cli 0.4.33 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/README.md +96 -210
  2. package/dist/{agents-VLK4BMVA.js → agents-5OPQKM5K.js} +6 -5
  3. package/dist/{chunk-OMNXYPXC.js → chunk-2V4NF7J2.js} +14 -1
  4. package/dist/chunk-2V4NF7J2.js.map +1 -0
  5. package/dist/{chunk-XKT5MHPT.js → chunk-4YSYJ4HM.js} +2 -2
  6. package/dist/{chunk-XFR2DLMR.js → chunk-76F6DSVS.js} +49 -10
  7. package/dist/chunk-76F6DSVS.js.map +1 -0
  8. package/dist/{chunk-PI7Y3PSN.js → chunk-F5555J3A.js} +42 -6
  9. package/dist/chunk-F5555J3A.js.map +1 -0
  10. package/dist/{chunk-KJ2TRXNK.js → chunk-FTCPTHIJ.js} +47 -420
  11. package/dist/chunk-FTCPTHIJ.js.map +1 -0
  12. package/dist/chunk-HJSM6E6U.js +1038 -0
  13. package/dist/chunk-HJSM6E6U.js.map +1 -0
  14. package/dist/{chunk-RBUO57TC.js → chunk-NLQRED36.js} +3 -3
  15. package/dist/chunk-NLQRED36.js.map +1 -0
  16. package/dist/{chunk-ASY7T35E.js → chunk-OWHXCGVO.js} +245 -90
  17. package/dist/chunk-OWHXCGVO.js.map +1 -0
  18. package/dist/{chunk-BKCWRMUX.js → chunk-VHKSS7QX.js} +106 -11
  19. package/dist/chunk-VHKSS7QX.js.map +1 -0
  20. package/dist/{chunk-GFP3PIPB.js → chunk-YGJ54GW2.js} +1 -1
  21. package/dist/chunk-YGJ54GW2.js.map +1 -0
  22. package/dist/cli/index.js +1521 -935
  23. package/dist/cli/index.js.map +1 -1
  24. package/dist/dashboard/prompts/work-agent.md +2 -0
  25. package/dist/dashboard/public/assets/index-Ce6q21Fm.js +743 -0
  26. package/dist/dashboard/public/assets/{index-UjZq6ykz.css → index-NzpI0ItZ.css} +1 -1
  27. package/dist/dashboard/public/index.html +2 -2
  28. package/dist/dashboard/server.js +4274 -2320
  29. package/dist/{feedback-writer-LVZ5TFYZ.js → feedback-writer-VRMMWWTW.js} +2 -2
  30. package/dist/git-utils-I2UDKNZH.js +131 -0
  31. package/dist/git-utils-I2UDKNZH.js.map +1 -0
  32. package/dist/index.d.ts +12 -1
  33. package/dist/index.js +5 -3
  34. package/dist/index.js.map +1 -1
  35. package/dist/{projects-JEIVIYC6.js → projects-CFX3RTDL.js} +4 -2
  36. package/dist/{remote-workspace-AHVHQEES.js → remote-workspace-7FPGF2RM.js} +2 -2
  37. package/dist/{review-status-EPFG4XM7.js → review-status-TDPSOU5J.js} +2 -2
  38. package/dist/{specialist-context-T3NBMCIE.js → specialist-context-WGUUYDWY.js} +5 -5
  39. package/dist/{specialist-logs-CVKD3YJ3.js → specialist-logs-XJB5TCKJ.js} +5 -5
  40. package/dist/{specialists-TKAP6T6Z.js → specialists-5LBRHYFA.js} +5 -5
  41. package/dist/{traefik-QX4ZV4YG.js → traefik-WFMQX2LY.js} +3 -3
  42. package/dist/{workspace-manager-KLHUCIZV.js → workspace-manager-E434Z45T.js} +2 -2
  43. package/package.json +1 -1
  44. package/scripts/record-cost-event.js +5 -5
  45. package/scripts/stop-hook +7 -0
  46. package/scripts/work-agent-stop-hook +137 -0
  47. package/skills/myn-standards/SKILL.md +351 -0
  48. package/skills/pan-new-project/SKILL.md +304 -0
  49. package/skills/write-spec/SKILL.md +138 -0
  50. package/dist/chunk-7XNJJBH6.js +0 -538
  51. package/dist/chunk-7XNJJBH6.js.map +0 -1
  52. package/dist/chunk-ASY7T35E.js.map +0 -1
  53. package/dist/chunk-BKCWRMUX.js.map +0 -1
  54. package/dist/chunk-GFP3PIPB.js.map +0 -1
  55. package/dist/chunk-KJ2TRXNK.js.map +0 -1
  56. package/dist/chunk-OMNXYPXC.js.map +0 -1
  57. package/dist/chunk-PI7Y3PSN.js.map +0 -1
  58. package/dist/chunk-RBUO57TC.js.map +0 -1
  59. package/dist/chunk-XFR2DLMR.js.map +0 -1
  60. package/dist/dashboard/public/assets/index-kAJqtLDO.js +0 -708
  61. /package/dist/{agents-VLK4BMVA.js.map → agents-5OPQKM5K.js.map} +0 -0
  62. /package/dist/{chunk-XKT5MHPT.js.map → chunk-4YSYJ4HM.js.map} +0 -0
  63. /package/dist/{feedback-writer-LVZ5TFYZ.js.map → feedback-writer-VRMMWWTW.js.map} +0 -0
  64. /package/dist/{projects-JEIVIYC6.js.map → projects-CFX3RTDL.js.map} +0 -0
  65. /package/dist/{remote-workspace-AHVHQEES.js.map → remote-workspace-7FPGF2RM.js.map} +0 -0
  66. /package/dist/{review-status-EPFG4XM7.js.map → review-status-TDPSOU5J.js.map} +0 -0
  67. /package/dist/{specialist-context-T3NBMCIE.js.map → specialist-context-WGUUYDWY.js.map} +0 -0
  68. /package/dist/{specialist-logs-CVKD3YJ3.js.map → specialist-logs-XJB5TCKJ.js.map} +0 -0
  69. /package/dist/{specialists-TKAP6T6Z.js.map → specialists-5LBRHYFA.js.map} +0 -0
  70. /package/dist/{traefik-QX4ZV4YG.js.map → traefik-WFMQX2LY.js.map} +0 -0
  71. /package/dist/{workspace-manager-KLHUCIZV.js.map → workspace-manager-E434Z45T.js.map} +0 -0
package/dist/cli/index.js CHANGED
@@ -1,18 +1,43 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ checkSpecialistQueue,
4
+ clearSessionId,
5
+ completeSpecialistTask,
6
+ getAllSpecialistStatus,
7
+ getEnabledSpecialists,
8
+ getNextSpecialistTask,
9
+ getProjectDirs,
10
+ getSessionFiles,
11
+ getSessionId,
12
+ getSpecialistMetadata,
13
+ getSpecialistStatus,
14
+ getTmuxSessionName,
15
+ init_jsonl_parser,
16
+ init_specialists,
17
+ initializeEnabledSpecialists,
18
+ isRunning,
19
+ parseClaudeSession,
20
+ recordWake,
21
+ setSessionId,
22
+ wakeSpecialist,
23
+ wakeSpecialistOrQueue,
24
+ wakeSpecialistWithTask
25
+ } from "../chunk-OWHXCGVO.js";
2
26
  import {
3
27
  cleanupTemplateFiles,
4
28
  ensureProjectCerts,
5
29
  generatePanopticonTraefikConfig,
6
30
  generateTlsConfig
7
- } from "../chunk-RBUO57TC.js";
31
+ } from "../chunk-NLQRED36.js";
8
32
  import {
9
33
  applyProjectTemplateOverlay,
10
34
  createWorkspace,
11
35
  init_skills_merge,
12
36
  init_workspace_manager,
13
37
  mergeSkillsIntoWorkspace,
14
- removeWorkspace
15
- } from "../chunk-PI7Y3PSN.js";
38
+ removeWorkspace,
39
+ stopWorkspaceDocker
40
+ } from "../chunk-F5555J3A.js";
16
41
  import "../chunk-7SN4L4PH.js";
17
42
  import {
18
43
  init_remote_agents,
@@ -41,7 +66,24 @@ import {
41
66
  saveSessionId,
42
67
  spawnAgent,
43
68
  stopAgent
44
- } from "../chunk-BKCWRMUX.js";
69
+ } from "../chunk-VHKSS7QX.js";
70
+ import {
71
+ checkHook,
72
+ clearHook,
73
+ createSession,
74
+ generateFixedPointPrompt,
75
+ getModelId,
76
+ init_hooks,
77
+ init_tmux,
78
+ init_work_type_router,
79
+ killSession,
80
+ popFromHook,
81
+ pushToHook,
82
+ sendKeys,
83
+ sendKeysAsync,
84
+ sendMail,
85
+ sessionExists
86
+ } from "../chunk-FTCPTHIJ.js";
45
87
  import {
46
88
  createShadowState,
47
89
  getPendingSyncCount,
@@ -69,48 +111,7 @@ import {
69
111
  init_review_status,
70
112
  loadReviewStatuses,
71
113
  saveReviewStatuses
72
- } from "../chunk-GFP3PIPB.js";
73
- import {
74
- checkSpecialistQueue,
75
- clearSessionId,
76
- completeSpecialistTask,
77
- getAllSpecialistStatus,
78
- getEnabledSpecialists,
79
- getNextSpecialistTask,
80
- getProjectDirs,
81
- getSessionFiles,
82
- getSessionId,
83
- getSpecialistMetadata,
84
- getSpecialistStatus,
85
- getTmuxSessionName,
86
- init_jsonl_parser,
87
- init_specialists,
88
- initializeEnabledSpecialists,
89
- isRunning,
90
- parseClaudeSession,
91
- recordWake,
92
- setSessionId,
93
- wakeSpecialist,
94
- wakeSpecialistOrQueue,
95
- wakeSpecialistWithTask
96
- } from "../chunk-ASY7T35E.js";
97
- import {
98
- checkHook,
99
- clearHook,
100
- createSession,
101
- generateFixedPointPrompt,
102
- getModelId,
103
- init_hooks,
104
- init_tmux,
105
- init_work_type_router,
106
- killSession,
107
- popFromHook,
108
- pushToHook,
109
- sendKeys,
110
- sendKeysAsync,
111
- sendMail,
112
- sessionExists
113
- } from "../chunk-KJ2TRXNK.js";
114
+ } from "../chunk-YGJ54GW2.js";
114
115
  import "../chunk-JQBV3Q2W.js";
115
116
  import {
116
117
  addAlias,
@@ -128,19 +129,19 @@ import {
128
129
  restoreBackup,
129
130
  syncHooks,
130
131
  syncStatusline
131
- } from "../chunk-XKT5MHPT.js";
132
+ } from "../chunk-4YSYJ4HM.js";
132
133
  import "../chunk-AQXETQHW.js";
133
134
  import {
134
135
  createTracker,
135
136
  createTrackerFromConfig,
136
137
  init_factory
137
- } from "../chunk-XFR2DLMR.js";
138
+ } from "../chunk-76F6DSVS.js";
138
139
  import {
139
140
  init_config_yaml,
140
141
  init_settings,
141
142
  loadConfig as loadConfig2,
142
143
  loadSettings
143
- } from "../chunk-7XNJJBH6.js";
144
+ } from "../chunk-HJSM6E6U.js";
144
145
  import {
145
146
  getDashboardApiUrl,
146
147
  getDefaultConfig,
@@ -162,7 +163,7 @@ import {
162
163
  registerProject,
163
164
  resolveProjectFromIssue,
164
165
  unregisterProject
165
- } from "../chunk-OMNXYPXC.js";
166
+ } from "../chunk-2V4NF7J2.js";
166
167
  import {
167
168
  NotImplementedError,
168
169
  init_interface
@@ -215,8 +216,8 @@ import {
215
216
 
216
217
  // src/cli/index.ts
217
218
  init_esm_shims();
218
- import { readFileSync as readFileSync46, existsSync as existsSync53 } from "fs";
219
- import { join as join53 } from "path";
219
+ import { readFileSync as readFileSync48, existsSync as existsSync55 } from "fs";
220
+ import { join as join55 } from "path";
220
221
  import { homedir as homedir26 } from "os";
221
222
  import { Command } from "commander";
222
223
  import chalk61 from "chalk";
@@ -691,6 +692,23 @@ async function syncCommand(options) {
691
692
  mkcertSpinner.warn("Failed to install mkcert - run: https://github.com/FiloSottile/mkcert/releases");
692
693
  }
693
694
  }
695
+ if (!checkCommand("ox")) {
696
+ const oxSpinner = ora2("Installing SageOx CLI (ox)...").start();
697
+ try {
698
+ const binDir = join3(homedir2(), ".local", "bin");
699
+ mkdirSync2(binDir, { recursive: true });
700
+ const oxPath = join3(binDir, "ox");
701
+ const arch = process.arch === "x64" ? "amd64" : process.arch;
702
+ const platform2 = process.platform === "darwin" ? "darwin" : "linux";
703
+ execSync(`curl -sL "https://github.com/eltmon/ox/releases/download/latest/ox-${platform2}-${arch}" -o "${oxPath}" && chmod +x "${oxPath}"`, {
704
+ stdio: "pipe",
705
+ timeout: 6e4
706
+ });
707
+ oxSpinner.succeed("SageOx CLI installed");
708
+ } catch {
709
+ oxSpinner.warn("Failed to install SageOx CLI - see: https://github.com/eltmon/ox/releases");
710
+ }
711
+ }
694
712
  const projects = listProjects();
695
713
  if (projects.length > 0 && existsSync3(BUNDLED_GIT_HOOKS_DIR)) {
696
714
  const gitHooksSpinner = ora2("Installing git hooks in registered projects...").start();
@@ -950,7 +968,7 @@ function resolveShadowMode(options = {}) {
950
968
  trackerType
951
969
  };
952
970
  }
953
- const config2 = loadConfig2();
971
+ const { config: config2 } = loadConfig2();
954
972
  let enabled = config2.shadow.enabled;
955
973
  let source = config2.shadow.enabled ? "project" : "default";
956
974
  if (process.env.SHADOW_MODE !== void 0) {
@@ -1539,7 +1557,7 @@ async function handleRemoteWorkspace(issueId, options, spinner) {
1539
1557
  if (!remoteMetadata) {
1540
1558
  spinner.text = "Remote workspace not found, creating...";
1541
1559
  try {
1542
- const { createRemoteWorkspace: createRemoteWorkspace2 } = await import("../remote-workspace-AHVHQEES.js");
1560
+ const { createRemoteWorkspace: createRemoteWorkspace2 } = await import("../remote-workspace-7FPGF2RM.js");
1543
1561
  remoteMetadata = await createRemoteWorkspace2(issueId, { spinner });
1544
1562
  } catch (error) {
1545
1563
  spinner.fail(`Failed to create remote workspace: ${error.message}`);
@@ -1752,7 +1770,7 @@ async function issueCommand(id, options) {
1752
1770
  issueId: id,
1753
1771
  workspace,
1754
1772
  model: options.model,
1755
- phase: options.phase,
1773
+ phase: options.phase || "implementation",
1756
1774
  prompt
1757
1775
  });
1758
1776
  spinner.succeed(`Agent spawned: ${agent.id}`);
@@ -1798,8 +1816,25 @@ async function issueCommand(id, options) {
1798
1816
  init_esm_shims();
1799
1817
  init_agents();
1800
1818
  import chalk7 from "chalk";
1819
+ import { existsSync as existsSync9, readFileSync as readFileSync7, statSync as statSync4, readdirSync as readdirSync9 } from "fs";
1820
+ import { join as join9, basename as basename2 } from "path";
1801
1821
  init_tldr_daemon();
1822
+ function readContextPercent(agentId) {
1823
+ const ctxFile = join9(getAgentDir(agentId), "context-pct");
1824
+ try {
1825
+ if (existsSync9(ctxFile)) {
1826
+ const val = parseInt(readFileSync7(ctxFile, "utf8").trim(), 10);
1827
+ return isNaN(val) ? null : val;
1828
+ }
1829
+ } catch {
1830
+ }
1831
+ return null;
1832
+ }
1802
1833
  async function statusCommand(options) {
1834
+ if (options.tldr) {
1835
+ await tldrIndexStatusCommand();
1836
+ return;
1837
+ }
1803
1838
  const agents = listRunningAgents().filter(
1804
1839
  (agent) => agent.id && agent.issueId && agent.workspace
1805
1840
  );
@@ -1811,7 +1846,8 @@ async function statusCommand(options) {
1811
1846
  ...agent,
1812
1847
  shadowMode: shadowed,
1813
1848
  shadowStatus: shadowState?.shadowStatus,
1814
- trackerStatus: shadowState?.trackerStatus
1849
+ trackerStatus: shadowState?.trackerStatus,
1850
+ ...options.context ? { contextPercent: readContextPercent(agent.id) } : {}
1815
1851
  };
1816
1852
  });
1817
1853
  console.log(JSON.stringify(agentsWithShadow, null, 2));
@@ -1837,6 +1873,11 @@ async function statusCommand(options) {
1837
1873
  const statusStr = `${shadowState.shadowStatus}${shadowState.trackerStatus !== shadowState.shadowStatus ? ` (tracker: ${shadowState.trackerStatus})` : ""}`;
1838
1874
  console.log(` Shadow: ${chalk7.cyan("\u{1F47B}")} ${statusStr}`);
1839
1875
  }
1876
+ if (options.context) {
1877
+ const ctxPct = readContextPercent(agent.id);
1878
+ const ctxStr = ctxPct !== null ? `${ctxPct}%` : "--";
1879
+ console.log(` Context: ${ctxStr}`);
1880
+ }
1840
1881
  console.log(` Runtime: ${agent.runtime} (${agent.model})`);
1841
1882
  console.log(` Duration: ${duration} min`);
1842
1883
  console.log(` Workspace: ${chalk7.dim(agent.workspace)}`);
@@ -1857,6 +1898,122 @@ async function statusCommand(options) {
1857
1898
  console.log("");
1858
1899
  }
1859
1900
  }
1901
+ function readTldrIndexData(workspacePath) {
1902
+ const tldrPath = join9(workspacePath, ".tldr");
1903
+ if (!existsSync9(tldrPath)) {
1904
+ return { fileCount: null, edgeCount: null, ageMs: null };
1905
+ }
1906
+ let fileCount = null;
1907
+ let edgeCount = null;
1908
+ let ageMs = null;
1909
+ const cgPath = join9(tldrPath, "cache", "call_graph.json");
1910
+ if (existsSync9(cgPath)) {
1911
+ try {
1912
+ const cg = JSON.parse(readFileSync7(cgPath, "utf-8"));
1913
+ if (Array.isArray(cg.edges)) {
1914
+ edgeCount = cg.edges.length;
1915
+ const files = /* @__PURE__ */ new Set();
1916
+ for (const e of cg.edges) {
1917
+ if (e.from_file) files.add(e.from_file);
1918
+ if (e.to_file) files.add(e.to_file);
1919
+ }
1920
+ fileCount = files.size;
1921
+ }
1922
+ } catch {
1923
+ }
1924
+ }
1925
+ const langPath = join9(tldrPath, "languages.json");
1926
+ if (existsSync9(langPath)) {
1927
+ try {
1928
+ const langData = JSON.parse(readFileSync7(langPath, "utf-8"));
1929
+ if (langData.timestamp) {
1930
+ ageMs = Date.now() - langData.timestamp * 1e3;
1931
+ }
1932
+ } catch {
1933
+ }
1934
+ }
1935
+ if (ageMs === null) {
1936
+ try {
1937
+ const stats = statSync4(tldrPath);
1938
+ ageMs = Date.now() - stats.mtimeMs;
1939
+ } catch {
1940
+ }
1941
+ }
1942
+ return { fileCount, edgeCount, ageMs };
1943
+ }
1944
+ function formatTldrAge(ageMs) {
1945
+ if (ageMs === null) return "unknown";
1946
+ const ageMin = Math.floor(ageMs / 6e4);
1947
+ if (ageMin < 60) return `${ageMin}m`;
1948
+ const ageHours = Math.floor(ageMin / 60);
1949
+ if (ageHours < 24) return `${ageHours}h`;
1950
+ return `${Math.floor(ageHours / 24)}d`;
1951
+ }
1952
+ function formatTldrRow(label, entry) {
1953
+ const files = entry.fileCount !== null ? entry.fileCount.toLocaleString() : "N/A";
1954
+ const edges = entry.edgeCount !== null ? entry.edgeCount.toLocaleString() : "N/A";
1955
+ const age = formatTldrAge(entry.ageMs);
1956
+ const daemonStr = entry.running ? chalk7.green("running \u2713") : chalk7.dim("stopped \u25CB");
1957
+ const notIndexed = entry.fileCount === null ? chalk7.dim(" (not indexed)") : "";
1958
+ return ` ${chalk7.bold(label)}${notIndexed} Files: ${files} Edges: ${edges} Age: ${age} Daemon: ${daemonStr}`;
1959
+ }
1960
+ async function tldrIndexStatusCommand(projectRoot = process.cwd()) {
1961
+ const projectName = basename2(projectRoot);
1962
+ const mainEntries = [];
1963
+ const workspaceEntries = [];
1964
+ const mainVenvPath = join9(projectRoot, ".venv");
1965
+ if (existsSync9(mainVenvPath)) {
1966
+ const service = getTldrDaemonService(projectRoot, mainVenvPath);
1967
+ const status = await service.getStatus();
1968
+ const { fileCount, edgeCount, ageMs } = readTldrIndexData(projectRoot);
1969
+ mainEntries.push({ label: `Main (${projectName})`, running: status.running, fileCount, edgeCount, ageMs });
1970
+ }
1971
+ const workspacesDir = join9(projectRoot, "workspaces");
1972
+ if (existsSync9(workspacesDir)) {
1973
+ const dirs = readdirSync9(workspacesDir, { withFileTypes: true }).filter((d) => d.isDirectory() && d.name.startsWith("feature-"));
1974
+ for (const ws of dirs) {
1975
+ const wsPath = join9(workspacesDir, ws.name);
1976
+ const wsVenvPath = join9(wsPath, ".venv");
1977
+ if (existsSync9(wsVenvPath)) {
1978
+ const service = getTldrDaemonService(wsPath, wsVenvPath);
1979
+ const status = await service.getStatus();
1980
+ const { fileCount, edgeCount, ageMs } = readTldrIndexData(wsPath);
1981
+ workspaceEntries.push({ label: ws.name, running: status.running, fileCount, edgeCount, ageMs });
1982
+ }
1983
+ }
1984
+ }
1985
+ console.log(chalk7.bold("\nTLDR Index Health"));
1986
+ console.log("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
1987
+ if (mainEntries.length === 0 && workspaceEntries.length === 0) {
1988
+ console.log(chalk7.dim("\nNo TLDR indexes found (no .venv directories)"));
1989
+ console.log(chalk7.dim("Run `pan setup` to configure TLDR"));
1990
+ return;
1991
+ }
1992
+ for (const entry of mainEntries) {
1993
+ console.log(formatTldrRow(entry.label, entry));
1994
+ }
1995
+ if (workspaceEntries.length > 0) {
1996
+ console.log("");
1997
+ console.log(chalk7.bold("Workspaces"));
1998
+ for (const entry of workspaceEntries) {
1999
+ console.log(formatTldrRow(entry.label, entry));
2000
+ }
2001
+ }
2002
+ const allEntries = [...mainEntries, ...workspaceEntries];
2003
+ const ONE_HOUR = 60 * 60 * 1e3;
2004
+ const anyMissing = allEntries.some((e) => e.fileCount === null);
2005
+ const anyNotRunning = allEntries.some((e) => !e.running);
2006
+ const anyStale = allEntries.some((e) => e.ageMs === null || e.ageMs >= ONE_HOUR);
2007
+ console.log("");
2008
+ if (anyMissing || anyNotRunning) {
2009
+ console.log(`Health: ${chalk7.red("\u2717 TLDR not fully configured")}`);
2010
+ } else if (anyStale) {
2011
+ console.log(`Health: ${chalk7.yellow("\u26A0 Some indexes stale (>1h)")}`);
2012
+ } else {
2013
+ console.log(`Health: ${chalk7.green("\u2713 All indexes fresh")}`);
2014
+ }
2015
+ console.log("");
2016
+ }
1860
2017
 
1861
2018
  // src/cli/commands/work/tell.ts
1862
2019
  init_esm_shims();
@@ -1925,8 +2082,8 @@ init_esm_shims();
1925
2082
  init_agents();
1926
2083
  init_paths();
1927
2084
  import chalk10 from "chalk";
1928
- import { existsSync as existsSync9, readFileSync as readFileSync7 } from "fs";
1929
- import { join as join9 } from "path";
2085
+ import { existsSync as existsSync10, readFileSync as readFileSync8 } from "fs";
2086
+ import { join as join10 } from "path";
1930
2087
  async function pendingCommand() {
1931
2088
  const agents = listRunningAgents().filter((a) => !a.tmuxActive && a.status !== "error");
1932
2089
  if (agents.length === 0) {
@@ -1939,9 +2096,9 @@ async function pendingCommand() {
1939
2096
  console.log(`${chalk10.cyan(agent.issueId)}`);
1940
2097
  console.log(` Agent: ${agent.id}`);
1941
2098
  console.log(` Workspace: ${chalk10.dim(agent.workspace)}`);
1942
- const completionFile = join9(AGENTS_DIR, agent.id, "completion.md");
1943
- if (existsSync9(completionFile)) {
1944
- const content = readFileSync7(completionFile, "utf8");
2099
+ const completionFile = join10(AGENTS_DIR, agent.id, "completion.md");
2100
+ if (existsSync10(completionFile)) {
2101
+ const content = readFileSync8(completionFile, "utf8");
1945
2102
  const firstLine = content.split("\n").find((l) => l.trim() && !l.startsWith("#"));
1946
2103
  if (firstLine) {
1947
2104
  console.log(` Summary: ${chalk10.dim(firstLine.trim())}`);
@@ -1958,14 +2115,14 @@ init_agents();
1958
2115
  init_paths();
1959
2116
  import chalk11 from "chalk";
1960
2117
  import ora5 from "ora";
1961
- import { existsSync as existsSync10, writeFileSync as writeFileSync3, readFileSync as readFileSync8 } from "fs";
1962
- import { join as join10 } from "path";
2118
+ import { existsSync as existsSync11, writeFileSync as writeFileSync3, readFileSync as readFileSync9 } from "fs";
2119
+ import { join as join11 } from "path";
1963
2120
  import { homedir as homedir4 } from "os";
1964
2121
  import { execSync as execSync2 } from "child_process";
1965
2122
  function getLinearApiKey2() {
1966
- const envFile = join10(homedir4(), ".panopticon.env");
1967
- if (existsSync10(envFile)) {
1968
- const content = readFileSync8(envFile, "utf-8");
2123
+ const envFile = join11(homedir4(), ".panopticon.env");
2124
+ if (existsSync11(envFile)) {
2125
+ const content = readFileSync9(envFile, "utf-8");
1969
2126
  const match = content.match(/LINEAR_API_KEY=(.+)/);
1970
2127
  if (match) return match[1].trim();
1971
2128
  }
@@ -2102,7 +2259,7 @@ async function approveCommand(id, options = {}) {
2102
2259
  state.status = "stopped";
2103
2260
  state.lastActivity = (/* @__PURE__ */ new Date()).toISOString();
2104
2261
  saveAgentState(state);
2105
- const approvedFile = join10(AGENTS_DIR, agentId, "approved");
2262
+ const approvedFile = join11(AGENTS_DIR, agentId, "approved");
2106
2263
  writeFileSync3(approvedFile, JSON.stringify({
2107
2264
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
2108
2265
  prMerged,
@@ -2133,9 +2290,9 @@ init_agents();
2133
2290
  init_paths();
2134
2291
  import chalk12 from "chalk";
2135
2292
  import ora6 from "ora";
2136
- import { existsSync as existsSync11, writeFileSync as writeFileSync4, readFileSync as readFileSync9, mkdirSync as mkdirSync4, readdirSync as readdirSync10 } from "fs";
2137
- import { join as join11 } from "path";
2138
- import { homedir as homedir5 } from "os";
2293
+ import { existsSync as existsSync13, writeFileSync as writeFileSync4, readFileSync as readFileSync11, mkdirSync as mkdirSync4, readdirSync as readdirSync11 } from "fs";
2294
+ import { join as join13 } from "path";
2295
+ import { homedir as homedir6 } from "os";
2139
2296
  import { exec } from "child_process";
2140
2297
  import { promisify } from "util";
2141
2298
 
@@ -2268,12 +2425,46 @@ function cleanupWorkflowLabels(currentLabels, targetState) {
2268
2425
  return cleaned;
2269
2426
  }
2270
2427
 
2428
+ // src/lib/tracker-utils.ts
2429
+ init_esm_shims();
2430
+ import { readFileSync as readFileSync10, existsSync as existsSync12 } from "fs";
2431
+ import { join as join12 } from "path";
2432
+ import { homedir as homedir5 } from "os";
2433
+ function parseGitHubRepos() {
2434
+ const envFile = join12(homedir5(), ".panopticon.env");
2435
+ if (!existsSync12(envFile)) return [];
2436
+ const content = readFileSync10(envFile, "utf-8");
2437
+ const reposMatch = content.match(/GITHUB_REPOS=(.+)/);
2438
+ if (!reposMatch) return [];
2439
+ return reposMatch[1].trim().split(",").map((r) => {
2440
+ const [repoPath, prefix] = r.trim().split(":");
2441
+ const [owner, repo] = (repoPath || "").split("/");
2442
+ return { owner: owner || "", repo: repo || "", prefix: (prefix || "").toUpperCase() };
2443
+ }).filter((r) => r.owner && r.repo && r.prefix);
2444
+ }
2445
+ function extractIssuePrefix(issueId) {
2446
+ return issueId.split("-")[0].toUpperCase();
2447
+ }
2448
+ function resolveGitHubIssue(issueId) {
2449
+ const prefix = extractIssuePrefix(issueId);
2450
+ const repos = parseGitHubRepos();
2451
+ for (const repoConfig of repos) {
2452
+ if (repoConfig.prefix === prefix) {
2453
+ const number = parseInt(issueId.split("-")[1], 10);
2454
+ if (!isNaN(number)) {
2455
+ return { isGitHub: true, ...repoConfig, number };
2456
+ }
2457
+ }
2458
+ }
2459
+ return { isGitHub: false };
2460
+ }
2461
+
2271
2462
  // src/cli/commands/work/done.ts
2272
2463
  var execAsync = promisify(exec);
2273
2464
  function getLinearApiKey3() {
2274
- const envFile = join11(homedir5(), ".panopticon.env");
2275
- if (existsSync11(envFile)) {
2276
- const content = readFileSync9(envFile, "utf-8");
2465
+ const envFile = join13(homedir6(), ".panopticon.env");
2466
+ if (existsSync13(envFile)) {
2467
+ const content = readFileSync11(envFile, "utf-8");
2277
2468
  const match = content.match(/LINEAR_API_KEY=(.+)/);
2278
2469
  if (match) return match[1].trim();
2279
2470
  }
@@ -2315,9 +2506,9 @@ ${comment}`
2315
2506
  }
2316
2507
  }
2317
2508
  function getGitHubConfig() {
2318
- const envFile = join11(homedir5(), ".panopticon.env");
2319
- if (!existsSync11(envFile)) return null;
2320
- const content = readFileSync9(envFile, "utf-8");
2509
+ const envFile = join13(homedir6(), ".panopticon.env");
2510
+ if (!existsSync13(envFile)) return null;
2511
+ const content = readFileSync11(envFile, "utf-8");
2321
2512
  const tokenMatch = content.match(/GITHUB_TOKEN=(.+)/);
2322
2513
  if (!tokenMatch) return null;
2323
2514
  const token = tokenMatch[1].trim();
@@ -2335,9 +2526,9 @@ async function updateGitHubToInReview(issueId, comment) {
2335
2526
  try {
2336
2527
  const ghConfig = getGitHubConfig();
2337
2528
  if (!ghConfig) return false;
2338
- const number = parseInt(issueId.split("-")[1], 10);
2339
- const repoConfig = ghConfig.repos.find((r) => r.prefix === "PAN") || ghConfig.repos[0];
2340
- const { owner, repo } = repoConfig;
2529
+ const resolved = resolveGitHubIssue(issueId);
2530
+ if (!resolved.isGitHub) return false;
2531
+ const { owner, repo, number } = resolved;
2341
2532
  const token = ghConfig.token;
2342
2533
  const headers = {
2343
2534
  "Authorization": `token ${token}`,
@@ -2373,10 +2564,59 @@ async function doneCommand(id, options = {}) {
2373
2564
  const issueId = id.replace(/^agent-/i, "").toUpperCase();
2374
2565
  const agentId = `agent-${issueId.toLowerCase()}`;
2375
2566
  if (!options.force) {
2376
- const { getAgentState: getAgentState2 } = await import("../agents-VLK4BMVA.js");
2567
+ const { getAgentState: getAgentState2 } = await import("../agents-5OPQKM5K.js");
2377
2568
  const agentState = getAgentState2(agentId);
2378
2569
  const workspacePath = agentState?.workspace;
2379
- if (workspacePath && existsSync11(workspacePath)) {
2570
+ if (workspacePath && existsSync13(workspacePath)) {
2571
+ try {
2572
+ const { getReviewStatus: getReviewStatus2 } = await import("../review-status-TDPSOU5J.js");
2573
+ const { getWorkspaceCommitHashes } = await import("../git-utils-I2UDKNZH.js");
2574
+ const { getDashboardApiUrl: getDashboardApiUrl2 } = await import("../config-4CJNUE3O.js");
2575
+ const reviewStatus = getReviewStatus2(issueId);
2576
+ if (reviewStatus) {
2577
+ const isBlockedOrFailed = ["blocked", "failed"].includes(reviewStatus.reviewStatus);
2578
+ if (isBlockedOrFailed) {
2579
+ const dashboardUrl = getDashboardApiUrl2();
2580
+ console.error(chalk12.red(`
2581
+ \u2716 Cannot mark work done \u2014 review is ${reviewStatus.reviewStatus} for ${issueId}
2582
+ `));
2583
+ console.error(" The review agent found issues that must be addressed before this issue can be completed.");
2584
+ console.error(" Fix the issues, commit your changes, then re-submit for review:\n");
2585
+ console.error(chalk12.cyan(` curl -X POST ${dashboardUrl}/api/workspaces/${issueId}/request-review \\`));
2586
+ console.error(chalk12.cyan(` -H "Content-Type: application/json" -d '{}'`));
2587
+ console.error("");
2588
+ console.error(chalk12.dim(" Use --force to skip this check."));
2589
+ console.error("");
2590
+ process.exit(1);
2591
+ }
2592
+ if (reviewStatus.lastReviewCommits) {
2593
+ const currentHashes = await getWorkspaceCommitHashes(workspacePath);
2594
+ const allKeys = /* @__PURE__ */ new Set([
2595
+ ...Object.keys(currentHashes),
2596
+ ...Object.keys(reviewStatus.lastReviewCommits)
2597
+ ]);
2598
+ const commitsChanged = [...allKeys].some(
2599
+ (k) => currentHashes[k] !== reviewStatus.lastReviewCommits[k]
2600
+ );
2601
+ if (commitsChanged) {
2602
+ const dashboardUrl = getDashboardApiUrl2();
2603
+ console.error(chalk12.red(`
2604
+ \u2716 Cannot mark work done \u2014 commits changed since last review for ${issueId}
2605
+ `));
2606
+ console.error(" New commits were added after the last review/test run. The specialists");
2607
+ console.error(" must review and test your latest code before work can be marked done.");
2608
+ console.error(" Re-submit for review:\n");
2609
+ console.error(chalk12.cyan(` curl -X POST ${dashboardUrl}/api/workspaces/${issueId}/request-review \\`));
2610
+ console.error(chalk12.cyan(` -H "Content-Type: application/json" -d '{}'`));
2611
+ console.error("");
2612
+ console.error(chalk12.dim(" Use --force to skip this check."));
2613
+ console.error("");
2614
+ process.exit(1);
2615
+ }
2616
+ }
2617
+ }
2618
+ } catch {
2619
+ }
2380
2620
  const failures = [];
2381
2621
  try {
2382
2622
  const { stdout } = await execAsync("bd list --status open --limit 0 --json", { cwd: workspacePath });
@@ -2391,7 +2631,7 @@ async function doneCommand(id, options = {}) {
2391
2631
  }
2392
2632
  } catch {
2393
2633
  }
2394
- const hasTopLevelGit = existsSync11(join11(workspacePath, ".git"));
2634
+ const hasTopLevelGit = existsSync13(join13(workspacePath, ".git"));
2395
2635
  if (hasTopLevelGit) {
2396
2636
  try {
2397
2637
  const { stdout } = await execAsync("git status --porcelain", { cwd: workspacePath });
@@ -2405,11 +2645,11 @@ async function doneCommand(id, options = {}) {
2405
2645
  }
2406
2646
  } else {
2407
2647
  try {
2408
- const entries = readdirSync10(workspacePath, { withFileTypes: true });
2648
+ const entries = readdirSync11(workspacePath, { withFileTypes: true });
2409
2649
  for (const entry of entries) {
2410
2650
  if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
2411
- const subPath = join11(workspacePath, entry.name);
2412
- if (!existsSync11(join11(subPath, ".git"))) continue;
2651
+ const subPath = join13(workspacePath, entry.name);
2652
+ if (!existsSync13(join13(subPath, ".git"))) continue;
2413
2653
  try {
2414
2654
  const { stdout } = await execAsync("git status --porcelain", { cwd: subPath });
2415
2655
  if (stdout.trim()) {
@@ -2443,14 +2683,14 @@ async function doneCommand(id, options = {}) {
2443
2683
  try {
2444
2684
  let trackerUpdated = false;
2445
2685
  let shadowModeActive = false;
2446
- const isGitHubIssue = issueId.startsWith("PAN-");
2686
+ const ghResolution = resolveGitHubIssue(issueId);
2447
2687
  const skipTrackerUpdate = shouldSkipTrackerUpdate(issueId, options.shadow);
2448
2688
  if (skipTrackerUpdate) {
2449
2689
  shadowModeActive = true;
2450
2690
  spinner.text = "Updating shadow state...";
2451
2691
  updateShadowState(issueId, "closed", "pan work done");
2452
2692
  console.log(chalk12.cyan(` \u{1F47B} Shadow mode: status updated locally`));
2453
- } else if (isGitHubIssue) {
2693
+ } else if (ghResolution.isGitHub) {
2454
2694
  spinner.text = "Updating GitHub labels...";
2455
2695
  trackerUpdated = await updateGitHubToInReview(issueId, options.comment);
2456
2696
  if (trackerUpdated) {
@@ -2472,7 +2712,7 @@ async function doneCommand(id, options = {}) {
2472
2712
  console.log(chalk12.dim(" LINEAR_API_KEY not set - skipping status update"));
2473
2713
  }
2474
2714
  }
2475
- const { getAgentState: getAgentState2, saveAgentState: saveAgentState2 } = await import("../agents-VLK4BMVA.js");
2715
+ const { getAgentState: getAgentState2, saveAgentState: saveAgentState2 } = await import("../agents-5OPQKM5K.js");
2476
2716
  const existingState = getAgentState2(agentId);
2477
2717
  if (existingState) {
2478
2718
  existingState.status = "stopped";
@@ -2483,8 +2723,8 @@ async function doneCommand(id, options = {}) {
2483
2723
  state: "idle",
2484
2724
  lastActivity: (/* @__PURE__ */ new Date()).toISOString()
2485
2725
  });
2486
- mkdirSync4(join11(AGENTS_DIR, agentId), { recursive: true });
2487
- const completedFile = join11(AGENTS_DIR, agentId, "completed");
2726
+ mkdirSync4(join13(AGENTS_DIR, agentId), { recursive: true });
2727
+ const completedFile = join13(AGENTS_DIR, agentId, "completed");
2488
2728
  writeFileSync4(completedFile, JSON.stringify({
2489
2729
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
2490
2730
  trackerUpdated,
@@ -2549,10 +2789,45 @@ async function doneCommand(id, options = {}) {
2549
2789
  req.write(postData);
2550
2790
  req.end();
2551
2791
  });
2552
- const result = await reviewReq();
2792
+ let result = await reviewReq();
2793
+ if (!result.success && result.alreadyMerged) {
2794
+ console.log(chalk12.yellow(` \u26A0 Issue was previously merged. Resetting specialist states for re-review...`));
2795
+ const resetReq = () => new Promise((resolve2, reject) => {
2796
+ const postData = JSON.stringify({});
2797
+ const req = http.request(
2798
+ `${dashboardUrl}/api/workspaces/${issueId}/reset-review`,
2799
+ { method: "POST", headers: { "Content-Type": "application/json" }, timeout: 5e3 },
2800
+ (res) => {
2801
+ let data = "";
2802
+ res.on("data", (chunk) => data += chunk);
2803
+ res.on("end", () => {
2804
+ try {
2805
+ resolve2(JSON.parse(data));
2806
+ } catch {
2807
+ resolve2({ success: false, error: "Invalid response" });
2808
+ }
2809
+ });
2810
+ }
2811
+ );
2812
+ req.on("error", reject);
2813
+ req.on("timeout", () => {
2814
+ req.destroy();
2815
+ reject(new Error("Timeout"));
2816
+ });
2817
+ req.write(postData);
2818
+ req.end();
2819
+ });
2820
+ const resetResult = await resetReq();
2821
+ if (resetResult.success) {
2822
+ console.log(chalk12.green(` \u2713 Specialist states reset`));
2823
+ result = await reviewReq();
2824
+ } else {
2825
+ console.log(chalk12.red(` \u2717 Failed to reset: ${resetResult.error || resetResult.message || "Unknown error"}`));
2826
+ }
2827
+ }
2553
2828
  if (result.success) {
2554
2829
  console.log(chalk12.green(` \u2713 Review & test ${result.queued ? "queued" : "started"} automatically`));
2555
- } else {
2830
+ } else if (!result.alreadyMerged) {
2556
2831
  console.log(chalk12.yellow(` \u26A0 Auto-review not triggered: ${result.error || result.message || "Unknown error"}`));
2557
2832
  if (result.alreadyReviewed) {
2558
2833
  console.log(chalk12.dim(` Manual review needed - click "Review and Test" in dashboard`));
@@ -2576,40 +2851,36 @@ init_esm_shims();
2576
2851
  import chalk13 from "chalk";
2577
2852
  import ora7 from "ora";
2578
2853
  import inquirer2 from "inquirer";
2579
- import { readFileSync as readFileSync10, writeFileSync as writeFileSync5, existsSync as existsSync12, mkdirSync as mkdirSync5 } from "fs";
2580
- import { join as join12, dirname as dirname5 } from "path";
2581
- import { homedir as homedir6 } from "os";
2582
- import { exec as exec2 } from "child_process";
2854
+ import { readFileSync as readFileSync13, existsSync as existsSync15 } from "fs";
2855
+ import { join as join15, dirname as dirname5 } from "path";
2856
+ import { homedir as homedir7 } from "os";
2857
+
2858
+ // src/lib/planning/plan-utils.ts
2859
+ init_esm_shims();
2860
+ import { existsSync as existsSync14, mkdirSync as mkdirSync5, writeFileSync as writeFileSync5 } from "fs";
2861
+ import { join as join14 } from "path";
2862
+ import { exec as exec2, spawn } from "child_process";
2583
2863
  import { promisify as promisify2 } from "util";
2584
2864
  init_paths();
2585
2865
  var execAsync2 = promisify2(exec2);
2586
- function getLinearApiKey4() {
2587
- const envFile = join12(homedir6(), ".panopticon.env");
2588
- if (existsSync12(envFile)) {
2589
- const content = readFileSync10(envFile, "utf-8");
2590
- const match = content.match(/LINEAR_API_KEY=(.+)/);
2591
- if (match) return match[1].trim();
2592
- }
2593
- return process.env.LINEAR_API_KEY || null;
2594
- }
2595
- async function findPRDFiles(issueId) {
2866
+ async function findPRDFiles(issueId, cwd) {
2596
2867
  const found = [];
2597
- const cwd = process.cwd();
2868
+ const searchRoot = cwd || process.cwd();
2598
2869
  if (hasPRDDraft(issueId)) {
2599
2870
  found.push(getPRDDraftPath(issueId));
2600
2871
  }
2601
2872
  const searchPaths = [
2602
- join12(PROJECT_DOCS_SUBDIR, PROJECT_PRDS_SUBDIR, PROJECT_PRDS_ACTIVE_SUBDIR),
2603
- join12(PROJECT_DOCS_SUBDIR, PROJECT_PRDS_SUBDIR, "planned"),
2604
- join12(PROJECT_DOCS_SUBDIR, PROJECT_PRDS_SUBDIR),
2605
- join12(PROJECT_DOCS_SUBDIR, "prd"),
2873
+ join14(PROJECT_DOCS_SUBDIR, PROJECT_PRDS_SUBDIR, PROJECT_PRDS_ACTIVE_SUBDIR),
2874
+ join14(PROJECT_DOCS_SUBDIR, PROJECT_PRDS_SUBDIR, "planned"),
2875
+ join14(PROJECT_DOCS_SUBDIR, PROJECT_PRDS_SUBDIR),
2876
+ join14(PROJECT_DOCS_SUBDIR, "prd"),
2606
2877
  PROJECT_PRDS_SUBDIR,
2607
2878
  PROJECT_DOCS_SUBDIR
2608
2879
  ];
2609
2880
  const issueIdLower = issueId.toLowerCase();
2610
2881
  for (const searchPath of searchPaths) {
2611
- const fullPath = join12(cwd, searchPath);
2612
- if (!existsSync12(fullPath)) continue;
2882
+ const fullPath = join14(searchRoot, searchPath);
2883
+ if (!existsSync14(fullPath)) continue;
2613
2884
  try {
2614
2885
  const { stdout: result } = await execAsync2(
2615
2886
  `find "${fullPath}" -type f -name "*.md" 2>/dev/null | xargs grep -l -i "${issueIdLower}" 2>/dev/null || true`,
@@ -2686,7 +2957,7 @@ function analyzeComplexity(issue, prdFiles) {
2686
2957
  estimatedTasks += 1;
2687
2958
  }
2688
2959
  if (prdFiles.length > 0) {
2689
- reasons.push(`PRD exists - complexity already documented`);
2960
+ reasons.push("PRD exists - complexity already documented");
2690
2961
  }
2691
2962
  const complexLabels = ["complex", "large", "epic", "multi-phase", "architecture"];
2692
2963
  for (const label of issue.labels || []) {
@@ -2703,138 +2974,7 @@ function analyzeComplexity(issue, prdFiles) {
2703
2974
  estimatedTasks: Math.max(estimatedTasks, subsystems.length + 1)
2704
2975
  };
2705
2976
  }
2706
- async function runDiscoveryPhase(issue, complexity, prdContent) {
2707
- const decisions = [];
2708
- const tasks = [];
2709
- console.log("");
2710
- console.log(chalk13.bold.cyan("\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550"));
2711
- console.log(chalk13.bold.cyan(" DISCOVERY PHASE"));
2712
- console.log(chalk13.bold.cyan("\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550"));
2713
- console.log("");
2714
- console.log(chalk13.dim("Answer questions to create a detailed execution plan."));
2715
- console.log(chalk13.dim("Press Enter to skip optional questions."));
2716
- console.log("");
2717
- console.log(chalk13.bold("Issue:"), `${issue.identifier} - ${issue.title}`);
2718
- if (complexity.subsystems.length > 0) {
2719
- console.log(chalk13.bold("Detected subsystems:"), complexity.subsystems.join(", "));
2720
- }
2721
- console.log("");
2722
- const scopeAnswer = await inquirer2.prompt([{
2723
- type: "input",
2724
- name: "scope",
2725
- message: "What specific changes are needed? (be specific about files/components):",
2726
- default: issue.description?.slice(0, 100) || ""
2727
- }]);
2728
- if (scopeAnswer.scope) {
2729
- decisions.push({ question: "Scope", answer: scopeAnswer.scope });
2730
- }
2731
- const approachAnswer = await inquirer2.prompt([{
2732
- type: "input",
2733
- name: "approach",
2734
- message: "Any specific technical approach or patterns to follow?"
2735
- }]);
2736
- if (approachAnswer.approach) {
2737
- decisions.push({ question: "Technical approach", answer: approachAnswer.approach });
2738
- }
2739
- const edgeCasesAnswer = await inquirer2.prompt([{
2740
- type: "input",
2741
- name: "edgeCases",
2742
- message: "Any edge cases or error scenarios to handle?"
2743
- }]);
2744
- if (edgeCasesAnswer.edgeCases) {
2745
- decisions.push({ question: "Edge cases", answer: edgeCasesAnswer.edgeCases });
2746
- }
2747
- const testingAnswer = await inquirer2.prompt([{
2748
- type: "checkbox",
2749
- name: "testing",
2750
- message: "What testing is required?",
2751
- choices: [
2752
- { name: "Unit tests", value: "unit", checked: true },
2753
- { name: "Integration tests", value: "integration" },
2754
- { name: "E2E tests (Playwright)", value: "e2e" },
2755
- { name: "Manual testing only", value: "manual" }
2756
- ]
2757
- }]);
2758
- if (testingAnswer.testing.length > 0) {
2759
- decisions.push({ question: "Testing", answer: testingAnswer.testing.join(", ") });
2760
- }
2761
- const outOfScopeAnswer = await inquirer2.prompt([{
2762
- type: "input",
2763
- name: "outOfScope",
2764
- message: "Anything explicitly OUT of scope for this issue?"
2765
- }]);
2766
- if (outOfScopeAnswer.outOfScope) {
2767
- decisions.push({ question: "Out of scope", answer: outOfScopeAnswer.outOfScope });
2768
- }
2769
- console.log("");
2770
- console.log(chalk13.bold("Define execution tasks:"));
2771
- console.log(chalk13.dim("Enter tasks in order. Empty task name to finish."));
2772
- console.log("");
2773
- const suggestedTasks = [
2774
- { name: "Understand requirements", description: "Review issue, PRD, and existing code" }
2775
- ];
2776
- if (complexity.subsystems.length > 1) {
2777
- suggestedTasks.push({ name: "Design approach", description: "Document architecture decisions", dependsOn: "Understand requirements" });
2778
- }
2779
- for (const subsystem of complexity.subsystems) {
2780
- suggestedTasks.push({
2781
- name: `Implement ${subsystem}`,
2782
- description: `Core ${subsystem} changes`,
2783
- dependsOn: complexity.subsystems.length > 1 ? "Design approach" : "Understand requirements"
2784
- });
2785
- }
2786
- if (suggestedTasks.length === 1) {
2787
- suggestedTasks.push({ name: "Implement changes", description: "Core implementation", dependsOn: "Understand requirements" });
2788
- }
2789
- if (testingAnswer.testing.includes("unit") || testingAnswer.testing.includes("integration")) {
2790
- suggestedTasks.push({ name: "Add tests", description: "Unit and/or integration tests", dependsOn: suggestedTasks[suggestedTasks.length - 1].name });
2791
- }
2792
- if (testingAnswer.testing.includes("e2e")) {
2793
- suggestedTasks.push({ name: "Add E2E tests", description: "Playwright E2E tests", dependsOn: "Add tests" });
2794
- }
2795
- suggestedTasks.push({ name: "Verify and cleanup", description: "Lint, type check, final review", dependsOn: suggestedTasks[suggestedTasks.length - 1].name });
2796
- console.log(chalk13.bold("Suggested tasks:"));
2797
- for (let i = 0; i < suggestedTasks.length; i++) {
2798
- const task = suggestedTasks[i];
2799
- console.log(` ${i + 1}. ${task.name}${task.dependsOn ? chalk13.dim(` (after: ${task.dependsOn})`) : ""}`);
2800
- }
2801
- console.log("");
2802
- const useDefaultAnswer = await inquirer2.prompt([{
2803
- type: "confirm",
2804
- name: "useDefault",
2805
- message: "Use these suggested tasks?",
2806
- default: true
2807
- }]);
2808
- if (useDefaultAnswer.useDefault) {
2809
- tasks.push(...suggestedTasks);
2810
- } else {
2811
- let taskIndex = 1;
2812
- let previousTask = "";
2813
- while (true) {
2814
- const taskAnswer = await inquirer2.prompt([{
2815
- type: "input",
2816
- name: "name",
2817
- message: `Task ${taskIndex} name (empty to finish):`
2818
- }]);
2819
- if (!taskAnswer.name) break;
2820
- const descAnswer = await inquirer2.prompt([{
2821
- type: "input",
2822
- name: "description",
2823
- message: `Task ${taskIndex} description:`,
2824
- default: taskAnswer.name
2825
- }]);
2826
- tasks.push({
2827
- name: taskAnswer.name,
2828
- description: descAnswer.description,
2829
- dependsOn: previousTask || void 0
2830
- });
2831
- previousTask = taskAnswer.name;
2832
- taskIndex++;
2833
- }
2834
- }
2835
- return { tasks, decisions };
2836
- }
2837
- function generateStateFile(issue, decisions, tasks) {
2977
+ function generateStateContent(issue, decisions, tasks) {
2838
2978
  const lines = [
2839
2979
  `# Agent State: ${issue.identifier}`,
2840
2980
  "",
@@ -2874,7 +3014,8 @@ function generateStateFile(issue, decisions, tasks) {
2874
3014
  lines.push("");
2875
3015
  return lines.join("\n");
2876
3016
  }
2877
- function generateWorkspaceFile(issue, prdFiles) {
3017
+ function generateWorkspaceContent(issue, prdFiles, cwd) {
3018
+ const searchRoot = cwd || process.cwd();
2878
3019
  const lines = [
2879
3020
  `# Workspace: ${issue.identifier}`,
2880
3021
  "",
@@ -2885,7 +3026,7 @@ function generateWorkspaceFile(issue, prdFiles) {
2885
3026
  `- [Linear Issue](${issue.url})`
2886
3027
  ];
2887
3028
  for (const prd of prdFiles) {
2888
- const relativePath = prd.replace(process.cwd() + "/", "");
3029
+ const relativePath = prd.replace(searchRoot + "/", "");
2889
3030
  lines.push(`- [PRD](${relativePath})`);
2890
3031
  }
2891
3032
  lines.push("");
@@ -2922,33 +3063,24 @@ function generateWorkspaceFile(issue, prdFiles) {
2922
3063
  lines.push("");
2923
3064
  return lines.join("\n");
2924
3065
  }
2925
- function estimateDifficulty(task) {
2926
- if (task.difficulty) {
2927
- return task.difficulty;
2928
- }
3066
+ function estimateTaskDifficulty(task) {
3067
+ if (task.difficulty) return task.difficulty;
2929
3068
  const combined = `${task.name} ${task.description || ""}`.toLowerCase();
2930
3069
  const expertPatterns = ["architecture", "security", "performance optimization", "distributed", "auth system", "redesign"];
2931
- if (expertPatterns.some((p) => combined.includes(p))) {
2932
- return "expert";
2933
- }
3070
+ if (expertPatterns.some((p) => combined.includes(p))) return "expert";
2934
3071
  const complexPatterns = ["refactor", "migration", "overhaul", "rewrite", "integrate", "multi-system"];
2935
- if (complexPatterns.some((p) => combined.includes(p))) {
2936
- return "complex";
2937
- }
3072
+ if (complexPatterns.some((p) => combined.includes(p))) return "complex";
2938
3073
  const mediumPatterns = ["implement", "feature", "endpoint", "component", "service", "integration", "add tests"];
2939
- if (mediumPatterns.some((p) => combined.includes(p))) {
2940
- return "medium";
2941
- }
3074
+ if (mediumPatterns.some((p) => combined.includes(p))) return "medium";
2942
3075
  const trivialPatterns = ["typo", "rename", "comment", "documentation", "readme", "formatting"];
2943
- if (trivialPatterns.some((p) => combined.includes(p))) {
2944
- return "trivial";
2945
- }
3076
+ if (trivialPatterns.some((p) => combined.includes(p))) return "trivial";
2946
3077
  return "simple";
2947
3078
  }
2948
- async function createBeadsTasks(issue, tasks) {
3079
+ async function createBeadsTasks(issue, tasks, cwd) {
2949
3080
  const created = [];
2950
3081
  const errors = [];
2951
3082
  const taskIds = /* @__PURE__ */ new Map();
3083
+ const workDir = cwd || process.cwd();
2952
3084
  try {
2953
3085
  await execAsync2("which bd", { encoding: "utf-8" });
2954
3086
  } catch {
@@ -2957,7 +3089,7 @@ async function createBeadsTasks(issue, tasks) {
2957
3089
  for (const task of tasks) {
2958
3090
  const fullName = `${issue.identifier}: ${task.name}`;
2959
3091
  try {
2960
- const difficulty = estimateDifficulty(task);
3092
+ const difficulty = estimateTaskDifficulty(task);
2961
3093
  const escapedName = fullName.replace(/"/g, '\\"');
2962
3094
  let cmd = `bd create "${escapedName}" --type task -l "${issue.identifier},linear,difficulty:${difficulty}"`;
2963
3095
  if (task.dependsOn) {
@@ -2971,7 +3103,7 @@ async function createBeadsTasks(issue, tasks) {
2971
3103
  const escapedDesc = task.description.replace(/"/g, '\\"');
2972
3104
  cmd += ` -d "${escapedDesc}"`;
2973
3105
  }
2974
- const { stdout: result } = await execAsync2(cmd, { encoding: "utf-8", cwd: process.cwd() });
3106
+ const { stdout: result } = await execAsync2(cmd, { encoding: "utf-8", cwd: workDir });
2975
3107
  const idMatch = result.match(/bd-[a-f0-9]+/i) || result.match(/([a-f0-9-]{8,})/i);
2976
3108
  if (idMatch) {
2977
3109
  taskIds.set(fullName, idMatch[0]);
@@ -2982,26 +3114,218 @@ async function createBeadsTasks(issue, tasks) {
2982
3114
  errors.push(`Failed to create "${task.name}": ${errMsg.split("\n")[0]}`);
2983
3115
  }
2984
3116
  }
2985
- if (created.length > 0) {
2986
- try {
2987
- await execAsync2("bd flush", { encoding: "utf-8", cwd: process.cwd() });
2988
- } catch {
2989
- }
3117
+ if (created.length > 0) {
3118
+ try {
3119
+ await execAsync2("bd flush", { encoding: "utf-8", cwd: workDir });
3120
+ } catch {
3121
+ }
3122
+ }
3123
+ return { success: errors.length === 0, created, errors };
3124
+ }
3125
+ function writePlanFiles(projectPath, stateContent, workspaceContent) {
3126
+ const planningDir = join14(projectPath, ".planning");
3127
+ mkdirSync5(planningDir, { recursive: true });
3128
+ const statePath = join14(planningDir, "STATE.md");
3129
+ const workspacePath = join14(planningDir, "WORKSPACE.md");
3130
+ writeFileSync5(statePath, stateContent);
3131
+ writeFileSync5(workspacePath, workspaceContent);
3132
+ return { statePath, workspacePath };
3133
+ }
3134
+ async function copyToPRDDirectory(projectPath, issue, content, options) {
3135
+ const prdDir = join14(projectPath, PROJECT_DOCS_SUBDIR, PROJECT_PRDS_SUBDIR, PROJECT_PRDS_ACTIVE_SUBDIR);
3136
+ try {
3137
+ mkdirSync5(prdDir, { recursive: true });
3138
+ const filename = `${issue.identifier.toLowerCase()}-plan.md`;
3139
+ const prdPath = join14(prdDir, filename);
3140
+ writeFileSync5(prdPath, content);
3141
+ let committed = false;
3142
+ if (options?.commitAndPush) {
3143
+ try {
3144
+ const relativePrdPath = join14(PROJECT_DOCS_SUBDIR, PROJECT_PRDS_SUBDIR, PROJECT_PRDS_ACTIVE_SUBDIR, filename);
3145
+ await execAsync2(`git add ${relativePrdPath}`, { cwd: projectPath, encoding: "utf-8" });
3146
+ try {
3147
+ await execAsync2("git diff --cached --quiet", { cwd: projectPath, encoding: "utf-8" });
3148
+ } catch {
3149
+ await execAsync2(`git commit -m "docs: add ${issue.identifier} PRD to active"`, {
3150
+ cwd: projectPath,
3151
+ encoding: "utf-8"
3152
+ });
3153
+ const pushChild = spawn("git", ["push"], { cwd: projectPath, detached: true, stdio: "ignore" });
3154
+ pushChild.unref();
3155
+ committed = true;
3156
+ }
3157
+ } catch (gitErr) {
3158
+ console.warn(`[plan] Could not commit PRD (non-fatal): ${gitErr.message}`);
3159
+ }
3160
+ }
3161
+ return { prdPath, committed };
3162
+ } catch {
3163
+ return { prdPath: null, committed: false };
3164
+ }
3165
+ }
3166
+ async function executePlan(issue, tasks, decisions, projectPath, options) {
3167
+ const prdFiles = options?.prdFiles || [];
3168
+ const stateContent = generateStateContent(issue, decisions, tasks);
3169
+ const workspaceContent = generateWorkspaceContent(issue, prdFiles, projectPath);
3170
+ const { statePath, workspacePath } = writePlanFiles(projectPath, stateContent, workspaceContent);
3171
+ const { prdPath, committed } = await copyToPRDDirectory(
3172
+ projectPath,
3173
+ issue,
3174
+ stateContent,
3175
+ { commitAndPush: options?.commitAndPush ?? false }
3176
+ );
3177
+ const beads = await createBeadsTasks(issue, tasks, projectPath);
3178
+ return {
3179
+ files: {
3180
+ state: statePath,
3181
+ workspace: workspacePath,
3182
+ prd: prdPath || void 0
3183
+ },
3184
+ prdCommitted: committed,
3185
+ beads
3186
+ };
3187
+ }
3188
+
3189
+ // src/cli/commands/work/plan.ts
3190
+ function getLinearApiKey4() {
3191
+ const envFile = join15(homedir7(), ".panopticon.env");
3192
+ if (existsSync15(envFile)) {
3193
+ const content = readFileSync13(envFile, "utf-8");
3194
+ const match = content.match(/LINEAR_API_KEY=(.+)/);
3195
+ if (match) return match[1].trim();
3196
+ }
3197
+ return process.env.LINEAR_API_KEY || null;
3198
+ }
3199
+ async function runDiscoveryPhase(issue, complexity, prdContent) {
3200
+ const decisions = [];
3201
+ const tasks = [];
3202
+ console.log("");
3203
+ console.log(chalk13.bold.cyan("\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550"));
3204
+ console.log(chalk13.bold.cyan(" DISCOVERY PHASE"));
3205
+ console.log(chalk13.bold.cyan("\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550"));
3206
+ console.log("");
3207
+ console.log(chalk13.dim("Answer questions to create a detailed execution plan."));
3208
+ console.log(chalk13.dim("Press Enter to skip optional questions."));
3209
+ console.log("");
3210
+ console.log(chalk13.bold("Issue:"), `${issue.identifier} - ${issue.title}`);
3211
+ if (complexity.subsystems.length > 0) {
3212
+ console.log(chalk13.bold("Detected subsystems:"), complexity.subsystems.join(", "));
3213
+ }
3214
+ console.log("");
3215
+ const scopeAnswer = await inquirer2.prompt([{
3216
+ type: "input",
3217
+ name: "scope",
3218
+ message: "What specific changes are needed? (be specific about files/components):",
3219
+ default: issue.description?.slice(0, 100) || ""
3220
+ }]);
3221
+ if (scopeAnswer.scope) {
3222
+ decisions.push({ question: "Scope", answer: scopeAnswer.scope });
3223
+ }
3224
+ const approachAnswer = await inquirer2.prompt([{
3225
+ type: "input",
3226
+ name: "approach",
3227
+ message: "Any specific technical approach or patterns to follow?"
3228
+ }]);
3229
+ if (approachAnswer.approach) {
3230
+ decisions.push({ question: "Technical approach", answer: approachAnswer.approach });
3231
+ }
3232
+ const edgeCasesAnswer = await inquirer2.prompt([{
3233
+ type: "input",
3234
+ name: "edgeCases",
3235
+ message: "Any edge cases or error scenarios to handle?"
3236
+ }]);
3237
+ if (edgeCasesAnswer.edgeCases) {
3238
+ decisions.push({ question: "Edge cases", answer: edgeCasesAnswer.edgeCases });
3239
+ }
3240
+ const testingAnswer = await inquirer2.prompt([{
3241
+ type: "checkbox",
3242
+ name: "testing",
3243
+ message: "What testing is required?",
3244
+ choices: [
3245
+ { name: "Unit tests", value: "unit", checked: true },
3246
+ { name: "Integration tests", value: "integration" },
3247
+ { name: "E2E tests (Playwright)", value: "e2e" },
3248
+ { name: "Manual testing only", value: "manual" }
3249
+ ]
3250
+ }]);
3251
+ if (testingAnswer.testing.length > 0) {
3252
+ decisions.push({ question: "Testing", answer: testingAnswer.testing.join(", ") });
3253
+ }
3254
+ const outOfScopeAnswer = await inquirer2.prompt([{
3255
+ type: "input",
3256
+ name: "outOfScope",
3257
+ message: "Anything explicitly OUT of scope for this issue?"
3258
+ }]);
3259
+ if (outOfScopeAnswer.outOfScope) {
3260
+ decisions.push({ question: "Out of scope", answer: outOfScopeAnswer.outOfScope });
3261
+ }
3262
+ console.log("");
3263
+ console.log(chalk13.bold("Define execution tasks:"));
3264
+ console.log(chalk13.dim("Enter tasks in order. Empty task name to finish."));
3265
+ console.log("");
3266
+ const suggestedTasks = [
3267
+ { name: "Understand requirements", description: "Review issue, PRD, and existing code" }
3268
+ ];
3269
+ if (complexity.subsystems.length > 1) {
3270
+ suggestedTasks.push({ name: "Design approach", description: "Document architecture decisions", dependsOn: "Understand requirements" });
3271
+ }
3272
+ for (const subsystem of complexity.subsystems) {
3273
+ suggestedTasks.push({
3274
+ name: `Implement ${subsystem}`,
3275
+ description: `Core ${subsystem} changes`,
3276
+ dependsOn: complexity.subsystems.length > 1 ? "Design approach" : "Understand requirements"
3277
+ });
3278
+ }
3279
+ if (suggestedTasks.length === 1) {
3280
+ suggestedTasks.push({ name: "Implement changes", description: "Core implementation", dependsOn: "Understand requirements" });
3281
+ }
3282
+ if (testingAnswer.testing.includes("unit") || testingAnswer.testing.includes("integration")) {
3283
+ suggestedTasks.push({ name: "Add tests", description: "Unit and/or integration tests", dependsOn: suggestedTasks[suggestedTasks.length - 1].name });
2990
3284
  }
2991
- return { success: errors.length === 0, created, errors };
2992
- }
2993
- function copyToPRDDirectory(issue, stateContent) {
2994
- const cwd = process.cwd();
2995
- const prdDir = join12(cwd, PROJECT_DOCS_SUBDIR, PROJECT_PRDS_SUBDIR, PROJECT_PRDS_ACTIVE_SUBDIR);
2996
- try {
2997
- mkdirSync5(prdDir, { recursive: true });
2998
- const filename = `${issue.identifier.toLowerCase()}-plan.md`;
2999
- const prdPath = join12(prdDir, filename);
3000
- writeFileSync5(prdPath, stateContent);
3001
- return prdPath;
3002
- } catch {
3003
- return null;
3285
+ if (testingAnswer.testing.includes("e2e")) {
3286
+ suggestedTasks.push({ name: "Add E2E tests", description: "Playwright E2E tests", dependsOn: "Add tests" });
3287
+ }
3288
+ suggestedTasks.push({ name: "Verify and cleanup", description: "Lint, type check, final review", dependsOn: suggestedTasks[suggestedTasks.length - 1].name });
3289
+ console.log(chalk13.bold("Suggested tasks:"));
3290
+ for (let i = 0; i < suggestedTasks.length; i++) {
3291
+ const task = suggestedTasks[i];
3292
+ console.log(` ${i + 1}. ${task.name}${task.dependsOn ? chalk13.dim(` (after: ${task.dependsOn})`) : ""}`);
3293
+ }
3294
+ console.log("");
3295
+ const useDefaultAnswer = await inquirer2.prompt([{
3296
+ type: "confirm",
3297
+ name: "useDefault",
3298
+ message: "Use these suggested tasks?",
3299
+ default: true
3300
+ }]);
3301
+ if (useDefaultAnswer.useDefault) {
3302
+ tasks.push(...suggestedTasks);
3303
+ } else {
3304
+ let taskIndex = 1;
3305
+ let previousTask = "";
3306
+ while (true) {
3307
+ const taskAnswer = await inquirer2.prompt([{
3308
+ type: "input",
3309
+ name: "name",
3310
+ message: `Task ${taskIndex} name (empty to finish):`
3311
+ }]);
3312
+ if (!taskAnswer.name) break;
3313
+ const descAnswer = await inquirer2.prompt([{
3314
+ type: "input",
3315
+ name: "description",
3316
+ message: `Task ${taskIndex} description:`,
3317
+ default: taskAnswer.name
3318
+ }]);
3319
+ tasks.push({
3320
+ name: taskAnswer.name,
3321
+ description: descAnswer.description,
3322
+ dependsOn: previousTask || void 0
3323
+ });
3324
+ previousTask = taskAnswer.name;
3325
+ taskIndex++;
3326
+ }
3004
3327
  }
3328
+ return { tasks, decisions };
3005
3329
  }
3006
3330
  async function planCommand(id, options = {}) {
3007
3331
  const spinner = ora7(`Creating execution plan for ${id}...`).start();
@@ -3041,9 +3365,9 @@ async function planCommand(id, options = {}) {
3041
3365
  identifier: issue.identifier,
3042
3366
  title: issue.title,
3043
3367
  description: issue.description || void 0,
3368
+ url: issue.url,
3044
3369
  state: { name: state?.name || "Unknown" },
3045
3370
  priority: issue.priority,
3046
- url: issue.url,
3047
3371
  labels: labels.nodes.map((l) => ({ name: l.name })),
3048
3372
  assignee: assignee ? { name: assignee.name } : void 0,
3049
3373
  project: project2 ? { name: project2.name } : void 0
@@ -3108,29 +3432,23 @@ async function planCommand(id, options = {}) {
3108
3432
  decisions = discovery.decisions;
3109
3433
  }
3110
3434
  const spinnerCreate = ora7("Creating context files...").start();
3111
- const stateContent = generateStateFile(issueData, decisions, tasks);
3112
- const workspaceContent = generateWorkspaceFile(issueData, prdFiles);
3113
3435
  const outputDir = options.output ? dirname5(options.output) : process.cwd();
3114
- const planningDir = join12(outputDir, ".planning");
3115
- mkdirSync5(planningDir, { recursive: true });
3116
- const statePath = join12(planningDir, "STATE.md");
3117
- writeFileSync5(statePath, stateContent);
3118
- const workspacePath = join12(planningDir, "WORKSPACE.md");
3119
- writeFileSync5(workspacePath, workspaceContent);
3436
+ const result = await executePlan(issueData, tasks, decisions, outputDir, {
3437
+ prdFiles
3438
+ });
3120
3439
  spinnerCreate.succeed("Context files created");
3121
- const spinnerBeads = ora7("Creating Beads tasks...").start();
3122
- const beadsResult = await createBeadsTasks(issueData, tasks);
3123
- if (beadsResult.success) {
3124
- spinnerBeads.succeed(`Created ${beadsResult.created.length} Beads tasks`);
3125
- } else {
3126
- spinnerBeads.warn(`Created ${beadsResult.created.length} tasks with errors`);
3127
- for (const error of beadsResult.errors) {
3128
- console.log(chalk13.red(` - ${error}`));
3440
+ if (result.beads.created.length > 0) {
3441
+ if (result.beads.success) {
3442
+ console.log(chalk13.green(`Created ${result.beads.created.length} Beads tasks`));
3443
+ } else {
3444
+ console.log(chalk13.yellow(`Created ${result.beads.created.length} Beads tasks with errors`));
3445
+ for (const error of result.beads.errors) {
3446
+ console.log(chalk13.red(` - ${error}`));
3447
+ }
3129
3448
  }
3130
3449
  }
3131
- const prdPath = copyToPRDDirectory(issueData, stateContent);
3132
- if (prdPath) {
3133
- console.log(chalk13.dim(`Plan copied to: ${prdPath.replace(process.cwd() + "/", "")}`));
3450
+ if (result.files.prd) {
3451
+ console.log(chalk13.dim(`Plan copied to: ${result.files.prd.replace(process.cwd() + "/", "")}`));
3134
3452
  }
3135
3453
  if (options.json) {
3136
3454
  console.log(JSON.stringify({
@@ -3138,12 +3456,8 @@ async function planCommand(id, options = {}) {
3138
3456
  complexity,
3139
3457
  tasks,
3140
3458
  decisions,
3141
- files: {
3142
- state: statePath,
3143
- workspace: workspacePath,
3144
- prd: prdPath
3145
- },
3146
- beads: beadsResult
3459
+ files: result.files,
3460
+ beads: result.beads
3147
3461
  }, null, 2));
3148
3462
  return;
3149
3463
  }
@@ -3153,8 +3467,8 @@ async function planCommand(id, options = {}) {
3153
3467
  console.log(chalk13.bold.green("\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550"));
3154
3468
  console.log("");
3155
3469
  console.log(chalk13.bold("Files created:"));
3156
- console.log(` ${chalk13.cyan(statePath.replace(process.cwd() + "/", ""))}`);
3157
- console.log(` ${chalk13.cyan(workspacePath.replace(process.cwd() + "/", ""))}`);
3470
+ console.log(` ${chalk13.cyan(result.files.state.replace(process.cwd() + "/", ""))}`);
3471
+ console.log(` ${chalk13.cyan(result.files.workspace.replace(process.cwd() + "/", ""))}`);
3158
3472
  console.log("");
3159
3473
  console.log(chalk13.bold("Beads tasks:"));
3160
3474
  for (const task of tasks) {
@@ -3359,9 +3673,9 @@ init_esm_shims();
3359
3673
  init_config();
3360
3674
  import chalk15 from "chalk";
3361
3675
  import ora9 from "ora";
3362
- import { readFileSync as readFileSync11, writeFileSync as writeFileSync6, existsSync as existsSync13, mkdirSync as mkdirSync6 } from "fs";
3363
- import { join as join13 } from "path";
3364
- import { homedir as homedir7 } from "os";
3676
+ import { readFileSync as readFileSync14, writeFileSync as writeFileSync6, existsSync as existsSync16, mkdirSync as mkdirSync6 } from "fs";
3677
+ import { join as join16 } from "path";
3678
+ import { homedir as homedir8 } from "os";
3365
3679
  function getTrackerConfig2(trackerType) {
3366
3680
  const config2 = loadConfig();
3367
3681
  const trackerConfig = config2.trackers[trackerType];
@@ -3379,13 +3693,13 @@ function getTrackerConfig2(trackerType) {
3379
3693
  };
3380
3694
  }
3381
3695
  function getTriageStatePath() {
3382
- return join13(homedir7(), ".panopticon", "triage-state.json");
3696
+ return join16(homedir8(), ".panopticon", "triage-state.json");
3383
3697
  }
3384
3698
  function loadTriageState() {
3385
3699
  const path = getTriageStatePath();
3386
- if (existsSync13(path)) {
3700
+ if (existsSync16(path)) {
3387
3701
  try {
3388
- return JSON.parse(readFileSync11(path, "utf-8"));
3702
+ return JSON.parse(readFileSync14(path, "utf-8"));
3389
3703
  } catch {
3390
3704
  return { dismissed: [], created: {} };
3391
3705
  }
@@ -3393,8 +3707,8 @@ function loadTriageState() {
3393
3707
  return { dismissed: [], created: {} };
3394
3708
  }
3395
3709
  function saveTriageState(state) {
3396
- const dir = join13(homedir7(), ".panopticon");
3397
- if (!existsSync13(dir)) {
3710
+ const dir = join16(homedir8(), ".panopticon");
3711
+ if (!existsSync16(dir)) {
3398
3712
  mkdirSync6(dir, { recursive: true });
3399
3713
  }
3400
3714
  const path = getTriageStatePath();
@@ -3779,23 +4093,23 @@ import chalk19 from "chalk";
3779
4093
  // src/lib/context.ts
3780
4094
  init_esm_shims();
3781
4095
  init_paths();
3782
- import { existsSync as existsSync14, mkdirSync as mkdirSync7, readFileSync as readFileSync12, writeFileSync as writeFileSync7, appendFileSync, readdirSync as readdirSync11 } from "fs";
3783
- import { join as join14 } from "path";
4096
+ import { existsSync as existsSync17, mkdirSync as mkdirSync7, readFileSync as readFileSync15, writeFileSync as writeFileSync7, appendFileSync, readdirSync as readdirSync12 } from "fs";
4097
+ import { join as join17 } from "path";
3784
4098
  function getStateFile(agentId) {
3785
- return join14(AGENTS_DIR, agentId, "STATE.md");
4099
+ return join17(AGENTS_DIR, agentId, "STATE.md");
3786
4100
  }
3787
4101
  function readAgentState(agentId) {
3788
4102
  const stateFile = getStateFile(agentId);
3789
- if (!existsSync14(stateFile)) return null;
4103
+ if (!existsSync17(stateFile)) return null;
3790
4104
  try {
3791
- const content = readFileSync12(stateFile, "utf-8");
4105
+ const content = readFileSync15(stateFile, "utf-8");
3792
4106
  return parseStateMd(content);
3793
4107
  } catch {
3794
4108
  return null;
3795
4109
  }
3796
4110
  }
3797
4111
  function writeAgentState(agentId, state) {
3798
- const dir = join14(AGENTS_DIR, agentId);
4112
+ const dir = join17(AGENTS_DIR, agentId);
3799
4113
  mkdirSync7(dir, { recursive: true });
3800
4114
  const content = generateStateMd(state);
3801
4115
  writeFileSync7(getStateFile(agentId), content);
@@ -3872,14 +4186,14 @@ function parseStateMd(content) {
3872
4186
  return state;
3873
4187
  }
3874
4188
  function getSummaryFile(agentId) {
3875
- return join14(AGENTS_DIR, agentId, "SUMMARY.md");
4189
+ return join17(AGENTS_DIR, agentId, "SUMMARY.md");
3876
4190
  }
3877
4191
  function appendSummary(agentId, summary) {
3878
- const dir = join14(AGENTS_DIR, agentId);
4192
+ const dir = join17(AGENTS_DIR, agentId);
3879
4193
  mkdirSync7(dir, { recursive: true });
3880
4194
  const summaryFile = getSummaryFile(agentId);
3881
4195
  const content = generateSummaryEntry(summary);
3882
- if (existsSync14(summaryFile)) {
4196
+ if (existsSync17(summaryFile)) {
3883
4197
  appendFileSync(summaryFile, "\n---\n\n" + content);
3884
4198
  } else {
3885
4199
  writeFileSync7(summaryFile, "# Work Summaries\n\n" + content);
@@ -3920,14 +4234,14 @@ function generateSummaryEntry(summary) {
3920
4234
  return lines.join("\n");
3921
4235
  }
3922
4236
  function getHistoryDir(agentId) {
3923
- return join14(AGENTS_DIR, agentId, "history");
4237
+ return join17(AGENTS_DIR, agentId, "history");
3924
4238
  }
3925
4239
  function logHistory(agentId, action, details) {
3926
4240
  const historyDir = getHistoryDir(agentId);
3927
4241
  mkdirSync7(historyDir, { recursive: true });
3928
4242
  const date = /* @__PURE__ */ new Date();
3929
4243
  const dateStr = date.toISOString().split("T")[0];
3930
- const historyFile = join14(historyDir, `${dateStr}.log`);
4244
+ const historyFile = join17(historyDir, `${dateStr}.log`);
3931
4245
  const timestamp = date.toISOString();
3932
4246
  const detailsStr = details ? ` ${JSON.stringify(details)}` : "";
3933
4247
  const logLine = `[${timestamp}] ${action}${detailsStr}
@@ -3936,13 +4250,13 @@ function logHistory(agentId, action, details) {
3936
4250
  }
3937
4251
  function searchHistory(agentId, pattern) {
3938
4252
  const historyDir = getHistoryDir(agentId);
3939
- if (!existsSync14(historyDir)) return [];
4253
+ if (!existsSync17(historyDir)) return [];
3940
4254
  const results = [];
3941
4255
  const regex = new RegExp(pattern, "i");
3942
- const files = readdirSync11(historyDir).filter((f) => f.endsWith(".log"));
4256
+ const files = readdirSync12(historyDir).filter((f) => f.endsWith(".log"));
3943
4257
  files.sort().reverse();
3944
4258
  for (const file of files) {
3945
- const content = readFileSync12(join14(historyDir, file), "utf-8");
4259
+ const content = readFileSync15(join17(historyDir, file), "utf-8");
3946
4260
  const lines = content.split("\n");
3947
4261
  for (const line of lines) {
3948
4262
  if (regex.test(line)) {
@@ -3954,13 +4268,13 @@ function searchHistory(agentId, pattern) {
3954
4268
  }
3955
4269
  function getRecentHistory(agentId, limit = 20) {
3956
4270
  const historyDir = getHistoryDir(agentId);
3957
- if (!existsSync14(historyDir)) return [];
4271
+ if (!existsSync17(historyDir)) return [];
3958
4272
  const results = [];
3959
- const files = readdirSync11(historyDir).filter((f) => f.endsWith(".log"));
4273
+ const files = readdirSync12(historyDir).filter((f) => f.endsWith(".log"));
3960
4274
  files.sort().reverse();
3961
4275
  for (const file of files) {
3962
4276
  if (results.length >= limit) break;
3963
- const content = readFileSync12(join14(historyDir, file), "utf-8");
4277
+ const content = readFileSync15(join17(historyDir, file), "utf-8");
3964
4278
  const lines = content.split("\n").filter((l) => l.trim());
3965
4279
  for (const line of lines.reverse()) {
3966
4280
  if (results.length >= limit) break;
@@ -3973,28 +4287,28 @@ function estimateTokens(text) {
3973
4287
  return Math.ceil(text.length / 4);
3974
4288
  }
3975
4289
  function getMaterializedDir(agentId) {
3976
- return join14(AGENTS_DIR, agentId, "materialized");
4290
+ return join17(AGENTS_DIR, agentId, "materialized");
3977
4291
  }
3978
4292
  function listMaterialized(agentId) {
3979
4293
  const dir = getMaterializedDir(agentId);
3980
- if (!existsSync14(dir)) return [];
3981
- return readdirSync11(dir).filter((f) => f.endsWith(".md")).map((f) => {
4294
+ if (!existsSync17(dir)) return [];
4295
+ return readdirSync12(dir).filter((f) => f.endsWith(".md")).map((f) => {
3982
4296
  const match = f.match(/^(.+)-(\d+)\.md$/);
3983
4297
  if (!match) return null;
3984
4298
  return {
3985
4299
  tool: match[1],
3986
4300
  timestamp: parseInt(match[2], 10),
3987
- file: join14(dir, f)
4301
+ file: join17(dir, f)
3988
4302
  };
3989
4303
  }).filter(Boolean);
3990
4304
  }
3991
4305
  function readMaterialized(filepath) {
3992
- if (!existsSync14(filepath)) return null;
3993
- return readFileSync12(filepath, "utf-8");
4306
+ if (!existsSync17(filepath)) return null;
4307
+ return readFileSync15(filepath, "utf-8");
3994
4308
  }
3995
4309
 
3996
4310
  // src/cli/commands/work/context.ts
3997
- import { readFileSync as readFileSync13, existsSync as existsSync15 } from "fs";
4311
+ import { readFileSync as readFileSync16, existsSync as existsSync18 } from "fs";
3998
4312
  async function contextCommand(action, arg1, arg2, options = {}) {
3999
4313
  const agentId = process.env.PANOPTICON_AGENT_ID || arg1 || "default";
4000
4314
  switch (action) {
@@ -4110,7 +4424,7 @@ History matches for "${pattern}":
4110
4424
  }
4111
4425
  case "materialize": {
4112
4426
  const filepath = arg1;
4113
- if (filepath && existsSync15(filepath)) {
4427
+ if (filepath && existsSync18(filepath)) {
4114
4428
  const content = readMaterialized(filepath);
4115
4429
  if (content) {
4116
4430
  console.log(content);
@@ -4138,8 +4452,8 @@ History matches for "${pattern}":
4138
4452
  return;
4139
4453
  }
4140
4454
  let text = target;
4141
- if (existsSync15(target)) {
4142
- text = readFileSync13(target, "utf-8");
4455
+ if (existsSync18(target)) {
4456
+ text = readFileSync16(target, "utf-8");
4143
4457
  }
4144
4458
  const tokens = estimateTokens(text);
4145
4459
  console.log(`Estimated tokens: ${chalk19.cyan(tokens.toLocaleString())}`);
@@ -4167,8 +4481,8 @@ import chalk20 from "chalk";
4167
4481
  init_esm_shims();
4168
4482
  init_paths();
4169
4483
  init_agents();
4170
- import { existsSync as existsSync16, mkdirSync as mkdirSync8, readFileSync as readFileSync14, writeFileSync as writeFileSync8 } from "fs";
4171
- import { join as join15 } from "path";
4484
+ import { existsSync as existsSync19, mkdirSync as mkdirSync8, readFileSync as readFileSync17, writeFileSync as writeFileSync8 } from "fs";
4485
+ import { join as join18 } from "path";
4172
4486
  import { exec as exec3 } from "child_process";
4173
4487
  import { promisify as promisify3 } from "util";
4174
4488
  var execAsync3 = promisify3(exec3);
@@ -4177,7 +4491,7 @@ var DEFAULT_CONSECUTIVE_FAILURES = 3;
4177
4491
  var DEFAULT_COOLDOWN_MS = 5 * 60 * 1e3;
4178
4492
  var DEFAULT_CHECK_INTERVAL_MS = 30 * 1e3;
4179
4493
  function getHealthFile(agentId) {
4180
- return join15(AGENTS_DIR, agentId, "health.json");
4494
+ return join18(AGENTS_DIR, agentId, "health.json");
4181
4495
  }
4182
4496
  function getAgentHealth(agentId) {
4183
4497
  const healthFile = getHealthFile(agentId);
@@ -4189,9 +4503,9 @@ function getAgentHealth(agentId) {
4189
4503
  recoveryCount: 0,
4190
4504
  inCooldown: false
4191
4505
  };
4192
- if (existsSync16(healthFile)) {
4506
+ if (existsSync19(healthFile)) {
4193
4507
  try {
4194
- const stored = JSON.parse(readFileSync14(healthFile, "utf-8"));
4508
+ const stored = JSON.parse(readFileSync17(healthFile, "utf-8"));
4195
4509
  return { ...defaultHealth, ...stored };
4196
4510
  } catch {
4197
4511
  }
@@ -4199,7 +4513,7 @@ function getAgentHealth(agentId) {
4199
4513
  return defaultHealth;
4200
4514
  }
4201
4515
  function saveAgentHealth(health) {
4202
- const dir = join15(AGENTS_DIR, health.agentId);
4516
+ const dir = join18(AGENTS_DIR, health.agentId);
4203
4517
  mkdirSync8(dir, { recursive: true });
4204
4518
  writeFileSync8(getHealthFile(health.agentId), JSON.stringify(health, null, 2));
4205
4519
  }
@@ -4322,9 +4636,9 @@ async function runHealthCheck(config2 = {
4322
4636
  sessions = output.trim().split("\n").filter((s) => s.startsWith("agent-"));
4323
4637
  } catch {
4324
4638
  }
4325
- if (existsSync16(AGENTS_DIR)) {
4326
- const { readdirSync: readdirSync21 } = await import("fs");
4327
- const dirs = readdirSync21(AGENTS_DIR, { withFileTypes: true }).filter((d) => d.isDirectory() && d.name.startsWith("agent-")).map((d) => d.name);
4639
+ if (existsSync19(AGENTS_DIR)) {
4640
+ const { readdirSync: readdirSync22 } = await import("fs");
4641
+ const dirs = readdirSync22(AGENTS_DIR, { withFileTypes: true }).filter((d) => d.isDirectory() && d.name.startsWith("agent-")).map((d) => d.name);
4328
4642
  for (const dir of dirs) {
4329
4643
  if (!sessions.includes(dir)) {
4330
4644
  sessions.push(dir);
@@ -4560,15 +4874,15 @@ init_esm_shims();
4560
4874
  import chalk21 from "chalk";
4561
4875
  import ora11 from "ora";
4562
4876
  import inquirer3 from "inquirer";
4563
- import { existsSync as existsSync18, readFileSync as readFileSync16 } from "fs";
4564
- import { join as join17, dirname as dirname6 } from "path";
4565
- import { homedir as homedir8 } from "os";
4877
+ import { existsSync as existsSync21, readFileSync as readFileSync19 } from "fs";
4878
+ import { join as join20, dirname as dirname6 } from "path";
4879
+ import { homedir as homedir9 } from "os";
4566
4880
  import { LinearClient } from "@linear/sdk";
4567
4881
 
4568
4882
  // src/lib/reopen.ts
4569
4883
  init_esm_shims();
4570
- import { existsSync as existsSync17, readFileSync as readFileSync15, appendFileSync as appendFileSync2 } from "fs";
4571
- import { join as join16 } from "path";
4884
+ import { existsSync as existsSync20, readFileSync as readFileSync18, appendFileSync as appendFileSync2 } from "fs";
4885
+ import { join as join19 } from "path";
4572
4886
 
4573
4887
  // src/dashboard/server/review-status.ts
4574
4888
  init_esm_shims();
@@ -4634,9 +4948,9 @@ function reopenWorkspaceState(issueId, workspacePath, options = {}) {
4634
4948
  result.queueItemsRemoved[specialistName] = removed;
4635
4949
  }
4636
4950
  }
4637
- const statePath = join16(workspacePath, ".planning", "STATE.md");
4638
- if (existsSync17(statePath)) {
4639
- const previousContent = readFileSync15(statePath, "utf-8");
4951
+ const statePath = join19(workspacePath, ".planning", "STATE.md");
4952
+ if (existsSync20(statePath)) {
4953
+ const previousContent = readFileSync18(statePath, "utf-8");
4640
4954
  const lastStatusMatch = previousContent.match(/\*\*STATUS:\s*([^*\n]+)\*\*/);
4641
4955
  const previousStatus = lastStatusMatch ? lastStatusMatch[1].trim() : "Unknown";
4642
4956
  const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
@@ -4672,9 +4986,9 @@ function reopenWorkspaceState(issueId, workspacePath, options = {}) {
4672
4986
  // src/cli/commands/work/reopen.ts
4673
4987
  init_projects();
4674
4988
  function getLinearApiKey5() {
4675
- const envFile = join17(homedir8(), ".panopticon.env");
4676
- if (existsSync18(envFile)) {
4677
- const content = readFileSync16(envFile, "utf-8");
4989
+ const envFile = join20(homedir9(), ".panopticon.env");
4990
+ if (existsSync21(envFile)) {
4991
+ const content = readFileSync19(envFile, "utf-8");
4678
4992
  const match = content.match(/LINEAR_API_KEY=(.+)/);
4679
4993
  if (match) return match[1].trim();
4680
4994
  }
@@ -4747,15 +5061,15 @@ function findLocalWorkspace2(issueId, startDir) {
4747
5061
  const normalizedId = issueId.toLowerCase();
4748
5062
  const resolved = resolveProjectFromIssue(issueId, []);
4749
5063
  if (resolved) {
4750
- const workspacePath = join17(resolved.projectPath, "workspaces", `feature-${normalizedId}`);
4751
- if (existsSync18(workspacePath)) return workspacePath;
5064
+ const workspacePath = join20(resolved.projectPath, "workspaces", `feature-${normalizedId}`);
5065
+ if (existsSync21(workspacePath)) return workspacePath;
4752
5066
  }
4753
5067
  let dir = startDir ?? process.cwd();
4754
5068
  for (let i = 0; i < 10; i++) {
4755
- const workspacesDir = join17(dir, "workspaces");
4756
- if (existsSync18(workspacesDir)) {
4757
- const workspacePath = join17(workspacesDir, `feature-${normalizedId}`);
4758
- if (existsSync18(workspacePath)) return workspacePath;
5069
+ const workspacesDir = join20(dir, "workspaces");
5070
+ if (existsSync21(workspacesDir)) {
5071
+ const workspacePath = join20(workspacesDir, `feature-${normalizedId}`);
5072
+ if (existsSync21(workspacePath)) return workspacePath;
4759
5073
  }
4760
5074
  const parent = dirname6(dir);
4761
5075
  if (parent === dir) break;
@@ -4881,8 +5195,22 @@ Previous state: ${issue.state}`
4881
5195
  console.log("");
4882
5196
  console.log(chalk21.green(`\u2713 ${issue.identifier} reopened and ready for re-work`));
4883
5197
  console.log("");
4884
- console.log(chalk21.dim("Start the agent to resume implementation:"));
4885
- console.log(` pan work ${id}`);
5198
+ try {
5199
+ const { getAgentState: getAgentState2 } = await import("../agents-5OPQKM5K.js");
5200
+ const agentId = `agent-${id.toLowerCase()}`;
5201
+ const agentState = getAgentState2(agentId);
5202
+ const agentRunning = agentState?.status === "active" || agentState?.status === "running";
5203
+ if (agentRunning) {
5204
+ console.log(chalk21.dim("Agent is still running. Send it context about the re-work:"));
5205
+ console.log(` pan tell ${id} "Issue reopened. <describe what needs to change>"`);
5206
+ } else {
5207
+ console.log(chalk21.dim("Start the agent to resume implementation:"));
5208
+ console.log(` pan work issue ${id}`);
5209
+ }
5210
+ } catch {
5211
+ console.log(chalk21.dim("Start the agent to resume implementation:"));
5212
+ console.log(` pan work issue ${id}`);
5213
+ }
4886
5214
  console.log("");
4887
5215
  } catch (error) {
4888
5216
  if (spinner.isSpinning) spinner.fail();
@@ -4984,11 +5312,12 @@ init_esm_shims();
4984
5312
  init_agents();
4985
5313
  init_tmux();
4986
5314
  import chalk24 from "chalk";
4987
- import { homedir as homedir9 } from "os";
4988
- import { join as join18 } from "path";
4989
- import { existsSync as existsSync19, rmSync, readFileSync as readFileSync17 } from "fs";
5315
+ import { homedir as homedir10 } from "os";
5316
+ import { join as join21 } from "path";
5317
+ import { existsSync as existsSync22, rmSync, readFileSync as readFileSync20 } from "fs";
4990
5318
  import { exec as exec4 } from "child_process";
4991
5319
  import { promisify as promisify4 } from "util";
5320
+ init_workspace_manager();
4992
5321
  var execAsync4 = promisify4(exec4);
4993
5322
  async function wipeCommand(issueId, options) {
4994
5323
  const issueLower = issueId.toLowerCase();
@@ -5075,22 +5404,22 @@ async function wipeCommand(issueId, options) {
5075
5404
  }
5076
5405
  }
5077
5406
  const agentDirs = [
5078
- join18(homedir9(), ".panopticon", "agents", `agent-${issueLower}`)
5407
+ join21(homedir10(), ".panopticon", "agents", `agent-${issueLower}`)
5079
5408
  ];
5080
5409
  for (const dir of agentDirs) {
5081
- if (existsSync19(dir)) {
5410
+ if (existsSync22(dir)) {
5082
5411
  rmSync(dir, { recursive: true, force: true });
5083
5412
  cleanupLog.push(`Deleted agent state: ${dir}`);
5084
- console.log(chalk24.green(` \u2713 Deleted agent state: ${dir.replace(homedir9(), "~")}`));
5413
+ console.log(chalk24.green(` \u2713 Deleted agent state: ${dir.replace(homedir10(), "~")}`));
5085
5414
  }
5086
5415
  }
5087
5416
  let projectPath;
5088
5417
  const prefix = issueId.split("-")[0].toUpperCase();
5089
- const projectsYamlPath = join18(homedir9(), ".panopticon", "projects.yaml");
5090
- if (existsSync19(projectsYamlPath)) {
5418
+ const projectsYamlPath = join21(homedir10(), ".panopticon", "projects.yaml");
5419
+ if (existsSync22(projectsYamlPath)) {
5091
5420
  try {
5092
5421
  const yaml2 = await import("js-yaml");
5093
- const projectsConfig = yaml2.load(readFileSync17(projectsYamlPath, "utf-8"));
5422
+ const projectsConfig = yaml2.load(readFileSync20(projectsYamlPath, "utf-8"));
5094
5423
  for (const [, config2] of Object.entries(projectsConfig.projects || {})) {
5095
5424
  const projConfig = config2;
5096
5425
  if (projConfig.linear_team?.toUpperCase() === prefix) {
@@ -5101,20 +5430,37 @@ async function wipeCommand(issueId, options) {
5101
5430
  } catch (e) {
5102
5431
  }
5103
5432
  }
5433
+ if (projectPath) {
5434
+ const workspacePath = join21(projectPath, "workspaces", `feature-${issueLower}`);
5435
+ if (existsSync22(workspacePath)) {
5436
+ try {
5437
+ const projName = projectPath.split("/").pop() || "";
5438
+ const dockerResult = await stopWorkspaceDocker(workspacePath, projName, issueLower);
5439
+ if (dockerResult.steps.length > 0) {
5440
+ for (const step of dockerResult.steps) {
5441
+ cleanupLog.push(step);
5442
+ console.log(chalk24.green(` \u2713 ${step}`));
5443
+ }
5444
+ }
5445
+ } catch (e) {
5446
+ console.log(chalk24.yellow(` \u26A0 Docker cleanup: ${e.message}`));
5447
+ }
5448
+ }
5449
+ }
5104
5450
  if (options.workspace && projectPath) {
5105
- const workspacePath = join18(projectPath, "workspaces", `feature-${issueLower}`);
5106
- if (existsSync19(workspacePath)) {
5451
+ const workspacePath = join21(projectPath, "workspaces", `feature-${issueLower}`);
5452
+ if (existsSync22(workspacePath)) {
5107
5453
  try {
5108
5454
  const gitDirs = ["api", "frontend", "fe", "."];
5109
5455
  for (const gitDir of gitDirs) {
5110
- const gitPath = join18(projectPath, gitDir);
5111
- if (existsSync19(join18(gitPath, ".git"))) {
5456
+ const gitPath = join21(projectPath, gitDir);
5457
+ if (existsSync22(join21(gitPath, ".git"))) {
5112
5458
  await execAsync4(`cd "${gitPath}" && git worktree remove "${workspacePath}" --force 2>/dev/null || true`);
5113
5459
  }
5114
5460
  }
5115
5461
  } catch (e) {
5116
5462
  }
5117
- if (existsSync19(workspacePath)) {
5463
+ if (existsSync22(workspacePath)) {
5118
5464
  rmSync(workspacePath, { recursive: true, force: true });
5119
5465
  }
5120
5466
  cleanupLog.push(`Deleted workspace: ${workspacePath}`);
@@ -5172,14 +5518,14 @@ import ora12 from "ora";
5172
5518
 
5173
5519
  // src/lib/shadow-utils.ts
5174
5520
  init_esm_shims();
5175
- import { existsSync as existsSync20, readFileSync as readFileSync18 } from "fs";
5176
- import { join as join19 } from "path";
5177
- import { homedir as homedir10 } from "os";
5521
+ import { existsSync as existsSync23, readFileSync as readFileSync21 } from "fs";
5522
+ import { join as join22 } from "path";
5523
+ import { homedir as homedir11 } from "os";
5178
5524
  import chalk25 from "chalk";
5179
5525
  function getLinearApiKey6() {
5180
- const envFile = join19(homedir10(), ".panopticon.env");
5181
- if (existsSync20(envFile)) {
5182
- const content = readFileSync18(envFile, "utf-8");
5526
+ const envFile = join22(homedir11(), ".panopticon.env");
5527
+ if (existsSync23(envFile)) {
5528
+ const content = readFileSync21(envFile, "utf-8");
5183
5529
  const match = content.match(/LINEAR_API_KEY=(.+)/);
5184
5530
  if (match) return match[1].trim();
5185
5531
  }
@@ -5278,10 +5624,10 @@ init_esm_shims();
5278
5624
  import chalk27 from "chalk";
5279
5625
  import ora13 from "ora";
5280
5626
  import inquirer4 from "inquirer";
5281
- import { existsSync as existsSync21, readFileSync as readFileSync19, writeFileSync as writeFileSync9, mkdirSync as mkdirSync9 } from "fs";
5282
- import { join as join20, dirname as dirname7 } from "path";
5283
- import { homedir as homedir11 } from "os";
5284
- var SYNC_QUEUE_FILE = join20(homedir11(), ".panopticon", "sync-queue.json");
5627
+ import { existsSync as existsSync24, readFileSync as readFileSync22, writeFileSync as writeFileSync9, mkdirSync as mkdirSync9 } from "fs";
5628
+ import { join as join23, dirname as dirname7 } from "path";
5629
+ import { homedir as homedir12 } from "os";
5630
+ var SYNC_QUEUE_FILE = join23(homedir12(), ".panopticon", "sync-queue.json");
5285
5631
  async function syncToLinear(apiKey, issueId, targetState) {
5286
5632
  try {
5287
5633
  const { LinearClient: LinearClient2 } = await import("@linear/sdk");
@@ -5453,11 +5799,11 @@ async function syncCommand2(id, options = {}) {
5453
5799
  }
5454
5800
  }
5455
5801
  function loadSyncQueue() {
5456
- if (!existsSync21(SYNC_QUEUE_FILE)) {
5802
+ if (!existsSync24(SYNC_QUEUE_FILE)) {
5457
5803
  return [];
5458
5804
  }
5459
5805
  try {
5460
- const content = readFileSync19(SYNC_QUEUE_FILE, "utf-8");
5806
+ const content = readFileSync22(SYNC_QUEUE_FILE, "utf-8");
5461
5807
  return JSON.parse(content);
5462
5808
  } catch (error) {
5463
5809
  console.error(chalk27.yellow("Warning: Failed to load sync queue"));
@@ -5466,7 +5812,7 @@ function loadSyncQueue() {
5466
5812
  }
5467
5813
  function saveSyncQueue(queue) {
5468
5814
  const dir = dirname7(SYNC_QUEUE_FILE);
5469
- if (!existsSync21(dir)) {
5815
+ if (!existsSync24(dir)) {
5470
5816
  mkdirSync9(dir, { recursive: true });
5471
5817
  }
5472
5818
  try {
@@ -5593,8 +5939,8 @@ async function refreshCommand(id, options = {}) {
5593
5939
  init_esm_shims();
5594
5940
  init_tldr_daemon();
5595
5941
  import chalk29 from "chalk";
5596
- import { existsSync as existsSync22, readdirSync as readdirSync13, readFileSync as readFileSync20, statSync as statSync4 } from "fs";
5597
- import { join as join21 } from "path";
5942
+ import { existsSync as existsSync25, readdirSync as readdirSync14, readFileSync as readFileSync23, statSync as statSync5 } from "fs";
5943
+ import { join as join24 } from "path";
5598
5944
  async function tldrCommand(action, workspace, options = {}) {
5599
5945
  switch (action) {
5600
5946
  case "status":
@@ -5617,19 +5963,19 @@ async function tldrCommand(action, workspace, options = {}) {
5617
5963
  }
5618
5964
  async function statusCommand2(options) {
5619
5965
  const projectRoot = process.cwd();
5620
- const venvPath = join21(projectRoot, ".venv");
5966
+ const venvPath = join24(projectRoot, ".venv");
5621
5967
  const results = [];
5622
- if (existsSync22(venvPath)) {
5968
+ if (existsSync25(venvPath)) {
5623
5969
  const service = getTldrDaemonService(projectRoot, venvPath);
5624
5970
  const status = await service.getStatus();
5625
- const tldrPath = join21(projectRoot, ".tldr");
5971
+ const tldrPath = join24(projectRoot, ".tldr");
5626
5972
  let indexAge = "N/A";
5627
5973
  let fileCount = "N/A";
5628
- if (existsSync22(tldrPath)) {
5974
+ if (existsSync25(tldrPath)) {
5629
5975
  try {
5630
- const langPath = join21(tldrPath, "languages.json");
5631
- if (existsSync22(langPath)) {
5632
- const langData = JSON.parse(readFileSync20(langPath, "utf-8"));
5976
+ const langPath = join24(tldrPath, "languages.json");
5977
+ if (existsSync25(langPath)) {
5978
+ const langData = JSON.parse(readFileSync23(langPath, "utf-8"));
5633
5979
  if (langData.timestamp) {
5634
5980
  const ageMs = Date.now() - langData.timestamp * 1e3;
5635
5981
  const ageDays = Math.floor(ageMs / (1e3 * 60 * 60 * 24));
@@ -5637,14 +5983,14 @@ async function statusCommand2(options) {
5637
5983
  }
5638
5984
  }
5639
5985
  if (indexAge === "N/A") {
5640
- const stats = statSync4(tldrPath);
5986
+ const stats = statSync5(tldrPath);
5641
5987
  const ageMs = Date.now() - stats.mtimeMs;
5642
5988
  const ageDays = Math.floor(ageMs / (1e3 * 60 * 60 * 24));
5643
5989
  indexAge = ageDays === 0 ? "today" : `${ageDays}d ago`;
5644
5990
  }
5645
- const cgPath = join21(tldrPath, "cache", "call_graph.json");
5646
- if (existsSync22(cgPath)) {
5647
- const cg = JSON.parse(readFileSync20(cgPath, "utf-8"));
5991
+ const cgPath = join24(tldrPath, "cache", "call_graph.json");
5992
+ if (existsSync25(cgPath)) {
5993
+ const cg = JSON.parse(readFileSync23(cgPath, "utf-8"));
5648
5994
  if (Array.isArray(cg.edges)) {
5649
5995
  const files = /* @__PURE__ */ new Set();
5650
5996
  for (const e of cg.edges) {
@@ -5666,23 +6012,23 @@ async function statusCommand2(options) {
5666
6012
  fileCount
5667
6013
  });
5668
6014
  }
5669
- const workspacesDir = join21(projectRoot, "workspaces");
5670
- if (existsSync22(workspacesDir)) {
5671
- const workspaces = readdirSync13(workspacesDir, { withFileTypes: true }).filter((d) => d.isDirectory() && d.name.startsWith("feature-"));
6015
+ const workspacesDir = join24(projectRoot, "workspaces");
6016
+ if (existsSync25(workspacesDir)) {
6017
+ const workspaces = readdirSync14(workspacesDir, { withFileTypes: true }).filter((d) => d.isDirectory() && d.name.startsWith("feature-"));
5672
6018
  for (const ws of workspaces) {
5673
- const wsPath = join21(workspacesDir, ws.name);
5674
- const wsVenvPath = join21(wsPath, ".venv");
5675
- if (existsSync22(wsVenvPath)) {
6019
+ const wsPath = join24(workspacesDir, ws.name);
6020
+ const wsVenvPath = join24(wsPath, ".venv");
6021
+ if (existsSync25(wsVenvPath)) {
5676
6022
  const service = getTldrDaemonService(wsPath, wsVenvPath);
5677
6023
  const status = await service.getStatus();
5678
- const tldrPath = join21(wsPath, ".tldr");
6024
+ const tldrPath = join24(wsPath, ".tldr");
5679
6025
  let indexAge = "N/A";
5680
6026
  let fileCount = "N/A";
5681
- if (existsSync22(tldrPath)) {
6027
+ if (existsSync25(tldrPath)) {
5682
6028
  try {
5683
- const langPath = join21(tldrPath, "languages.json");
5684
- if (existsSync22(langPath)) {
5685
- const langData = JSON.parse(readFileSync20(langPath, "utf-8"));
6029
+ const langPath = join24(tldrPath, "languages.json");
6030
+ if (existsSync25(langPath)) {
6031
+ const langData = JSON.parse(readFileSync23(langPath, "utf-8"));
5686
6032
  if (langData.timestamp) {
5687
6033
  const ageMs = Date.now() - langData.timestamp * 1e3;
5688
6034
  const ageHours = Math.floor(ageMs / (1e3 * 60 * 60));
@@ -5690,14 +6036,14 @@ async function statusCommand2(options) {
5690
6036
  }
5691
6037
  }
5692
6038
  if (indexAge === "N/A") {
5693
- const stats = statSync4(tldrPath);
6039
+ const stats = statSync5(tldrPath);
5694
6040
  const ageMs = Date.now() - stats.mtimeMs;
5695
6041
  const ageHours = Math.floor(ageMs / (1e3 * 60 * 60));
5696
6042
  indexAge = ageHours === 0 ? "now" : ageHours < 24 ? `${ageHours}h ago` : `${Math.floor(ageHours / 24)}d ago`;
5697
6043
  }
5698
- const cgPath = join21(tldrPath, "cache", "call_graph.json");
5699
- if (existsSync22(cgPath)) {
5700
- const cg = JSON.parse(readFileSync20(cgPath, "utf-8"));
6044
+ const cgPath = join24(tldrPath, "cache", "call_graph.json");
6045
+ if (existsSync25(cgPath)) {
6046
+ const cg = JSON.parse(readFileSync23(cgPath, "utf-8"));
5701
6047
  if (Array.isArray(cg.edges)) {
5702
6048
  const files = /* @__PURE__ */ new Set();
5703
6049
  for (const e of cg.edges) {
@@ -5747,13 +6093,13 @@ async function statusCommand2(options) {
5747
6093
  async function startCommand(workspace, options) {
5748
6094
  const projectRoot = process.cwd();
5749
6095
  if (workspace) {
5750
- const wsPath = join21(projectRoot, "workspaces", workspace);
5751
- const venvPath = join21(wsPath, ".venv");
5752
- if (!existsSync22(wsPath)) {
6096
+ const wsPath = join24(projectRoot, "workspaces", workspace);
6097
+ const venvPath = join24(wsPath, ".venv");
6098
+ if (!existsSync25(wsPath)) {
5753
6099
  console.error(chalk29.red(`Error: Workspace not found: ${workspace}`));
5754
6100
  process.exit(1);
5755
6101
  }
5756
- if (!existsSync22(venvPath)) {
6102
+ if (!existsSync25(venvPath)) {
5757
6103
  console.error(chalk29.red(`Error: No .venv found in workspace: ${workspace}`));
5758
6104
  console.error(chalk29.dim("Workspace needs to be recreated with TLDR support"));
5759
6105
  process.exit(1);
@@ -5764,8 +6110,8 @@ async function startCommand(workspace, options) {
5764
6110
  console.log(chalk29.green(`\u2713 Started TLDR daemon for ${workspace}`));
5765
6111
  }
5766
6112
  } else {
5767
- const venvPath = join21(projectRoot, ".venv");
5768
- if (!existsSync22(venvPath)) {
6113
+ const venvPath = join24(projectRoot, ".venv");
6114
+ if (!existsSync25(venvPath)) {
5769
6115
  console.error(chalk29.red("Error: No .venv found in project root"));
5770
6116
  console.error(chalk29.dim("Run `pan setup` to configure TLDR"));
5771
6117
  process.exit(1);
@@ -5780,13 +6126,13 @@ async function startCommand(workspace, options) {
5780
6126
  async function stopCommand(workspace, options) {
5781
6127
  const projectRoot = process.cwd();
5782
6128
  if (workspace) {
5783
- const wsPath = join21(projectRoot, "workspaces", workspace);
5784
- const venvPath = join21(wsPath, ".venv");
5785
- if (!existsSync22(wsPath)) {
6129
+ const wsPath = join24(projectRoot, "workspaces", workspace);
6130
+ const venvPath = join24(wsPath, ".venv");
6131
+ if (!existsSync25(wsPath)) {
5786
6132
  console.error(chalk29.red(`Error: Workspace not found: ${workspace}`));
5787
6133
  process.exit(1);
5788
6134
  }
5789
- if (!existsSync22(venvPath)) {
6135
+ if (!existsSync25(venvPath)) {
5790
6136
  console.error(chalk29.red(`Error: No .venv found in workspace: ${workspace}`));
5791
6137
  process.exit(1);
5792
6138
  }
@@ -5796,8 +6142,8 @@ async function stopCommand(workspace, options) {
5796
6142
  console.log(chalk29.green(`\u2713 Stopped TLDR daemon for ${workspace}`));
5797
6143
  }
5798
6144
  } else {
5799
- const venvPath = join21(projectRoot, ".venv");
5800
- if (!existsSync22(venvPath)) {
6145
+ const venvPath = join24(projectRoot, ".venv");
6146
+ if (!existsSync25(venvPath)) {
5801
6147
  console.error(chalk29.red("Error: No .venv found in project root"));
5802
6148
  process.exit(1);
5803
6149
  }
@@ -5811,13 +6157,13 @@ async function stopCommand(workspace, options) {
5811
6157
  async function warmCommand(workspace, options) {
5812
6158
  const projectRoot = process.cwd();
5813
6159
  if (workspace) {
5814
- const wsPath = join21(projectRoot, "workspaces", workspace);
5815
- const venvPath = join21(wsPath, ".venv");
5816
- if (!existsSync22(wsPath)) {
6160
+ const wsPath = join24(projectRoot, "workspaces", workspace);
6161
+ const venvPath = join24(wsPath, ".venv");
6162
+ if (!existsSync25(wsPath)) {
5817
6163
  console.error(chalk29.red(`Error: Workspace not found: ${workspace}`));
5818
6164
  process.exit(1);
5819
6165
  }
5820
- if (!existsSync22(venvPath)) {
6166
+ if (!existsSync25(venvPath)) {
5821
6167
  console.error(chalk29.red(`Error: No .venv found in workspace: ${workspace}`));
5822
6168
  process.exit(1);
5823
6169
  }
@@ -5831,8 +6177,8 @@ async function warmCommand(workspace, options) {
5831
6177
  console.log(chalk29.green(`\u2713 Index warming complete for ${workspace}`));
5832
6178
  }
5833
6179
  } else {
5834
- const venvPath = join21(projectRoot, ".venv");
5835
- if (!existsSync22(venvPath)) {
6180
+ const venvPath = join24(projectRoot, ".venv");
6181
+ if (!existsSync25(venvPath)) {
5836
6182
  console.error(chalk29.red("Error: No .venv found in project root"));
5837
6183
  console.error(chalk29.dim("Run `pan setup` to configure TLDR"));
5838
6184
  process.exit(1);
@@ -5915,18 +6261,15 @@ async function syncMainCommand(id) {
5915
6261
  // src/cli/commands/work/close-out.ts
5916
6262
  init_esm_shims();
5917
6263
  import chalk31 from "chalk";
5918
- import { existsSync as existsSync27, readFileSync as readFileSync23 } from "fs";
5919
- import { join as join26 } from "path";
5920
- import { homedir as homedir13 } from "os";
5921
6264
 
5922
6265
  // src/lib/lifecycle/index.ts
5923
6266
  init_esm_shims();
5924
6267
 
5925
6268
  // src/lib/lifecycle/types.ts
5926
6269
  init_esm_shims();
5927
- import { existsSync as existsSync23, readFileSync as readFileSync21 } from "fs";
5928
- import { join as join22 } from "path";
5929
- import { homedir as homedir12 } from "os";
6270
+ import { existsSync as existsSync26, readFileSync as readFileSync24 } from "fs";
6271
+ import { join as join25 } from "path";
6272
+ import { homedir as homedir13 } from "os";
5930
6273
  function stepOk(step, details) {
5931
6274
  return { step, success: true, skipped: false, details };
5932
6275
  }
@@ -5938,9 +6281,9 @@ function stepFailed(step, error, details) {
5938
6281
  }
5939
6282
  function getLinearApiKey7() {
5940
6283
  if (process.env.LINEAR_API_KEY) return process.env.LINEAR_API_KEY;
5941
- const envFile = join22(homedir12(), ".panopticon.env");
5942
- if (existsSync23(envFile)) {
5943
- const content = readFileSync21(envFile, "utf-8");
6284
+ const envFile = join25(homedir13(), ".panopticon.env");
6285
+ if (existsSync26(envFile)) {
6286
+ const content = readFileSync24(envFile, "utf-8");
5944
6287
  const match = content.match(/LINEAR_API_KEY=(.+)/);
5945
6288
  if (match) return match[1].trim();
5946
6289
  }
@@ -5950,20 +6293,20 @@ function getLinearApiKey7() {
5950
6293
  // src/lib/lifecycle/archive-planning.ts
5951
6294
  init_esm_shims();
5952
6295
  init_paths();
5953
- import { existsSync as existsSync24, mkdirSync as mkdirSync10, cpSync as cpSync2, rmSync as rmSync2 } from "fs";
5954
- import { join as join23, dirname as dirname8 } from "path";
6296
+ import { existsSync as existsSync27, mkdirSync as mkdirSync10, cpSync as cpSync2, rmSync as rmSync2 } from "fs";
6297
+ import { join as join26, dirname as dirname8 } from "path";
5955
6298
  import { exec as exec5 } from "child_process";
5956
6299
  import { promisify as promisify5 } from "util";
5957
6300
  var execAsync5 = promisify5(exec5);
5958
6301
  function findWorkspacePath(projectPath, issueLower) {
5959
6302
  const candidates = [
5960
- join23(projectPath, "workspaces", `feature-${issueLower}`),
5961
- join23(projectPath, "workspaces", issueLower),
5962
- join23(projectPath, ".worktrees", issueLower),
5963
- join23(dirname8(projectPath), `feature-${issueLower}`)
6303
+ join26(projectPath, "workspaces", `feature-${issueLower}`),
6304
+ join26(projectPath, "workspaces", issueLower),
6305
+ join26(projectPath, ".worktrees", issueLower),
6306
+ join26(dirname8(projectPath), `feature-${issueLower}`)
5964
6307
  ];
5965
6308
  for (const p of candidates) {
5966
- if (existsSync24(p)) return p;
6309
+ if (existsSync27(p)) return p;
5967
6310
  }
5968
6311
  return null;
5969
6312
  }
@@ -5971,28 +6314,28 @@ async function movePrd(ctx, opts = {}) {
5971
6314
  const { pushToRemote = true } = opts;
5972
6315
  const issueLower = ctx.issueId.toLowerCase();
5973
6316
  const step = "archive-planning:move-prd";
5974
- const completedPrdPath = join23(
6317
+ const completedPrdPath = join26(
5975
6318
  ctx.projectPath,
5976
6319
  PROJECT_DOCS_SUBDIR,
5977
6320
  PROJECT_PRDS_SUBDIR,
5978
6321
  PROJECT_PRDS_COMPLETED_SUBDIR,
5979
6322
  `${issueLower}-plan.md`
5980
6323
  );
5981
- const activePrdPath = join23(
6324
+ const activePrdPath = join26(
5982
6325
  ctx.projectPath,
5983
6326
  PROJECT_DOCS_SUBDIR,
5984
6327
  PROJECT_PRDS_SUBDIR,
5985
6328
  PROJECT_PRDS_ACTIVE_SUBDIR,
5986
6329
  `${issueLower}-plan.md`
5987
6330
  );
5988
- if (existsSync24(completedPrdPath)) {
6331
+ if (existsSync27(completedPrdPath)) {
5989
6332
  return stepSkipped(step, ["PRD already in completed/"]);
5990
6333
  }
5991
- if (!existsSync24(activePrdPath)) {
6334
+ if (!existsSync27(activePrdPath)) {
5992
6335
  return stepSkipped(step, ["No PRD found in active/ (may not have had one)"]);
5993
6336
  }
5994
6337
  const completedDir = dirname8(completedPrdPath);
5995
- if (!existsSync24(completedDir)) {
6338
+ if (!existsSync27(completedDir)) {
5996
6339
  mkdirSync10(completedDir, { recursive: true });
5997
6340
  }
5998
6341
  try {
@@ -6006,7 +6349,7 @@ async function movePrd(ctx, opts = {}) {
6006
6349
  }
6007
6350
  try {
6008
6351
  cpSync2(activePrdPath, completedPrdPath);
6009
- if (!existsSync24(completedPrdPath)) {
6352
+ if (!existsSync27(completedPrdPath)) {
6010
6353
  return stepFailed(step, "PRD copy appeared to succeed but file not found at destination");
6011
6354
  }
6012
6355
  return stepOk(step, ["Copied PRD to completed/ (git mv failed, plain copy succeeded)"]);
@@ -6018,14 +6361,14 @@ async function archiveWorkspaceArtifacts(ctx) {
6018
6361
  const issueLower = ctx.issueId.toLowerCase();
6019
6362
  const step = "archive-planning:archive-artifacts";
6020
6363
  const workspacePath = findWorkspacePath(ctx.projectPath, issueLower);
6021
- if (!workspacePath || !existsSync24(workspacePath)) {
6364
+ if (!workspacePath || !existsSync27(workspacePath)) {
6022
6365
  return stepSkipped(step, ["No workspace found to archive"]);
6023
6366
  }
6024
6367
  try {
6025
- let archiveDir = join23(ARCHIVES_DIR, issueLower);
6026
- if (existsSync24(archiveDir)) {
6368
+ let archiveDir = join26(ARCHIVES_DIR, issueLower);
6369
+ if (existsSync27(archiveDir)) {
6027
6370
  let version = 1;
6028
- while (existsSync24(`${archiveDir}.${version}`)) {
6371
+ while (existsSync27(`${archiveDir}.${version}`)) {
6029
6372
  version++;
6030
6373
  }
6031
6374
  const rotatedDir = `${archiveDir}.${version}`;
@@ -6034,24 +6377,24 @@ async function archiveWorkspaceArtifacts(ctx) {
6034
6377
  }
6035
6378
  mkdirSync10(archiveDir, { recursive: true });
6036
6379
  const details = [];
6037
- const feedbackDir = join23(workspacePath, ".planning", "feedback");
6038
- if (existsSync24(feedbackDir)) {
6039
- cpSync2(feedbackDir, join23(archiveDir, "feedback"), { recursive: true });
6380
+ const feedbackDir = join26(workspacePath, ".planning", "feedback");
6381
+ if (existsSync27(feedbackDir)) {
6382
+ cpSync2(feedbackDir, join26(archiveDir, "feedback"), { recursive: true });
6040
6383
  details.push("Archived feedback/");
6041
6384
  }
6042
- const stateMd = join23(workspacePath, ".planning", "STATE.md");
6043
- if (existsSync24(stateMd)) {
6044
- cpSync2(stateMd, join23(archiveDir, "STATE.md"));
6385
+ const stateMd = join26(workspacePath, ".planning", "STATE.md");
6386
+ if (existsSync27(stateMd)) {
6387
+ cpSync2(stateMd, join26(archiveDir, "STATE.md"));
6045
6388
  details.push("Archived STATE.md");
6046
6389
  }
6047
- const beadsDir = join23(workspacePath, ".planning", "beads");
6048
- if (existsSync24(beadsDir)) {
6049
- cpSync2(beadsDir, join23(archiveDir, "beads"), { recursive: true });
6390
+ const beadsDir = join26(workspacePath, ".planning", "beads");
6391
+ if (existsSync27(beadsDir)) {
6392
+ cpSync2(beadsDir, join26(archiveDir, "beads"), { recursive: true });
6050
6393
  details.push("Archived beads/");
6051
6394
  }
6052
- const prdMd = join23(workspacePath, ".planning", "PRD.md");
6053
- if (existsSync24(prdMd)) {
6054
- cpSync2(prdMd, join23(archiveDir, "PRD.md"));
6395
+ const prdMd = join26(workspacePath, ".planning", "PRD.md");
6396
+ if (existsSync27(prdMd)) {
6397
+ cpSync2(prdMd, join26(archiveDir, "PRD.md"));
6055
6398
  details.push("Archived workspace PRD.md");
6056
6399
  }
6057
6400
  details.push(`Archived to ${archiveDir}`);
@@ -6316,8 +6659,8 @@ async function applyLabelLinear(ctx, apiKey) {
6316
6659
  init_esm_shims();
6317
6660
  init_paths();
6318
6661
  init_tmux();
6319
- import { existsSync as existsSync25, rmSync as rmSync3, unlinkSync as unlinkSync2 } from "fs";
6320
- import { join as join24, basename as basename4 } from "path";
6662
+ import { existsSync as existsSync28, rmSync as rmSync3, unlinkSync as unlinkSync2 } from "fs";
6663
+ import { join as join27, basename as basename5 } from "path";
6321
6664
  import { exec as exec7 } from "child_process";
6322
6665
  import { promisify as promisify7 } from "util";
6323
6666
  var execAsync7 = promisify7(exec7);
@@ -6347,8 +6690,8 @@ async function killTmuxSessions(issueLower) {
6347
6690
  }
6348
6691
  async function stopTldrDaemon(workspacePath) {
6349
6692
  const step = "teardown:tldr-daemon";
6350
- const venvPath = join24(workspacePath, ".venv");
6351
- if (!existsSync25(venvPath)) {
6693
+ const venvPath = join27(workspacePath, ".venv");
6694
+ if (!existsSync28(venvPath)) {
6352
6695
  return stepSkipped(step, ["No .venv found"]);
6353
6696
  }
6354
6697
  try {
@@ -6363,8 +6706,8 @@ async function stopTldrDaemon(workspacePath) {
6363
6706
  async function stopDocker(workspacePath, projectName, issueLower) {
6364
6707
  const step = "teardown:docker";
6365
6708
  try {
6366
- const { stopWorkspaceDocker } = await import("../workspace-manager-KLHUCIZV.js");
6367
- await stopWorkspaceDocker(workspacePath, projectName, issueLower);
6709
+ const { stopWorkspaceDocker: stopWorkspaceDocker2 } = await import("../workspace-manager-E434Z45T.js");
6710
+ await stopWorkspaceDocker2(workspacePath, projectName, issueLower);
6368
6711
  return stepOk(step, ["Stopped Docker containers"]);
6369
6712
  } catch {
6370
6713
  return stepSkipped(step, ["Docker cleanup skipped (not running or failed)"]);
@@ -6372,7 +6715,7 @@ async function stopDocker(workspacePath, projectName, issueLower) {
6372
6715
  }
6373
6716
  async function removeWorktree(projectPath, workspacePath) {
6374
6717
  const step = "teardown:worktree";
6375
- if (!existsSync25(workspacePath)) {
6718
+ if (!existsSync28(workspacePath)) {
6376
6719
  return stepSkipped(step, ["Workspace directory does not exist"]);
6377
6720
  }
6378
6721
  try {
@@ -6390,12 +6733,12 @@ async function removeWorktree(projectPath, workspacePath) {
6390
6733
  async function removeAgentState(issueLower) {
6391
6734
  const step = "teardown:agent-state";
6392
6735
  const dirs = [
6393
- join24(AGENTS_DIR, `agent-${issueLower}`),
6394
- join24(AGENTS_DIR, `planning-${issueLower}`)
6736
+ join27(AGENTS_DIR, `agent-${issueLower}`),
6737
+ join27(AGENTS_DIR, `planning-${issueLower}`)
6395
6738
  ];
6396
6739
  let removed = 0;
6397
6740
  for (const dir of dirs) {
6398
- if (existsSync25(dir)) {
6741
+ if (existsSync28(dir)) {
6399
6742
  rmSync3(dir, { recursive: true, force: true });
6400
6743
  removed++;
6401
6744
  }
@@ -6438,8 +6781,8 @@ async function clearShadowState(issueId) {
6438
6781
  }
6439
6782
  async function clearLegacyPlanningDir(projectPath, issueLower) {
6440
6783
  const step = "teardown:legacy-planning-dir";
6441
- const legacyDir = join24(projectPath, ".planning", issueLower);
6442
- if (existsSync25(legacyDir)) {
6784
+ const legacyDir = join27(projectPath, ".planning", issueLower);
6785
+ if (existsSync28(legacyDir)) {
6443
6786
  rmSync3(legacyDir, { recursive: true, force: true });
6444
6787
  return stepOk(step, [`Deleted legacy planning dir: ${legacyDir}`]);
6445
6788
  }
@@ -6447,8 +6790,8 @@ async function clearLegacyPlanningDir(projectPath, issueLower) {
6447
6790
  }
6448
6791
  async function clearPlanningMarker(workspacePath) {
6449
6792
  const step = "teardown:planning-marker";
6450
- const markerPath = join24(workspacePath, ".planning", ".planning-complete");
6451
- if (existsSync25(markerPath)) {
6793
+ const markerPath = join27(workspacePath, ".planning", ".planning-complete");
6794
+ if (existsSync28(markerPath)) {
6452
6795
  unlinkSync2(markerPath);
6453
6796
  return stepOk(step, ["Cleared .planning-complete marker"]);
6454
6797
  }
@@ -6457,7 +6800,7 @@ async function clearPlanningMarker(workspacePath) {
6457
6800
  function buildPlaceholders(ctx, opts, workspacePath) {
6458
6801
  const issueLower = ctx.issueId.toLowerCase();
6459
6802
  const featureFolder = `feature-${issueLower}`;
6460
- const projName = opts.projectName || ctx.projectName || basename4(ctx.projectPath);
6803
+ const projName = opts.projectName || ctx.projectName || basename5(ctx.projectPath);
6461
6804
  const domain = opts.workspaceConfig?.dns?.domain || "localhost";
6462
6805
  return {
6463
6806
  FEATURE_NAME: issueLower,
@@ -6499,7 +6842,7 @@ async function teardownWorkspace(ctx, opts = {}) {
6499
6842
  results.push(await killTmuxSessions(issueLower));
6500
6843
  results.push(await clearShadowState(ctx.issueId));
6501
6844
  results.push(await clearLegacyPlanningDir(ctx.projectPath, issueLower));
6502
- if (workspacePath && existsSync25(workspacePath)) {
6845
+ if (workspacePath && existsSync28(workspacePath)) {
6503
6846
  if (shouldDeleteWorkspace) {
6504
6847
  results.push(await stopTldrDaemon(workspacePath));
6505
6848
  }
@@ -6538,8 +6881,8 @@ var execAsync8 = promisify8(exec8);
6538
6881
  // src/lib/lifecycle/workflows.ts
6539
6882
  init_esm_shims();
6540
6883
  init_paths();
6541
- import { existsSync as existsSync26, readFileSync as readFileSync22 } from "fs";
6542
- import { join as join25 } from "path";
6884
+ import { existsSync as existsSync29, readFileSync as readFileSync25 } from "fs";
6885
+ import { join as join28 } from "path";
6543
6886
  import { exec as exec9 } from "child_process";
6544
6887
  import { promisify as promisify9 } from "util";
6545
6888
  var execAsync9 = promisify9(exec9);
@@ -6584,20 +6927,34 @@ async function verifyBranchMerged(ctx) {
6584
6927
  const issueLower = ctx.issueId.toLowerCase();
6585
6928
  const branchName = `feature/${issueLower}`;
6586
6929
  try {
6930
+ try {
6931
+ const { loadReviewStatuses: loadReviewStatuses3 } = await import("../review-status-TDPSOU5J.js");
6932
+ const statuses = loadReviewStatuses3();
6933
+ const issueKey = ctx.issueId.toUpperCase();
6934
+ if (statuses[issueKey]?.mergeStatus === "merged") {
6935
+ return stepOk(step, ["Merge specialist confirmed merge completed"]);
6936
+ }
6937
+ } catch {
6938
+ }
6587
6939
  const { stdout: branchExists } = await execAsync9(
6588
6940
  `git branch --list "${branchName}" 2>/dev/null || true`,
6589
6941
  { cwd: ctx.projectPath, encoding: "utf-8" }
6590
6942
  );
6591
6943
  if (branchExists.trim()) {
6592
- const { stdout: unmerged } = await execAsync9(
6593
- `git log main..${branchName} --oneline 2>/dev/null || true`,
6594
- { cwd: ctx.projectPath, encoding: "utf-8" }
6595
- );
6596
- if (unmerged.trim()) {
6597
- const count = unmerged.trim().split("\n").length;
6944
+ try {
6945
+ await execAsync9(
6946
+ `git merge-base --is-ancestor ${branchName} main`,
6947
+ { cwd: ctx.projectPath, encoding: "utf-8" }
6948
+ );
6949
+ return stepOk(step, ["All commits merged to main"]);
6950
+ } catch {
6951
+ const { stdout: unmerged } = await execAsync9(
6952
+ `git log main..${branchName} --oneline 2>/dev/null || true`,
6953
+ { cwd: ctx.projectPath, encoding: "utf-8" }
6954
+ );
6955
+ const count = unmerged.trim() ? unmerged.trim().split("\n").length : 0;
6598
6956
  return stepFailed(step, `${count} unmerged commit(s) on ${branchName}. Merge before closing out.`);
6599
6957
  }
6600
- return stepOk(step, ["All commits merged to main"]);
6601
6958
  }
6602
6959
  const { stdout: remoteBranch } = await execAsync9(
6603
6960
  `git ls-remote --heads origin "${branchName}" 2>/dev/null || true`,
@@ -6606,15 +6963,20 @@ async function verifyBranchMerged(ctx) {
6606
6963
  if (remoteBranch.trim()) {
6607
6964
  await execAsync9(`git fetch origin ${branchName}`, { cwd: ctx.projectPath }).catch(() => {
6608
6965
  });
6609
- const { stdout: remoteUnmerged } = await execAsync9(
6610
- `git log main..origin/${branchName} --oneline 2>/dev/null || true`,
6611
- { cwd: ctx.projectPath, encoding: "utf-8" }
6612
- );
6613
- if (remoteUnmerged.trim()) {
6614
- const count = remoteUnmerged.trim().split("\n").length;
6966
+ try {
6967
+ await execAsync9(
6968
+ `git merge-base --is-ancestor origin/${branchName} main`,
6969
+ { cwd: ctx.projectPath, encoding: "utf-8" }
6970
+ );
6971
+ return stepOk(step, ["Remote branch fully merged"]);
6972
+ } catch {
6973
+ const { stdout: remoteUnmerged } = await execAsync9(
6974
+ `git log main..origin/${branchName} --oneline 2>/dev/null || true`,
6975
+ { cwd: ctx.projectPath, encoding: "utf-8" }
6976
+ );
6977
+ const count = remoteUnmerged.trim() ? remoteUnmerged.trim().split("\n").length : 0;
6615
6978
  return stepFailed(step, `${count} unmerged commit(s) on remote ${branchName}.`);
6616
6979
  }
6617
- return stepOk(step, ["Remote branch fully merged"]);
6618
6980
  }
6619
6981
  return stepOk(step, ["Branch already cleaned up (squash-merged)"]);
6620
6982
  } catch (err) {
@@ -6624,14 +6986,14 @@ async function verifyBranchMerged(ctx) {
6624
6986
  async function clearReviewStatusStep(issueId) {
6625
6987
  const step = "clear-review-status";
6626
6988
  try {
6627
- const { clearReviewStatus: clearReviewStatus2 } = await import("../review-status-EPFG4XM7.js");
6989
+ const { clearReviewStatus: clearReviewStatus2 } = await import("../review-status-TDPSOU5J.js");
6628
6990
  clearReviewStatus2(issueId.toUpperCase());
6629
6991
  return stepOk(step, ["Review status cleared"]);
6630
6992
  } catch {
6631
6993
  try {
6632
- const statusFile = join25(PANOPTICON_HOME, "review-status.json");
6633
- if (existsSync26(statusFile)) {
6634
- const data = JSON.parse(readFileSync22(statusFile, "utf-8"));
6994
+ const statusFile = join28(PANOPTICON_HOME, "review-status.json");
6995
+ if (existsSync29(statusFile)) {
6996
+ const data = JSON.parse(readFileSync25(statusFile, "utf-8"));
6635
6997
  const upperKey = issueId.toUpperCase();
6636
6998
  if (data[upperKey]) {
6637
6999
  delete data[upperKey];
@@ -6648,20 +7010,6 @@ async function clearReviewStatusStep(issueId) {
6648
7010
 
6649
7011
  // src/cli/commands/work/close-out.ts
6650
7012
  init_projects();
6651
- function getGitHubConfig2() {
6652
- const envFile = join26(homedir13(), ".panopticon.env");
6653
- if (!existsSync27(envFile)) return null;
6654
- const content = readFileSync23(envFile, "utf-8");
6655
- const reposMatch = content.match(/GITHUB_REPOS=(.+)/);
6656
- if (!reposMatch) return null;
6657
- const repoStr = reposMatch[1].trim();
6658
- const parts = repoStr.split(",")[0];
6659
- if (!parts) return null;
6660
- const [ownerRepo, prefix] = parts.split(":");
6661
- const [owner, repo] = ownerRepo.split("/");
6662
- if (!owner || !repo) return null;
6663
- return { owner, repo, prefix: prefix || repo.toUpperCase().replace(/-CLI$/, "").replace(/-/g, "") };
6664
- }
6665
7013
  async function closeOutCommand(issueId, options) {
6666
7014
  if (process.env.PANOPTICON_AGENT_ID) {
6667
7015
  console.error(chalk31.red("Close-out is a human-only operation. Agents cannot close out issues."));
@@ -6684,22 +7032,11 @@ async function closeOutCommand(issueId, options) {
6684
7032
  console.error(chalk31.red(`Could not resolve project for ${issueId}`));
6685
7033
  process.exit(1);
6686
7034
  }
6687
- const isGitHub = issueUpper.startsWith("PAN-");
6688
- let owner;
6689
- let repo;
6690
- let number;
6691
- if (isGitHub) {
6692
- const ghConfig = getGitHubConfig2();
6693
- if (ghConfig) {
6694
- owner = ghConfig.owner;
6695
- repo = ghConfig.repo;
6696
- number = parseInt(issueId.replace(/^PAN-/i, ""), 10);
6697
- } else {
6698
- owner = "eltmon";
6699
- repo = "panopticon-cli";
6700
- number = parseInt(issueId.replace(/^PAN-/i, ""), 10);
6701
- }
6702
- }
7035
+ const ghResolution = resolveGitHubIssue(issueId);
7036
+ const isGitHub = ghResolution.isGitHub;
7037
+ const owner = ghResolution.isGitHub ? ghResolution.owner : void 0;
7038
+ const repo = ghResolution.isGitHub ? ghResolution.repo : void 0;
7039
+ const number = ghResolution.isGitHub ? ghResolution.number : void 0;
6703
7040
  if (!options.force) {
6704
7041
  console.log(chalk31.yellow(`
6705
7042
  Close-out ceremony for ${issueUpper}
@@ -6758,13 +7095,13 @@ Running close-out for ${issueUpper}...
6758
7095
  // src/cli/commands/work/linear-states.ts
6759
7096
  init_esm_shims();
6760
7097
  import chalk32 from "chalk";
6761
- import { readFileSync as readFileSync24, existsSync as existsSync28 } from "fs";
7098
+ import { readFileSync as readFileSync26, existsSync as existsSync30 } from "fs";
6762
7099
  import { homedir as homedir14 } from "os";
6763
- import { join as join27 } from "path";
7100
+ import { join as join29 } from "path";
6764
7101
  function getLinearApiKey8() {
6765
- const envFile = join27(homedir14(), ".panopticon.env");
6766
- if (existsSync28(envFile)) {
6767
- const content = readFileSync24(envFile, "utf-8");
7102
+ const envFile = join29(homedir14(), ".panopticon.env");
7103
+ if (existsSync30(envFile)) {
7104
+ const content = readFileSync26(envFile, "utf-8");
6768
7105
  const match = content.match(/LINEAR_API_KEY=(.+)/);
6769
7106
  if (match) return match[1].trim();
6770
7107
  }
@@ -6910,7 +7247,7 @@ async function cleanupStatesCommand(options) {
6910
7247
  function registerWorkCommands(program2) {
6911
7248
  const work = program2.command("work").description("Agent and work management");
6912
7249
  work.command("issue <id>").description("Spawn agent for Linear issue").option("--model <model>", "Model to use (sonnet/opus/haiku/kimi-k2.5/etc) - defaults to Cloister config").option("--dry-run", "Show what would be created").option("--shadow", "Enable shadow mode (track status locally, don't update tracker)").option("--no-shadow", "Disable shadow mode (override config/env settings)").option("--remote", "Use remote workspace (exe.dev)").option("--local", "Use local workspace (explicit override)").option("--phase <phase>", "Work phase for model routing (exploration/implementation/documentation/review-response)").action(issueCommand);
6913
- work.command("status").description("Show all running agents").option("--json", "Output as JSON").action(statusCommand);
7250
+ work.command("status").description("Show all running agents").option("--json", "Output as JSON").option("--tldr", "Show TLDR index health across all workspaces").option("--context", "Show context window usage % for each agent").action(statusCommand);
6914
7251
  work.command("tell <id> <message>").description("Send message to running agent").action(tellCommand);
6915
7252
  work.command("kill <id>").description("Kill an agent").option("--force", "Kill without confirmation").action(killCommand);
6916
7253
  work.command("pending").description("Show completed work awaiting review").action(pendingCommand);
@@ -6953,8 +7290,8 @@ function registerWorkCommands(program2) {
6953
7290
  init_esm_shims();
6954
7291
  import chalk33 from "chalk";
6955
7292
  import ora16 from "ora";
6956
- import { existsSync as existsSync29, writeFileSync as writeFileSync10, rmSync as rmSync4, readFileSync as readFileSync25, realpathSync } from "fs";
6957
- import { join as join28, basename as basename5 } from "path";
7293
+ import { existsSync as existsSync31, writeFileSync as writeFileSync10, rmSync as rmSync4, readFileSync as readFileSync27, realpathSync } from "fs";
7294
+ import { join as join30, basename as basename6 } from "path";
6958
7295
 
6959
7296
  // src/lib/worktree.ts
6960
7297
  init_esm_shims();
@@ -6999,6 +7336,10 @@ function createWorktree(repoPath, targetPath, branchName) {
6999
7336
  stdio: "pipe"
7000
7337
  });
7001
7338
  }
7339
+ try {
7340
+ execSync3("git config beads.role agent", { cwd: targetPath, stdio: "pipe" });
7341
+ } catch {
7342
+ }
7002
7343
  }
7003
7344
  function removeWorktree2(repoPath, worktreePath) {
7004
7345
  execSync3(`git worktree remove "${worktreePath}" --force`, {
@@ -7052,8 +7393,8 @@ async function initializeWorkspaceBeads(workspacePath, issueId) {
7052
7393
  const match = stdout.match(/([a-z]+-[a-z0-9]+)/);
7053
7394
  return { success: true, beadId: match?.[1] };
7054
7395
  } else {
7055
- const beadsDir = join28(workspacePath, ".beads");
7056
- if (existsSync29(beadsDir)) {
7396
+ const beadsDir = join30(workspacePath, ".beads");
7397
+ if (existsSync31(beadsDir)) {
7057
7398
  rmSync4(beadsDir, { recursive: true, force: true });
7058
7399
  }
7059
7400
  const prefix = "workspace";
@@ -7146,7 +7487,7 @@ async function createCommand(issueId, options) {
7146
7487
  if (projectConfig.workspace.services && projectConfig.workspace.services.length > 0) {
7147
7488
  console.log("");
7148
7489
  console.log(chalk33.bold("To start services:"));
7149
- const composeProject = `${basename5(projectConfig.path)}-${folderName}`;
7490
+ const composeProject = `${basename6(projectConfig.path)}-${folderName}`;
7150
7491
  for (const service of projectConfig.workspace.services) {
7151
7492
  const containerName = `${composeProject}-${service.name}-1`;
7152
7493
  const cmd = service.docker_command || service.start_command;
@@ -7218,8 +7559,8 @@ async function createCommand(issueId, options) {
7218
7559
  projectRoot = process.cwd();
7219
7560
  }
7220
7561
  }
7221
- const workspacesDir = join28(projectRoot, "workspaces");
7222
- const workspacePath = join28(workspacesDir, folderName);
7562
+ const workspacesDir = join30(projectRoot, "workspaces");
7563
+ const workspacePath = join30(workspacesDir, folderName);
7223
7564
  if (options.dryRun) {
7224
7565
  spinner.info("Dry run mode");
7225
7566
  console.log("");
@@ -7232,11 +7573,11 @@ async function createCommand(issueId, options) {
7232
7573
  console.log(` Branch: ${chalk33.cyan(branchName)}`);
7233
7574
  return;
7234
7575
  }
7235
- if (existsSync29(workspacePath)) {
7576
+ if (existsSync31(workspacePath)) {
7236
7577
  spinner.fail(`Workspace already exists: ${workspacePath}`);
7237
7578
  process.exit(1);
7238
7579
  }
7239
- if (!existsSync29(join28(projectRoot, ".git"))) {
7580
+ if (!existsSync31(join30(projectRoot, ".git"))) {
7240
7581
  spinner.fail("Not a git repository. Run this from the project root.");
7241
7582
  process.exit(1);
7242
7583
  }
@@ -7260,7 +7601,7 @@ async function createCommand(issueId, options) {
7260
7601
  BEAD_ID: workspaceBeadId
7261
7602
  };
7262
7603
  const claudeMd = generateClaudeMd(projectRoot, variables);
7263
- writeFileSync10(join28(workspacePath, "CLAUDE.md"), claudeMd);
7604
+ writeFileSync10(join30(workspacePath, "CLAUDE.md"), claudeMd);
7264
7605
  let skillsResult = { added: [], updated: [], skipped: [], overlayed: [] };
7265
7606
  if (options.skills !== false) {
7266
7607
  spinner.text = "Merging skills and agents...";
@@ -7270,19 +7611,19 @@ async function createCommand(issueId, options) {
7270
7611
  let dockerError;
7271
7612
  if (options.docker) {
7272
7613
  const composeLocations = [
7273
- join28(workspacePath, "docker-compose.yml"),
7274
- join28(workspacePath, "docker-compose.yaml"),
7275
- join28(workspacePath, ".devcontainer", "docker-compose.yml"),
7276
- join28(workspacePath, ".devcontainer", "docker-compose.yaml"),
7277
- join28(workspacePath, ".devcontainer", "docker-compose.devcontainer.yml"),
7278
- join28(workspacePath, ".devcontainer", "compose.yml"),
7279
- join28(workspacePath, ".devcontainer", "compose.yaml")
7614
+ join30(workspacePath, "docker-compose.yml"),
7615
+ join30(workspacePath, "docker-compose.yaml"),
7616
+ join30(workspacePath, ".devcontainer", "docker-compose.yml"),
7617
+ join30(workspacePath, ".devcontainer", "docker-compose.yaml"),
7618
+ join30(workspacePath, ".devcontainer", "docker-compose.devcontainer.yml"),
7619
+ join30(workspacePath, ".devcontainer", "compose.yml"),
7620
+ join30(workspacePath, ".devcontainer", "compose.yaml")
7280
7621
  ];
7281
- const composeFile = composeLocations.find((f) => existsSync29(f));
7622
+ const composeFile = composeLocations.find((f) => existsSync31(f));
7282
7623
  if (composeFile) {
7283
7624
  spinner.text = "Starting Docker containers...";
7284
7625
  try {
7285
- const composeDir = join28(composeFile, "..");
7626
+ const composeDir = join30(composeFile, "..");
7286
7627
  await execAsync10(`docker compose -f "${composeFile}" up -d --build`, {
7287
7628
  cwd: composeDir,
7288
7629
  encoding: "utf-8",
@@ -7349,15 +7690,15 @@ async function listCommand2(options) {
7349
7690
  const workspaces2 = [];
7350
7691
  if (isPolyrepo && config2.workspace?.repos) {
7351
7692
  for (const repo of config2.workspace.repos) {
7352
- const repoPath = join28(config2.path, repo.path);
7353
- if (!existsSync29(join28(repoPath, ".git"))) continue;
7693
+ const repoPath = join30(config2.path, repo.path);
7694
+ if (!existsSync31(join30(repoPath, ".git"))) continue;
7354
7695
  const repoWorktrees = listWorktrees(repoPath);
7355
7696
  for (const wt of repoWorktrees) {
7356
7697
  if (wt.path.includes("/workspaces/") || wt.path.includes("\\workspaces\\")) {
7357
7698
  const parts = wt.path.split("/workspaces/");
7358
7699
  if (parts.length > 1) {
7359
7700
  const workspaceDir = parts[1].split("/")[0];
7360
- const canonicalPath = join28(config2.path, "workspaces", workspaceDir);
7701
+ const canonicalPath = join30(config2.path, "workspaces", workspaceDir);
7361
7702
  if (!workspaces2.some((w) => w.path === canonicalPath)) {
7362
7703
  workspaces2.push({ ...wt, path: canonicalPath });
7363
7704
  }
@@ -7366,7 +7707,7 @@ async function listCommand2(options) {
7366
7707
  }
7367
7708
  }
7368
7709
  } else {
7369
- if (!existsSync29(join28(config2.path, ".git"))) continue;
7710
+ if (!existsSync31(join30(config2.path, ".git"))) continue;
7370
7711
  const worktrees2 = listWorktrees(config2.path);
7371
7712
  for (const wt of worktrees2) {
7372
7713
  if (wt.path.includes("/workspaces/") || wt.path.includes("\\workspaces\\")) {
@@ -7396,7 +7737,7 @@ async function listCommand2(options) {
7396
7737
  ${proj.projectName}
7397
7738
  `));
7398
7739
  for (const ws of proj.workspaces) {
7399
- const name = basename5(ws.path);
7740
+ const name = basename6(ws.path);
7400
7741
  const status = ws.prunable ? chalk33.yellow(" (prunable)") : "";
7401
7742
  console.log(` ${chalk33.cyan(name)}${status}`);
7402
7743
  console.log(` Branch: ${ws.branch || chalk33.dim("(detached)")}`);
@@ -7406,7 +7747,7 @@ ${proj.projectName}
7406
7747
  return;
7407
7748
  }
7408
7749
  const projectRoot = process.cwd();
7409
- if (!existsSync29(join28(projectRoot, ".git"))) {
7750
+ if (!existsSync31(join30(projectRoot, ".git"))) {
7410
7751
  console.error(chalk33.red("Not a git repository."));
7411
7752
  if (projects.length > 0) {
7412
7753
  console.log(chalk33.dim("Tip: Use --all to list workspaces across all registered projects."));
@@ -7431,7 +7772,7 @@ ${proj.projectName}
7431
7772
  }
7432
7773
  console.log(chalk33.bold("\nWorkspaces\n"));
7433
7774
  for (const ws of workspaces) {
7434
- const name = basename5(ws.path);
7775
+ const name = basename6(ws.path);
7435
7776
  const status = ws.prunable ? chalk33.yellow(" (prunable)") : "";
7436
7777
  console.log(`${chalk33.cyan(name)}${status}`);
7437
7778
  console.log(` Branch: ${ws.branch || chalk33.dim("(detached)")}`);
@@ -7502,17 +7843,17 @@ async function destroyCommand(issueId, options) {
7502
7843
  projectRoot = process.cwd();
7503
7844
  }
7504
7845
  }
7505
- const workspacePath = join28(projectRoot, "workspaces", folderName);
7506
- if (!existsSync29(workspacePath)) {
7507
- const cwdPath = join28(process.cwd(), "workspaces", folderName);
7508
- if (projectRoot !== process.cwd() && existsSync29(cwdPath)) {
7846
+ const workspacePath = join30(projectRoot, "workspaces", folderName);
7847
+ if (!existsSync31(workspacePath)) {
7848
+ const cwdPath = join30(process.cwd(), "workspaces", folderName);
7849
+ if (projectRoot !== process.cwd() && existsSync31(cwdPath)) {
7509
7850
  projectRoot = process.cwd();
7510
7851
  } else {
7511
7852
  spinner.fail(`Workspace not found: ${workspacePath}`);
7512
7853
  process.exit(1);
7513
7854
  }
7514
7855
  }
7515
- const finalWorkspacePath = join28(projectRoot, "workspaces", folderName);
7856
+ const finalWorkspacePath = join30(projectRoot, "workspaces", folderName);
7516
7857
  spinner.text = "Removing git worktree...";
7517
7858
  removeWorktree2(projectRoot, finalWorkspacePath);
7518
7859
  spinner.succeed(`Workspace destroyed: ${folderName}`);
@@ -7598,13 +7939,13 @@ async function createRemoteWorkspace(issueId, normalizedId, branchName, spinner,
7598
7939
  await exe.ssh(vmName, `ssh-keyscan -t ed25519,rsa ${gitHost} >> ~/.ssh/known_hosts 2>/dev/null`);
7599
7940
  }
7600
7941
  const sshKeyPaths = [
7601
- join28(homedir15(), ".panopticon", "ssh", "exe-dev-key"),
7602
- join28(homedir15(), ".ssh", "id_ed25519"),
7603
- join28(homedir15(), ".ssh", "id_rsa")
7942
+ join30(homedir15(), ".panopticon", "ssh", "exe-dev-key"),
7943
+ join30(homedir15(), ".ssh", "id_ed25519"),
7944
+ join30(homedir15(), ".ssh", "id_rsa")
7604
7945
  ];
7605
- const sshKeyPath = sshKeyPaths.find((p) => existsSync29(p));
7946
+ const sshKeyPath = sshKeyPaths.find((p) => existsSync31(p));
7606
7947
  if (sshKeyPath) {
7607
- const sshKeyBase64 = Buffer.from(readFileSync25(sshKeyPath, "utf-8")).toString("base64");
7948
+ const sshKeyBase64 = Buffer.from(readFileSync27(sshKeyPath, "utf-8")).toString("base64");
7608
7949
  const keyFilename = sshKeyPath.includes("id_rsa") ? "id_rsa" : "id_ed25519";
7609
7950
  await exe.ssh(vmName, `echo '${sshKeyBase64}' | base64 -d > ~/.ssh/${keyFilename} && chmod 600 ~/.ssh/${keyFilename}`);
7610
7951
  }
@@ -7620,8 +7961,8 @@ async function createRemoteWorkspace(issueId, normalizedId, branchName, spinner,
7620
7961
  await exe.ssh(vmName, "mkdir -p ~/workspace");
7621
7962
  for (const repo of projectConfig.workspace.repos) {
7622
7963
  spinner.text = `Cloning ${repo.name}...`;
7623
- const rawRepoPath = join28(projectRoot, repo.path);
7624
- const actualRepoPath = existsSync29(rawRepoPath) ? realpathSync(rawRepoPath) : rawRepoPath;
7964
+ const rawRepoPath = join30(projectRoot, repo.path);
7965
+ const actualRepoPath = existsSync31(rawRepoPath) ? realpathSync(rawRepoPath) : rawRepoPath;
7625
7966
  let repoRemoteUrl;
7626
7967
  try {
7627
7968
  const { stdout } = await execAsync10("git remote get-url origin", {
@@ -7882,8 +8223,8 @@ async function sshCommand(issueId) {
7882
8223
  console.log(chalk33.dim("Create one with: pan workspace create --remote " + issueId));
7883
8224
  process.exit(1);
7884
8225
  }
7885
- const { spawn } = await import("child_process");
7886
- const child = spawn("exe", ["ssh", metadata.vmName], {
8226
+ const { spawn: spawn2 } = await import("child_process");
8227
+ const child = spawn2("exe", ["ssh", metadata.vmName], {
7887
8228
  stdio: "inherit"
7888
8229
  });
7889
8230
  child.on("exit", (code) => {
@@ -7954,8 +8295,8 @@ async function destroyRemoteWorkspace(issueId, normalizedId, metadata, spinner,
7954
8295
  } catch {
7955
8296
  }
7956
8297
  }
7957
- const metadataFile = join28(WORKSPACES_DIR, `${normalizedId}.yaml`);
7958
- if (existsSync29(metadataFile)) {
8298
+ const metadataFile = join30(WORKSPACES_DIR, `${normalizedId}.yaml`);
8299
+ if (existsSync31(metadataFile)) {
7959
8300
  rmSync4(metadataFile);
7960
8301
  }
7961
8302
  spinner.succeed(`Remote workspace ${issueId} destroyed`);
@@ -7981,9 +8322,9 @@ async function updateCommand(issueId, options) {
7981
8322
  process.exit(1);
7982
8323
  }
7983
8324
  const workspaceConfig = projectConfig.workspace;
7984
- const workspacesDir = join28(projectConfig.path, workspaceConfig?.workspaces_dir || "workspaces");
7985
- const workspacePath = join28(workspacesDir, folderName);
7986
- if (!existsSync29(workspacePath)) {
8325
+ const workspacesDir = join30(projectConfig.path, workspaceConfig?.workspaces_dir || "workspaces");
8326
+ const workspacePath = join30(workspacesDir, folderName);
8327
+ if (!existsSync31(workspacePath)) {
7987
8328
  spinner.fail(`Workspace not found: ${workspacePath}`);
7988
8329
  process.exit(1);
7989
8330
  }
@@ -8003,7 +8344,7 @@ async function updateCommand(issueId, options) {
8003
8344
  const result = mergeSkillsIntoWorkspace(workspacePath);
8004
8345
  if (workspaceConfig?.agent?.template_dir && (workspaceConfig.agent.copy_dirs || workspaceConfig.agent.symlinks)) {
8005
8346
  spinner.text = "Applying project template overlay...";
8006
- const templateDir = join28(projectConfig.path, workspaceConfig.agent.template_dir);
8347
+ const templateDir = join30(projectConfig.path, workspaceConfig.agent.template_dir);
8007
8348
  const overlayed = applyProjectTemplateOverlay(workspacePath, templateDir);
8008
8349
  result.overlayed = overlayed;
8009
8350
  }
@@ -8035,8 +8376,8 @@ import ora17 from "ora";
8035
8376
  // src/lib/test-runner.ts
8036
8377
  init_esm_shims();
8037
8378
  init_workspace_config();
8038
- import { existsSync as existsSync30, mkdirSync as mkdirSync13, writeFileSync as writeFileSync11 } from "fs";
8039
- import { join as join29, basename as basename6 } from "path";
8379
+ import { existsSync as existsSync32, mkdirSync as mkdirSync13, writeFileSync as writeFileSync11 } from "fs";
8380
+ import { join as join31, basename as basename7 } from "path";
8040
8381
  import { exec as exec11 } from "child_process";
8041
8382
  import { promisify as promisify11 } from "util";
8042
8383
  import { homedir as homedir16 } from "os";
@@ -8090,8 +8431,8 @@ function parseTestOutput(output, type) {
8090
8431
  return { passed, failed };
8091
8432
  }
8092
8433
  async function runTestSuite(testName, testConfig, workspacePath, placeholders, reportsDir, timestamp) {
8093
- const testPath = join29(workspacePath, testConfig.path);
8094
- const logFile = join29(reportsDir, `${testName}-${timestamp}.log`);
8434
+ const testPath = join31(workspacePath, testConfig.path);
8435
+ const logFile = join31(reportsDir, `${testName}-${timestamp}.log`);
8095
8436
  const result = {
8096
8437
  name: testName,
8097
8438
  status: "pending",
@@ -8183,8 +8524,8 @@ function generateReport(result) {
8183
8524
  async function sendNotification(result) {
8184
8525
  const title = `Tests (${result.target}): ${result.overallStatus === "passed" ? "\u2705 All Passed" : "\u274C Failed"}`;
8185
8526
  const message = result.overallStatus === "passed" ? "All test suites passed" : `${result.totalFailures} suite(s) failed. Check report: ${result.reportFile}`;
8186
- const notifyScript = join29(homedir16(), ".panopticon", "bin", "notify-complete");
8187
- if (existsSync30(notifyScript)) {
8527
+ const notifyScript = join31(homedir16(), ".panopticon", "bin", "notify-complete");
8528
+ if (existsSync32(notifyScript)) {
8188
8529
  try {
8189
8530
  await execAsync11(`"${notifyScript}" "${result.target}" "${message}"`);
8190
8531
  } catch {
@@ -8202,12 +8543,12 @@ async function runTests(options) {
8202
8543
  let target;
8203
8544
  let baseUrl;
8204
8545
  if (featureName) {
8205
- const workspacesDir = join29(projectConfig.path, workspaceConfig?.workspaces_dir || "workspaces");
8546
+ const workspacesDir = join31(projectConfig.path, workspaceConfig?.workspaces_dir || "workspaces");
8206
8547
  const featureFolder2 = `feature-${featureName}`;
8207
- workspacePath = join29(workspacesDir, featureFolder2);
8548
+ workspacePath = join31(workspacesDir, featureFolder2);
8208
8549
  target = featureFolder2;
8209
8550
  baseUrl = workspaceConfig?.dns?.domain ? `https://${featureFolder2}.${workspaceConfig.dns.domain}` : `http://localhost:3000`;
8210
- if (!existsSync30(workspacePath)) {
8551
+ if (!existsSync32(workspacePath)) {
8211
8552
  throw new Error(`Workspace not found: ${workspacePath}`);
8212
8553
  }
8213
8554
  } else {
@@ -8220,16 +8561,16 @@ async function runTests(options) {
8220
8561
  FEATURE_NAME: featureName || "main",
8221
8562
  FEATURE_FOLDER: featureFolder,
8222
8563
  BRANCH_NAME: featureName ? `feature/${featureName}` : "main",
8223
- COMPOSE_PROJECT: `${basename6(projectConfig.path)}-${featureFolder}`,
8564
+ COMPOSE_PROJECT: `${basename7(projectConfig.path)}-${featureFolder}`,
8224
8565
  DOMAIN: workspaceConfig?.dns?.domain || "localhost",
8225
- PROJECT_NAME: basename6(projectConfig.path),
8566
+ PROJECT_NAME: basename7(projectConfig.path),
8226
8567
  PROJECT_PATH: projectConfig.path,
8227
8568
  WORKSPACE_PATH: workspacePath
8228
8569
  };
8229
- const reportsDir = join29(projectConfig.path, "reports");
8570
+ const reportsDir = join31(projectConfig.path, "reports");
8230
8571
  mkdirSync13(reportsDir, { recursive: true });
8231
8572
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
8232
- const reportFile = join29(reportsDir, `test-run-${target}-${timestamp}.md`);
8573
+ const reportFile = join31(reportsDir, `test-run-${target}-${timestamp}.md`);
8233
8574
  const result = {
8234
8575
  target,
8235
8576
  baseUrl,
@@ -8393,22 +8734,22 @@ import chalk35 from "chalk";
8393
8734
  import ora18 from "ora";
8394
8735
  import inquirer5 from "inquirer";
8395
8736
  import { execSync as execSync4 } from "child_process";
8396
- import { existsSync as existsSync31, mkdirSync as mkdirSync14, writeFileSync as writeFileSync12, readFileSync as readFileSync26, copyFileSync, readdirSync as readdirSync14, statSync as statSync5 } from "fs";
8397
- import { join as join30 } from "path";
8737
+ import { existsSync as existsSync33, mkdirSync as mkdirSync14, writeFileSync as writeFileSync12, readFileSync as readFileSync28, copyFileSync, readdirSync as readdirSync15, statSync as statSync6 } from "fs";
8738
+ import { join as join32 } from "path";
8398
8739
  import { homedir as homedir17 } from "os";
8399
8740
  function registerInstallCommand(program2) {
8400
- program2.command("install").description("Install Panopticon prerequisites").option("--check", "Check prerequisites only").option("--minimal", "Skip Traefik and mkcert (use port-based routing)").option("--skip-mkcert", "Skip mkcert/HTTPS setup").option("--skip-docker", "Skip Docker network setup").option("--skip-beads", "Skip beads CLI installation").option("--skip-router", "Skip claude-code-router installation").action(installCommand);
8741
+ program2.command("install").description("Install Panopticon prerequisites").option("--check", "Check prerequisites only").option("--minimal", "Skip Traefik and mkcert (use port-based routing)").option("--skip-mkcert", "Skip mkcert/HTTPS setup").option("--skip-docker", "Skip Docker network setup").option("--skip-beads", "Skip beads CLI installation").option("--skip-router", "Skip claude-code-router installation").option("--skip-sageox", "Skip SageOx CLI installation").action(installCommand);
8401
8742
  }
8402
8743
  function copyDirectoryRecursive(source, dest) {
8403
- if (!existsSync31(source)) {
8744
+ if (!existsSync33(source)) {
8404
8745
  throw new Error(`Source directory not found: ${source}`);
8405
8746
  }
8406
8747
  mkdirSync14(dest, { recursive: true });
8407
- const entries = readdirSync14(source);
8748
+ const entries = readdirSync15(source);
8408
8749
  for (const entry of entries) {
8409
- const sourcePath = join30(source, entry);
8410
- const destPath = join30(dest, entry);
8411
- const stat = statSync5(sourcePath);
8750
+ const sourcePath = join32(source, entry);
8751
+ const destPath = join32(dest, entry);
8752
+ const stat = statSync6(sourcePath);
8412
8753
  if (stat.isDirectory()) {
8413
8754
  copyDirectoryRecursive(sourcePath, destPath);
8414
8755
  } else {
@@ -8493,6 +8834,13 @@ function checkPrerequisites() {
8493
8834
  message: hasRouter ? "installed" : "not found (will auto-install)",
8494
8835
  fix: "npm install -g @musistudio/claude-code-router"
8495
8836
  });
8837
+ const hasOx = checkCommand2("ox");
8838
+ results.push({
8839
+ name: "SageOx CLI (ox)",
8840
+ passed: hasOx,
8841
+ message: hasOx ? "installed" : "not found (will auto-install)",
8842
+ fix: "curl -sL https://github.com/eltmon/ox/releases/download/latest/ox-linux-amd64 -o ~/.local/bin/ox && chmod +x ~/.local/bin/ox"
8843
+ });
8496
8844
  const hasJq = checkCommand2("jq");
8497
8845
  results.push({
8498
8846
  name: "jq",
@@ -8500,7 +8848,7 @@ function checkPrerequisites() {
8500
8848
  message: hasJq ? "installed" : "not found",
8501
8849
  fix: "apt install jq / brew install jq"
8502
8850
  });
8503
- const hasTtyd = checkCommand2("ttyd") || existsSync31(join30(homedir17(), "bin", "ttyd"));
8851
+ const hasTtyd = checkCommand2("ttyd") || existsSync33(join32(homedir17(), "bin", "ttyd"));
8504
8852
  results.push({
8505
8853
  name: "ttyd",
8506
8854
  passed: hasTtyd,
@@ -8510,7 +8858,7 @@ function checkPrerequisites() {
8510
8858
  return {
8511
8859
  results,
8512
8860
  // mkcert, ttyd, beads, and claude-code-router are optional (will be auto-installed or skipped)
8513
- allPassed: results.filter((r) => r.name !== "mkcert" && r.name !== "ttyd" && r.name !== "Beads CLI (bd)" && r.name !== "claude-code-router").every((r) => r.passed)
8861
+ allPassed: results.filter((r) => r.name !== "mkcert" && r.name !== "ttyd" && r.name !== "Beads CLI (bd)" && r.name !== "claude-code-router" && r.name !== "SageOx CLI (ox)").every((r) => r.passed)
8514
8862
  };
8515
8863
  }
8516
8864
  function printPrereqStatus(prereqs) {
@@ -8576,9 +8924,9 @@ async function installCommand(options) {
8576
8924
  execSync4("brew install mkcert", { stdio: "pipe", timeout: 12e4 });
8577
8925
  spinner.succeed("mkcert installed via Homebrew");
8578
8926
  } else {
8579
- const binDir = join30(homedir17(), ".local", "bin");
8927
+ const binDir = join32(homedir17(), ".local", "bin");
8580
8928
  mkdirSync14(binDir, { recursive: true });
8581
- const mkcertPath = join30(binDir, "mkcert");
8929
+ const mkcertPath = join32(binDir, "mkcert");
8582
8930
  const arch = process.arch === "x64" ? "amd64" : process.arch;
8583
8931
  execSync4(`curl -sL "https://github.com/FiloSottile/mkcert/releases/latest/download/mkcert-v1.4.4-linux-${arch}" -o "${mkcertPath}" && chmod +x "${mkcertPath}"`, {
8584
8932
  stdio: "pipe",
@@ -8597,14 +8945,14 @@ async function installCommand(options) {
8597
8945
  execSync4("mkcert -install", { stdio: "pipe" });
8598
8946
  spinner.succeed("mkcert CA installed");
8599
8947
  spinner.start("Generating wildcard certificates...");
8600
- const traefikCertFile = join30(TRAEFIK_CERTS_DIR, "_wildcard.pan.localhost.pem");
8601
- const traefikKeyFile = join30(TRAEFIK_CERTS_DIR, "_wildcard.pan.localhost-key.pem");
8948
+ const traefikCertFile = join32(TRAEFIK_CERTS_DIR, "_wildcard.pan.localhost.pem");
8949
+ const traefikKeyFile = join32(TRAEFIK_CERTS_DIR, "_wildcard.pan.localhost-key.pem");
8602
8950
  execSync4(
8603
8951
  `mkcert -cert-file "${traefikCertFile}" -key-file "${traefikKeyFile}" "pan.localhost" "*.pan.localhost" "*.localhost" localhost 127.0.0.1 ::1`,
8604
8952
  { stdio: "pipe" }
8605
8953
  );
8606
- const legacyCertFile = join30(CERTS_DIR, "localhost.pem");
8607
- const legacyKeyFile = join30(CERTS_DIR, "localhost-key.pem");
8954
+ const legacyCertFile = join32(CERTS_DIR, "localhost.pem");
8955
+ const legacyKeyFile = join32(CERTS_DIR, "localhost-key.pem");
8608
8956
  copyFileSync(traefikCertFile, legacyCertFile);
8609
8957
  copyFileSync(traefikKeyFile, legacyKeyFile);
8610
8958
  spinner.succeed("Wildcard certificates generated (*.pan.localhost, *.localhost)");
@@ -8622,13 +8970,13 @@ async function installCommand(options) {
8622
8970
  spinner.info("Skipping mkcert (not installed)");
8623
8971
  }
8624
8972
  }
8625
- const hasTtyd = checkCommand2("ttyd") || existsSync31(join30(homedir17(), "bin", "ttyd"));
8973
+ const hasTtyd = checkCommand2("ttyd") || existsSync33(join32(homedir17(), "bin", "ttyd"));
8626
8974
  if (!hasTtyd) {
8627
8975
  spinner.start("Installing ttyd (web terminal)...");
8628
8976
  try {
8629
- const binDir = join30(homedir17(), "bin");
8977
+ const binDir = join32(homedir17(), "bin");
8630
8978
  mkdirSync14(binDir, { recursive: true });
8631
- const ttydPath = join30(binDir, "ttyd");
8979
+ const ttydPath = join32(binDir, "ttyd");
8632
8980
  const plat2 = detectPlatform();
8633
8981
  let downloadUrl = "";
8634
8982
  if (plat2 === "darwin") {
@@ -8733,10 +9081,35 @@ async function installCommand(options) {
8733
9081
  spinner.info("claude-code-router already installed");
8734
9082
  }
8735
9083
  }
9084
+ if (options.skipSageox) {
9085
+ spinner.info("Skipping SageOx installation (--skip-sageox)");
9086
+ } else {
9087
+ const hasOxNow = checkCommand2("ox");
9088
+ if (!hasOxNow) {
9089
+ spinner.start("Installing SageOx CLI (ox)...");
9090
+ try {
9091
+ const binDir = join32(homedir17(), ".local", "bin");
9092
+ mkdirSync14(binDir, { recursive: true });
9093
+ const oxPath = join32(binDir, "ox");
9094
+ const arch = process.arch === "x64" ? "amd64" : process.arch;
9095
+ const plat2 = detectPlatform();
9096
+ const platform2 = plat2 === "darwin" ? "darwin" : "linux";
9097
+ execSync4(`curl -sL "https://github.com/eltmon/ox/releases/download/latest/ox-${platform2}-${arch}" -o "${oxPath}" && chmod +x "${oxPath}"`, {
9098
+ stdio: "pipe",
9099
+ timeout: 6e4
9100
+ });
9101
+ spinner.succeed(`SageOx CLI installed to ${oxPath}`);
9102
+ } catch {
9103
+ spinner.warn("SageOx installation failed - install manually from https://github.com/eltmon/ox/releases");
9104
+ }
9105
+ } else {
9106
+ spinner.info("SageOx CLI already installed");
9107
+ }
9108
+ }
8736
9109
  if (!options.minimal) {
8737
9110
  spinner.start("Setting up Traefik configuration...");
8738
9111
  try {
8739
- if (!existsSync31(join30(TRAEFIK_DIR, "docker-compose.yml"))) {
9112
+ if (!existsSync33(join32(TRAEFIK_DIR, "docker-compose.yml"))) {
8740
9113
  copyDirectoryRecursive(SOURCE_TRAEFIK_TEMPLATES, TRAEFIK_DIR);
8741
9114
  cleanupTemplateFiles();
8742
9115
  spinner.succeed("Traefik configuration created from templates");
@@ -8749,9 +9122,9 @@ async function installCommand(options) {
8749
9122
  if (generateTlsConfig()) {
8750
9123
  spinner.succeed("TLS config generated (tls.yml)");
8751
9124
  }
8752
- const existingCompose = join30(TRAEFIK_DIR, "docker-compose.yml");
8753
- if (existsSync31(existingCompose)) {
8754
- const content = readFileSync26(existingCompose, "utf-8");
9125
+ const existingCompose = join32(TRAEFIK_DIR, "docker-compose.yml");
9126
+ if (existsSync33(existingCompose)) {
9127
+ const content = readFileSync28(existingCompose, "utf-8");
8755
9128
  if (content.includes("panopticon:") && !content.includes("external: true")) {
8756
9129
  const patched = content.replace(
8757
9130
  /networks:\s*\n\s*panopticon:\s*\n\s*name: panopticon\s*\n\s*driver: bridge/,
@@ -8766,8 +9139,8 @@ async function installCommand(options) {
8766
9139
  console.log(chalk35.yellow("You can set up Traefik manually later"));
8767
9140
  }
8768
9141
  }
8769
- const configFile = join30(PANOPTICON_HOME, "config.toml");
8770
- const configExists = existsSync31(configFile);
9142
+ const configFile = join32(PANOPTICON_HOME, "config.toml");
9143
+ const configExists = existsSync33(configFile);
8771
9144
  if (!configExists) {
8772
9145
  spinner.start("Creating default config...");
8773
9146
  } else {
@@ -8979,13 +9352,13 @@ function getHealthLabel(state) {
8979
9352
  init_esm_shims();
8980
9353
  init_paths();
8981
9354
  import Database from "better-sqlite3";
8982
- import { join as join31 } from "path";
8983
- import { existsSync as existsSync32, mkdirSync as mkdirSync15 } from "fs";
8984
- var CLOISTER_DB_PATH = join31(PANOPTICON_HOME, "cloister.db");
9355
+ import { join as join33 } from "path";
9356
+ import { existsSync as existsSync34, mkdirSync as mkdirSync15 } from "fs";
9357
+ var CLOISTER_DB_PATH = join33(PANOPTICON_HOME, "cloister.db");
8985
9358
  var RETENTION_DAYS = 7;
8986
9359
  var db = null;
8987
9360
  function initHealthDatabase() {
8988
- if (!existsSync32(PANOPTICON_HOME)) {
9361
+ if (!existsSync34(PANOPTICON_HOME)) {
8989
9362
  mkdirSync15(PANOPTICON_HOME, { recursive: true });
8990
9363
  }
8991
9364
  db = new Database(CLOISTER_DB_PATH);
@@ -9064,10 +9437,10 @@ init_esm_shims();
9064
9437
  init_agents();
9065
9438
  init_tmux();
9066
9439
  init_jsonl_parser();
9067
- import { existsSync as existsSync33, readFileSync as readFileSync27, statSync as statSync6, mkdirSync as mkdirSync16, writeFileSync as writeFileSync13 } from "fs";
9068
- import { join as join32 } from "path";
9440
+ import { existsSync as existsSync35, readFileSync as readFileSync29, statSync as statSync7, mkdirSync as mkdirSync16, writeFileSync as writeFileSync13 } from "fs";
9441
+ import { join as join34 } from "path";
9069
9442
  import { homedir as homedir18 } from "os";
9070
- var CLAUDE_PROJECTS_DIR = join32(homedir18(), ".claude", "projects");
9443
+ var CLAUDE_PROJECTS_DIR = join34(homedir18(), ".claude", "projects");
9071
9444
  var ClaudeCodeRuntime = class {
9072
9445
  name = "claude-code";
9073
9446
  /**
@@ -9077,15 +9450,15 @@ var ClaudeCodeRuntime = class {
9077
9450
  * We need to find the project directory that contains sessions for this workspace.
9078
9451
  */
9079
9452
  getProjectDirForWorkspace(workspace) {
9080
- if (!existsSync33(CLAUDE_PROJECTS_DIR)) {
9453
+ if (!existsSync35(CLAUDE_PROJECTS_DIR)) {
9081
9454
  return null;
9082
9455
  }
9083
9456
  const projectDirs = getProjectDirs();
9084
9457
  for (const projectDir of projectDirs) {
9085
- const indexPath = join32(projectDir, "sessions-index.json");
9086
- if (existsSync33(indexPath)) {
9458
+ const indexPath = join34(projectDir, "sessions-index.json");
9459
+ if (existsSync35(indexPath)) {
9087
9460
  try {
9088
- const indexContent = readFileSync27(indexPath, "utf-8");
9461
+ const indexContent = readFileSync29(indexPath, "utf-8");
9089
9462
  if (indexContent.includes(workspace)) {
9090
9463
  return projectDir;
9091
9464
  }
@@ -9099,12 +9472,12 @@ var ClaudeCodeRuntime = class {
9099
9472
  * Get the active session ID for an agent from the sessions index
9100
9473
  */
9101
9474
  getActiveSessionId(projectDir) {
9102
- const indexPath = join32(projectDir, "sessions-index.json");
9103
- if (!existsSync33(indexPath)) {
9475
+ const indexPath = join34(projectDir, "sessions-index.json");
9476
+ if (!existsSync35(indexPath)) {
9104
9477
  return null;
9105
9478
  }
9106
9479
  try {
9107
- const indexContent = readFileSync27(indexPath, "utf-8");
9480
+ const indexContent = readFileSync29(indexPath, "utf-8");
9108
9481
  const index = JSON.parse(indexContent);
9109
9482
  if (index.sessions && Array.isArray(index.sessions)) {
9110
9483
  const sessions = index.sessions;
@@ -9139,8 +9512,8 @@ var ClaudeCodeRuntime = class {
9139
9512
  }
9140
9513
  const sessionId = this.getActiveSessionId(projectDir);
9141
9514
  if (sessionId) {
9142
- const sessionPath = join32(projectDir, `${sessionId}.jsonl`);
9143
- if (existsSync33(sessionPath)) {
9515
+ const sessionPath = join34(projectDir, `${sessionId}.jsonl`);
9516
+ if (existsSync35(sessionPath)) {
9144
9517
  return sessionPath;
9145
9518
  }
9146
9519
  }
@@ -9153,11 +9526,11 @@ var ClaudeCodeRuntime = class {
9153
9526
  */
9154
9527
  getLastActivity(agentId) {
9155
9528
  const sessionPath = this.getSessionPath(agentId);
9156
- if (!sessionPath || !existsSync33(sessionPath)) {
9529
+ if (!sessionPath || !existsSync35(sessionPath)) {
9157
9530
  return null;
9158
9531
  }
9159
9532
  try {
9160
- const stat = statSync6(sessionPath);
9533
+ const stat = statSync7(sessionPath);
9161
9534
  return stat.mtime;
9162
9535
  } catch {
9163
9536
  return null;
@@ -9167,12 +9540,12 @@ var ClaudeCodeRuntime = class {
9167
9540
  * Read active heartbeat file if it exists
9168
9541
  */
9169
9542
  getActiveHeartbeat(agentId) {
9170
- const heartbeatPath = join32(homedir18(), ".panopticon", "heartbeats", `${agentId}.json`);
9171
- if (!existsSync33(heartbeatPath)) {
9543
+ const heartbeatPath = join34(homedir18(), ".panopticon", "heartbeats", `${agentId}.json`);
9544
+ if (!existsSync35(heartbeatPath)) {
9172
9545
  return null;
9173
9546
  }
9174
9547
  try {
9175
- const content = readFileSync27(heartbeatPath, "utf-8");
9548
+ const content = readFileSync29(heartbeatPath, "utf-8");
9176
9549
  const data = JSON.parse(content);
9177
9550
  const timestamp = new Date(data.timestamp);
9178
9551
  const now = /* @__PURE__ */ new Date();
@@ -9276,11 +9649,11 @@ var ClaudeCodeRuntime = class {
9276
9649
  throw new Error(`Agent ${agentId} is not running`);
9277
9650
  }
9278
9651
  await sendKeysAsync(agentId, message);
9279
- const mailDir = join32(getAgentDir(agentId), "mail");
9652
+ const mailDir = join34(getAgentDir(agentId), "mail");
9280
9653
  mkdirSync16(mailDir, { recursive: true });
9281
9654
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
9282
9655
  writeFileSync13(
9283
- join32(mailDir, `${timestamp}.md`),
9656
+ join34(mailDir, `${timestamp}.md`),
9284
9657
  `# Message
9285
9658
 
9286
9659
  ${message}
@@ -9361,7 +9734,7 @@ ${message}
9361
9734
  if (!sessionUsage) {
9362
9735
  return null;
9363
9736
  }
9364
- const stat = statSync6(file);
9737
+ const stat = statSync7(file);
9365
9738
  return {
9366
9739
  id: sessionUsage.sessionId,
9367
9740
  agentId: "unknown",
@@ -9616,12 +9989,12 @@ async function checkAllTriggers(agentId, workspace, issueId, currentModel, healt
9616
9989
  init_esm_shims();
9617
9990
  init_agents();
9618
9991
  import { writeFileSync as writeFileSync14, mkdirSync as mkdirSync17 } from "fs";
9619
- import { join as join34 } from "path";
9992
+ import { join as join36 } from "path";
9620
9993
 
9621
9994
  // src/lib/cloister/handoff-context.ts
9622
9995
  init_esm_shims();
9623
- import { existsSync as existsSync34, readFileSync as readFileSync28 } from "fs";
9624
- import { join as join33 } from "path";
9996
+ import { existsSync as existsSync36, readFileSync as readFileSync30 } from "fs";
9997
+ import { join as join35 } from "path";
9625
9998
  import { exec as exec13 } from "child_process";
9626
9999
  import { promisify as promisify13 } from "util";
9627
10000
  var execAsync13 = promisify13(exec13);
@@ -9645,13 +10018,13 @@ async function captureHandoffContext(agentState, targetModel, reason) {
9645
10018
  }
9646
10019
  async function captureFiles(context, workspace) {
9647
10020
  try {
9648
- const stateFile = join33(workspace, ".planning/STATE.md");
9649
- if (existsSync34(stateFile)) {
9650
- context.stateFile = readFileSync28(stateFile, "utf-8");
10021
+ const stateFile = join35(workspace, ".planning/STATE.md");
10022
+ if (existsSync36(stateFile)) {
10023
+ context.stateFile = readFileSync30(stateFile, "utf-8");
9651
10024
  }
9652
- const claudeMd = join33(workspace, "CLAUDE.md");
9653
- if (existsSync34(claudeMd)) {
9654
- context.claudeMd = readFileSync28(claudeMd, "utf-8");
10025
+ const claudeMd = join35(workspace, "CLAUDE.md");
10026
+ if (existsSync36(claudeMd)) {
10027
+ context.claudeMd = readFileSync30(claudeMd, "utf-8");
9655
10028
  }
9656
10029
  } catch (error) {
9657
10030
  console.error("Error capturing files:", error);
@@ -9842,15 +10215,16 @@ async function performKillAndSpawn(state, options) {
9842
10215
  const context = await captureHandoffContext(state, options.targetModel, options.reason);
9843
10216
  stopAgent(state.id);
9844
10217
  const prompt = buildHandoffPrompt(context, options.additionalInstructions);
9845
- const handoffDir = join34(getAgentDir(state.id), "handoffs");
10218
+ const handoffDir = join36(getAgentDir(state.id), "handoffs");
9846
10219
  mkdirSync17(handoffDir, { recursive: true });
9847
- const handoffFile = join34(handoffDir, `handoff-${Date.now()}.md`);
10220
+ const handoffFile = join36(handoffDir, `handoff-${Date.now()}.md`);
9848
10221
  writeFileSync14(handoffFile, prompt);
9849
10222
  const newState = await spawnAgent({
9850
10223
  issueId: state.issueId,
9851
10224
  workspace: state.workspace,
9852
10225
  runtime: state.runtime,
9853
10226
  model: options.targetModel,
10227
+ phase: "implementation",
9854
10228
  prompt
9855
10229
  });
9856
10230
  newState.handoffCount = (state.handoffCount || 0) + 1;
@@ -9952,12 +10326,12 @@ function sleep(ms) {
9952
10326
  // src/lib/cloister/handoff-logger.ts
9953
10327
  init_esm_shims();
9954
10328
  init_paths();
9955
- import { existsSync as existsSync36, mkdirSync as mkdirSync18, appendFileSync as appendFileSync3, readFileSync as readFileSync29, writeFileSync as writeFileSync15 } from "fs";
9956
- import { join as join35 } from "path";
9957
- var HANDOFF_LOG_FILE = join35(PANOPTICON_HOME, "logs", "handoffs.jsonl");
10329
+ import { existsSync as existsSync38, mkdirSync as mkdirSync18, appendFileSync as appendFileSync3, readFileSync as readFileSync31, writeFileSync as writeFileSync15 } from "fs";
10330
+ import { join as join37 } from "path";
10331
+ var HANDOFF_LOG_FILE = join37(PANOPTICON_HOME, "logs", "handoffs.jsonl");
9958
10332
  function ensureLogDir() {
9959
- const logDir = join35(PANOPTICON_HOME, "logs");
9960
- if (!existsSync36(logDir)) {
10333
+ const logDir = join37(PANOPTICON_HOME, "logs");
10334
+ if (!existsSync38(logDir)) {
9961
10335
  mkdirSync18(logDir, { recursive: true });
9962
10336
  }
9963
10337
  }
@@ -10001,8 +10375,8 @@ function createHandoffEvent(agentId, issueId, context, trigger, success, errorMe
10001
10375
  // src/lib/cloister/fpp-violations.ts
10002
10376
  init_esm_shims();
10003
10377
  init_hooks();
10004
- import { readFileSync as readFileSync30, existsSync as existsSync37, writeFileSync as writeFileSync16, mkdirSync as mkdirSync19, unlinkSync as unlinkSync3 } from "fs";
10005
- import { join as join36, dirname as dirname11 } from "path";
10378
+ import { readFileSync as readFileSync32, existsSync as existsSync39, writeFileSync as writeFileSync16, mkdirSync as mkdirSync19, unlinkSync as unlinkSync3 } from "fs";
10379
+ import { join as join38, dirname as dirname11 } from "path";
10006
10380
  init_paths();
10007
10381
  var DEFAULT_FPP_CONFIG = {
10008
10382
  hook_idle_minutes: 5,
@@ -10010,13 +10384,13 @@ var DEFAULT_FPP_CONFIG = {
10010
10384
  review_pending_minutes: 15,
10011
10385
  max_nudges: 3
10012
10386
  };
10013
- var VIOLATIONS_DATA_FILE = join36(PANOPTICON_HOME, "fpp-violations.json");
10387
+ var VIOLATIONS_DATA_FILE = join38(PANOPTICON_HOME, "fpp-violations.json");
10014
10388
  function loadViolations() {
10015
- if (!existsSync37(VIOLATIONS_DATA_FILE)) {
10389
+ if (!existsSync39(VIOLATIONS_DATA_FILE)) {
10016
10390
  return /* @__PURE__ */ new Map();
10017
10391
  }
10018
10392
  try {
10019
- const fileContent = readFileSync30(VIOLATIONS_DATA_FILE, "utf-8");
10393
+ const fileContent = readFileSync32(VIOLATIONS_DATA_FILE, "utf-8");
10020
10394
  const persisted = JSON.parse(fileContent);
10021
10395
  return new Map(persisted.violations || []);
10022
10396
  } catch (error) {
@@ -10027,7 +10401,7 @@ function loadViolations() {
10027
10401
  function saveViolations(violations) {
10028
10402
  try {
10029
10403
  const dir = dirname11(VIOLATIONS_DATA_FILE);
10030
- if (!existsSync37(dir)) {
10404
+ if (!existsSync39(dir)) {
10031
10405
  mkdirSync19(dir, { recursive: true });
10032
10406
  }
10033
10407
  const persisted = {
@@ -10035,7 +10409,7 @@ function saveViolations(violations) {
10035
10409
  };
10036
10410
  const tempFile = `${VIOLATIONS_DATA_FILE}.tmp`;
10037
10411
  writeFileSync16(tempFile, JSON.stringify(persisted, null, 2));
10038
- writeFileSync16(VIOLATIONS_DATA_FILE, readFileSync30(tempFile));
10412
+ writeFileSync16(VIOLATIONS_DATA_FILE, readFileSync32(tempFile));
10039
10413
  try {
10040
10414
  unlinkSync3(tempFile);
10041
10415
  } catch (unlinkError) {
@@ -10125,11 +10499,11 @@ function clearOldViolations(hoursOld = 24) {
10125
10499
  init_esm_shims();
10126
10500
  init_paths();
10127
10501
  init_config2();
10128
- import { readFileSync as readFileSync31, existsSync as existsSync38, writeFileSync as writeFileSync17, mkdirSync as mkdirSync20, unlinkSync as unlinkSync4 } from "fs";
10129
- import { join as join37, dirname as dirname12 } from "path";
10130
- var COST_DATA_FILE = join37(PANOPTICON_HOME, "cost-data.json");
10502
+ import { readFileSync as readFileSync33, existsSync as existsSync40, writeFileSync as writeFileSync17, mkdirSync as mkdirSync20, unlinkSync as unlinkSync4 } from "fs";
10503
+ import { join as join39, dirname as dirname12 } from "path";
10504
+ var COST_DATA_FILE = join39(PANOPTICON_HOME, "cost-data.json");
10131
10505
  function loadCostData() {
10132
- if (!existsSync38(COST_DATA_FILE)) {
10506
+ if (!existsSync40(COST_DATA_FILE)) {
10133
10507
  return {
10134
10508
  perAgent: /* @__PURE__ */ new Map(),
10135
10509
  perIssue: /* @__PURE__ */ new Map(),
@@ -10138,7 +10512,7 @@ function loadCostData() {
10138
10512
  };
10139
10513
  }
10140
10514
  try {
10141
- const fileContent = readFileSync31(COST_DATA_FILE, "utf-8");
10515
+ const fileContent = readFileSync33(COST_DATA_FILE, "utf-8");
10142
10516
  const persisted = JSON.parse(fileContent);
10143
10517
  return {
10144
10518
  perAgent: new Map(Object.entries(persisted.perAgent || {})),
@@ -10159,7 +10533,7 @@ function loadCostData() {
10159
10533
  function saveCostData(data) {
10160
10534
  try {
10161
10535
  const dir = dirname12(COST_DATA_FILE);
10162
- if (!existsSync38(dir)) {
10536
+ if (!existsSync40(dir)) {
10163
10537
  mkdirSync20(dir, { recursive: true });
10164
10538
  }
10165
10539
  const persisted = {
@@ -10170,7 +10544,7 @@ function saveCostData(data) {
10170
10544
  };
10171
10545
  const tempFile = `${COST_DATA_FILE}.tmp`;
10172
10546
  writeFileSync17(tempFile, JSON.stringify(persisted, null, 2));
10173
- writeFileSync17(COST_DATA_FILE, readFileSync31(tempFile));
10547
+ writeFileSync17(COST_DATA_FILE, readFileSync33(tempFile));
10174
10548
  try {
10175
10549
  unlinkSync4(tempFile);
10176
10550
  } catch (unlinkError) {
@@ -10291,7 +10665,7 @@ function getCostSummary() {
10291
10665
  init_esm_shims();
10292
10666
  init_paths();
10293
10667
  import { writeFileSync as writeFileSync18 } from "fs";
10294
- import { join as join38 } from "path";
10668
+ import { join as join40 } from "path";
10295
10669
  import { exec as exec14 } from "child_process";
10296
10670
  import { promisify as promisify14 } from "util";
10297
10671
  init_agents();
@@ -10431,7 +10805,7 @@ async function rotateSpecialistSession(specialistName, workingDir) {
10431
10805
  let memoryFile;
10432
10806
  if (specialistName === "merge-agent" && workingDir) {
10433
10807
  memoryContent = await buildMergeAgentMemory(workingDir);
10434
- memoryFile = join38(PANOPTICON_HOME, `merge-agent-memory-${Date.now()}.md`);
10808
+ memoryFile = join40(PANOPTICON_HOME, `merge-agent-memory-${Date.now()}.md`);
10435
10809
  writeFileSync18(memoryFile, memoryContent);
10436
10810
  console.log(`Built memory file: ${memoryFile}`);
10437
10811
  }
@@ -10487,17 +10861,17 @@ init_config2();
10487
10861
  init_specialists();
10488
10862
  init_agents();
10489
10863
  init_tmux();
10490
- import { readFileSync as readFileSync33, writeFileSync as writeFileSync19, existsSync as existsSync40, mkdirSync as mkdirSync21, readdirSync as readdirSync16, statSync as statSync7, rmSync as rmSync5 } from "fs";
10491
- import { join as join39 } from "path";
10864
+ import { readFileSync as readFileSync35, writeFileSync as writeFileSync19, existsSync as existsSync42, mkdirSync as mkdirSync21, readdirSync as readdirSync17, statSync as statSync8, rmSync as rmSync5 } from "fs";
10865
+ import { join as join41 } from "path";
10492
10866
  import { exec as exec15 } from "child_process";
10493
10867
  import { promisify as promisify15 } from "util";
10494
10868
  import { homedir as homedir19 } from "os";
10495
10869
  var execAsync15 = promisify15(exec15);
10496
- var REVIEW_STATUS_FILE = join39(homedir19(), ".panopticon", "review-status.json");
10870
+ var REVIEW_STATUS_FILE = join41(homedir19(), ".panopticon", "review-status.json");
10497
10871
  function updateTestStatusToTesting(issueId) {
10498
10872
  try {
10499
- if (!existsSync40(REVIEW_STATUS_FILE)) return;
10500
- const data = JSON.parse(readFileSync33(REVIEW_STATUS_FILE, "utf-8"));
10873
+ if (!existsSync42(REVIEW_STATUS_FILE)) return;
10874
+ const data = JSON.parse(readFileSync35(REVIEW_STATUS_FILE, "utf-8"));
10501
10875
  const upper = issueId.toUpperCase();
10502
10876
  if (data[upper]) {
10503
10877
  data[upper].testStatus = "testing";
@@ -10523,15 +10897,15 @@ var DEFAULT_CONFIG = {
10523
10897
  massDeathWindowMs: 6e4
10524
10898
  // 1 minute window for mass death detection
10525
10899
  };
10526
- var DEACON_DIR = join39(PANOPTICON_HOME, "deacon");
10527
- var STATE_FILE = join39(DEACON_DIR, "health-state.json");
10528
- var CONFIG_FILE2 = join39(DEACON_DIR, "config.json");
10900
+ var DEACON_DIR = join41(PANOPTICON_HOME, "deacon");
10901
+ var STATE_FILE = join41(DEACON_DIR, "health-state.json");
10902
+ var CONFIG_FILE2 = join41(DEACON_DIR, "config.json");
10529
10903
  var deaconInterval = null;
10530
10904
  var config = { ...DEFAULT_CONFIG };
10531
10905
  function loadConfig3() {
10532
10906
  try {
10533
- if (existsSync40(CONFIG_FILE2)) {
10534
- const content = readFileSync33(CONFIG_FILE2, "utf-8");
10907
+ if (existsSync42(CONFIG_FILE2)) {
10908
+ const content = readFileSync35(CONFIG_FILE2, "utf-8");
10535
10909
  const loaded = JSON.parse(content);
10536
10910
  config = { ...DEFAULT_CONFIG, ...loaded };
10537
10911
  }
@@ -10541,15 +10915,15 @@ function loadConfig3() {
10541
10915
  return config;
10542
10916
  }
10543
10917
  function ensureDeaconDir() {
10544
- if (!existsSync40(DEACON_DIR)) {
10918
+ if (!existsSync42(DEACON_DIR)) {
10545
10919
  mkdirSync21(DEACON_DIR, { recursive: true });
10546
10920
  }
10547
10921
  }
10548
10922
  function loadState() {
10549
10923
  ensureDeaconDir();
10550
10924
  try {
10551
- if (existsSync40(STATE_FILE)) {
10552
- const content = readFileSync33(STATE_FILE, "utf-8");
10925
+ if (existsSync42(STATE_FILE)) {
10926
+ const content = readFileSync35(STATE_FILE, "utf-8");
10553
10927
  return JSON.parse(content);
10554
10928
  }
10555
10929
  } catch (error) {
@@ -10598,12 +10972,12 @@ function getCooldownRemaining(healthState) {
10598
10972
  }
10599
10973
  function checkHeartbeat(name) {
10600
10974
  const tmuxSession = getTmuxSessionName(name);
10601
- const heartbeatFile = join39(PANOPTICON_HOME, "heartbeats", `${tmuxSession}.json`);
10975
+ const heartbeatFile = join41(PANOPTICON_HOME, "heartbeats", `${tmuxSession}.json`);
10602
10976
  try {
10603
- if (!existsSync40(heartbeatFile)) {
10977
+ if (!existsSync42(heartbeatFile)) {
10604
10978
  return { isResponsive: false };
10605
10979
  }
10606
- const content = readFileSync33(heartbeatFile, "utf-8");
10980
+ const content = readFileSync35(heartbeatFile, "utf-8");
10607
10981
  const heartbeat = JSON.parse(content);
10608
10982
  const lastActivity = new Date(heartbeat.timestamp).getTime();
10609
10983
  const age = Date.now() - lastActivity;
@@ -10889,10 +11263,10 @@ function isIssueCompletedOrInReview(agentId) {
10889
11263
  const match = agentId.match(/agent-([a-z]+-\d+)/i);
10890
11264
  if (!match) return false;
10891
11265
  const issueId = match[1].toUpperCase();
10892
- if (!existsSync40(REVIEW_STATUS_FILE)) {
11266
+ if (!existsSync42(REVIEW_STATUS_FILE)) {
10893
11267
  return false;
10894
11268
  }
10895
- const content = readFileSync33(REVIEW_STATUS_FILE, "utf-8");
11269
+ const content = readFileSync35(REVIEW_STATUS_FILE, "utf-8");
10896
11270
  const statuses = JSON.parse(content);
10897
11271
  const status = statuses[issueId];
10898
11272
  if (!status) {
@@ -10951,6 +11325,12 @@ async function isAgentActiveInTmux(sessionName) {
10951
11325
  if (!stdout.trim()) return false;
10952
11326
  for (const pattern of ACTIVE_STATUS_PATTERNS) {
10953
11327
  if (pattern.test(stdout)) {
11328
+ if (/thinking/i.test(stdout)) {
11329
+ const thinkingMs = parseThinkingDuration(stdout);
11330
+ if (thinkingMs !== null && thinkingMs >= STUCK_THINKING_THRESHOLD_MS) {
11331
+ return false;
11332
+ }
11333
+ }
10954
11334
  return true;
10955
11335
  }
10956
11336
  }
@@ -10959,39 +11339,123 @@ async function isAgentActiveInTmux(sessionName) {
10959
11339
  return false;
10960
11340
  }
10961
11341
  }
11342
+ var STUCK_THINKING_THRESHOLD_MS = 10 * 60 * 1e3;
11343
+ var STUCK_RECOVERY_COOLDOWN_MS = 5 * 60 * 1e3;
11344
+ var stuckRecoveryState = /* @__PURE__ */ new Map();
11345
+ function parseThinkingDuration(tmuxOutput) {
11346
+ const match = tmuxOutput.match(/[Tt]hinking[^\n]*?\((?:(\d+)m\s*)?(\d+)s/);
11347
+ if (!match) return null;
11348
+ const minutes = match[1] ? parseInt(match[1], 10) : 0;
11349
+ const seconds = parseInt(match[2], 10);
11350
+ return (minutes * 60 + seconds) * 1e3;
11351
+ }
11352
+ async function checkStuckWorkAgents() {
11353
+ const actions = [];
11354
+ const agents = listRunningAgents();
11355
+ const specialists = getEnabledSpecialists();
11356
+ const specialistNames = new Set(specialists.map((s) => getTmuxSessionName(s.name)));
11357
+ const now = Date.now();
11358
+ for (const agent of agents) {
11359
+ if (!agent.tmuxActive) continue;
11360
+ const isWorkAgent = agent.id.startsWith("agent-") && !specialistNames.has(agent.id);
11361
+ if (!isWorkAgent) continue;
11362
+ const recovery = stuckRecoveryState.get(agent.id);
11363
+ if (recovery && now - recovery.lastAttempt < STUCK_RECOVERY_COOLDOWN_MS) {
11364
+ continue;
11365
+ }
11366
+ let tmuxOutput;
11367
+ try {
11368
+ const { stdout } = await execAsync15(
11369
+ `tmux capture-pane -t "${agent.id}" -p -S -10 2>/dev/null || echo ""`,
11370
+ { encoding: "utf-8" }
11371
+ );
11372
+ tmuxOutput = stdout;
11373
+ } catch {
11374
+ continue;
11375
+ }
11376
+ if (!tmuxOutput.trim()) continue;
11377
+ const thinkingMs = parseThinkingDuration(tmuxOutput);
11378
+ if (thinkingMs === null || thinkingMs < STUCK_THINKING_THRESHOLD_MS) {
11379
+ if (recovery && recovery.attempts > 0) {
11380
+ stuckRecoveryState.delete(agent.id);
11381
+ }
11382
+ continue;
11383
+ }
11384
+ const thinkingMinutes = Math.round(thinkingMs / 6e4);
11385
+ const attempts = recovery?.attempts ?? 0;
11386
+ console.log(`[deacon] Work agent ${agent.id} stuck thinking for ${thinkingMinutes}m (attempt ${attempts + 1})`);
11387
+ try {
11388
+ if (attempts === 0) {
11389
+ await execAsync15(`tmux send-keys -t "${agent.id}" Escape 2>/dev/null || true`);
11390
+ actions.push(`Stuck recovery: sent Escape to ${agent.id} (thinking ${thinkingMinutes}m)`);
11391
+ } else if (attempts === 1) {
11392
+ await execAsync15(`tmux send-keys -t "${agent.id}" C-c 2>/dev/null || true`);
11393
+ actions.push(`Stuck recovery: sent Ctrl+C to ${agent.id} (thinking ${thinkingMinutes}m)`);
11394
+ } else {
11395
+ const launcherPath = join41(AGENTS_DIR, agent.id, "launcher.sh");
11396
+ const agentState = getAgentState(agent.id);
11397
+ const workspace = agentState?.workspace;
11398
+ if (!existsSync42(launcherPath) || !workspace) {
11399
+ console.error(`[deacon] Cannot respawn ${agent.id}: missing launcher.sh or workspace`);
11400
+ actions.push(`Stuck recovery failed for ${agent.id}: missing launcher or workspace`);
11401
+ continue;
11402
+ }
11403
+ await execAsync15(`tmux kill-session -t "${agent.id}" 2>/dev/null || true`);
11404
+ await new Promise((r) => setTimeout(r, 1e3));
11405
+ await execAsync15(
11406
+ `tmux new-session -d -s "${agent.id}" -c "${workspace}" "bash ${launcherPath}"`,
11407
+ { encoding: "utf-8" }
11408
+ );
11409
+ stuckRecoveryState.set(agent.id, { lastAttempt: now, attempts: 0 });
11410
+ actions.push(`Stuck recovery: respawned ${agent.id} (was stuck thinking ${thinkingMinutes}m, attempt ${attempts + 1})`);
11411
+ console.log(`[deacon] Respawned stuck work agent ${agent.id}`);
11412
+ continue;
11413
+ }
11414
+ } catch (error) {
11415
+ const msg = error instanceof Error ? error.message : String(error);
11416
+ console.error(`[deacon] Stuck recovery failed for ${agent.id}:`, msg);
11417
+ actions.push(`Stuck recovery error for ${agent.id}: ${msg}`);
11418
+ }
11419
+ stuckRecoveryState.set(agent.id, {
11420
+ lastAttempt: now,
11421
+ attempts: attempts + 1
11422
+ });
11423
+ }
11424
+ return actions;
11425
+ }
10962
11426
  async function cleanupStaleAgentState() {
10963
11427
  const actions = [];
10964
11428
  const cloisterConfig = loadCloisterConfig();
10965
11429
  const retentionDays = cloisterConfig.retention?.agent_state_days ?? 30;
10966
11430
  const retentionMs = retentionDays * 24 * 60 * 60 * 1e3;
10967
11431
  const now = Date.now();
10968
- if (!existsSync40(AGENTS_DIR)) {
11432
+ if (!existsSync42(AGENTS_DIR)) {
10969
11433
  return actions;
10970
11434
  }
10971
11435
  try {
10972
- const dirs = readdirSync16(AGENTS_DIR, { withFileTypes: true }).filter((d) => d.isDirectory());
11436
+ const dirs = readdirSync17(AGENTS_DIR, { withFileTypes: true }).filter((d) => d.isDirectory());
10973
11437
  for (const dir of dirs) {
10974
- const agentDir = join39(AGENTS_DIR, dir.name);
11438
+ const agentDir = join41(AGENTS_DIR, dir.name);
10975
11439
  try {
10976
11440
  try {
10977
11441
  await execAsync15(`tmux has-session -t "${dir.name}" 2>/dev/null`);
10978
11442
  continue;
10979
11443
  } catch {
10980
11444
  }
10981
- const stateFile = join39(agentDir, "state.json");
11445
+ const stateFile = join41(agentDir, "state.json");
10982
11446
  let mtime;
10983
- if (existsSync40(stateFile)) {
10984
- mtime = statSync7(stateFile).mtimeMs;
11447
+ if (existsSync42(stateFile)) {
11448
+ mtime = statSync8(stateFile).mtimeMs;
10985
11449
  } else {
10986
- mtime = statSync7(agentDir).mtimeMs;
11450
+ mtime = statSync8(agentDir).mtimeMs;
10987
11451
  }
10988
11452
  const ageMs = now - mtime;
10989
11453
  if (ageMs < retentionMs) {
10990
11454
  continue;
10991
11455
  }
10992
- const completedFile = join39(agentDir, "completed");
10993
- if (existsSync40(completedFile)) {
10994
- const completedAge = now - statSync7(completedFile).mtimeMs;
11456
+ const completedFile = join41(agentDir, "completed");
11457
+ if (existsSync42(completedFile)) {
11458
+ const completedAge = now - statSync8(completedFile).mtimeMs;
10995
11459
  if (completedAge < 7 * 24 * 60 * 60 * 1e3) {
10996
11460
  continue;
10997
11461
  }
@@ -11017,10 +11481,10 @@ async function cleanupStaleAgentState() {
11017
11481
  async function checkOrphanedReviewStatuses() {
11018
11482
  const actions = [];
11019
11483
  try {
11020
- if (!existsSync40(REVIEW_STATUS_FILE)) {
11484
+ if (!existsSync42(REVIEW_STATUS_FILE)) {
11021
11485
  return actions;
11022
11486
  }
11023
- const content = readFileSync33(REVIEW_STATUS_FILE, "utf-8");
11487
+ const content = readFileSync35(REVIEW_STATUS_FILE, "utf-8");
11024
11488
  const statuses = JSON.parse(content);
11025
11489
  const reviewAgentSession = getTmuxSessionName("review-agent");
11026
11490
  const reviewAgentRunning = sessionExists(reviewAgentSession);
@@ -11066,10 +11530,10 @@ var DEAD_END_COOLDOWN_MS = 10 * 60 * 1e3;
11066
11530
  async function checkDeadEndAgents() {
11067
11531
  const actions = [];
11068
11532
  try {
11069
- if (!existsSync40(REVIEW_STATUS_FILE)) {
11533
+ if (!existsSync42(REVIEW_STATUS_FILE)) {
11070
11534
  return actions;
11071
11535
  }
11072
- const content = readFileSync33(REVIEW_STATUS_FILE, "utf-8");
11536
+ const content = readFileSync35(REVIEW_STATUS_FILE, "utf-8");
11073
11537
  const statuses = JSON.parse(content);
11074
11538
  const now = Date.now();
11075
11539
  for (const [key, status] of Object.entries(statuses)) {
@@ -11084,8 +11548,8 @@ async function checkDeadEndAgents() {
11084
11548
  const lastRecovery = deadEndCooldowns.get(key);
11085
11549
  if (lastRecovery && now - lastRecovery < DEAD_END_COOLDOWN_MS) continue;
11086
11550
  const autoRequeueCount = status.autoRequeueCount || 0;
11087
- if (autoRequeueCount >= 3) {
11088
- console.log(`[deacon] Dead-end detected for ${key} but circuit breaker active (${autoRequeueCount}/3 requeues used)`);
11551
+ if (autoRequeueCount >= 7) {
11552
+ console.log(`[deacon] Dead-end detected for ${key} but circuit breaker active (${autoRequeueCount}/7 requeues used)`);
11089
11553
  continue;
11090
11554
  }
11091
11555
  const issueId = status.issueId || key;
@@ -11117,6 +11581,117 @@ async function checkDeadEndAgents() {
11117
11581
  }
11118
11582
  return actions;
11119
11583
  }
11584
+ var firstCompletionCooldowns = /* @__PURE__ */ new Map();
11585
+ var FIRST_COMPLETION_IDLE_MS = 10 * 60 * 1e3;
11586
+ var FIRST_COMPLETION_COOLDOWN_MS = 15 * 60 * 1e3;
11587
+ async function checkFirstCompletionAgents() {
11588
+ const actions = [];
11589
+ try {
11590
+ const agents = listRunningAgents();
11591
+ const now = Date.now();
11592
+ for (const agent of agents) {
11593
+ const agentId = agent.id;
11594
+ if (!agentId || !agentId.startsWith("agent-") || !agent.tmuxActive) continue;
11595
+ if (agentId.startsWith("specialist-")) continue;
11596
+ const completedFile = join41(AGENTS_DIR, agent.id, "completed");
11597
+ if (existsSync42(completedFile)) continue;
11598
+ const runtimeState = getAgentRuntimeState(agent.id);
11599
+ if (!runtimeState || runtimeState.state !== "idle") continue;
11600
+ const lastActivity = new Date(runtimeState.lastActivity);
11601
+ const idleMs = now - lastActivity.getTime();
11602
+ if (idleMs < FIRST_COMPLETION_IDLE_MS) continue;
11603
+ try {
11604
+ const { stdout: lastLines } = await execAsync15(
11605
+ `tmux capture-pane -t "${agent.id}" -p -S -3 2>/dev/null || echo ""`,
11606
+ { encoding: "utf-8" }
11607
+ );
11608
+ const lines = lastLines.split("\n").filter((l) => l.trim().length > 0);
11609
+ const tail = lines.slice(-3).join("\n");
11610
+ const isAtPrompt = /❯/.test(tail) || /bypass permissions/.test(tail) || /Worked for/.test(tail);
11611
+ if (!isAtPrompt) continue;
11612
+ } catch {
11613
+ continue;
11614
+ }
11615
+ const lastNudge = firstCompletionCooldowns.get(agent.id);
11616
+ if (lastNudge && now - lastNudge < FIRST_COMPLETION_COOLDOWN_MS) continue;
11617
+ const issueId = agent.issueId || agent.id.replace("agent-", "").toUpperCase();
11618
+ const issueKey = issueId.toLowerCase();
11619
+ if (existsSync42(REVIEW_STATUS_FILE)) {
11620
+ try {
11621
+ const statuses = JSON.parse(readFileSync35(REVIEW_STATUS_FILE, "utf-8"));
11622
+ const hasStatus = statuses[issueKey] || statuses[issueId] || statuses[issueId.toUpperCase()];
11623
+ if (hasStatus) {
11624
+ console.log(`[deacon] First-completion gate: skipping ${agent.id} \u2014 has review status entry (readyForMerge=${hasStatus.readyForMerge ?? false})`);
11625
+ continue;
11626
+ }
11627
+ } catch {
11628
+ }
11629
+ }
11630
+ const agentStateForGate = getAgentState(agent.id);
11631
+ if (agentStateForGate?.workspace) {
11632
+ const feedbackDir = join41(agentStateForGate.workspace, ".planning", "feedback");
11633
+ if (existsSync42(feedbackDir)) {
11634
+ try {
11635
+ const feedbackFiles = readdirSync17(feedbackDir);
11636
+ if (feedbackFiles.length > 0) {
11637
+ console.log(`[deacon] First-completion gate: skipping ${agent.id} \u2014 has ${feedbackFiles.length} review feedback file(s) in .planning/feedback/`);
11638
+ continue;
11639
+ }
11640
+ } catch {
11641
+ }
11642
+ }
11643
+ }
11644
+ const agentState = getAgentState(agent.id);
11645
+ if (!agentState?.workspace || !existsSync42(agentState.workspace)) continue;
11646
+ let hasCommits = false;
11647
+ try {
11648
+ const { stdout: gitLog } = await execAsync15(
11649
+ "git log --oneline -3 2>/dev/null",
11650
+ { cwd: agentState.workspace }
11651
+ );
11652
+ hasCommits = gitLog.trim().length > 0;
11653
+ } catch {
11654
+ try {
11655
+ const subdirs = readdirSync17(agentState.workspace, { withFileTypes: true }).filter((d) => d.isDirectory() && !d.name.startsWith("."));
11656
+ for (const sub of subdirs) {
11657
+ try {
11658
+ const { stdout: subLog } = await execAsync15(
11659
+ "git log --oneline -3 2>/dev/null",
11660
+ { cwd: join41(agentState.workspace, sub.name) }
11661
+ );
11662
+ if (subLog.trim().length > 0) {
11663
+ hasCommits = true;
11664
+ break;
11665
+ }
11666
+ } catch {
11667
+ }
11668
+ }
11669
+ } catch {
11670
+ }
11671
+ }
11672
+ if (!hasCommits) continue;
11673
+ const idleMinutes = Math.round(idleMs / 6e4);
11674
+ console.log(`[deacon] First-completion gap detected: ${agent.id} (${issueId}) idle for ${idleMinutes}m with commits but no completion marker`);
11675
+ firstCompletionCooldowns.set(agent.id, now);
11676
+ try {
11677
+ const nudgeMessage = `You appear to have stopped working without calling "pan work done". If your implementation is complete, run this now:
11678
+
11679
+ pan work done ${issueId} -c "Implementation complete"
11680
+
11681
+ If you still have remaining tasks, continue working on them.`;
11682
+ await sendKeysAsync(agent.id, nudgeMessage);
11683
+ actions.push(`First-completion nudge: ${agent.id} (idle ${idleMinutes}m)`);
11684
+ console.log(`[deacon] Sent first-completion nudge to ${agent.id}`);
11685
+ } catch (error) {
11686
+ const msg = error instanceof Error ? error.message : String(error);
11687
+ console.error(`[deacon] Failed to send first-completion nudge to ${agent.id}:`, msg);
11688
+ }
11689
+ }
11690
+ } catch (error) {
11691
+ console.error("[deacon] Error in first-completion detection:", error);
11692
+ }
11693
+ return actions;
11694
+ }
11120
11695
  async function runPatrol() {
11121
11696
  const state = loadState();
11122
11697
  state.patrolCycle++;
@@ -11172,7 +11747,7 @@ async function runPatrol() {
11172
11747
  if (nextTask) {
11173
11748
  console.log(`[deacon] Auto-resuming suspended ${specialist.name} for queued task: ${nextTask.payload.issueId}`);
11174
11749
  try {
11175
- const { resumeAgent } = await import("../agents-VLK4BMVA.js");
11750
+ const { resumeAgent } = await import("../agents-5OPQKM5K.js");
11176
11751
  const message = `# Queued Work
11177
11752
 
11178
11753
  Processing queued task: ${nextTask.payload.issueId}`;
@@ -11226,9 +11801,15 @@ Processing queued task: ${nextTask.payload.issueId}`;
11226
11801
  const deadEndActions = await checkDeadEndAgents();
11227
11802
  actions.push(...deadEndActions);
11228
11803
  for (const a of deadEndActions) addLog("action", a, state.patrolCycle);
11804
+ const firstCompletionActions = await checkFirstCompletionAgents();
11805
+ actions.push(...firstCompletionActions);
11806
+ for (const a of firstCompletionActions) addLog("action", a, state.patrolCycle);
11229
11807
  const lazyActions = await checkAndCorrectLazyAgents();
11230
11808
  actions.push(...lazyActions);
11231
11809
  for (const a of lazyActions) addLog("action", a, state.patrolCycle);
11810
+ const stuckActions = await checkStuckWorkAgents();
11811
+ actions.push(...stuckActions);
11812
+ for (const a of stuckActions) addLog("action", a, state.patrolCycle);
11232
11813
  if (Math.random() < 3e-3) {
11233
11814
  const cleanupActions = await cleanupStaleAgentState();
11234
11815
  actions.push(...cleanupActions);
@@ -11304,9 +11885,9 @@ function getDeaconStatus() {
11304
11885
  // src/lib/cloister/service.ts
11305
11886
  init_paths();
11306
11887
  init_paths();
11307
- import { existsSync as existsSync41, writeFileSync as writeFileSync20, unlinkSync as unlinkSync5, readFileSync as readFileSync34, readdirSync as readdirSync17, renameSync as renameSync3 } from "fs";
11308
- import { join as join40 } from "path";
11309
- var CLOISTER_STATE_FILE = join40(PANOPTICON_HOME, "cloister.state");
11888
+ import { existsSync as existsSync43, writeFileSync as writeFileSync20, unlinkSync as unlinkSync5, readFileSync as readFileSync36, readdirSync as readdirSync18, renameSync as renameSync3 } from "fs";
11889
+ import { join as join42 } from "path";
11890
+ var CLOISTER_STATE_FILE = join42(PANOPTICON_HOME, "cloister.state");
11310
11891
  function writeStateFile(running, pid) {
11311
11892
  try {
11312
11893
  if (running) {
@@ -11316,7 +11897,7 @@ function writeStateFile(running, pid) {
11316
11897
  startedAt: (/* @__PURE__ */ new Date()).toISOString()
11317
11898
  }));
11318
11899
  } else {
11319
- if (existsSync41(CLOISTER_STATE_FILE)) {
11900
+ if (existsSync43(CLOISTER_STATE_FILE)) {
11320
11901
  unlinkSync5(CLOISTER_STATE_FILE);
11321
11902
  }
11322
11903
  }
@@ -11326,8 +11907,8 @@ function writeStateFile(running, pid) {
11326
11907
  }
11327
11908
  function readStateFile() {
11328
11909
  try {
11329
- if (existsSync41(CLOISTER_STATE_FILE)) {
11330
- const data = JSON.parse(readFileSync34(CLOISTER_STATE_FILE, "utf-8"));
11910
+ if (existsSync43(CLOISTER_STATE_FILE)) {
11911
+ const data = JSON.parse(readFileSync36(CLOISTER_STATE_FILE, "utf-8"));
11331
11912
  if (data.pid) {
11332
11913
  try {
11333
11914
  process.kill(data.pid, 0);
@@ -11545,14 +12126,14 @@ var CloisterService = class {
11545
12126
  */
11546
12127
  async checkCompletionMarkers() {
11547
12128
  try {
11548
- if (!existsSync41(AGENTS_DIR)) return;
11549
- const agentDirs = readdirSync17(AGENTS_DIR, { withFileTypes: true }).filter((d) => d.isDirectory() && d.name.startsWith("agent-"));
12129
+ if (!existsSync43(AGENTS_DIR)) return;
12130
+ const agentDirs = readdirSync18(AGENTS_DIR, { withFileTypes: true }).filter((d) => d.isDirectory() && d.name.startsWith("agent-"));
11550
12131
  for (const dir of agentDirs) {
11551
- const completedFile = join40(AGENTS_DIR, dir.name, "completed");
11552
- const processedFile = join40(AGENTS_DIR, dir.name, "completed.processed");
11553
- if (!existsSync41(completedFile) || existsSync41(processedFile)) continue;
12132
+ const completedFile = join42(AGENTS_DIR, dir.name, "completed");
12133
+ const processedFile = join42(AGENTS_DIR, dir.name, "completed.processed");
12134
+ if (!existsSync43(completedFile) || existsSync43(processedFile)) continue;
11554
12135
  try {
11555
- const content = JSON.parse(readFileSync34(completedFile, "utf-8"));
12136
+ const content = JSON.parse(readFileSync36(completedFile, "utf-8"));
11556
12137
  const ageMs = Date.now() - new Date(content.timestamp).getTime();
11557
12138
  if (ageMs > 24 * 60 * 60 * 1e3) {
11558
12139
  console.log(`\u{1F514} Cloister: Skipping stale completion marker for ${dir.name} (${Math.floor(ageMs / 36e5)}h old)`);
@@ -12209,8 +12790,8 @@ init_esm_shims();
12209
12790
  // src/cli/commands/setup/hooks.ts
12210
12791
  init_esm_shims();
12211
12792
  import chalk39 from "chalk";
12212
- import { readFileSync as readFileSync35, writeFileSync as writeFileSync21, existsSync as existsSync42, mkdirSync as mkdirSync22, copyFileSync as copyFileSync2, chmodSync } from "fs";
12213
- import { join as join41 } from "path";
12793
+ import { readFileSync as readFileSync37, writeFileSync as writeFileSync21, existsSync as existsSync44, mkdirSync as mkdirSync22, copyFileSync as copyFileSync2, chmodSync } from "fs";
12794
+ import { join as join43 } from "path";
12214
12795
  import { execSync as execSync5 } from "child_process";
12215
12796
  import { homedir as homedir20 } from "os";
12216
12797
  function checkJqInstalled() {
@@ -12288,14 +12869,14 @@ async function setupHooksCommand() {
12288
12869
  } else {
12289
12870
  console.log(chalk39.green("\u2713 jq is installed"));
12290
12871
  }
12291
- const panopticonHome = join41(homedir20(), ".panopticon");
12292
- const binDir = join41(panopticonHome, "bin");
12293
- const heartbeatsDir = join41(panopticonHome, "heartbeats");
12294
- if (!existsSync42(binDir)) {
12872
+ const panopticonHome = join43(homedir20(), ".panopticon");
12873
+ const binDir = join43(panopticonHome, "bin");
12874
+ const heartbeatsDir = join43(panopticonHome, "heartbeats");
12875
+ if (!existsSync44(binDir)) {
12295
12876
  mkdirSync22(binDir, { recursive: true });
12296
12877
  console.log(chalk39.green("\u2713 Created ~/.panopticon/bin/"));
12297
12878
  }
12298
- if (!existsSync42(heartbeatsDir)) {
12879
+ if (!existsSync44(heartbeatsDir)) {
12299
12880
  mkdirSync22(heartbeatsDir, { recursive: true });
12300
12881
  console.log(chalk39.green("\u2713 Created ~/.panopticon/heartbeats/"));
12301
12882
  }
@@ -12304,13 +12885,13 @@ async function setupHooksCommand() {
12304
12885
  const { dirname: dirname17 } = await import("path");
12305
12886
  const __dirname6 = dirname17(fileURLToPath6(import.meta.url));
12306
12887
  for (const scriptName of hookScripts) {
12307
- const devSource = join41(process.cwd(), "scripts", scriptName);
12308
- const installedSource = join41(__dirname6, "..", "..", "..", "scripts", scriptName);
12309
- const scriptDest = join41(binDir, scriptName);
12888
+ const devSource = join43(process.cwd(), "scripts", scriptName);
12889
+ const installedSource = join43(__dirname6, "..", "..", "..", "scripts", scriptName);
12890
+ const scriptDest = join43(binDir, scriptName);
12310
12891
  let sourcePath = null;
12311
- if (existsSync42(devSource)) {
12892
+ if (existsSync44(devSource)) {
12312
12893
  sourcePath = devSource;
12313
- } else if (existsSync42(installedSource)) {
12894
+ } else if (existsSync44(installedSource)) {
12314
12895
  sourcePath = installedSource;
12315
12896
  }
12316
12897
  if (!sourcePath) {
@@ -12323,12 +12904,12 @@ async function setupHooksCommand() {
12323
12904
  chmodSync(scriptDest, 493);
12324
12905
  }
12325
12906
  console.log(chalk39.green("\u2713 Installed hook scripts (pre-tool, post-tool, stop, specialist-stop)"));
12326
- const claudeDir = join41(homedir20(), ".claude");
12327
- const settingsPath = join41(claudeDir, "settings.json");
12907
+ const claudeDir = join43(homedir20(), ".claude");
12908
+ const settingsPath = join43(claudeDir, "settings.json");
12328
12909
  let settings = {};
12329
- if (existsSync42(settingsPath)) {
12910
+ if (existsSync44(settingsPath)) {
12330
12911
  try {
12331
- const settingsContent = readFileSync35(settingsPath, "utf-8");
12912
+ const settingsContent = readFileSync37(settingsPath, "utf-8");
12332
12913
  settings = JSON.parse(settingsContent);
12333
12914
  console.log(chalk39.green("\u2713 Read existing Claude Code settings"));
12334
12915
  } catch (error) {
@@ -12337,7 +12918,7 @@ async function setupHooksCommand() {
12337
12918
  }
12338
12919
  } else {
12339
12920
  console.log(chalk39.dim("No existing settings.json found, creating new file"));
12340
- if (!existsSync42(claudeDir)) {
12921
+ if (!existsSync44(claudeDir)) {
12341
12922
  mkdirSync22(claudeDir, { recursive: true });
12342
12923
  }
12343
12924
  }
@@ -12351,11 +12932,11 @@ async function setupHooksCommand() {
12351
12932
  console.log(chalk39.dim(" Install Python3 to enable token-efficient code analysis\n"));
12352
12933
  }
12353
12934
  if (python3Available) {
12354
- const mcpPath = join41(dirname17(settingsPath), "mcp.json");
12935
+ const mcpPath = join43(dirname17(settingsPath), "mcp.json");
12355
12936
  let mcpConfig = {};
12356
12937
  try {
12357
- if (existsSync42(mcpPath)) {
12358
- mcpConfig = JSON.parse(readFileSync35(mcpPath, "utf-8"));
12938
+ if (existsSync44(mcpPath)) {
12939
+ mcpConfig = JSON.parse(readFileSync37(mcpPath, "utf-8"));
12359
12940
  }
12360
12941
  } catch {
12361
12942
  mcpConfig = {};
@@ -12390,7 +12971,7 @@ async function setupHooksCommand() {
12390
12971
  hooks: [
12391
12972
  {
12392
12973
  type: "command",
12393
- command: join41(binDir, "pre-tool-hook")
12974
+ command: join43(binDir, "pre-tool-hook")
12394
12975
  }
12395
12976
  ]
12396
12977
  });
@@ -12400,7 +12981,7 @@ async function setupHooksCommand() {
12400
12981
  hooks: [
12401
12982
  {
12402
12983
  type: "command",
12403
- command: join41(binDir, "tldr-read-enforcer")
12984
+ command: join43(binDir, "tldr-read-enforcer")
12404
12985
  }
12405
12986
  ]
12406
12987
  });
@@ -12413,7 +12994,7 @@ async function setupHooksCommand() {
12413
12994
  hooks: [
12414
12995
  {
12415
12996
  type: "command",
12416
- command: join41(binDir, "heartbeat-hook")
12997
+ command: join43(binDir, "heartbeat-hook")
12417
12998
  }
12418
12999
  ]
12419
13000
  });
@@ -12423,7 +13004,7 @@ async function setupHooksCommand() {
12423
13004
  hooks: [
12424
13005
  {
12425
13006
  type: "command",
12426
- command: join41(binDir, "tldr-post-edit")
13007
+ command: join43(binDir, "tldr-post-edit")
12427
13008
  }
12428
13009
  ]
12429
13010
  });
@@ -12436,7 +13017,7 @@ async function setupHooksCommand() {
12436
13017
  hooks: [
12437
13018
  {
12438
13019
  type: "command",
12439
- command: join41(binDir, "stop-hook")
13020
+ command: join43(binDir, "stop-hook")
12440
13021
  }
12441
13022
  ]
12442
13023
  });
@@ -12549,17 +13130,17 @@ import chalk41 from "chalk";
12549
13130
  import { exec as exec16 } from "child_process";
12550
13131
  import { promisify as promisify16 } from "util";
12551
13132
  import { setTimeout as sleep2 } from "timers/promises";
12552
- import { existsSync as existsSync43, mkdirSync as mkdirSync23, writeFileSync as writeFileSync22 } from "fs";
12553
- import { join as join42 } from "path";
13133
+ import { existsSync as existsSync45, mkdirSync as mkdirSync23, writeFileSync as writeFileSync22 } from "fs";
13134
+ import { join as join44 } from "path";
12554
13135
  var execAsync16 = promisify16(exec16);
12555
- var TASKS_DIR = join42(PANOPTICON_HOME, "specialists", "tasks");
13136
+ var TASKS_DIR = join44(PANOPTICON_HOME, "specialists", "tasks");
12556
13137
  function sendTask(tmuxSession, specialistName, task) {
12557
13138
  const isLargeTask = task.length > 500 || task.includes("\n");
12558
13139
  if (isLargeTask) {
12559
- if (!existsSync43(TASKS_DIR)) {
13140
+ if (!existsSync45(TASKS_DIR)) {
12560
13141
  mkdirSync23(TASKS_DIR, { recursive: true });
12561
13142
  }
12562
- const taskFile = join42(TASKS_DIR, `${specialistName}-${Date.now()}.md`);
13143
+ const taskFile = join44(TASKS_DIR, `${specialistName}-${Date.now()}.md`);
12563
13144
  writeFileSync22(taskFile, task, "utf-8");
12564
13145
  const shortMessage = `Read and execute the task in: ${taskFile}`;
12565
13146
  sendKeys(tmuxSession, shortMessage);
@@ -12867,11 +13448,11 @@ init_esm_shims();
12867
13448
  init_specialists();
12868
13449
  init_paths();
12869
13450
  import chalk44 from "chalk";
12870
- import { existsSync as existsSync44, readFileSync as readFileSync36, writeFileSync as writeFileSync23 } from "fs";
12871
- import { join as join43 } from "path";
13451
+ import { existsSync as existsSync46, readFileSync as readFileSync38, writeFileSync as writeFileSync23 } from "fs";
13452
+ import { join as join45 } from "path";
12872
13453
  import * as readline2 from "readline";
12873
13454
  var ALL_SPECIALISTS2 = ["merge-agent", "review-agent", "test-agent"];
12874
- var REVIEW_STATUS_FILE2 = join43(PANOPTICON_HOME, "review-status.json");
13455
+ var REVIEW_STATUS_FILE2 = join45(PANOPTICON_HOME, "review-status.json");
12875
13456
  async function clearQueueCommand(name, options) {
12876
13457
  if (!ALL_SPECIALISTS2.includes(name)) {
12877
13458
  console.log(chalk44.red(`
@@ -12921,10 +13502,10 @@ ${metadata.displayName} Queue:
12921
13502
  issueIds.push(payload.issueId);
12922
13503
  }
12923
13504
  }
12924
- const hookFile = join43(PANOPTICON_HOME, "agents", specialistName, "hook.json");
12925
- if (existsSync44(hookFile)) {
13505
+ const hookFile = join45(PANOPTICON_HOME, "agents", specialistName, "hook.json");
13506
+ if (existsSync46(hookFile)) {
12926
13507
  try {
12927
- const hook = JSON.parse(readFileSync36(hookFile, "utf-8"));
13508
+ const hook = JSON.parse(readFileSync38(hookFile, "utf-8"));
12928
13509
  hook.items = [];
12929
13510
  hook.lastChecked = (/* @__PURE__ */ new Date()).toISOString();
12930
13511
  writeFileSync23(hookFile, JSON.stringify(hook, null, 2), "utf-8");
@@ -12935,9 +13516,9 @@ ${metadata.displayName} Queue:
12935
13516
  }
12936
13517
  }
12937
13518
  if (options.resetStatus && issueIds.length > 0) {
12938
- if (existsSync44(REVIEW_STATUS_FILE2)) {
13519
+ if (existsSync46(REVIEW_STATUS_FILE2)) {
12939
13520
  try {
12940
- const statuses = JSON.parse(readFileSync36(REVIEW_STATUS_FILE2, "utf-8"));
13521
+ const statuses = JSON.parse(readFileSync38(REVIEW_STATUS_FILE2, "utf-8"));
12941
13522
  let resetCount = 0;
12942
13523
  for (const issueId of issueIds) {
12943
13524
  const key = Object.keys(statuses).find((k) => k.toLowerCase() === issueId.toLowerCase());
@@ -12979,14 +13560,14 @@ function confirm2(question) {
12979
13560
  // src/cli/commands/specialists/done.ts
12980
13561
  init_esm_shims();
12981
13562
  import chalk45 from "chalk";
12982
- import { existsSync as existsSync45, readFileSync as readFileSync37, writeFileSync as writeFileSync24 } from "fs";
12983
- import { join as join44 } from "path";
13563
+ import { existsSync as existsSync47, readFileSync as readFileSync39, writeFileSync as writeFileSync24 } from "fs";
13564
+ import { join as join46 } from "path";
12984
13565
  import { homedir as homedir21 } from "os";
12985
- var REVIEW_STATUS_FILE3 = join44(homedir21(), ".panopticon", "review-status.json");
13566
+ var REVIEW_STATUS_FILE3 = join46(homedir21(), ".panopticon", "review-status.json");
12986
13567
  function loadReviewStatuses2() {
12987
13568
  try {
12988
- if (existsSync45(REVIEW_STATUS_FILE3)) {
12989
- return JSON.parse(readFileSync37(REVIEW_STATUS_FILE3, "utf-8"));
13569
+ if (existsSync47(REVIEW_STATUS_FILE3)) {
13570
+ return JSON.parse(readFileSync39(REVIEW_STATUS_FILE3, "utf-8"));
12990
13571
  }
12991
13572
  } catch (error) {
12992
13573
  console.error(chalk45.yellow("Warning: Could not load review statuses"));
@@ -13095,13 +13676,13 @@ function formatStatus(status) {
13095
13676
 
13096
13677
  // src/cli/commands/specialists/logs.ts
13097
13678
  init_esm_shims();
13098
- import { existsSync as existsSync46 } from "fs";
13679
+ import { existsSync as existsSync48 } from "fs";
13099
13680
  import { exec as exec18 } from "child_process";
13100
13681
  import { promisify as promisify18 } from "util";
13101
13682
  var execAsync18 = promisify18(exec18);
13102
13683
  async function listLogsCommand(project2, type, options) {
13103
13684
  try {
13104
- const { listRunLogs } = await import("../specialist-logs-CVKD3YJ3.js");
13685
+ const { listRunLogs } = await import("../specialist-logs-XJB5TCKJ.js");
13105
13686
  const limit = options.limit ? parseInt(options.limit) : 10;
13106
13687
  const runs = listRunLogs(project2, type, { limit });
13107
13688
  if (options.json) {
@@ -13146,7 +13727,7 @@ View a specific run: pan specialists logs ${project2} ${type} <runId>
13146
13727
  }
13147
13728
  async function viewLogCommand(project2, type, runId, options) {
13148
13729
  try {
13149
- const { getRunLog, parseLogMetadata, getRunLogPath } = await import("../specialist-logs-CVKD3YJ3.js");
13730
+ const { getRunLog, parseLogMetadata, getRunLogPath } = await import("../specialist-logs-XJB5TCKJ.js");
13150
13731
  const content = getRunLog(project2, type, runId);
13151
13732
  if (!content) {
13152
13733
  console.error(`\u274C Run log not found: ${runId}`);
@@ -13170,23 +13751,23 @@ async function viewLogCommand(project2, type, runId, options) {
13170
13751
  }
13171
13752
  async function tailLogCommand(project2, type) {
13172
13753
  try {
13173
- const { getRunLogPath } = await import("../specialist-logs-CVKD3YJ3.js");
13174
- const { getProjectSpecialistMetadata } = await import("../specialists-TKAP6T6Z.js");
13754
+ const { getRunLogPath } = await import("../specialist-logs-XJB5TCKJ.js");
13755
+ const { getProjectSpecialistMetadata } = await import("../specialists-5LBRHYFA.js");
13175
13756
  const metadata = getProjectSpecialistMetadata(project2, type);
13176
13757
  if (!metadata.currentRun) {
13177
13758
  console.error(`\u274C No active run for ${project2}/${type}`);
13178
13759
  process.exit(1);
13179
13760
  }
13180
13761
  const logPath = getRunLogPath(project2, type, metadata.currentRun);
13181
- if (!existsSync46(logPath)) {
13762
+ if (!existsSync48(logPath)) {
13182
13763
  console.error(`\u274C Log file not found: ${logPath}`);
13183
13764
  process.exit(1);
13184
13765
  }
13185
13766
  console.log(`\u{1F4E1} Following ${project2}/${type} (${metadata.currentRun})...`);
13186
13767
  console.log(` Press Ctrl+C to stop
13187
13768
  `);
13188
- const { spawn } = await import("child_process");
13189
- const tail = spawn("tail", ["-f", logPath], {
13769
+ const { spawn: spawn2 } = await import("child_process");
13770
+ const tail = spawn2("tail", ["-f", logPath], {
13190
13771
  stdio: "inherit"
13191
13772
  });
13192
13773
  process.on("SIGINT", () => {
@@ -13240,7 +13821,7 @@ async function cleanupLogsCommand(projectOrAll, type, options) {
13240
13821
  console.log(" Use --force to confirm.");
13241
13822
  process.exit(1);
13242
13823
  }
13243
- const { cleanupAllLogs } = await import("../specialist-logs-CVKD3YJ3.js");
13824
+ const { cleanupAllLogs } = await import("../specialist-logs-XJB5TCKJ.js");
13244
13825
  console.log("\u{1F9F9} Cleaning up old logs for all projects...\n");
13245
13826
  const results = cleanupAllLogs();
13246
13827
  console.log(`
@@ -13267,8 +13848,8 @@ async function cleanupLogsCommand(projectOrAll, type, options) {
13267
13848
  console.log(" Use --force to confirm.");
13268
13849
  process.exit(1);
13269
13850
  }
13270
- const { cleanupOldLogs } = await import("../specialist-logs-CVKD3YJ3.js");
13271
- const { getSpecialistRetention } = await import("../projects-JEIVIYC6.js");
13851
+ const { cleanupOldLogs } = await import("../specialist-logs-XJB5TCKJ.js");
13852
+ const { getSpecialistRetention } = await import("../projects-CFX3RTDL.js");
13272
13853
  const retention = getSpecialistRetention(projectOrAll);
13273
13854
  console.log(`\u{1F9F9} Cleaning up old logs for ${projectOrAll}/${type}...`);
13274
13855
  console.log(` Retention: ${retention.max_days} days or ${retention.max_runs} runs
@@ -13306,8 +13887,8 @@ import ora19 from "ora";
13306
13887
  // src/lib/convoy.ts
13307
13888
  init_esm_shims();
13308
13889
  init_tmux();
13309
- import { existsSync as existsSync47, mkdirSync as mkdirSync24, writeFileSync as writeFileSync25, readFileSync as readFileSync39, readdirSync as readdirSync18 } from "fs";
13310
- import { join as join45 } from "path";
13890
+ import { existsSync as existsSync49, mkdirSync as mkdirSync24, writeFileSync as writeFileSync25, readFileSync as readFileSync41, readdirSync as readdirSync19 } from "fs";
13891
+ import { join as join47 } from "path";
13311
13892
  import { homedir as homedir22 } from "os";
13312
13893
  import { exec as exec19 } from "child_process";
13313
13894
  import { promisify as promisify19 } from "util";
@@ -13416,13 +13997,13 @@ function getExecutionOrder(template) {
13416
13997
  init_paths();
13417
13998
  init_work_type_router();
13418
13999
  var execAsync19 = promisify19(exec19);
13419
- var CONVOY_DIR = join45(homedir22(), ".panopticon", "convoys");
14000
+ var CONVOY_DIR = join47(homedir22(), ".panopticon", "convoys");
13420
14001
  function getConvoyStateFile(convoyId) {
13421
- return join45(CONVOY_DIR, `${convoyId}.json`);
14002
+ return join47(CONVOY_DIR, `${convoyId}.json`);
13422
14003
  }
13423
14004
  function getConvoyOutputDir(convoyId, template) {
13424
14005
  const baseDir = template.config?.outputDir || ".panopticon/convoy-output";
13425
- return join45(process.cwd(), baseDir, convoyId);
14006
+ return join47(process.cwd(), baseDir, convoyId);
13426
14007
  }
13427
14008
  function saveConvoyState(state) {
13428
14009
  mkdirSync24(CONVOY_DIR, { recursive: true });
@@ -13430,11 +14011,11 @@ function saveConvoyState(state) {
13430
14011
  }
13431
14012
  function loadConvoyState(convoyId) {
13432
14013
  const stateFile = getConvoyStateFile(convoyId);
13433
- if (!existsSync47(stateFile)) {
14014
+ if (!existsSync49(stateFile)) {
13434
14015
  return void 0;
13435
14016
  }
13436
14017
  try {
13437
- const content = readFileSync39(stateFile, "utf-8");
14018
+ const content = readFileSync41(stateFile, "utf-8");
13438
14019
  return JSON.parse(content);
13439
14020
  } catch {
13440
14021
  return void 0;
@@ -13444,10 +14025,10 @@ function getConvoyStatus(convoyId) {
13444
14025
  return loadConvoyState(convoyId);
13445
14026
  }
13446
14027
  function listConvoys(filter) {
13447
- if (!existsSync47(CONVOY_DIR)) {
14028
+ if (!existsSync49(CONVOY_DIR)) {
13448
14029
  return [];
13449
14030
  }
13450
- const files = readdirSync18(CONVOY_DIR).filter((f) => f.endsWith(".json"));
14031
+ const files = readdirSync19(CONVOY_DIR).filter((f) => f.endsWith(".json"));
13451
14032
  const convoys = [];
13452
14033
  for (const file of files) {
13453
14034
  const convoyId = file.replace(".json", "");
@@ -13463,10 +14044,10 @@ function listConvoys(filter) {
13463
14044
  );
13464
14045
  }
13465
14046
  function parseAgentTemplate(templatePath) {
13466
- if (!existsSync47(templatePath)) {
14047
+ if (!existsSync49(templatePath)) {
13467
14048
  throw new Error(`Agent template not found: ${templatePath}`);
13468
14049
  }
13469
- const content = readFileSync39(templatePath, "utf-8");
14050
+ const content = readFileSync41(templatePath, "utf-8");
13470
14051
  const frontmatterMatch = content.match(/^---\n([\s\S]+?)\n---\n([\s\S]*)$/);
13471
14052
  if (!frontmatterMatch) {
13472
14053
  throw new Error(`Invalid agent template format (missing frontmatter): ${templatePath}`);
@@ -13492,7 +14073,7 @@ function mapConvoyRoleToWorkType(role) {
13492
14073
  }
13493
14074
  async function spawnConvoyAgent(convoy, agent, agentState, context) {
13494
14075
  const { role, subagent } = agent;
13495
- const templatePath = join45(AGENTS_DIR, `${subagent}.md`);
14076
+ const templatePath = join47(AGENTS_DIR, `${subagent}.md`);
13496
14077
  const template = parseAgentTemplate(templatePath);
13497
14078
  let model = template.model;
13498
14079
  try {
@@ -13531,7 +14112,7 @@ ${context.issueId ? `**Issue ID**: ${context.issueId}` : ""}
13531
14112
  `;
13532
14113
  prompt = contextInstructions + prompt;
13533
14114
  mkdirSync24(convoy.outputDir, { recursive: true });
13534
- const promptFile = join45(convoy.outputDir, `${role}-prompt.md`);
14115
+ const promptFile = join47(convoy.outputDir, `${role}-prompt.md`);
13535
14116
  writeFileSync25(promptFile, prompt);
13536
14117
  const claudeCmd = `claude --dangerously-skip-permissions --model ${model}`;
13537
14118
  createSession(agentState.tmuxSession, convoy.context.projectPath, claudeCmd, {
@@ -13570,7 +14151,7 @@ async function startConvoy(templateName, context) {
13570
14151
  };
13571
14152
  for (const agent of template.agents) {
13572
14153
  const tmuxSession = `${convoyId}-${agent.role}`;
13573
- const outputFile = join45(outputDir, `${agent.role}.md`);
14154
+ const outputFile = join47(outputDir, `${agent.role}.md`);
13574
14155
  state.agents.push({
13575
14156
  role: agent.role,
13576
14157
  subagent: agent.subagent,
@@ -13605,8 +14186,8 @@ async function executePhase(convoy, template, phaseAgents, context) {
13605
14186
  const agentContext = { ...context };
13606
14187
  for (const depRole of deps) {
13607
14188
  const depAgent = convoy.agents.find((a) => a.role === depRole);
13608
- if (depAgent?.outputFile && existsSync47(depAgent.outputFile)) {
13609
- agentContext[`${depRole}_output`] = readFileSync39(depAgent.outputFile, "utf-8");
14189
+ if (depAgent?.outputFile && existsSync49(depAgent.outputFile)) {
14190
+ agentContext[`${depRole}_output`] = readFileSync41(depAgent.outputFile, "utf-8");
13610
14191
  }
13611
14192
  }
13612
14193
  spawnPromises.push(spawnConvoyAgent(convoy, agent, agentState, agentContext));
@@ -13669,7 +14250,7 @@ function updateAgentStatuses(convoy) {
13669
14250
  agent.status = "completed";
13670
14251
  agent.completedAt = (/* @__PURE__ */ new Date()).toISOString();
13671
14252
  updated = true;
13672
- if (agent.outputFile && existsSync47(agent.outputFile)) {
14253
+ if (agent.outputFile && existsSync49(agent.outputFile)) {
13673
14254
  agent.exitCode = 0;
13674
14255
  } else {
13675
14256
  agent.exitCode = 1;
@@ -13915,30 +14496,30 @@ function registerConvoyCommands(program2) {
13915
14496
  init_esm_shims();
13916
14497
  init_projects();
13917
14498
  import chalk50 from "chalk";
13918
- import { existsSync as existsSync48, readFileSync as readFileSync40, symlinkSync as symlinkSync2, mkdirSync as mkdirSync25, readdirSync as readdirSync19, statSync as statSync9 } from "fs";
13919
- import { join as join46, resolve, dirname as dirname14 } from "path";
14499
+ import { existsSync as existsSync50, readFileSync as readFileSync42, symlinkSync as symlinkSync2, mkdirSync as mkdirSync25, readdirSync as readdirSync20, statSync as statSync10 } from "fs";
14500
+ import { join as join48, resolve, dirname as dirname14 } from "path";
13920
14501
  import { fileURLToPath as fileURLToPath4 } from "url";
13921
14502
  var __filename5 = fileURLToPath4(import.meta.url);
13922
14503
  var __dirname5 = dirname14(__filename5);
13923
- var BUNDLED_HOOKS_DIR = join46(__dirname5, "..", "..", "scripts", "git-hooks");
14504
+ var BUNDLED_HOOKS_DIR = join48(__dirname5, "..", "..", "scripts", "git-hooks");
13924
14505
  function installGitHooks(gitDir) {
13925
- const hooksTarget = join46(gitDir, "hooks");
14506
+ const hooksTarget = join48(gitDir, "hooks");
13926
14507
  let installed = 0;
13927
- if (!existsSync48(hooksTarget)) {
14508
+ if (!existsSync50(hooksTarget)) {
13928
14509
  mkdirSync25(hooksTarget, { recursive: true });
13929
14510
  }
13930
- if (!existsSync48(BUNDLED_HOOKS_DIR)) {
14511
+ if (!existsSync50(BUNDLED_HOOKS_DIR)) {
13931
14512
  return 0;
13932
14513
  }
13933
14514
  try {
13934
- const hooks = readdirSync19(BUNDLED_HOOKS_DIR).filter((f) => {
13935
- const p = join46(BUNDLED_HOOKS_DIR, f);
13936
- return existsSync48(p) && statSync9(p).isFile();
14515
+ const hooks = readdirSync20(BUNDLED_HOOKS_DIR).filter((f) => {
14516
+ const p = join48(BUNDLED_HOOKS_DIR, f);
14517
+ return existsSync50(p) && statSync10(p).isFile();
13937
14518
  });
13938
14519
  for (const hook of hooks) {
13939
- const source = join46(BUNDLED_HOOKS_DIR, hook);
13940
- const target = join46(hooksTarget, hook);
13941
- if (existsSync48(target)) {
14520
+ const source = join48(BUNDLED_HOOKS_DIR, hook);
14521
+ const target = join48(hooksTarget, hook);
14522
+ if (existsSync50(target)) {
13942
14523
  try {
13943
14524
  const { readlinkSync: readlinkSync2 } = __require("fs");
13944
14525
  if (readlinkSync2(target) === source) {
@@ -13947,7 +14528,7 @@ function installGitHooks(gitDir) {
13947
14528
  } catch {
13948
14529
  }
13949
14530
  }
13950
- if (existsSync48(target)) {
14531
+ if (existsSync50(target)) {
13951
14532
  const { renameSync: renameSync4 } = __require("fs");
13952
14533
  renameSync4(target, `${target}.backup`);
13953
14534
  }
@@ -13960,7 +14541,7 @@ function installGitHooks(gitDir) {
13960
14541
  }
13961
14542
  async function projectAddCommand(projectPath, options = {}) {
13962
14543
  const fullPath = resolve(projectPath);
13963
- if (!existsSync48(fullPath)) {
14544
+ if (!existsSync50(fullPath)) {
13964
14545
  console.log(chalk50.red(`Path does not exist: ${fullPath}`));
13965
14546
  return;
13966
14547
  }
@@ -13975,9 +14556,9 @@ async function projectAddCommand(projectPath, options = {}) {
13975
14556
  }
13976
14557
  let linearTeam = options.linearTeam;
13977
14558
  if (!linearTeam) {
13978
- const projectToml = join46(fullPath, ".panopticon", "project.toml");
13979
- if (existsSync48(projectToml)) {
13980
- const content = readFileSync40(projectToml, "utf-8");
14559
+ const projectToml = join48(fullPath, ".panopticon", "project.toml");
14560
+ if (existsSync50(projectToml)) {
14561
+ const content = readFileSync42(projectToml, "utf-8");
13981
14562
  const match = content.match(/team\s*=\s*"([^"]+)"/);
13982
14563
  if (match) linearTeam = match[1];
13983
14564
  }
@@ -14003,19 +14584,19 @@ async function projectAddCommand(projectPath, options = {}) {
14003
14584
  console.log(chalk50.dim(` Rally project: ${options.rallyProject}`));
14004
14585
  }
14005
14586
  console.log("");
14006
- const hasDevcontainer = existsSync48(join46(fullPath, ".devcontainer"));
14007
- const hasInfra = existsSync48(join46(fullPath, "infra"));
14008
- const hasDevcontainerTemplate = existsSync48(join46(fullPath, "infra", ".devcontainer-template")) || existsSync48(join46(fullPath, ".devcontainer-template"));
14009
- const hasRootGit = existsSync48(join46(fullPath, ".git"));
14587
+ const hasDevcontainer = existsSync50(join48(fullPath, ".devcontainer"));
14588
+ const hasInfra = existsSync50(join48(fullPath, "infra"));
14589
+ const hasDevcontainerTemplate = existsSync50(join48(fullPath, "infra", ".devcontainer-template")) || existsSync50(join48(fullPath, ".devcontainer-template"));
14590
+ const hasRootGit = existsSync50(join48(fullPath, ".git"));
14010
14591
  const subRepos = [];
14011
14592
  if (!hasRootGit) {
14012
- const { readdirSync: readdirSync21, statSync: statSync11 } = await import("fs");
14593
+ const { readdirSync: readdirSync22, statSync: statSync12 } = await import("fs");
14013
14594
  try {
14014
- const entries = readdirSync21(fullPath);
14595
+ const entries = readdirSync22(fullPath);
14015
14596
  for (const entry of entries) {
14016
- const entryPath = join46(fullPath, entry);
14597
+ const entryPath = join48(fullPath, entry);
14017
14598
  try {
14018
- if (statSync11(entryPath).isDirectory() && existsSync48(join46(entryPath, ".git"))) {
14599
+ if (statSync12(entryPath).isDirectory() && existsSync50(join48(entryPath, ".git"))) {
14019
14600
  subRepos.push(entry);
14020
14601
  }
14021
14602
  } catch {
@@ -14025,15 +14606,20 @@ async function projectAddCommand(projectPath, options = {}) {
14025
14606
  }
14026
14607
  }
14027
14608
  const isPolyrepo = !hasRootGit && subRepos.length > 0;
14609
+ try {
14610
+ const { preTrustDirectory } = await import("../workspace-manager-E434Z45T.js");
14611
+ preTrustDirectory(fullPath);
14612
+ } catch {
14613
+ }
14028
14614
  let hooksInstalled = 0;
14029
14615
  if (hasRootGit) {
14030
- hooksInstalled = installGitHooks(join46(fullPath, ".git"));
14616
+ hooksInstalled = installGitHooks(join48(fullPath, ".git"));
14031
14617
  if (hooksInstalled > 0) {
14032
14618
  console.log(chalk50.green(`\u2713 Installed ${hooksInstalled} git hook(s) for branch protection`));
14033
14619
  }
14034
14620
  } else if (isPolyrepo) {
14035
14621
  for (const repo of subRepos) {
14036
- const count = installGitHooks(join46(fullPath, repo, ".git"));
14622
+ const count = installGitHooks(join48(fullPath, repo, ".git"));
14037
14623
  hooksInstalled += count;
14038
14624
  }
14039
14625
  if (hooksInstalled > 0) {
@@ -14105,7 +14691,7 @@ async function projectListCommand(options = {}) {
14105
14691
  }
14106
14692
  console.log(chalk50.bold("\nRegistered Projects:\n"));
14107
14693
  for (const { key, config: config2 } of projects) {
14108
- const exists = existsSync48(config2.path);
14694
+ const exists = existsSync50(config2.path);
14109
14695
  const statusIcon = exists ? chalk50.green("\u2713") : chalk50.red("\u2717");
14110
14696
  console.log(`${statusIcon} ${chalk50.bold(config2.name)} ${chalk50.dim(`(${key})`)}`);
14111
14697
  console.log(` ${chalk50.dim(config2.path)}`);
@@ -14139,7 +14725,7 @@ async function projectRemoveCommand(nameOrPath) {
14139
14725
  console.log(chalk50.dim(`Use 'pan project list' to see registered projects.`));
14140
14726
  }
14141
14727
  async function projectInitCommand() {
14142
- if (existsSync48(PROJECTS_CONFIG_FILE)) {
14728
+ if (existsSync50(PROJECTS_CONFIG_FILE)) {
14143
14729
  console.log(chalk50.yellow(`Config already exists: ${PROJECTS_CONFIG_FILE}`));
14144
14730
  return;
14145
14731
  }
@@ -14173,7 +14759,7 @@ async function projectShowCommand(keyOrName) {
14173
14759
  console.log(chalk50.dim(`Use 'pan project list' to see registered projects.`));
14174
14760
  process.exit(1);
14175
14761
  }
14176
- const pathExists = existsSync48(found.path);
14762
+ const pathExists = existsSync50(found.path);
14177
14763
  const pathStatus = pathExists ? chalk50.green("\u2713") : chalk50.red("\u2717");
14178
14764
  console.log(chalk50.bold(`
14179
14765
  Project: ${foundKey}
@@ -14205,10 +14791,10 @@ Project: ${foundKey}
14205
14791
  init_esm_shims();
14206
14792
  init_paths();
14207
14793
  import chalk51 from "chalk";
14208
- import { existsSync as existsSync49, readdirSync as readdirSync20, readFileSync as readFileSync41 } from "fs";
14794
+ import { existsSync as existsSync51, readdirSync as readdirSync21, readFileSync as readFileSync43 } from "fs";
14209
14795
  import { execSync as execSync6 } from "child_process";
14210
14796
  import { homedir as homedir23 } from "os";
14211
- import { join as join47 } from "path";
14797
+ import { join as join49 } from "path";
14212
14798
  function checkCommand3(cmd) {
14213
14799
  try {
14214
14800
  execSync6(`which ${cmd}`, { encoding: "utf-8", stdio: "pipe" });
@@ -14218,12 +14804,12 @@ function checkCommand3(cmd) {
14218
14804
  }
14219
14805
  }
14220
14806
  function checkDirectory(path) {
14221
- return existsSync49(path);
14807
+ return existsSync51(path);
14222
14808
  }
14223
14809
  function countItems(path) {
14224
- if (!existsSync49(path)) return 0;
14810
+ if (!existsSync51(path)) return 0;
14225
14811
  try {
14226
- return readdirSync20(path).length;
14812
+ return readdirSync21(path).length;
14227
14813
  } catch {
14228
14814
  return 0;
14229
14815
  }
@@ -14272,8 +14858,8 @@ async function doctorCommand() {
14272
14858
  }
14273
14859
  }
14274
14860
  if (checkDirectory(CLAUDE_DIR)) {
14275
- const skillsCount = countItems(join47(CLAUDE_DIR, "skills"));
14276
- const commandsCount = countItems(join47(CLAUDE_DIR, "commands"));
14861
+ const skillsCount = countItems(join49(CLAUDE_DIR, "skills"));
14862
+ const commandsCount = countItems(join49(CLAUDE_DIR, "commands"));
14277
14863
  checks.push({
14278
14864
  name: "Claude Code Skills",
14279
14865
  status: skillsCount > 0 ? "ok" : "warn",
@@ -14294,8 +14880,8 @@ async function doctorCommand() {
14294
14880
  fix: "Install Claude Code first"
14295
14881
  });
14296
14882
  }
14297
- const envFile = join47(homedir23(), ".panopticon.env");
14298
- if (existsSync49(envFile)) {
14883
+ const envFile = join49(homedir23(), ".panopticon.env");
14884
+ if (existsSync51(envFile)) {
14299
14885
  checks.push({ name: "Config File", status: "ok", message: "~/.panopticon.env exists" });
14300
14886
  } else {
14301
14887
  checks.push({
@@ -14307,8 +14893,8 @@ async function doctorCommand() {
14307
14893
  }
14308
14894
  if (process.env.LINEAR_API_KEY) {
14309
14895
  checks.push({ name: "LINEAR_API_KEY", status: "ok", message: "Set in environment" });
14310
- } else if (existsSync49(envFile)) {
14311
- const content = readFileSync41(envFile, "utf-8");
14896
+ } else if (existsSync51(envFile)) {
14897
+ const content = readFileSync43(envFile, "utf-8");
14312
14898
  if (content.includes("LINEAR_API_KEY")) {
14313
14899
  checks.push({ name: "LINEAR_API_KEY", status: "ok", message: "Set in config file" });
14314
14900
  } else {
@@ -14376,15 +14962,15 @@ init_esm_shims();
14376
14962
  init_config();
14377
14963
  import { execSync as execSync7 } from "child_process";
14378
14964
  import chalk52 from "chalk";
14379
- import { readFileSync as readFileSync42 } from "fs";
14965
+ import { readFileSync as readFileSync44 } from "fs";
14380
14966
  import { fileURLToPath as fileURLToPath5 } from "url";
14381
- import { dirname as dirname15, join as join48 } from "path";
14967
+ import { dirname as dirname15, join as join50 } from "path";
14382
14968
  function getCurrentVersion() {
14383
14969
  try {
14384
14970
  const __filename6 = fileURLToPath5(import.meta.url);
14385
14971
  const __dirname6 = dirname15(__filename6);
14386
- const pkgPath = join48(__dirname6, "..", "..", "..", "package.json");
14387
- const pkg = JSON.parse(readFileSync42(pkgPath, "utf-8"));
14972
+ const pkgPath = join50(__dirname6, "..", "..", "..", "package.json");
14973
+ const pkg = JSON.parse(readFileSync44(pkgPath, "utf-8"));
14388
14974
  return pkg.version;
14389
14975
  } catch {
14390
14976
  return "unknown";
@@ -14470,8 +15056,8 @@ init_esm_shims();
14470
15056
  init_projects();
14471
15057
  import chalk53 from "chalk";
14472
15058
  import ora21 from "ora";
14473
- import { existsSync as existsSync50, readFileSync as readFileSync43, writeFileSync as writeFileSync26, mkdirSync as mkdirSync26, statSync as statSync10 } from "fs";
14474
- import { join as join49, dirname as dirname16 } from "path";
15059
+ import { existsSync as existsSync52, readFileSync as readFileSync45, writeFileSync as writeFileSync26, mkdirSync as mkdirSync26, statSync as statSync11 } from "fs";
15060
+ import { join as join51, dirname as dirname16 } from "path";
14475
15061
  import { exec as exec20 } from "child_process";
14476
15062
  import { promisify as promisify20 } from "util";
14477
15063
  var execAsync20 = promisify20(exec20);
@@ -14533,9 +15119,9 @@ async function snapshotCommand(options) {
14533
15119
  `));
14534
15120
  return;
14535
15121
  }
14536
- const outputPath = options.output || dbConfig.seed_file || join49(projectConfig.path, "infra", "seed", "seed.sql");
15122
+ const outputPath = options.output || dbConfig.seed_file || join51(projectConfig.path, "infra", "seed", "seed.sql");
14537
15123
  const outputDir = dirname16(outputPath);
14538
- if (!existsSync50(outputDir)) {
15124
+ if (!existsSync52(outputDir)) {
14539
15125
  mkdirSync26(outputDir, { recursive: true });
14540
15126
  }
14541
15127
  spinner.text = "Running snapshot command...";
@@ -14558,8 +15144,8 @@ async function snapshotCommand(options) {
14558
15144
  try {
14559
15145
  await execAsync20(fullCmd, { timeout: 3e5 });
14560
15146
  } catch (error) {
14561
- if (existsSync50(outputPath)) {
14562
- const content2 = readFileSync43(outputPath, "utf-8");
15147
+ if (existsSync52(outputPath)) {
15148
+ const content2 = readFileSync45(outputPath, "utf-8");
14563
15149
  if (content2.includes("PostgreSQL database dump")) {
14564
15150
  spinner.warn("Snapshot completed with warnings (stderr captured)");
14565
15151
  console.log(chalk53.dim(" Run `pan db clean` to remove stderr noise from the file"));
@@ -14572,7 +15158,7 @@ async function snapshotCommand(options) {
14572
15158
  return;
14573
15159
  }
14574
15160
  }
14575
- const content = readFileSync43(outputPath, "utf-8");
15161
+ const content = readFileSync45(outputPath, "utf-8");
14576
15162
  if (content.includes("Defaulted container") || content.includes("Unable to use a TTY")) {
14577
15163
  spinner.text = "Cleaning kubectl output from snapshot...";
14578
15164
  await cleanFile(outputPath);
@@ -14586,7 +15172,7 @@ async function snapshotCommand(options) {
14586
15172
  }
14587
15173
  }
14588
15174
  spinner.succeed(`Snapshot saved to ${outputPath}`);
14589
- const stats = statSync10(outputPath);
15175
+ const stats = statSync11(outputPath);
14590
15176
  const sizeMB = (stats.size / 1024 / 1024).toFixed(2);
14591
15177
  console.log(chalk53.dim(` Size: ${sizeMB} MB`));
14592
15178
  } catch (error) {
@@ -14604,14 +15190,14 @@ async function seedCommand(workspaceOrIssue, options) {
14604
15190
  spinner.fail("Could not find project workspace configuration");
14605
15191
  return;
14606
15192
  }
14607
- const workspacePath = join49(projectConfig.path, projectConfig.workspace.workspaces_dir || "workspaces", folderName);
14608
- if (!existsSync50(workspacePath)) {
15193
+ const workspacePath = join51(projectConfig.path, projectConfig.workspace.workspaces_dir || "workspaces", folderName);
15194
+ if (!existsSync52(workspacePath)) {
14609
15195
  spinner.fail(`Workspace not found: ${workspacePath}`);
14610
15196
  return;
14611
15197
  }
14612
15198
  const dbConfig = projectConfig.workspace.database;
14613
15199
  const seedFile = options.file || dbConfig?.seed_file;
14614
- if (!seedFile || !existsSync50(seedFile)) {
15200
+ if (!seedFile || !existsSync52(seedFile)) {
14615
15201
  spinner.fail(`Seed file not found: ${seedFile || "(not configured)"}`);
14616
15202
  console.log(chalk53.dim("\nConfigure seed_file in projects.yaml or use --file"));
14617
15203
  return;
@@ -14753,11 +15339,11 @@ async function statusCommand5(workspaceOrIssue) {
14753
15339
  async function cleanCommand(file, options) {
14754
15340
  const spinner = ora21("Cleaning database dump file...").start();
14755
15341
  try {
14756
- if (!existsSync50(file)) {
15342
+ if (!existsSync52(file)) {
14757
15343
  spinner.fail(`File not found: ${file}`);
14758
15344
  return;
14759
15345
  }
14760
- const content = readFileSync43(file, "utf-8");
15346
+ const content = readFileSync45(file, "utf-8");
14761
15347
  const lines = content.split("\n");
14762
15348
  const patternsToRemove = [
14763
15349
  /^Defaulted container/,
@@ -14827,7 +15413,7 @@ async function cleanCommand(file, options) {
14827
15413
  }
14828
15414
  }
14829
15415
  async function cleanFile(filePath) {
14830
- const content = readFileSync43(filePath, "utf-8");
15416
+ const content = readFileSync45(filePath, "utf-8");
14831
15417
  const lines = content.split("\n");
14832
15418
  let startIndex = 0;
14833
15419
  for (let i = 0; i < lines.length; i++) {
@@ -14881,11 +15467,11 @@ async function configCommand(project2) {
14881
15467
  return;
14882
15468
  }
14883
15469
  if (dbConfig.seed_file) {
14884
- const exists = existsSync50(dbConfig.seed_file);
15470
+ const exists = existsSync52(dbConfig.seed_file);
14885
15471
  console.log(` Seed file: ${dbConfig.seed_file}`);
14886
15472
  console.log(chalk53.dim(` Status: ${exists ? chalk53.green("exists") : chalk53.red("not found")}`));
14887
15473
  if (exists) {
14888
- const stats = statSync10(dbConfig.seed_file);
15474
+ const stats = statSync11(dbConfig.seed_file);
14889
15475
  console.log(chalk53.dim(` Size: ${(stats.size / 1024 / 1024).toFixed(2)} MB`));
14890
15476
  }
14891
15477
  }
@@ -14910,8 +15496,8 @@ async function configCommand(project2) {
14910
15496
  init_esm_shims();
14911
15497
  import chalk54 from "chalk";
14912
15498
  import ora22 from "ora";
14913
- import { existsSync as existsSync51, readFileSync as readFileSync44 } from "fs";
14914
- import { join as join50 } from "path";
15499
+ import { existsSync as existsSync53, readFileSync as readFileSync46 } from "fs";
15500
+ import { join as join52 } from "path";
14915
15501
  import { exec as exec21, execSync as execSync8 } from "child_process";
14916
15502
  import { promisify as promisify21 } from "util";
14917
15503
  import { platform } from "os";
@@ -14920,7 +15506,7 @@ function detectPlatform2() {
14920
15506
  const os = platform();
14921
15507
  if (os === "linux") {
14922
15508
  try {
14923
- const release = readFileSync44("/proc/version", "utf8").toLowerCase();
15509
+ const release = readFileSync46("/proc/version", "utf8").toLowerCase();
14924
15510
  if (release.includes("microsoft") || release.includes("wsl")) {
14925
15511
  return "wsl";
14926
15512
  }
@@ -14958,8 +15544,8 @@ async function compactCommand(options) {
14958
15544
  console.log(chalk54.dim("Install beads: https://github.com/steveyegge/beads"));
14959
15545
  process.exit(1);
14960
15546
  }
14961
- const beadsDir = join50(cwd, ".beads");
14962
- if (!existsSync51(beadsDir)) {
15547
+ const beadsDir = join52(cwd, ".beads");
15548
+ if (!existsSync53(beadsDir)) {
14963
15549
  console.error(chalk54.red("Error: No .beads directory found in current directory"));
14964
15550
  console.log(chalk54.dim("Run bd init to initialize beads"));
14965
15551
  process.exit(1);
@@ -15018,8 +15604,8 @@ async function statsCommand() {
15018
15604
  console.error(chalk54.red("Error: bd (beads) CLI not found"));
15019
15605
  process.exit(1);
15020
15606
  }
15021
- const beadsDir = join50(cwd, ".beads");
15022
- if (!existsSync51(beadsDir)) {
15607
+ const beadsDir = join52(cwd, ".beads");
15608
+ if (!existsSync53(beadsDir)) {
15023
15609
  console.error(chalk54.red("Error: No .beads directory found"));
15024
15610
  process.exit(1);
15025
15611
  }
@@ -15647,8 +16233,8 @@ import chalk59 from "chalk";
15647
16233
  import ora27 from "ora";
15648
16234
  import { exec as exec22 } from "child_process";
15649
16235
  import { promisify as promisify22 } from "util";
15650
- import { existsSync as existsSync52, readFileSync as readFileSync45 } from "fs";
15651
- import { join as join51 } from "path";
16236
+ import { existsSync as existsSync54, readFileSync as readFileSync47 } from "fs";
16237
+ import { join as join53 } from "path";
15652
16238
  import { homedir as homedir24 } from "os";
15653
16239
  var execAsync22 = promisify22(exec22);
15654
16240
  async function setupCommand() {
@@ -15661,16 +16247,16 @@ async function setupCommand() {
15661
16247
  console.log("");
15662
16248
  console.log(chalk59.bold(" Step 2: SSH Key Configuration"));
15663
16249
  console.log("");
15664
- const sshDir = join51(homedir24(), ".ssh");
15665
- const defaultKeyPath = join51(sshDir, "id_ed25519");
15666
- const rsaKeyPath = join51(sshDir, "id_rsa");
16250
+ const sshDir = join53(homedir24(), ".ssh");
16251
+ const defaultKeyPath = join53(sshDir, "id_ed25519");
16252
+ const rsaKeyPath = join53(sshDir, "id_rsa");
15667
16253
  let sshKeyExists = false;
15668
16254
  let keyPath = "";
15669
- if (existsSync52(defaultKeyPath)) {
16255
+ if (existsSync54(defaultKeyPath)) {
15670
16256
  sshKeyExists = true;
15671
16257
  keyPath = defaultKeyPath;
15672
16258
  console.log(` ${chalk59.green("\u2713")} SSH key found: ${chalk59.dim(defaultKeyPath)}`);
15673
- } else if (existsSync52(rsaKeyPath)) {
16259
+ } else if (existsSync54(rsaKeyPath)) {
15674
16260
  sshKeyExists = true;
15675
16261
  keyPath = rsaKeyPath;
15676
16262
  console.log(` ${chalk59.green("\u2713")} SSH key found: ${chalk59.dim(rsaKeyPath)}`);
@@ -15684,8 +16270,8 @@ async function setupCommand() {
15684
16270
  }
15685
16271
  if (sshKeyExists) {
15686
16272
  const pubKeyPath = `${keyPath}.pub`;
15687
- if (existsSync52(pubKeyPath)) {
15688
- const pubKey = readFileSync45(pubKeyPath, "utf8").trim();
16273
+ if (existsSync54(pubKeyPath)) {
16274
+ const pubKey = readFileSync47(pubKeyPath, "utf8").trim();
15689
16275
  console.log("");
15690
16276
  console.log(" Your public key (add this to exe.dev if not already):");
15691
16277
  console.log("");
@@ -15712,8 +16298,8 @@ async function setupCommand() {
15712
16298
  console.log(" Your public key to add:");
15713
16299
  if (sshKeyExists) {
15714
16300
  const pubKeyPath = `${keyPath}.pub`;
15715
- if (existsSync52(pubKeyPath)) {
15716
- const pubKey = readFileSync45(pubKeyPath, "utf8").trim();
16301
+ if (existsSync54(pubKeyPath)) {
16302
+ const pubKey = readFileSync47(pubKeyPath, "utf8").trim();
15717
16303
  console.log(chalk59.dim(` ${pubKey}`));
15718
16304
  }
15719
16305
  } else {
@@ -15798,9 +16384,9 @@ import chalk60 from "chalk";
15798
16384
 
15799
16385
  // src/lib/env-loader.ts
15800
16386
  init_esm_shims();
15801
- import { join as join52 } from "path";
16387
+ import { join as join54 } from "path";
15802
16388
  import { homedir as homedir25 } from "os";
15803
- var ENV_FILE_PATH = join52(homedir25(), ".panopticon.env");
16389
+ var ENV_FILE_PATH = join54(homedir25(), ".panopticon.env");
15804
16390
  function getShadowModeFromEnv() {
15805
16391
  const value = process.env.SHADOW_MODE;
15806
16392
  if (!value) return false;
@@ -15895,10 +16481,10 @@ Shadowed issues: ${shadowedIssues.length}`));
15895
16481
  }
15896
16482
 
15897
16483
  // src/cli/index.ts
15898
- var PANOPTICON_ENV_FILE = join53(homedir26(), ".panopticon.env");
15899
- if (existsSync53(PANOPTICON_ENV_FILE)) {
16484
+ var PANOPTICON_ENV_FILE = join55(homedir26(), ".panopticon.env");
16485
+ if (existsSync55(PANOPTICON_ENV_FILE)) {
15900
16486
  try {
15901
- const envContent = readFileSync46(PANOPTICON_ENV_FILE, "utf-8");
16487
+ const envContent = readFileSync48(PANOPTICON_ENV_FILE, "utf-8");
15902
16488
  for (const line of envContent.split("\n")) {
15903
16489
  const trimmed = line.trim();
15904
16490
  if (!trimmed || trimmed.startsWith("#")) continue;
@@ -15915,7 +16501,7 @@ if (existsSync53(PANOPTICON_ENV_FILE)) {
15915
16501
  }
15916
16502
  }
15917
16503
  var program = new Command();
15918
- program.name("pan").description("Multi-agent orchestration for AI coding assistants").version(JSON.parse(readFileSync46(join53(import.meta.dirname, "../../package.json"), "utf-8")).version);
16504
+ program.name("pan").description("Multi-agent orchestration for AI coding assistants").version(JSON.parse(readFileSync48(join55(import.meta.dirname, "../../package.json"), "utf-8")).version);
15919
16505
  program.command("init").description("Initialize Panopticon (~/.panopticon/)").action(initCommand);
15920
16506
  program.command("sync").description("Sync skills/agents/rules to devroot").option("--dry-run", "Show what would be synced").option("--force", "Overwrite files modified since Panopticon installed them").option("--diff", "Show diff for modified files").option("--backup-only", "Only create backup").action(syncCommand);
15921
16507
  program.command("restore [timestamp]").description("Restore from backup").action(restoreCommand);
@@ -15936,24 +16522,24 @@ registerBeadsCommands(program);
15936
16522
  registerRemoteCommands(program);
15937
16523
  registerConfigCommand(program);
15938
16524
  program.command("migrate-config").description("Migrate from settings.json to config.yaml").option("--force", "Force migration even if config.yaml exists").option("--preview", "Preview migration without applying changes").option("--no-backup", "Do not back up settings.json").option("--delete-legacy", "Delete settings.json after migration").action(migrateConfigCommand);
15939
- program.command("status").description("Show running agents (shorthand for work status)").option("--json", "Output as JSON").action(statusCommand);
16525
+ program.command("status").description("Show running agents (shorthand for work status)").option("--json", "Output as JSON").option("--tldr", "Show TLDR index health across all workspaces").option("--context", "Show context window usage % for each agent").action(statusCommand);
15940
16526
  program.command("up").description("Start dashboard (and Traefik if enabled)").option("--detach", "Run in background").option("--skip-traefik", "Skip Traefik startup").action(async (options) => {
15941
- const { spawn, execSync: execSync9 } = await import("child_process");
15942
- const { join: join54, dirname: dirname17 } = await import("path");
16527
+ const { spawn: spawn2, execSync: execSync9 } = await import("child_process");
16528
+ const { join: join56, dirname: dirname17 } = await import("path");
15943
16529
  const { fileURLToPath: fileURLToPath6 } = await import("url");
15944
- const { readFileSync: readFileSync47, existsSync: existsSync54 } = await import("fs");
16530
+ const { readFileSync: readFileSync49, existsSync: existsSync56 } = await import("fs");
15945
16531
  const { parse } = await import("@iarna/toml");
15946
16532
  const __dirname6 = dirname17(fileURLToPath6(import.meta.url));
15947
- const bundledServer = join54(__dirname6, "..", "dashboard", "server.js");
15948
- const srcDashboard = join54(__dirname6, "..", "..", "src", "dashboard");
15949
- const configFile = join54(process.env.HOME || "", ".panopticon", "config.toml");
16533
+ const bundledServer = join56(__dirname6, "..", "dashboard", "server.js");
16534
+ const srcDashboard = join56(__dirname6, "..", "..", "src", "dashboard");
16535
+ const configFile = join56(process.env.HOME || "", ".panopticon", "config.toml");
15950
16536
  let traefikEnabled = false;
15951
16537
  let traefikDomain = "pan.localhost";
15952
16538
  let dashboardPort = 3010;
15953
16539
  let dashboardApiPort = 3011;
15954
- if (existsSync54(configFile)) {
16540
+ if (existsSync56(configFile)) {
15955
16541
  try {
15956
- const configContent = readFileSync47(configFile, "utf-8");
16542
+ const configContent = readFileSync49(configFile, "utf-8");
15957
16543
  const config2 = parse(configContent);
15958
16544
  traefikEnabled = config2.traefik?.enabled === true;
15959
16545
  traefikDomain = config2.traefik?.domain || "pan.localhost";
@@ -15966,7 +16552,7 @@ program.command("up").description("Start dashboard (and Traefik if enabled)").op
15966
16552
  console.log(chalk61.bold("Starting Panopticon...\n"));
15967
16553
  if (traefikEnabled && !options.skipTraefik) {
15968
16554
  try {
15969
- const { generatePanopticonTraefikConfig: generatePanopticonTraefikConfig2, ensureProjectCerts: ensureProjectCerts2, generateTlsConfig: generateTlsConfig2, cleanupStaleTlsSections } = await import("../traefik-QX4ZV4YG.js");
16555
+ const { generatePanopticonTraefikConfig: generatePanopticonTraefikConfig2, ensureProjectCerts: ensureProjectCerts2, generateTlsConfig: generateTlsConfig2, cleanupStaleTlsSections } = await import("../traefik-WFMQX2LY.js");
15970
16556
  cleanupStaleTlsSections();
15971
16557
  if (generatePanopticonTraefikConfig2()) {
15972
16558
  console.log(chalk61.dim(" Regenerated Traefik config from template"));
@@ -15983,7 +16569,7 @@ program.command("up").description("Start dashboard (and Traefik if enabled)").op
15983
16569
  }
15984
16570
  try {
15985
16571
  const { ensureBaseDomain: ensureBaseDomain2, detectDnsSyncMethod: detectDnsSyncMethod2, syncDnsToWindows: syncDnsToWindows2 } = await import("../dns-7BDJSD3E.js");
15986
- const dnsMethod = (existsSync54(configFile) ? parse(readFileSync47(configFile, "utf-8")).traefik?.dns_sync_method : null) || detectDnsSyncMethod2();
16572
+ const dnsMethod = (existsSync56(configFile) ? parse(readFileSync49(configFile, "utf-8")).traefik?.dns_sync_method : null) || detectDnsSyncMethod2();
15987
16573
  ensureBaseDomain2(dnsMethod, traefikDomain);
15988
16574
  if (dnsMethod === "wsl2hosts") {
15989
16575
  syncDnsToWindows2().catch(() => {
@@ -16006,12 +16592,12 @@ program.command("up").description("Start dashboard (and Traefik if enabled)").op
16006
16592
  }
16007
16593
  }
16008
16594
  if (traefikEnabled && !options.skipTraefik) {
16009
- const traefikDir = join54(process.env.HOME || "", ".panopticon", "traefik");
16010
- if (existsSync54(traefikDir)) {
16595
+ const traefikDir = join56(process.env.HOME || "", ".panopticon", "traefik");
16596
+ if (existsSync56(traefikDir)) {
16011
16597
  try {
16012
- const composeFile = join54(traefikDir, "docker-compose.yml");
16013
- if (existsSync54(composeFile)) {
16014
- const content = readFileSync47(composeFile, "utf-8");
16598
+ const composeFile = join56(traefikDir, "docker-compose.yml");
16599
+ if (existsSync56(composeFile)) {
16600
+ const content = readFileSync49(composeFile, "utf-8");
16015
16601
  if (!content.includes("external: true") && content.includes("panopticon:")) {
16016
16602
  const patched = content.replace(
16017
16603
  /networks:\s*\n\s*panopticon:\s*\n\s*name: panopticon\s*\n\s*driver: bridge/,
@@ -16036,8 +16622,8 @@ program.command("up").description("Start dashboard (and Traefik if enabled)").op
16036
16622
  }
16037
16623
  }
16038
16624
  }
16039
- const isProduction = existsSync54(bundledServer);
16040
- const isDevelopment = existsSync54(srcDashboard);
16625
+ const isProduction = existsSync56(bundledServer);
16626
+ const isDevelopment = existsSync56(srcDashboard);
16041
16627
  if (!isProduction && !isDevelopment) {
16042
16628
  console.error(chalk61.red("Error: Dashboard not found"));
16043
16629
  console.error(chalk61.dim("This may be a corrupted installation. Try reinstalling panopticon-cli."));
@@ -16058,11 +16644,11 @@ program.command("up").description("Start dashboard (and Traefik if enabled)").op
16058
16644
  console.log(chalk61.dim("Starting dashboard (development mode)..."));
16059
16645
  }
16060
16646
  if (options.detach) {
16061
- const child = isProduction ? spawn("node", [bundledServer], {
16647
+ const child = isProduction ? spawn2("node", [bundledServer], {
16062
16648
  detached: true,
16063
16649
  stdio: "ignore",
16064
16650
  env: { ...process.env, DASHBOARD_PORT: String(dashboardPort) }
16065
- }) : spawn("npm", ["run", "dev"], {
16651
+ }) : spawn2("npm", ["run", "dev"], {
16066
16652
  cwd: srcDashboard,
16067
16653
  detached: true,
16068
16654
  stdio: "ignore",
@@ -16096,10 +16682,10 @@ program.command("up").description("Start dashboard (and Traefik if enabled)").op
16096
16682
  console.log(` API: ${chalk61.cyan(`http://localhost:${dashboardApiPort}`)}`);
16097
16683
  }
16098
16684
  console.log(chalk61.dim("\nPress Ctrl+C to stop\n"));
16099
- const child = isProduction ? spawn("node", [bundledServer], {
16685
+ const child = isProduction ? spawn2("node", [bundledServer], {
16100
16686
  stdio: "inherit",
16101
16687
  env: { ...process.env, DASHBOARD_PORT: String(dashboardPort) }
16102
- }) : spawn("npm", ["run", "dev"], {
16688
+ }) : spawn2("npm", ["run", "dev"], {
16103
16689
  cwd: srcDashboard,
16104
16690
  stdio: "inherit",
16105
16691
  shell: true
@@ -16112,8 +16698,8 @@ program.command("up").description("Start dashboard (and Traefik if enabled)").op
16112
16698
  try {
16113
16699
  const { getTldrDaemonService: getTldrDaemonService2 } = await import("../tldr-daemon-T3THOUGT.js");
16114
16700
  const projectRoot = process.cwd();
16115
- const venvPath = join54(projectRoot, ".venv");
16116
- if (existsSync54(venvPath)) {
16701
+ const venvPath = join56(projectRoot, ".venv");
16702
+ if (existsSync56(venvPath)) {
16117
16703
  console.log(chalk61.dim("\nStarting TLDR daemon for project root..."));
16118
16704
  const tldrService = getTldrDaemonService2(projectRoot, venvPath);
16119
16705
  await tldrService.start(true);
@@ -16129,17 +16715,17 @@ program.command("up").description("Start dashboard (and Traefik if enabled)").op
16129
16715
  });
16130
16716
  program.command("down").description("Stop dashboard (and Traefik if enabled)").option("--skip-traefik", "Skip Traefik shutdown").action(async (options) => {
16131
16717
  const { execSync: execSync9 } = await import("child_process");
16132
- const { join: join54 } = await import("path");
16133
- const { readFileSync: readFileSync47, existsSync: existsSync54 } = await import("fs");
16718
+ const { join: join56 } = await import("path");
16719
+ const { readFileSync: readFileSync49, existsSync: existsSync56 } = await import("fs");
16134
16720
  const { parse } = await import("@iarna/toml");
16135
16721
  console.log(chalk61.bold("Stopping Panopticon...\n"));
16136
- const configFile = join54(process.env.HOME || "", ".panopticon", "config.toml");
16722
+ const configFile = join56(process.env.HOME || "", ".panopticon", "config.toml");
16137
16723
  let traefikEnabled = false;
16138
16724
  let dashboardPort = 3010;
16139
16725
  let dashboardApiPort = 3011;
16140
- if (existsSync54(configFile)) {
16726
+ if (existsSync56(configFile)) {
16141
16727
  try {
16142
- const configContent = readFileSync47(configFile, "utf-8");
16728
+ const configContent = readFileSync49(configFile, "utf-8");
16143
16729
  const config2 = parse(configContent);
16144
16730
  traefikEnabled = config2.traefik?.enabled === true;
16145
16731
  dashboardPort = config2.dashboard?.port || 3010;
@@ -16156,8 +16742,8 @@ program.command("down").description("Stop dashboard (and Traefik if enabled)").o
16156
16742
  console.log(chalk61.dim(" No dashboard processes found"));
16157
16743
  }
16158
16744
  if (traefikEnabled && !options.skipTraefik) {
16159
- const traefikDir = join54(process.env.HOME || "", ".panopticon", "traefik");
16160
- if (existsSync54(traefikDir)) {
16745
+ const traefikDir = join56(process.env.HOME || "", ".panopticon", "traefik");
16746
+ if (existsSync56(traefikDir)) {
16161
16747
  console.log(chalk61.dim("Stopping Traefik..."));
16162
16748
  try {
16163
16749
  execSync9("docker compose down", {
@@ -16176,8 +16762,8 @@ program.command("down").description("Stop dashboard (and Traefik if enabled)").o
16176
16762
  const { promisify: promisify23 } = await import("util");
16177
16763
  const execAsync23 = promisify23(exec23);
16178
16764
  const projectRoot = process.cwd();
16179
- const venvPath = join54(projectRoot, ".venv");
16180
- if (existsSync54(venvPath)) {
16765
+ const venvPath = join56(projectRoot, ".venv");
16766
+ if (existsSync56(venvPath)) {
16181
16767
  console.log(chalk61.dim("\nStopping TLDR daemon..."));
16182
16768
  const tldrService = getTldrDaemonService2(projectRoot, venvPath);
16183
16769
  await tldrService.stop();