panopticon-cli 0.4.33 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/README.md +96 -210
  2. package/dist/{agents-VLK4BMVA.js → agents-E43Y3HNU.js} +5 -5
  3. package/dist/{chunk-ASY7T35E.js → chunk-AAFQANKW.js} +231 -76
  4. package/dist/chunk-AAFQANKW.js.map +1 -0
  5. package/dist/{chunk-KJ2TRXNK.js → chunk-FTCPTHIJ.js} +47 -420
  6. package/dist/chunk-FTCPTHIJ.js.map +1 -0
  7. package/dist/{chunk-PI7Y3PSN.js → chunk-GR6ZZMCX.js} +25 -6
  8. package/dist/chunk-GR6ZZMCX.js.map +1 -0
  9. package/dist/chunk-HJSM6E6U.js +1038 -0
  10. package/dist/chunk-HJSM6E6U.js.map +1 -0
  11. package/dist/{chunk-BKCWRMUX.js → chunk-HZT2AOPN.js} +81 -9
  12. package/dist/chunk-HZT2AOPN.js.map +1 -0
  13. package/dist/{chunk-XFR2DLMR.js → chunk-NTO3EDB3.js} +3 -3
  14. package/dist/{chunk-XFR2DLMR.js.map → chunk-NTO3EDB3.js.map} +1 -1
  15. package/dist/{chunk-RBUO57TC.js → chunk-PPRFKTVC.js} +2 -2
  16. package/dist/chunk-PPRFKTVC.js.map +1 -0
  17. package/dist/{chunk-XKT5MHPT.js → chunk-WQG2TYCB.js} +2 -2
  18. package/dist/cli/index.js +1383 -880
  19. package/dist/cli/index.js.map +1 -1
  20. package/dist/dashboard/prompts/work-agent.md +2 -0
  21. package/dist/dashboard/public/assets/{index-UjZq6ykz.css → index-BxpjweAL.css} +1 -1
  22. package/dist/dashboard/public/assets/index-DQHkwvvJ.js +743 -0
  23. package/dist/dashboard/public/index.html +2 -2
  24. package/dist/dashboard/server.js +3593 -2052
  25. package/dist/index.d.ts +10 -1
  26. package/dist/index.js +5 -3
  27. package/dist/index.js.map +1 -1
  28. package/dist/{specialist-context-T3NBMCIE.js → specialist-context-ZC6A4M3I.js} +4 -4
  29. package/dist/{specialist-logs-CVKD3YJ3.js → specialist-logs-KLGJCEUL.js} +4 -4
  30. package/dist/{specialists-TKAP6T6Z.js → specialists-O4HWDJL5.js} +4 -4
  31. package/dist/{traefik-QX4ZV4YG.js → traefik-QN7R5I6V.js} +2 -2
  32. package/dist/{workspace-manager-KLHUCIZV.js → workspace-manager-IE4JL2JP.js} +2 -2
  33. package/package.json +1 -1
  34. package/scripts/stop-hook +7 -0
  35. package/scripts/work-agent-stop-hook +137 -0
  36. package/skills/myn-standards/SKILL.md +351 -0
  37. package/skills/write-spec/SKILL.md +138 -0
  38. package/dist/chunk-7XNJJBH6.js +0 -538
  39. package/dist/chunk-7XNJJBH6.js.map +0 -1
  40. package/dist/chunk-ASY7T35E.js.map +0 -1
  41. package/dist/chunk-BKCWRMUX.js.map +0 -1
  42. package/dist/chunk-KJ2TRXNK.js.map +0 -1
  43. package/dist/chunk-PI7Y3PSN.js.map +0 -1
  44. package/dist/chunk-RBUO57TC.js.map +0 -1
  45. package/dist/dashboard/public/assets/index-kAJqtLDO.js +0 -708
  46. /package/dist/{agents-VLK4BMVA.js.map → agents-E43Y3HNU.js.map} +0 -0
  47. /package/dist/{chunk-XKT5MHPT.js.map → chunk-WQG2TYCB.js.map} +0 -0
  48. /package/dist/{specialist-context-T3NBMCIE.js.map → specialist-context-ZC6A4M3I.js.map} +0 -0
  49. /package/dist/{specialist-logs-CVKD3YJ3.js.map → specialist-logs-KLGJCEUL.js.map} +0 -0
  50. /package/dist/{specialists-TKAP6T6Z.js.map → specialists-O4HWDJL5.js.map} +0 -0
  51. /package/dist/{traefik-QX4ZV4YG.js.map → traefik-QN7R5I6V.js.map} +0 -0
  52. /package/dist/{workspace-manager-KLHUCIZV.js.map → workspace-manager-IE4JL2JP.js.map} +0 -0
package/dist/cli/index.js CHANGED
@@ -4,7 +4,7 @@ import {
4
4
  ensureProjectCerts,
5
5
  generatePanopticonTraefikConfig,
6
6
  generateTlsConfig
7
- } from "../chunk-RBUO57TC.js";
7
+ } from "../chunk-PPRFKTVC.js";
8
8
  import {
9
9
  applyProjectTemplateOverlay,
10
10
  createWorkspace,
@@ -12,7 +12,7 @@ import {
12
12
  init_workspace_manager,
13
13
  mergeSkillsIntoWorkspace,
14
14
  removeWorkspace
15
- } from "../chunk-PI7Y3PSN.js";
15
+ } from "../chunk-GR6ZZMCX.js";
16
16
  import "../chunk-7SN4L4PH.js";
17
17
  import {
18
18
  init_remote_agents,
@@ -41,7 +41,7 @@ import {
41
41
  saveSessionId,
42
42
  spawnAgent,
43
43
  stopAgent
44
- } from "../chunk-BKCWRMUX.js";
44
+ } from "../chunk-HZT2AOPN.js";
45
45
  import {
46
46
  createShadowState,
47
47
  getPendingSyncCount,
@@ -93,7 +93,7 @@ import {
93
93
  wakeSpecialist,
94
94
  wakeSpecialistOrQueue,
95
95
  wakeSpecialistWithTask
96
- } from "../chunk-ASY7T35E.js";
96
+ } from "../chunk-AAFQANKW.js";
97
97
  import {
98
98
  checkHook,
99
99
  clearHook,
@@ -110,7 +110,7 @@ import {
110
110
  sendKeysAsync,
111
111
  sendMail,
112
112
  sessionExists
113
- } from "../chunk-KJ2TRXNK.js";
113
+ } from "../chunk-FTCPTHIJ.js";
114
114
  import "../chunk-JQBV3Q2W.js";
115
115
  import {
116
116
  addAlias,
@@ -128,19 +128,19 @@ import {
128
128
  restoreBackup,
129
129
  syncHooks,
130
130
  syncStatusline
131
- } from "../chunk-XKT5MHPT.js";
131
+ } from "../chunk-WQG2TYCB.js";
132
132
  import "../chunk-AQXETQHW.js";
133
133
  import {
134
134
  createTracker,
135
135
  createTrackerFromConfig,
136
136
  init_factory
137
- } from "../chunk-XFR2DLMR.js";
137
+ } from "../chunk-NTO3EDB3.js";
138
138
  import {
139
139
  init_config_yaml,
140
140
  init_settings,
141
141
  loadConfig as loadConfig2,
142
142
  loadSettings
143
- } from "../chunk-7XNJJBH6.js";
143
+ } from "../chunk-HJSM6E6U.js";
144
144
  import {
145
145
  getDashboardApiUrl,
146
146
  getDefaultConfig,
@@ -215,8 +215,8 @@ import {
215
215
 
216
216
  // src/cli/index.ts
217
217
  init_esm_shims();
218
- import { readFileSync as readFileSync46, existsSync as existsSync53 } from "fs";
219
- import { join as join53 } from "path";
218
+ import { readFileSync as readFileSync48, existsSync as existsSync55 } from "fs";
219
+ import { join as join55 } from "path";
220
220
  import { homedir as homedir26 } from "os";
221
221
  import { Command } from "commander";
222
222
  import chalk61 from "chalk";
@@ -691,6 +691,23 @@ async function syncCommand(options) {
691
691
  mkcertSpinner.warn("Failed to install mkcert - run: https://github.com/FiloSottile/mkcert/releases");
692
692
  }
693
693
  }
694
+ if (!checkCommand("ox")) {
695
+ const oxSpinner = ora2("Installing SageOx CLI (ox)...").start();
696
+ try {
697
+ const binDir = join3(homedir2(), ".local", "bin");
698
+ mkdirSync2(binDir, { recursive: true });
699
+ const oxPath = join3(binDir, "ox");
700
+ const arch = process.arch === "x64" ? "amd64" : process.arch;
701
+ const platform2 = process.platform === "darwin" ? "darwin" : "linux";
702
+ execSync(`curl -sL "https://github.com/eltmon/ox/releases/download/latest/ox-${platform2}-${arch}" -o "${oxPath}" && chmod +x "${oxPath}"`, {
703
+ stdio: "pipe",
704
+ timeout: 6e4
705
+ });
706
+ oxSpinner.succeed("SageOx CLI installed");
707
+ } catch {
708
+ oxSpinner.warn("Failed to install SageOx CLI - see: https://github.com/eltmon/ox/releases");
709
+ }
710
+ }
694
711
  const projects = listProjects();
695
712
  if (projects.length > 0 && existsSync3(BUNDLED_GIT_HOOKS_DIR)) {
696
713
  const gitHooksSpinner = ora2("Installing git hooks in registered projects...").start();
@@ -950,7 +967,7 @@ function resolveShadowMode(options = {}) {
950
967
  trackerType
951
968
  };
952
969
  }
953
- const config2 = loadConfig2();
970
+ const { config: config2 } = loadConfig2();
954
971
  let enabled = config2.shadow.enabled;
955
972
  let source = config2.shadow.enabled ? "project" : "default";
956
973
  if (process.env.SHADOW_MODE !== void 0) {
@@ -1752,7 +1769,7 @@ async function issueCommand(id, options) {
1752
1769
  issueId: id,
1753
1770
  workspace,
1754
1771
  model: options.model,
1755
- phase: options.phase,
1772
+ phase: options.phase || "implementation",
1756
1773
  prompt
1757
1774
  });
1758
1775
  spinner.succeed(`Agent spawned: ${agent.id}`);
@@ -1798,8 +1815,25 @@ async function issueCommand(id, options) {
1798
1815
  init_esm_shims();
1799
1816
  init_agents();
1800
1817
  import chalk7 from "chalk";
1818
+ import { existsSync as existsSync9, readFileSync as readFileSync7, statSync as statSync4, readdirSync as readdirSync9 } from "fs";
1819
+ import { join as join9, basename as basename2 } from "path";
1801
1820
  init_tldr_daemon();
1821
+ function readContextPercent(agentId) {
1822
+ const ctxFile = join9(getAgentDir(agentId), "context-pct");
1823
+ try {
1824
+ if (existsSync9(ctxFile)) {
1825
+ const val = parseInt(readFileSync7(ctxFile, "utf8").trim(), 10);
1826
+ return isNaN(val) ? null : val;
1827
+ }
1828
+ } catch {
1829
+ }
1830
+ return null;
1831
+ }
1802
1832
  async function statusCommand(options) {
1833
+ if (options.tldr) {
1834
+ await tldrIndexStatusCommand();
1835
+ return;
1836
+ }
1803
1837
  const agents = listRunningAgents().filter(
1804
1838
  (agent) => agent.id && agent.issueId && agent.workspace
1805
1839
  );
@@ -1811,7 +1845,8 @@ async function statusCommand(options) {
1811
1845
  ...agent,
1812
1846
  shadowMode: shadowed,
1813
1847
  shadowStatus: shadowState?.shadowStatus,
1814
- trackerStatus: shadowState?.trackerStatus
1848
+ trackerStatus: shadowState?.trackerStatus,
1849
+ ...options.context ? { contextPercent: readContextPercent(agent.id) } : {}
1815
1850
  };
1816
1851
  });
1817
1852
  console.log(JSON.stringify(agentsWithShadow, null, 2));
@@ -1837,6 +1872,11 @@ async function statusCommand(options) {
1837
1872
  const statusStr = `${shadowState.shadowStatus}${shadowState.trackerStatus !== shadowState.shadowStatus ? ` (tracker: ${shadowState.trackerStatus})` : ""}`;
1838
1873
  console.log(` Shadow: ${chalk7.cyan("\u{1F47B}")} ${statusStr}`);
1839
1874
  }
1875
+ if (options.context) {
1876
+ const ctxPct = readContextPercent(agent.id);
1877
+ const ctxStr = ctxPct !== null ? `${ctxPct}%` : "--";
1878
+ console.log(` Context: ${ctxStr}`);
1879
+ }
1840
1880
  console.log(` Runtime: ${agent.runtime} (${agent.model})`);
1841
1881
  console.log(` Duration: ${duration} min`);
1842
1882
  console.log(` Workspace: ${chalk7.dim(agent.workspace)}`);
@@ -1857,6 +1897,122 @@ async function statusCommand(options) {
1857
1897
  console.log("");
1858
1898
  }
1859
1899
  }
1900
+ function readTldrIndexData(workspacePath) {
1901
+ const tldrPath = join9(workspacePath, ".tldr");
1902
+ if (!existsSync9(tldrPath)) {
1903
+ return { fileCount: null, edgeCount: null, ageMs: null };
1904
+ }
1905
+ let fileCount = null;
1906
+ let edgeCount = null;
1907
+ let ageMs = null;
1908
+ const cgPath = join9(tldrPath, "cache", "call_graph.json");
1909
+ if (existsSync9(cgPath)) {
1910
+ try {
1911
+ const cg = JSON.parse(readFileSync7(cgPath, "utf-8"));
1912
+ if (Array.isArray(cg.edges)) {
1913
+ edgeCount = cg.edges.length;
1914
+ const files = /* @__PURE__ */ new Set();
1915
+ for (const e of cg.edges) {
1916
+ if (e.from_file) files.add(e.from_file);
1917
+ if (e.to_file) files.add(e.to_file);
1918
+ }
1919
+ fileCount = files.size;
1920
+ }
1921
+ } catch {
1922
+ }
1923
+ }
1924
+ const langPath = join9(tldrPath, "languages.json");
1925
+ if (existsSync9(langPath)) {
1926
+ try {
1927
+ const langData = JSON.parse(readFileSync7(langPath, "utf-8"));
1928
+ if (langData.timestamp) {
1929
+ ageMs = Date.now() - langData.timestamp * 1e3;
1930
+ }
1931
+ } catch {
1932
+ }
1933
+ }
1934
+ if (ageMs === null) {
1935
+ try {
1936
+ const stats = statSync4(tldrPath);
1937
+ ageMs = Date.now() - stats.mtimeMs;
1938
+ } catch {
1939
+ }
1940
+ }
1941
+ return { fileCount, edgeCount, ageMs };
1942
+ }
1943
+ function formatTldrAge(ageMs) {
1944
+ if (ageMs === null) return "unknown";
1945
+ const ageMin = Math.floor(ageMs / 6e4);
1946
+ if (ageMin < 60) return `${ageMin}m`;
1947
+ const ageHours = Math.floor(ageMin / 60);
1948
+ if (ageHours < 24) return `${ageHours}h`;
1949
+ return `${Math.floor(ageHours / 24)}d`;
1950
+ }
1951
+ function formatTldrRow(label, entry) {
1952
+ const files = entry.fileCount !== null ? entry.fileCount.toLocaleString() : "N/A";
1953
+ const edges = entry.edgeCount !== null ? entry.edgeCount.toLocaleString() : "N/A";
1954
+ const age = formatTldrAge(entry.ageMs);
1955
+ const daemonStr = entry.running ? chalk7.green("running \u2713") : chalk7.dim("stopped \u25CB");
1956
+ const notIndexed = entry.fileCount === null ? chalk7.dim(" (not indexed)") : "";
1957
+ return ` ${chalk7.bold(label)}${notIndexed} Files: ${files} Edges: ${edges} Age: ${age} Daemon: ${daemonStr}`;
1958
+ }
1959
+ async function tldrIndexStatusCommand(projectRoot = process.cwd()) {
1960
+ const projectName = basename2(projectRoot);
1961
+ const mainEntries = [];
1962
+ const workspaceEntries = [];
1963
+ const mainVenvPath = join9(projectRoot, ".venv");
1964
+ if (existsSync9(mainVenvPath)) {
1965
+ const service = getTldrDaemonService(projectRoot, mainVenvPath);
1966
+ const status = await service.getStatus();
1967
+ const { fileCount, edgeCount, ageMs } = readTldrIndexData(projectRoot);
1968
+ mainEntries.push({ label: `Main (${projectName})`, running: status.running, fileCount, edgeCount, ageMs });
1969
+ }
1970
+ const workspacesDir = join9(projectRoot, "workspaces");
1971
+ if (existsSync9(workspacesDir)) {
1972
+ const dirs = readdirSync9(workspacesDir, { withFileTypes: true }).filter((d) => d.isDirectory() && d.name.startsWith("feature-"));
1973
+ for (const ws of dirs) {
1974
+ const wsPath = join9(workspacesDir, ws.name);
1975
+ const wsVenvPath = join9(wsPath, ".venv");
1976
+ if (existsSync9(wsVenvPath)) {
1977
+ const service = getTldrDaemonService(wsPath, wsVenvPath);
1978
+ const status = await service.getStatus();
1979
+ const { fileCount, edgeCount, ageMs } = readTldrIndexData(wsPath);
1980
+ workspaceEntries.push({ label: ws.name, running: status.running, fileCount, edgeCount, ageMs });
1981
+ }
1982
+ }
1983
+ }
1984
+ console.log(chalk7.bold("\nTLDR Index Health"));
1985
+ console.log("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
1986
+ if (mainEntries.length === 0 && workspaceEntries.length === 0) {
1987
+ console.log(chalk7.dim("\nNo TLDR indexes found (no .venv directories)"));
1988
+ console.log(chalk7.dim("Run `pan setup` to configure TLDR"));
1989
+ return;
1990
+ }
1991
+ for (const entry of mainEntries) {
1992
+ console.log(formatTldrRow(entry.label, entry));
1993
+ }
1994
+ if (workspaceEntries.length > 0) {
1995
+ console.log("");
1996
+ console.log(chalk7.bold("Workspaces"));
1997
+ for (const entry of workspaceEntries) {
1998
+ console.log(formatTldrRow(entry.label, entry));
1999
+ }
2000
+ }
2001
+ const allEntries = [...mainEntries, ...workspaceEntries];
2002
+ const ONE_HOUR = 60 * 60 * 1e3;
2003
+ const anyMissing = allEntries.some((e) => e.fileCount === null);
2004
+ const anyNotRunning = allEntries.some((e) => !e.running);
2005
+ const anyStale = allEntries.some((e) => e.ageMs === null || e.ageMs >= ONE_HOUR);
2006
+ console.log("");
2007
+ if (anyMissing || anyNotRunning) {
2008
+ console.log(`Health: ${chalk7.red("\u2717 TLDR not fully configured")}`);
2009
+ } else if (anyStale) {
2010
+ console.log(`Health: ${chalk7.yellow("\u26A0 Some indexes stale (>1h)")}`);
2011
+ } else {
2012
+ console.log(`Health: ${chalk7.green("\u2713 All indexes fresh")}`);
2013
+ }
2014
+ console.log("");
2015
+ }
1860
2016
 
1861
2017
  // src/cli/commands/work/tell.ts
1862
2018
  init_esm_shims();
@@ -1925,8 +2081,8 @@ init_esm_shims();
1925
2081
  init_agents();
1926
2082
  init_paths();
1927
2083
  import chalk10 from "chalk";
1928
- import { existsSync as existsSync9, readFileSync as readFileSync7 } from "fs";
1929
- import { join as join9 } from "path";
2084
+ import { existsSync as existsSync10, readFileSync as readFileSync8 } from "fs";
2085
+ import { join as join10 } from "path";
1930
2086
  async function pendingCommand() {
1931
2087
  const agents = listRunningAgents().filter((a) => !a.tmuxActive && a.status !== "error");
1932
2088
  if (agents.length === 0) {
@@ -1939,9 +2095,9 @@ async function pendingCommand() {
1939
2095
  console.log(`${chalk10.cyan(agent.issueId)}`);
1940
2096
  console.log(` Agent: ${agent.id}`);
1941
2097
  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");
2098
+ const completionFile = join10(AGENTS_DIR, agent.id, "completion.md");
2099
+ if (existsSync10(completionFile)) {
2100
+ const content = readFileSync8(completionFile, "utf8");
1945
2101
  const firstLine = content.split("\n").find((l) => l.trim() && !l.startsWith("#"));
1946
2102
  if (firstLine) {
1947
2103
  console.log(` Summary: ${chalk10.dim(firstLine.trim())}`);
@@ -1958,14 +2114,14 @@ init_agents();
1958
2114
  init_paths();
1959
2115
  import chalk11 from "chalk";
1960
2116
  import ora5 from "ora";
1961
- import { existsSync as existsSync10, writeFileSync as writeFileSync3, readFileSync as readFileSync8 } from "fs";
1962
- import { join as join10 } from "path";
2117
+ import { existsSync as existsSync11, writeFileSync as writeFileSync3, readFileSync as readFileSync9 } from "fs";
2118
+ import { join as join11 } from "path";
1963
2119
  import { homedir as homedir4 } from "os";
1964
2120
  import { execSync as execSync2 } from "child_process";
1965
2121
  function getLinearApiKey2() {
1966
- const envFile = join10(homedir4(), ".panopticon.env");
1967
- if (existsSync10(envFile)) {
1968
- const content = readFileSync8(envFile, "utf-8");
2122
+ const envFile = join11(homedir4(), ".panopticon.env");
2123
+ if (existsSync11(envFile)) {
2124
+ const content = readFileSync9(envFile, "utf-8");
1969
2125
  const match = content.match(/LINEAR_API_KEY=(.+)/);
1970
2126
  if (match) return match[1].trim();
1971
2127
  }
@@ -2102,7 +2258,7 @@ async function approveCommand(id, options = {}) {
2102
2258
  state.status = "stopped";
2103
2259
  state.lastActivity = (/* @__PURE__ */ new Date()).toISOString();
2104
2260
  saveAgentState(state);
2105
- const approvedFile = join10(AGENTS_DIR, agentId, "approved");
2261
+ const approvedFile = join11(AGENTS_DIR, agentId, "approved");
2106
2262
  writeFileSync3(approvedFile, JSON.stringify({
2107
2263
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
2108
2264
  prMerged,
@@ -2133,8 +2289,8 @@ init_agents();
2133
2289
  init_paths();
2134
2290
  import chalk12 from "chalk";
2135
2291
  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";
2292
+ import { existsSync as existsSync12, writeFileSync as writeFileSync4, readFileSync as readFileSync10, mkdirSync as mkdirSync4, readdirSync as readdirSync11 } from "fs";
2293
+ import { join as join12 } from "path";
2138
2294
  import { homedir as homedir5 } from "os";
2139
2295
  import { exec } from "child_process";
2140
2296
  import { promisify } from "util";
@@ -2271,9 +2427,9 @@ function cleanupWorkflowLabels(currentLabels, targetState) {
2271
2427
  // src/cli/commands/work/done.ts
2272
2428
  var execAsync = promisify(exec);
2273
2429
  function getLinearApiKey3() {
2274
- const envFile = join11(homedir5(), ".panopticon.env");
2275
- if (existsSync11(envFile)) {
2276
- const content = readFileSync9(envFile, "utf-8");
2430
+ const envFile = join12(homedir5(), ".panopticon.env");
2431
+ if (existsSync12(envFile)) {
2432
+ const content = readFileSync10(envFile, "utf-8");
2277
2433
  const match = content.match(/LINEAR_API_KEY=(.+)/);
2278
2434
  if (match) return match[1].trim();
2279
2435
  }
@@ -2315,9 +2471,9 @@ ${comment}`
2315
2471
  }
2316
2472
  }
2317
2473
  function getGitHubConfig() {
2318
- const envFile = join11(homedir5(), ".panopticon.env");
2319
- if (!existsSync11(envFile)) return null;
2320
- const content = readFileSync9(envFile, "utf-8");
2474
+ const envFile = join12(homedir5(), ".panopticon.env");
2475
+ if (!existsSync12(envFile)) return null;
2476
+ const content = readFileSync10(envFile, "utf-8");
2321
2477
  const tokenMatch = content.match(/GITHUB_TOKEN=(.+)/);
2322
2478
  if (!tokenMatch) return null;
2323
2479
  const token = tokenMatch[1].trim();
@@ -2373,10 +2529,10 @@ async function doneCommand(id, options = {}) {
2373
2529
  const issueId = id.replace(/^agent-/i, "").toUpperCase();
2374
2530
  const agentId = `agent-${issueId.toLowerCase()}`;
2375
2531
  if (!options.force) {
2376
- const { getAgentState: getAgentState2 } = await import("../agents-VLK4BMVA.js");
2532
+ const { getAgentState: getAgentState2 } = await import("../agents-E43Y3HNU.js");
2377
2533
  const agentState = getAgentState2(agentId);
2378
2534
  const workspacePath = agentState?.workspace;
2379
- if (workspacePath && existsSync11(workspacePath)) {
2535
+ if (workspacePath && existsSync12(workspacePath)) {
2380
2536
  const failures = [];
2381
2537
  try {
2382
2538
  const { stdout } = await execAsync("bd list --status open --limit 0 --json", { cwd: workspacePath });
@@ -2391,7 +2547,7 @@ async function doneCommand(id, options = {}) {
2391
2547
  }
2392
2548
  } catch {
2393
2549
  }
2394
- const hasTopLevelGit = existsSync11(join11(workspacePath, ".git"));
2550
+ const hasTopLevelGit = existsSync12(join12(workspacePath, ".git"));
2395
2551
  if (hasTopLevelGit) {
2396
2552
  try {
2397
2553
  const { stdout } = await execAsync("git status --porcelain", { cwd: workspacePath });
@@ -2405,11 +2561,11 @@ async function doneCommand(id, options = {}) {
2405
2561
  }
2406
2562
  } else {
2407
2563
  try {
2408
- const entries = readdirSync10(workspacePath, { withFileTypes: true });
2564
+ const entries = readdirSync11(workspacePath, { withFileTypes: true });
2409
2565
  for (const entry of entries) {
2410
2566
  if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
2411
- const subPath = join11(workspacePath, entry.name);
2412
- if (!existsSync11(join11(subPath, ".git"))) continue;
2567
+ const subPath = join12(workspacePath, entry.name);
2568
+ if (!existsSync12(join12(subPath, ".git"))) continue;
2413
2569
  try {
2414
2570
  const { stdout } = await execAsync("git status --porcelain", { cwd: subPath });
2415
2571
  if (stdout.trim()) {
@@ -2472,7 +2628,7 @@ async function doneCommand(id, options = {}) {
2472
2628
  console.log(chalk12.dim(" LINEAR_API_KEY not set - skipping status update"));
2473
2629
  }
2474
2630
  }
2475
- const { getAgentState: getAgentState2, saveAgentState: saveAgentState2 } = await import("../agents-VLK4BMVA.js");
2631
+ const { getAgentState: getAgentState2, saveAgentState: saveAgentState2 } = await import("../agents-E43Y3HNU.js");
2476
2632
  const existingState = getAgentState2(agentId);
2477
2633
  if (existingState) {
2478
2634
  existingState.status = "stopped";
@@ -2483,8 +2639,8 @@ async function doneCommand(id, options = {}) {
2483
2639
  state: "idle",
2484
2640
  lastActivity: (/* @__PURE__ */ new Date()).toISOString()
2485
2641
  });
2486
- mkdirSync4(join11(AGENTS_DIR, agentId), { recursive: true });
2487
- const completedFile = join11(AGENTS_DIR, agentId, "completed");
2642
+ mkdirSync4(join12(AGENTS_DIR, agentId), { recursive: true });
2643
+ const completedFile = join12(AGENTS_DIR, agentId, "completed");
2488
2644
  writeFileSync4(completedFile, JSON.stringify({
2489
2645
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
2490
2646
  trackerUpdated,
@@ -2549,10 +2705,45 @@ async function doneCommand(id, options = {}) {
2549
2705
  req.write(postData);
2550
2706
  req.end();
2551
2707
  });
2552
- const result = await reviewReq();
2708
+ let result = await reviewReq();
2709
+ if (!result.success && result.alreadyMerged) {
2710
+ console.log(chalk12.yellow(` \u26A0 Issue was previously merged. Resetting specialist states for re-review...`));
2711
+ const resetReq = () => new Promise((resolve2, reject) => {
2712
+ const postData = JSON.stringify({});
2713
+ const req = http.request(
2714
+ `${dashboardUrl}/api/workspaces/${issueId}/reset-review`,
2715
+ { method: "POST", headers: { "Content-Type": "application/json" }, timeout: 5e3 },
2716
+ (res) => {
2717
+ let data = "";
2718
+ res.on("data", (chunk) => data += chunk);
2719
+ res.on("end", () => {
2720
+ try {
2721
+ resolve2(JSON.parse(data));
2722
+ } catch {
2723
+ resolve2({ success: false, error: "Invalid response" });
2724
+ }
2725
+ });
2726
+ }
2727
+ );
2728
+ req.on("error", reject);
2729
+ req.on("timeout", () => {
2730
+ req.destroy();
2731
+ reject(new Error("Timeout"));
2732
+ });
2733
+ req.write(postData);
2734
+ req.end();
2735
+ });
2736
+ const resetResult = await resetReq();
2737
+ if (resetResult.success) {
2738
+ console.log(chalk12.green(` \u2713 Specialist states reset`));
2739
+ result = await reviewReq();
2740
+ } else {
2741
+ console.log(chalk12.red(` \u2717 Failed to reset: ${resetResult.error || resetResult.message || "Unknown error"}`));
2742
+ }
2743
+ }
2553
2744
  if (result.success) {
2554
2745
  console.log(chalk12.green(` \u2713 Review & test ${result.queued ? "queued" : "started"} automatically`));
2555
- } else {
2746
+ } else if (!result.alreadyMerged) {
2556
2747
  console.log(chalk12.yellow(` \u26A0 Auto-review not triggered: ${result.error || result.message || "Unknown error"}`));
2557
2748
  if (result.alreadyReviewed) {
2558
2749
  console.log(chalk12.dim(` Manual review needed - click "Review and Test" in dashboard`));
@@ -2576,40 +2767,36 @@ init_esm_shims();
2576
2767
  import chalk13 from "chalk";
2577
2768
  import ora7 from "ora";
2578
2769
  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";
2770
+ import { readFileSync as readFileSync12, existsSync as existsSync14 } from "fs";
2771
+ import { join as join14, dirname as dirname5 } from "path";
2581
2772
  import { homedir as homedir6 } from "os";
2582
- import { exec as exec2 } from "child_process";
2773
+
2774
+ // src/lib/planning/plan-utils.ts
2775
+ init_esm_shims();
2776
+ import { existsSync as existsSync13, mkdirSync as mkdirSync5, writeFileSync as writeFileSync5 } from "fs";
2777
+ import { join as join13 } from "path";
2778
+ import { exec as exec2, spawn } from "child_process";
2583
2779
  import { promisify as promisify2 } from "util";
2584
2780
  init_paths();
2585
2781
  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) {
2782
+ async function findPRDFiles(issueId, cwd) {
2596
2783
  const found = [];
2597
- const cwd = process.cwd();
2784
+ const searchRoot = cwd || process.cwd();
2598
2785
  if (hasPRDDraft(issueId)) {
2599
2786
  found.push(getPRDDraftPath(issueId));
2600
2787
  }
2601
2788
  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"),
2789
+ join13(PROJECT_DOCS_SUBDIR, PROJECT_PRDS_SUBDIR, PROJECT_PRDS_ACTIVE_SUBDIR),
2790
+ join13(PROJECT_DOCS_SUBDIR, PROJECT_PRDS_SUBDIR, "planned"),
2791
+ join13(PROJECT_DOCS_SUBDIR, PROJECT_PRDS_SUBDIR),
2792
+ join13(PROJECT_DOCS_SUBDIR, "prd"),
2606
2793
  PROJECT_PRDS_SUBDIR,
2607
2794
  PROJECT_DOCS_SUBDIR
2608
2795
  ];
2609
2796
  const issueIdLower = issueId.toLowerCase();
2610
2797
  for (const searchPath of searchPaths) {
2611
- const fullPath = join12(cwd, searchPath);
2612
- if (!existsSync12(fullPath)) continue;
2798
+ const fullPath = join13(searchRoot, searchPath);
2799
+ if (!existsSync13(fullPath)) continue;
2613
2800
  try {
2614
2801
  const { stdout: result } = await execAsync2(
2615
2802
  `find "${fullPath}" -type f -name "*.md" 2>/dev/null | xargs grep -l -i "${issueIdLower}" 2>/dev/null || true`,
@@ -2686,7 +2873,7 @@ function analyzeComplexity(issue, prdFiles) {
2686
2873
  estimatedTasks += 1;
2687
2874
  }
2688
2875
  if (prdFiles.length > 0) {
2689
- reasons.push(`PRD exists - complexity already documented`);
2876
+ reasons.push("PRD exists - complexity already documented");
2690
2877
  }
2691
2878
  const complexLabels = ["complex", "large", "epic", "multi-phase", "architecture"];
2692
2879
  for (const label of issue.labels || []) {
@@ -2703,138 +2890,7 @@ function analyzeComplexity(issue, prdFiles) {
2703
2890
  estimatedTasks: Math.max(estimatedTasks, subsystems.length + 1)
2704
2891
  };
2705
2892
  }
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) {
2893
+ function generateStateContent(issue, decisions, tasks) {
2838
2894
  const lines = [
2839
2895
  `# Agent State: ${issue.identifier}`,
2840
2896
  "",
@@ -2874,7 +2930,8 @@ function generateStateFile(issue, decisions, tasks) {
2874
2930
  lines.push("");
2875
2931
  return lines.join("\n");
2876
2932
  }
2877
- function generateWorkspaceFile(issue, prdFiles) {
2933
+ function generateWorkspaceContent(issue, prdFiles, cwd) {
2934
+ const searchRoot = cwd || process.cwd();
2878
2935
  const lines = [
2879
2936
  `# Workspace: ${issue.identifier}`,
2880
2937
  "",
@@ -2885,7 +2942,7 @@ function generateWorkspaceFile(issue, prdFiles) {
2885
2942
  `- [Linear Issue](${issue.url})`
2886
2943
  ];
2887
2944
  for (const prd of prdFiles) {
2888
- const relativePath = prd.replace(process.cwd() + "/", "");
2945
+ const relativePath = prd.replace(searchRoot + "/", "");
2889
2946
  lines.push(`- [PRD](${relativePath})`);
2890
2947
  }
2891
2948
  lines.push("");
@@ -2922,86 +2979,269 @@ function generateWorkspaceFile(issue, prdFiles) {
2922
2979
  lines.push("");
2923
2980
  return lines.join("\n");
2924
2981
  }
2925
- function estimateDifficulty(task) {
2926
- if (task.difficulty) {
2927
- return task.difficulty;
2928
- }
2982
+ function estimateTaskDifficulty(task) {
2983
+ if (task.difficulty) return task.difficulty;
2929
2984
  const combined = `${task.name} ${task.description || ""}`.toLowerCase();
2930
2985
  const expertPatterns = ["architecture", "security", "performance optimization", "distributed", "auth system", "redesign"];
2931
- if (expertPatterns.some((p) => combined.includes(p))) {
2932
- return "expert";
2933
- }
2986
+ if (expertPatterns.some((p) => combined.includes(p))) return "expert";
2934
2987
  const complexPatterns = ["refactor", "migration", "overhaul", "rewrite", "integrate", "multi-system"];
2935
- if (complexPatterns.some((p) => combined.includes(p))) {
2936
- return "complex";
2988
+ if (complexPatterns.some((p) => combined.includes(p))) return "complex";
2989
+ const mediumPatterns = ["implement", "feature", "endpoint", "component", "service", "integration", "add tests"];
2990
+ if (mediumPatterns.some((p) => combined.includes(p))) return "medium";
2991
+ const trivialPatterns = ["typo", "rename", "comment", "documentation", "readme", "formatting"];
2992
+ if (trivialPatterns.some((p) => combined.includes(p))) return "trivial";
2993
+ return "simple";
2994
+ }
2995
+ async function createBeadsTasks(issue, tasks, cwd) {
2996
+ const created = [];
2997
+ const errors = [];
2998
+ const taskIds = /* @__PURE__ */ new Map();
2999
+ const workDir = cwd || process.cwd();
3000
+ try {
3001
+ await execAsync2("which bd", { encoding: "utf-8" });
3002
+ } catch {
3003
+ return { success: false, created: [], errors: ["bd (beads) CLI not found in PATH"] };
3004
+ }
3005
+ for (const task of tasks) {
3006
+ const fullName = `${issue.identifier}: ${task.name}`;
3007
+ try {
3008
+ const difficulty = estimateTaskDifficulty(task);
3009
+ const escapedName = fullName.replace(/"/g, '\\"');
3010
+ let cmd = `bd create "${escapedName}" --type task -l "${issue.identifier},linear,difficulty:${difficulty}"`;
3011
+ if (task.dependsOn) {
3012
+ const depName = `${issue.identifier}: ${task.dependsOn}`;
3013
+ const depId = taskIds.get(depName);
3014
+ if (depId) {
3015
+ cmd += ` --deps "blocks:${depId}"`;
3016
+ }
3017
+ }
3018
+ if (task.description) {
3019
+ const escapedDesc = task.description.replace(/"/g, '\\"');
3020
+ cmd += ` -d "${escapedDesc}"`;
3021
+ }
3022
+ const { stdout: result } = await execAsync2(cmd, { encoding: "utf-8", cwd: workDir });
3023
+ const idMatch = result.match(/bd-[a-f0-9]+/i) || result.match(/([a-f0-9-]{8,})/i);
3024
+ if (idMatch) {
3025
+ taskIds.set(fullName, idMatch[0]);
3026
+ }
3027
+ created.push(fullName);
3028
+ } catch (error) {
3029
+ const errMsg = error.stderr?.toString() || error.message;
3030
+ errors.push(`Failed to create "${task.name}": ${errMsg.split("\n")[0]}`);
3031
+ }
3032
+ }
3033
+ if (created.length > 0) {
3034
+ try {
3035
+ await execAsync2("bd flush", { encoding: "utf-8", cwd: workDir });
3036
+ } catch {
3037
+ }
3038
+ }
3039
+ return { success: errors.length === 0, created, errors };
3040
+ }
3041
+ function writePlanFiles(projectPath, stateContent, workspaceContent) {
3042
+ const planningDir = join13(projectPath, ".planning");
3043
+ mkdirSync5(planningDir, { recursive: true });
3044
+ const statePath = join13(planningDir, "STATE.md");
3045
+ const workspacePath = join13(planningDir, "WORKSPACE.md");
3046
+ writeFileSync5(statePath, stateContent);
3047
+ writeFileSync5(workspacePath, workspaceContent);
3048
+ return { statePath, workspacePath };
3049
+ }
3050
+ async function copyToPRDDirectory(projectPath, issue, content, options) {
3051
+ const prdDir = join13(projectPath, PROJECT_DOCS_SUBDIR, PROJECT_PRDS_SUBDIR, PROJECT_PRDS_ACTIVE_SUBDIR);
3052
+ try {
3053
+ mkdirSync5(prdDir, { recursive: true });
3054
+ const filename = `${issue.identifier.toLowerCase()}-plan.md`;
3055
+ const prdPath = join13(prdDir, filename);
3056
+ writeFileSync5(prdPath, content);
3057
+ let committed = false;
3058
+ if (options?.commitAndPush) {
3059
+ try {
3060
+ const relativePrdPath = join13(PROJECT_DOCS_SUBDIR, PROJECT_PRDS_SUBDIR, PROJECT_PRDS_ACTIVE_SUBDIR, filename);
3061
+ await execAsync2(`git add ${relativePrdPath}`, { cwd: projectPath, encoding: "utf-8" });
3062
+ try {
3063
+ await execAsync2("git diff --cached --quiet", { cwd: projectPath, encoding: "utf-8" });
3064
+ } catch {
3065
+ await execAsync2(`git commit -m "docs: add ${issue.identifier} PRD to active"`, {
3066
+ cwd: projectPath,
3067
+ encoding: "utf-8"
3068
+ });
3069
+ const pushChild = spawn("git", ["push"], { cwd: projectPath, detached: true, stdio: "ignore" });
3070
+ pushChild.unref();
3071
+ committed = true;
3072
+ }
3073
+ } catch (gitErr) {
3074
+ console.warn(`[plan] Could not commit PRD (non-fatal): ${gitErr.message}`);
3075
+ }
3076
+ }
3077
+ return { prdPath, committed };
3078
+ } catch {
3079
+ return { prdPath: null, committed: false };
3080
+ }
3081
+ }
3082
+ async function executePlan(issue, tasks, decisions, projectPath, options) {
3083
+ const prdFiles = options?.prdFiles || [];
3084
+ const stateContent = generateStateContent(issue, decisions, tasks);
3085
+ const workspaceContent = generateWorkspaceContent(issue, prdFiles, projectPath);
3086
+ const { statePath, workspacePath } = writePlanFiles(projectPath, stateContent, workspaceContent);
3087
+ const { prdPath, committed } = await copyToPRDDirectory(
3088
+ projectPath,
3089
+ issue,
3090
+ stateContent,
3091
+ { commitAndPush: options?.commitAndPush ?? false }
3092
+ );
3093
+ const beads = await createBeadsTasks(issue, tasks, projectPath);
3094
+ return {
3095
+ files: {
3096
+ state: statePath,
3097
+ workspace: workspacePath,
3098
+ prd: prdPath || void 0
3099
+ },
3100
+ prdCommitted: committed,
3101
+ beads
3102
+ };
3103
+ }
3104
+
3105
+ // src/cli/commands/work/plan.ts
3106
+ function getLinearApiKey4() {
3107
+ const envFile = join14(homedir6(), ".panopticon.env");
3108
+ if (existsSync14(envFile)) {
3109
+ const content = readFileSync12(envFile, "utf-8");
3110
+ const match = content.match(/LINEAR_API_KEY=(.+)/);
3111
+ if (match) return match[1].trim();
3112
+ }
3113
+ return process.env.LINEAR_API_KEY || null;
3114
+ }
3115
+ async function runDiscoveryPhase(issue, complexity, prdContent) {
3116
+ const decisions = [];
3117
+ const tasks = [];
3118
+ console.log("");
3119
+ 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"));
3120
+ console.log(chalk13.bold.cyan(" DISCOVERY PHASE"));
3121
+ 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"));
3122
+ console.log("");
3123
+ console.log(chalk13.dim("Answer questions to create a detailed execution plan."));
3124
+ console.log(chalk13.dim("Press Enter to skip optional questions."));
3125
+ console.log("");
3126
+ console.log(chalk13.bold("Issue:"), `${issue.identifier} - ${issue.title}`);
3127
+ if (complexity.subsystems.length > 0) {
3128
+ console.log(chalk13.bold("Detected subsystems:"), complexity.subsystems.join(", "));
3129
+ }
3130
+ console.log("");
3131
+ const scopeAnswer = await inquirer2.prompt([{
3132
+ type: "input",
3133
+ name: "scope",
3134
+ message: "What specific changes are needed? (be specific about files/components):",
3135
+ default: issue.description?.slice(0, 100) || ""
3136
+ }]);
3137
+ if (scopeAnswer.scope) {
3138
+ decisions.push({ question: "Scope", answer: scopeAnswer.scope });
3139
+ }
3140
+ const approachAnswer = await inquirer2.prompt([{
3141
+ type: "input",
3142
+ name: "approach",
3143
+ message: "Any specific technical approach or patterns to follow?"
3144
+ }]);
3145
+ if (approachAnswer.approach) {
3146
+ decisions.push({ question: "Technical approach", answer: approachAnswer.approach });
3147
+ }
3148
+ const edgeCasesAnswer = await inquirer2.prompt([{
3149
+ type: "input",
3150
+ name: "edgeCases",
3151
+ message: "Any edge cases or error scenarios to handle?"
3152
+ }]);
3153
+ if (edgeCasesAnswer.edgeCases) {
3154
+ decisions.push({ question: "Edge cases", answer: edgeCasesAnswer.edgeCases });
3155
+ }
3156
+ const testingAnswer = await inquirer2.prompt([{
3157
+ type: "checkbox",
3158
+ name: "testing",
3159
+ message: "What testing is required?",
3160
+ choices: [
3161
+ { name: "Unit tests", value: "unit", checked: true },
3162
+ { name: "Integration tests", value: "integration" },
3163
+ { name: "E2E tests (Playwright)", value: "e2e" },
3164
+ { name: "Manual testing only", value: "manual" }
3165
+ ]
3166
+ }]);
3167
+ if (testingAnswer.testing.length > 0) {
3168
+ decisions.push({ question: "Testing", answer: testingAnswer.testing.join(", ") });
3169
+ }
3170
+ const outOfScopeAnswer = await inquirer2.prompt([{
3171
+ type: "input",
3172
+ name: "outOfScope",
3173
+ message: "Anything explicitly OUT of scope for this issue?"
3174
+ }]);
3175
+ if (outOfScopeAnswer.outOfScope) {
3176
+ decisions.push({ question: "Out of scope", answer: outOfScopeAnswer.outOfScope });
3177
+ }
3178
+ console.log("");
3179
+ console.log(chalk13.bold("Define execution tasks:"));
3180
+ console.log(chalk13.dim("Enter tasks in order. Empty task name to finish."));
3181
+ console.log("");
3182
+ const suggestedTasks = [
3183
+ { name: "Understand requirements", description: "Review issue, PRD, and existing code" }
3184
+ ];
3185
+ if (complexity.subsystems.length > 1) {
3186
+ suggestedTasks.push({ name: "Design approach", description: "Document architecture decisions", dependsOn: "Understand requirements" });
3187
+ }
3188
+ for (const subsystem of complexity.subsystems) {
3189
+ suggestedTasks.push({
3190
+ name: `Implement ${subsystem}`,
3191
+ description: `Core ${subsystem} changes`,
3192
+ dependsOn: complexity.subsystems.length > 1 ? "Design approach" : "Understand requirements"
3193
+ });
2937
3194
  }
2938
- const mediumPatterns = ["implement", "feature", "endpoint", "component", "service", "integration", "add tests"];
2939
- if (mediumPatterns.some((p) => combined.includes(p))) {
2940
- return "medium";
3195
+ if (suggestedTasks.length === 1) {
3196
+ suggestedTasks.push({ name: "Implement changes", description: "Core implementation", dependsOn: "Understand requirements" });
2941
3197
  }
2942
- const trivialPatterns = ["typo", "rename", "comment", "documentation", "readme", "formatting"];
2943
- if (trivialPatterns.some((p) => combined.includes(p))) {
2944
- return "trivial";
3198
+ if (testingAnswer.testing.includes("unit") || testingAnswer.testing.includes("integration")) {
3199
+ suggestedTasks.push({ name: "Add tests", description: "Unit and/or integration tests", dependsOn: suggestedTasks[suggestedTasks.length - 1].name });
2945
3200
  }
2946
- return "simple";
2947
- }
2948
- async function createBeadsTasks(issue, tasks) {
2949
- const created = [];
2950
- const errors = [];
2951
- const taskIds = /* @__PURE__ */ new Map();
2952
- try {
2953
- await execAsync2("which bd", { encoding: "utf-8" });
2954
- } catch {
2955
- return { success: false, created: [], errors: ["bd (beads) CLI not found in PATH"] };
3201
+ if (testingAnswer.testing.includes("e2e")) {
3202
+ suggestedTasks.push({ name: "Add E2E tests", description: "Playwright E2E tests", dependsOn: "Add tests" });
2956
3203
  }
2957
- for (const task of tasks) {
2958
- const fullName = `${issue.identifier}: ${task.name}`;
2959
- try {
2960
- const difficulty = estimateDifficulty(task);
2961
- const escapedName = fullName.replace(/"/g, '\\"');
2962
- let cmd = `bd create "${escapedName}" --type task -l "${issue.identifier},linear,difficulty:${difficulty}"`;
2963
- if (task.dependsOn) {
2964
- const depName = `${issue.identifier}: ${task.dependsOn}`;
2965
- const depId = taskIds.get(depName);
2966
- if (depId) {
2967
- cmd += ` --deps "blocks:${depId}"`;
2968
- }
2969
- }
2970
- if (task.description) {
2971
- const escapedDesc = task.description.replace(/"/g, '\\"');
2972
- cmd += ` -d "${escapedDesc}"`;
2973
- }
2974
- const { stdout: result } = await execAsync2(cmd, { encoding: "utf-8", cwd: process.cwd() });
2975
- const idMatch = result.match(/bd-[a-f0-9]+/i) || result.match(/([a-f0-9-]{8,})/i);
2976
- if (idMatch) {
2977
- taskIds.set(fullName, idMatch[0]);
2978
- }
2979
- created.push(fullName);
2980
- } catch (error) {
2981
- const errMsg = error.stderr?.toString() || error.message;
2982
- errors.push(`Failed to create "${task.name}": ${errMsg.split("\n")[0]}`);
2983
- }
3204
+ suggestedTasks.push({ name: "Verify and cleanup", description: "Lint, type check, final review", dependsOn: suggestedTasks[suggestedTasks.length - 1].name });
3205
+ console.log(chalk13.bold("Suggested tasks:"));
3206
+ for (let i = 0; i < suggestedTasks.length; i++) {
3207
+ const task = suggestedTasks[i];
3208
+ console.log(` ${i + 1}. ${task.name}${task.dependsOn ? chalk13.dim(` (after: ${task.dependsOn})`) : ""}`);
2984
3209
  }
2985
- if (created.length > 0) {
2986
- try {
2987
- await execAsync2("bd flush", { encoding: "utf-8", cwd: process.cwd() });
2988
- } catch {
3210
+ console.log("");
3211
+ const useDefaultAnswer = await inquirer2.prompt([{
3212
+ type: "confirm",
3213
+ name: "useDefault",
3214
+ message: "Use these suggested tasks?",
3215
+ default: true
3216
+ }]);
3217
+ if (useDefaultAnswer.useDefault) {
3218
+ tasks.push(...suggestedTasks);
3219
+ } else {
3220
+ let taskIndex = 1;
3221
+ let previousTask = "";
3222
+ while (true) {
3223
+ const taskAnswer = await inquirer2.prompt([{
3224
+ type: "input",
3225
+ name: "name",
3226
+ message: `Task ${taskIndex} name (empty to finish):`
3227
+ }]);
3228
+ if (!taskAnswer.name) break;
3229
+ const descAnswer = await inquirer2.prompt([{
3230
+ type: "input",
3231
+ name: "description",
3232
+ message: `Task ${taskIndex} description:`,
3233
+ default: taskAnswer.name
3234
+ }]);
3235
+ tasks.push({
3236
+ name: taskAnswer.name,
3237
+ description: descAnswer.description,
3238
+ dependsOn: previousTask || void 0
3239
+ });
3240
+ previousTask = taskAnswer.name;
3241
+ taskIndex++;
2989
3242
  }
2990
3243
  }
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;
3004
- }
3244
+ return { tasks, decisions };
3005
3245
  }
3006
3246
  async function planCommand(id, options = {}) {
3007
3247
  const spinner = ora7(`Creating execution plan for ${id}...`).start();
@@ -3041,9 +3281,9 @@ async function planCommand(id, options = {}) {
3041
3281
  identifier: issue.identifier,
3042
3282
  title: issue.title,
3043
3283
  description: issue.description || void 0,
3284
+ url: issue.url,
3044
3285
  state: { name: state?.name || "Unknown" },
3045
3286
  priority: issue.priority,
3046
- url: issue.url,
3047
3287
  labels: labels.nodes.map((l) => ({ name: l.name })),
3048
3288
  assignee: assignee ? { name: assignee.name } : void 0,
3049
3289
  project: project2 ? { name: project2.name } : void 0
@@ -3108,29 +3348,23 @@ async function planCommand(id, options = {}) {
3108
3348
  decisions = discovery.decisions;
3109
3349
  }
3110
3350
  const spinnerCreate = ora7("Creating context files...").start();
3111
- const stateContent = generateStateFile(issueData, decisions, tasks);
3112
- const workspaceContent = generateWorkspaceFile(issueData, prdFiles);
3113
3351
  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);
3352
+ const result = await executePlan(issueData, tasks, decisions, outputDir, {
3353
+ prdFiles
3354
+ });
3120
3355
  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}`));
3356
+ if (result.beads.created.length > 0) {
3357
+ if (result.beads.success) {
3358
+ console.log(chalk13.green(`Created ${result.beads.created.length} Beads tasks`));
3359
+ } else {
3360
+ console.log(chalk13.yellow(`Created ${result.beads.created.length} Beads tasks with errors`));
3361
+ for (const error of result.beads.errors) {
3362
+ console.log(chalk13.red(` - ${error}`));
3363
+ }
3129
3364
  }
3130
3365
  }
3131
- const prdPath = copyToPRDDirectory(issueData, stateContent);
3132
- if (prdPath) {
3133
- console.log(chalk13.dim(`Plan copied to: ${prdPath.replace(process.cwd() + "/", "")}`));
3366
+ if (result.files.prd) {
3367
+ console.log(chalk13.dim(`Plan copied to: ${result.files.prd.replace(process.cwd() + "/", "")}`));
3134
3368
  }
3135
3369
  if (options.json) {
3136
3370
  console.log(JSON.stringify({
@@ -3138,12 +3372,8 @@ async function planCommand(id, options = {}) {
3138
3372
  complexity,
3139
3373
  tasks,
3140
3374
  decisions,
3141
- files: {
3142
- state: statePath,
3143
- workspace: workspacePath,
3144
- prd: prdPath
3145
- },
3146
- beads: beadsResult
3375
+ files: result.files,
3376
+ beads: result.beads
3147
3377
  }, null, 2));
3148
3378
  return;
3149
3379
  }
@@ -3153,8 +3383,8 @@ async function planCommand(id, options = {}) {
3153
3383
  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
3384
  console.log("");
3155
3385
  console.log(chalk13.bold("Files created:"));
3156
- console.log(` ${chalk13.cyan(statePath.replace(process.cwd() + "/", ""))}`);
3157
- console.log(` ${chalk13.cyan(workspacePath.replace(process.cwd() + "/", ""))}`);
3386
+ console.log(` ${chalk13.cyan(result.files.state.replace(process.cwd() + "/", ""))}`);
3387
+ console.log(` ${chalk13.cyan(result.files.workspace.replace(process.cwd() + "/", ""))}`);
3158
3388
  console.log("");
3159
3389
  console.log(chalk13.bold("Beads tasks:"));
3160
3390
  for (const task of tasks) {
@@ -3359,8 +3589,8 @@ init_esm_shims();
3359
3589
  init_config();
3360
3590
  import chalk15 from "chalk";
3361
3591
  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";
3592
+ import { readFileSync as readFileSync13, writeFileSync as writeFileSync6, existsSync as existsSync15, mkdirSync as mkdirSync6 } from "fs";
3593
+ import { join as join15 } from "path";
3364
3594
  import { homedir as homedir7 } from "os";
3365
3595
  function getTrackerConfig2(trackerType) {
3366
3596
  const config2 = loadConfig();
@@ -3379,13 +3609,13 @@ function getTrackerConfig2(trackerType) {
3379
3609
  };
3380
3610
  }
3381
3611
  function getTriageStatePath() {
3382
- return join13(homedir7(), ".panopticon", "triage-state.json");
3612
+ return join15(homedir7(), ".panopticon", "triage-state.json");
3383
3613
  }
3384
3614
  function loadTriageState() {
3385
3615
  const path = getTriageStatePath();
3386
- if (existsSync13(path)) {
3616
+ if (existsSync15(path)) {
3387
3617
  try {
3388
- return JSON.parse(readFileSync11(path, "utf-8"));
3618
+ return JSON.parse(readFileSync13(path, "utf-8"));
3389
3619
  } catch {
3390
3620
  return { dismissed: [], created: {} };
3391
3621
  }
@@ -3393,8 +3623,8 @@ function loadTriageState() {
3393
3623
  return { dismissed: [], created: {} };
3394
3624
  }
3395
3625
  function saveTriageState(state) {
3396
- const dir = join13(homedir7(), ".panopticon");
3397
- if (!existsSync13(dir)) {
3626
+ const dir = join15(homedir7(), ".panopticon");
3627
+ if (!existsSync15(dir)) {
3398
3628
  mkdirSync6(dir, { recursive: true });
3399
3629
  }
3400
3630
  const path = getTriageStatePath();
@@ -3779,23 +4009,23 @@ import chalk19 from "chalk";
3779
4009
  // src/lib/context.ts
3780
4010
  init_esm_shims();
3781
4011
  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";
4012
+ import { existsSync as existsSync16, mkdirSync as mkdirSync7, readFileSync as readFileSync14, writeFileSync as writeFileSync7, appendFileSync, readdirSync as readdirSync12 } from "fs";
4013
+ import { join as join16 } from "path";
3784
4014
  function getStateFile(agentId) {
3785
- return join14(AGENTS_DIR, agentId, "STATE.md");
4015
+ return join16(AGENTS_DIR, agentId, "STATE.md");
3786
4016
  }
3787
4017
  function readAgentState(agentId) {
3788
4018
  const stateFile = getStateFile(agentId);
3789
- if (!existsSync14(stateFile)) return null;
4019
+ if (!existsSync16(stateFile)) return null;
3790
4020
  try {
3791
- const content = readFileSync12(stateFile, "utf-8");
4021
+ const content = readFileSync14(stateFile, "utf-8");
3792
4022
  return parseStateMd(content);
3793
4023
  } catch {
3794
4024
  return null;
3795
4025
  }
3796
4026
  }
3797
4027
  function writeAgentState(agentId, state) {
3798
- const dir = join14(AGENTS_DIR, agentId);
4028
+ const dir = join16(AGENTS_DIR, agentId);
3799
4029
  mkdirSync7(dir, { recursive: true });
3800
4030
  const content = generateStateMd(state);
3801
4031
  writeFileSync7(getStateFile(agentId), content);
@@ -3872,14 +4102,14 @@ function parseStateMd(content) {
3872
4102
  return state;
3873
4103
  }
3874
4104
  function getSummaryFile(agentId) {
3875
- return join14(AGENTS_DIR, agentId, "SUMMARY.md");
4105
+ return join16(AGENTS_DIR, agentId, "SUMMARY.md");
3876
4106
  }
3877
4107
  function appendSummary(agentId, summary) {
3878
- const dir = join14(AGENTS_DIR, agentId);
4108
+ const dir = join16(AGENTS_DIR, agentId);
3879
4109
  mkdirSync7(dir, { recursive: true });
3880
4110
  const summaryFile = getSummaryFile(agentId);
3881
4111
  const content = generateSummaryEntry(summary);
3882
- if (existsSync14(summaryFile)) {
4112
+ if (existsSync16(summaryFile)) {
3883
4113
  appendFileSync(summaryFile, "\n---\n\n" + content);
3884
4114
  } else {
3885
4115
  writeFileSync7(summaryFile, "# Work Summaries\n\n" + content);
@@ -3920,14 +4150,14 @@ function generateSummaryEntry(summary) {
3920
4150
  return lines.join("\n");
3921
4151
  }
3922
4152
  function getHistoryDir(agentId) {
3923
- return join14(AGENTS_DIR, agentId, "history");
4153
+ return join16(AGENTS_DIR, agentId, "history");
3924
4154
  }
3925
4155
  function logHistory(agentId, action, details) {
3926
4156
  const historyDir = getHistoryDir(agentId);
3927
4157
  mkdirSync7(historyDir, { recursive: true });
3928
4158
  const date = /* @__PURE__ */ new Date();
3929
4159
  const dateStr = date.toISOString().split("T")[0];
3930
- const historyFile = join14(historyDir, `${dateStr}.log`);
4160
+ const historyFile = join16(historyDir, `${dateStr}.log`);
3931
4161
  const timestamp = date.toISOString();
3932
4162
  const detailsStr = details ? ` ${JSON.stringify(details)}` : "";
3933
4163
  const logLine = `[${timestamp}] ${action}${detailsStr}
@@ -3936,13 +4166,13 @@ function logHistory(agentId, action, details) {
3936
4166
  }
3937
4167
  function searchHistory(agentId, pattern) {
3938
4168
  const historyDir = getHistoryDir(agentId);
3939
- if (!existsSync14(historyDir)) return [];
4169
+ if (!existsSync16(historyDir)) return [];
3940
4170
  const results = [];
3941
4171
  const regex = new RegExp(pattern, "i");
3942
- const files = readdirSync11(historyDir).filter((f) => f.endsWith(".log"));
4172
+ const files = readdirSync12(historyDir).filter((f) => f.endsWith(".log"));
3943
4173
  files.sort().reverse();
3944
4174
  for (const file of files) {
3945
- const content = readFileSync12(join14(historyDir, file), "utf-8");
4175
+ const content = readFileSync14(join16(historyDir, file), "utf-8");
3946
4176
  const lines = content.split("\n");
3947
4177
  for (const line of lines) {
3948
4178
  if (regex.test(line)) {
@@ -3954,13 +4184,13 @@ function searchHistory(agentId, pattern) {
3954
4184
  }
3955
4185
  function getRecentHistory(agentId, limit = 20) {
3956
4186
  const historyDir = getHistoryDir(agentId);
3957
- if (!existsSync14(historyDir)) return [];
4187
+ if (!existsSync16(historyDir)) return [];
3958
4188
  const results = [];
3959
- const files = readdirSync11(historyDir).filter((f) => f.endsWith(".log"));
4189
+ const files = readdirSync12(historyDir).filter((f) => f.endsWith(".log"));
3960
4190
  files.sort().reverse();
3961
4191
  for (const file of files) {
3962
4192
  if (results.length >= limit) break;
3963
- const content = readFileSync12(join14(historyDir, file), "utf-8");
4193
+ const content = readFileSync14(join16(historyDir, file), "utf-8");
3964
4194
  const lines = content.split("\n").filter((l) => l.trim());
3965
4195
  for (const line of lines.reverse()) {
3966
4196
  if (results.length >= limit) break;
@@ -3973,28 +4203,28 @@ function estimateTokens(text) {
3973
4203
  return Math.ceil(text.length / 4);
3974
4204
  }
3975
4205
  function getMaterializedDir(agentId) {
3976
- return join14(AGENTS_DIR, agentId, "materialized");
4206
+ return join16(AGENTS_DIR, agentId, "materialized");
3977
4207
  }
3978
4208
  function listMaterialized(agentId) {
3979
4209
  const dir = getMaterializedDir(agentId);
3980
- if (!existsSync14(dir)) return [];
3981
- return readdirSync11(dir).filter((f) => f.endsWith(".md")).map((f) => {
4210
+ if (!existsSync16(dir)) return [];
4211
+ return readdirSync12(dir).filter((f) => f.endsWith(".md")).map((f) => {
3982
4212
  const match = f.match(/^(.+)-(\d+)\.md$/);
3983
4213
  if (!match) return null;
3984
4214
  return {
3985
4215
  tool: match[1],
3986
4216
  timestamp: parseInt(match[2], 10),
3987
- file: join14(dir, f)
4217
+ file: join16(dir, f)
3988
4218
  };
3989
4219
  }).filter(Boolean);
3990
4220
  }
3991
4221
  function readMaterialized(filepath) {
3992
- if (!existsSync14(filepath)) return null;
3993
- return readFileSync12(filepath, "utf-8");
4222
+ if (!existsSync16(filepath)) return null;
4223
+ return readFileSync14(filepath, "utf-8");
3994
4224
  }
3995
4225
 
3996
4226
  // src/cli/commands/work/context.ts
3997
- import { readFileSync as readFileSync13, existsSync as existsSync15 } from "fs";
4227
+ import { readFileSync as readFileSync15, existsSync as existsSync17 } from "fs";
3998
4228
  async function contextCommand(action, arg1, arg2, options = {}) {
3999
4229
  const agentId = process.env.PANOPTICON_AGENT_ID || arg1 || "default";
4000
4230
  switch (action) {
@@ -4110,7 +4340,7 @@ History matches for "${pattern}":
4110
4340
  }
4111
4341
  case "materialize": {
4112
4342
  const filepath = arg1;
4113
- if (filepath && existsSync15(filepath)) {
4343
+ if (filepath && existsSync17(filepath)) {
4114
4344
  const content = readMaterialized(filepath);
4115
4345
  if (content) {
4116
4346
  console.log(content);
@@ -4138,8 +4368,8 @@ History matches for "${pattern}":
4138
4368
  return;
4139
4369
  }
4140
4370
  let text = target;
4141
- if (existsSync15(target)) {
4142
- text = readFileSync13(target, "utf-8");
4371
+ if (existsSync17(target)) {
4372
+ text = readFileSync15(target, "utf-8");
4143
4373
  }
4144
4374
  const tokens = estimateTokens(text);
4145
4375
  console.log(`Estimated tokens: ${chalk19.cyan(tokens.toLocaleString())}`);
@@ -4167,8 +4397,8 @@ import chalk20 from "chalk";
4167
4397
  init_esm_shims();
4168
4398
  init_paths();
4169
4399
  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";
4400
+ import { existsSync as existsSync18, mkdirSync as mkdirSync8, readFileSync as readFileSync16, writeFileSync as writeFileSync8 } from "fs";
4401
+ import { join as join17 } from "path";
4172
4402
  import { exec as exec3 } from "child_process";
4173
4403
  import { promisify as promisify3 } from "util";
4174
4404
  var execAsync3 = promisify3(exec3);
@@ -4177,7 +4407,7 @@ var DEFAULT_CONSECUTIVE_FAILURES = 3;
4177
4407
  var DEFAULT_COOLDOWN_MS = 5 * 60 * 1e3;
4178
4408
  var DEFAULT_CHECK_INTERVAL_MS = 30 * 1e3;
4179
4409
  function getHealthFile(agentId) {
4180
- return join15(AGENTS_DIR, agentId, "health.json");
4410
+ return join17(AGENTS_DIR, agentId, "health.json");
4181
4411
  }
4182
4412
  function getAgentHealth(agentId) {
4183
4413
  const healthFile = getHealthFile(agentId);
@@ -4189,9 +4419,9 @@ function getAgentHealth(agentId) {
4189
4419
  recoveryCount: 0,
4190
4420
  inCooldown: false
4191
4421
  };
4192
- if (existsSync16(healthFile)) {
4422
+ if (existsSync18(healthFile)) {
4193
4423
  try {
4194
- const stored = JSON.parse(readFileSync14(healthFile, "utf-8"));
4424
+ const stored = JSON.parse(readFileSync16(healthFile, "utf-8"));
4195
4425
  return { ...defaultHealth, ...stored };
4196
4426
  } catch {
4197
4427
  }
@@ -4199,7 +4429,7 @@ function getAgentHealth(agentId) {
4199
4429
  return defaultHealth;
4200
4430
  }
4201
4431
  function saveAgentHealth(health) {
4202
- const dir = join15(AGENTS_DIR, health.agentId);
4432
+ const dir = join17(AGENTS_DIR, health.agentId);
4203
4433
  mkdirSync8(dir, { recursive: true });
4204
4434
  writeFileSync8(getHealthFile(health.agentId), JSON.stringify(health, null, 2));
4205
4435
  }
@@ -4322,9 +4552,9 @@ async function runHealthCheck(config2 = {
4322
4552
  sessions = output.trim().split("\n").filter((s) => s.startsWith("agent-"));
4323
4553
  } catch {
4324
4554
  }
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);
4555
+ if (existsSync18(AGENTS_DIR)) {
4556
+ const { readdirSync: readdirSync22 } = await import("fs");
4557
+ const dirs = readdirSync22(AGENTS_DIR, { withFileTypes: true }).filter((d) => d.isDirectory() && d.name.startsWith("agent-")).map((d) => d.name);
4328
4558
  for (const dir of dirs) {
4329
4559
  if (!sessions.includes(dir)) {
4330
4560
  sessions.push(dir);
@@ -4560,15 +4790,15 @@ init_esm_shims();
4560
4790
  import chalk21 from "chalk";
4561
4791
  import ora11 from "ora";
4562
4792
  import inquirer3 from "inquirer";
4563
- import { existsSync as existsSync18, readFileSync as readFileSync16 } from "fs";
4564
- import { join as join17, dirname as dirname6 } from "path";
4793
+ import { existsSync as existsSync20, readFileSync as readFileSync18 } from "fs";
4794
+ import { join as join19, dirname as dirname6 } from "path";
4565
4795
  import { homedir as homedir8 } from "os";
4566
4796
  import { LinearClient } from "@linear/sdk";
4567
4797
 
4568
4798
  // src/lib/reopen.ts
4569
4799
  init_esm_shims();
4570
- import { existsSync as existsSync17, readFileSync as readFileSync15, appendFileSync as appendFileSync2 } from "fs";
4571
- import { join as join16 } from "path";
4800
+ import { existsSync as existsSync19, readFileSync as readFileSync17, appendFileSync as appendFileSync2 } from "fs";
4801
+ import { join as join18 } from "path";
4572
4802
 
4573
4803
  // src/dashboard/server/review-status.ts
4574
4804
  init_esm_shims();
@@ -4634,9 +4864,9 @@ function reopenWorkspaceState(issueId, workspacePath, options = {}) {
4634
4864
  result.queueItemsRemoved[specialistName] = removed;
4635
4865
  }
4636
4866
  }
4637
- const statePath = join16(workspacePath, ".planning", "STATE.md");
4638
- if (existsSync17(statePath)) {
4639
- const previousContent = readFileSync15(statePath, "utf-8");
4867
+ const statePath = join18(workspacePath, ".planning", "STATE.md");
4868
+ if (existsSync19(statePath)) {
4869
+ const previousContent = readFileSync17(statePath, "utf-8");
4640
4870
  const lastStatusMatch = previousContent.match(/\*\*STATUS:\s*([^*\n]+)\*\*/);
4641
4871
  const previousStatus = lastStatusMatch ? lastStatusMatch[1].trim() : "Unknown";
4642
4872
  const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
@@ -4672,9 +4902,9 @@ function reopenWorkspaceState(issueId, workspacePath, options = {}) {
4672
4902
  // src/cli/commands/work/reopen.ts
4673
4903
  init_projects();
4674
4904
  function getLinearApiKey5() {
4675
- const envFile = join17(homedir8(), ".panopticon.env");
4676
- if (existsSync18(envFile)) {
4677
- const content = readFileSync16(envFile, "utf-8");
4905
+ const envFile = join19(homedir8(), ".panopticon.env");
4906
+ if (existsSync20(envFile)) {
4907
+ const content = readFileSync18(envFile, "utf-8");
4678
4908
  const match = content.match(/LINEAR_API_KEY=(.+)/);
4679
4909
  if (match) return match[1].trim();
4680
4910
  }
@@ -4747,15 +4977,15 @@ function findLocalWorkspace2(issueId, startDir) {
4747
4977
  const normalizedId = issueId.toLowerCase();
4748
4978
  const resolved = resolveProjectFromIssue(issueId, []);
4749
4979
  if (resolved) {
4750
- const workspacePath = join17(resolved.projectPath, "workspaces", `feature-${normalizedId}`);
4751
- if (existsSync18(workspacePath)) return workspacePath;
4980
+ const workspacePath = join19(resolved.projectPath, "workspaces", `feature-${normalizedId}`);
4981
+ if (existsSync20(workspacePath)) return workspacePath;
4752
4982
  }
4753
4983
  let dir = startDir ?? process.cwd();
4754
4984
  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;
4985
+ const workspacesDir = join19(dir, "workspaces");
4986
+ if (existsSync20(workspacesDir)) {
4987
+ const workspacePath = join19(workspacesDir, `feature-${normalizedId}`);
4988
+ if (existsSync20(workspacePath)) return workspacePath;
4759
4989
  }
4760
4990
  const parent = dirname6(dir);
4761
4991
  if (parent === dir) break;
@@ -4881,8 +5111,22 @@ Previous state: ${issue.state}`
4881
5111
  console.log("");
4882
5112
  console.log(chalk21.green(`\u2713 ${issue.identifier} reopened and ready for re-work`));
4883
5113
  console.log("");
4884
- console.log(chalk21.dim("Start the agent to resume implementation:"));
4885
- console.log(` pan work ${id}`);
5114
+ try {
5115
+ const { getAgentState: getAgentState2 } = await import("../agents-E43Y3HNU.js");
5116
+ const agentId = `agent-${id.toLowerCase()}`;
5117
+ const agentState = getAgentState2(agentId);
5118
+ const agentRunning = agentState?.status === "active" || agentState?.status === "running";
5119
+ if (agentRunning) {
5120
+ console.log(chalk21.dim("Agent is still running. Send it context about the re-work:"));
5121
+ console.log(` pan tell ${id} "Issue reopened. <describe what needs to change>"`);
5122
+ } else {
5123
+ console.log(chalk21.dim("Start the agent to resume implementation:"));
5124
+ console.log(` pan work issue ${id}`);
5125
+ }
5126
+ } catch {
5127
+ console.log(chalk21.dim("Start the agent to resume implementation:"));
5128
+ console.log(` pan work issue ${id}`);
5129
+ }
4886
5130
  console.log("");
4887
5131
  } catch (error) {
4888
5132
  if (spinner.isSpinning) spinner.fail();
@@ -4985,8 +5229,8 @@ init_agents();
4985
5229
  init_tmux();
4986
5230
  import chalk24 from "chalk";
4987
5231
  import { homedir as homedir9 } from "os";
4988
- import { join as join18 } from "path";
4989
- import { existsSync as existsSync19, rmSync, readFileSync as readFileSync17 } from "fs";
5232
+ import { join as join20 } from "path";
5233
+ import { existsSync as existsSync21, rmSync, readFileSync as readFileSync19 } from "fs";
4990
5234
  import { exec as exec4 } from "child_process";
4991
5235
  import { promisify as promisify4 } from "util";
4992
5236
  var execAsync4 = promisify4(exec4);
@@ -5075,10 +5319,10 @@ async function wipeCommand(issueId, options) {
5075
5319
  }
5076
5320
  }
5077
5321
  const agentDirs = [
5078
- join18(homedir9(), ".panopticon", "agents", `agent-${issueLower}`)
5322
+ join20(homedir9(), ".panopticon", "agents", `agent-${issueLower}`)
5079
5323
  ];
5080
5324
  for (const dir of agentDirs) {
5081
- if (existsSync19(dir)) {
5325
+ if (existsSync21(dir)) {
5082
5326
  rmSync(dir, { recursive: true, force: true });
5083
5327
  cleanupLog.push(`Deleted agent state: ${dir}`);
5084
5328
  console.log(chalk24.green(` \u2713 Deleted agent state: ${dir.replace(homedir9(), "~")}`));
@@ -5086,11 +5330,11 @@ async function wipeCommand(issueId, options) {
5086
5330
  }
5087
5331
  let projectPath;
5088
5332
  const prefix = issueId.split("-")[0].toUpperCase();
5089
- const projectsYamlPath = join18(homedir9(), ".panopticon", "projects.yaml");
5090
- if (existsSync19(projectsYamlPath)) {
5333
+ const projectsYamlPath = join20(homedir9(), ".panopticon", "projects.yaml");
5334
+ if (existsSync21(projectsYamlPath)) {
5091
5335
  try {
5092
5336
  const yaml2 = await import("js-yaml");
5093
- const projectsConfig = yaml2.load(readFileSync17(projectsYamlPath, "utf-8"));
5337
+ const projectsConfig = yaml2.load(readFileSync19(projectsYamlPath, "utf-8"));
5094
5338
  for (const [, config2] of Object.entries(projectsConfig.projects || {})) {
5095
5339
  const projConfig = config2;
5096
5340
  if (projConfig.linear_team?.toUpperCase() === prefix) {
@@ -5102,19 +5346,19 @@ async function wipeCommand(issueId, options) {
5102
5346
  }
5103
5347
  }
5104
5348
  if (options.workspace && projectPath) {
5105
- const workspacePath = join18(projectPath, "workspaces", `feature-${issueLower}`);
5106
- if (existsSync19(workspacePath)) {
5349
+ const workspacePath = join20(projectPath, "workspaces", `feature-${issueLower}`);
5350
+ if (existsSync21(workspacePath)) {
5107
5351
  try {
5108
5352
  const gitDirs = ["api", "frontend", "fe", "."];
5109
5353
  for (const gitDir of gitDirs) {
5110
- const gitPath = join18(projectPath, gitDir);
5111
- if (existsSync19(join18(gitPath, ".git"))) {
5354
+ const gitPath = join20(projectPath, gitDir);
5355
+ if (existsSync21(join20(gitPath, ".git"))) {
5112
5356
  await execAsync4(`cd "${gitPath}" && git worktree remove "${workspacePath}" --force 2>/dev/null || true`);
5113
5357
  }
5114
5358
  }
5115
5359
  } catch (e) {
5116
5360
  }
5117
- if (existsSync19(workspacePath)) {
5361
+ if (existsSync21(workspacePath)) {
5118
5362
  rmSync(workspacePath, { recursive: true, force: true });
5119
5363
  }
5120
5364
  cleanupLog.push(`Deleted workspace: ${workspacePath}`);
@@ -5172,14 +5416,14 @@ import ora12 from "ora";
5172
5416
 
5173
5417
  // src/lib/shadow-utils.ts
5174
5418
  init_esm_shims();
5175
- import { existsSync as existsSync20, readFileSync as readFileSync18 } from "fs";
5176
- import { join as join19 } from "path";
5419
+ import { existsSync as existsSync22, readFileSync as readFileSync20 } from "fs";
5420
+ import { join as join21 } from "path";
5177
5421
  import { homedir as homedir10 } from "os";
5178
5422
  import chalk25 from "chalk";
5179
5423
  function getLinearApiKey6() {
5180
- const envFile = join19(homedir10(), ".panopticon.env");
5181
- if (existsSync20(envFile)) {
5182
- const content = readFileSync18(envFile, "utf-8");
5424
+ const envFile = join21(homedir10(), ".panopticon.env");
5425
+ if (existsSync22(envFile)) {
5426
+ const content = readFileSync20(envFile, "utf-8");
5183
5427
  const match = content.match(/LINEAR_API_KEY=(.+)/);
5184
5428
  if (match) return match[1].trim();
5185
5429
  }
@@ -5278,10 +5522,10 @@ init_esm_shims();
5278
5522
  import chalk27 from "chalk";
5279
5523
  import ora13 from "ora";
5280
5524
  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";
5525
+ import { existsSync as existsSync23, readFileSync as readFileSync21, writeFileSync as writeFileSync9, mkdirSync as mkdirSync9 } from "fs";
5526
+ import { join as join22, dirname as dirname7 } from "path";
5283
5527
  import { homedir as homedir11 } from "os";
5284
- var SYNC_QUEUE_FILE = join20(homedir11(), ".panopticon", "sync-queue.json");
5528
+ var SYNC_QUEUE_FILE = join22(homedir11(), ".panopticon", "sync-queue.json");
5285
5529
  async function syncToLinear(apiKey, issueId, targetState) {
5286
5530
  try {
5287
5531
  const { LinearClient: LinearClient2 } = await import("@linear/sdk");
@@ -5453,11 +5697,11 @@ async function syncCommand2(id, options = {}) {
5453
5697
  }
5454
5698
  }
5455
5699
  function loadSyncQueue() {
5456
- if (!existsSync21(SYNC_QUEUE_FILE)) {
5700
+ if (!existsSync23(SYNC_QUEUE_FILE)) {
5457
5701
  return [];
5458
5702
  }
5459
5703
  try {
5460
- const content = readFileSync19(SYNC_QUEUE_FILE, "utf-8");
5704
+ const content = readFileSync21(SYNC_QUEUE_FILE, "utf-8");
5461
5705
  return JSON.parse(content);
5462
5706
  } catch (error) {
5463
5707
  console.error(chalk27.yellow("Warning: Failed to load sync queue"));
@@ -5466,7 +5710,7 @@ function loadSyncQueue() {
5466
5710
  }
5467
5711
  function saveSyncQueue(queue) {
5468
5712
  const dir = dirname7(SYNC_QUEUE_FILE);
5469
- if (!existsSync21(dir)) {
5713
+ if (!existsSync23(dir)) {
5470
5714
  mkdirSync9(dir, { recursive: true });
5471
5715
  }
5472
5716
  try {
@@ -5593,8 +5837,8 @@ async function refreshCommand(id, options = {}) {
5593
5837
  init_esm_shims();
5594
5838
  init_tldr_daemon();
5595
5839
  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";
5840
+ import { existsSync as existsSync24, readdirSync as readdirSync14, readFileSync as readFileSync22, statSync as statSync5 } from "fs";
5841
+ import { join as join23 } from "path";
5598
5842
  async function tldrCommand(action, workspace, options = {}) {
5599
5843
  switch (action) {
5600
5844
  case "status":
@@ -5617,19 +5861,19 @@ async function tldrCommand(action, workspace, options = {}) {
5617
5861
  }
5618
5862
  async function statusCommand2(options) {
5619
5863
  const projectRoot = process.cwd();
5620
- const venvPath = join21(projectRoot, ".venv");
5864
+ const venvPath = join23(projectRoot, ".venv");
5621
5865
  const results = [];
5622
- if (existsSync22(venvPath)) {
5866
+ if (existsSync24(venvPath)) {
5623
5867
  const service = getTldrDaemonService(projectRoot, venvPath);
5624
5868
  const status = await service.getStatus();
5625
- const tldrPath = join21(projectRoot, ".tldr");
5869
+ const tldrPath = join23(projectRoot, ".tldr");
5626
5870
  let indexAge = "N/A";
5627
5871
  let fileCount = "N/A";
5628
- if (existsSync22(tldrPath)) {
5872
+ if (existsSync24(tldrPath)) {
5629
5873
  try {
5630
- const langPath = join21(tldrPath, "languages.json");
5631
- if (existsSync22(langPath)) {
5632
- const langData = JSON.parse(readFileSync20(langPath, "utf-8"));
5874
+ const langPath = join23(tldrPath, "languages.json");
5875
+ if (existsSync24(langPath)) {
5876
+ const langData = JSON.parse(readFileSync22(langPath, "utf-8"));
5633
5877
  if (langData.timestamp) {
5634
5878
  const ageMs = Date.now() - langData.timestamp * 1e3;
5635
5879
  const ageDays = Math.floor(ageMs / (1e3 * 60 * 60 * 24));
@@ -5637,14 +5881,14 @@ async function statusCommand2(options) {
5637
5881
  }
5638
5882
  }
5639
5883
  if (indexAge === "N/A") {
5640
- const stats = statSync4(tldrPath);
5884
+ const stats = statSync5(tldrPath);
5641
5885
  const ageMs = Date.now() - stats.mtimeMs;
5642
5886
  const ageDays = Math.floor(ageMs / (1e3 * 60 * 60 * 24));
5643
5887
  indexAge = ageDays === 0 ? "today" : `${ageDays}d ago`;
5644
5888
  }
5645
- const cgPath = join21(tldrPath, "cache", "call_graph.json");
5646
- if (existsSync22(cgPath)) {
5647
- const cg = JSON.parse(readFileSync20(cgPath, "utf-8"));
5889
+ const cgPath = join23(tldrPath, "cache", "call_graph.json");
5890
+ if (existsSync24(cgPath)) {
5891
+ const cg = JSON.parse(readFileSync22(cgPath, "utf-8"));
5648
5892
  if (Array.isArray(cg.edges)) {
5649
5893
  const files = /* @__PURE__ */ new Set();
5650
5894
  for (const e of cg.edges) {
@@ -5666,23 +5910,23 @@ async function statusCommand2(options) {
5666
5910
  fileCount
5667
5911
  });
5668
5912
  }
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-"));
5913
+ const workspacesDir = join23(projectRoot, "workspaces");
5914
+ if (existsSync24(workspacesDir)) {
5915
+ const workspaces = readdirSync14(workspacesDir, { withFileTypes: true }).filter((d) => d.isDirectory() && d.name.startsWith("feature-"));
5672
5916
  for (const ws of workspaces) {
5673
- const wsPath = join21(workspacesDir, ws.name);
5674
- const wsVenvPath = join21(wsPath, ".venv");
5675
- if (existsSync22(wsVenvPath)) {
5917
+ const wsPath = join23(workspacesDir, ws.name);
5918
+ const wsVenvPath = join23(wsPath, ".venv");
5919
+ if (existsSync24(wsVenvPath)) {
5676
5920
  const service = getTldrDaemonService(wsPath, wsVenvPath);
5677
5921
  const status = await service.getStatus();
5678
- const tldrPath = join21(wsPath, ".tldr");
5922
+ const tldrPath = join23(wsPath, ".tldr");
5679
5923
  let indexAge = "N/A";
5680
5924
  let fileCount = "N/A";
5681
- if (existsSync22(tldrPath)) {
5925
+ if (existsSync24(tldrPath)) {
5682
5926
  try {
5683
- const langPath = join21(tldrPath, "languages.json");
5684
- if (existsSync22(langPath)) {
5685
- const langData = JSON.parse(readFileSync20(langPath, "utf-8"));
5927
+ const langPath = join23(tldrPath, "languages.json");
5928
+ if (existsSync24(langPath)) {
5929
+ const langData = JSON.parse(readFileSync22(langPath, "utf-8"));
5686
5930
  if (langData.timestamp) {
5687
5931
  const ageMs = Date.now() - langData.timestamp * 1e3;
5688
5932
  const ageHours = Math.floor(ageMs / (1e3 * 60 * 60));
@@ -5690,14 +5934,14 @@ async function statusCommand2(options) {
5690
5934
  }
5691
5935
  }
5692
5936
  if (indexAge === "N/A") {
5693
- const stats = statSync4(tldrPath);
5937
+ const stats = statSync5(tldrPath);
5694
5938
  const ageMs = Date.now() - stats.mtimeMs;
5695
5939
  const ageHours = Math.floor(ageMs / (1e3 * 60 * 60));
5696
5940
  indexAge = ageHours === 0 ? "now" : ageHours < 24 ? `${ageHours}h ago` : `${Math.floor(ageHours / 24)}d ago`;
5697
5941
  }
5698
- const cgPath = join21(tldrPath, "cache", "call_graph.json");
5699
- if (existsSync22(cgPath)) {
5700
- const cg = JSON.parse(readFileSync20(cgPath, "utf-8"));
5942
+ const cgPath = join23(tldrPath, "cache", "call_graph.json");
5943
+ if (existsSync24(cgPath)) {
5944
+ const cg = JSON.parse(readFileSync22(cgPath, "utf-8"));
5701
5945
  if (Array.isArray(cg.edges)) {
5702
5946
  const files = /* @__PURE__ */ new Set();
5703
5947
  for (const e of cg.edges) {
@@ -5747,13 +5991,13 @@ async function statusCommand2(options) {
5747
5991
  async function startCommand(workspace, options) {
5748
5992
  const projectRoot = process.cwd();
5749
5993
  if (workspace) {
5750
- const wsPath = join21(projectRoot, "workspaces", workspace);
5751
- const venvPath = join21(wsPath, ".venv");
5752
- if (!existsSync22(wsPath)) {
5994
+ const wsPath = join23(projectRoot, "workspaces", workspace);
5995
+ const venvPath = join23(wsPath, ".venv");
5996
+ if (!existsSync24(wsPath)) {
5753
5997
  console.error(chalk29.red(`Error: Workspace not found: ${workspace}`));
5754
5998
  process.exit(1);
5755
5999
  }
5756
- if (!existsSync22(venvPath)) {
6000
+ if (!existsSync24(venvPath)) {
5757
6001
  console.error(chalk29.red(`Error: No .venv found in workspace: ${workspace}`));
5758
6002
  console.error(chalk29.dim("Workspace needs to be recreated with TLDR support"));
5759
6003
  process.exit(1);
@@ -5764,8 +6008,8 @@ async function startCommand(workspace, options) {
5764
6008
  console.log(chalk29.green(`\u2713 Started TLDR daemon for ${workspace}`));
5765
6009
  }
5766
6010
  } else {
5767
- const venvPath = join21(projectRoot, ".venv");
5768
- if (!existsSync22(venvPath)) {
6011
+ const venvPath = join23(projectRoot, ".venv");
6012
+ if (!existsSync24(venvPath)) {
5769
6013
  console.error(chalk29.red("Error: No .venv found in project root"));
5770
6014
  console.error(chalk29.dim("Run `pan setup` to configure TLDR"));
5771
6015
  process.exit(1);
@@ -5780,13 +6024,13 @@ async function startCommand(workspace, options) {
5780
6024
  async function stopCommand(workspace, options) {
5781
6025
  const projectRoot = process.cwd();
5782
6026
  if (workspace) {
5783
- const wsPath = join21(projectRoot, "workspaces", workspace);
5784
- const venvPath = join21(wsPath, ".venv");
5785
- if (!existsSync22(wsPath)) {
6027
+ const wsPath = join23(projectRoot, "workspaces", workspace);
6028
+ const venvPath = join23(wsPath, ".venv");
6029
+ if (!existsSync24(wsPath)) {
5786
6030
  console.error(chalk29.red(`Error: Workspace not found: ${workspace}`));
5787
6031
  process.exit(1);
5788
6032
  }
5789
- if (!existsSync22(venvPath)) {
6033
+ if (!existsSync24(venvPath)) {
5790
6034
  console.error(chalk29.red(`Error: No .venv found in workspace: ${workspace}`));
5791
6035
  process.exit(1);
5792
6036
  }
@@ -5796,8 +6040,8 @@ async function stopCommand(workspace, options) {
5796
6040
  console.log(chalk29.green(`\u2713 Stopped TLDR daemon for ${workspace}`));
5797
6041
  }
5798
6042
  } else {
5799
- const venvPath = join21(projectRoot, ".venv");
5800
- if (!existsSync22(venvPath)) {
6043
+ const venvPath = join23(projectRoot, ".venv");
6044
+ if (!existsSync24(venvPath)) {
5801
6045
  console.error(chalk29.red("Error: No .venv found in project root"));
5802
6046
  process.exit(1);
5803
6047
  }
@@ -5811,13 +6055,13 @@ async function stopCommand(workspace, options) {
5811
6055
  async function warmCommand(workspace, options) {
5812
6056
  const projectRoot = process.cwd();
5813
6057
  if (workspace) {
5814
- const wsPath = join21(projectRoot, "workspaces", workspace);
5815
- const venvPath = join21(wsPath, ".venv");
5816
- if (!existsSync22(wsPath)) {
6058
+ const wsPath = join23(projectRoot, "workspaces", workspace);
6059
+ const venvPath = join23(wsPath, ".venv");
6060
+ if (!existsSync24(wsPath)) {
5817
6061
  console.error(chalk29.red(`Error: Workspace not found: ${workspace}`));
5818
6062
  process.exit(1);
5819
6063
  }
5820
- if (!existsSync22(venvPath)) {
6064
+ if (!existsSync24(venvPath)) {
5821
6065
  console.error(chalk29.red(`Error: No .venv found in workspace: ${workspace}`));
5822
6066
  process.exit(1);
5823
6067
  }
@@ -5831,8 +6075,8 @@ async function warmCommand(workspace, options) {
5831
6075
  console.log(chalk29.green(`\u2713 Index warming complete for ${workspace}`));
5832
6076
  }
5833
6077
  } else {
5834
- const venvPath = join21(projectRoot, ".venv");
5835
- if (!existsSync22(venvPath)) {
6078
+ const venvPath = join23(projectRoot, ".venv");
6079
+ if (!existsSync24(venvPath)) {
5836
6080
  console.error(chalk29.red("Error: No .venv found in project root"));
5837
6081
  console.error(chalk29.dim("Run `pan setup` to configure TLDR"));
5838
6082
  process.exit(1);
@@ -5915,8 +6159,8 @@ async function syncMainCommand(id) {
5915
6159
  // src/cli/commands/work/close-out.ts
5916
6160
  init_esm_shims();
5917
6161
  import chalk31 from "chalk";
5918
- import { existsSync as existsSync27, readFileSync as readFileSync23 } from "fs";
5919
- import { join as join26 } from "path";
6162
+ import { existsSync as existsSync29, readFileSync as readFileSync25 } from "fs";
6163
+ import { join as join28 } from "path";
5920
6164
  import { homedir as homedir13 } from "os";
5921
6165
 
5922
6166
  // src/lib/lifecycle/index.ts
@@ -5924,8 +6168,8 @@ init_esm_shims();
5924
6168
 
5925
6169
  // src/lib/lifecycle/types.ts
5926
6170
  init_esm_shims();
5927
- import { existsSync as existsSync23, readFileSync as readFileSync21 } from "fs";
5928
- import { join as join22 } from "path";
6171
+ import { existsSync as existsSync25, readFileSync as readFileSync23 } from "fs";
6172
+ import { join as join24 } from "path";
5929
6173
  import { homedir as homedir12 } from "os";
5930
6174
  function stepOk(step, details) {
5931
6175
  return { step, success: true, skipped: false, details };
@@ -5938,9 +6182,9 @@ function stepFailed(step, error, details) {
5938
6182
  }
5939
6183
  function getLinearApiKey7() {
5940
6184
  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");
6185
+ const envFile = join24(homedir12(), ".panopticon.env");
6186
+ if (existsSync25(envFile)) {
6187
+ const content = readFileSync23(envFile, "utf-8");
5944
6188
  const match = content.match(/LINEAR_API_KEY=(.+)/);
5945
6189
  if (match) return match[1].trim();
5946
6190
  }
@@ -5950,20 +6194,20 @@ function getLinearApiKey7() {
5950
6194
  // src/lib/lifecycle/archive-planning.ts
5951
6195
  init_esm_shims();
5952
6196
  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";
6197
+ import { existsSync as existsSync26, mkdirSync as mkdirSync10, cpSync as cpSync2, rmSync as rmSync2 } from "fs";
6198
+ import { join as join25, dirname as dirname8 } from "path";
5955
6199
  import { exec as exec5 } from "child_process";
5956
6200
  import { promisify as promisify5 } from "util";
5957
6201
  var execAsync5 = promisify5(exec5);
5958
6202
  function findWorkspacePath(projectPath, issueLower) {
5959
6203
  const candidates = [
5960
- join23(projectPath, "workspaces", `feature-${issueLower}`),
5961
- join23(projectPath, "workspaces", issueLower),
5962
- join23(projectPath, ".worktrees", issueLower),
5963
- join23(dirname8(projectPath), `feature-${issueLower}`)
6204
+ join25(projectPath, "workspaces", `feature-${issueLower}`),
6205
+ join25(projectPath, "workspaces", issueLower),
6206
+ join25(projectPath, ".worktrees", issueLower),
6207
+ join25(dirname8(projectPath), `feature-${issueLower}`)
5964
6208
  ];
5965
6209
  for (const p of candidates) {
5966
- if (existsSync24(p)) return p;
6210
+ if (existsSync26(p)) return p;
5967
6211
  }
5968
6212
  return null;
5969
6213
  }
@@ -5971,28 +6215,28 @@ async function movePrd(ctx, opts = {}) {
5971
6215
  const { pushToRemote = true } = opts;
5972
6216
  const issueLower = ctx.issueId.toLowerCase();
5973
6217
  const step = "archive-planning:move-prd";
5974
- const completedPrdPath = join23(
6218
+ const completedPrdPath = join25(
5975
6219
  ctx.projectPath,
5976
6220
  PROJECT_DOCS_SUBDIR,
5977
6221
  PROJECT_PRDS_SUBDIR,
5978
6222
  PROJECT_PRDS_COMPLETED_SUBDIR,
5979
6223
  `${issueLower}-plan.md`
5980
6224
  );
5981
- const activePrdPath = join23(
6225
+ const activePrdPath = join25(
5982
6226
  ctx.projectPath,
5983
6227
  PROJECT_DOCS_SUBDIR,
5984
6228
  PROJECT_PRDS_SUBDIR,
5985
6229
  PROJECT_PRDS_ACTIVE_SUBDIR,
5986
6230
  `${issueLower}-plan.md`
5987
6231
  );
5988
- if (existsSync24(completedPrdPath)) {
6232
+ if (existsSync26(completedPrdPath)) {
5989
6233
  return stepSkipped(step, ["PRD already in completed/"]);
5990
6234
  }
5991
- if (!existsSync24(activePrdPath)) {
6235
+ if (!existsSync26(activePrdPath)) {
5992
6236
  return stepSkipped(step, ["No PRD found in active/ (may not have had one)"]);
5993
6237
  }
5994
6238
  const completedDir = dirname8(completedPrdPath);
5995
- if (!existsSync24(completedDir)) {
6239
+ if (!existsSync26(completedDir)) {
5996
6240
  mkdirSync10(completedDir, { recursive: true });
5997
6241
  }
5998
6242
  try {
@@ -6006,7 +6250,7 @@ async function movePrd(ctx, opts = {}) {
6006
6250
  }
6007
6251
  try {
6008
6252
  cpSync2(activePrdPath, completedPrdPath);
6009
- if (!existsSync24(completedPrdPath)) {
6253
+ if (!existsSync26(completedPrdPath)) {
6010
6254
  return stepFailed(step, "PRD copy appeared to succeed but file not found at destination");
6011
6255
  }
6012
6256
  return stepOk(step, ["Copied PRD to completed/ (git mv failed, plain copy succeeded)"]);
@@ -6018,14 +6262,14 @@ async function archiveWorkspaceArtifacts(ctx) {
6018
6262
  const issueLower = ctx.issueId.toLowerCase();
6019
6263
  const step = "archive-planning:archive-artifacts";
6020
6264
  const workspacePath = findWorkspacePath(ctx.projectPath, issueLower);
6021
- if (!workspacePath || !existsSync24(workspacePath)) {
6265
+ if (!workspacePath || !existsSync26(workspacePath)) {
6022
6266
  return stepSkipped(step, ["No workspace found to archive"]);
6023
6267
  }
6024
6268
  try {
6025
- let archiveDir = join23(ARCHIVES_DIR, issueLower);
6026
- if (existsSync24(archiveDir)) {
6269
+ let archiveDir = join25(ARCHIVES_DIR, issueLower);
6270
+ if (existsSync26(archiveDir)) {
6027
6271
  let version = 1;
6028
- while (existsSync24(`${archiveDir}.${version}`)) {
6272
+ while (existsSync26(`${archiveDir}.${version}`)) {
6029
6273
  version++;
6030
6274
  }
6031
6275
  const rotatedDir = `${archiveDir}.${version}`;
@@ -6034,24 +6278,24 @@ async function archiveWorkspaceArtifacts(ctx) {
6034
6278
  }
6035
6279
  mkdirSync10(archiveDir, { recursive: true });
6036
6280
  const details = [];
6037
- const feedbackDir = join23(workspacePath, ".planning", "feedback");
6038
- if (existsSync24(feedbackDir)) {
6039
- cpSync2(feedbackDir, join23(archiveDir, "feedback"), { recursive: true });
6281
+ const feedbackDir = join25(workspacePath, ".planning", "feedback");
6282
+ if (existsSync26(feedbackDir)) {
6283
+ cpSync2(feedbackDir, join25(archiveDir, "feedback"), { recursive: true });
6040
6284
  details.push("Archived feedback/");
6041
6285
  }
6042
- const stateMd = join23(workspacePath, ".planning", "STATE.md");
6043
- if (existsSync24(stateMd)) {
6044
- cpSync2(stateMd, join23(archiveDir, "STATE.md"));
6286
+ const stateMd = join25(workspacePath, ".planning", "STATE.md");
6287
+ if (existsSync26(stateMd)) {
6288
+ cpSync2(stateMd, join25(archiveDir, "STATE.md"));
6045
6289
  details.push("Archived STATE.md");
6046
6290
  }
6047
- const beadsDir = join23(workspacePath, ".planning", "beads");
6048
- if (existsSync24(beadsDir)) {
6049
- cpSync2(beadsDir, join23(archiveDir, "beads"), { recursive: true });
6291
+ const beadsDir = join25(workspacePath, ".planning", "beads");
6292
+ if (existsSync26(beadsDir)) {
6293
+ cpSync2(beadsDir, join25(archiveDir, "beads"), { recursive: true });
6050
6294
  details.push("Archived beads/");
6051
6295
  }
6052
- const prdMd = join23(workspacePath, ".planning", "PRD.md");
6053
- if (existsSync24(prdMd)) {
6054
- cpSync2(prdMd, join23(archiveDir, "PRD.md"));
6296
+ const prdMd = join25(workspacePath, ".planning", "PRD.md");
6297
+ if (existsSync26(prdMd)) {
6298
+ cpSync2(prdMd, join25(archiveDir, "PRD.md"));
6055
6299
  details.push("Archived workspace PRD.md");
6056
6300
  }
6057
6301
  details.push(`Archived to ${archiveDir}`);
@@ -6316,8 +6560,8 @@ async function applyLabelLinear(ctx, apiKey) {
6316
6560
  init_esm_shims();
6317
6561
  init_paths();
6318
6562
  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";
6563
+ import { existsSync as existsSync27, rmSync as rmSync3, unlinkSync as unlinkSync2 } from "fs";
6564
+ import { join as join26, basename as basename5 } from "path";
6321
6565
  import { exec as exec7 } from "child_process";
6322
6566
  import { promisify as promisify7 } from "util";
6323
6567
  var execAsync7 = promisify7(exec7);
@@ -6347,8 +6591,8 @@ async function killTmuxSessions(issueLower) {
6347
6591
  }
6348
6592
  async function stopTldrDaemon(workspacePath) {
6349
6593
  const step = "teardown:tldr-daemon";
6350
- const venvPath = join24(workspacePath, ".venv");
6351
- if (!existsSync25(venvPath)) {
6594
+ const venvPath = join26(workspacePath, ".venv");
6595
+ if (!existsSync27(venvPath)) {
6352
6596
  return stepSkipped(step, ["No .venv found"]);
6353
6597
  }
6354
6598
  try {
@@ -6363,7 +6607,7 @@ async function stopTldrDaemon(workspacePath) {
6363
6607
  async function stopDocker(workspacePath, projectName, issueLower) {
6364
6608
  const step = "teardown:docker";
6365
6609
  try {
6366
- const { stopWorkspaceDocker } = await import("../workspace-manager-KLHUCIZV.js");
6610
+ const { stopWorkspaceDocker } = await import("../workspace-manager-IE4JL2JP.js");
6367
6611
  await stopWorkspaceDocker(workspacePath, projectName, issueLower);
6368
6612
  return stepOk(step, ["Stopped Docker containers"]);
6369
6613
  } catch {
@@ -6372,7 +6616,7 @@ async function stopDocker(workspacePath, projectName, issueLower) {
6372
6616
  }
6373
6617
  async function removeWorktree(projectPath, workspacePath) {
6374
6618
  const step = "teardown:worktree";
6375
- if (!existsSync25(workspacePath)) {
6619
+ if (!existsSync27(workspacePath)) {
6376
6620
  return stepSkipped(step, ["Workspace directory does not exist"]);
6377
6621
  }
6378
6622
  try {
@@ -6390,12 +6634,12 @@ async function removeWorktree(projectPath, workspacePath) {
6390
6634
  async function removeAgentState(issueLower) {
6391
6635
  const step = "teardown:agent-state";
6392
6636
  const dirs = [
6393
- join24(AGENTS_DIR, `agent-${issueLower}`),
6394
- join24(AGENTS_DIR, `planning-${issueLower}`)
6637
+ join26(AGENTS_DIR, `agent-${issueLower}`),
6638
+ join26(AGENTS_DIR, `planning-${issueLower}`)
6395
6639
  ];
6396
6640
  let removed = 0;
6397
6641
  for (const dir of dirs) {
6398
- if (existsSync25(dir)) {
6642
+ if (existsSync27(dir)) {
6399
6643
  rmSync3(dir, { recursive: true, force: true });
6400
6644
  removed++;
6401
6645
  }
@@ -6438,8 +6682,8 @@ async function clearShadowState(issueId) {
6438
6682
  }
6439
6683
  async function clearLegacyPlanningDir(projectPath, issueLower) {
6440
6684
  const step = "teardown:legacy-planning-dir";
6441
- const legacyDir = join24(projectPath, ".planning", issueLower);
6442
- if (existsSync25(legacyDir)) {
6685
+ const legacyDir = join26(projectPath, ".planning", issueLower);
6686
+ if (existsSync27(legacyDir)) {
6443
6687
  rmSync3(legacyDir, { recursive: true, force: true });
6444
6688
  return stepOk(step, [`Deleted legacy planning dir: ${legacyDir}`]);
6445
6689
  }
@@ -6447,8 +6691,8 @@ async function clearLegacyPlanningDir(projectPath, issueLower) {
6447
6691
  }
6448
6692
  async function clearPlanningMarker(workspacePath) {
6449
6693
  const step = "teardown:planning-marker";
6450
- const markerPath = join24(workspacePath, ".planning", ".planning-complete");
6451
- if (existsSync25(markerPath)) {
6694
+ const markerPath = join26(workspacePath, ".planning", ".planning-complete");
6695
+ if (existsSync27(markerPath)) {
6452
6696
  unlinkSync2(markerPath);
6453
6697
  return stepOk(step, ["Cleared .planning-complete marker"]);
6454
6698
  }
@@ -6457,7 +6701,7 @@ async function clearPlanningMarker(workspacePath) {
6457
6701
  function buildPlaceholders(ctx, opts, workspacePath) {
6458
6702
  const issueLower = ctx.issueId.toLowerCase();
6459
6703
  const featureFolder = `feature-${issueLower}`;
6460
- const projName = opts.projectName || ctx.projectName || basename4(ctx.projectPath);
6704
+ const projName = opts.projectName || ctx.projectName || basename5(ctx.projectPath);
6461
6705
  const domain = opts.workspaceConfig?.dns?.domain || "localhost";
6462
6706
  return {
6463
6707
  FEATURE_NAME: issueLower,
@@ -6499,7 +6743,7 @@ async function teardownWorkspace(ctx, opts = {}) {
6499
6743
  results.push(await killTmuxSessions(issueLower));
6500
6744
  results.push(await clearShadowState(ctx.issueId));
6501
6745
  results.push(await clearLegacyPlanningDir(ctx.projectPath, issueLower));
6502
- if (workspacePath && existsSync25(workspacePath)) {
6746
+ if (workspacePath && existsSync27(workspacePath)) {
6503
6747
  if (shouldDeleteWorkspace) {
6504
6748
  results.push(await stopTldrDaemon(workspacePath));
6505
6749
  }
@@ -6538,8 +6782,8 @@ var execAsync8 = promisify8(exec8);
6538
6782
  // src/lib/lifecycle/workflows.ts
6539
6783
  init_esm_shims();
6540
6784
  init_paths();
6541
- import { existsSync as existsSync26, readFileSync as readFileSync22 } from "fs";
6542
- import { join as join25 } from "path";
6785
+ import { existsSync as existsSync28, readFileSync as readFileSync24 } from "fs";
6786
+ import { join as join27 } from "path";
6543
6787
  import { exec as exec9 } from "child_process";
6544
6788
  import { promisify as promisify9 } from "util";
6545
6789
  var execAsync9 = promisify9(exec9);
@@ -6584,20 +6828,34 @@ async function verifyBranchMerged(ctx) {
6584
6828
  const issueLower = ctx.issueId.toLowerCase();
6585
6829
  const branchName = `feature/${issueLower}`;
6586
6830
  try {
6831
+ try {
6832
+ const { loadReviewStatuses: loadReviewStatuses3 } = await import("../review-status-EPFG4XM7.js");
6833
+ const statuses = loadReviewStatuses3();
6834
+ const issueKey = ctx.issueId.toUpperCase();
6835
+ if (statuses[issueKey]?.mergeStatus === "merged") {
6836
+ return stepOk(step, ["Merge specialist confirmed merge completed"]);
6837
+ }
6838
+ } catch {
6839
+ }
6587
6840
  const { stdout: branchExists } = await execAsync9(
6588
6841
  `git branch --list "${branchName}" 2>/dev/null || true`,
6589
6842
  { cwd: ctx.projectPath, encoding: "utf-8" }
6590
6843
  );
6591
6844
  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;
6845
+ try {
6846
+ await execAsync9(
6847
+ `git merge-base --is-ancestor ${branchName} main`,
6848
+ { cwd: ctx.projectPath, encoding: "utf-8" }
6849
+ );
6850
+ return stepOk(step, ["All commits merged to main"]);
6851
+ } catch {
6852
+ const { stdout: unmerged } = await execAsync9(
6853
+ `git log main..${branchName} --oneline 2>/dev/null || true`,
6854
+ { cwd: ctx.projectPath, encoding: "utf-8" }
6855
+ );
6856
+ const count = unmerged.trim() ? unmerged.trim().split("\n").length : 0;
6598
6857
  return stepFailed(step, `${count} unmerged commit(s) on ${branchName}. Merge before closing out.`);
6599
6858
  }
6600
- return stepOk(step, ["All commits merged to main"]);
6601
6859
  }
6602
6860
  const { stdout: remoteBranch } = await execAsync9(
6603
6861
  `git ls-remote --heads origin "${branchName}" 2>/dev/null || true`,
@@ -6606,15 +6864,20 @@ async function verifyBranchMerged(ctx) {
6606
6864
  if (remoteBranch.trim()) {
6607
6865
  await execAsync9(`git fetch origin ${branchName}`, { cwd: ctx.projectPath }).catch(() => {
6608
6866
  });
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;
6867
+ try {
6868
+ await execAsync9(
6869
+ `git merge-base --is-ancestor origin/${branchName} main`,
6870
+ { cwd: ctx.projectPath, encoding: "utf-8" }
6871
+ );
6872
+ return stepOk(step, ["Remote branch fully merged"]);
6873
+ } catch {
6874
+ const { stdout: remoteUnmerged } = await execAsync9(
6875
+ `git log main..origin/${branchName} --oneline 2>/dev/null || true`,
6876
+ { cwd: ctx.projectPath, encoding: "utf-8" }
6877
+ );
6878
+ const count = remoteUnmerged.trim() ? remoteUnmerged.trim().split("\n").length : 0;
6615
6879
  return stepFailed(step, `${count} unmerged commit(s) on remote ${branchName}.`);
6616
6880
  }
6617
- return stepOk(step, ["Remote branch fully merged"]);
6618
6881
  }
6619
6882
  return stepOk(step, ["Branch already cleaned up (squash-merged)"]);
6620
6883
  } catch (err) {
@@ -6629,9 +6892,9 @@ async function clearReviewStatusStep(issueId) {
6629
6892
  return stepOk(step, ["Review status cleared"]);
6630
6893
  } catch {
6631
6894
  try {
6632
- const statusFile = join25(PANOPTICON_HOME, "review-status.json");
6633
- if (existsSync26(statusFile)) {
6634
- const data = JSON.parse(readFileSync22(statusFile, "utf-8"));
6895
+ const statusFile = join27(PANOPTICON_HOME, "review-status.json");
6896
+ if (existsSync28(statusFile)) {
6897
+ const data = JSON.parse(readFileSync24(statusFile, "utf-8"));
6635
6898
  const upperKey = issueId.toUpperCase();
6636
6899
  if (data[upperKey]) {
6637
6900
  delete data[upperKey];
@@ -6649,9 +6912,9 @@ async function clearReviewStatusStep(issueId) {
6649
6912
  // src/cli/commands/work/close-out.ts
6650
6913
  init_projects();
6651
6914
  function getGitHubConfig2() {
6652
- const envFile = join26(homedir13(), ".panopticon.env");
6653
- if (!existsSync27(envFile)) return null;
6654
- const content = readFileSync23(envFile, "utf-8");
6915
+ const envFile = join28(homedir13(), ".panopticon.env");
6916
+ if (!existsSync29(envFile)) return null;
6917
+ const content = readFileSync25(envFile, "utf-8");
6655
6918
  const reposMatch = content.match(/GITHUB_REPOS=(.+)/);
6656
6919
  if (!reposMatch) return null;
6657
6920
  const repoStr = reposMatch[1].trim();
@@ -6758,13 +7021,13 @@ Running close-out for ${issueUpper}...
6758
7021
  // src/cli/commands/work/linear-states.ts
6759
7022
  init_esm_shims();
6760
7023
  import chalk32 from "chalk";
6761
- import { readFileSync as readFileSync24, existsSync as existsSync28 } from "fs";
7024
+ import { readFileSync as readFileSync26, existsSync as existsSync30 } from "fs";
6762
7025
  import { homedir as homedir14 } from "os";
6763
- import { join as join27 } from "path";
7026
+ import { join as join29 } from "path";
6764
7027
  function getLinearApiKey8() {
6765
- const envFile = join27(homedir14(), ".panopticon.env");
6766
- if (existsSync28(envFile)) {
6767
- const content = readFileSync24(envFile, "utf-8");
7028
+ const envFile = join29(homedir14(), ".panopticon.env");
7029
+ if (existsSync30(envFile)) {
7030
+ const content = readFileSync26(envFile, "utf-8");
6768
7031
  const match = content.match(/LINEAR_API_KEY=(.+)/);
6769
7032
  if (match) return match[1].trim();
6770
7033
  }
@@ -6910,7 +7173,7 @@ async function cleanupStatesCommand(options) {
6910
7173
  function registerWorkCommands(program2) {
6911
7174
  const work = program2.command("work").description("Agent and work management");
6912
7175
  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);
7176
+ 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
7177
  work.command("tell <id> <message>").description("Send message to running agent").action(tellCommand);
6915
7178
  work.command("kill <id>").description("Kill an agent").option("--force", "Kill without confirmation").action(killCommand);
6916
7179
  work.command("pending").description("Show completed work awaiting review").action(pendingCommand);
@@ -6953,8 +7216,8 @@ function registerWorkCommands(program2) {
6953
7216
  init_esm_shims();
6954
7217
  import chalk33 from "chalk";
6955
7218
  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";
7219
+ import { existsSync as existsSync31, writeFileSync as writeFileSync10, rmSync as rmSync4, readFileSync as readFileSync27, realpathSync } from "fs";
7220
+ import { join as join30, basename as basename6 } from "path";
6958
7221
 
6959
7222
  // src/lib/worktree.ts
6960
7223
  init_esm_shims();
@@ -7052,8 +7315,8 @@ async function initializeWorkspaceBeads(workspacePath, issueId) {
7052
7315
  const match = stdout.match(/([a-z]+-[a-z0-9]+)/);
7053
7316
  return { success: true, beadId: match?.[1] };
7054
7317
  } else {
7055
- const beadsDir = join28(workspacePath, ".beads");
7056
- if (existsSync29(beadsDir)) {
7318
+ const beadsDir = join30(workspacePath, ".beads");
7319
+ if (existsSync31(beadsDir)) {
7057
7320
  rmSync4(beadsDir, { recursive: true, force: true });
7058
7321
  }
7059
7322
  const prefix = "workspace";
@@ -7146,7 +7409,7 @@ async function createCommand(issueId, options) {
7146
7409
  if (projectConfig.workspace.services && projectConfig.workspace.services.length > 0) {
7147
7410
  console.log("");
7148
7411
  console.log(chalk33.bold("To start services:"));
7149
- const composeProject = `${basename5(projectConfig.path)}-${folderName}`;
7412
+ const composeProject = `${basename6(projectConfig.path)}-${folderName}`;
7150
7413
  for (const service of projectConfig.workspace.services) {
7151
7414
  const containerName = `${composeProject}-${service.name}-1`;
7152
7415
  const cmd = service.docker_command || service.start_command;
@@ -7218,8 +7481,8 @@ async function createCommand(issueId, options) {
7218
7481
  projectRoot = process.cwd();
7219
7482
  }
7220
7483
  }
7221
- const workspacesDir = join28(projectRoot, "workspaces");
7222
- const workspacePath = join28(workspacesDir, folderName);
7484
+ const workspacesDir = join30(projectRoot, "workspaces");
7485
+ const workspacePath = join30(workspacesDir, folderName);
7223
7486
  if (options.dryRun) {
7224
7487
  spinner.info("Dry run mode");
7225
7488
  console.log("");
@@ -7232,11 +7495,11 @@ async function createCommand(issueId, options) {
7232
7495
  console.log(` Branch: ${chalk33.cyan(branchName)}`);
7233
7496
  return;
7234
7497
  }
7235
- if (existsSync29(workspacePath)) {
7498
+ if (existsSync31(workspacePath)) {
7236
7499
  spinner.fail(`Workspace already exists: ${workspacePath}`);
7237
7500
  process.exit(1);
7238
7501
  }
7239
- if (!existsSync29(join28(projectRoot, ".git"))) {
7502
+ if (!existsSync31(join30(projectRoot, ".git"))) {
7240
7503
  spinner.fail("Not a git repository. Run this from the project root.");
7241
7504
  process.exit(1);
7242
7505
  }
@@ -7260,7 +7523,7 @@ async function createCommand(issueId, options) {
7260
7523
  BEAD_ID: workspaceBeadId
7261
7524
  };
7262
7525
  const claudeMd = generateClaudeMd(projectRoot, variables);
7263
- writeFileSync10(join28(workspacePath, "CLAUDE.md"), claudeMd);
7526
+ writeFileSync10(join30(workspacePath, "CLAUDE.md"), claudeMd);
7264
7527
  let skillsResult = { added: [], updated: [], skipped: [], overlayed: [] };
7265
7528
  if (options.skills !== false) {
7266
7529
  spinner.text = "Merging skills and agents...";
@@ -7270,19 +7533,19 @@ async function createCommand(issueId, options) {
7270
7533
  let dockerError;
7271
7534
  if (options.docker) {
7272
7535
  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")
7536
+ join30(workspacePath, "docker-compose.yml"),
7537
+ join30(workspacePath, "docker-compose.yaml"),
7538
+ join30(workspacePath, ".devcontainer", "docker-compose.yml"),
7539
+ join30(workspacePath, ".devcontainer", "docker-compose.yaml"),
7540
+ join30(workspacePath, ".devcontainer", "docker-compose.devcontainer.yml"),
7541
+ join30(workspacePath, ".devcontainer", "compose.yml"),
7542
+ join30(workspacePath, ".devcontainer", "compose.yaml")
7280
7543
  ];
7281
- const composeFile = composeLocations.find((f) => existsSync29(f));
7544
+ const composeFile = composeLocations.find((f) => existsSync31(f));
7282
7545
  if (composeFile) {
7283
7546
  spinner.text = "Starting Docker containers...";
7284
7547
  try {
7285
- const composeDir = join28(composeFile, "..");
7548
+ const composeDir = join30(composeFile, "..");
7286
7549
  await execAsync10(`docker compose -f "${composeFile}" up -d --build`, {
7287
7550
  cwd: composeDir,
7288
7551
  encoding: "utf-8",
@@ -7349,15 +7612,15 @@ async function listCommand2(options) {
7349
7612
  const workspaces2 = [];
7350
7613
  if (isPolyrepo && config2.workspace?.repos) {
7351
7614
  for (const repo of config2.workspace.repos) {
7352
- const repoPath = join28(config2.path, repo.path);
7353
- if (!existsSync29(join28(repoPath, ".git"))) continue;
7615
+ const repoPath = join30(config2.path, repo.path);
7616
+ if (!existsSync31(join30(repoPath, ".git"))) continue;
7354
7617
  const repoWorktrees = listWorktrees(repoPath);
7355
7618
  for (const wt of repoWorktrees) {
7356
7619
  if (wt.path.includes("/workspaces/") || wt.path.includes("\\workspaces\\")) {
7357
7620
  const parts = wt.path.split("/workspaces/");
7358
7621
  if (parts.length > 1) {
7359
7622
  const workspaceDir = parts[1].split("/")[0];
7360
- const canonicalPath = join28(config2.path, "workspaces", workspaceDir);
7623
+ const canonicalPath = join30(config2.path, "workspaces", workspaceDir);
7361
7624
  if (!workspaces2.some((w) => w.path === canonicalPath)) {
7362
7625
  workspaces2.push({ ...wt, path: canonicalPath });
7363
7626
  }
@@ -7366,7 +7629,7 @@ async function listCommand2(options) {
7366
7629
  }
7367
7630
  }
7368
7631
  } else {
7369
- if (!existsSync29(join28(config2.path, ".git"))) continue;
7632
+ if (!existsSync31(join30(config2.path, ".git"))) continue;
7370
7633
  const worktrees2 = listWorktrees(config2.path);
7371
7634
  for (const wt of worktrees2) {
7372
7635
  if (wt.path.includes("/workspaces/") || wt.path.includes("\\workspaces\\")) {
@@ -7396,7 +7659,7 @@ async function listCommand2(options) {
7396
7659
  ${proj.projectName}
7397
7660
  `));
7398
7661
  for (const ws of proj.workspaces) {
7399
- const name = basename5(ws.path);
7662
+ const name = basename6(ws.path);
7400
7663
  const status = ws.prunable ? chalk33.yellow(" (prunable)") : "";
7401
7664
  console.log(` ${chalk33.cyan(name)}${status}`);
7402
7665
  console.log(` Branch: ${ws.branch || chalk33.dim("(detached)")}`);
@@ -7406,7 +7669,7 @@ ${proj.projectName}
7406
7669
  return;
7407
7670
  }
7408
7671
  const projectRoot = process.cwd();
7409
- if (!existsSync29(join28(projectRoot, ".git"))) {
7672
+ if (!existsSync31(join30(projectRoot, ".git"))) {
7410
7673
  console.error(chalk33.red("Not a git repository."));
7411
7674
  if (projects.length > 0) {
7412
7675
  console.log(chalk33.dim("Tip: Use --all to list workspaces across all registered projects."));
@@ -7431,7 +7694,7 @@ ${proj.projectName}
7431
7694
  }
7432
7695
  console.log(chalk33.bold("\nWorkspaces\n"));
7433
7696
  for (const ws of workspaces) {
7434
- const name = basename5(ws.path);
7697
+ const name = basename6(ws.path);
7435
7698
  const status = ws.prunable ? chalk33.yellow(" (prunable)") : "";
7436
7699
  console.log(`${chalk33.cyan(name)}${status}`);
7437
7700
  console.log(` Branch: ${ws.branch || chalk33.dim("(detached)")}`);
@@ -7502,17 +7765,17 @@ async function destroyCommand(issueId, options) {
7502
7765
  projectRoot = process.cwd();
7503
7766
  }
7504
7767
  }
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)) {
7768
+ const workspacePath = join30(projectRoot, "workspaces", folderName);
7769
+ if (!existsSync31(workspacePath)) {
7770
+ const cwdPath = join30(process.cwd(), "workspaces", folderName);
7771
+ if (projectRoot !== process.cwd() && existsSync31(cwdPath)) {
7509
7772
  projectRoot = process.cwd();
7510
7773
  } else {
7511
7774
  spinner.fail(`Workspace not found: ${workspacePath}`);
7512
7775
  process.exit(1);
7513
7776
  }
7514
7777
  }
7515
- const finalWorkspacePath = join28(projectRoot, "workspaces", folderName);
7778
+ const finalWorkspacePath = join30(projectRoot, "workspaces", folderName);
7516
7779
  spinner.text = "Removing git worktree...";
7517
7780
  removeWorktree2(projectRoot, finalWorkspacePath);
7518
7781
  spinner.succeed(`Workspace destroyed: ${folderName}`);
@@ -7598,13 +7861,13 @@ async function createRemoteWorkspace(issueId, normalizedId, branchName, spinner,
7598
7861
  await exe.ssh(vmName, `ssh-keyscan -t ed25519,rsa ${gitHost} >> ~/.ssh/known_hosts 2>/dev/null`);
7599
7862
  }
7600
7863
  const sshKeyPaths = [
7601
- join28(homedir15(), ".panopticon", "ssh", "exe-dev-key"),
7602
- join28(homedir15(), ".ssh", "id_ed25519"),
7603
- join28(homedir15(), ".ssh", "id_rsa")
7864
+ join30(homedir15(), ".panopticon", "ssh", "exe-dev-key"),
7865
+ join30(homedir15(), ".ssh", "id_ed25519"),
7866
+ join30(homedir15(), ".ssh", "id_rsa")
7604
7867
  ];
7605
- const sshKeyPath = sshKeyPaths.find((p) => existsSync29(p));
7868
+ const sshKeyPath = sshKeyPaths.find((p) => existsSync31(p));
7606
7869
  if (sshKeyPath) {
7607
- const sshKeyBase64 = Buffer.from(readFileSync25(sshKeyPath, "utf-8")).toString("base64");
7870
+ const sshKeyBase64 = Buffer.from(readFileSync27(sshKeyPath, "utf-8")).toString("base64");
7608
7871
  const keyFilename = sshKeyPath.includes("id_rsa") ? "id_rsa" : "id_ed25519";
7609
7872
  await exe.ssh(vmName, `echo '${sshKeyBase64}' | base64 -d > ~/.ssh/${keyFilename} && chmod 600 ~/.ssh/${keyFilename}`);
7610
7873
  }
@@ -7620,8 +7883,8 @@ async function createRemoteWorkspace(issueId, normalizedId, branchName, spinner,
7620
7883
  await exe.ssh(vmName, "mkdir -p ~/workspace");
7621
7884
  for (const repo of projectConfig.workspace.repos) {
7622
7885
  spinner.text = `Cloning ${repo.name}...`;
7623
- const rawRepoPath = join28(projectRoot, repo.path);
7624
- const actualRepoPath = existsSync29(rawRepoPath) ? realpathSync(rawRepoPath) : rawRepoPath;
7886
+ const rawRepoPath = join30(projectRoot, repo.path);
7887
+ const actualRepoPath = existsSync31(rawRepoPath) ? realpathSync(rawRepoPath) : rawRepoPath;
7625
7888
  let repoRemoteUrl;
7626
7889
  try {
7627
7890
  const { stdout } = await execAsync10("git remote get-url origin", {
@@ -7882,8 +8145,8 @@ async function sshCommand(issueId) {
7882
8145
  console.log(chalk33.dim("Create one with: pan workspace create --remote " + issueId));
7883
8146
  process.exit(1);
7884
8147
  }
7885
- const { spawn } = await import("child_process");
7886
- const child = spawn("exe", ["ssh", metadata.vmName], {
8148
+ const { spawn: spawn2 } = await import("child_process");
8149
+ const child = spawn2("exe", ["ssh", metadata.vmName], {
7887
8150
  stdio: "inherit"
7888
8151
  });
7889
8152
  child.on("exit", (code) => {
@@ -7954,8 +8217,8 @@ async function destroyRemoteWorkspace(issueId, normalizedId, metadata, spinner,
7954
8217
  } catch {
7955
8218
  }
7956
8219
  }
7957
- const metadataFile = join28(WORKSPACES_DIR, `${normalizedId}.yaml`);
7958
- if (existsSync29(metadataFile)) {
8220
+ const metadataFile = join30(WORKSPACES_DIR, `${normalizedId}.yaml`);
8221
+ if (existsSync31(metadataFile)) {
7959
8222
  rmSync4(metadataFile);
7960
8223
  }
7961
8224
  spinner.succeed(`Remote workspace ${issueId} destroyed`);
@@ -7981,9 +8244,9 @@ async function updateCommand(issueId, options) {
7981
8244
  process.exit(1);
7982
8245
  }
7983
8246
  const workspaceConfig = projectConfig.workspace;
7984
- const workspacesDir = join28(projectConfig.path, workspaceConfig?.workspaces_dir || "workspaces");
7985
- const workspacePath = join28(workspacesDir, folderName);
7986
- if (!existsSync29(workspacePath)) {
8247
+ const workspacesDir = join30(projectConfig.path, workspaceConfig?.workspaces_dir || "workspaces");
8248
+ const workspacePath = join30(workspacesDir, folderName);
8249
+ if (!existsSync31(workspacePath)) {
7987
8250
  spinner.fail(`Workspace not found: ${workspacePath}`);
7988
8251
  process.exit(1);
7989
8252
  }
@@ -8003,7 +8266,7 @@ async function updateCommand(issueId, options) {
8003
8266
  const result = mergeSkillsIntoWorkspace(workspacePath);
8004
8267
  if (workspaceConfig?.agent?.template_dir && (workspaceConfig.agent.copy_dirs || workspaceConfig.agent.symlinks)) {
8005
8268
  spinner.text = "Applying project template overlay...";
8006
- const templateDir = join28(projectConfig.path, workspaceConfig.agent.template_dir);
8269
+ const templateDir = join30(projectConfig.path, workspaceConfig.agent.template_dir);
8007
8270
  const overlayed = applyProjectTemplateOverlay(workspacePath, templateDir);
8008
8271
  result.overlayed = overlayed;
8009
8272
  }
@@ -8035,8 +8298,8 @@ import ora17 from "ora";
8035
8298
  // src/lib/test-runner.ts
8036
8299
  init_esm_shims();
8037
8300
  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";
8301
+ import { existsSync as existsSync32, mkdirSync as mkdirSync13, writeFileSync as writeFileSync11 } from "fs";
8302
+ import { join as join31, basename as basename7 } from "path";
8040
8303
  import { exec as exec11 } from "child_process";
8041
8304
  import { promisify as promisify11 } from "util";
8042
8305
  import { homedir as homedir16 } from "os";
@@ -8090,8 +8353,8 @@ function parseTestOutput(output, type) {
8090
8353
  return { passed, failed };
8091
8354
  }
8092
8355
  async function runTestSuite(testName, testConfig, workspacePath, placeholders, reportsDir, timestamp) {
8093
- const testPath = join29(workspacePath, testConfig.path);
8094
- const logFile = join29(reportsDir, `${testName}-${timestamp}.log`);
8356
+ const testPath = join31(workspacePath, testConfig.path);
8357
+ const logFile = join31(reportsDir, `${testName}-${timestamp}.log`);
8095
8358
  const result = {
8096
8359
  name: testName,
8097
8360
  status: "pending",
@@ -8183,8 +8446,8 @@ function generateReport(result) {
8183
8446
  async function sendNotification(result) {
8184
8447
  const title = `Tests (${result.target}): ${result.overallStatus === "passed" ? "\u2705 All Passed" : "\u274C Failed"}`;
8185
8448
  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)) {
8449
+ const notifyScript = join31(homedir16(), ".panopticon", "bin", "notify-complete");
8450
+ if (existsSync32(notifyScript)) {
8188
8451
  try {
8189
8452
  await execAsync11(`"${notifyScript}" "${result.target}" "${message}"`);
8190
8453
  } catch {
@@ -8202,12 +8465,12 @@ async function runTests(options) {
8202
8465
  let target;
8203
8466
  let baseUrl;
8204
8467
  if (featureName) {
8205
- const workspacesDir = join29(projectConfig.path, workspaceConfig?.workspaces_dir || "workspaces");
8468
+ const workspacesDir = join31(projectConfig.path, workspaceConfig?.workspaces_dir || "workspaces");
8206
8469
  const featureFolder2 = `feature-${featureName}`;
8207
- workspacePath = join29(workspacesDir, featureFolder2);
8470
+ workspacePath = join31(workspacesDir, featureFolder2);
8208
8471
  target = featureFolder2;
8209
8472
  baseUrl = workspaceConfig?.dns?.domain ? `https://${featureFolder2}.${workspaceConfig.dns.domain}` : `http://localhost:3000`;
8210
- if (!existsSync30(workspacePath)) {
8473
+ if (!existsSync32(workspacePath)) {
8211
8474
  throw new Error(`Workspace not found: ${workspacePath}`);
8212
8475
  }
8213
8476
  } else {
@@ -8220,16 +8483,16 @@ async function runTests(options) {
8220
8483
  FEATURE_NAME: featureName || "main",
8221
8484
  FEATURE_FOLDER: featureFolder,
8222
8485
  BRANCH_NAME: featureName ? `feature/${featureName}` : "main",
8223
- COMPOSE_PROJECT: `${basename6(projectConfig.path)}-${featureFolder}`,
8486
+ COMPOSE_PROJECT: `${basename7(projectConfig.path)}-${featureFolder}`,
8224
8487
  DOMAIN: workspaceConfig?.dns?.domain || "localhost",
8225
- PROJECT_NAME: basename6(projectConfig.path),
8488
+ PROJECT_NAME: basename7(projectConfig.path),
8226
8489
  PROJECT_PATH: projectConfig.path,
8227
8490
  WORKSPACE_PATH: workspacePath
8228
8491
  };
8229
- const reportsDir = join29(projectConfig.path, "reports");
8492
+ const reportsDir = join31(projectConfig.path, "reports");
8230
8493
  mkdirSync13(reportsDir, { recursive: true });
8231
8494
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
8232
- const reportFile = join29(reportsDir, `test-run-${target}-${timestamp}.md`);
8495
+ const reportFile = join31(reportsDir, `test-run-${target}-${timestamp}.md`);
8233
8496
  const result = {
8234
8497
  target,
8235
8498
  baseUrl,
@@ -8393,22 +8656,22 @@ import chalk35 from "chalk";
8393
8656
  import ora18 from "ora";
8394
8657
  import inquirer5 from "inquirer";
8395
8658
  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";
8659
+ import { existsSync as existsSync33, mkdirSync as mkdirSync14, writeFileSync as writeFileSync12, readFileSync as readFileSync28, copyFileSync, readdirSync as readdirSync15, statSync as statSync6 } from "fs";
8660
+ import { join as join32 } from "path";
8398
8661
  import { homedir as homedir17 } from "os";
8399
8662
  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);
8663
+ 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
8664
  }
8402
8665
  function copyDirectoryRecursive(source, dest) {
8403
- if (!existsSync31(source)) {
8666
+ if (!existsSync33(source)) {
8404
8667
  throw new Error(`Source directory not found: ${source}`);
8405
8668
  }
8406
8669
  mkdirSync14(dest, { recursive: true });
8407
- const entries = readdirSync14(source);
8670
+ const entries = readdirSync15(source);
8408
8671
  for (const entry of entries) {
8409
- const sourcePath = join30(source, entry);
8410
- const destPath = join30(dest, entry);
8411
- const stat = statSync5(sourcePath);
8672
+ const sourcePath = join32(source, entry);
8673
+ const destPath = join32(dest, entry);
8674
+ const stat = statSync6(sourcePath);
8412
8675
  if (stat.isDirectory()) {
8413
8676
  copyDirectoryRecursive(sourcePath, destPath);
8414
8677
  } else {
@@ -8493,6 +8756,13 @@ function checkPrerequisites() {
8493
8756
  message: hasRouter ? "installed" : "not found (will auto-install)",
8494
8757
  fix: "npm install -g @musistudio/claude-code-router"
8495
8758
  });
8759
+ const hasOx = checkCommand2("ox");
8760
+ results.push({
8761
+ name: "SageOx CLI (ox)",
8762
+ passed: hasOx,
8763
+ message: hasOx ? "installed" : "not found (will auto-install)",
8764
+ fix: "curl -sL https://github.com/eltmon/ox/releases/download/latest/ox-linux-amd64 -o ~/.local/bin/ox && chmod +x ~/.local/bin/ox"
8765
+ });
8496
8766
  const hasJq = checkCommand2("jq");
8497
8767
  results.push({
8498
8768
  name: "jq",
@@ -8500,7 +8770,7 @@ function checkPrerequisites() {
8500
8770
  message: hasJq ? "installed" : "not found",
8501
8771
  fix: "apt install jq / brew install jq"
8502
8772
  });
8503
- const hasTtyd = checkCommand2("ttyd") || existsSync31(join30(homedir17(), "bin", "ttyd"));
8773
+ const hasTtyd = checkCommand2("ttyd") || existsSync33(join32(homedir17(), "bin", "ttyd"));
8504
8774
  results.push({
8505
8775
  name: "ttyd",
8506
8776
  passed: hasTtyd,
@@ -8510,7 +8780,7 @@ function checkPrerequisites() {
8510
8780
  return {
8511
8781
  results,
8512
8782
  // 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)
8783
+ 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
8784
  };
8515
8785
  }
8516
8786
  function printPrereqStatus(prereqs) {
@@ -8576,9 +8846,9 @@ async function installCommand(options) {
8576
8846
  execSync4("brew install mkcert", { stdio: "pipe", timeout: 12e4 });
8577
8847
  spinner.succeed("mkcert installed via Homebrew");
8578
8848
  } else {
8579
- const binDir = join30(homedir17(), ".local", "bin");
8849
+ const binDir = join32(homedir17(), ".local", "bin");
8580
8850
  mkdirSync14(binDir, { recursive: true });
8581
- const mkcertPath = join30(binDir, "mkcert");
8851
+ const mkcertPath = join32(binDir, "mkcert");
8582
8852
  const arch = process.arch === "x64" ? "amd64" : process.arch;
8583
8853
  execSync4(`curl -sL "https://github.com/FiloSottile/mkcert/releases/latest/download/mkcert-v1.4.4-linux-${arch}" -o "${mkcertPath}" && chmod +x "${mkcertPath}"`, {
8584
8854
  stdio: "pipe",
@@ -8597,14 +8867,14 @@ async function installCommand(options) {
8597
8867
  execSync4("mkcert -install", { stdio: "pipe" });
8598
8868
  spinner.succeed("mkcert CA installed");
8599
8869
  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");
8870
+ const traefikCertFile = join32(TRAEFIK_CERTS_DIR, "_wildcard.pan.localhost.pem");
8871
+ const traefikKeyFile = join32(TRAEFIK_CERTS_DIR, "_wildcard.pan.localhost-key.pem");
8602
8872
  execSync4(
8603
8873
  `mkcert -cert-file "${traefikCertFile}" -key-file "${traefikKeyFile}" "pan.localhost" "*.pan.localhost" "*.localhost" localhost 127.0.0.1 ::1`,
8604
8874
  { stdio: "pipe" }
8605
8875
  );
8606
- const legacyCertFile = join30(CERTS_DIR, "localhost.pem");
8607
- const legacyKeyFile = join30(CERTS_DIR, "localhost-key.pem");
8876
+ const legacyCertFile = join32(CERTS_DIR, "localhost.pem");
8877
+ const legacyKeyFile = join32(CERTS_DIR, "localhost-key.pem");
8608
8878
  copyFileSync(traefikCertFile, legacyCertFile);
8609
8879
  copyFileSync(traefikKeyFile, legacyKeyFile);
8610
8880
  spinner.succeed("Wildcard certificates generated (*.pan.localhost, *.localhost)");
@@ -8622,13 +8892,13 @@ async function installCommand(options) {
8622
8892
  spinner.info("Skipping mkcert (not installed)");
8623
8893
  }
8624
8894
  }
8625
- const hasTtyd = checkCommand2("ttyd") || existsSync31(join30(homedir17(), "bin", "ttyd"));
8895
+ const hasTtyd = checkCommand2("ttyd") || existsSync33(join32(homedir17(), "bin", "ttyd"));
8626
8896
  if (!hasTtyd) {
8627
8897
  spinner.start("Installing ttyd (web terminal)...");
8628
8898
  try {
8629
- const binDir = join30(homedir17(), "bin");
8899
+ const binDir = join32(homedir17(), "bin");
8630
8900
  mkdirSync14(binDir, { recursive: true });
8631
- const ttydPath = join30(binDir, "ttyd");
8901
+ const ttydPath = join32(binDir, "ttyd");
8632
8902
  const plat2 = detectPlatform();
8633
8903
  let downloadUrl = "";
8634
8904
  if (plat2 === "darwin") {
@@ -8733,10 +9003,35 @@ async function installCommand(options) {
8733
9003
  spinner.info("claude-code-router already installed");
8734
9004
  }
8735
9005
  }
9006
+ if (options.skipSageox) {
9007
+ spinner.info("Skipping SageOx installation (--skip-sageox)");
9008
+ } else {
9009
+ const hasOxNow = checkCommand2("ox");
9010
+ if (!hasOxNow) {
9011
+ spinner.start("Installing SageOx CLI (ox)...");
9012
+ try {
9013
+ const binDir = join32(homedir17(), ".local", "bin");
9014
+ mkdirSync14(binDir, { recursive: true });
9015
+ const oxPath = join32(binDir, "ox");
9016
+ const arch = process.arch === "x64" ? "amd64" : process.arch;
9017
+ const plat2 = detectPlatform();
9018
+ const platform2 = plat2 === "darwin" ? "darwin" : "linux";
9019
+ execSync4(`curl -sL "https://github.com/eltmon/ox/releases/download/latest/ox-${platform2}-${arch}" -o "${oxPath}" && chmod +x "${oxPath}"`, {
9020
+ stdio: "pipe",
9021
+ timeout: 6e4
9022
+ });
9023
+ spinner.succeed(`SageOx CLI installed to ${oxPath}`);
9024
+ } catch {
9025
+ spinner.warn("SageOx installation failed - install manually from https://github.com/eltmon/ox/releases");
9026
+ }
9027
+ } else {
9028
+ spinner.info("SageOx CLI already installed");
9029
+ }
9030
+ }
8736
9031
  if (!options.minimal) {
8737
9032
  spinner.start("Setting up Traefik configuration...");
8738
9033
  try {
8739
- if (!existsSync31(join30(TRAEFIK_DIR, "docker-compose.yml"))) {
9034
+ if (!existsSync33(join32(TRAEFIK_DIR, "docker-compose.yml"))) {
8740
9035
  copyDirectoryRecursive(SOURCE_TRAEFIK_TEMPLATES, TRAEFIK_DIR);
8741
9036
  cleanupTemplateFiles();
8742
9037
  spinner.succeed("Traefik configuration created from templates");
@@ -8749,9 +9044,9 @@ async function installCommand(options) {
8749
9044
  if (generateTlsConfig()) {
8750
9045
  spinner.succeed("TLS config generated (tls.yml)");
8751
9046
  }
8752
- const existingCompose = join30(TRAEFIK_DIR, "docker-compose.yml");
8753
- if (existsSync31(existingCompose)) {
8754
- const content = readFileSync26(existingCompose, "utf-8");
9047
+ const existingCompose = join32(TRAEFIK_DIR, "docker-compose.yml");
9048
+ if (existsSync33(existingCompose)) {
9049
+ const content = readFileSync28(existingCompose, "utf-8");
8755
9050
  if (content.includes("panopticon:") && !content.includes("external: true")) {
8756
9051
  const patched = content.replace(
8757
9052
  /networks:\s*\n\s*panopticon:\s*\n\s*name: panopticon\s*\n\s*driver: bridge/,
@@ -8766,8 +9061,8 @@ async function installCommand(options) {
8766
9061
  console.log(chalk35.yellow("You can set up Traefik manually later"));
8767
9062
  }
8768
9063
  }
8769
- const configFile = join30(PANOPTICON_HOME, "config.toml");
8770
- const configExists = existsSync31(configFile);
9064
+ const configFile = join32(PANOPTICON_HOME, "config.toml");
9065
+ const configExists = existsSync33(configFile);
8771
9066
  if (!configExists) {
8772
9067
  spinner.start("Creating default config...");
8773
9068
  } else {
@@ -8979,13 +9274,13 @@ function getHealthLabel(state) {
8979
9274
  init_esm_shims();
8980
9275
  init_paths();
8981
9276
  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");
9277
+ import { join as join33 } from "path";
9278
+ import { existsSync as existsSync34, mkdirSync as mkdirSync15 } from "fs";
9279
+ var CLOISTER_DB_PATH = join33(PANOPTICON_HOME, "cloister.db");
8985
9280
  var RETENTION_DAYS = 7;
8986
9281
  var db = null;
8987
9282
  function initHealthDatabase() {
8988
- if (!existsSync32(PANOPTICON_HOME)) {
9283
+ if (!existsSync34(PANOPTICON_HOME)) {
8989
9284
  mkdirSync15(PANOPTICON_HOME, { recursive: true });
8990
9285
  }
8991
9286
  db = new Database(CLOISTER_DB_PATH);
@@ -9064,10 +9359,10 @@ init_esm_shims();
9064
9359
  init_agents();
9065
9360
  init_tmux();
9066
9361
  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";
9362
+ import { existsSync as existsSync35, readFileSync as readFileSync29, statSync as statSync7, mkdirSync as mkdirSync16, writeFileSync as writeFileSync13 } from "fs";
9363
+ import { join as join34 } from "path";
9069
9364
  import { homedir as homedir18 } from "os";
9070
- var CLAUDE_PROJECTS_DIR = join32(homedir18(), ".claude", "projects");
9365
+ var CLAUDE_PROJECTS_DIR = join34(homedir18(), ".claude", "projects");
9071
9366
  var ClaudeCodeRuntime = class {
9072
9367
  name = "claude-code";
9073
9368
  /**
@@ -9077,15 +9372,15 @@ var ClaudeCodeRuntime = class {
9077
9372
  * We need to find the project directory that contains sessions for this workspace.
9078
9373
  */
9079
9374
  getProjectDirForWorkspace(workspace) {
9080
- if (!existsSync33(CLAUDE_PROJECTS_DIR)) {
9375
+ if (!existsSync35(CLAUDE_PROJECTS_DIR)) {
9081
9376
  return null;
9082
9377
  }
9083
9378
  const projectDirs = getProjectDirs();
9084
9379
  for (const projectDir of projectDirs) {
9085
- const indexPath = join32(projectDir, "sessions-index.json");
9086
- if (existsSync33(indexPath)) {
9380
+ const indexPath = join34(projectDir, "sessions-index.json");
9381
+ if (existsSync35(indexPath)) {
9087
9382
  try {
9088
- const indexContent = readFileSync27(indexPath, "utf-8");
9383
+ const indexContent = readFileSync29(indexPath, "utf-8");
9089
9384
  if (indexContent.includes(workspace)) {
9090
9385
  return projectDir;
9091
9386
  }
@@ -9099,12 +9394,12 @@ var ClaudeCodeRuntime = class {
9099
9394
  * Get the active session ID for an agent from the sessions index
9100
9395
  */
9101
9396
  getActiveSessionId(projectDir) {
9102
- const indexPath = join32(projectDir, "sessions-index.json");
9103
- if (!existsSync33(indexPath)) {
9397
+ const indexPath = join34(projectDir, "sessions-index.json");
9398
+ if (!existsSync35(indexPath)) {
9104
9399
  return null;
9105
9400
  }
9106
9401
  try {
9107
- const indexContent = readFileSync27(indexPath, "utf-8");
9402
+ const indexContent = readFileSync29(indexPath, "utf-8");
9108
9403
  const index = JSON.parse(indexContent);
9109
9404
  if (index.sessions && Array.isArray(index.sessions)) {
9110
9405
  const sessions = index.sessions;
@@ -9139,8 +9434,8 @@ var ClaudeCodeRuntime = class {
9139
9434
  }
9140
9435
  const sessionId = this.getActiveSessionId(projectDir);
9141
9436
  if (sessionId) {
9142
- const sessionPath = join32(projectDir, `${sessionId}.jsonl`);
9143
- if (existsSync33(sessionPath)) {
9437
+ const sessionPath = join34(projectDir, `${sessionId}.jsonl`);
9438
+ if (existsSync35(sessionPath)) {
9144
9439
  return sessionPath;
9145
9440
  }
9146
9441
  }
@@ -9153,11 +9448,11 @@ var ClaudeCodeRuntime = class {
9153
9448
  */
9154
9449
  getLastActivity(agentId) {
9155
9450
  const sessionPath = this.getSessionPath(agentId);
9156
- if (!sessionPath || !existsSync33(sessionPath)) {
9451
+ if (!sessionPath || !existsSync35(sessionPath)) {
9157
9452
  return null;
9158
9453
  }
9159
9454
  try {
9160
- const stat = statSync6(sessionPath);
9455
+ const stat = statSync7(sessionPath);
9161
9456
  return stat.mtime;
9162
9457
  } catch {
9163
9458
  return null;
@@ -9167,12 +9462,12 @@ var ClaudeCodeRuntime = class {
9167
9462
  * Read active heartbeat file if it exists
9168
9463
  */
9169
9464
  getActiveHeartbeat(agentId) {
9170
- const heartbeatPath = join32(homedir18(), ".panopticon", "heartbeats", `${agentId}.json`);
9171
- if (!existsSync33(heartbeatPath)) {
9465
+ const heartbeatPath = join34(homedir18(), ".panopticon", "heartbeats", `${agentId}.json`);
9466
+ if (!existsSync35(heartbeatPath)) {
9172
9467
  return null;
9173
9468
  }
9174
9469
  try {
9175
- const content = readFileSync27(heartbeatPath, "utf-8");
9470
+ const content = readFileSync29(heartbeatPath, "utf-8");
9176
9471
  const data = JSON.parse(content);
9177
9472
  const timestamp = new Date(data.timestamp);
9178
9473
  const now = /* @__PURE__ */ new Date();
@@ -9276,11 +9571,11 @@ var ClaudeCodeRuntime = class {
9276
9571
  throw new Error(`Agent ${agentId} is not running`);
9277
9572
  }
9278
9573
  await sendKeysAsync(agentId, message);
9279
- const mailDir = join32(getAgentDir(agentId), "mail");
9574
+ const mailDir = join34(getAgentDir(agentId), "mail");
9280
9575
  mkdirSync16(mailDir, { recursive: true });
9281
9576
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
9282
9577
  writeFileSync13(
9283
- join32(mailDir, `${timestamp}.md`),
9578
+ join34(mailDir, `${timestamp}.md`),
9284
9579
  `# Message
9285
9580
 
9286
9581
  ${message}
@@ -9361,7 +9656,7 @@ ${message}
9361
9656
  if (!sessionUsage) {
9362
9657
  return null;
9363
9658
  }
9364
- const stat = statSync6(file);
9659
+ const stat = statSync7(file);
9365
9660
  return {
9366
9661
  id: sessionUsage.sessionId,
9367
9662
  agentId: "unknown",
@@ -9616,12 +9911,12 @@ async function checkAllTriggers(agentId, workspace, issueId, currentModel, healt
9616
9911
  init_esm_shims();
9617
9912
  init_agents();
9618
9913
  import { writeFileSync as writeFileSync14, mkdirSync as mkdirSync17 } from "fs";
9619
- import { join as join34 } from "path";
9914
+ import { join as join36 } from "path";
9620
9915
 
9621
9916
  // src/lib/cloister/handoff-context.ts
9622
9917
  init_esm_shims();
9623
- import { existsSync as existsSync34, readFileSync as readFileSync28 } from "fs";
9624
- import { join as join33 } from "path";
9918
+ import { existsSync as existsSync36, readFileSync as readFileSync30 } from "fs";
9919
+ import { join as join35 } from "path";
9625
9920
  import { exec as exec13 } from "child_process";
9626
9921
  import { promisify as promisify13 } from "util";
9627
9922
  var execAsync13 = promisify13(exec13);
@@ -9645,13 +9940,13 @@ async function captureHandoffContext(agentState, targetModel, reason) {
9645
9940
  }
9646
9941
  async function captureFiles(context, workspace) {
9647
9942
  try {
9648
- const stateFile = join33(workspace, ".planning/STATE.md");
9649
- if (existsSync34(stateFile)) {
9650
- context.stateFile = readFileSync28(stateFile, "utf-8");
9943
+ const stateFile = join35(workspace, ".planning/STATE.md");
9944
+ if (existsSync36(stateFile)) {
9945
+ context.stateFile = readFileSync30(stateFile, "utf-8");
9651
9946
  }
9652
- const claudeMd = join33(workspace, "CLAUDE.md");
9653
- if (existsSync34(claudeMd)) {
9654
- context.claudeMd = readFileSync28(claudeMd, "utf-8");
9947
+ const claudeMd = join35(workspace, "CLAUDE.md");
9948
+ if (existsSync36(claudeMd)) {
9949
+ context.claudeMd = readFileSync30(claudeMd, "utf-8");
9655
9950
  }
9656
9951
  } catch (error) {
9657
9952
  console.error("Error capturing files:", error);
@@ -9842,15 +10137,16 @@ async function performKillAndSpawn(state, options) {
9842
10137
  const context = await captureHandoffContext(state, options.targetModel, options.reason);
9843
10138
  stopAgent(state.id);
9844
10139
  const prompt = buildHandoffPrompt(context, options.additionalInstructions);
9845
- const handoffDir = join34(getAgentDir(state.id), "handoffs");
10140
+ const handoffDir = join36(getAgentDir(state.id), "handoffs");
9846
10141
  mkdirSync17(handoffDir, { recursive: true });
9847
- const handoffFile = join34(handoffDir, `handoff-${Date.now()}.md`);
10142
+ const handoffFile = join36(handoffDir, `handoff-${Date.now()}.md`);
9848
10143
  writeFileSync14(handoffFile, prompt);
9849
10144
  const newState = await spawnAgent({
9850
10145
  issueId: state.issueId,
9851
10146
  workspace: state.workspace,
9852
10147
  runtime: state.runtime,
9853
10148
  model: options.targetModel,
10149
+ phase: "implementation",
9854
10150
  prompt
9855
10151
  });
9856
10152
  newState.handoffCount = (state.handoffCount || 0) + 1;
@@ -9952,12 +10248,12 @@ function sleep(ms) {
9952
10248
  // src/lib/cloister/handoff-logger.ts
9953
10249
  init_esm_shims();
9954
10250
  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");
10251
+ import { existsSync as existsSync38, mkdirSync as mkdirSync18, appendFileSync as appendFileSync3, readFileSync as readFileSync31, writeFileSync as writeFileSync15 } from "fs";
10252
+ import { join as join37 } from "path";
10253
+ var HANDOFF_LOG_FILE = join37(PANOPTICON_HOME, "logs", "handoffs.jsonl");
9958
10254
  function ensureLogDir() {
9959
- const logDir = join35(PANOPTICON_HOME, "logs");
9960
- if (!existsSync36(logDir)) {
10255
+ const logDir = join37(PANOPTICON_HOME, "logs");
10256
+ if (!existsSync38(logDir)) {
9961
10257
  mkdirSync18(logDir, { recursive: true });
9962
10258
  }
9963
10259
  }
@@ -10001,8 +10297,8 @@ function createHandoffEvent(agentId, issueId, context, trigger, success, errorMe
10001
10297
  // src/lib/cloister/fpp-violations.ts
10002
10298
  init_esm_shims();
10003
10299
  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";
10300
+ import { readFileSync as readFileSync32, existsSync as existsSync39, writeFileSync as writeFileSync16, mkdirSync as mkdirSync19, unlinkSync as unlinkSync3 } from "fs";
10301
+ import { join as join38, dirname as dirname11 } from "path";
10006
10302
  init_paths();
10007
10303
  var DEFAULT_FPP_CONFIG = {
10008
10304
  hook_idle_minutes: 5,
@@ -10010,13 +10306,13 @@ var DEFAULT_FPP_CONFIG = {
10010
10306
  review_pending_minutes: 15,
10011
10307
  max_nudges: 3
10012
10308
  };
10013
- var VIOLATIONS_DATA_FILE = join36(PANOPTICON_HOME, "fpp-violations.json");
10309
+ var VIOLATIONS_DATA_FILE = join38(PANOPTICON_HOME, "fpp-violations.json");
10014
10310
  function loadViolations() {
10015
- if (!existsSync37(VIOLATIONS_DATA_FILE)) {
10311
+ if (!existsSync39(VIOLATIONS_DATA_FILE)) {
10016
10312
  return /* @__PURE__ */ new Map();
10017
10313
  }
10018
10314
  try {
10019
- const fileContent = readFileSync30(VIOLATIONS_DATA_FILE, "utf-8");
10315
+ const fileContent = readFileSync32(VIOLATIONS_DATA_FILE, "utf-8");
10020
10316
  const persisted = JSON.parse(fileContent);
10021
10317
  return new Map(persisted.violations || []);
10022
10318
  } catch (error) {
@@ -10027,7 +10323,7 @@ function loadViolations() {
10027
10323
  function saveViolations(violations) {
10028
10324
  try {
10029
10325
  const dir = dirname11(VIOLATIONS_DATA_FILE);
10030
- if (!existsSync37(dir)) {
10326
+ if (!existsSync39(dir)) {
10031
10327
  mkdirSync19(dir, { recursive: true });
10032
10328
  }
10033
10329
  const persisted = {
@@ -10035,7 +10331,7 @@ function saveViolations(violations) {
10035
10331
  };
10036
10332
  const tempFile = `${VIOLATIONS_DATA_FILE}.tmp`;
10037
10333
  writeFileSync16(tempFile, JSON.stringify(persisted, null, 2));
10038
- writeFileSync16(VIOLATIONS_DATA_FILE, readFileSync30(tempFile));
10334
+ writeFileSync16(VIOLATIONS_DATA_FILE, readFileSync32(tempFile));
10039
10335
  try {
10040
10336
  unlinkSync3(tempFile);
10041
10337
  } catch (unlinkError) {
@@ -10125,11 +10421,11 @@ function clearOldViolations(hoursOld = 24) {
10125
10421
  init_esm_shims();
10126
10422
  init_paths();
10127
10423
  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");
10424
+ import { readFileSync as readFileSync33, existsSync as existsSync40, writeFileSync as writeFileSync17, mkdirSync as mkdirSync20, unlinkSync as unlinkSync4 } from "fs";
10425
+ import { join as join39, dirname as dirname12 } from "path";
10426
+ var COST_DATA_FILE = join39(PANOPTICON_HOME, "cost-data.json");
10131
10427
  function loadCostData() {
10132
- if (!existsSync38(COST_DATA_FILE)) {
10428
+ if (!existsSync40(COST_DATA_FILE)) {
10133
10429
  return {
10134
10430
  perAgent: /* @__PURE__ */ new Map(),
10135
10431
  perIssue: /* @__PURE__ */ new Map(),
@@ -10138,7 +10434,7 @@ function loadCostData() {
10138
10434
  };
10139
10435
  }
10140
10436
  try {
10141
- const fileContent = readFileSync31(COST_DATA_FILE, "utf-8");
10437
+ const fileContent = readFileSync33(COST_DATA_FILE, "utf-8");
10142
10438
  const persisted = JSON.parse(fileContent);
10143
10439
  return {
10144
10440
  perAgent: new Map(Object.entries(persisted.perAgent || {})),
@@ -10159,7 +10455,7 @@ function loadCostData() {
10159
10455
  function saveCostData(data) {
10160
10456
  try {
10161
10457
  const dir = dirname12(COST_DATA_FILE);
10162
- if (!existsSync38(dir)) {
10458
+ if (!existsSync40(dir)) {
10163
10459
  mkdirSync20(dir, { recursive: true });
10164
10460
  }
10165
10461
  const persisted = {
@@ -10170,7 +10466,7 @@ function saveCostData(data) {
10170
10466
  };
10171
10467
  const tempFile = `${COST_DATA_FILE}.tmp`;
10172
10468
  writeFileSync17(tempFile, JSON.stringify(persisted, null, 2));
10173
- writeFileSync17(COST_DATA_FILE, readFileSync31(tempFile));
10469
+ writeFileSync17(COST_DATA_FILE, readFileSync33(tempFile));
10174
10470
  try {
10175
10471
  unlinkSync4(tempFile);
10176
10472
  } catch (unlinkError) {
@@ -10291,7 +10587,7 @@ function getCostSummary() {
10291
10587
  init_esm_shims();
10292
10588
  init_paths();
10293
10589
  import { writeFileSync as writeFileSync18 } from "fs";
10294
- import { join as join38 } from "path";
10590
+ import { join as join40 } from "path";
10295
10591
  import { exec as exec14 } from "child_process";
10296
10592
  import { promisify as promisify14 } from "util";
10297
10593
  init_agents();
@@ -10431,7 +10727,7 @@ async function rotateSpecialistSession(specialistName, workingDir) {
10431
10727
  let memoryFile;
10432
10728
  if (specialistName === "merge-agent" && workingDir) {
10433
10729
  memoryContent = await buildMergeAgentMemory(workingDir);
10434
- memoryFile = join38(PANOPTICON_HOME, `merge-agent-memory-${Date.now()}.md`);
10730
+ memoryFile = join40(PANOPTICON_HOME, `merge-agent-memory-${Date.now()}.md`);
10435
10731
  writeFileSync18(memoryFile, memoryContent);
10436
10732
  console.log(`Built memory file: ${memoryFile}`);
10437
10733
  }
@@ -10487,17 +10783,17 @@ init_config2();
10487
10783
  init_specialists();
10488
10784
  init_agents();
10489
10785
  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";
10786
+ import { readFileSync as readFileSync35, writeFileSync as writeFileSync19, existsSync as existsSync42, mkdirSync as mkdirSync21, readdirSync as readdirSync17, statSync as statSync8, rmSync as rmSync5 } from "fs";
10787
+ import { join as join41 } from "path";
10492
10788
  import { exec as exec15 } from "child_process";
10493
10789
  import { promisify as promisify15 } from "util";
10494
10790
  import { homedir as homedir19 } from "os";
10495
10791
  var execAsync15 = promisify15(exec15);
10496
- var REVIEW_STATUS_FILE = join39(homedir19(), ".panopticon", "review-status.json");
10792
+ var REVIEW_STATUS_FILE = join41(homedir19(), ".panopticon", "review-status.json");
10497
10793
  function updateTestStatusToTesting(issueId) {
10498
10794
  try {
10499
- if (!existsSync40(REVIEW_STATUS_FILE)) return;
10500
- const data = JSON.parse(readFileSync33(REVIEW_STATUS_FILE, "utf-8"));
10795
+ if (!existsSync42(REVIEW_STATUS_FILE)) return;
10796
+ const data = JSON.parse(readFileSync35(REVIEW_STATUS_FILE, "utf-8"));
10501
10797
  const upper = issueId.toUpperCase();
10502
10798
  if (data[upper]) {
10503
10799
  data[upper].testStatus = "testing";
@@ -10523,15 +10819,15 @@ var DEFAULT_CONFIG = {
10523
10819
  massDeathWindowMs: 6e4
10524
10820
  // 1 minute window for mass death detection
10525
10821
  };
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");
10822
+ var DEACON_DIR = join41(PANOPTICON_HOME, "deacon");
10823
+ var STATE_FILE = join41(DEACON_DIR, "health-state.json");
10824
+ var CONFIG_FILE2 = join41(DEACON_DIR, "config.json");
10529
10825
  var deaconInterval = null;
10530
10826
  var config = { ...DEFAULT_CONFIG };
10531
10827
  function loadConfig3() {
10532
10828
  try {
10533
- if (existsSync40(CONFIG_FILE2)) {
10534
- const content = readFileSync33(CONFIG_FILE2, "utf-8");
10829
+ if (existsSync42(CONFIG_FILE2)) {
10830
+ const content = readFileSync35(CONFIG_FILE2, "utf-8");
10535
10831
  const loaded = JSON.parse(content);
10536
10832
  config = { ...DEFAULT_CONFIG, ...loaded };
10537
10833
  }
@@ -10541,15 +10837,15 @@ function loadConfig3() {
10541
10837
  return config;
10542
10838
  }
10543
10839
  function ensureDeaconDir() {
10544
- if (!existsSync40(DEACON_DIR)) {
10840
+ if (!existsSync42(DEACON_DIR)) {
10545
10841
  mkdirSync21(DEACON_DIR, { recursive: true });
10546
10842
  }
10547
10843
  }
10548
10844
  function loadState() {
10549
10845
  ensureDeaconDir();
10550
10846
  try {
10551
- if (existsSync40(STATE_FILE)) {
10552
- const content = readFileSync33(STATE_FILE, "utf-8");
10847
+ if (existsSync42(STATE_FILE)) {
10848
+ const content = readFileSync35(STATE_FILE, "utf-8");
10553
10849
  return JSON.parse(content);
10554
10850
  }
10555
10851
  } catch (error) {
@@ -10598,12 +10894,12 @@ function getCooldownRemaining(healthState) {
10598
10894
  }
10599
10895
  function checkHeartbeat(name) {
10600
10896
  const tmuxSession = getTmuxSessionName(name);
10601
- const heartbeatFile = join39(PANOPTICON_HOME, "heartbeats", `${tmuxSession}.json`);
10897
+ const heartbeatFile = join41(PANOPTICON_HOME, "heartbeats", `${tmuxSession}.json`);
10602
10898
  try {
10603
- if (!existsSync40(heartbeatFile)) {
10899
+ if (!existsSync42(heartbeatFile)) {
10604
10900
  return { isResponsive: false };
10605
10901
  }
10606
- const content = readFileSync33(heartbeatFile, "utf-8");
10902
+ const content = readFileSync35(heartbeatFile, "utf-8");
10607
10903
  const heartbeat = JSON.parse(content);
10608
10904
  const lastActivity = new Date(heartbeat.timestamp).getTime();
10609
10905
  const age = Date.now() - lastActivity;
@@ -10889,10 +11185,10 @@ function isIssueCompletedOrInReview(agentId) {
10889
11185
  const match = agentId.match(/agent-([a-z]+-\d+)/i);
10890
11186
  if (!match) return false;
10891
11187
  const issueId = match[1].toUpperCase();
10892
- if (!existsSync40(REVIEW_STATUS_FILE)) {
11188
+ if (!existsSync42(REVIEW_STATUS_FILE)) {
10893
11189
  return false;
10894
11190
  }
10895
- const content = readFileSync33(REVIEW_STATUS_FILE, "utf-8");
11191
+ const content = readFileSync35(REVIEW_STATUS_FILE, "utf-8");
10896
11192
  const statuses = JSON.parse(content);
10897
11193
  const status = statuses[issueId];
10898
11194
  if (!status) {
@@ -10951,6 +11247,12 @@ async function isAgentActiveInTmux(sessionName) {
10951
11247
  if (!stdout.trim()) return false;
10952
11248
  for (const pattern of ACTIVE_STATUS_PATTERNS) {
10953
11249
  if (pattern.test(stdout)) {
11250
+ if (/thinking/i.test(stdout)) {
11251
+ const thinkingMs = parseThinkingDuration(stdout);
11252
+ if (thinkingMs !== null && thinkingMs >= STUCK_THINKING_THRESHOLD_MS) {
11253
+ return false;
11254
+ }
11255
+ }
10954
11256
  return true;
10955
11257
  }
10956
11258
  }
@@ -10959,39 +11261,123 @@ async function isAgentActiveInTmux(sessionName) {
10959
11261
  return false;
10960
11262
  }
10961
11263
  }
11264
+ var STUCK_THINKING_THRESHOLD_MS = 10 * 60 * 1e3;
11265
+ var STUCK_RECOVERY_COOLDOWN_MS = 5 * 60 * 1e3;
11266
+ var stuckRecoveryState = /* @__PURE__ */ new Map();
11267
+ function parseThinkingDuration(tmuxOutput) {
11268
+ const match = tmuxOutput.match(/[Tt]hinking[^\n]*?\((?:(\d+)m\s*)?(\d+)s/);
11269
+ if (!match) return null;
11270
+ const minutes = match[1] ? parseInt(match[1], 10) : 0;
11271
+ const seconds = parseInt(match[2], 10);
11272
+ return (minutes * 60 + seconds) * 1e3;
11273
+ }
11274
+ async function checkStuckWorkAgents() {
11275
+ const actions = [];
11276
+ const agents = listRunningAgents();
11277
+ const specialists = getEnabledSpecialists();
11278
+ const specialistNames = new Set(specialists.map((s) => getTmuxSessionName(s.name)));
11279
+ const now = Date.now();
11280
+ for (const agent of agents) {
11281
+ if (!agent.tmuxActive) continue;
11282
+ const isWorkAgent = agent.id.startsWith("agent-") && !specialistNames.has(agent.id);
11283
+ if (!isWorkAgent) continue;
11284
+ const recovery = stuckRecoveryState.get(agent.id);
11285
+ if (recovery && now - recovery.lastAttempt < STUCK_RECOVERY_COOLDOWN_MS) {
11286
+ continue;
11287
+ }
11288
+ let tmuxOutput;
11289
+ try {
11290
+ const { stdout } = await execAsync15(
11291
+ `tmux capture-pane -t "${agent.id}" -p -S -10 2>/dev/null || echo ""`,
11292
+ { encoding: "utf-8" }
11293
+ );
11294
+ tmuxOutput = stdout;
11295
+ } catch {
11296
+ continue;
11297
+ }
11298
+ if (!tmuxOutput.trim()) continue;
11299
+ const thinkingMs = parseThinkingDuration(tmuxOutput);
11300
+ if (thinkingMs === null || thinkingMs < STUCK_THINKING_THRESHOLD_MS) {
11301
+ if (recovery && recovery.attempts > 0) {
11302
+ stuckRecoveryState.delete(agent.id);
11303
+ }
11304
+ continue;
11305
+ }
11306
+ const thinkingMinutes = Math.round(thinkingMs / 6e4);
11307
+ const attempts = recovery?.attempts ?? 0;
11308
+ console.log(`[deacon] Work agent ${agent.id} stuck thinking for ${thinkingMinutes}m (attempt ${attempts + 1})`);
11309
+ try {
11310
+ if (attempts === 0) {
11311
+ await execAsync15(`tmux send-keys -t "${agent.id}" Escape 2>/dev/null || true`);
11312
+ actions.push(`Stuck recovery: sent Escape to ${agent.id} (thinking ${thinkingMinutes}m)`);
11313
+ } else if (attempts === 1) {
11314
+ await execAsync15(`tmux send-keys -t "${agent.id}" C-c 2>/dev/null || true`);
11315
+ actions.push(`Stuck recovery: sent Ctrl+C to ${agent.id} (thinking ${thinkingMinutes}m)`);
11316
+ } else {
11317
+ const launcherPath = join41(AGENTS_DIR, agent.id, "launcher.sh");
11318
+ const agentState = getAgentState(agent.id);
11319
+ const workspace = agentState?.workspace;
11320
+ if (!existsSync42(launcherPath) || !workspace) {
11321
+ console.error(`[deacon] Cannot respawn ${agent.id}: missing launcher.sh or workspace`);
11322
+ actions.push(`Stuck recovery failed for ${agent.id}: missing launcher or workspace`);
11323
+ continue;
11324
+ }
11325
+ await execAsync15(`tmux kill-session -t "${agent.id}" 2>/dev/null || true`);
11326
+ await new Promise((r) => setTimeout(r, 1e3));
11327
+ await execAsync15(
11328
+ `tmux new-session -d -s "${agent.id}" -c "${workspace}" "bash ${launcherPath}"`,
11329
+ { encoding: "utf-8" }
11330
+ );
11331
+ stuckRecoveryState.set(agent.id, { lastAttempt: now, attempts: 0 });
11332
+ actions.push(`Stuck recovery: respawned ${agent.id} (was stuck thinking ${thinkingMinutes}m, attempt ${attempts + 1})`);
11333
+ console.log(`[deacon] Respawned stuck work agent ${agent.id}`);
11334
+ continue;
11335
+ }
11336
+ } catch (error) {
11337
+ const msg = error instanceof Error ? error.message : String(error);
11338
+ console.error(`[deacon] Stuck recovery failed for ${agent.id}:`, msg);
11339
+ actions.push(`Stuck recovery error for ${agent.id}: ${msg}`);
11340
+ }
11341
+ stuckRecoveryState.set(agent.id, {
11342
+ lastAttempt: now,
11343
+ attempts: attempts + 1
11344
+ });
11345
+ }
11346
+ return actions;
11347
+ }
10962
11348
  async function cleanupStaleAgentState() {
10963
11349
  const actions = [];
10964
11350
  const cloisterConfig = loadCloisterConfig();
10965
11351
  const retentionDays = cloisterConfig.retention?.agent_state_days ?? 30;
10966
11352
  const retentionMs = retentionDays * 24 * 60 * 60 * 1e3;
10967
11353
  const now = Date.now();
10968
- if (!existsSync40(AGENTS_DIR)) {
11354
+ if (!existsSync42(AGENTS_DIR)) {
10969
11355
  return actions;
10970
11356
  }
10971
11357
  try {
10972
- const dirs = readdirSync16(AGENTS_DIR, { withFileTypes: true }).filter((d) => d.isDirectory());
11358
+ const dirs = readdirSync17(AGENTS_DIR, { withFileTypes: true }).filter((d) => d.isDirectory());
10973
11359
  for (const dir of dirs) {
10974
- const agentDir = join39(AGENTS_DIR, dir.name);
11360
+ const agentDir = join41(AGENTS_DIR, dir.name);
10975
11361
  try {
10976
11362
  try {
10977
11363
  await execAsync15(`tmux has-session -t "${dir.name}" 2>/dev/null`);
10978
11364
  continue;
10979
11365
  } catch {
10980
11366
  }
10981
- const stateFile = join39(agentDir, "state.json");
11367
+ const stateFile = join41(agentDir, "state.json");
10982
11368
  let mtime;
10983
- if (existsSync40(stateFile)) {
10984
- mtime = statSync7(stateFile).mtimeMs;
11369
+ if (existsSync42(stateFile)) {
11370
+ mtime = statSync8(stateFile).mtimeMs;
10985
11371
  } else {
10986
- mtime = statSync7(agentDir).mtimeMs;
11372
+ mtime = statSync8(agentDir).mtimeMs;
10987
11373
  }
10988
11374
  const ageMs = now - mtime;
10989
11375
  if (ageMs < retentionMs) {
10990
11376
  continue;
10991
11377
  }
10992
- const completedFile = join39(agentDir, "completed");
10993
- if (existsSync40(completedFile)) {
10994
- const completedAge = now - statSync7(completedFile).mtimeMs;
11378
+ const completedFile = join41(agentDir, "completed");
11379
+ if (existsSync42(completedFile)) {
11380
+ const completedAge = now - statSync8(completedFile).mtimeMs;
10995
11381
  if (completedAge < 7 * 24 * 60 * 60 * 1e3) {
10996
11382
  continue;
10997
11383
  }
@@ -11017,10 +11403,10 @@ async function cleanupStaleAgentState() {
11017
11403
  async function checkOrphanedReviewStatuses() {
11018
11404
  const actions = [];
11019
11405
  try {
11020
- if (!existsSync40(REVIEW_STATUS_FILE)) {
11406
+ if (!existsSync42(REVIEW_STATUS_FILE)) {
11021
11407
  return actions;
11022
11408
  }
11023
- const content = readFileSync33(REVIEW_STATUS_FILE, "utf-8");
11409
+ const content = readFileSync35(REVIEW_STATUS_FILE, "utf-8");
11024
11410
  const statuses = JSON.parse(content);
11025
11411
  const reviewAgentSession = getTmuxSessionName("review-agent");
11026
11412
  const reviewAgentRunning = sessionExists(reviewAgentSession);
@@ -11066,10 +11452,10 @@ var DEAD_END_COOLDOWN_MS = 10 * 60 * 1e3;
11066
11452
  async function checkDeadEndAgents() {
11067
11453
  const actions = [];
11068
11454
  try {
11069
- if (!existsSync40(REVIEW_STATUS_FILE)) {
11455
+ if (!existsSync42(REVIEW_STATUS_FILE)) {
11070
11456
  return actions;
11071
11457
  }
11072
- const content = readFileSync33(REVIEW_STATUS_FILE, "utf-8");
11458
+ const content = readFileSync35(REVIEW_STATUS_FILE, "utf-8");
11073
11459
  const statuses = JSON.parse(content);
11074
11460
  const now = Date.now();
11075
11461
  for (const [key, status] of Object.entries(statuses)) {
@@ -11084,8 +11470,8 @@ async function checkDeadEndAgents() {
11084
11470
  const lastRecovery = deadEndCooldowns.get(key);
11085
11471
  if (lastRecovery && now - lastRecovery < DEAD_END_COOLDOWN_MS) continue;
11086
11472
  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)`);
11473
+ if (autoRequeueCount >= 7) {
11474
+ console.log(`[deacon] Dead-end detected for ${key} but circuit breaker active (${autoRequeueCount}/7 requeues used)`);
11089
11475
  continue;
11090
11476
  }
11091
11477
  const issueId = status.issueId || key;
@@ -11117,6 +11503,117 @@ async function checkDeadEndAgents() {
11117
11503
  }
11118
11504
  return actions;
11119
11505
  }
11506
+ var firstCompletionCooldowns = /* @__PURE__ */ new Map();
11507
+ var FIRST_COMPLETION_IDLE_MS = 10 * 60 * 1e3;
11508
+ var FIRST_COMPLETION_COOLDOWN_MS = 15 * 60 * 1e3;
11509
+ async function checkFirstCompletionAgents() {
11510
+ const actions = [];
11511
+ try {
11512
+ const agents = listRunningAgents();
11513
+ const now = Date.now();
11514
+ for (const agent of agents) {
11515
+ const agentId = agent.id;
11516
+ if (!agentId || !agentId.startsWith("agent-") || !agent.tmuxActive) continue;
11517
+ if (agentId.startsWith("specialist-")) continue;
11518
+ const completedFile = join41(AGENTS_DIR, agent.id, "completed");
11519
+ if (existsSync42(completedFile)) continue;
11520
+ const runtimeState = getAgentRuntimeState(agent.id);
11521
+ if (!runtimeState || runtimeState.state !== "idle") continue;
11522
+ const lastActivity = new Date(runtimeState.lastActivity);
11523
+ const idleMs = now - lastActivity.getTime();
11524
+ if (idleMs < FIRST_COMPLETION_IDLE_MS) continue;
11525
+ try {
11526
+ const { stdout: lastLines } = await execAsync15(
11527
+ `tmux capture-pane -t "${agent.id}" -p -S -3 2>/dev/null || echo ""`,
11528
+ { encoding: "utf-8" }
11529
+ );
11530
+ const lines = lastLines.split("\n").filter((l) => l.trim().length > 0);
11531
+ const tail = lines.slice(-3).join("\n");
11532
+ const isAtPrompt = /❯/.test(tail) || /bypass permissions/.test(tail) || /Worked for/.test(tail);
11533
+ if (!isAtPrompt) continue;
11534
+ } catch {
11535
+ continue;
11536
+ }
11537
+ const lastNudge = firstCompletionCooldowns.get(agent.id);
11538
+ if (lastNudge && now - lastNudge < FIRST_COMPLETION_COOLDOWN_MS) continue;
11539
+ const issueId = agent.issueId || agent.id.replace("agent-", "").toUpperCase();
11540
+ const issueKey = issueId.toLowerCase();
11541
+ if (existsSync42(REVIEW_STATUS_FILE)) {
11542
+ try {
11543
+ const statuses = JSON.parse(readFileSync35(REVIEW_STATUS_FILE, "utf-8"));
11544
+ const hasStatus = statuses[issueKey] || statuses[issueId] || statuses[issueId.toUpperCase()];
11545
+ if (hasStatus) {
11546
+ console.log(`[deacon] First-completion gate: skipping ${agent.id} \u2014 has review status entry (readyForMerge=${hasStatus.readyForMerge ?? false})`);
11547
+ continue;
11548
+ }
11549
+ } catch {
11550
+ }
11551
+ }
11552
+ const agentStateForGate = getAgentState(agent.id);
11553
+ if (agentStateForGate?.workspace) {
11554
+ const feedbackDir = join41(agentStateForGate.workspace, ".planning", "feedback");
11555
+ if (existsSync42(feedbackDir)) {
11556
+ try {
11557
+ const feedbackFiles = readdirSync17(feedbackDir);
11558
+ if (feedbackFiles.length > 0) {
11559
+ console.log(`[deacon] First-completion gate: skipping ${agent.id} \u2014 has ${feedbackFiles.length} review feedback file(s) in .planning/feedback/`);
11560
+ continue;
11561
+ }
11562
+ } catch {
11563
+ }
11564
+ }
11565
+ }
11566
+ const agentState = getAgentState(agent.id);
11567
+ if (!agentState?.workspace || !existsSync42(agentState.workspace)) continue;
11568
+ let hasCommits = false;
11569
+ try {
11570
+ const { stdout: gitLog } = await execAsync15(
11571
+ "git log --oneline -3 2>/dev/null",
11572
+ { cwd: agentState.workspace }
11573
+ );
11574
+ hasCommits = gitLog.trim().length > 0;
11575
+ } catch {
11576
+ try {
11577
+ const subdirs = readdirSync17(agentState.workspace, { withFileTypes: true }).filter((d) => d.isDirectory() && !d.name.startsWith("."));
11578
+ for (const sub of subdirs) {
11579
+ try {
11580
+ const { stdout: subLog } = await execAsync15(
11581
+ "git log --oneline -3 2>/dev/null",
11582
+ { cwd: join41(agentState.workspace, sub.name) }
11583
+ );
11584
+ if (subLog.trim().length > 0) {
11585
+ hasCommits = true;
11586
+ break;
11587
+ }
11588
+ } catch {
11589
+ }
11590
+ }
11591
+ } catch {
11592
+ }
11593
+ }
11594
+ if (!hasCommits) continue;
11595
+ const idleMinutes = Math.round(idleMs / 6e4);
11596
+ console.log(`[deacon] First-completion gap detected: ${agent.id} (${issueId}) idle for ${idleMinutes}m with commits but no completion marker`);
11597
+ firstCompletionCooldowns.set(agent.id, now);
11598
+ try {
11599
+ const nudgeMessage = `You appear to have stopped working without calling "pan work done". If your implementation is complete, run this now:
11600
+
11601
+ pan work done ${issueId} -c "Implementation complete"
11602
+
11603
+ If you still have remaining tasks, continue working on them.`;
11604
+ await sendKeysAsync(agent.id, nudgeMessage);
11605
+ actions.push(`First-completion nudge: ${agent.id} (idle ${idleMinutes}m)`);
11606
+ console.log(`[deacon] Sent first-completion nudge to ${agent.id}`);
11607
+ } catch (error) {
11608
+ const msg = error instanceof Error ? error.message : String(error);
11609
+ console.error(`[deacon] Failed to send first-completion nudge to ${agent.id}:`, msg);
11610
+ }
11611
+ }
11612
+ } catch (error) {
11613
+ console.error("[deacon] Error in first-completion detection:", error);
11614
+ }
11615
+ return actions;
11616
+ }
11120
11617
  async function runPatrol() {
11121
11618
  const state = loadState();
11122
11619
  state.patrolCycle++;
@@ -11172,7 +11669,7 @@ async function runPatrol() {
11172
11669
  if (nextTask) {
11173
11670
  console.log(`[deacon] Auto-resuming suspended ${specialist.name} for queued task: ${nextTask.payload.issueId}`);
11174
11671
  try {
11175
- const { resumeAgent } = await import("../agents-VLK4BMVA.js");
11672
+ const { resumeAgent } = await import("../agents-E43Y3HNU.js");
11176
11673
  const message = `# Queued Work
11177
11674
 
11178
11675
  Processing queued task: ${nextTask.payload.issueId}`;
@@ -11226,9 +11723,15 @@ Processing queued task: ${nextTask.payload.issueId}`;
11226
11723
  const deadEndActions = await checkDeadEndAgents();
11227
11724
  actions.push(...deadEndActions);
11228
11725
  for (const a of deadEndActions) addLog("action", a, state.patrolCycle);
11726
+ const firstCompletionActions = await checkFirstCompletionAgents();
11727
+ actions.push(...firstCompletionActions);
11728
+ for (const a of firstCompletionActions) addLog("action", a, state.patrolCycle);
11229
11729
  const lazyActions = await checkAndCorrectLazyAgents();
11230
11730
  actions.push(...lazyActions);
11231
11731
  for (const a of lazyActions) addLog("action", a, state.patrolCycle);
11732
+ const stuckActions = await checkStuckWorkAgents();
11733
+ actions.push(...stuckActions);
11734
+ for (const a of stuckActions) addLog("action", a, state.patrolCycle);
11232
11735
  if (Math.random() < 3e-3) {
11233
11736
  const cleanupActions = await cleanupStaleAgentState();
11234
11737
  actions.push(...cleanupActions);
@@ -11304,9 +11807,9 @@ function getDeaconStatus() {
11304
11807
  // src/lib/cloister/service.ts
11305
11808
  init_paths();
11306
11809
  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");
11810
+ import { existsSync as existsSync43, writeFileSync as writeFileSync20, unlinkSync as unlinkSync5, readFileSync as readFileSync36, readdirSync as readdirSync18, renameSync as renameSync3 } from "fs";
11811
+ import { join as join42 } from "path";
11812
+ var CLOISTER_STATE_FILE = join42(PANOPTICON_HOME, "cloister.state");
11310
11813
  function writeStateFile(running, pid) {
11311
11814
  try {
11312
11815
  if (running) {
@@ -11316,7 +11819,7 @@ function writeStateFile(running, pid) {
11316
11819
  startedAt: (/* @__PURE__ */ new Date()).toISOString()
11317
11820
  }));
11318
11821
  } else {
11319
- if (existsSync41(CLOISTER_STATE_FILE)) {
11822
+ if (existsSync43(CLOISTER_STATE_FILE)) {
11320
11823
  unlinkSync5(CLOISTER_STATE_FILE);
11321
11824
  }
11322
11825
  }
@@ -11326,8 +11829,8 @@ function writeStateFile(running, pid) {
11326
11829
  }
11327
11830
  function readStateFile() {
11328
11831
  try {
11329
- if (existsSync41(CLOISTER_STATE_FILE)) {
11330
- const data = JSON.parse(readFileSync34(CLOISTER_STATE_FILE, "utf-8"));
11832
+ if (existsSync43(CLOISTER_STATE_FILE)) {
11833
+ const data = JSON.parse(readFileSync36(CLOISTER_STATE_FILE, "utf-8"));
11331
11834
  if (data.pid) {
11332
11835
  try {
11333
11836
  process.kill(data.pid, 0);
@@ -11545,14 +12048,14 @@ var CloisterService = class {
11545
12048
  */
11546
12049
  async checkCompletionMarkers() {
11547
12050
  try {
11548
- if (!existsSync41(AGENTS_DIR)) return;
11549
- const agentDirs = readdirSync17(AGENTS_DIR, { withFileTypes: true }).filter((d) => d.isDirectory() && d.name.startsWith("agent-"));
12051
+ if (!existsSync43(AGENTS_DIR)) return;
12052
+ const agentDirs = readdirSync18(AGENTS_DIR, { withFileTypes: true }).filter((d) => d.isDirectory() && d.name.startsWith("agent-"));
11550
12053
  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;
12054
+ const completedFile = join42(AGENTS_DIR, dir.name, "completed");
12055
+ const processedFile = join42(AGENTS_DIR, dir.name, "completed.processed");
12056
+ if (!existsSync43(completedFile) || existsSync43(processedFile)) continue;
11554
12057
  try {
11555
- const content = JSON.parse(readFileSync34(completedFile, "utf-8"));
12058
+ const content = JSON.parse(readFileSync36(completedFile, "utf-8"));
11556
12059
  const ageMs = Date.now() - new Date(content.timestamp).getTime();
11557
12060
  if (ageMs > 24 * 60 * 60 * 1e3) {
11558
12061
  console.log(`\u{1F514} Cloister: Skipping stale completion marker for ${dir.name} (${Math.floor(ageMs / 36e5)}h old)`);
@@ -12209,8 +12712,8 @@ init_esm_shims();
12209
12712
  // src/cli/commands/setup/hooks.ts
12210
12713
  init_esm_shims();
12211
12714
  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";
12715
+ import { readFileSync as readFileSync37, writeFileSync as writeFileSync21, existsSync as existsSync44, mkdirSync as mkdirSync22, copyFileSync as copyFileSync2, chmodSync } from "fs";
12716
+ import { join as join43 } from "path";
12214
12717
  import { execSync as execSync5 } from "child_process";
12215
12718
  import { homedir as homedir20 } from "os";
12216
12719
  function checkJqInstalled() {
@@ -12288,14 +12791,14 @@ async function setupHooksCommand() {
12288
12791
  } else {
12289
12792
  console.log(chalk39.green("\u2713 jq is installed"));
12290
12793
  }
12291
- const panopticonHome = join41(homedir20(), ".panopticon");
12292
- const binDir = join41(panopticonHome, "bin");
12293
- const heartbeatsDir = join41(panopticonHome, "heartbeats");
12294
- if (!existsSync42(binDir)) {
12794
+ const panopticonHome = join43(homedir20(), ".panopticon");
12795
+ const binDir = join43(panopticonHome, "bin");
12796
+ const heartbeatsDir = join43(panopticonHome, "heartbeats");
12797
+ if (!existsSync44(binDir)) {
12295
12798
  mkdirSync22(binDir, { recursive: true });
12296
12799
  console.log(chalk39.green("\u2713 Created ~/.panopticon/bin/"));
12297
12800
  }
12298
- if (!existsSync42(heartbeatsDir)) {
12801
+ if (!existsSync44(heartbeatsDir)) {
12299
12802
  mkdirSync22(heartbeatsDir, { recursive: true });
12300
12803
  console.log(chalk39.green("\u2713 Created ~/.panopticon/heartbeats/"));
12301
12804
  }
@@ -12304,13 +12807,13 @@ async function setupHooksCommand() {
12304
12807
  const { dirname: dirname17 } = await import("path");
12305
12808
  const __dirname6 = dirname17(fileURLToPath6(import.meta.url));
12306
12809
  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);
12810
+ const devSource = join43(process.cwd(), "scripts", scriptName);
12811
+ const installedSource = join43(__dirname6, "..", "..", "..", "scripts", scriptName);
12812
+ const scriptDest = join43(binDir, scriptName);
12310
12813
  let sourcePath = null;
12311
- if (existsSync42(devSource)) {
12814
+ if (existsSync44(devSource)) {
12312
12815
  sourcePath = devSource;
12313
- } else if (existsSync42(installedSource)) {
12816
+ } else if (existsSync44(installedSource)) {
12314
12817
  sourcePath = installedSource;
12315
12818
  }
12316
12819
  if (!sourcePath) {
@@ -12323,12 +12826,12 @@ async function setupHooksCommand() {
12323
12826
  chmodSync(scriptDest, 493);
12324
12827
  }
12325
12828
  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");
12829
+ const claudeDir = join43(homedir20(), ".claude");
12830
+ const settingsPath = join43(claudeDir, "settings.json");
12328
12831
  let settings = {};
12329
- if (existsSync42(settingsPath)) {
12832
+ if (existsSync44(settingsPath)) {
12330
12833
  try {
12331
- const settingsContent = readFileSync35(settingsPath, "utf-8");
12834
+ const settingsContent = readFileSync37(settingsPath, "utf-8");
12332
12835
  settings = JSON.parse(settingsContent);
12333
12836
  console.log(chalk39.green("\u2713 Read existing Claude Code settings"));
12334
12837
  } catch (error) {
@@ -12337,7 +12840,7 @@ async function setupHooksCommand() {
12337
12840
  }
12338
12841
  } else {
12339
12842
  console.log(chalk39.dim("No existing settings.json found, creating new file"));
12340
- if (!existsSync42(claudeDir)) {
12843
+ if (!existsSync44(claudeDir)) {
12341
12844
  mkdirSync22(claudeDir, { recursive: true });
12342
12845
  }
12343
12846
  }
@@ -12351,11 +12854,11 @@ async function setupHooksCommand() {
12351
12854
  console.log(chalk39.dim(" Install Python3 to enable token-efficient code analysis\n"));
12352
12855
  }
12353
12856
  if (python3Available) {
12354
- const mcpPath = join41(dirname17(settingsPath), "mcp.json");
12857
+ const mcpPath = join43(dirname17(settingsPath), "mcp.json");
12355
12858
  let mcpConfig = {};
12356
12859
  try {
12357
- if (existsSync42(mcpPath)) {
12358
- mcpConfig = JSON.parse(readFileSync35(mcpPath, "utf-8"));
12860
+ if (existsSync44(mcpPath)) {
12861
+ mcpConfig = JSON.parse(readFileSync37(mcpPath, "utf-8"));
12359
12862
  }
12360
12863
  } catch {
12361
12864
  mcpConfig = {};
@@ -12390,7 +12893,7 @@ async function setupHooksCommand() {
12390
12893
  hooks: [
12391
12894
  {
12392
12895
  type: "command",
12393
- command: join41(binDir, "pre-tool-hook")
12896
+ command: join43(binDir, "pre-tool-hook")
12394
12897
  }
12395
12898
  ]
12396
12899
  });
@@ -12400,7 +12903,7 @@ async function setupHooksCommand() {
12400
12903
  hooks: [
12401
12904
  {
12402
12905
  type: "command",
12403
- command: join41(binDir, "tldr-read-enforcer")
12906
+ command: join43(binDir, "tldr-read-enforcer")
12404
12907
  }
12405
12908
  ]
12406
12909
  });
@@ -12413,7 +12916,7 @@ async function setupHooksCommand() {
12413
12916
  hooks: [
12414
12917
  {
12415
12918
  type: "command",
12416
- command: join41(binDir, "heartbeat-hook")
12919
+ command: join43(binDir, "heartbeat-hook")
12417
12920
  }
12418
12921
  ]
12419
12922
  });
@@ -12423,7 +12926,7 @@ async function setupHooksCommand() {
12423
12926
  hooks: [
12424
12927
  {
12425
12928
  type: "command",
12426
- command: join41(binDir, "tldr-post-edit")
12929
+ command: join43(binDir, "tldr-post-edit")
12427
12930
  }
12428
12931
  ]
12429
12932
  });
@@ -12436,7 +12939,7 @@ async function setupHooksCommand() {
12436
12939
  hooks: [
12437
12940
  {
12438
12941
  type: "command",
12439
- command: join41(binDir, "stop-hook")
12942
+ command: join43(binDir, "stop-hook")
12440
12943
  }
12441
12944
  ]
12442
12945
  });
@@ -12549,17 +13052,17 @@ import chalk41 from "chalk";
12549
13052
  import { exec as exec16 } from "child_process";
12550
13053
  import { promisify as promisify16 } from "util";
12551
13054
  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";
13055
+ import { existsSync as existsSync45, mkdirSync as mkdirSync23, writeFileSync as writeFileSync22 } from "fs";
13056
+ import { join as join44 } from "path";
12554
13057
  var execAsync16 = promisify16(exec16);
12555
- var TASKS_DIR = join42(PANOPTICON_HOME, "specialists", "tasks");
13058
+ var TASKS_DIR = join44(PANOPTICON_HOME, "specialists", "tasks");
12556
13059
  function sendTask(tmuxSession, specialistName, task) {
12557
13060
  const isLargeTask = task.length > 500 || task.includes("\n");
12558
13061
  if (isLargeTask) {
12559
- if (!existsSync43(TASKS_DIR)) {
13062
+ if (!existsSync45(TASKS_DIR)) {
12560
13063
  mkdirSync23(TASKS_DIR, { recursive: true });
12561
13064
  }
12562
- const taskFile = join42(TASKS_DIR, `${specialistName}-${Date.now()}.md`);
13065
+ const taskFile = join44(TASKS_DIR, `${specialistName}-${Date.now()}.md`);
12563
13066
  writeFileSync22(taskFile, task, "utf-8");
12564
13067
  const shortMessage = `Read and execute the task in: ${taskFile}`;
12565
13068
  sendKeys(tmuxSession, shortMessage);
@@ -12867,11 +13370,11 @@ init_esm_shims();
12867
13370
  init_specialists();
12868
13371
  init_paths();
12869
13372
  import chalk44 from "chalk";
12870
- import { existsSync as existsSync44, readFileSync as readFileSync36, writeFileSync as writeFileSync23 } from "fs";
12871
- import { join as join43 } from "path";
13373
+ import { existsSync as existsSync46, readFileSync as readFileSync38, writeFileSync as writeFileSync23 } from "fs";
13374
+ import { join as join45 } from "path";
12872
13375
  import * as readline2 from "readline";
12873
13376
  var ALL_SPECIALISTS2 = ["merge-agent", "review-agent", "test-agent"];
12874
- var REVIEW_STATUS_FILE2 = join43(PANOPTICON_HOME, "review-status.json");
13377
+ var REVIEW_STATUS_FILE2 = join45(PANOPTICON_HOME, "review-status.json");
12875
13378
  async function clearQueueCommand(name, options) {
12876
13379
  if (!ALL_SPECIALISTS2.includes(name)) {
12877
13380
  console.log(chalk44.red(`
@@ -12921,10 +13424,10 @@ ${metadata.displayName} Queue:
12921
13424
  issueIds.push(payload.issueId);
12922
13425
  }
12923
13426
  }
12924
- const hookFile = join43(PANOPTICON_HOME, "agents", specialistName, "hook.json");
12925
- if (existsSync44(hookFile)) {
13427
+ const hookFile = join45(PANOPTICON_HOME, "agents", specialistName, "hook.json");
13428
+ if (existsSync46(hookFile)) {
12926
13429
  try {
12927
- const hook = JSON.parse(readFileSync36(hookFile, "utf-8"));
13430
+ const hook = JSON.parse(readFileSync38(hookFile, "utf-8"));
12928
13431
  hook.items = [];
12929
13432
  hook.lastChecked = (/* @__PURE__ */ new Date()).toISOString();
12930
13433
  writeFileSync23(hookFile, JSON.stringify(hook, null, 2), "utf-8");
@@ -12935,9 +13438,9 @@ ${metadata.displayName} Queue:
12935
13438
  }
12936
13439
  }
12937
13440
  if (options.resetStatus && issueIds.length > 0) {
12938
- if (existsSync44(REVIEW_STATUS_FILE2)) {
13441
+ if (existsSync46(REVIEW_STATUS_FILE2)) {
12939
13442
  try {
12940
- const statuses = JSON.parse(readFileSync36(REVIEW_STATUS_FILE2, "utf-8"));
13443
+ const statuses = JSON.parse(readFileSync38(REVIEW_STATUS_FILE2, "utf-8"));
12941
13444
  let resetCount = 0;
12942
13445
  for (const issueId of issueIds) {
12943
13446
  const key = Object.keys(statuses).find((k) => k.toLowerCase() === issueId.toLowerCase());
@@ -12979,14 +13482,14 @@ function confirm2(question) {
12979
13482
  // src/cli/commands/specialists/done.ts
12980
13483
  init_esm_shims();
12981
13484
  import chalk45 from "chalk";
12982
- import { existsSync as existsSync45, readFileSync as readFileSync37, writeFileSync as writeFileSync24 } from "fs";
12983
- import { join as join44 } from "path";
13485
+ import { existsSync as existsSync47, readFileSync as readFileSync39, writeFileSync as writeFileSync24 } from "fs";
13486
+ import { join as join46 } from "path";
12984
13487
  import { homedir as homedir21 } from "os";
12985
- var REVIEW_STATUS_FILE3 = join44(homedir21(), ".panopticon", "review-status.json");
13488
+ var REVIEW_STATUS_FILE3 = join46(homedir21(), ".panopticon", "review-status.json");
12986
13489
  function loadReviewStatuses2() {
12987
13490
  try {
12988
- if (existsSync45(REVIEW_STATUS_FILE3)) {
12989
- return JSON.parse(readFileSync37(REVIEW_STATUS_FILE3, "utf-8"));
13491
+ if (existsSync47(REVIEW_STATUS_FILE3)) {
13492
+ return JSON.parse(readFileSync39(REVIEW_STATUS_FILE3, "utf-8"));
12990
13493
  }
12991
13494
  } catch (error) {
12992
13495
  console.error(chalk45.yellow("Warning: Could not load review statuses"));
@@ -13095,13 +13598,13 @@ function formatStatus(status) {
13095
13598
 
13096
13599
  // src/cli/commands/specialists/logs.ts
13097
13600
  init_esm_shims();
13098
- import { existsSync as existsSync46 } from "fs";
13601
+ import { existsSync as existsSync48 } from "fs";
13099
13602
  import { exec as exec18 } from "child_process";
13100
13603
  import { promisify as promisify18 } from "util";
13101
13604
  var execAsync18 = promisify18(exec18);
13102
13605
  async function listLogsCommand(project2, type, options) {
13103
13606
  try {
13104
- const { listRunLogs } = await import("../specialist-logs-CVKD3YJ3.js");
13607
+ const { listRunLogs } = await import("../specialist-logs-KLGJCEUL.js");
13105
13608
  const limit = options.limit ? parseInt(options.limit) : 10;
13106
13609
  const runs = listRunLogs(project2, type, { limit });
13107
13610
  if (options.json) {
@@ -13146,7 +13649,7 @@ View a specific run: pan specialists logs ${project2} ${type} <runId>
13146
13649
  }
13147
13650
  async function viewLogCommand(project2, type, runId, options) {
13148
13651
  try {
13149
- const { getRunLog, parseLogMetadata, getRunLogPath } = await import("../specialist-logs-CVKD3YJ3.js");
13652
+ const { getRunLog, parseLogMetadata, getRunLogPath } = await import("../specialist-logs-KLGJCEUL.js");
13150
13653
  const content = getRunLog(project2, type, runId);
13151
13654
  if (!content) {
13152
13655
  console.error(`\u274C Run log not found: ${runId}`);
@@ -13170,23 +13673,23 @@ async function viewLogCommand(project2, type, runId, options) {
13170
13673
  }
13171
13674
  async function tailLogCommand(project2, type) {
13172
13675
  try {
13173
- const { getRunLogPath } = await import("../specialist-logs-CVKD3YJ3.js");
13174
- const { getProjectSpecialistMetadata } = await import("../specialists-TKAP6T6Z.js");
13676
+ const { getRunLogPath } = await import("../specialist-logs-KLGJCEUL.js");
13677
+ const { getProjectSpecialistMetadata } = await import("../specialists-O4HWDJL5.js");
13175
13678
  const metadata = getProjectSpecialistMetadata(project2, type);
13176
13679
  if (!metadata.currentRun) {
13177
13680
  console.error(`\u274C No active run for ${project2}/${type}`);
13178
13681
  process.exit(1);
13179
13682
  }
13180
13683
  const logPath = getRunLogPath(project2, type, metadata.currentRun);
13181
- if (!existsSync46(logPath)) {
13684
+ if (!existsSync48(logPath)) {
13182
13685
  console.error(`\u274C Log file not found: ${logPath}`);
13183
13686
  process.exit(1);
13184
13687
  }
13185
13688
  console.log(`\u{1F4E1} Following ${project2}/${type} (${metadata.currentRun})...`);
13186
13689
  console.log(` Press Ctrl+C to stop
13187
13690
  `);
13188
- const { spawn } = await import("child_process");
13189
- const tail = spawn("tail", ["-f", logPath], {
13691
+ const { spawn: spawn2 } = await import("child_process");
13692
+ const tail = spawn2("tail", ["-f", logPath], {
13190
13693
  stdio: "inherit"
13191
13694
  });
13192
13695
  process.on("SIGINT", () => {
@@ -13240,7 +13743,7 @@ async function cleanupLogsCommand(projectOrAll, type, options) {
13240
13743
  console.log(" Use --force to confirm.");
13241
13744
  process.exit(1);
13242
13745
  }
13243
- const { cleanupAllLogs } = await import("../specialist-logs-CVKD3YJ3.js");
13746
+ const { cleanupAllLogs } = await import("../specialist-logs-KLGJCEUL.js");
13244
13747
  console.log("\u{1F9F9} Cleaning up old logs for all projects...\n");
13245
13748
  const results = cleanupAllLogs();
13246
13749
  console.log(`
@@ -13267,7 +13770,7 @@ async function cleanupLogsCommand(projectOrAll, type, options) {
13267
13770
  console.log(" Use --force to confirm.");
13268
13771
  process.exit(1);
13269
13772
  }
13270
- const { cleanupOldLogs } = await import("../specialist-logs-CVKD3YJ3.js");
13773
+ const { cleanupOldLogs } = await import("../specialist-logs-KLGJCEUL.js");
13271
13774
  const { getSpecialistRetention } = await import("../projects-JEIVIYC6.js");
13272
13775
  const retention = getSpecialistRetention(projectOrAll);
13273
13776
  console.log(`\u{1F9F9} Cleaning up old logs for ${projectOrAll}/${type}...`);
@@ -13306,8 +13809,8 @@ import ora19 from "ora";
13306
13809
  // src/lib/convoy.ts
13307
13810
  init_esm_shims();
13308
13811
  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";
13812
+ import { existsSync as existsSync49, mkdirSync as mkdirSync24, writeFileSync as writeFileSync25, readFileSync as readFileSync41, readdirSync as readdirSync19 } from "fs";
13813
+ import { join as join47 } from "path";
13311
13814
  import { homedir as homedir22 } from "os";
13312
13815
  import { exec as exec19 } from "child_process";
13313
13816
  import { promisify as promisify19 } from "util";
@@ -13416,13 +13919,13 @@ function getExecutionOrder(template) {
13416
13919
  init_paths();
13417
13920
  init_work_type_router();
13418
13921
  var execAsync19 = promisify19(exec19);
13419
- var CONVOY_DIR = join45(homedir22(), ".panopticon", "convoys");
13922
+ var CONVOY_DIR = join47(homedir22(), ".panopticon", "convoys");
13420
13923
  function getConvoyStateFile(convoyId) {
13421
- return join45(CONVOY_DIR, `${convoyId}.json`);
13924
+ return join47(CONVOY_DIR, `${convoyId}.json`);
13422
13925
  }
13423
13926
  function getConvoyOutputDir(convoyId, template) {
13424
13927
  const baseDir = template.config?.outputDir || ".panopticon/convoy-output";
13425
- return join45(process.cwd(), baseDir, convoyId);
13928
+ return join47(process.cwd(), baseDir, convoyId);
13426
13929
  }
13427
13930
  function saveConvoyState(state) {
13428
13931
  mkdirSync24(CONVOY_DIR, { recursive: true });
@@ -13430,11 +13933,11 @@ function saveConvoyState(state) {
13430
13933
  }
13431
13934
  function loadConvoyState(convoyId) {
13432
13935
  const stateFile = getConvoyStateFile(convoyId);
13433
- if (!existsSync47(stateFile)) {
13936
+ if (!existsSync49(stateFile)) {
13434
13937
  return void 0;
13435
13938
  }
13436
13939
  try {
13437
- const content = readFileSync39(stateFile, "utf-8");
13940
+ const content = readFileSync41(stateFile, "utf-8");
13438
13941
  return JSON.parse(content);
13439
13942
  } catch {
13440
13943
  return void 0;
@@ -13444,10 +13947,10 @@ function getConvoyStatus(convoyId) {
13444
13947
  return loadConvoyState(convoyId);
13445
13948
  }
13446
13949
  function listConvoys(filter) {
13447
- if (!existsSync47(CONVOY_DIR)) {
13950
+ if (!existsSync49(CONVOY_DIR)) {
13448
13951
  return [];
13449
13952
  }
13450
- const files = readdirSync18(CONVOY_DIR).filter((f) => f.endsWith(".json"));
13953
+ const files = readdirSync19(CONVOY_DIR).filter((f) => f.endsWith(".json"));
13451
13954
  const convoys = [];
13452
13955
  for (const file of files) {
13453
13956
  const convoyId = file.replace(".json", "");
@@ -13463,10 +13966,10 @@ function listConvoys(filter) {
13463
13966
  );
13464
13967
  }
13465
13968
  function parseAgentTemplate(templatePath) {
13466
- if (!existsSync47(templatePath)) {
13969
+ if (!existsSync49(templatePath)) {
13467
13970
  throw new Error(`Agent template not found: ${templatePath}`);
13468
13971
  }
13469
- const content = readFileSync39(templatePath, "utf-8");
13972
+ const content = readFileSync41(templatePath, "utf-8");
13470
13973
  const frontmatterMatch = content.match(/^---\n([\s\S]+?)\n---\n([\s\S]*)$/);
13471
13974
  if (!frontmatterMatch) {
13472
13975
  throw new Error(`Invalid agent template format (missing frontmatter): ${templatePath}`);
@@ -13492,7 +13995,7 @@ function mapConvoyRoleToWorkType(role) {
13492
13995
  }
13493
13996
  async function spawnConvoyAgent(convoy, agent, agentState, context) {
13494
13997
  const { role, subagent } = agent;
13495
- const templatePath = join45(AGENTS_DIR, `${subagent}.md`);
13998
+ const templatePath = join47(AGENTS_DIR, `${subagent}.md`);
13496
13999
  const template = parseAgentTemplate(templatePath);
13497
14000
  let model = template.model;
13498
14001
  try {
@@ -13531,7 +14034,7 @@ ${context.issueId ? `**Issue ID**: ${context.issueId}` : ""}
13531
14034
  `;
13532
14035
  prompt = contextInstructions + prompt;
13533
14036
  mkdirSync24(convoy.outputDir, { recursive: true });
13534
- const promptFile = join45(convoy.outputDir, `${role}-prompt.md`);
14037
+ const promptFile = join47(convoy.outputDir, `${role}-prompt.md`);
13535
14038
  writeFileSync25(promptFile, prompt);
13536
14039
  const claudeCmd = `claude --dangerously-skip-permissions --model ${model}`;
13537
14040
  createSession(agentState.tmuxSession, convoy.context.projectPath, claudeCmd, {
@@ -13570,7 +14073,7 @@ async function startConvoy(templateName, context) {
13570
14073
  };
13571
14074
  for (const agent of template.agents) {
13572
14075
  const tmuxSession = `${convoyId}-${agent.role}`;
13573
- const outputFile = join45(outputDir, `${agent.role}.md`);
14076
+ const outputFile = join47(outputDir, `${agent.role}.md`);
13574
14077
  state.agents.push({
13575
14078
  role: agent.role,
13576
14079
  subagent: agent.subagent,
@@ -13605,8 +14108,8 @@ async function executePhase(convoy, template, phaseAgents, context) {
13605
14108
  const agentContext = { ...context };
13606
14109
  for (const depRole of deps) {
13607
14110
  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");
14111
+ if (depAgent?.outputFile && existsSync49(depAgent.outputFile)) {
14112
+ agentContext[`${depRole}_output`] = readFileSync41(depAgent.outputFile, "utf-8");
13610
14113
  }
13611
14114
  }
13612
14115
  spawnPromises.push(spawnConvoyAgent(convoy, agent, agentState, agentContext));
@@ -13669,7 +14172,7 @@ function updateAgentStatuses(convoy) {
13669
14172
  agent.status = "completed";
13670
14173
  agent.completedAt = (/* @__PURE__ */ new Date()).toISOString();
13671
14174
  updated = true;
13672
- if (agent.outputFile && existsSync47(agent.outputFile)) {
14175
+ if (agent.outputFile && existsSync49(agent.outputFile)) {
13673
14176
  agent.exitCode = 0;
13674
14177
  } else {
13675
14178
  agent.exitCode = 1;
@@ -13915,30 +14418,30 @@ function registerConvoyCommands(program2) {
13915
14418
  init_esm_shims();
13916
14419
  init_projects();
13917
14420
  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";
14421
+ import { existsSync as existsSync50, readFileSync as readFileSync42, symlinkSync as symlinkSync2, mkdirSync as mkdirSync25, readdirSync as readdirSync20, statSync as statSync10 } from "fs";
14422
+ import { join as join48, resolve, dirname as dirname14 } from "path";
13920
14423
  import { fileURLToPath as fileURLToPath4 } from "url";
13921
14424
  var __filename5 = fileURLToPath4(import.meta.url);
13922
14425
  var __dirname5 = dirname14(__filename5);
13923
- var BUNDLED_HOOKS_DIR = join46(__dirname5, "..", "..", "scripts", "git-hooks");
14426
+ var BUNDLED_HOOKS_DIR = join48(__dirname5, "..", "..", "scripts", "git-hooks");
13924
14427
  function installGitHooks(gitDir) {
13925
- const hooksTarget = join46(gitDir, "hooks");
14428
+ const hooksTarget = join48(gitDir, "hooks");
13926
14429
  let installed = 0;
13927
- if (!existsSync48(hooksTarget)) {
14430
+ if (!existsSync50(hooksTarget)) {
13928
14431
  mkdirSync25(hooksTarget, { recursive: true });
13929
14432
  }
13930
- if (!existsSync48(BUNDLED_HOOKS_DIR)) {
14433
+ if (!existsSync50(BUNDLED_HOOKS_DIR)) {
13931
14434
  return 0;
13932
14435
  }
13933
14436
  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();
14437
+ const hooks = readdirSync20(BUNDLED_HOOKS_DIR).filter((f) => {
14438
+ const p = join48(BUNDLED_HOOKS_DIR, f);
14439
+ return existsSync50(p) && statSync10(p).isFile();
13937
14440
  });
13938
14441
  for (const hook of hooks) {
13939
- const source = join46(BUNDLED_HOOKS_DIR, hook);
13940
- const target = join46(hooksTarget, hook);
13941
- if (existsSync48(target)) {
14442
+ const source = join48(BUNDLED_HOOKS_DIR, hook);
14443
+ const target = join48(hooksTarget, hook);
14444
+ if (existsSync50(target)) {
13942
14445
  try {
13943
14446
  const { readlinkSync: readlinkSync2 } = __require("fs");
13944
14447
  if (readlinkSync2(target) === source) {
@@ -13947,7 +14450,7 @@ function installGitHooks(gitDir) {
13947
14450
  } catch {
13948
14451
  }
13949
14452
  }
13950
- if (existsSync48(target)) {
14453
+ if (existsSync50(target)) {
13951
14454
  const { renameSync: renameSync4 } = __require("fs");
13952
14455
  renameSync4(target, `${target}.backup`);
13953
14456
  }
@@ -13960,7 +14463,7 @@ function installGitHooks(gitDir) {
13960
14463
  }
13961
14464
  async function projectAddCommand(projectPath, options = {}) {
13962
14465
  const fullPath = resolve(projectPath);
13963
- if (!existsSync48(fullPath)) {
14466
+ if (!existsSync50(fullPath)) {
13964
14467
  console.log(chalk50.red(`Path does not exist: ${fullPath}`));
13965
14468
  return;
13966
14469
  }
@@ -13975,9 +14478,9 @@ async function projectAddCommand(projectPath, options = {}) {
13975
14478
  }
13976
14479
  let linearTeam = options.linearTeam;
13977
14480
  if (!linearTeam) {
13978
- const projectToml = join46(fullPath, ".panopticon", "project.toml");
13979
- if (existsSync48(projectToml)) {
13980
- const content = readFileSync40(projectToml, "utf-8");
14481
+ const projectToml = join48(fullPath, ".panopticon", "project.toml");
14482
+ if (existsSync50(projectToml)) {
14483
+ const content = readFileSync42(projectToml, "utf-8");
13981
14484
  const match = content.match(/team\s*=\s*"([^"]+)"/);
13982
14485
  if (match) linearTeam = match[1];
13983
14486
  }
@@ -14003,19 +14506,19 @@ async function projectAddCommand(projectPath, options = {}) {
14003
14506
  console.log(chalk50.dim(` Rally project: ${options.rallyProject}`));
14004
14507
  }
14005
14508
  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"));
14509
+ const hasDevcontainer = existsSync50(join48(fullPath, ".devcontainer"));
14510
+ const hasInfra = existsSync50(join48(fullPath, "infra"));
14511
+ const hasDevcontainerTemplate = existsSync50(join48(fullPath, "infra", ".devcontainer-template")) || existsSync50(join48(fullPath, ".devcontainer-template"));
14512
+ const hasRootGit = existsSync50(join48(fullPath, ".git"));
14010
14513
  const subRepos = [];
14011
14514
  if (!hasRootGit) {
14012
- const { readdirSync: readdirSync21, statSync: statSync11 } = await import("fs");
14515
+ const { readdirSync: readdirSync22, statSync: statSync12 } = await import("fs");
14013
14516
  try {
14014
- const entries = readdirSync21(fullPath);
14517
+ const entries = readdirSync22(fullPath);
14015
14518
  for (const entry of entries) {
14016
- const entryPath = join46(fullPath, entry);
14519
+ const entryPath = join48(fullPath, entry);
14017
14520
  try {
14018
- if (statSync11(entryPath).isDirectory() && existsSync48(join46(entryPath, ".git"))) {
14521
+ if (statSync12(entryPath).isDirectory() && existsSync50(join48(entryPath, ".git"))) {
14019
14522
  subRepos.push(entry);
14020
14523
  }
14021
14524
  } catch {
@@ -14027,13 +14530,13 @@ async function projectAddCommand(projectPath, options = {}) {
14027
14530
  const isPolyrepo = !hasRootGit && subRepos.length > 0;
14028
14531
  let hooksInstalled = 0;
14029
14532
  if (hasRootGit) {
14030
- hooksInstalled = installGitHooks(join46(fullPath, ".git"));
14533
+ hooksInstalled = installGitHooks(join48(fullPath, ".git"));
14031
14534
  if (hooksInstalled > 0) {
14032
14535
  console.log(chalk50.green(`\u2713 Installed ${hooksInstalled} git hook(s) for branch protection`));
14033
14536
  }
14034
14537
  } else if (isPolyrepo) {
14035
14538
  for (const repo of subRepos) {
14036
- const count = installGitHooks(join46(fullPath, repo, ".git"));
14539
+ const count = installGitHooks(join48(fullPath, repo, ".git"));
14037
14540
  hooksInstalled += count;
14038
14541
  }
14039
14542
  if (hooksInstalled > 0) {
@@ -14105,7 +14608,7 @@ async function projectListCommand(options = {}) {
14105
14608
  }
14106
14609
  console.log(chalk50.bold("\nRegistered Projects:\n"));
14107
14610
  for (const { key, config: config2 } of projects) {
14108
- const exists = existsSync48(config2.path);
14611
+ const exists = existsSync50(config2.path);
14109
14612
  const statusIcon = exists ? chalk50.green("\u2713") : chalk50.red("\u2717");
14110
14613
  console.log(`${statusIcon} ${chalk50.bold(config2.name)} ${chalk50.dim(`(${key})`)}`);
14111
14614
  console.log(` ${chalk50.dim(config2.path)}`);
@@ -14139,7 +14642,7 @@ async function projectRemoveCommand(nameOrPath) {
14139
14642
  console.log(chalk50.dim(`Use 'pan project list' to see registered projects.`));
14140
14643
  }
14141
14644
  async function projectInitCommand() {
14142
- if (existsSync48(PROJECTS_CONFIG_FILE)) {
14645
+ if (existsSync50(PROJECTS_CONFIG_FILE)) {
14143
14646
  console.log(chalk50.yellow(`Config already exists: ${PROJECTS_CONFIG_FILE}`));
14144
14647
  return;
14145
14648
  }
@@ -14173,7 +14676,7 @@ async function projectShowCommand(keyOrName) {
14173
14676
  console.log(chalk50.dim(`Use 'pan project list' to see registered projects.`));
14174
14677
  process.exit(1);
14175
14678
  }
14176
- const pathExists = existsSync48(found.path);
14679
+ const pathExists = existsSync50(found.path);
14177
14680
  const pathStatus = pathExists ? chalk50.green("\u2713") : chalk50.red("\u2717");
14178
14681
  console.log(chalk50.bold(`
14179
14682
  Project: ${foundKey}
@@ -14205,10 +14708,10 @@ Project: ${foundKey}
14205
14708
  init_esm_shims();
14206
14709
  init_paths();
14207
14710
  import chalk51 from "chalk";
14208
- import { existsSync as existsSync49, readdirSync as readdirSync20, readFileSync as readFileSync41 } from "fs";
14711
+ import { existsSync as existsSync51, readdirSync as readdirSync21, readFileSync as readFileSync43 } from "fs";
14209
14712
  import { execSync as execSync6 } from "child_process";
14210
14713
  import { homedir as homedir23 } from "os";
14211
- import { join as join47 } from "path";
14714
+ import { join as join49 } from "path";
14212
14715
  function checkCommand3(cmd) {
14213
14716
  try {
14214
14717
  execSync6(`which ${cmd}`, { encoding: "utf-8", stdio: "pipe" });
@@ -14218,12 +14721,12 @@ function checkCommand3(cmd) {
14218
14721
  }
14219
14722
  }
14220
14723
  function checkDirectory(path) {
14221
- return existsSync49(path);
14724
+ return existsSync51(path);
14222
14725
  }
14223
14726
  function countItems(path) {
14224
- if (!existsSync49(path)) return 0;
14727
+ if (!existsSync51(path)) return 0;
14225
14728
  try {
14226
- return readdirSync20(path).length;
14729
+ return readdirSync21(path).length;
14227
14730
  } catch {
14228
14731
  return 0;
14229
14732
  }
@@ -14272,8 +14775,8 @@ async function doctorCommand() {
14272
14775
  }
14273
14776
  }
14274
14777
  if (checkDirectory(CLAUDE_DIR)) {
14275
- const skillsCount = countItems(join47(CLAUDE_DIR, "skills"));
14276
- const commandsCount = countItems(join47(CLAUDE_DIR, "commands"));
14778
+ const skillsCount = countItems(join49(CLAUDE_DIR, "skills"));
14779
+ const commandsCount = countItems(join49(CLAUDE_DIR, "commands"));
14277
14780
  checks.push({
14278
14781
  name: "Claude Code Skills",
14279
14782
  status: skillsCount > 0 ? "ok" : "warn",
@@ -14294,8 +14797,8 @@ async function doctorCommand() {
14294
14797
  fix: "Install Claude Code first"
14295
14798
  });
14296
14799
  }
14297
- const envFile = join47(homedir23(), ".panopticon.env");
14298
- if (existsSync49(envFile)) {
14800
+ const envFile = join49(homedir23(), ".panopticon.env");
14801
+ if (existsSync51(envFile)) {
14299
14802
  checks.push({ name: "Config File", status: "ok", message: "~/.panopticon.env exists" });
14300
14803
  } else {
14301
14804
  checks.push({
@@ -14307,8 +14810,8 @@ async function doctorCommand() {
14307
14810
  }
14308
14811
  if (process.env.LINEAR_API_KEY) {
14309
14812
  checks.push({ name: "LINEAR_API_KEY", status: "ok", message: "Set in environment" });
14310
- } else if (existsSync49(envFile)) {
14311
- const content = readFileSync41(envFile, "utf-8");
14813
+ } else if (existsSync51(envFile)) {
14814
+ const content = readFileSync43(envFile, "utf-8");
14312
14815
  if (content.includes("LINEAR_API_KEY")) {
14313
14816
  checks.push({ name: "LINEAR_API_KEY", status: "ok", message: "Set in config file" });
14314
14817
  } else {
@@ -14376,15 +14879,15 @@ init_esm_shims();
14376
14879
  init_config();
14377
14880
  import { execSync as execSync7 } from "child_process";
14378
14881
  import chalk52 from "chalk";
14379
- import { readFileSync as readFileSync42 } from "fs";
14882
+ import { readFileSync as readFileSync44 } from "fs";
14380
14883
  import { fileURLToPath as fileURLToPath5 } from "url";
14381
- import { dirname as dirname15, join as join48 } from "path";
14884
+ import { dirname as dirname15, join as join50 } from "path";
14382
14885
  function getCurrentVersion() {
14383
14886
  try {
14384
14887
  const __filename6 = fileURLToPath5(import.meta.url);
14385
14888
  const __dirname6 = dirname15(__filename6);
14386
- const pkgPath = join48(__dirname6, "..", "..", "..", "package.json");
14387
- const pkg = JSON.parse(readFileSync42(pkgPath, "utf-8"));
14889
+ const pkgPath = join50(__dirname6, "..", "..", "..", "package.json");
14890
+ const pkg = JSON.parse(readFileSync44(pkgPath, "utf-8"));
14388
14891
  return pkg.version;
14389
14892
  } catch {
14390
14893
  return "unknown";
@@ -14470,8 +14973,8 @@ init_esm_shims();
14470
14973
  init_projects();
14471
14974
  import chalk53 from "chalk";
14472
14975
  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";
14976
+ import { existsSync as existsSync52, readFileSync as readFileSync45, writeFileSync as writeFileSync26, mkdirSync as mkdirSync26, statSync as statSync11 } from "fs";
14977
+ import { join as join51, dirname as dirname16 } from "path";
14475
14978
  import { exec as exec20 } from "child_process";
14476
14979
  import { promisify as promisify20 } from "util";
14477
14980
  var execAsync20 = promisify20(exec20);
@@ -14533,9 +15036,9 @@ async function snapshotCommand(options) {
14533
15036
  `));
14534
15037
  return;
14535
15038
  }
14536
- const outputPath = options.output || dbConfig.seed_file || join49(projectConfig.path, "infra", "seed", "seed.sql");
15039
+ const outputPath = options.output || dbConfig.seed_file || join51(projectConfig.path, "infra", "seed", "seed.sql");
14537
15040
  const outputDir = dirname16(outputPath);
14538
- if (!existsSync50(outputDir)) {
15041
+ if (!existsSync52(outputDir)) {
14539
15042
  mkdirSync26(outputDir, { recursive: true });
14540
15043
  }
14541
15044
  spinner.text = "Running snapshot command...";
@@ -14558,8 +15061,8 @@ async function snapshotCommand(options) {
14558
15061
  try {
14559
15062
  await execAsync20(fullCmd, { timeout: 3e5 });
14560
15063
  } catch (error) {
14561
- if (existsSync50(outputPath)) {
14562
- const content2 = readFileSync43(outputPath, "utf-8");
15064
+ if (existsSync52(outputPath)) {
15065
+ const content2 = readFileSync45(outputPath, "utf-8");
14563
15066
  if (content2.includes("PostgreSQL database dump")) {
14564
15067
  spinner.warn("Snapshot completed with warnings (stderr captured)");
14565
15068
  console.log(chalk53.dim(" Run `pan db clean` to remove stderr noise from the file"));
@@ -14572,7 +15075,7 @@ async function snapshotCommand(options) {
14572
15075
  return;
14573
15076
  }
14574
15077
  }
14575
- const content = readFileSync43(outputPath, "utf-8");
15078
+ const content = readFileSync45(outputPath, "utf-8");
14576
15079
  if (content.includes("Defaulted container") || content.includes("Unable to use a TTY")) {
14577
15080
  spinner.text = "Cleaning kubectl output from snapshot...";
14578
15081
  await cleanFile(outputPath);
@@ -14586,7 +15089,7 @@ async function snapshotCommand(options) {
14586
15089
  }
14587
15090
  }
14588
15091
  spinner.succeed(`Snapshot saved to ${outputPath}`);
14589
- const stats = statSync10(outputPath);
15092
+ const stats = statSync11(outputPath);
14590
15093
  const sizeMB = (stats.size / 1024 / 1024).toFixed(2);
14591
15094
  console.log(chalk53.dim(` Size: ${sizeMB} MB`));
14592
15095
  } catch (error) {
@@ -14604,14 +15107,14 @@ async function seedCommand(workspaceOrIssue, options) {
14604
15107
  spinner.fail("Could not find project workspace configuration");
14605
15108
  return;
14606
15109
  }
14607
- const workspacePath = join49(projectConfig.path, projectConfig.workspace.workspaces_dir || "workspaces", folderName);
14608
- if (!existsSync50(workspacePath)) {
15110
+ const workspacePath = join51(projectConfig.path, projectConfig.workspace.workspaces_dir || "workspaces", folderName);
15111
+ if (!existsSync52(workspacePath)) {
14609
15112
  spinner.fail(`Workspace not found: ${workspacePath}`);
14610
15113
  return;
14611
15114
  }
14612
15115
  const dbConfig = projectConfig.workspace.database;
14613
15116
  const seedFile = options.file || dbConfig?.seed_file;
14614
- if (!seedFile || !existsSync50(seedFile)) {
15117
+ if (!seedFile || !existsSync52(seedFile)) {
14615
15118
  spinner.fail(`Seed file not found: ${seedFile || "(not configured)"}`);
14616
15119
  console.log(chalk53.dim("\nConfigure seed_file in projects.yaml or use --file"));
14617
15120
  return;
@@ -14753,11 +15256,11 @@ async function statusCommand5(workspaceOrIssue) {
14753
15256
  async function cleanCommand(file, options) {
14754
15257
  const spinner = ora21("Cleaning database dump file...").start();
14755
15258
  try {
14756
- if (!existsSync50(file)) {
15259
+ if (!existsSync52(file)) {
14757
15260
  spinner.fail(`File not found: ${file}`);
14758
15261
  return;
14759
15262
  }
14760
- const content = readFileSync43(file, "utf-8");
15263
+ const content = readFileSync45(file, "utf-8");
14761
15264
  const lines = content.split("\n");
14762
15265
  const patternsToRemove = [
14763
15266
  /^Defaulted container/,
@@ -14827,7 +15330,7 @@ async function cleanCommand(file, options) {
14827
15330
  }
14828
15331
  }
14829
15332
  async function cleanFile(filePath) {
14830
- const content = readFileSync43(filePath, "utf-8");
15333
+ const content = readFileSync45(filePath, "utf-8");
14831
15334
  const lines = content.split("\n");
14832
15335
  let startIndex = 0;
14833
15336
  for (let i = 0; i < lines.length; i++) {
@@ -14881,11 +15384,11 @@ async function configCommand(project2) {
14881
15384
  return;
14882
15385
  }
14883
15386
  if (dbConfig.seed_file) {
14884
- const exists = existsSync50(dbConfig.seed_file);
15387
+ const exists = existsSync52(dbConfig.seed_file);
14885
15388
  console.log(` Seed file: ${dbConfig.seed_file}`);
14886
15389
  console.log(chalk53.dim(` Status: ${exists ? chalk53.green("exists") : chalk53.red("not found")}`));
14887
15390
  if (exists) {
14888
- const stats = statSync10(dbConfig.seed_file);
15391
+ const stats = statSync11(dbConfig.seed_file);
14889
15392
  console.log(chalk53.dim(` Size: ${(stats.size / 1024 / 1024).toFixed(2)} MB`));
14890
15393
  }
14891
15394
  }
@@ -14910,8 +15413,8 @@ async function configCommand(project2) {
14910
15413
  init_esm_shims();
14911
15414
  import chalk54 from "chalk";
14912
15415
  import ora22 from "ora";
14913
- import { existsSync as existsSync51, readFileSync as readFileSync44 } from "fs";
14914
- import { join as join50 } from "path";
15416
+ import { existsSync as existsSync53, readFileSync as readFileSync46 } from "fs";
15417
+ import { join as join52 } from "path";
14915
15418
  import { exec as exec21, execSync as execSync8 } from "child_process";
14916
15419
  import { promisify as promisify21 } from "util";
14917
15420
  import { platform } from "os";
@@ -14920,7 +15423,7 @@ function detectPlatform2() {
14920
15423
  const os = platform();
14921
15424
  if (os === "linux") {
14922
15425
  try {
14923
- const release = readFileSync44("/proc/version", "utf8").toLowerCase();
15426
+ const release = readFileSync46("/proc/version", "utf8").toLowerCase();
14924
15427
  if (release.includes("microsoft") || release.includes("wsl")) {
14925
15428
  return "wsl";
14926
15429
  }
@@ -14958,8 +15461,8 @@ async function compactCommand(options) {
14958
15461
  console.log(chalk54.dim("Install beads: https://github.com/steveyegge/beads"));
14959
15462
  process.exit(1);
14960
15463
  }
14961
- const beadsDir = join50(cwd, ".beads");
14962
- if (!existsSync51(beadsDir)) {
15464
+ const beadsDir = join52(cwd, ".beads");
15465
+ if (!existsSync53(beadsDir)) {
14963
15466
  console.error(chalk54.red("Error: No .beads directory found in current directory"));
14964
15467
  console.log(chalk54.dim("Run bd init to initialize beads"));
14965
15468
  process.exit(1);
@@ -15018,8 +15521,8 @@ async function statsCommand() {
15018
15521
  console.error(chalk54.red("Error: bd (beads) CLI not found"));
15019
15522
  process.exit(1);
15020
15523
  }
15021
- const beadsDir = join50(cwd, ".beads");
15022
- if (!existsSync51(beadsDir)) {
15524
+ const beadsDir = join52(cwd, ".beads");
15525
+ if (!existsSync53(beadsDir)) {
15023
15526
  console.error(chalk54.red("Error: No .beads directory found"));
15024
15527
  process.exit(1);
15025
15528
  }
@@ -15647,8 +16150,8 @@ import chalk59 from "chalk";
15647
16150
  import ora27 from "ora";
15648
16151
  import { exec as exec22 } from "child_process";
15649
16152
  import { promisify as promisify22 } from "util";
15650
- import { existsSync as existsSync52, readFileSync as readFileSync45 } from "fs";
15651
- import { join as join51 } from "path";
16153
+ import { existsSync as existsSync54, readFileSync as readFileSync47 } from "fs";
16154
+ import { join as join53 } from "path";
15652
16155
  import { homedir as homedir24 } from "os";
15653
16156
  var execAsync22 = promisify22(exec22);
15654
16157
  async function setupCommand() {
@@ -15661,16 +16164,16 @@ async function setupCommand() {
15661
16164
  console.log("");
15662
16165
  console.log(chalk59.bold(" Step 2: SSH Key Configuration"));
15663
16166
  console.log("");
15664
- const sshDir = join51(homedir24(), ".ssh");
15665
- const defaultKeyPath = join51(sshDir, "id_ed25519");
15666
- const rsaKeyPath = join51(sshDir, "id_rsa");
16167
+ const sshDir = join53(homedir24(), ".ssh");
16168
+ const defaultKeyPath = join53(sshDir, "id_ed25519");
16169
+ const rsaKeyPath = join53(sshDir, "id_rsa");
15667
16170
  let sshKeyExists = false;
15668
16171
  let keyPath = "";
15669
- if (existsSync52(defaultKeyPath)) {
16172
+ if (existsSync54(defaultKeyPath)) {
15670
16173
  sshKeyExists = true;
15671
16174
  keyPath = defaultKeyPath;
15672
16175
  console.log(` ${chalk59.green("\u2713")} SSH key found: ${chalk59.dim(defaultKeyPath)}`);
15673
- } else if (existsSync52(rsaKeyPath)) {
16176
+ } else if (existsSync54(rsaKeyPath)) {
15674
16177
  sshKeyExists = true;
15675
16178
  keyPath = rsaKeyPath;
15676
16179
  console.log(` ${chalk59.green("\u2713")} SSH key found: ${chalk59.dim(rsaKeyPath)}`);
@@ -15684,8 +16187,8 @@ async function setupCommand() {
15684
16187
  }
15685
16188
  if (sshKeyExists) {
15686
16189
  const pubKeyPath = `${keyPath}.pub`;
15687
- if (existsSync52(pubKeyPath)) {
15688
- const pubKey = readFileSync45(pubKeyPath, "utf8").trim();
16190
+ if (existsSync54(pubKeyPath)) {
16191
+ const pubKey = readFileSync47(pubKeyPath, "utf8").trim();
15689
16192
  console.log("");
15690
16193
  console.log(" Your public key (add this to exe.dev if not already):");
15691
16194
  console.log("");
@@ -15712,8 +16215,8 @@ async function setupCommand() {
15712
16215
  console.log(" Your public key to add:");
15713
16216
  if (sshKeyExists) {
15714
16217
  const pubKeyPath = `${keyPath}.pub`;
15715
- if (existsSync52(pubKeyPath)) {
15716
- const pubKey = readFileSync45(pubKeyPath, "utf8").trim();
16218
+ if (existsSync54(pubKeyPath)) {
16219
+ const pubKey = readFileSync47(pubKeyPath, "utf8").trim();
15717
16220
  console.log(chalk59.dim(` ${pubKey}`));
15718
16221
  }
15719
16222
  } else {
@@ -15798,9 +16301,9 @@ import chalk60 from "chalk";
15798
16301
 
15799
16302
  // src/lib/env-loader.ts
15800
16303
  init_esm_shims();
15801
- import { join as join52 } from "path";
16304
+ import { join as join54 } from "path";
15802
16305
  import { homedir as homedir25 } from "os";
15803
- var ENV_FILE_PATH = join52(homedir25(), ".panopticon.env");
16306
+ var ENV_FILE_PATH = join54(homedir25(), ".panopticon.env");
15804
16307
  function getShadowModeFromEnv() {
15805
16308
  const value = process.env.SHADOW_MODE;
15806
16309
  if (!value) return false;
@@ -15895,10 +16398,10 @@ Shadowed issues: ${shadowedIssues.length}`));
15895
16398
  }
15896
16399
 
15897
16400
  // src/cli/index.ts
15898
- var PANOPTICON_ENV_FILE = join53(homedir26(), ".panopticon.env");
15899
- if (existsSync53(PANOPTICON_ENV_FILE)) {
16401
+ var PANOPTICON_ENV_FILE = join55(homedir26(), ".panopticon.env");
16402
+ if (existsSync55(PANOPTICON_ENV_FILE)) {
15900
16403
  try {
15901
- const envContent = readFileSync46(PANOPTICON_ENV_FILE, "utf-8");
16404
+ const envContent = readFileSync48(PANOPTICON_ENV_FILE, "utf-8");
15902
16405
  for (const line of envContent.split("\n")) {
15903
16406
  const trimmed = line.trim();
15904
16407
  if (!trimmed || trimmed.startsWith("#")) continue;
@@ -15915,7 +16418,7 @@ if (existsSync53(PANOPTICON_ENV_FILE)) {
15915
16418
  }
15916
16419
  }
15917
16420
  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);
16421
+ 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
16422
  program.command("init").description("Initialize Panopticon (~/.panopticon/)").action(initCommand);
15920
16423
  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
16424
  program.command("restore [timestamp]").description("Restore from backup").action(restoreCommand);
@@ -15936,24 +16439,24 @@ registerBeadsCommands(program);
15936
16439
  registerRemoteCommands(program);
15937
16440
  registerConfigCommand(program);
15938
16441
  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);
16442
+ 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
16443
  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");
16444
+ const { spawn: spawn2, execSync: execSync9 } = await import("child_process");
16445
+ const { join: join56, dirname: dirname17 } = await import("path");
15943
16446
  const { fileURLToPath: fileURLToPath6 } = await import("url");
15944
- const { readFileSync: readFileSync47, existsSync: existsSync54 } = await import("fs");
16447
+ const { readFileSync: readFileSync49, existsSync: existsSync56 } = await import("fs");
15945
16448
  const { parse } = await import("@iarna/toml");
15946
16449
  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");
16450
+ const bundledServer = join56(__dirname6, "..", "dashboard", "server.js");
16451
+ const srcDashboard = join56(__dirname6, "..", "..", "src", "dashboard");
16452
+ const configFile = join56(process.env.HOME || "", ".panopticon", "config.toml");
15950
16453
  let traefikEnabled = false;
15951
16454
  let traefikDomain = "pan.localhost";
15952
16455
  let dashboardPort = 3010;
15953
16456
  let dashboardApiPort = 3011;
15954
- if (existsSync54(configFile)) {
16457
+ if (existsSync56(configFile)) {
15955
16458
  try {
15956
- const configContent = readFileSync47(configFile, "utf-8");
16459
+ const configContent = readFileSync49(configFile, "utf-8");
15957
16460
  const config2 = parse(configContent);
15958
16461
  traefikEnabled = config2.traefik?.enabled === true;
15959
16462
  traefikDomain = config2.traefik?.domain || "pan.localhost";
@@ -15966,7 +16469,7 @@ program.command("up").description("Start dashboard (and Traefik if enabled)").op
15966
16469
  console.log(chalk61.bold("Starting Panopticon...\n"));
15967
16470
  if (traefikEnabled && !options.skipTraefik) {
15968
16471
  try {
15969
- const { generatePanopticonTraefikConfig: generatePanopticonTraefikConfig2, ensureProjectCerts: ensureProjectCerts2, generateTlsConfig: generateTlsConfig2, cleanupStaleTlsSections } = await import("../traefik-QX4ZV4YG.js");
16472
+ const { generatePanopticonTraefikConfig: generatePanopticonTraefikConfig2, ensureProjectCerts: ensureProjectCerts2, generateTlsConfig: generateTlsConfig2, cleanupStaleTlsSections } = await import("../traefik-QN7R5I6V.js");
15970
16473
  cleanupStaleTlsSections();
15971
16474
  if (generatePanopticonTraefikConfig2()) {
15972
16475
  console.log(chalk61.dim(" Regenerated Traefik config from template"));
@@ -15983,7 +16486,7 @@ program.command("up").description("Start dashboard (and Traefik if enabled)").op
15983
16486
  }
15984
16487
  try {
15985
16488
  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();
16489
+ const dnsMethod = (existsSync56(configFile) ? parse(readFileSync49(configFile, "utf-8")).traefik?.dns_sync_method : null) || detectDnsSyncMethod2();
15987
16490
  ensureBaseDomain2(dnsMethod, traefikDomain);
15988
16491
  if (dnsMethod === "wsl2hosts") {
15989
16492
  syncDnsToWindows2().catch(() => {
@@ -16006,12 +16509,12 @@ program.command("up").description("Start dashboard (and Traefik if enabled)").op
16006
16509
  }
16007
16510
  }
16008
16511
  if (traefikEnabled && !options.skipTraefik) {
16009
- const traefikDir = join54(process.env.HOME || "", ".panopticon", "traefik");
16010
- if (existsSync54(traefikDir)) {
16512
+ const traefikDir = join56(process.env.HOME || "", ".panopticon", "traefik");
16513
+ if (existsSync56(traefikDir)) {
16011
16514
  try {
16012
- const composeFile = join54(traefikDir, "docker-compose.yml");
16013
- if (existsSync54(composeFile)) {
16014
- const content = readFileSync47(composeFile, "utf-8");
16515
+ const composeFile = join56(traefikDir, "docker-compose.yml");
16516
+ if (existsSync56(composeFile)) {
16517
+ const content = readFileSync49(composeFile, "utf-8");
16015
16518
  if (!content.includes("external: true") && content.includes("panopticon:")) {
16016
16519
  const patched = content.replace(
16017
16520
  /networks:\s*\n\s*panopticon:\s*\n\s*name: panopticon\s*\n\s*driver: bridge/,
@@ -16036,8 +16539,8 @@ program.command("up").description("Start dashboard (and Traefik if enabled)").op
16036
16539
  }
16037
16540
  }
16038
16541
  }
16039
- const isProduction = existsSync54(bundledServer);
16040
- const isDevelopment = existsSync54(srcDashboard);
16542
+ const isProduction = existsSync56(bundledServer);
16543
+ const isDevelopment = existsSync56(srcDashboard);
16041
16544
  if (!isProduction && !isDevelopment) {
16042
16545
  console.error(chalk61.red("Error: Dashboard not found"));
16043
16546
  console.error(chalk61.dim("This may be a corrupted installation. Try reinstalling panopticon-cli."));
@@ -16058,11 +16561,11 @@ program.command("up").description("Start dashboard (and Traefik if enabled)").op
16058
16561
  console.log(chalk61.dim("Starting dashboard (development mode)..."));
16059
16562
  }
16060
16563
  if (options.detach) {
16061
- const child = isProduction ? spawn("node", [bundledServer], {
16564
+ const child = isProduction ? spawn2("node", [bundledServer], {
16062
16565
  detached: true,
16063
16566
  stdio: "ignore",
16064
16567
  env: { ...process.env, DASHBOARD_PORT: String(dashboardPort) }
16065
- }) : spawn("npm", ["run", "dev"], {
16568
+ }) : spawn2("npm", ["run", "dev"], {
16066
16569
  cwd: srcDashboard,
16067
16570
  detached: true,
16068
16571
  stdio: "ignore",
@@ -16096,10 +16599,10 @@ program.command("up").description("Start dashboard (and Traefik if enabled)").op
16096
16599
  console.log(` API: ${chalk61.cyan(`http://localhost:${dashboardApiPort}`)}`);
16097
16600
  }
16098
16601
  console.log(chalk61.dim("\nPress Ctrl+C to stop\n"));
16099
- const child = isProduction ? spawn("node", [bundledServer], {
16602
+ const child = isProduction ? spawn2("node", [bundledServer], {
16100
16603
  stdio: "inherit",
16101
16604
  env: { ...process.env, DASHBOARD_PORT: String(dashboardPort) }
16102
- }) : spawn("npm", ["run", "dev"], {
16605
+ }) : spawn2("npm", ["run", "dev"], {
16103
16606
  cwd: srcDashboard,
16104
16607
  stdio: "inherit",
16105
16608
  shell: true
@@ -16112,8 +16615,8 @@ program.command("up").description("Start dashboard (and Traefik if enabled)").op
16112
16615
  try {
16113
16616
  const { getTldrDaemonService: getTldrDaemonService2 } = await import("../tldr-daemon-T3THOUGT.js");
16114
16617
  const projectRoot = process.cwd();
16115
- const venvPath = join54(projectRoot, ".venv");
16116
- if (existsSync54(venvPath)) {
16618
+ const venvPath = join56(projectRoot, ".venv");
16619
+ if (existsSync56(venvPath)) {
16117
16620
  console.log(chalk61.dim("\nStarting TLDR daemon for project root..."));
16118
16621
  const tldrService = getTldrDaemonService2(projectRoot, venvPath);
16119
16622
  await tldrService.start(true);
@@ -16129,17 +16632,17 @@ program.command("up").description("Start dashboard (and Traefik if enabled)").op
16129
16632
  });
16130
16633
  program.command("down").description("Stop dashboard (and Traefik if enabled)").option("--skip-traefik", "Skip Traefik shutdown").action(async (options) => {
16131
16634
  const { execSync: execSync9 } = await import("child_process");
16132
- const { join: join54 } = await import("path");
16133
- const { readFileSync: readFileSync47, existsSync: existsSync54 } = await import("fs");
16635
+ const { join: join56 } = await import("path");
16636
+ const { readFileSync: readFileSync49, existsSync: existsSync56 } = await import("fs");
16134
16637
  const { parse } = await import("@iarna/toml");
16135
16638
  console.log(chalk61.bold("Stopping Panopticon...\n"));
16136
- const configFile = join54(process.env.HOME || "", ".panopticon", "config.toml");
16639
+ const configFile = join56(process.env.HOME || "", ".panopticon", "config.toml");
16137
16640
  let traefikEnabled = false;
16138
16641
  let dashboardPort = 3010;
16139
16642
  let dashboardApiPort = 3011;
16140
- if (existsSync54(configFile)) {
16643
+ if (existsSync56(configFile)) {
16141
16644
  try {
16142
- const configContent = readFileSync47(configFile, "utf-8");
16645
+ const configContent = readFileSync49(configFile, "utf-8");
16143
16646
  const config2 = parse(configContent);
16144
16647
  traefikEnabled = config2.traefik?.enabled === true;
16145
16648
  dashboardPort = config2.dashboard?.port || 3010;
@@ -16156,8 +16659,8 @@ program.command("down").description("Stop dashboard (and Traefik if enabled)").o
16156
16659
  console.log(chalk61.dim(" No dashboard processes found"));
16157
16660
  }
16158
16661
  if (traefikEnabled && !options.skipTraefik) {
16159
- const traefikDir = join54(process.env.HOME || "", ".panopticon", "traefik");
16160
- if (existsSync54(traefikDir)) {
16662
+ const traefikDir = join56(process.env.HOME || "", ".panopticon", "traefik");
16663
+ if (existsSync56(traefikDir)) {
16161
16664
  console.log(chalk61.dim("Stopping Traefik..."));
16162
16665
  try {
16163
16666
  execSync9("docker compose down", {
@@ -16176,8 +16679,8 @@ program.command("down").description("Stop dashboard (and Traefik if enabled)").o
16176
16679
  const { promisify: promisify23 } = await import("util");
16177
16680
  const execAsync23 = promisify23(exec23);
16178
16681
  const projectRoot = process.cwd();
16179
- const venvPath = join54(projectRoot, ".venv");
16180
- if (existsSync54(venvPath)) {
16682
+ const venvPath = join56(projectRoot, ".venv");
16683
+ if (existsSync56(venvPath)) {
16181
16684
  console.log(chalk61.dim("\nStopping TLDR daemon..."));
16182
16685
  const tldrService = getTldrDaemonService2(projectRoot, venvPath);
16183
16686
  await tldrService.stop();