panopticon-cli 0.5.1 → 0.5.4

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 (120) hide show
  1. package/dist/{agents-5OPQKM5K.js → agents-HNMF52RM.js} +7 -6
  2. package/dist/{chunk-F5555J3A.js → chunk-4HST45MO.js} +13 -27
  3. package/dist/chunk-4HST45MO.js.map +1 -0
  4. package/dist/{chunk-YLPSQAM2.js → chunk-565HZ6VV.js} +2 -2
  5. package/dist/chunk-6N2KBSJA.js +452 -0
  6. package/dist/chunk-6N2KBSJA.js.map +1 -0
  7. package/dist/{chunk-4YSYJ4HM.js → chunk-DFNVHK3N.js} +2 -2
  8. package/dist/{chunk-7SN4L4PH.js → chunk-HOGYHJ2G.js} +2 -2
  9. package/dist/{chunk-FTCPTHIJ.js → chunk-HRU7S4TA.js} +24 -7
  10. package/dist/chunk-HRU7S4TA.js.map +1 -0
  11. package/dist/{chunk-OWHXCGVO.js → chunk-ID4OYXVH.js} +378 -101
  12. package/dist/chunk-ID4OYXVH.js.map +1 -0
  13. package/dist/{chunk-NLQRED36.js → chunk-KBHRXV5T.js} +3 -3
  14. package/dist/chunk-KBHRXV5T.js.map +1 -0
  15. package/dist/{chunk-VHKSS7QX.js → chunk-KY2E2Q3T.js} +25 -19
  16. package/dist/chunk-KY2E2Q3T.js.map +1 -0
  17. package/dist/{chunk-CWELWPWQ.js → chunk-MOPGR3CL.js} +1 -1
  18. package/dist/chunk-MOPGR3CL.js.map +1 -0
  19. package/dist/{chunk-2V4NF7J2.js → chunk-RLZQB7HS.js} +2 -2
  20. package/dist/chunk-RLZQB7HS.js.map +1 -0
  21. package/dist/{chunk-76F6DSVS.js → chunk-T7BBPDEJ.js} +11 -7
  22. package/dist/chunk-T7BBPDEJ.js.map +1 -0
  23. package/dist/chunk-USYP2SBE.js +317 -0
  24. package/dist/chunk-USYP2SBE.js.map +1 -0
  25. package/dist/{chunk-JM6V62LT.js → chunk-ZDNQFWR5.js} +2 -2
  26. package/dist/{chunk-JM6V62LT.js.map → chunk-ZDNQFWR5.js.map} +1 -1
  27. package/dist/{chunk-HJSM6E6U.js → chunk-ZP6EWSZV.js} +29 -322
  28. package/dist/chunk-ZP6EWSZV.js.map +1 -0
  29. package/dist/{chunk-PELXV435.js → chunk-ZTYHZMEC.js} +2 -2
  30. package/dist/chunk-ZTYHZMEC.js.map +1 -0
  31. package/dist/cli/index.js +1390 -777
  32. package/dist/cli/index.js.map +1 -1
  33. package/dist/config-yaml-OVZLKFMA.js +18 -0
  34. package/dist/dashboard/prompts/merge-agent.md +7 -5
  35. package/dist/dashboard/prompts/review-agent.md +12 -1
  36. package/dist/dashboard/prompts/test-agent.md +3 -1
  37. package/dist/dashboard/public/assets/index-DA6pnizT.js +767 -0
  38. package/dist/dashboard/public/assets/index-DSvt5pPn.css +32 -0
  39. package/dist/dashboard/public/assets/jetbrains-mono-cyrillic-400-normal-BEIGL1Tu.woff2 +0 -0
  40. package/dist/dashboard/public/assets/jetbrains-mono-cyrillic-400-normal-ugxPyKxw.woff +0 -0
  41. package/dist/dashboard/public/assets/jetbrains-mono-greek-400-normal-B9oWc5Lo.woff +0 -0
  42. package/dist/dashboard/public/assets/jetbrains-mono-greek-400-normal-C190GLew.woff2 +0 -0
  43. package/dist/dashboard/public/assets/jetbrains-mono-latin-400-normal-6-qcROiO.woff +0 -0
  44. package/dist/dashboard/public/assets/jetbrains-mono-latin-400-normal-V6pRDFza.woff2 +0 -0
  45. package/dist/dashboard/public/assets/jetbrains-mono-latin-ext-400-normal-Bc8Ftmh3.woff2 +0 -0
  46. package/dist/dashboard/public/assets/jetbrains-mono-latin-ext-400-normal-fXTG6kC5.woff +0 -0
  47. package/dist/dashboard/public/assets/jetbrains-mono-vietnamese-400-normal-CqNFfHCs.woff +0 -0
  48. package/dist/dashboard/public/assets/space-grotesk-latin-400-normal-BnQMeOim.woff +0 -0
  49. package/dist/dashboard/public/assets/space-grotesk-latin-400-normal-CJ-V5oYT.woff2 +0 -0
  50. package/dist/dashboard/public/assets/space-grotesk-latin-600-normal-BflQw4A9.woff +0 -0
  51. package/dist/dashboard/public/assets/space-grotesk-latin-600-normal-DjKNqYRj.woff2 +0 -0
  52. package/dist/dashboard/public/assets/space-grotesk-latin-700-normal-CwsQ-cCU.woff +0 -0
  53. package/dist/dashboard/public/assets/space-grotesk-latin-700-normal-RjhwGPKo.woff2 +0 -0
  54. package/dist/dashboard/public/assets/space-grotesk-latin-ext-400-normal-CfP_5XZW.woff2 +0 -0
  55. package/dist/dashboard/public/assets/space-grotesk-latin-ext-400-normal-DRPE3kg4.woff +0 -0
  56. package/dist/dashboard/public/assets/space-grotesk-latin-ext-600-normal-DxxdqCpr.woff2 +0 -0
  57. package/dist/dashboard/public/assets/space-grotesk-latin-ext-600-normal-VcznFIpX.woff +0 -0
  58. package/dist/dashboard/public/assets/space-grotesk-latin-ext-700-normal-BQnZhY3m.woff2 +0 -0
  59. package/dist/dashboard/public/assets/space-grotesk-latin-ext-700-normal-HVCqSBdx.woff +0 -0
  60. package/dist/dashboard/public/assets/space-grotesk-vietnamese-400-normal-B7xT_GF5.woff2 +0 -0
  61. package/dist/dashboard/public/assets/space-grotesk-vietnamese-400-normal-BIWiOVfw.woff +0 -0
  62. package/dist/dashboard/public/assets/space-grotesk-vietnamese-600-normal-D6zpsUhD.woff +0 -0
  63. package/dist/dashboard/public/assets/space-grotesk-vietnamese-600-normal-DUi7WF5p.woff2 +0 -0
  64. package/dist/dashboard/public/assets/space-grotesk-vietnamese-700-normal-DMty7AZE.woff2 +0 -0
  65. package/dist/dashboard/public/assets/space-grotesk-vietnamese-700-normal-Duxec5Rn.woff +0 -0
  66. package/dist/dashboard/public/index.html +5 -3
  67. package/dist/dashboard/server.js +4728 -2767
  68. package/dist/{feedback-writer-VRMMWWTW.js → feedback-writer-T43PI5S2.js} +2 -2
  69. package/dist/{hume-WMAUBBV2.js → hume-CKJJ3OUU.js} +3 -3
  70. package/dist/index.js +4 -3
  71. package/dist/index.js.map +1 -1
  72. package/dist/{projects-CFX3RTDL.js → projects-KVM3MN3Y.js} +2 -2
  73. package/dist/{remote-agents-TFSMW7GN.js → remote-agents-ULPD6C5U.js} +3 -3
  74. package/dist/{remote-workspace-7FPGF2RM.js → remote-workspace-XX6ARE6I.js} +3 -3
  75. package/dist/{review-status-TDPSOU5J.js → review-status-XKUKZF6J.js} +3 -2
  76. package/dist/{specialist-context-WGUUYDWY.js → specialist-context-C66TEMXS.js} +6 -5
  77. package/dist/{specialist-context-WGUUYDWY.js.map → specialist-context-C66TEMXS.js.map} +1 -1
  78. package/dist/{specialist-logs-XJB5TCKJ.js → specialist-logs-CJKXM3SR.js} +6 -5
  79. package/dist/{specialists-5LBRHYFA.js → specialists-NXYD4Z62.js} +6 -5
  80. package/dist/{traefik-WFMQX2LY.js → traefik-5GL3Q7DJ.js} +3 -3
  81. package/dist/{tunnel-W2GZBLEV.js → tunnel-BKC7KLBX.js} +3 -3
  82. package/dist/{workspace-manager-E434Z45T.js → workspace-manager-ALBR62AS.js} +5 -5
  83. package/dist/workspace-manager-ALBR62AS.js.map +1 -0
  84. package/package.json +1 -1
  85. package/scripts/record-cost-event.js +8424 -42
  86. package/scripts/recover-costs-deep.mjs +209 -0
  87. package/scripts/recover-costs-proportional.mjs +206 -0
  88. package/scripts/recover-costs.mjs +169 -0
  89. package/scripts/work-agent-stop-hook +221 -24
  90. package/dist/chunk-2V4NF7J2.js.map +0 -1
  91. package/dist/chunk-76F6DSVS.js.map +0 -1
  92. package/dist/chunk-CWELWPWQ.js.map +0 -1
  93. package/dist/chunk-F5555J3A.js.map +0 -1
  94. package/dist/chunk-FTCPTHIJ.js.map +0 -1
  95. package/dist/chunk-HJSM6E6U.js.map +0 -1
  96. package/dist/chunk-NLQRED36.js.map +0 -1
  97. package/dist/chunk-OWHXCGVO.js.map +0 -1
  98. package/dist/chunk-PELXV435.js.map +0 -1
  99. package/dist/chunk-VHKSS7QX.js.map +0 -1
  100. package/dist/chunk-YGJ54GW2.js +0 -96
  101. package/dist/chunk-YGJ54GW2.js.map +0 -1
  102. package/dist/dashboard/public/assets/index-Ce6q21Fm.js +0 -743
  103. package/dist/dashboard/public/assets/index-NzpI0ItZ.css +0 -32
  104. package/dist/git-utils-I2UDKNZH.js +0 -131
  105. package/dist/git-utils-I2UDKNZH.js.map +0 -1
  106. /package/dist/{agents-5OPQKM5K.js.map → agents-HNMF52RM.js.map} +0 -0
  107. /package/dist/{chunk-YLPSQAM2.js.map → chunk-565HZ6VV.js.map} +0 -0
  108. /package/dist/{chunk-4YSYJ4HM.js.map → chunk-DFNVHK3N.js.map} +0 -0
  109. /package/dist/{chunk-7SN4L4PH.js.map → chunk-HOGYHJ2G.js.map} +0 -0
  110. /package/dist/{hume-WMAUBBV2.js.map → config-yaml-OVZLKFMA.js.map} +0 -0
  111. /package/dist/{feedback-writer-VRMMWWTW.js.map → feedback-writer-T43PI5S2.js.map} +0 -0
  112. /package/dist/{projects-CFX3RTDL.js.map → hume-CKJJ3OUU.js.map} +0 -0
  113. /package/dist/{remote-agents-TFSMW7GN.js.map → projects-KVM3MN3Y.js.map} +0 -0
  114. /package/dist/{review-status-TDPSOU5J.js.map → remote-agents-ULPD6C5U.js.map} +0 -0
  115. /package/dist/{remote-workspace-7FPGF2RM.js.map → remote-workspace-XX6ARE6I.js.map} +0 -0
  116. /package/dist/{specialist-logs-XJB5TCKJ.js.map → review-status-XKUKZF6J.js.map} +0 -0
  117. /package/dist/{specialists-5LBRHYFA.js.map → specialist-logs-CJKXM3SR.js.map} +0 -0
  118. /package/dist/{traefik-WFMQX2LY.js.map → specialists-NXYD4Z62.js.map} +0 -0
  119. /package/dist/{tunnel-W2GZBLEV.js.map → traefik-5GL3Q7DJ.js.map} +0 -0
  120. /package/dist/{workspace-manager-E434Z45T.js.map → tunnel-BKC7KLBX.js.map} +0 -0
package/dist/cli/index.js CHANGED
@@ -1,10 +1,19 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
+ checkBudget,
3
4
  checkSpecialistQueue,
4
5
  clearSessionId,
5
6
  completeSpecialistTask,
7
+ createBudget,
8
+ deleteBudget,
9
+ formatCost,
10
+ generateReport,
11
+ getAllBudgets,
12
+ getAllProjectSpecialistStatuses,
6
13
  getAllSpecialistStatus,
14
+ getDailySummary,
7
15
  getEnabledSpecialists,
16
+ getMonthlySummary,
8
17
  getNextSpecialistTask,
9
18
  getProjectDirs,
10
19
  getSessionFiles,
@@ -12,38 +21,47 @@ import {
12
21
  getSpecialistMetadata,
13
22
  getSpecialistStatus,
14
23
  getTmuxSessionName,
24
+ getWeeklySummary,
25
+ init_cost,
15
26
  init_jsonl_parser,
16
27
  init_specialists,
17
28
  initializeEnabledSpecialists,
18
29
  isRunning,
19
30
  parseClaudeSession,
31
+ readIssueCosts,
32
+ readTodayCosts,
20
33
  recordWake,
21
34
  setSessionId,
35
+ summarizeCosts,
22
36
  wakeSpecialist,
23
37
  wakeSpecialistOrQueue,
24
38
  wakeSpecialistWithTask
25
- } from "../chunk-OWHXCGVO.js";
39
+ } from "../chunk-ID4OYXVH.js";
26
40
  import {
27
41
  cleanupTemplateFiles,
28
42
  ensureProjectCerts,
29
43
  generatePanopticonTraefikConfig,
30
44
  generateTlsConfig
31
- } from "../chunk-NLQRED36.js";
45
+ } from "../chunk-KBHRXV5T.js";
32
46
  import {
33
47
  applyProjectTemplateOverlay,
34
48
  createWorkspace,
35
49
  init_skills_merge,
36
50
  init_workspace_manager,
37
51
  mergeSkillsIntoWorkspace,
38
- removeWorkspace,
39
- stopWorkspaceDocker
40
- } from "../chunk-F5555J3A.js";
41
- import "../chunk-7SN4L4PH.js";
52
+ removeWorkspace
53
+ } from "../chunk-4HST45MO.js";
54
+ import "../chunk-ZTYHZMEC.js";
55
+ import "../chunk-HOGYHJ2G.js";
56
+ import {
57
+ init_workspace_config,
58
+ replacePlaceholders
59
+ } from "../chunk-MOPGR3CL.js";
42
60
  import {
43
61
  init_remote_agents,
44
62
  isRemoteAgentRunning,
45
63
  spawnRemoteAgent
46
- } from "../chunk-YLPSQAM2.js";
64
+ } from "../chunk-565HZ6VV.js";
47
65
  import {
48
66
  autoRecoverAgents,
49
67
  detectCrashedAgents,
@@ -66,7 +84,7 @@ import {
66
84
  saveSessionId,
67
85
  spawnAgent,
68
86
  stopAgent
69
- } from "../chunk-VHKSS7QX.js";
87
+ } from "../chunk-KY2E2Q3T.js";
70
88
  import {
71
89
  checkHook,
72
90
  clearHook,
@@ -83,7 +101,7 @@ import {
83
101
  sendKeysAsync,
84
102
  sendMail,
85
103
  sessionExists
86
- } from "../chunk-FTCPTHIJ.js";
104
+ } from "../chunk-HRU7S4TA.js";
87
105
  import {
88
106
  createShadowState,
89
107
  getPendingSyncCount,
@@ -106,12 +124,15 @@ import {
106
124
  import {
107
125
  createExeProvider,
108
126
  init_exe_provider
109
- } from "../chunk-JM6V62LT.js";
127
+ } from "../chunk-ZDNQFWR5.js";
110
128
  import {
129
+ closeDatabase,
130
+ getDatabase,
131
+ init_database,
111
132
  init_review_status,
112
133
  loadReviewStatuses,
113
134
  saveReviewStatuses
114
- } from "../chunk-YGJ54GW2.js";
135
+ } from "../chunk-6N2KBSJA.js";
115
136
  import "../chunk-JQBV3Q2W.js";
116
137
  import {
117
138
  addAlias,
@@ -129,19 +150,17 @@ import {
129
150
  restoreBackup,
130
151
  syncHooks,
131
152
  syncStatusline
132
- } from "../chunk-4YSYJ4HM.js";
153
+ } from "../chunk-DFNVHK3N.js";
133
154
  import "../chunk-AQXETQHW.js";
134
155
  import {
135
156
  createTracker,
136
157
  createTrackerFromConfig,
137
158
  init_factory
138
- } from "../chunk-76F6DSVS.js";
159
+ } from "../chunk-T7BBPDEJ.js";
139
160
  import {
140
- init_config_yaml,
141
161
  init_settings,
142
- loadConfig as loadConfig2,
143
162
  loadSettings
144
- } from "../chunk-HJSM6E6U.js";
163
+ } from "../chunk-USYP2SBE.js";
145
164
  import {
146
165
  getDashboardApiUrl,
147
166
  getDefaultConfig,
@@ -163,7 +182,11 @@ import {
163
182
  registerProject,
164
183
  resolveProjectFromIssue,
165
184
  unregisterProject
166
- } from "../chunk-2V4NF7J2.js";
185
+ } from "../chunk-RLZQB7HS.js";
186
+ import {
187
+ init_config_yaml,
188
+ loadConfig as loadConfig2
189
+ } from "../chunk-ZP6EWSZV.js";
167
190
  import {
168
191
  NotImplementedError,
169
192
  init_interface
@@ -204,11 +227,6 @@ import {
204
227
  init_platform,
205
228
  syncDnsToWindows
206
229
  } from "../chunk-JT4O4YVM.js";
207
- import "../chunk-PELXV435.js";
208
- import {
209
- init_workspace_config,
210
- replacePlaceholders
211
- } from "../chunk-CWELWPWQ.js";
212
230
  import {
213
231
  __require,
214
232
  init_esm_shims
@@ -216,11 +234,11 @@ import {
216
234
 
217
235
  // src/cli/index.ts
218
236
  init_esm_shims();
219
- import { readFileSync as readFileSync48, existsSync as existsSync55 } from "fs";
220
- import { join as join55 } from "path";
221
- import { homedir as homedir26 } from "os";
222
- import { Command } from "commander";
223
- import chalk61 from "chalk";
237
+ import { readFileSync as readFileSync49, existsSync as existsSync56 } from "fs";
238
+ import { join as join56 } from "path";
239
+ import { homedir as homedir27 } from "os";
240
+ import { Command as Command2 } from "commander";
241
+ import chalk62 from "chalk";
224
242
 
225
243
  // src/cli/commands/init.ts
226
244
  init_esm_shims();
@@ -933,9 +951,9 @@ init_agents();
933
951
  init_projects();
934
952
  import chalk6 from "chalk";
935
953
  import ora4 from "ora";
936
- import { existsSync as existsSync8, readFileSync as readFileSync6 } from "fs";
937
- import { join as join8, dirname as dirname4 } from "path";
938
- import { homedir as homedir3 } from "os";
954
+ import { existsSync as existsSync9, readFileSync as readFileSync7 } from "fs";
955
+ import { join as join9, dirname as dirname4 } from "path";
956
+ import { homedir as homedir4 } from "os";
939
957
 
940
958
  // src/lib/prd-draft.ts
941
959
  init_esm_shims();
@@ -949,6 +967,63 @@ function hasPRDDraft(issueId) {
949
967
  return existsSync5(getPRDDraftPath(issueId));
950
968
  }
951
969
 
970
+ // src/lib/tracker-utils.ts
971
+ init_esm_shims();
972
+ init_projects();
973
+ import { readFileSync as readFileSync4, existsSync as existsSync6 } from "fs";
974
+ import { join as join6 } from "path";
975
+ import { homedir as homedir3 } from "os";
976
+ function parseGitHubRepos() {
977
+ const repos = [];
978
+ const envFile = join6(homedir3(), ".panopticon.env");
979
+ if (existsSync6(envFile)) {
980
+ const content = readFileSync4(envFile, "utf-8");
981
+ const reposMatch = content.match(/GITHUB_REPOS=(.+)/);
982
+ if (reposMatch) {
983
+ repos.push(...reposMatch[1].trim().split(",").map((r) => {
984
+ const [repoPath, prefix] = r.trim().split(":");
985
+ const [owner, repo] = (repoPath || "").split("/");
986
+ return { owner: owner || "", repo: repo || "", prefix: (prefix || "").toUpperCase() };
987
+ }).filter((r) => r.owner && r.repo && r.prefix));
988
+ }
989
+ }
990
+ if (repos.length === 0) {
991
+ try {
992
+ const { projects } = loadProjectsConfig();
993
+ for (const [, project2] of Object.entries(projects)) {
994
+ if (project2.github_repo) {
995
+ const [owner, repo] = project2.github_repo.split("/");
996
+ const prefix = project2.linear_team || "";
997
+ if (owner && repo && prefix) {
998
+ repos.push({ owner, repo, prefix: prefix.toUpperCase() });
999
+ }
1000
+ }
1001
+ }
1002
+ } catch {
1003
+ }
1004
+ }
1005
+ return repos;
1006
+ }
1007
+ function extractIssuePrefix(issueId) {
1008
+ return issueId.split("-")[0].toUpperCase();
1009
+ }
1010
+ function resolveGitHubIssue(issueId) {
1011
+ const prefix = extractIssuePrefix(issueId);
1012
+ const repos = parseGitHubRepos();
1013
+ for (const repoConfig of repos) {
1014
+ if (repoConfig.prefix === prefix) {
1015
+ const number = parseInt(issueId.split("-")[1], 10);
1016
+ if (!isNaN(number)) {
1017
+ return { isGitHub: true, ...repoConfig, number };
1018
+ }
1019
+ }
1020
+ }
1021
+ return { isGitHub: false };
1022
+ }
1023
+ function isGitHubIssue(issueId) {
1024
+ return resolveGitHubIssue(issueId).isGitHub;
1025
+ }
1026
+
952
1027
  // src/lib/shadow-mode.ts
953
1028
  init_esm_shims();
954
1029
  init_config_yaml();
@@ -1026,20 +1101,20 @@ async function isRemoteAvailable() {
1026
1101
 
1027
1102
  // src/lib/cloister/work-agent-prompt.ts
1028
1103
  init_esm_shims();
1029
- import { existsSync as existsSync7, readFileSync as readFileSync5, readdirSync as readdirSync7, statSync as statSync3 } from "fs";
1030
- import { join as join7, dirname as dirname3 } from "path";
1104
+ import { existsSync as existsSync8, readFileSync as readFileSync6, readdirSync as readdirSync7, statSync as statSync3 } from "fs";
1105
+ import { join as join8, dirname as dirname3 } from "path";
1031
1106
  import { fileURLToPath as fileURLToPath3 } from "url";
1032
1107
 
1033
1108
  // src/lib/template.ts
1034
1109
  init_esm_shims();
1035
1110
  init_paths();
1036
- import { readFileSync as readFileSync4, existsSync as existsSync6, readdirSync as readdirSync6 } from "fs";
1037
- import { join as join6 } from "path";
1111
+ import { readFileSync as readFileSync5, existsSync as existsSync7, readdirSync as readdirSync6 } from "fs";
1112
+ import { join as join7 } from "path";
1038
1113
  function loadTemplate(templatePath) {
1039
- if (!existsSync6(templatePath)) {
1114
+ if (!existsSync7(templatePath)) {
1040
1115
  throw new Error(`Template not found: ${templatePath}`);
1041
1116
  }
1042
- return readFileSync4(templatePath, "utf8");
1117
+ return readFileSync5(templatePath, "utf8");
1043
1118
  }
1044
1119
  function processEnvBlocks(template, env) {
1045
1120
  let result = template.replace(
@@ -1081,17 +1156,17 @@ function generateClaudeMd(projectPath, variables) {
1081
1156
  "warnings.md"
1082
1157
  ];
1083
1158
  for (const section of defaultOrder) {
1084
- const sectionPath = join6(CLAUDE_MD_TEMPLATES, section);
1085
- if (existsSync6(sectionPath)) {
1159
+ const sectionPath = join7(CLAUDE_MD_TEMPLATES, section);
1160
+ if (existsSync7(sectionPath)) {
1086
1161
  const content = loadTemplate(sectionPath);
1087
1162
  sections.push(substituteVariables(content, variables));
1088
1163
  }
1089
1164
  }
1090
- const projectSections = join6(projectPath, ".panopticon", "claude-md", "sections");
1091
- if (existsSync6(projectSections)) {
1165
+ const projectSections = join7(projectPath, ".panopticon", "claude-md", "sections");
1166
+ if (existsSync7(projectSections)) {
1092
1167
  const projectFiles = readdirSync6(projectSections).filter((f) => f.endsWith(".md")).sort();
1093
1168
  for (const file of projectFiles) {
1094
- const content = loadTemplate(join6(projectSections, file));
1169
+ const content = loadTemplate(join7(projectSections, file));
1095
1170
  sections.push(substituteVariables(content, variables));
1096
1171
  }
1097
1172
  }
@@ -1118,28 +1193,28 @@ init_interface();
1118
1193
  var __filename4 = fileURLToPath3(import.meta.url);
1119
1194
  var __dirname4 = dirname3(__filename4);
1120
1195
  function resolvePromptsDir() {
1121
- const direct = join7(__dirname4, "prompts");
1122
- if (existsSync7(direct) && existsSync7(join7(direct, "work-agent.md"))) {
1196
+ const direct = join8(__dirname4, "prompts");
1197
+ if (existsSync8(direct) && existsSync8(join8(direct, "work-agent.md"))) {
1123
1198
  return direct;
1124
1199
  }
1125
1200
  let packageRoot = __dirname4;
1126
1201
  if (packageRoot.includes("/src/")) {
1127
1202
  packageRoot = packageRoot.replace(/\/src\/.*$/, "");
1128
1203
  } else {
1129
- packageRoot = join7(packageRoot, "..", "..");
1204
+ packageRoot = join8(packageRoot, "..", "..");
1130
1205
  }
1131
- const fromRoot = join7(packageRoot, "src", "lib", "cloister", "prompts");
1132
- if (existsSync7(fromRoot)) {
1206
+ const fromRoot = join8(packageRoot, "src", "lib", "cloister", "prompts");
1207
+ if (existsSync8(fromRoot)) {
1133
1208
  return fromRoot;
1134
1209
  }
1135
1210
  return direct;
1136
1211
  }
1137
1212
  function buildWorkAgentPrompt(ctx) {
1138
- const templatePath = join7(resolvePromptsDir(), "work-agent.md");
1139
- if (!existsSync7(templatePath)) {
1213
+ const templatePath = join8(resolvePromptsDir(), "work-agent.md");
1214
+ if (!existsSync8(templatePath)) {
1140
1215
  throw new Error(`Work agent prompt template not found at ${templatePath}`);
1141
1216
  }
1142
- let template = readFileSync5(templatePath, "utf-8");
1217
+ let template = readFileSync6(templatePath, "utf-8");
1143
1218
  let beadsTasksStr = "";
1144
1219
  let stitchDesignsStr = "";
1145
1220
  let polyrepoContextStr = "";
@@ -1184,8 +1259,8 @@ ${template}
1184
1259
  <!-- panopticon:orchestration-context-end -->`;
1185
1260
  }
1186
1261
  function readPendingFeedback(workspacePath) {
1187
- const feedbackDir = join7(workspacePath, ".planning", "feedback");
1188
- if (!existsSync7(feedbackDir)) return "";
1262
+ const feedbackDir = join8(workspacePath, ".planning", "feedback");
1263
+ if (!existsSync8(feedbackDir)) return "";
1189
1264
  try {
1190
1265
  const files = readdirSync7(feedbackDir).filter((f) => f.endsWith(".md")).sort();
1191
1266
  if (files.length === 0) return "";
@@ -1209,8 +1284,8 @@ var COMMENT_BODY_LIMIT = 500;
1209
1284
  var TOTAL_CONTEXT_LIMIT = 2e3;
1210
1285
  async function getTrackerContext(issueId, workspacePath) {
1211
1286
  let stateMtime = null;
1212
- const statePath = join7(workspacePath, ".planning", "STATE.md");
1213
- if (existsSync7(statePath)) {
1287
+ const statePath = join8(workspacePath, ".planning", "STATE.md");
1288
+ if (existsSync8(statePath)) {
1214
1289
  try {
1215
1290
  stateMtime = statSync3(statePath).mtime;
1216
1291
  } catch {
@@ -1305,9 +1380,9 @@ async function getTrackerContext(issueId, workspacePath) {
1305
1380
  return "";
1306
1381
  }
1307
1382
  function readPlanningContext(workspacePath) {
1308
- const statePath = join7(workspacePath, ".planning", "STATE.md");
1309
- if (existsSync7(statePath)) {
1310
- return readFileSync5(statePath, "utf-8");
1383
+ const statePath = join8(workspacePath, ".planning", "STATE.md");
1384
+ if (existsSync8(statePath)) {
1385
+ return readFileSync6(statePath, "utf-8");
1311
1386
  }
1312
1387
  return null;
1313
1388
  }
@@ -1362,14 +1437,14 @@ function readBeadsTasks(workspacePath, projectRoot, issueId) {
1362
1437
  const stateContent = readPlanningContext(workspacePath);
1363
1438
  const beadsIds = stateContent ? extractBeadsIdsFromState(stateContent) : [];
1364
1439
  const beadsPaths = [
1365
- join7(workspacePath, ".beads", "issues.jsonl"),
1366
- join7(projectRoot, ".beads", "issues.jsonl")
1440
+ join8(workspacePath, ".beads", "issues.jsonl"),
1441
+ join8(projectRoot, ".beads", "issues.jsonl")
1367
1442
  ];
1368
1443
  const seenIds = /* @__PURE__ */ new Set();
1369
1444
  for (const beadsPath of beadsPaths) {
1370
- if (!existsSync7(beadsPath)) continue;
1445
+ if (!existsSync8(beadsPath)) continue;
1371
1446
  try {
1372
- const content = readFileSync5(beadsPath, "utf-8");
1447
+ const content = readFileSync6(beadsPath, "utf-8");
1373
1448
  const lines = content.split("\n").filter((line) => line.trim());
1374
1449
  for (const line of lines) {
1375
1450
  try {
@@ -1425,9 +1500,9 @@ function buildPolyrepoContext(issueId, workspacePath) {
1425
1500
 
1426
1501
  // src/cli/commands/work/issue.ts
1427
1502
  function getLinearApiKey() {
1428
- const envFile = join8(homedir3(), ".panopticon.env");
1429
- if (existsSync8(envFile)) {
1430
- const content = readFileSync6(envFile, "utf-8");
1503
+ const envFile = join9(homedir4(), ".panopticon.env");
1504
+ if (existsSync9(envFile)) {
1505
+ const content = readFileSync7(envFile, "utf-8");
1431
1506
  const match = content.match(/LINEAR_API_KEY=(.+)/);
1432
1507
  if (match) return match[1].trim();
1433
1508
  }
@@ -1500,26 +1575,26 @@ function findLocalWorkspace(issueId, labels = []) {
1500
1575
  const resolved = resolveProjectFromIssue(issueId, labels);
1501
1576
  if (resolved) {
1502
1577
  const workspaceName = `feature-${normalizedId}`;
1503
- const workspacePath = join8(resolved.projectPath, "workspaces", workspaceName);
1504
- if (existsSync8(workspacePath)) {
1578
+ const workspacePath = join9(resolved.projectPath, "workspaces", workspaceName);
1579
+ if (existsSync9(workspacePath)) {
1505
1580
  return workspacePath;
1506
1581
  }
1507
- const altPath = join8(resolved.projectPath, "workspaces", normalizedId);
1508
- if (existsSync8(altPath)) {
1582
+ const altPath = join9(resolved.projectPath, "workspaces", normalizedId);
1583
+ if (existsSync9(altPath)) {
1509
1584
  return altPath;
1510
1585
  }
1511
1586
  }
1512
1587
  let dir = process.cwd();
1513
1588
  for (let i = 0; i < 10; i++) {
1514
- const workspacesDir = join8(dir, "workspaces");
1515
- if (existsSync8(workspacesDir)) {
1589
+ const workspacesDir = join9(dir, "workspaces");
1590
+ if (existsSync9(workspacesDir)) {
1516
1591
  const workspaceName = `feature-${normalizedId}`;
1517
- const workspacePath = join8(workspacesDir, workspaceName);
1518
- if (existsSync8(workspacePath)) {
1592
+ const workspacePath = join9(workspacesDir, workspaceName);
1593
+ if (existsSync9(workspacePath)) {
1519
1594
  return workspacePath;
1520
1595
  }
1521
- const altPath = join8(workspacesDir, normalizedId);
1522
- if (existsSync8(altPath)) {
1596
+ const altPath = join9(workspacesDir, normalizedId);
1597
+ if (existsSync9(altPath)) {
1523
1598
  return altPath;
1524
1599
  }
1525
1600
  }
@@ -1557,7 +1632,7 @@ async function handleRemoteWorkspace(issueId, options, spinner) {
1557
1632
  if (!remoteMetadata) {
1558
1633
  spinner.text = "Remote workspace not found, creating...";
1559
1634
  try {
1560
- const { createRemoteWorkspace: createRemoteWorkspace2 } = await import("../remote-workspace-7FPGF2RM.js");
1635
+ const { createRemoteWorkspace: createRemoteWorkspace2 } = await import("../remote-workspace-XX6ARE6I.js");
1561
1636
  remoteMetadata = await createRemoteWorkspace2(issueId, { spinner });
1562
1637
  } catch (error) {
1563
1638
  spinner.fail(`Failed to create remote workspace: ${error.message}`);
@@ -1609,6 +1684,23 @@ async function handleRemoteWorkspace(issueId, options, spinner) {
1609
1684
  createShadowState(issueId, "open", "pan work issue");
1610
1685
  updateShadowState(issueId, "in_progress", "pan work issue");
1611
1686
  console.log(chalk6.cyan(` \u{1F47B} Shadow mode: tracking status locally`));
1687
+ } else if (isGitHubIssue(issueId)) {
1688
+ const gh = resolveGitHubIssue(issueId);
1689
+ if (gh.isGitHub) {
1690
+ try {
1691
+ const { loadConfig: loadYamlConfig } = await import("../config-yaml-OVZLKFMA.js");
1692
+ const yamlConfig = loadYamlConfig();
1693
+ const token = yamlConfig.config.trackerKeys?.github || process.env.GITHUB_TOKEN;
1694
+ if (token) {
1695
+ const { Octokit } = await import("@octokit/rest");
1696
+ const octokit = new Octokit({ auth: token });
1697
+ await octokit.issues.addLabels({ owner: gh.owner, repo: gh.repo, issue_number: gh.number, labels: ["in-progress"] });
1698
+ console.log(chalk6.green(` \u2713 Updated ${issueId.toUpperCase()} to In Progress`));
1699
+ }
1700
+ } catch (err) {
1701
+ console.warn(chalk6.dim(` \u26A0 Could not update GitHub label: ${err.message}`));
1702
+ }
1703
+ }
1612
1704
  } else if (isLinearIssue(issueId)) {
1613
1705
  const apiKey = getLinearApiKey();
1614
1706
  if (apiKey) {
@@ -1653,7 +1745,7 @@ function findProjectRoot(issueId, labels = []) {
1653
1745
  }
1654
1746
  let dir = process.cwd();
1655
1747
  for (let i = 0; i < 10; i++) {
1656
- if (existsSync8(join8(dir, "workspaces")) || existsSync8(join8(dir, ".git")) || existsSync8(join8(dir, "CLAUDE.md"))) {
1748
+ if (existsSync9(join9(dir, "workspaces")) || existsSync9(join9(dir, ".git")) || existsSync9(join9(dir, "CLAUDE.md"))) {
1657
1749
  return dir;
1658
1750
  }
1659
1751
  const parent = dirname4(dir);
@@ -1662,13 +1754,16 @@ function findProjectRoot(issueId, labels = []) {
1662
1754
  }
1663
1755
  return process.cwd();
1664
1756
  }
1757
+ function hasBeadsTasks(workspacePath) {
1758
+ return existsSync9(join9(workspacePath, ".beads", "issues.jsonl"));
1759
+ }
1665
1760
  function validateAndCleanStateFile(workspacePath, issueId) {
1666
- const statePath = join8(workspacePath, ".planning", "STATE.md");
1667
- if (!existsSync8(statePath)) {
1761
+ const statePath = join9(workspacePath, ".planning", "STATE.md");
1762
+ if (!existsSync9(statePath)) {
1668
1763
  return { valid: true, removed: false };
1669
1764
  }
1670
1765
  try {
1671
- const content = readFileSync6(statePath, "utf-8");
1766
+ const content = readFileSync7(statePath, "utf-8");
1672
1767
  const firstLine = content.split("\n")[0] || "";
1673
1768
  const issueMatch = firstLine.match(/^#\s*([A-Z]+-\d+)/i);
1674
1769
  if (issueMatch) {
@@ -1762,6 +1857,16 @@ async function issueCommand(id, options) {
1762
1857
  if (stateValidation.removed) {
1763
1858
  spinner.warn(`Cleaned stale planning state from ${stateValidation.wrongIssue}`);
1764
1859
  }
1860
+ if (!hasBeadsTasks(workspace)) {
1861
+ spinner.fail(`No beads tasks found for ${id}`);
1862
+ console.log("");
1863
+ console.log(chalk6.red(`Planning must create a task breakdown before work begins.`));
1864
+ console.log(chalk6.dim(`Run planning again and ensure it creates beads with "bd create".`));
1865
+ console.log("");
1866
+ console.log(chalk6.bold("To re-run planning:"));
1867
+ console.log(` ${chalk6.cyan(`pan work plan ${id}`)}`);
1868
+ process.exit(1);
1869
+ }
1765
1870
  spinner.text = "Building agent prompt with planning context...";
1766
1871
  const trackerContext = await getTrackerContext(id, workspace);
1767
1872
  const prompt = buildWorkAgentPrompt({ issueId: id, env: "LOCAL", workspacePath: workspace, projectRoot, trackerContext });
@@ -1779,14 +1884,6 @@ async function issueCommand(id, options) {
1779
1884
  createShadowState(id, "open", "pan work issue");
1780
1885
  updateShadowState(id, "in_progress", "pan work issue");
1781
1886
  console.log(chalk6.cyan(` \u{1F47B} Shadow mode: tracking status locally`));
1782
- } else if (isLinearIssue(id)) {
1783
- const apiKey = getLinearApiKey();
1784
- if (apiKey) {
1785
- const updated = await updateLinearToInProgress(apiKey, id);
1786
- if (updated) {
1787
- console.log(chalk6.green(` \u2713 Updated ${id.toUpperCase()} to In Progress`));
1788
- }
1789
- }
1790
1887
  }
1791
1888
  console.log("");
1792
1889
  console.log(chalk6.bold("Agent Details:"));
@@ -1816,14 +1913,14 @@ async function issueCommand(id, options) {
1816
1913
  init_esm_shims();
1817
1914
  init_agents();
1818
1915
  import chalk7 from "chalk";
1819
- import { existsSync as existsSync9, readFileSync as readFileSync7, statSync as statSync4, readdirSync as readdirSync9 } from "fs";
1820
- import { join as join9, basename as basename2 } from "path";
1916
+ import { existsSync as existsSync10, readFileSync as readFileSync8, statSync as statSync4, readdirSync as readdirSync9 } from "fs";
1917
+ import { join as join10, basename as basename2 } from "path";
1821
1918
  init_tldr_daemon();
1822
1919
  function readContextPercent(agentId) {
1823
- const ctxFile = join9(getAgentDir(agentId), "context-pct");
1920
+ const ctxFile = join10(getAgentDir(agentId), "context-pct");
1824
1921
  try {
1825
- if (existsSync9(ctxFile)) {
1826
- const val = parseInt(readFileSync7(ctxFile, "utf8").trim(), 10);
1922
+ if (existsSync10(ctxFile)) {
1923
+ const val = parseInt(readFileSync8(ctxFile, "utf8").trim(), 10);
1827
1924
  return isNaN(val) ? null : val;
1828
1925
  }
1829
1926
  } catch {
@@ -1899,17 +1996,17 @@ async function statusCommand(options) {
1899
1996
  }
1900
1997
  }
1901
1998
  function readTldrIndexData(workspacePath) {
1902
- const tldrPath = join9(workspacePath, ".tldr");
1903
- if (!existsSync9(tldrPath)) {
1999
+ const tldrPath = join10(workspacePath, ".tldr");
2000
+ if (!existsSync10(tldrPath)) {
1904
2001
  return { fileCount: null, edgeCount: null, ageMs: null };
1905
2002
  }
1906
2003
  let fileCount = null;
1907
2004
  let edgeCount = null;
1908
2005
  let ageMs = null;
1909
- const cgPath = join9(tldrPath, "cache", "call_graph.json");
1910
- if (existsSync9(cgPath)) {
2006
+ const cgPath = join10(tldrPath, "cache", "call_graph.json");
2007
+ if (existsSync10(cgPath)) {
1911
2008
  try {
1912
- const cg = JSON.parse(readFileSync7(cgPath, "utf-8"));
2009
+ const cg = JSON.parse(readFileSync8(cgPath, "utf-8"));
1913
2010
  if (Array.isArray(cg.edges)) {
1914
2011
  edgeCount = cg.edges.length;
1915
2012
  const files = /* @__PURE__ */ new Set();
@@ -1922,10 +2019,10 @@ function readTldrIndexData(workspacePath) {
1922
2019
  } catch {
1923
2020
  }
1924
2021
  }
1925
- const langPath = join9(tldrPath, "languages.json");
1926
- if (existsSync9(langPath)) {
2022
+ const langPath = join10(tldrPath, "languages.json");
2023
+ if (existsSync10(langPath)) {
1927
2024
  try {
1928
- const langData = JSON.parse(readFileSync7(langPath, "utf-8"));
2025
+ const langData = JSON.parse(readFileSync8(langPath, "utf-8"));
1929
2026
  if (langData.timestamp) {
1930
2027
  ageMs = Date.now() - langData.timestamp * 1e3;
1931
2028
  }
@@ -1961,20 +2058,20 @@ async function tldrIndexStatusCommand(projectRoot = process.cwd()) {
1961
2058
  const projectName = basename2(projectRoot);
1962
2059
  const mainEntries = [];
1963
2060
  const workspaceEntries = [];
1964
- const mainVenvPath = join9(projectRoot, ".venv");
1965
- if (existsSync9(mainVenvPath)) {
2061
+ const mainVenvPath = join10(projectRoot, ".venv");
2062
+ if (existsSync10(mainVenvPath)) {
1966
2063
  const service = getTldrDaemonService(projectRoot, mainVenvPath);
1967
2064
  const status = await service.getStatus();
1968
2065
  const { fileCount, edgeCount, ageMs } = readTldrIndexData(projectRoot);
1969
2066
  mainEntries.push({ label: `Main (${projectName})`, running: status.running, fileCount, edgeCount, ageMs });
1970
2067
  }
1971
- const workspacesDir = join9(projectRoot, "workspaces");
1972
- if (existsSync9(workspacesDir)) {
2068
+ const workspacesDir = join10(projectRoot, "workspaces");
2069
+ if (existsSync10(workspacesDir)) {
1973
2070
  const dirs = readdirSync9(workspacesDir, { withFileTypes: true }).filter((d) => d.isDirectory() && d.name.startsWith("feature-"));
1974
2071
  for (const ws of dirs) {
1975
- const wsPath = join9(workspacesDir, ws.name);
1976
- const wsVenvPath = join9(wsPath, ".venv");
1977
- if (existsSync9(wsVenvPath)) {
2072
+ const wsPath = join10(workspacesDir, ws.name);
2073
+ const wsVenvPath = join10(wsPath, ".venv");
2074
+ if (existsSync10(wsVenvPath)) {
1978
2075
  const service = getTldrDaemonService(wsPath, wsVenvPath);
1979
2076
  const status = await service.getStatus();
1980
2077
  const { fileCount, edgeCount, ageMs } = readTldrIndexData(wsPath);
@@ -2082,8 +2179,8 @@ init_esm_shims();
2082
2179
  init_agents();
2083
2180
  init_paths();
2084
2181
  import chalk10 from "chalk";
2085
- import { existsSync as existsSync10, readFileSync as readFileSync8 } from "fs";
2086
- import { join as join10 } from "path";
2182
+ import { existsSync as existsSync11, readFileSync as readFileSync9 } from "fs";
2183
+ import { join as join11 } from "path";
2087
2184
  async function pendingCommand() {
2088
2185
  const agents = listRunningAgents().filter((a) => !a.tmuxActive && a.status !== "error");
2089
2186
  if (agents.length === 0) {
@@ -2096,9 +2193,9 @@ async function pendingCommand() {
2096
2193
  console.log(`${chalk10.cyan(agent.issueId)}`);
2097
2194
  console.log(` Agent: ${agent.id}`);
2098
2195
  console.log(` Workspace: ${chalk10.dim(agent.workspace)}`);
2099
- const completionFile = join10(AGENTS_DIR, agent.id, "completion.md");
2100
- if (existsSync10(completionFile)) {
2101
- const content = readFileSync8(completionFile, "utf8");
2196
+ const completionFile = join11(AGENTS_DIR, agent.id, "completion.md");
2197
+ if (existsSync11(completionFile)) {
2198
+ const content = readFileSync9(completionFile, "utf8");
2102
2199
  const firstLine = content.split("\n").find((l) => l.trim() && !l.startsWith("#"));
2103
2200
  if (firstLine) {
2104
2201
  console.log(` Summary: ${chalk10.dim(firstLine.trim())}`);
@@ -2115,14 +2212,14 @@ init_agents();
2115
2212
  init_paths();
2116
2213
  import chalk11 from "chalk";
2117
2214
  import ora5 from "ora";
2118
- import { existsSync as existsSync11, writeFileSync as writeFileSync3, readFileSync as readFileSync9 } from "fs";
2119
- import { join as join11 } from "path";
2120
- import { homedir as homedir4 } from "os";
2215
+ import { existsSync as existsSync12, writeFileSync as writeFileSync3, readFileSync as readFileSync10 } from "fs";
2216
+ import { join as join12 } from "path";
2217
+ import { homedir as homedir5 } from "os";
2121
2218
  import { execSync as execSync2 } from "child_process";
2122
2219
  function getLinearApiKey2() {
2123
- const envFile = join11(homedir4(), ".panopticon.env");
2124
- if (existsSync11(envFile)) {
2125
- const content = readFileSync9(envFile, "utf-8");
2220
+ const envFile = join12(homedir5(), ".panopticon.env");
2221
+ if (existsSync12(envFile)) {
2222
+ const content = readFileSync10(envFile, "utf-8");
2126
2223
  const match = content.match(/LINEAR_API_KEY=(.+)/);
2127
2224
  if (match) return match[1].trim();
2128
2225
  }
@@ -2259,7 +2356,7 @@ async function approveCommand(id, options = {}) {
2259
2356
  state.status = "stopped";
2260
2357
  state.lastActivity = (/* @__PURE__ */ new Date()).toISOString();
2261
2358
  saveAgentState(state);
2262
- const approvedFile = join11(AGENTS_DIR, agentId, "approved");
2359
+ const approvedFile = join12(AGENTS_DIR, agentId, "approved");
2263
2360
  writeFileSync3(approvedFile, JSON.stringify({
2264
2361
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
2265
2362
  prMerged,
@@ -2425,40 +2522,6 @@ function cleanupWorkflowLabels(currentLabels, targetState) {
2425
2522
  return cleaned;
2426
2523
  }
2427
2524
 
2428
- // src/lib/tracker-utils.ts
2429
- init_esm_shims();
2430
- import { readFileSync as readFileSync10, existsSync as existsSync12 } from "fs";
2431
- import { join as join12 } from "path";
2432
- import { homedir as homedir5 } from "os";
2433
- function parseGitHubRepos() {
2434
- const envFile = join12(homedir5(), ".panopticon.env");
2435
- if (!existsSync12(envFile)) return [];
2436
- const content = readFileSync10(envFile, "utf-8");
2437
- const reposMatch = content.match(/GITHUB_REPOS=(.+)/);
2438
- if (!reposMatch) return [];
2439
- return reposMatch[1].trim().split(",").map((r) => {
2440
- const [repoPath, prefix] = r.trim().split(":");
2441
- const [owner, repo] = (repoPath || "").split("/");
2442
- return { owner: owner || "", repo: repo || "", prefix: (prefix || "").toUpperCase() };
2443
- }).filter((r) => r.owner && r.repo && r.prefix);
2444
- }
2445
- function extractIssuePrefix(issueId) {
2446
- return issueId.split("-")[0].toUpperCase();
2447
- }
2448
- function resolveGitHubIssue(issueId) {
2449
- const prefix = extractIssuePrefix(issueId);
2450
- const repos = parseGitHubRepos();
2451
- for (const repoConfig of repos) {
2452
- if (repoConfig.prefix === prefix) {
2453
- const number = parseInt(issueId.split("-")[1], 10);
2454
- if (!isNaN(number)) {
2455
- return { isGitHub: true, ...repoConfig, number };
2456
- }
2457
- }
2458
- }
2459
- return { isGitHub: false };
2460
- }
2461
-
2462
2525
  // src/cli/commands/work/done.ts
2463
2526
  var execAsync = promisify(exec);
2464
2527
  function getLinearApiKey3() {
@@ -2526,9 +2589,9 @@ async function updateGitHubToInReview(issueId, comment) {
2526
2589
  try {
2527
2590
  const ghConfig = getGitHubConfig();
2528
2591
  if (!ghConfig) return false;
2529
- const resolved = resolveGitHubIssue(issueId);
2530
- if (!resolved.isGitHub) return false;
2531
- const { owner, repo, number } = resolved;
2592
+ const number = parseInt(issueId.split("-")[1], 10);
2593
+ const repoConfig = ghConfig.repos.find((r) => r.prefix === "PAN") || ghConfig.repos[0];
2594
+ const { owner, repo } = repoConfig;
2532
2595
  const token = ghConfig.token;
2533
2596
  const headers = {
2534
2597
  "Authorization": `token ${token}`,
@@ -2564,59 +2627,10 @@ async function doneCommand(id, options = {}) {
2564
2627
  const issueId = id.replace(/^agent-/i, "").toUpperCase();
2565
2628
  const agentId = `agent-${issueId.toLowerCase()}`;
2566
2629
  if (!options.force) {
2567
- const { getAgentState: getAgentState2 } = await import("../agents-5OPQKM5K.js");
2630
+ const { getAgentState: getAgentState2 } = await import("../agents-HNMF52RM.js");
2568
2631
  const agentState = getAgentState2(agentId);
2569
2632
  const workspacePath = agentState?.workspace;
2570
2633
  if (workspacePath && existsSync13(workspacePath)) {
2571
- try {
2572
- const { getReviewStatus: getReviewStatus2 } = await import("../review-status-TDPSOU5J.js");
2573
- const { getWorkspaceCommitHashes } = await import("../git-utils-I2UDKNZH.js");
2574
- const { getDashboardApiUrl: getDashboardApiUrl2 } = await import("../config-4CJNUE3O.js");
2575
- const reviewStatus = getReviewStatus2(issueId);
2576
- if (reviewStatus) {
2577
- const isBlockedOrFailed = ["blocked", "failed"].includes(reviewStatus.reviewStatus);
2578
- if (isBlockedOrFailed) {
2579
- const dashboardUrl = getDashboardApiUrl2();
2580
- console.error(chalk12.red(`
2581
- \u2716 Cannot mark work done \u2014 review is ${reviewStatus.reviewStatus} for ${issueId}
2582
- `));
2583
- console.error(" The review agent found issues that must be addressed before this issue can be completed.");
2584
- console.error(" Fix the issues, commit your changes, then re-submit for review:\n");
2585
- console.error(chalk12.cyan(` curl -X POST ${dashboardUrl}/api/workspaces/${issueId}/request-review \\`));
2586
- console.error(chalk12.cyan(` -H "Content-Type: application/json" -d '{}'`));
2587
- console.error("");
2588
- console.error(chalk12.dim(" Use --force to skip this check."));
2589
- console.error("");
2590
- process.exit(1);
2591
- }
2592
- if (reviewStatus.lastReviewCommits) {
2593
- const currentHashes = await getWorkspaceCommitHashes(workspacePath);
2594
- const allKeys = /* @__PURE__ */ new Set([
2595
- ...Object.keys(currentHashes),
2596
- ...Object.keys(reviewStatus.lastReviewCommits)
2597
- ]);
2598
- const commitsChanged = [...allKeys].some(
2599
- (k) => currentHashes[k] !== reviewStatus.lastReviewCommits[k]
2600
- );
2601
- if (commitsChanged) {
2602
- const dashboardUrl = getDashboardApiUrl2();
2603
- console.error(chalk12.red(`
2604
- \u2716 Cannot mark work done \u2014 commits changed since last review for ${issueId}
2605
- `));
2606
- console.error(" New commits were added after the last review/test run. The specialists");
2607
- console.error(" must review and test your latest code before work can be marked done.");
2608
- console.error(" Re-submit for review:\n");
2609
- console.error(chalk12.cyan(` curl -X POST ${dashboardUrl}/api/workspaces/${issueId}/request-review \\`));
2610
- console.error(chalk12.cyan(` -H "Content-Type: application/json" -d '{}'`));
2611
- console.error("");
2612
- console.error(chalk12.dim(" Use --force to skip this check."));
2613
- console.error("");
2614
- process.exit(1);
2615
- }
2616
- }
2617
- }
2618
- } catch {
2619
- }
2620
2634
  const failures = [];
2621
2635
  try {
2622
2636
  const { stdout } = await execAsync("bd list --status open --limit 0 --json", { cwd: workspacePath });
@@ -2683,14 +2697,14 @@ async function doneCommand(id, options = {}) {
2683
2697
  try {
2684
2698
  let trackerUpdated = false;
2685
2699
  let shadowModeActive = false;
2686
- const ghResolution = resolveGitHubIssue(issueId);
2700
+ const isGitHubIssue2 = issueId.startsWith("PAN-");
2687
2701
  const skipTrackerUpdate = shouldSkipTrackerUpdate(issueId, options.shadow);
2688
2702
  if (skipTrackerUpdate) {
2689
2703
  shadowModeActive = true;
2690
2704
  spinner.text = "Updating shadow state...";
2691
2705
  updateShadowState(issueId, "closed", "pan work done");
2692
2706
  console.log(chalk12.cyan(` \u{1F47B} Shadow mode: status updated locally`));
2693
- } else if (ghResolution.isGitHub) {
2707
+ } else if (isGitHubIssue2) {
2694
2708
  spinner.text = "Updating GitHub labels...";
2695
2709
  trackerUpdated = await updateGitHubToInReview(issueId, options.comment);
2696
2710
  if (trackerUpdated) {
@@ -2712,7 +2726,7 @@ async function doneCommand(id, options = {}) {
2712
2726
  console.log(chalk12.dim(" LINEAR_API_KEY not set - skipping status update"));
2713
2727
  }
2714
2728
  }
2715
- const { getAgentState: getAgentState2, saveAgentState: saveAgentState2 } = await import("../agents-5OPQKM5K.js");
2729
+ const { getAgentState: getAgentState2, saveAgentState: saveAgentState2 } = await import("../agents-HNMF52RM.js");
2716
2730
  const existingState = getAgentState2(agentId);
2717
2731
  if (existingState) {
2718
2732
  existingState.status = "stopped";
@@ -3330,48 +3344,79 @@ async function runDiscoveryPhase(issue, complexity, prdContent) {
3330
3344
  async function planCommand(id, options = {}) {
3331
3345
  const spinner = ora7(`Creating execution plan for ${id}...`).start();
3332
3346
  try {
3333
- const apiKey = getLinearApiKey4();
3334
- if (!apiKey) {
3335
- spinner.fail("LINEAR_API_KEY not found");
3336
- console.log("");
3337
- console.log(chalk13.dim("Set it in ~/.panopticon.env:"));
3338
- console.log(" LINEAR_API_KEY=lin_api_xxxxx");
3339
- process.exit(1);
3340
- }
3341
- spinner.text = "Fetching issue from Linear...";
3342
- const { LinearClient: LinearClient2 } = await import("@linear/sdk");
3343
- const client = new LinearClient2({ apiKey });
3344
- const me = await client.viewer;
3345
- const teams = await me.teams();
3346
- const team = teams.nodes[0];
3347
- if (!team) {
3348
- spinner.fail("No Linear team found");
3349
- process.exit(1);
3350
- }
3351
- const searchResult = await team.issues({ first: 100 });
3352
- const issue = searchResult.nodes.find(
3353
- (i) => i.identifier.toUpperCase() === id.toUpperCase()
3354
- );
3355
- if (!issue) {
3356
- spinner.fail(`Issue not found: ${id}`);
3357
- process.exit(1);
3347
+ const ghResolution = resolveGitHubIssue(id);
3348
+ let issueData;
3349
+ if (ghResolution.isGitHub) {
3350
+ spinner.text = "Fetching issue from GitHub...";
3351
+ const { loadConfig: loadYamlConfig } = await import("../config-yaml-OVZLKFMA.js");
3352
+ const yamlConfig = loadYamlConfig();
3353
+ const token = yamlConfig.config.trackerKeys?.github || process.env.GITHUB_TOKEN;
3354
+ if (!token) {
3355
+ spinner.fail("GitHub token not found");
3356
+ process.exit(1);
3357
+ }
3358
+ const { Octokit } = await import("@octokit/rest");
3359
+ const octokit = new Octokit({ auth: token });
3360
+ const { data: ghIssue } = await octokit.issues.get({
3361
+ owner: ghResolution.owner,
3362
+ repo: ghResolution.repo,
3363
+ issue_number: ghResolution.number
3364
+ });
3365
+ issueData = {
3366
+ id: String(ghIssue.id),
3367
+ identifier: id,
3368
+ title: ghIssue.title,
3369
+ description: ghIssue.body || void 0,
3370
+ url: ghIssue.html_url,
3371
+ state: { name: ghIssue.state === "open" ? "Todo" : "Done" },
3372
+ priority: 0,
3373
+ labels: (ghIssue.labels || []).map((l) => ({ name: typeof l === "string" ? l : l.name || "" })),
3374
+ assignee: ghIssue.assignee ? { name: ghIssue.assignee.login } : void 0
3375
+ };
3376
+ } else {
3377
+ const apiKey = getLinearApiKey4();
3378
+ if (!apiKey) {
3379
+ spinner.fail("LINEAR_API_KEY not found");
3380
+ console.log("");
3381
+ console.log(chalk13.dim("Set it in ~/.panopticon.env:"));
3382
+ console.log(" LINEAR_API_KEY=lin_api_xxxxx");
3383
+ process.exit(1);
3384
+ }
3385
+ spinner.text = "Fetching issue from Linear...";
3386
+ const { LinearClient: LinearClient2 } = await import("@linear/sdk");
3387
+ const client = new LinearClient2({ apiKey });
3388
+ const me = await client.viewer;
3389
+ const teams = await me.teams();
3390
+ const team = teams.nodes[0];
3391
+ if (!team) {
3392
+ spinner.fail("No Linear team found");
3393
+ process.exit(1);
3394
+ }
3395
+ const searchResult = await team.issues({ first: 100 });
3396
+ const issue = searchResult.nodes.find(
3397
+ (i) => i.identifier.toUpperCase() === id.toUpperCase()
3398
+ );
3399
+ if (!issue) {
3400
+ spinner.fail(`Issue not found: ${id}`);
3401
+ process.exit(1);
3402
+ }
3403
+ const state = await issue.state;
3404
+ const assignee = await issue.assignee;
3405
+ const project2 = await issue.project;
3406
+ const labels = await issue.labels();
3407
+ issueData = {
3408
+ id: issue.id,
3409
+ identifier: issue.identifier,
3410
+ title: issue.title,
3411
+ description: issue.description || void 0,
3412
+ url: issue.url,
3413
+ state: { name: state?.name || "Unknown" },
3414
+ priority: issue.priority,
3415
+ labels: labels.nodes.map((l) => ({ name: l.name })),
3416
+ assignee: assignee ? { name: assignee.name } : void 0,
3417
+ project: project2 ? { name: project2.name } : void 0
3418
+ };
3358
3419
  }
3359
- const state = await issue.state;
3360
- const assignee = await issue.assignee;
3361
- const project2 = await issue.project;
3362
- const labels = await issue.labels();
3363
- const issueData = {
3364
- id: issue.id,
3365
- identifier: issue.identifier,
3366
- title: issue.title,
3367
- description: issue.description || void 0,
3368
- url: issue.url,
3369
- state: { name: state?.name || "Unknown" },
3370
- priority: issue.priority,
3371
- labels: labels.nodes.map((l) => ({ name: l.name })),
3372
- assignee: assignee ? { name: assignee.name } : void 0,
3373
- project: project2 ? { name: project2.name } : void 0
3374
- };
3375
3420
  spinner.text = "Searching for related PRDs...";
3376
3421
  const prdFiles = await findPRDFiles(id);
3377
3422
  spinner.text = "Analyzing complexity...";
@@ -3504,6 +3549,7 @@ init_esm_shims();
3504
3549
  init_config();
3505
3550
  import chalk14 from "chalk";
3506
3551
  import ora8 from "ora";
3552
+ init_projects();
3507
3553
  var PRIORITY_LABELS = {
3508
3554
  0: chalk14.dim("None"),
3509
3555
  1: chalk14.red("Urgent"),
@@ -3519,21 +3565,40 @@ var STATE_COLORS = {
3519
3565
  function getTrackerConfig(trackerType) {
3520
3566
  const config2 = loadConfig();
3521
3567
  const trackerConfig = config2.trackers[trackerType];
3522
- if (!trackerConfig) {
3523
- return null;
3568
+ if (trackerConfig) {
3569
+ return {
3570
+ type: trackerType,
3571
+ apiKeyEnv: trackerConfig.api_key_env,
3572
+ team: trackerConfig.team,
3573
+ tokenEnv: trackerConfig.token_env,
3574
+ owner: trackerConfig.owner,
3575
+ repo: trackerConfig.repo,
3576
+ projectId: trackerConfig.project_id,
3577
+ server: trackerConfig.server,
3578
+ workspace: trackerConfig.workspace,
3579
+ project: trackerConfig.project
3580
+ };
3524
3581
  }
3525
- return {
3526
- type: trackerType,
3527
- apiKeyEnv: trackerConfig.api_key_env,
3528
- team: trackerConfig.team,
3529
- tokenEnv: trackerConfig.token_env,
3530
- owner: trackerConfig.owner,
3531
- repo: trackerConfig.repo,
3532
- projectId: trackerConfig.project_id,
3533
- server: trackerConfig.server,
3534
- workspace: trackerConfig.workspace,
3535
- project: trackerConfig.project
3536
- };
3582
+ if (trackerType === "github") {
3583
+ try {
3584
+ const { projects } = loadProjectsConfig();
3585
+ for (const [, project2] of Object.entries(projects)) {
3586
+ if (project2.github_repo) {
3587
+ const [owner, repo] = project2.github_repo.split("/");
3588
+ if (owner && repo) {
3589
+ return {
3590
+ type: "github",
3591
+ tokenEnv: "GITHUB_TOKEN",
3592
+ owner,
3593
+ repo
3594
+ };
3595
+ }
3596
+ }
3597
+ }
3598
+ } catch {
3599
+ }
3600
+ }
3601
+ return null;
3537
3602
  }
3538
3603
  function getConfiguredTrackers() {
3539
3604
  const config2 = loadConfig();
@@ -3542,6 +3607,16 @@ function getConfiguredTrackers() {
3542
3607
  if (config2.trackers.github) trackers.push("github");
3543
3608
  if (config2.trackers.gitlab) trackers.push("gitlab");
3544
3609
  if (config2.trackers.rally) trackers.push("rally");
3610
+ if (!trackers.includes("github") || !trackers.includes("gitlab")) {
3611
+ try {
3612
+ const { projects } = loadProjectsConfig();
3613
+ for (const [, project2] of Object.entries(projects)) {
3614
+ if (project2.github_repo && !trackers.includes("github")) trackers.push("github");
3615
+ if (project2.gitlab_repo && !trackers.includes("gitlab")) trackers.push("gitlab");
3616
+ }
3617
+ } catch {
3618
+ }
3619
+ }
3545
3620
  return trackers;
3546
3621
  }
3547
3622
  function displayIssues(issues, trackerName) {
@@ -5196,10 +5271,10 @@ Previous state: ${issue.state}`
5196
5271
  console.log(chalk21.green(`\u2713 ${issue.identifier} reopened and ready for re-work`));
5197
5272
  console.log("");
5198
5273
  try {
5199
- const { getAgentState: getAgentState2 } = await import("../agents-5OPQKM5K.js");
5274
+ const { getAgentState: getAgentState2 } = await import("../agents-HNMF52RM.js");
5200
5275
  const agentId = `agent-${id.toLowerCase()}`;
5201
5276
  const agentState = getAgentState2(agentId);
5202
- const agentRunning = agentState?.status === "active" || agentState?.status === "running";
5277
+ const agentRunning = agentState?.status === "running" || agentState?.status === "starting";
5203
5278
  if (agentRunning) {
5204
5279
  console.log(chalk21.dim("Agent is still running. Send it context about the re-work:"));
5205
5280
  console.log(` pan tell ${id} "Issue reopened. <describe what needs to change>"`);
@@ -5317,7 +5392,6 @@ import { join as join21 } from "path";
5317
5392
  import { existsSync as existsSync22, rmSync, readFileSync as readFileSync20 } from "fs";
5318
5393
  import { exec as exec4 } from "child_process";
5319
5394
  import { promisify as promisify4 } from "util";
5320
- init_workspace_manager();
5321
5395
  var execAsync4 = promisify4(exec4);
5322
5396
  async function wipeCommand(issueId, options) {
5323
5397
  const issueLower = issueId.toLowerCase();
@@ -5430,23 +5504,6 @@ async function wipeCommand(issueId, options) {
5430
5504
  } catch (e) {
5431
5505
  }
5432
5506
  }
5433
- if (projectPath) {
5434
- const workspacePath = join21(projectPath, "workspaces", `feature-${issueLower}`);
5435
- if (existsSync22(workspacePath)) {
5436
- try {
5437
- const projName = projectPath.split("/").pop() || "";
5438
- const dockerResult = await stopWorkspaceDocker(workspacePath, projName, issueLower);
5439
- if (dockerResult.steps.length > 0) {
5440
- for (const step of dockerResult.steps) {
5441
- cleanupLog.push(step);
5442
- console.log(chalk24.green(` \u2713 ${step}`));
5443
- }
5444
- }
5445
- } catch (e) {
5446
- console.log(chalk24.yellow(` \u26A0 Docker cleanup: ${e.message}`));
5447
- }
5448
- }
5449
- }
5450
5507
  if (options.workspace && projectPath) {
5451
5508
  const workspacePath = join21(projectPath, "workspaces", `feature-${issueLower}`);
5452
5509
  if (existsSync22(workspacePath)) {
@@ -6261,6 +6318,9 @@ async function syncMainCommand(id) {
6261
6318
  // src/cli/commands/work/close-out.ts
6262
6319
  init_esm_shims();
6263
6320
  import chalk31 from "chalk";
6321
+ import { existsSync as existsSync30, readFileSync as readFileSync26 } from "fs";
6322
+ import { join as join29 } from "path";
6323
+ import { homedir as homedir14 } from "os";
6264
6324
 
6265
6325
  // src/lib/lifecycle/index.ts
6266
6326
  init_esm_shims();
@@ -6412,11 +6472,17 @@ async function archivePlanning(ctx, opts = {}) {
6412
6472
  return results;
6413
6473
  }
6414
6474
 
6415
- // src/lib/lifecycle/close-issue.ts
6475
+ // src/lib/lifecycle/clean-planning.ts
6416
6476
  init_esm_shims();
6417
6477
  import { exec as exec6 } from "child_process";
6418
6478
  import { promisify as promisify6 } from "util";
6419
6479
  var execAsync6 = promisify6(exec6);
6480
+
6481
+ // src/lib/lifecycle/close-issue.ts
6482
+ init_esm_shims();
6483
+ import { exec as exec7 } from "child_process";
6484
+ import { promisify as promisify7 } from "util";
6485
+ var execAsync7 = promisify7(exec7);
6420
6486
  var CLOSED_OUT_LABEL = "closed-out";
6421
6487
  var CLOSED_OUT_COLOR = "1d4ed8";
6422
6488
  var WORKFLOW_LABELS2 = ["in-progress", "in-review", "needs-close-out"];
@@ -6477,7 +6543,7 @@ async function closeGitHubDirect(ctx, comment) {
6477
6543
  const { owner, repo, number } = ctx.github;
6478
6544
  try {
6479
6545
  const commentArg = comment ? ` --comment "${comment.replace(/"/g, '\\"')}"` : "";
6480
- await execAsync6(
6546
+ await execAsync7(
6481
6547
  `gh issue close ${number} --repo ${owner}/${repo}${commentArg}`,
6482
6548
  { encoding: "utf-8" }
6483
6549
  );
@@ -6495,7 +6561,7 @@ async function closeGitHubPr(ctx) {
6495
6561
  const issueLower = ctx.issueId.toLowerCase();
6496
6562
  const branchName = `feature/${issueLower}`;
6497
6563
  try {
6498
- const { stdout: prListRaw } = await execAsync6(
6564
+ const { stdout: prListRaw } = await execAsync7(
6499
6565
  `gh pr list --repo ${owner}/${repo} --head "${branchName}" --state open --json number --jq '.[0].number'`,
6500
6566
  { encoding: "utf-8" }
6501
6567
  );
@@ -6503,7 +6569,7 @@ async function closeGitHubPr(ctx) {
6503
6569
  if (!prNumber) {
6504
6570
  return stepSkipped(step, ["No open PR found for branch"]);
6505
6571
  }
6506
- await execAsync6(
6572
+ await execAsync7(
6507
6573
  `gh pr close ${prNumber} --repo ${owner}/${repo} --comment "Merged via Panopticon lifecycle"`,
6508
6574
  { encoding: "utf-8" }
6509
6575
  );
@@ -6512,8 +6578,14 @@ async function closeGitHubPr(ctx) {
6512
6578
  return stepSkipped(step, [`PR close failed (non-fatal): ${err.message}`]);
6513
6579
  }
6514
6580
  }
6581
+ var _linearRateLimitUntil = 0;
6582
+ var LINEAR_RATE_LIMIT_COOLDOWN_MS = 60 * 60 * 1e3;
6515
6583
  async function closeLinearDirect(ctx, apiKey) {
6516
6584
  const step = "close-issue:transition";
6585
+ if (Date.now() < _linearRateLimitUntil) {
6586
+ const remainingMin = Math.ceil((_linearRateLimitUntil - Date.now()) / 6e4);
6587
+ return stepFailed(step, `Linear rate limit cooldown active (${remainingMin}min remaining). Issue will be closed during close-out ceremony.`);
6588
+ }
6517
6589
  try {
6518
6590
  const { LinearClient: LinearClient2 } = await import("@linear/sdk");
6519
6591
  const client = new LinearClient2({ apiKey });
@@ -6540,7 +6612,13 @@ async function closeLinearDirect(ctx, apiKey) {
6540
6612
  }
6541
6613
  return stepOk(step, [`Moved Linear issue ${ctx.issueId} to Done`]);
6542
6614
  } catch (err) {
6543
- return stepFailed(step, `Linear close failed: ${err.message}`);
6615
+ const message = err.message;
6616
+ if (message.includes("Rate limit") || message.includes("rate limit") || message.includes("429")) {
6617
+ _linearRateLimitUntil = Date.now() + LINEAR_RATE_LIMIT_COOLDOWN_MS;
6618
+ console.warn(`[close-issue] Linear rate limit hit \u2014 circuit breaker activated for 1 hour`);
6619
+ return stepFailed(step, `Linear rate limit exceeded. Circuit breaker activated \u2014 no Linear API calls for 1 hour. Issue will be closed during close-out ceremony.`);
6620
+ }
6621
+ return stepFailed(step, `Linear close failed: ${message}`);
6544
6622
  }
6545
6623
  }
6546
6624
  async function closeRallyDirect(ctx) {
@@ -6595,16 +6673,16 @@ async function applyLabelGitHub(ctx) {
6595
6673
  if (!ctx.github) return stepSkipped(step);
6596
6674
  const { owner, repo, number } = ctx.github;
6597
6675
  try {
6598
- await execAsync6(
6676
+ await execAsync7(
6599
6677
  `gh label create "${CLOSED_OUT_LABEL}" --repo ${owner}/${repo} --color "${CLOSED_OUT_COLOR}" --description "Verified and closed out" --force 2>/dev/null || true`,
6600
6678
  { encoding: "utf-8" }
6601
6679
  );
6602
- await execAsync6(
6680
+ await execAsync7(
6603
6681
  `gh issue edit ${number} --repo ${owner}/${repo} --add-label "${CLOSED_OUT_LABEL}"`,
6604
6682
  { encoding: "utf-8" }
6605
6683
  );
6606
6684
  for (const label of WORKFLOW_LABELS2) {
6607
- await execAsync6(
6685
+ await execAsync7(
6608
6686
  `gh issue edit ${number} --repo ${owner}/${repo} --remove-label "${label}" 2>/dev/null || true`,
6609
6687
  { encoding: "utf-8" }
6610
6688
  );
@@ -6661,9 +6739,9 @@ init_paths();
6661
6739
  init_tmux();
6662
6740
  import { existsSync as existsSync28, rmSync as rmSync3, unlinkSync as unlinkSync2 } from "fs";
6663
6741
  import { join as join27, basename as basename5 } from "path";
6664
- import { exec as exec7 } from "child_process";
6665
- import { promisify as promisify7 } from "util";
6666
- var execAsync7 = promisify7(exec7);
6742
+ import { exec as exec8 } from "child_process";
6743
+ import { promisify as promisify8 } from "util";
6744
+ var execAsync8 = promisify8(exec8);
6667
6745
  async function killTmuxSessions(issueLower) {
6668
6746
  const step = "teardown:tmux-sessions";
6669
6747
  const patterns = [
@@ -6677,7 +6755,7 @@ async function killTmuxSessions(issueLower) {
6677
6755
  for (const session of patterns) {
6678
6756
  if (sessionExists(session)) {
6679
6757
  try {
6680
- await execAsync7(`tmux kill-session -t ${session}`);
6758
+ await execAsync8(`tmux kill-session -t ${session}`);
6681
6759
  killed++;
6682
6760
  } catch {
6683
6761
  }
@@ -6706,8 +6784,8 @@ async function stopTldrDaemon(workspacePath) {
6706
6784
  async function stopDocker(workspacePath, projectName, issueLower) {
6707
6785
  const step = "teardown:docker";
6708
6786
  try {
6709
- const { stopWorkspaceDocker: stopWorkspaceDocker2 } = await import("../workspace-manager-E434Z45T.js");
6710
- await stopWorkspaceDocker2(workspacePath, projectName, issueLower);
6787
+ const { stopWorkspaceDocker } = await import("../workspace-manager-ALBR62AS.js");
6788
+ await stopWorkspaceDocker(workspacePath, projectName, issueLower);
6711
6789
  return stepOk(step, ["Stopped Docker containers"]);
6712
6790
  } catch {
6713
6791
  return stepSkipped(step, ["Docker cleanup skipped (not running or failed)"]);
@@ -6719,7 +6797,7 @@ async function removeWorktree(projectPath, workspacePath) {
6719
6797
  return stepSkipped(step, ["Workspace directory does not exist"]);
6720
6798
  }
6721
6799
  try {
6722
- await execAsync7(`git worktree remove "${workspacePath}" --force`, { cwd: projectPath });
6800
+ await execAsync8(`git worktree remove "${workspacePath}" --force`, { cwd: projectPath });
6723
6801
  return stepOk(step, ["Removed git worktree"]);
6724
6802
  } catch {
6725
6803
  try {
@@ -6753,13 +6831,13 @@ async function deleteBranches(projectPath, issueLower) {
6753
6831
  const branchName = `feature/${issueLower}`;
6754
6832
  const details = [];
6755
6833
  try {
6756
- await execAsync7(`git branch -D "${branchName}"`, { cwd: projectPath, encoding: "utf-8" });
6834
+ await execAsync8(`git branch -D "${branchName}"`, { cwd: projectPath, encoding: "utf-8" });
6757
6835
  details.push(`Deleted local branch ${branchName}`);
6758
6836
  } catch {
6759
6837
  details.push(`Local branch ${branchName} not found (already deleted)`);
6760
6838
  }
6761
6839
  try {
6762
- await execAsync7(`git push origin --delete "${branchName}"`, { cwd: projectPath, encoding: "utf-8" });
6840
+ await execAsync8(`git push origin --delete "${branchName}"`, { cwd: projectPath, encoding: "utf-8" });
6763
6841
  details.push(`Deleted remote branch ${branchName}`);
6764
6842
  } catch {
6765
6843
  details.push(`Remote branch ${branchName} not found (already deleted)`);
@@ -6816,7 +6894,7 @@ function buildPlaceholders(ctx, opts, workspacePath) {
6816
6894
  async function removeTunnelConfig(tunnelConfig, placeholders) {
6817
6895
  const step = "teardown:tunnel";
6818
6896
  try {
6819
- const { removeTunnelIngress } = await import("../tunnel-W2GZBLEV.js");
6897
+ const { removeTunnelIngress } = await import("../tunnel-BKC7KLBX.js");
6820
6898
  const result = await removeTunnelIngress(tunnelConfig, placeholders);
6821
6899
  return stepOk(step, result.steps || ["Removed tunnel ingress"]);
6822
6900
  } catch (err) {
@@ -6826,7 +6904,7 @@ async function removeTunnelConfig(tunnelConfig, placeholders) {
6826
6904
  async function removeHumeEviConfig(humeConfig, placeholders) {
6827
6905
  const step = "teardown:hume";
6828
6906
  try {
6829
- const { deleteHumeConfig } = await import("../hume-WMAUBBV2.js");
6907
+ const { deleteHumeConfig } = await import("../hume-CKJJ3OUU.js");
6830
6908
  const result = await deleteHumeConfig(humeConfig, placeholders);
6831
6909
  return stepOk(step, result.steps || ["Removed Hume EVI config"]);
6832
6910
  } catch (err) {
@@ -6874,18 +6952,18 @@ async function teardownWorkspace(ctx, opts = {}) {
6874
6952
 
6875
6953
  // src/lib/lifecycle/compact-beads.ts
6876
6954
  init_esm_shims();
6877
- import { exec as exec8 } from "child_process";
6878
- import { promisify as promisify8 } from "util";
6879
- var execAsync8 = promisify8(exec8);
6955
+ import { exec as exec9 } from "child_process";
6956
+ import { promisify as promisify9 } from "util";
6957
+ var execAsync9 = promisify9(exec9);
6880
6958
 
6881
6959
  // src/lib/lifecycle/workflows.ts
6882
6960
  init_esm_shims();
6883
6961
  init_paths();
6884
6962
  import { existsSync as existsSync29, readFileSync as readFileSync25 } from "fs";
6885
6963
  import { join as join28 } from "path";
6886
- import { exec as exec9 } from "child_process";
6887
- import { promisify as promisify9 } from "util";
6888
- var execAsync9 = promisify9(exec9);
6964
+ import { exec as exec10 } from "child_process";
6965
+ import { promisify as promisify10 } from "util";
6966
+ var execAsync10 = promisify10(exec10);
6889
6967
  function buildResult(workflow, issueId, steps, startTime) {
6890
6968
  return {
6891
6969
  workflow,
@@ -6928,7 +7006,7 @@ async function verifyBranchMerged(ctx) {
6928
7006
  const branchName = `feature/${issueLower}`;
6929
7007
  try {
6930
7008
  try {
6931
- const { loadReviewStatuses: loadReviewStatuses3 } = await import("../review-status-TDPSOU5J.js");
7009
+ const { loadReviewStatuses: loadReviewStatuses3 } = await import("../review-status-XKUKZF6J.js");
6932
7010
  const statuses = loadReviewStatuses3();
6933
7011
  const issueKey = ctx.issueId.toUpperCase();
6934
7012
  if (statuses[issueKey]?.mergeStatus === "merged") {
@@ -6936,19 +7014,19 @@ async function verifyBranchMerged(ctx) {
6936
7014
  }
6937
7015
  } catch {
6938
7016
  }
6939
- const { stdout: branchExists } = await execAsync9(
7017
+ const { stdout: branchExists } = await execAsync10(
6940
7018
  `git branch --list "${branchName}" 2>/dev/null || true`,
6941
7019
  { cwd: ctx.projectPath, encoding: "utf-8" }
6942
7020
  );
6943
7021
  if (branchExists.trim()) {
6944
7022
  try {
6945
- await execAsync9(
7023
+ await execAsync10(
6946
7024
  `git merge-base --is-ancestor ${branchName} main`,
6947
7025
  { cwd: ctx.projectPath, encoding: "utf-8" }
6948
7026
  );
6949
7027
  return stepOk(step, ["All commits merged to main"]);
6950
7028
  } catch {
6951
- const { stdout: unmerged } = await execAsync9(
7029
+ const { stdout: unmerged } = await execAsync10(
6952
7030
  `git log main..${branchName} --oneline 2>/dev/null || true`,
6953
7031
  { cwd: ctx.projectPath, encoding: "utf-8" }
6954
7032
  );
@@ -6956,21 +7034,21 @@ async function verifyBranchMerged(ctx) {
6956
7034
  return stepFailed(step, `${count} unmerged commit(s) on ${branchName}. Merge before closing out.`);
6957
7035
  }
6958
7036
  }
6959
- const { stdout: remoteBranch } = await execAsync9(
7037
+ const { stdout: remoteBranch } = await execAsync10(
6960
7038
  `git ls-remote --heads origin "${branchName}" 2>/dev/null || true`,
6961
7039
  { cwd: ctx.projectPath, encoding: "utf-8" }
6962
7040
  );
6963
7041
  if (remoteBranch.trim()) {
6964
- await execAsync9(`git fetch origin ${branchName}`, { cwd: ctx.projectPath }).catch(() => {
7042
+ await execAsync10(`git fetch origin ${branchName}`, { cwd: ctx.projectPath }).catch(() => {
6965
7043
  });
6966
7044
  try {
6967
- await execAsync9(
7045
+ await execAsync10(
6968
7046
  `git merge-base --is-ancestor origin/${branchName} main`,
6969
7047
  { cwd: ctx.projectPath, encoding: "utf-8" }
6970
7048
  );
6971
7049
  return stepOk(step, ["Remote branch fully merged"]);
6972
7050
  } catch {
6973
- const { stdout: remoteUnmerged } = await execAsync9(
7051
+ const { stdout: remoteUnmerged } = await execAsync10(
6974
7052
  `git log main..origin/${branchName} --oneline 2>/dev/null || true`,
6975
7053
  { cwd: ctx.projectPath, encoding: "utf-8" }
6976
7054
  );
@@ -6986,7 +7064,7 @@ async function verifyBranchMerged(ctx) {
6986
7064
  async function clearReviewStatusStep(issueId) {
6987
7065
  const step = "clear-review-status";
6988
7066
  try {
6989
- const { clearReviewStatus: clearReviewStatus2 } = await import("../review-status-TDPSOU5J.js");
7067
+ const { clearReviewStatus: clearReviewStatus2 } = await import("../review-status-XKUKZF6J.js");
6990
7068
  clearReviewStatus2(issueId.toUpperCase());
6991
7069
  return stepOk(step, ["Review status cleared"]);
6992
7070
  } catch {
@@ -7010,6 +7088,20 @@ async function clearReviewStatusStep(issueId) {
7010
7088
 
7011
7089
  // src/cli/commands/work/close-out.ts
7012
7090
  init_projects();
7091
+ function getGitHubConfig2() {
7092
+ const envFile = join29(homedir14(), ".panopticon.env");
7093
+ if (!existsSync30(envFile)) return null;
7094
+ const content = readFileSync26(envFile, "utf-8");
7095
+ const reposMatch = content.match(/GITHUB_REPOS=(.+)/);
7096
+ if (!reposMatch) return null;
7097
+ const repoStr = reposMatch[1].trim();
7098
+ const parts = repoStr.split(",")[0];
7099
+ if (!parts) return null;
7100
+ const [ownerRepo, prefix] = parts.split(":");
7101
+ const [owner, repo] = ownerRepo.split("/");
7102
+ if (!owner || !repo) return null;
7103
+ return { owner, repo, prefix: prefix || repo.toUpperCase().replace(/-CLI$/, "").replace(/-/g, "") };
7104
+ }
7013
7105
  async function closeOutCommand(issueId, options) {
7014
7106
  if (process.env.PANOPTICON_AGENT_ID) {
7015
7107
  console.error(chalk31.red("Close-out is a human-only operation. Agents cannot close out issues."));
@@ -7032,11 +7124,22 @@ async function closeOutCommand(issueId, options) {
7032
7124
  console.error(chalk31.red(`Could not resolve project for ${issueId}`));
7033
7125
  process.exit(1);
7034
7126
  }
7035
- const ghResolution = resolveGitHubIssue(issueId);
7036
- const isGitHub = ghResolution.isGitHub;
7037
- const owner = ghResolution.isGitHub ? ghResolution.owner : void 0;
7038
- const repo = ghResolution.isGitHub ? ghResolution.repo : void 0;
7039
- const number = ghResolution.isGitHub ? ghResolution.number : void 0;
7127
+ const isGitHub = issueUpper.startsWith("PAN-");
7128
+ let owner;
7129
+ let repo;
7130
+ let number;
7131
+ if (isGitHub) {
7132
+ const ghConfig = getGitHubConfig2();
7133
+ if (ghConfig) {
7134
+ owner = ghConfig.owner;
7135
+ repo = ghConfig.repo;
7136
+ number = parseInt(issueId.replace(/^PAN-/i, ""), 10);
7137
+ } else {
7138
+ owner = "eltmon";
7139
+ repo = "panopticon-cli";
7140
+ number = parseInt(issueId.replace(/^PAN-/i, ""), 10);
7141
+ }
7142
+ }
7040
7143
  if (!options.force) {
7041
7144
  console.log(chalk31.yellow(`
7042
7145
  Close-out ceremony for ${issueUpper}
@@ -7095,13 +7198,13 @@ Running close-out for ${issueUpper}...
7095
7198
  // src/cli/commands/work/linear-states.ts
7096
7199
  init_esm_shims();
7097
7200
  import chalk32 from "chalk";
7098
- import { readFileSync as readFileSync26, existsSync as existsSync30 } from "fs";
7099
- import { homedir as homedir14 } from "os";
7100
- import { join as join29 } from "path";
7201
+ import { readFileSync as readFileSync27, existsSync as existsSync31 } from "fs";
7202
+ import { homedir as homedir15 } from "os";
7203
+ import { join as join30 } from "path";
7101
7204
  function getLinearApiKey8() {
7102
- const envFile = join29(homedir14(), ".panopticon.env");
7103
- if (existsSync30(envFile)) {
7104
- const content = readFileSync26(envFile, "utf-8");
7205
+ const envFile = join30(homedir15(), ".panopticon.env");
7206
+ if (existsSync31(envFile)) {
7207
+ const content = readFileSync27(envFile, "utf-8");
7105
7208
  const match = content.match(/LINEAR_API_KEY=(.+)/);
7106
7209
  if (match) return match[1].trim();
7107
7210
  }
@@ -7290,8 +7393,8 @@ function registerWorkCommands(program2) {
7290
7393
  init_esm_shims();
7291
7394
  import chalk33 from "chalk";
7292
7395
  import ora16 from "ora";
7293
- import { existsSync as existsSync31, writeFileSync as writeFileSync10, rmSync as rmSync4, readFileSync as readFileSync27, realpathSync } from "fs";
7294
- import { join as join30, basename as basename6 } from "path";
7396
+ import { existsSync as existsSync32, writeFileSync as writeFileSync10, rmSync as rmSync4, readFileSync as readFileSync28, realpathSync } from "fs";
7397
+ import { join as join31, basename as basename6 } from "path";
7295
7398
 
7296
7399
  // src/lib/worktree.ts
7297
7400
  init_esm_shims();
@@ -7354,13 +7457,13 @@ init_agents();
7354
7457
  init_projects();
7355
7458
  init_workspace_manager();
7356
7459
  init_config();
7357
- import { exec as exec10 } from "child_process";
7358
- import { promisify as promisify10 } from "util";
7359
- import { homedir as homedir15 } from "os";
7360
- var execAsync10 = promisify10(exec10);
7460
+ import { exec as exec11 } from "child_process";
7461
+ import { promisify as promisify11 } from "util";
7462
+ import { homedir as homedir16 } from "os";
7463
+ var execAsync11 = promisify11(exec11);
7361
7464
  async function getBeadsVersion() {
7362
7465
  try {
7363
- const { stdout } = await execAsync10("bd --version", { encoding: "utf-8" });
7466
+ const { stdout } = await execAsync11("bd --version", { encoding: "utf-8" });
7364
7467
  const match = stdout.match(/(\d+)\.(\d+)\.(\d+)/);
7365
7468
  if (match) {
7366
7469
  const [, , minor, patch] = match.map(Number);
@@ -7386,21 +7489,21 @@ async function initializeWorkspaceBeads(workspacePath, issueId) {
7386
7489
  if (beadsVersion >= 4701) {
7387
7490
  const workspaceLabel = `workspace:${issueId.toLowerCase().replace(/[^a-z0-9-]/g, "-")}`;
7388
7491
  const title = `${issueId.toUpperCase()}: Implementation`;
7389
- const { stdout } = await execAsync10(
7492
+ const { stdout } = await execAsync11(
7390
7493
  `bd create --title "${title}" --priority 1 --type task --labels "${workspaceLabel}" 2>&1`,
7391
7494
  { cwd: workspacePath, encoding: "utf-8" }
7392
7495
  );
7393
7496
  const match = stdout.match(/([a-z]+-[a-z0-9]+)/);
7394
7497
  return { success: true, beadId: match?.[1] };
7395
7498
  } else {
7396
- const beadsDir = join30(workspacePath, ".beads");
7397
- if (existsSync31(beadsDir)) {
7499
+ const beadsDir = join31(workspacePath, ".beads");
7500
+ if (existsSync32(beadsDir)) {
7398
7501
  rmSync4(beadsDir, { recursive: true, force: true });
7399
7502
  }
7400
7503
  const prefix = "workspace";
7401
- await execAsync10(`bd init --prefix ${prefix}`, { cwd: workspacePath, encoding: "utf-8" });
7504
+ await execAsync11(`bd init --prefix ${prefix}`, { cwd: workspacePath, encoding: "utf-8" });
7402
7505
  const title = `${issueId.toUpperCase()}: Implementation`;
7403
- const { stdout } = await execAsync10(
7506
+ const { stdout } = await execAsync11(
7404
7507
  `bd create --title "${title}" --priority 1 --type task --json`,
7405
7508
  { cwd: workspacePath, encoding: "utf-8" }
7406
7509
  );
@@ -7523,7 +7626,7 @@ async function createCommand(issueId, options) {
7523
7626
  const dockerFlag = options.docker ? " --docker" : "";
7524
7627
  const cmd = `${projectConfig.workspace_command} ${normalizedId}${dockerFlag}`;
7525
7628
  try {
7526
- const { stdout } = await execAsync10(cmd, {
7629
+ const { stdout } = await execAsync11(cmd, {
7527
7630
  cwd: projectConfig.path,
7528
7631
  encoding: "utf-8",
7529
7632
  timeout: options.docker ? 3e5 : 12e4
@@ -7559,8 +7662,8 @@ async function createCommand(issueId, options) {
7559
7662
  projectRoot = process.cwd();
7560
7663
  }
7561
7664
  }
7562
- const workspacesDir = join30(projectRoot, "workspaces");
7563
- const workspacePath = join30(workspacesDir, folderName);
7665
+ const workspacesDir = join31(projectRoot, "workspaces");
7666
+ const workspacePath = join31(workspacesDir, folderName);
7564
7667
  if (options.dryRun) {
7565
7668
  spinner.info("Dry run mode");
7566
7669
  console.log("");
@@ -7573,11 +7676,11 @@ async function createCommand(issueId, options) {
7573
7676
  console.log(` Branch: ${chalk33.cyan(branchName)}`);
7574
7677
  return;
7575
7678
  }
7576
- if (existsSync31(workspacePath)) {
7679
+ if (existsSync32(workspacePath)) {
7577
7680
  spinner.fail(`Workspace already exists: ${workspacePath}`);
7578
7681
  process.exit(1);
7579
7682
  }
7580
- if (!existsSync31(join30(projectRoot, ".git"))) {
7683
+ if (!existsSync32(join31(projectRoot, ".git"))) {
7581
7684
  spinner.fail("Not a git repository. Run this from the project root.");
7582
7685
  process.exit(1);
7583
7686
  }
@@ -7601,7 +7704,7 @@ async function createCommand(issueId, options) {
7601
7704
  BEAD_ID: workspaceBeadId
7602
7705
  };
7603
7706
  const claudeMd = generateClaudeMd(projectRoot, variables);
7604
- writeFileSync10(join30(workspacePath, "CLAUDE.md"), claudeMd);
7707
+ writeFileSync10(join31(workspacePath, "CLAUDE.md"), claudeMd);
7605
7708
  let skillsResult = { added: [], updated: [], skipped: [], overlayed: [] };
7606
7709
  if (options.skills !== false) {
7607
7710
  spinner.text = "Merging skills and agents...";
@@ -7611,20 +7714,20 @@ async function createCommand(issueId, options) {
7611
7714
  let dockerError;
7612
7715
  if (options.docker) {
7613
7716
  const composeLocations = [
7614
- join30(workspacePath, "docker-compose.yml"),
7615
- join30(workspacePath, "docker-compose.yaml"),
7616
- join30(workspacePath, ".devcontainer", "docker-compose.yml"),
7617
- join30(workspacePath, ".devcontainer", "docker-compose.yaml"),
7618
- join30(workspacePath, ".devcontainer", "docker-compose.devcontainer.yml"),
7619
- join30(workspacePath, ".devcontainer", "compose.yml"),
7620
- join30(workspacePath, ".devcontainer", "compose.yaml")
7717
+ join31(workspacePath, "docker-compose.yml"),
7718
+ join31(workspacePath, "docker-compose.yaml"),
7719
+ join31(workspacePath, ".devcontainer", "docker-compose.yml"),
7720
+ join31(workspacePath, ".devcontainer", "docker-compose.yaml"),
7721
+ join31(workspacePath, ".devcontainer", "docker-compose.devcontainer.yml"),
7722
+ join31(workspacePath, ".devcontainer", "compose.yml"),
7723
+ join31(workspacePath, ".devcontainer", "compose.yaml")
7621
7724
  ];
7622
- const composeFile = composeLocations.find((f) => existsSync31(f));
7725
+ const composeFile = composeLocations.find((f) => existsSync32(f));
7623
7726
  if (composeFile) {
7624
7727
  spinner.text = "Starting Docker containers...";
7625
7728
  try {
7626
- const composeDir = join30(composeFile, "..");
7627
- await execAsync10(`docker compose -f "${composeFile}" up -d --build`, {
7729
+ const composeDir = join31(composeFile, "..");
7730
+ await execAsync11(`docker compose -f "${composeFile}" up -d --build`, {
7628
7731
  cwd: composeDir,
7629
7732
  encoding: "utf-8",
7630
7733
  timeout: 3e5
@@ -7690,15 +7793,15 @@ async function listCommand2(options) {
7690
7793
  const workspaces2 = [];
7691
7794
  if (isPolyrepo && config2.workspace?.repos) {
7692
7795
  for (const repo of config2.workspace.repos) {
7693
- const repoPath = join30(config2.path, repo.path);
7694
- if (!existsSync31(join30(repoPath, ".git"))) continue;
7796
+ const repoPath = join31(config2.path, repo.path);
7797
+ if (!existsSync32(join31(repoPath, ".git"))) continue;
7695
7798
  const repoWorktrees = listWorktrees(repoPath);
7696
7799
  for (const wt of repoWorktrees) {
7697
7800
  if (wt.path.includes("/workspaces/") || wt.path.includes("\\workspaces\\")) {
7698
7801
  const parts = wt.path.split("/workspaces/");
7699
7802
  if (parts.length > 1) {
7700
7803
  const workspaceDir = parts[1].split("/")[0];
7701
- const canonicalPath = join30(config2.path, "workspaces", workspaceDir);
7804
+ const canonicalPath = join31(config2.path, "workspaces", workspaceDir);
7702
7805
  if (!workspaces2.some((w) => w.path === canonicalPath)) {
7703
7806
  workspaces2.push({ ...wt, path: canonicalPath });
7704
7807
  }
@@ -7707,7 +7810,7 @@ async function listCommand2(options) {
7707
7810
  }
7708
7811
  }
7709
7812
  } else {
7710
- if (!existsSync31(join30(config2.path, ".git"))) continue;
7813
+ if (!existsSync32(join31(config2.path, ".git"))) continue;
7711
7814
  const worktrees2 = listWorktrees(config2.path);
7712
7815
  for (const wt of worktrees2) {
7713
7816
  if (wt.path.includes("/workspaces/") || wt.path.includes("\\workspaces\\")) {
@@ -7747,7 +7850,7 @@ ${proj.projectName}
7747
7850
  return;
7748
7851
  }
7749
7852
  const projectRoot = process.cwd();
7750
- if (!existsSync31(join30(projectRoot, ".git"))) {
7853
+ if (!existsSync32(join31(projectRoot, ".git"))) {
7751
7854
  console.error(chalk33.red("Not a git repository."));
7752
7855
  if (projects.length > 0) {
7753
7856
  console.log(chalk33.dim("Tip: Use --all to list workspaces across all registered projects."));
@@ -7817,7 +7920,7 @@ async function destroyCommand(issueId, options) {
7817
7920
  spinner.text = "Running custom remove command...";
7818
7921
  const cmd = `${projectConfig.workspace_remove_command} ${normalizedId}`;
7819
7922
  try {
7820
- const { stdout } = await execAsync10(cmd, {
7923
+ const { stdout } = await execAsync11(cmd, {
7821
7924
  cwd: projectConfig.path,
7822
7925
  encoding: "utf-8",
7823
7926
  timeout: 12e4
@@ -7843,17 +7946,17 @@ async function destroyCommand(issueId, options) {
7843
7946
  projectRoot = process.cwd();
7844
7947
  }
7845
7948
  }
7846
- const workspacePath = join30(projectRoot, "workspaces", folderName);
7847
- if (!existsSync31(workspacePath)) {
7848
- const cwdPath = join30(process.cwd(), "workspaces", folderName);
7849
- if (projectRoot !== process.cwd() && existsSync31(cwdPath)) {
7949
+ const workspacePath = join31(projectRoot, "workspaces", folderName);
7950
+ if (!existsSync32(workspacePath)) {
7951
+ const cwdPath = join31(process.cwd(), "workspaces", folderName);
7952
+ if (projectRoot !== process.cwd() && existsSync32(cwdPath)) {
7850
7953
  projectRoot = process.cwd();
7851
7954
  } else {
7852
7955
  spinner.fail(`Workspace not found: ${workspacePath}`);
7853
7956
  process.exit(1);
7854
7957
  }
7855
7958
  }
7856
- const finalWorkspacePath = join30(projectRoot, "workspaces", folderName);
7959
+ const finalWorkspacePath = join31(projectRoot, "workspaces", folderName);
7857
7960
  spinner.text = "Removing git worktree...";
7858
7961
  removeWorktree2(projectRoot, finalWorkspacePath);
7859
7962
  spinner.succeed(`Workspace destroyed: ${folderName}`);
@@ -7892,7 +7995,7 @@ async function createRemoteWorkspace(issueId, normalizedId, branchName, spinner,
7892
7995
  }
7893
7996
  if (!projectId) {
7894
7997
  try {
7895
- const { stdout } = await execAsync10("git remote get-url origin", {
7998
+ const { stdout } = await execAsync11("git remote get-url origin", {
7896
7999
  cwd: projectRoot,
7897
8000
  encoding: "utf-8"
7898
8001
  });
@@ -7918,7 +8021,7 @@ async function createRemoteWorkspace(issueId, normalizedId, branchName, spinner,
7918
8021
  await exe.createVm(vmName);
7919
8022
  let repoUrl = "";
7920
8023
  try {
7921
- const { stdout } = await execAsync10("git remote get-url origin", {
8024
+ const { stdout } = await execAsync11("git remote get-url origin", {
7922
8025
  cwd: projectRoot,
7923
8026
  encoding: "utf-8"
7924
8027
  });
@@ -7939,13 +8042,13 @@ async function createRemoteWorkspace(issueId, normalizedId, branchName, spinner,
7939
8042
  await exe.ssh(vmName, `ssh-keyscan -t ed25519,rsa ${gitHost} >> ~/.ssh/known_hosts 2>/dev/null`);
7940
8043
  }
7941
8044
  const sshKeyPaths = [
7942
- join30(homedir15(), ".panopticon", "ssh", "exe-dev-key"),
7943
- join30(homedir15(), ".ssh", "id_ed25519"),
7944
- join30(homedir15(), ".ssh", "id_rsa")
8045
+ join31(homedir16(), ".panopticon", "ssh", "exe-dev-key"),
8046
+ join31(homedir16(), ".ssh", "id_ed25519"),
8047
+ join31(homedir16(), ".ssh", "id_rsa")
7945
8048
  ];
7946
- const sshKeyPath = sshKeyPaths.find((p) => existsSync31(p));
8049
+ const sshKeyPath = sshKeyPaths.find((p) => existsSync32(p));
7947
8050
  if (sshKeyPath) {
7948
- const sshKeyBase64 = Buffer.from(readFileSync27(sshKeyPath, "utf-8")).toString("base64");
8051
+ const sshKeyBase64 = Buffer.from(readFileSync28(sshKeyPath, "utf-8")).toString("base64");
7949
8052
  const keyFilename = sshKeyPath.includes("id_rsa") ? "id_rsa" : "id_ed25519";
7950
8053
  await exe.ssh(vmName, `echo '${sshKeyBase64}' | base64 -d > ~/.ssh/${keyFilename} && chmod 600 ~/.ssh/${keyFilename}`);
7951
8054
  }
@@ -7961,11 +8064,11 @@ async function createRemoteWorkspace(issueId, normalizedId, branchName, spinner,
7961
8064
  await exe.ssh(vmName, "mkdir -p ~/workspace");
7962
8065
  for (const repo of projectConfig.workspace.repos) {
7963
8066
  spinner.text = `Cloning ${repo.name}...`;
7964
- const rawRepoPath = join30(projectRoot, repo.path);
7965
- const actualRepoPath = existsSync31(rawRepoPath) ? realpathSync(rawRepoPath) : rawRepoPath;
8067
+ const rawRepoPath = join31(projectRoot, repo.path);
8068
+ const actualRepoPath = existsSync32(rawRepoPath) ? realpathSync(rawRepoPath) : rawRepoPath;
7966
8069
  let repoRemoteUrl;
7967
8070
  try {
7968
- const { stdout } = await execAsync10("git remote get-url origin", {
8071
+ const { stdout } = await execAsync11("git remote get-url origin", {
7969
8072
  cwd: actualRepoPath,
7970
8073
  encoding: "utf-8"
7971
8074
  });
@@ -8001,7 +8104,7 @@ async function createRemoteWorkspace(issueId, normalizedId, branchName, spinner,
8001
8104
  spinner.text = "Configuring Claude Code...";
8002
8105
  await exe.ssh(vmName, `mkdir -p ~/.claude`);
8003
8106
  try {
8004
- const { stdout: credentials } = await execAsync10(
8107
+ const { stdout: credentials } = await execAsync11(
8005
8108
  'security find-generic-password -s "Claude Code-credentials" -w 2>/dev/null',
8006
8109
  { encoding: "utf-8" }
8007
8110
  );
@@ -8153,8 +8256,8 @@ async function migrateCommand(issueId, options) {
8153
8256
  }
8154
8257
  spinner.text = "Syncing beads...";
8155
8258
  try {
8156
- await execAsync10("bd sync", { encoding: "utf-8" });
8157
- await execAsync10('git add .beads/ && git commit -m "Sync beads before migration" && git push', { encoding: "utf-8" });
8259
+ await execAsync11("bd sync", { encoding: "utf-8" });
8260
+ await execAsync11('git add .beads/ && git commit -m "Sync beads before migration" && git push', { encoding: "utf-8" });
8158
8261
  } catch {
8159
8262
  }
8160
8263
  const branchName = `feature/${normalizedId}`;
@@ -8295,8 +8398,8 @@ async function destroyRemoteWorkspace(issueId, normalizedId, metadata, spinner,
8295
8398
  } catch {
8296
8399
  }
8297
8400
  }
8298
- const metadataFile = join30(WORKSPACES_DIR, `${normalizedId}.yaml`);
8299
- if (existsSync31(metadataFile)) {
8401
+ const metadataFile = join31(WORKSPACES_DIR, `${normalizedId}.yaml`);
8402
+ if (existsSync32(metadataFile)) {
8300
8403
  rmSync4(metadataFile);
8301
8404
  }
8302
8405
  spinner.succeed(`Remote workspace ${issueId} destroyed`);
@@ -8322,9 +8425,9 @@ async function updateCommand(issueId, options) {
8322
8425
  process.exit(1);
8323
8426
  }
8324
8427
  const workspaceConfig = projectConfig.workspace;
8325
- const workspacesDir = join30(projectConfig.path, workspaceConfig?.workspaces_dir || "workspaces");
8326
- const workspacePath = join30(workspacesDir, folderName);
8327
- if (!existsSync31(workspacePath)) {
8428
+ const workspacesDir = join31(projectConfig.path, workspaceConfig?.workspaces_dir || "workspaces");
8429
+ const workspacePath = join31(workspacesDir, folderName);
8430
+ if (!existsSync32(workspacePath)) {
8328
8431
  spinner.fail(`Workspace not found: ${workspacePath}`);
8329
8432
  process.exit(1);
8330
8433
  }
@@ -8344,7 +8447,7 @@ async function updateCommand(issueId, options) {
8344
8447
  const result = mergeSkillsIntoWorkspace(workspacePath);
8345
8448
  if (workspaceConfig?.agent?.template_dir && (workspaceConfig.agent.copy_dirs || workspaceConfig.agent.symlinks)) {
8346
8449
  spinner.text = "Applying project template overlay...";
8347
- const templateDir = join30(projectConfig.path, workspaceConfig.agent.template_dir);
8450
+ const templateDir = join31(projectConfig.path, workspaceConfig.agent.template_dir);
8348
8451
  const overlayed = applyProjectTemplateOverlay(workspacePath, templateDir);
8349
8452
  result.overlayed = overlayed;
8350
8453
  }
@@ -8376,12 +8479,12 @@ import ora17 from "ora";
8376
8479
  // src/lib/test-runner.ts
8377
8480
  init_esm_shims();
8378
8481
  init_workspace_config();
8379
- import { existsSync as existsSync32, mkdirSync as mkdirSync13, writeFileSync as writeFileSync11 } from "fs";
8380
- import { join as join31, basename as basename7 } from "path";
8381
- import { exec as exec11 } from "child_process";
8382
- import { promisify as promisify11 } from "util";
8383
- import { homedir as homedir16 } from "os";
8384
- var execAsync11 = promisify11(exec11);
8482
+ import { existsSync as existsSync33, mkdirSync as mkdirSync13, writeFileSync as writeFileSync11 } from "fs";
8483
+ import { join as join32, basename as basename7 } from "path";
8484
+ import { exec as exec12 } from "child_process";
8485
+ import { promisify as promisify12 } from "util";
8486
+ import { homedir as homedir17 } from "os";
8487
+ var execAsync12 = promisify12(exec12);
8385
8488
  function formatDuration(seconds) {
8386
8489
  const minutes = Math.floor(seconds / 60);
8387
8490
  const secs = seconds % 60;
@@ -8431,8 +8534,8 @@ function parseTestOutput(output, type) {
8431
8534
  return { passed, failed };
8432
8535
  }
8433
8536
  async function runTestSuite(testName, testConfig, workspacePath, placeholders, reportsDir, timestamp) {
8434
- const testPath = join31(workspacePath, testConfig.path);
8435
- const logFile = join31(reportsDir, `${testName}-${timestamp}.log`);
8537
+ const testPath = join32(workspacePath, testConfig.path);
8538
+ const logFile = join32(reportsDir, `${testName}-${timestamp}.log`);
8436
8539
  const result = {
8437
8540
  name: testName,
8438
8541
  status: "pending",
@@ -8455,7 +8558,7 @@ async function runTestSuite(testName, testConfig, workspacePath, placeholders, r
8455
8558
  }
8456
8559
  const startTime = Date.now();
8457
8560
  try {
8458
- const { stdout, stderr } = await execAsync11(command, {
8561
+ const { stdout, stderr } = await execAsync12(command, {
8459
8562
  cwd: testPath,
8460
8563
  env,
8461
8564
  timeout: 6e5,
@@ -8483,7 +8586,7 @@ async function runTestSuite(testName, testConfig, workspacePath, placeholders, r
8483
8586
  result.duration = formatDuration(Math.floor((endTime - startTime) / 1e3));
8484
8587
  return result;
8485
8588
  }
8486
- function generateReport(result) {
8589
+ function generateReport2(result) {
8487
8590
  const lines = [
8488
8591
  `# Test Run Report - ${result.target}`,
8489
8592
  "",
@@ -8524,10 +8627,10 @@ function generateReport(result) {
8524
8627
  async function sendNotification(result) {
8525
8628
  const title = `Tests (${result.target}): ${result.overallStatus === "passed" ? "\u2705 All Passed" : "\u274C Failed"}`;
8526
8629
  const message = result.overallStatus === "passed" ? "All test suites passed" : `${result.totalFailures} suite(s) failed. Check report: ${result.reportFile}`;
8527
- const notifyScript = join31(homedir16(), ".panopticon", "bin", "notify-complete");
8528
- if (existsSync32(notifyScript)) {
8630
+ const notifyScript = join32(homedir17(), ".panopticon", "bin", "notify-complete");
8631
+ if (existsSync33(notifyScript)) {
8529
8632
  try {
8530
- await execAsync11(`"${notifyScript}" "${result.target}" "${message}"`);
8633
+ await execAsync12(`"${notifyScript}" "${result.target}" "${message}"`);
8531
8634
  } catch {
8532
8635
  }
8533
8636
  }
@@ -8543,12 +8646,12 @@ async function runTests(options) {
8543
8646
  let target;
8544
8647
  let baseUrl;
8545
8648
  if (featureName) {
8546
- const workspacesDir = join31(projectConfig.path, workspaceConfig?.workspaces_dir || "workspaces");
8649
+ const workspacesDir = join32(projectConfig.path, workspaceConfig?.workspaces_dir || "workspaces");
8547
8650
  const featureFolder2 = `feature-${featureName}`;
8548
- workspacePath = join31(workspacesDir, featureFolder2);
8651
+ workspacePath = join32(workspacesDir, featureFolder2);
8549
8652
  target = featureFolder2;
8550
8653
  baseUrl = workspaceConfig?.dns?.domain ? `https://${featureFolder2}.${workspaceConfig.dns.domain}` : `http://localhost:3000`;
8551
- if (!existsSync32(workspacePath)) {
8654
+ if (!existsSync33(workspacePath)) {
8552
8655
  throw new Error(`Workspace not found: ${workspacePath}`);
8553
8656
  }
8554
8657
  } else {
@@ -8565,12 +8668,13 @@ async function runTests(options) {
8565
8668
  DOMAIN: workspaceConfig?.dns?.domain || "localhost",
8566
8669
  PROJECT_NAME: basename7(projectConfig.path),
8567
8670
  PROJECT_PATH: projectConfig.path,
8671
+ PROJECTS_DIR: join32(projectConfig.path, ".."),
8568
8672
  WORKSPACE_PATH: workspacePath
8569
8673
  };
8570
- const reportsDir = join31(projectConfig.path, "reports");
8674
+ const reportsDir = join32(projectConfig.path, "reports");
8571
8675
  mkdirSync13(reportsDir, { recursive: true });
8572
8676
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
8573
- const reportFile = join31(reportsDir, `test-run-${target}-${timestamp}.md`);
8677
+ const reportFile = join32(reportsDir, `test-run-${target}-${timestamp}.md`);
8574
8678
  const result = {
8575
8679
  target,
8576
8680
  baseUrl,
@@ -8608,7 +8712,7 @@ async function runTests(options) {
8608
8712
  console.log("");
8609
8713
  }
8610
8714
  result.overallStatus = result.totalFailures === 0 ? "passed" : "failed";
8611
- const report = generateReport(result);
8715
+ const report = generateReport2(result);
8612
8716
  writeFileSync11(reportFile, report);
8613
8717
  console.log("==============================================");
8614
8718
  console.log(`COMPLETE: ${result.overallStatus === "passed" ? "\u2705 ALL PASSED" : "\u274C FAILED"}`);
@@ -8734,21 +8838,21 @@ import chalk35 from "chalk";
8734
8838
  import ora18 from "ora";
8735
8839
  import inquirer5 from "inquirer";
8736
8840
  import { execSync as execSync4 } from "child_process";
8737
- import { existsSync as existsSync33, mkdirSync as mkdirSync14, writeFileSync as writeFileSync12, readFileSync as readFileSync28, copyFileSync, readdirSync as readdirSync15, statSync as statSync6 } from "fs";
8738
- import { join as join32 } from "path";
8739
- import { homedir as homedir17 } from "os";
8841
+ import { existsSync as existsSync34, mkdirSync as mkdirSync14, writeFileSync as writeFileSync12, readFileSync as readFileSync29, copyFileSync, readdirSync as readdirSync15, statSync as statSync6 } from "fs";
8842
+ import { join as join33 } from "path";
8843
+ import { homedir as homedir18 } from "os";
8740
8844
  function registerInstallCommand(program2) {
8741
8845
  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);
8742
8846
  }
8743
8847
  function copyDirectoryRecursive(source, dest) {
8744
- if (!existsSync33(source)) {
8848
+ if (!existsSync34(source)) {
8745
8849
  throw new Error(`Source directory not found: ${source}`);
8746
8850
  }
8747
8851
  mkdirSync14(dest, { recursive: true });
8748
8852
  const entries = readdirSync15(source);
8749
8853
  for (const entry of entries) {
8750
- const sourcePath = join32(source, entry);
8751
- const destPath = join32(dest, entry);
8854
+ const sourcePath = join33(source, entry);
8855
+ const destPath = join33(dest, entry);
8752
8856
  const stat = statSync6(sourcePath);
8753
8857
  if (stat.isDirectory()) {
8754
8858
  copyDirectoryRecursive(sourcePath, destPath);
@@ -8848,7 +8952,7 @@ function checkPrerequisites() {
8848
8952
  message: hasJq ? "installed" : "not found",
8849
8953
  fix: "apt install jq / brew install jq"
8850
8954
  });
8851
- const hasTtyd = checkCommand2("ttyd") || existsSync33(join32(homedir17(), "bin", "ttyd"));
8955
+ const hasTtyd = checkCommand2("ttyd") || existsSync34(join33(homedir18(), "bin", "ttyd"));
8852
8956
  results.push({
8853
8957
  name: "ttyd",
8854
8958
  passed: hasTtyd,
@@ -8924,9 +9028,9 @@ async function installCommand(options) {
8924
9028
  execSync4("brew install mkcert", { stdio: "pipe", timeout: 12e4 });
8925
9029
  spinner.succeed("mkcert installed via Homebrew");
8926
9030
  } else {
8927
- const binDir = join32(homedir17(), ".local", "bin");
9031
+ const binDir = join33(homedir18(), ".local", "bin");
8928
9032
  mkdirSync14(binDir, { recursive: true });
8929
- const mkcertPath = join32(binDir, "mkcert");
9033
+ const mkcertPath = join33(binDir, "mkcert");
8930
9034
  const arch = process.arch === "x64" ? "amd64" : process.arch;
8931
9035
  execSync4(`curl -sL "https://github.com/FiloSottile/mkcert/releases/latest/download/mkcert-v1.4.4-linux-${arch}" -o "${mkcertPath}" && chmod +x "${mkcertPath}"`, {
8932
9036
  stdio: "pipe",
@@ -8945,14 +9049,14 @@ async function installCommand(options) {
8945
9049
  execSync4("mkcert -install", { stdio: "pipe" });
8946
9050
  spinner.succeed("mkcert CA installed");
8947
9051
  spinner.start("Generating wildcard certificates...");
8948
- const traefikCertFile = join32(TRAEFIK_CERTS_DIR, "_wildcard.pan.localhost.pem");
8949
- const traefikKeyFile = join32(TRAEFIK_CERTS_DIR, "_wildcard.pan.localhost-key.pem");
9052
+ const traefikCertFile = join33(TRAEFIK_CERTS_DIR, "_wildcard.pan.localhost.pem");
9053
+ const traefikKeyFile = join33(TRAEFIK_CERTS_DIR, "_wildcard.pan.localhost-key.pem");
8950
9054
  execSync4(
8951
9055
  `mkcert -cert-file "${traefikCertFile}" -key-file "${traefikKeyFile}" "pan.localhost" "*.pan.localhost" "*.localhost" localhost 127.0.0.1 ::1`,
8952
9056
  { stdio: "pipe" }
8953
9057
  );
8954
- const legacyCertFile = join32(CERTS_DIR, "localhost.pem");
8955
- const legacyKeyFile = join32(CERTS_DIR, "localhost-key.pem");
9058
+ const legacyCertFile = join33(CERTS_DIR, "localhost.pem");
9059
+ const legacyKeyFile = join33(CERTS_DIR, "localhost-key.pem");
8956
9060
  copyFileSync(traefikCertFile, legacyCertFile);
8957
9061
  copyFileSync(traefikKeyFile, legacyKeyFile);
8958
9062
  spinner.succeed("Wildcard certificates generated (*.pan.localhost, *.localhost)");
@@ -8970,13 +9074,13 @@ async function installCommand(options) {
8970
9074
  spinner.info("Skipping mkcert (not installed)");
8971
9075
  }
8972
9076
  }
8973
- const hasTtyd = checkCommand2("ttyd") || existsSync33(join32(homedir17(), "bin", "ttyd"));
9077
+ const hasTtyd = checkCommand2("ttyd") || existsSync34(join33(homedir18(), "bin", "ttyd"));
8974
9078
  if (!hasTtyd) {
8975
9079
  spinner.start("Installing ttyd (web terminal)...");
8976
9080
  try {
8977
- const binDir = join32(homedir17(), "bin");
9081
+ const binDir = join33(homedir18(), "bin");
8978
9082
  mkdirSync14(binDir, { recursive: true });
8979
- const ttydPath = join32(binDir, "ttyd");
9083
+ const ttydPath = join33(binDir, "ttyd");
8980
9084
  const plat2 = detectPlatform();
8981
9085
  let downloadUrl = "";
8982
9086
  if (plat2 === "darwin") {
@@ -9088,9 +9192,9 @@ async function installCommand(options) {
9088
9192
  if (!hasOxNow) {
9089
9193
  spinner.start("Installing SageOx CLI (ox)...");
9090
9194
  try {
9091
- const binDir = join32(homedir17(), ".local", "bin");
9195
+ const binDir = join33(homedir18(), ".local", "bin");
9092
9196
  mkdirSync14(binDir, { recursive: true });
9093
- const oxPath = join32(binDir, "ox");
9197
+ const oxPath = join33(binDir, "ox");
9094
9198
  const arch = process.arch === "x64" ? "amd64" : process.arch;
9095
9199
  const plat2 = detectPlatform();
9096
9200
  const platform2 = plat2 === "darwin" ? "darwin" : "linux";
@@ -9109,7 +9213,7 @@ async function installCommand(options) {
9109
9213
  if (!options.minimal) {
9110
9214
  spinner.start("Setting up Traefik configuration...");
9111
9215
  try {
9112
- if (!existsSync33(join32(TRAEFIK_DIR, "docker-compose.yml"))) {
9216
+ if (!existsSync34(join33(TRAEFIK_DIR, "docker-compose.yml"))) {
9113
9217
  copyDirectoryRecursive(SOURCE_TRAEFIK_TEMPLATES, TRAEFIK_DIR);
9114
9218
  cleanupTemplateFiles();
9115
9219
  spinner.succeed("Traefik configuration created from templates");
@@ -9122,9 +9226,9 @@ async function installCommand(options) {
9122
9226
  if (generateTlsConfig()) {
9123
9227
  spinner.succeed("TLS config generated (tls.yml)");
9124
9228
  }
9125
- const existingCompose = join32(TRAEFIK_DIR, "docker-compose.yml");
9126
- if (existsSync33(existingCompose)) {
9127
- const content = readFileSync28(existingCompose, "utf-8");
9229
+ const existingCompose = join33(TRAEFIK_DIR, "docker-compose.yml");
9230
+ if (existsSync34(existingCompose)) {
9231
+ const content = readFileSync29(existingCompose, "utf-8");
9128
9232
  if (content.includes("panopticon:") && !content.includes("external: true")) {
9129
9233
  const patched = content.replace(
9130
9234
  /networks:\s*\n\s*panopticon:\s*\n\s*name: panopticon\s*\n\s*driver: bridge/,
@@ -9139,8 +9243,8 @@ async function installCommand(options) {
9139
9243
  console.log(chalk35.yellow("You can set up Traefik manually later"));
9140
9244
  }
9141
9245
  }
9142
- const configFile = join32(PANOPTICON_HOME, "config.toml");
9143
- const configExists = existsSync33(configFile);
9246
+ const configFile = join33(PANOPTICON_HOME, "config.toml");
9247
+ const configExists = existsSync34(configFile);
9144
9248
  if (!configExists) {
9145
9249
  spinner.start("Creating default config...");
9146
9250
  } else {
@@ -9348,82 +9452,27 @@ function getHealthLabel(state) {
9348
9452
  }
9349
9453
  }
9350
9454
 
9351
- // src/lib/cloister/database.ts
9455
+ // src/lib/database/health-events-db.ts
9352
9456
  init_esm_shims();
9353
- init_paths();
9354
- import Database from "better-sqlite3";
9355
- import { join as join33 } from "path";
9356
- import { existsSync as existsSync34, mkdirSync as mkdirSync15 } from "fs";
9357
- var CLOISTER_DB_PATH = join33(PANOPTICON_HOME, "cloister.db");
9358
- var RETENTION_DAYS = 7;
9359
- var db = null;
9360
- function initHealthDatabase() {
9361
- if (!existsSync34(PANOPTICON_HOME)) {
9362
- mkdirSync15(PANOPTICON_HOME, { recursive: true });
9363
- }
9364
- db = new Database(CLOISTER_DB_PATH);
9365
- db.pragma("journal_mode = WAL");
9366
- db.exec(`
9367
- CREATE TABLE IF NOT EXISTS health_events (
9368
- id INTEGER PRIMARY KEY AUTOINCREMENT,
9369
- agent_id TEXT NOT NULL,
9370
- timestamp TEXT NOT NULL,
9371
- state TEXT NOT NULL,
9372
- previous_state TEXT,
9373
- source TEXT,
9374
- metadata TEXT
9375
- );
9376
-
9377
- CREATE INDEX IF NOT EXISTS idx_agent_timestamp
9378
- ON health_events(agent_id, timestamp);
9379
-
9380
- CREATE INDEX IF NOT EXISTS idx_timestamp
9381
- ON health_events(timestamp);
9382
- `);
9383
- cleanupOldEvents(db);
9384
- return db;
9385
- }
9386
- function getHealthDatabase() {
9387
- if (!db) {
9388
- return initHealthDatabase();
9389
- }
9390
- return db;
9391
- }
9392
- function closeHealthDatabase() {
9393
- if (db) {
9394
- db.close();
9395
- db = null;
9396
- }
9397
- }
9457
+ init_database();
9398
9458
  function writeHealthEvent(event) {
9399
- const database = getHealthDatabase();
9400
- const stmt = database.prepare(`
9459
+ const db = getDatabase();
9460
+ const result = db.prepare(`
9401
9461
  INSERT INTO health_events (agent_id, timestamp, state, previous_state, source, metadata)
9402
9462
  VALUES (?, ?, ?, ?, ?, ?)
9403
- `);
9404
- const result = stmt.run(
9463
+ `).run(
9405
9464
  event.agentId,
9406
9465
  event.timestamp,
9407
9466
  event.state,
9408
- event.previousState || null,
9409
- event.source || null,
9410
- event.metadata || null
9467
+ event.previousState ?? null,
9468
+ event.source ?? null,
9469
+ event.metadata ?? null
9411
9470
  );
9412
9471
  return result.lastInsertRowid;
9413
9472
  }
9414
- function cleanupOldEvents(database = getHealthDatabase(), retentionDays = RETENTION_DAYS) {
9415
- const cutoffDate = /* @__PURE__ */ new Date();
9416
- cutoffDate.setDate(cutoffDate.getDate() - retentionDays);
9417
- const cutoffTimestamp = cutoffDate.toISOString();
9418
- const stmt = database.prepare(`
9419
- DELETE FROM health_events
9420
- WHERE timestamp < ?
9421
- `);
9422
- const result = stmt.run(cutoffTimestamp);
9423
- return result.changes;
9424
- }
9425
9473
 
9426
9474
  // src/lib/cloister/service.ts
9475
+ init_database();
9427
9476
  init_specialists();
9428
9477
 
9429
9478
  // src/lib/runtimes/index.ts
@@ -9437,10 +9486,10 @@ init_esm_shims();
9437
9486
  init_agents();
9438
9487
  init_tmux();
9439
9488
  init_jsonl_parser();
9440
- import { existsSync as existsSync35, readFileSync as readFileSync29, statSync as statSync7, mkdirSync as mkdirSync16, writeFileSync as writeFileSync13 } from "fs";
9489
+ import { existsSync as existsSync35, readFileSync as readFileSync30, statSync as statSync7, mkdirSync as mkdirSync15, writeFileSync as writeFileSync13 } from "fs";
9441
9490
  import { join as join34 } from "path";
9442
- import { homedir as homedir18 } from "os";
9443
- var CLAUDE_PROJECTS_DIR = join34(homedir18(), ".claude", "projects");
9491
+ import { homedir as homedir19 } from "os";
9492
+ var CLAUDE_PROJECTS_DIR = join34(homedir19(), ".claude", "projects");
9444
9493
  var ClaudeCodeRuntime = class {
9445
9494
  name = "claude-code";
9446
9495
  /**
@@ -9458,7 +9507,7 @@ var ClaudeCodeRuntime = class {
9458
9507
  const indexPath = join34(projectDir, "sessions-index.json");
9459
9508
  if (existsSync35(indexPath)) {
9460
9509
  try {
9461
- const indexContent = readFileSync29(indexPath, "utf-8");
9510
+ const indexContent = readFileSync30(indexPath, "utf-8");
9462
9511
  if (indexContent.includes(workspace)) {
9463
9512
  return projectDir;
9464
9513
  }
@@ -9477,7 +9526,7 @@ var ClaudeCodeRuntime = class {
9477
9526
  return null;
9478
9527
  }
9479
9528
  try {
9480
- const indexContent = readFileSync29(indexPath, "utf-8");
9529
+ const indexContent = readFileSync30(indexPath, "utf-8");
9481
9530
  const index = JSON.parse(indexContent);
9482
9531
  if (index.sessions && Array.isArray(index.sessions)) {
9483
9532
  const sessions = index.sessions;
@@ -9540,12 +9589,12 @@ var ClaudeCodeRuntime = class {
9540
9589
  * Read active heartbeat file if it exists
9541
9590
  */
9542
9591
  getActiveHeartbeat(agentId) {
9543
- const heartbeatPath = join34(homedir18(), ".panopticon", "heartbeats", `${agentId}.json`);
9592
+ const heartbeatPath = join34(homedir19(), ".panopticon", "heartbeats", `${agentId}.json`);
9544
9593
  if (!existsSync35(heartbeatPath)) {
9545
9594
  return null;
9546
9595
  }
9547
9596
  try {
9548
- const content = readFileSync29(heartbeatPath, "utf-8");
9597
+ const content = readFileSync30(heartbeatPath, "utf-8");
9549
9598
  const data = JSON.parse(content);
9550
9599
  const timestamp = new Date(data.timestamp);
9551
9600
  const now = /* @__PURE__ */ new Date();
@@ -9650,7 +9699,7 @@ var ClaudeCodeRuntime = class {
9650
9699
  }
9651
9700
  await sendKeysAsync(agentId, message);
9652
9701
  const mailDir = join34(getAgentDir(agentId), "mail");
9653
- mkdirSync16(mailDir, { recursive: true });
9702
+ mkdirSync15(mailDir, { recursive: true });
9654
9703
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
9655
9704
  writeFileSync13(
9656
9705
  join34(mailDir, `${timestamp}.md`),
@@ -9668,6 +9717,7 @@ ${message}
9668
9717
  throw new Error(`Agent ${agentId} is not running`);
9669
9718
  }
9670
9719
  killSession(agentId);
9720
+ saveAgentRuntimeState(agentId, { state: "idle", lastActivity: (/* @__PURE__ */ new Date()).toISOString() });
9671
9721
  const state = getAgentState(agentId);
9672
9722
  if (state) {
9673
9723
  state.status = "stopped";
@@ -9810,9 +9860,9 @@ init_agents();
9810
9860
  // src/lib/cloister/triggers.ts
9811
9861
  init_esm_shims();
9812
9862
  init_config2();
9813
- import { exec as exec12 } from "child_process";
9814
- import { promisify as promisify12 } from "util";
9815
- var execAsync12 = promisify12(exec12);
9863
+ import { exec as exec13 } from "child_process";
9864
+ import { promisify as promisify13 } from "util";
9865
+ var execAsync13 = promisify13(exec13);
9816
9866
  function checkStuckEscalation(health, currentModel, config2) {
9817
9867
  const conf = config2 || loadCloisterConfig();
9818
9868
  const stuckConfig = conf.handoffs?.auto_triggers?.stuck_escalation;
@@ -9936,7 +9986,7 @@ async function checkTaskCompletion(issueId, config2) {
9936
9986
  };
9937
9987
  }
9938
9988
  try {
9939
- const { stdout: output } = await execAsync12(`bd list --json -l ${issueId.toLowerCase()} --status closed`, {
9989
+ const { stdout: output } = await execAsync13(`bd list --json -l ${issueId.toLowerCase()} --status closed`, {
9940
9990
  encoding: "utf-8"
9941
9991
  });
9942
9992
  const tasks = JSON.parse(output);
@@ -9944,7 +9994,7 @@ async function checkTaskCompletion(issueId, config2) {
9944
9994
  (t) => t.title.toLowerCase().includes("implement") || t.labels?.includes("implementation")
9945
9995
  );
9946
9996
  if (implementTask) {
9947
- const { stdout: openOutput } = await execAsync12(`bd list --json -l ${issueId.toLowerCase()} --status open`, {
9997
+ const { stdout: openOutput } = await execAsync13(`bd list --json -l ${issueId.toLowerCase()} --status open`, {
9948
9998
  encoding: "utf-8"
9949
9999
  });
9950
10000
  const openTasks = JSON.parse(openOutput);
@@ -9988,16 +10038,16 @@ async function checkAllTriggers(agentId, workspace, issueId, currentModel, healt
9988
10038
  // src/lib/cloister/handoff.ts
9989
10039
  init_esm_shims();
9990
10040
  init_agents();
9991
- import { writeFileSync as writeFileSync14, mkdirSync as mkdirSync17 } from "fs";
10041
+ import { writeFileSync as writeFileSync14, mkdirSync as mkdirSync16 } from "fs";
9992
10042
  import { join as join36 } from "path";
9993
10043
 
9994
10044
  // src/lib/cloister/handoff-context.ts
9995
10045
  init_esm_shims();
9996
- import { existsSync as existsSync36, readFileSync as readFileSync30 } from "fs";
10046
+ import { existsSync as existsSync36, readFileSync as readFileSync31 } from "fs";
9997
10047
  import { join as join35 } from "path";
9998
- import { exec as exec13 } from "child_process";
9999
- import { promisify as promisify13 } from "util";
10000
- var execAsync13 = promisify13(exec13);
10048
+ import { exec as exec14 } from "child_process";
10049
+ import { promisify as promisify14 } from "util";
10050
+ var execAsync14 = promisify14(exec14);
10001
10051
  async function captureHandoffContext(agentState, targetModel, reason) {
10002
10052
  const context = {
10003
10053
  issueId: agentState.issueId,
@@ -10020,11 +10070,11 @@ async function captureFiles(context, workspace) {
10020
10070
  try {
10021
10071
  const stateFile = join35(workspace, ".planning/STATE.md");
10022
10072
  if (existsSync36(stateFile)) {
10023
- context.stateFile = readFileSync30(stateFile, "utf-8");
10073
+ context.stateFile = readFileSync31(stateFile, "utf-8");
10024
10074
  }
10025
10075
  const claudeMd = join35(workspace, "CLAUDE.md");
10026
10076
  if (existsSync36(claudeMd)) {
10027
- context.claudeMd = readFileSync30(claudeMd, "utf-8");
10077
+ context.claudeMd = readFileSync31(claudeMd, "utf-8");
10028
10078
  }
10029
10079
  } catch (error) {
10030
10080
  console.error("Error capturing files:", error);
@@ -10032,17 +10082,17 @@ async function captureFiles(context, workspace) {
10032
10082
  }
10033
10083
  async function captureGitState(context, workspace) {
10034
10084
  try {
10035
- const { stdout: branch } = await execAsync13("git branch --show-current", {
10085
+ const { stdout: branch } = await execAsync14("git branch --show-current", {
10036
10086
  cwd: workspace,
10037
10087
  encoding: "utf-8"
10038
10088
  });
10039
10089
  context.gitBranch = branch.trim();
10040
- const { stdout: status } = await execAsync13("git status --porcelain", {
10090
+ const { stdout: status } = await execAsync14("git status --porcelain", {
10041
10091
  cwd: workspace,
10042
10092
  encoding: "utf-8"
10043
10093
  });
10044
10094
  context.uncommittedFiles = status.split("\n").filter((line) => line.trim()).map((line) => line.substring(3));
10045
- const { stdout: lastCommit } = await execAsync13("git log -1 --oneline", {
10095
+ const { stdout: lastCommit } = await execAsync14("git log -1 --oneline", {
10046
10096
  cwd: workspace,
10047
10097
  encoding: "utf-8"
10048
10098
  });
@@ -10054,7 +10104,7 @@ async function captureGitState(context, workspace) {
10054
10104
  async function captureBeadsTasks(context, issueId) {
10055
10105
  try {
10056
10106
  const label = issueId.toLowerCase();
10057
- const { stdout: output } = await execAsync13(`bd list --json -l ${label}`, {
10107
+ const { stdout: output } = await execAsync14(`bd list --json -l ${label}`, {
10058
10108
  encoding: "utf-8"
10059
10109
  });
10060
10110
  const tasks = JSON.parse(output);
@@ -10216,7 +10266,7 @@ async function performKillAndSpawn(state, options) {
10216
10266
  stopAgent(state.id);
10217
10267
  const prompt = buildHandoffPrompt(context, options.additionalInstructions);
10218
10268
  const handoffDir = join36(getAgentDir(state.id), "handoffs");
10219
- mkdirSync17(handoffDir, { recursive: true });
10269
+ mkdirSync16(handoffDir, { recursive: true });
10220
10270
  const handoffFile = join36(handoffDir, `handoff-${Date.now()}.md`);
10221
10271
  writeFileSync14(handoffFile, prompt);
10222
10272
  const newState = await spawnAgent({
@@ -10326,13 +10376,13 @@ function sleep(ms) {
10326
10376
  // src/lib/cloister/handoff-logger.ts
10327
10377
  init_esm_shims();
10328
10378
  init_paths();
10329
- import { existsSync as existsSync38, mkdirSync as mkdirSync18, appendFileSync as appendFileSync3, readFileSync as readFileSync31, writeFileSync as writeFileSync15 } from "fs";
10379
+ import { existsSync as existsSync38, mkdirSync as mkdirSync17, appendFileSync as appendFileSync3, readFileSync as readFileSync32, writeFileSync as writeFileSync15 } from "fs";
10330
10380
  import { join as join37 } from "path";
10331
10381
  var HANDOFF_LOG_FILE = join37(PANOPTICON_HOME, "logs", "handoffs.jsonl");
10332
10382
  function ensureLogDir() {
10333
10383
  const logDir = join37(PANOPTICON_HOME, "logs");
10334
10384
  if (!existsSync38(logDir)) {
10335
- mkdirSync18(logDir, { recursive: true });
10385
+ mkdirSync17(logDir, { recursive: true });
10336
10386
  }
10337
10387
  }
10338
10388
  function logHandoffEvent(event) {
@@ -10375,7 +10425,7 @@ function createHandoffEvent(agentId, issueId, context, trigger, success, errorMe
10375
10425
  // src/lib/cloister/fpp-violations.ts
10376
10426
  init_esm_shims();
10377
10427
  init_hooks();
10378
- import { readFileSync as readFileSync32, existsSync as existsSync39, writeFileSync as writeFileSync16, mkdirSync as mkdirSync19, unlinkSync as unlinkSync3 } from "fs";
10428
+ import { readFileSync as readFileSync33, existsSync as existsSync39, writeFileSync as writeFileSync16, mkdirSync as mkdirSync18, unlinkSync as unlinkSync3 } from "fs";
10379
10429
  import { join as join38, dirname as dirname11 } from "path";
10380
10430
  init_paths();
10381
10431
  var DEFAULT_FPP_CONFIG = {
@@ -10390,7 +10440,7 @@ function loadViolations() {
10390
10440
  return /* @__PURE__ */ new Map();
10391
10441
  }
10392
10442
  try {
10393
- const fileContent = readFileSync32(VIOLATIONS_DATA_FILE, "utf-8");
10443
+ const fileContent = readFileSync33(VIOLATIONS_DATA_FILE, "utf-8");
10394
10444
  const persisted = JSON.parse(fileContent);
10395
10445
  return new Map(persisted.violations || []);
10396
10446
  } catch (error) {
@@ -10402,14 +10452,14 @@ function saveViolations(violations) {
10402
10452
  try {
10403
10453
  const dir = dirname11(VIOLATIONS_DATA_FILE);
10404
10454
  if (!existsSync39(dir)) {
10405
- mkdirSync19(dir, { recursive: true });
10455
+ mkdirSync18(dir, { recursive: true });
10406
10456
  }
10407
10457
  const persisted = {
10408
10458
  violations: Array.from(violations.entries())
10409
10459
  };
10410
10460
  const tempFile = `${VIOLATIONS_DATA_FILE}.tmp`;
10411
10461
  writeFileSync16(tempFile, JSON.stringify(persisted, null, 2));
10412
- writeFileSync16(VIOLATIONS_DATA_FILE, readFileSync32(tempFile));
10462
+ writeFileSync16(VIOLATIONS_DATA_FILE, readFileSync33(tempFile));
10413
10463
  try {
10414
10464
  unlinkSync3(tempFile);
10415
10465
  } catch (unlinkError) {
@@ -10499,7 +10549,7 @@ function clearOldViolations(hoursOld = 24) {
10499
10549
  init_esm_shims();
10500
10550
  init_paths();
10501
10551
  init_config2();
10502
- import { readFileSync as readFileSync33, existsSync as existsSync40, writeFileSync as writeFileSync17, mkdirSync as mkdirSync20, unlinkSync as unlinkSync4 } from "fs";
10552
+ import { readFileSync as readFileSync34, existsSync as existsSync40, writeFileSync as writeFileSync17, mkdirSync as mkdirSync19, unlinkSync as unlinkSync4 } from "fs";
10503
10553
  import { join as join39, dirname as dirname12 } from "path";
10504
10554
  var COST_DATA_FILE = join39(PANOPTICON_HOME, "cost-data.json");
10505
10555
  function loadCostData() {
@@ -10512,7 +10562,7 @@ function loadCostData() {
10512
10562
  };
10513
10563
  }
10514
10564
  try {
10515
- const fileContent = readFileSync33(COST_DATA_FILE, "utf-8");
10565
+ const fileContent = readFileSync34(COST_DATA_FILE, "utf-8");
10516
10566
  const persisted = JSON.parse(fileContent);
10517
10567
  return {
10518
10568
  perAgent: new Map(Object.entries(persisted.perAgent || {})),
@@ -10534,7 +10584,7 @@ function saveCostData(data) {
10534
10584
  try {
10535
10585
  const dir = dirname12(COST_DATA_FILE);
10536
10586
  if (!existsSync40(dir)) {
10537
- mkdirSync20(dir, { recursive: true });
10587
+ mkdirSync19(dir, { recursive: true });
10538
10588
  }
10539
10589
  const persisted = {
10540
10590
  perAgent: Object.fromEntries(data.perAgent),
@@ -10544,7 +10594,7 @@ function saveCostData(data) {
10544
10594
  };
10545
10595
  const tempFile = `${COST_DATA_FILE}.tmp`;
10546
10596
  writeFileSync17(tempFile, JSON.stringify(persisted, null, 2));
10547
- writeFileSync17(COST_DATA_FILE, readFileSync33(tempFile));
10597
+ writeFileSync17(COST_DATA_FILE, readFileSync34(tempFile));
10548
10598
  try {
10549
10599
  unlinkSync4(tempFile);
10550
10600
  } catch (unlinkError) {
@@ -10666,11 +10716,11 @@ init_esm_shims();
10666
10716
  init_paths();
10667
10717
  import { writeFileSync as writeFileSync18 } from "fs";
10668
10718
  import { join as join40 } from "path";
10669
- import { exec as exec14 } from "child_process";
10670
- import { promisify as promisify14 } from "util";
10719
+ import { exec as exec15 } from "child_process";
10720
+ import { promisify as promisify15 } from "util";
10671
10721
  init_agents();
10672
10722
  init_specialists();
10673
- var execAsync14 = promisify14(exec14);
10723
+ var execAsync15 = promisify15(exec15);
10674
10724
  var SESSION_ROTATION_THRESHOLD = 1e5;
10675
10725
  var DEFAULT_MEMORY_TIERS = {
10676
10726
  recent_summary: 100,
@@ -10692,7 +10742,7 @@ async function buildMergeAgentMemory(workingDir, tiers = DEFAULT_MEMORY_TIERS) {
10692
10742
  const merges = [];
10693
10743
  try {
10694
10744
  const totalMerges = Math.max(tiers.recent_summary, tiers.recent_detailed, tiers.recent_full);
10695
- const { stdout: gitLog } = await execAsync14(
10745
+ const { stdout: gitLog } = await execAsync15(
10696
10746
  `git log --merges --format="%H|%s|%an|%ad|%D" -n ${totalMerges}`,
10697
10747
  { cwd: workingDir, encoding: "utf-8" }
10698
10748
  );
@@ -10727,14 +10777,14 @@ async function buildMergeAgentMemory(workingDir, tiers = DEFAULT_MEMORY_TIERS) {
10727
10777
  if (merge.branch) memory += `- Branch: ${merge.branch}
10728
10778
  `;
10729
10779
  try {
10730
- const { stdout: filesOutput } = await execAsync14(`git show --name-only --format= ${merge.hash}`, {
10780
+ const { stdout: filesOutput } = await execAsync15(`git show --name-only --format= ${merge.hash}`, {
10731
10781
  cwd: workingDir,
10732
10782
  encoding: "utf-8"
10733
10783
  });
10734
10784
  const files = filesOutput.trim().split("\n").filter((f) => f);
10735
10785
  memory += `- Files changed: ${files.length}
10736
10786
  `;
10737
- const { stdout: diff } = await execAsync14(`git show ${merge.hash} --stat`, {
10787
+ const { stdout: diff } = await execAsync15(`git show ${merge.hash} --stat`, {
10738
10788
  cwd: workingDir,
10739
10789
  encoding: "utf-8",
10740
10790
  maxBuffer: 10 * 1024 * 1024
@@ -10811,7 +10861,7 @@ async function rotateSpecialistSession(specialistName, workingDir) {
10811
10861
  }
10812
10862
  const tmuxSession = getTmuxSessionName(specialistName);
10813
10863
  try {
10814
- await execAsync14(`tmux kill-session -t "${tmuxSession}"`);
10864
+ await execAsync15(`tmux kill-session -t "${tmuxSession}"`);
10815
10865
  console.log(`Killed session: ${tmuxSession}`);
10816
10866
  } catch (error) {
10817
10867
  console.log(`Session ${tmuxSession} not found or already killed`);
@@ -10861,17 +10911,18 @@ init_config2();
10861
10911
  init_specialists();
10862
10912
  init_agents();
10863
10913
  init_tmux();
10864
- import { readFileSync as readFileSync35, writeFileSync as writeFileSync19, existsSync as existsSync42, mkdirSync as mkdirSync21, readdirSync as readdirSync17, statSync as statSync8, rmSync as rmSync5 } from "fs";
10914
+ import { readFileSync as readFileSync36, writeFileSync as writeFileSync19, existsSync as existsSync42, mkdirSync as mkdirSync20, readdirSync as readdirSync17, statSync as statSync8, rmSync as rmSync5 } from "fs";
10865
10915
  import { join as join41 } from "path";
10866
- import { exec as exec15 } from "child_process";
10867
- import { promisify as promisify15 } from "util";
10868
- import { homedir as homedir19 } from "os";
10869
- var execAsync15 = promisify15(exec15);
10870
- var REVIEW_STATUS_FILE = join41(homedir19(), ".panopticon", "review-status.json");
10916
+ import { exec as exec16, execFile } from "child_process";
10917
+ import { promisify as promisify16 } from "util";
10918
+ import { homedir as homedir20 } from "os";
10919
+ var execAsync16 = promisify16(exec16);
10920
+ var execFileAsync = promisify16(execFile);
10921
+ var REVIEW_STATUS_FILE = join41(homedir20(), ".panopticon", "review-status.json");
10871
10922
  function updateTestStatusToTesting(issueId) {
10872
10923
  try {
10873
10924
  if (!existsSync42(REVIEW_STATUS_FILE)) return;
10874
- const data = JSON.parse(readFileSync35(REVIEW_STATUS_FILE, "utf-8"));
10925
+ const data = JSON.parse(readFileSync36(REVIEW_STATUS_FILE, "utf-8"));
10875
10926
  const upper = issueId.toUpperCase();
10876
10927
  if (data[upper]) {
10877
10928
  data[upper].testStatus = "testing";
@@ -10905,7 +10956,7 @@ var config = { ...DEFAULT_CONFIG };
10905
10956
  function loadConfig3() {
10906
10957
  try {
10907
10958
  if (existsSync42(CONFIG_FILE2)) {
10908
- const content = readFileSync35(CONFIG_FILE2, "utf-8");
10959
+ const content = readFileSync36(CONFIG_FILE2, "utf-8");
10909
10960
  const loaded = JSON.parse(content);
10910
10961
  config = { ...DEFAULT_CONFIG, ...loaded };
10911
10962
  }
@@ -10916,14 +10967,14 @@ function loadConfig3() {
10916
10967
  }
10917
10968
  function ensureDeaconDir() {
10918
10969
  if (!existsSync42(DEACON_DIR)) {
10919
- mkdirSync21(DEACON_DIR, { recursive: true });
10970
+ mkdirSync20(DEACON_DIR, { recursive: true });
10920
10971
  }
10921
10972
  }
10922
10973
  function loadState() {
10923
10974
  ensureDeaconDir();
10924
10975
  try {
10925
10976
  if (existsSync42(STATE_FILE)) {
10926
- const content = readFileSync35(STATE_FILE, "utf-8");
10977
+ const content = readFileSync36(STATE_FILE, "utf-8");
10927
10978
  return JSON.parse(content);
10928
10979
  }
10929
10980
  } catch (error) {
@@ -10977,7 +11028,7 @@ function checkHeartbeat(name) {
10977
11028
  if (!existsSync42(heartbeatFile)) {
10978
11029
  return { isResponsive: false };
10979
11030
  }
10980
- const content = readFileSync35(heartbeatFile, "utf-8");
11031
+ const content = readFileSync36(heartbeatFile, "utf-8");
10981
11032
  const heartbeat = JSON.parse(content);
10982
11033
  const lastActivity = new Date(heartbeat.timestamp).getTime();
10983
11034
  const age = Date.now() - lastActivity;
@@ -11067,7 +11118,7 @@ async function forceKillSpecialist(name, sharedState) {
11067
11118
  };
11068
11119
  }
11069
11120
  try {
11070
- await execAsync15(`tmux kill-session -t "${tmuxSession}"`);
11121
+ await execAsync16(`tmux kill-session -t "${tmuxSession}"`);
11071
11122
  healthState.lastForceKillTime = (/* @__PURE__ */ new Date()).toISOString();
11072
11123
  healthState.forceKillCount++;
11073
11124
  healthState.consecutiveFailures = 0;
@@ -11166,7 +11217,7 @@ async function checkAndSuspendIdleAgents() {
11166
11217
  try {
11167
11218
  const sessionId = runtimeState.sessionId || `session-${agent.id}`;
11168
11219
  saveSessionId(agent.id, sessionId);
11169
- await execAsync15(`tmux kill-session -t "${agent.id}" 2>/dev/null || true`);
11220
+ await execAsync16(`tmux kill-session -t "${agent.id}" 2>/dev/null || true`);
11170
11221
  saveAgentRuntimeState(agent.id, {
11171
11222
  state: "suspended",
11172
11223
  suspendedAt: (/* @__PURE__ */ new Date()).toISOString(),
@@ -11209,7 +11260,7 @@ async function checkLazyAgent(sessionName) {
11209
11260
  if (Date.now() - lastSent < LAZY_COOLDOWN_MS) {
11210
11261
  return { isLazy: false };
11211
11262
  }
11212
- const { stdout } = await execAsync15(
11263
+ const { stdout } = await execAsync16(
11213
11264
  `tmux capture-pane -t "${sessionName}" -p -S -20 2>/dev/null || echo ""`,
11214
11265
  { encoding: "utf-8" }
11215
11266
  );
@@ -11244,11 +11295,11 @@ async function checkLazyAgent(sessionName) {
11244
11295
  }
11245
11296
  async function sendAntiLazyMessage(sessionName) {
11246
11297
  try {
11247
- await execAsync15(
11298
+ await execAsync16(
11248
11299
  `tmux send-keys -t "${sessionName}" "${ANTI_LAZY_MESSAGE.replace(/"/g, '\\"')}"`,
11249
11300
  { encoding: "utf-8" }
11250
11301
  );
11251
- await execAsync15(`tmux send-keys -t "${sessionName}" Enter`, { encoding: "utf-8" });
11302
+ await execAsync16(`tmux send-keys -t "${sessionName}" Enter`, { encoding: "utf-8" });
11252
11303
  lazyMessageCooldowns.set(sessionName, Date.now());
11253
11304
  console.log(`[deacon] Sent anti-lazy message to ${sessionName}`);
11254
11305
  return true;
@@ -11266,7 +11317,7 @@ function isIssueCompletedOrInReview(agentId) {
11266
11317
  if (!existsSync42(REVIEW_STATUS_FILE)) {
11267
11318
  return false;
11268
11319
  }
11269
- const content = readFileSync35(REVIEW_STATUS_FILE, "utf-8");
11320
+ const content = readFileSync36(REVIEW_STATUS_FILE, "utf-8");
11270
11321
  const statuses = JSON.parse(content);
11271
11322
  const status = statuses[issueId];
11272
11323
  if (!status) {
@@ -11318,7 +11369,7 @@ var ACTIVE_STATUS_PATTERNS = [
11318
11369
  ];
11319
11370
  async function isAgentActiveInTmux(sessionName) {
11320
11371
  try {
11321
- const { stdout } = await execAsync15(
11372
+ const { stdout } = await execAsync16(
11322
11373
  `tmux capture-pane -t "${sessionName}" -p -S -5 2>/dev/null || echo ""`,
11323
11374
  { encoding: "utf-8" }
11324
11375
  );
@@ -11365,7 +11416,7 @@ async function checkStuckWorkAgents() {
11365
11416
  }
11366
11417
  let tmuxOutput;
11367
11418
  try {
11368
- const { stdout } = await execAsync15(
11419
+ const { stdout } = await execAsync16(
11369
11420
  `tmux capture-pane -t "${agent.id}" -p -S -10 2>/dev/null || echo ""`,
11370
11421
  { encoding: "utf-8" }
11371
11422
  );
@@ -11386,10 +11437,10 @@ async function checkStuckWorkAgents() {
11386
11437
  console.log(`[deacon] Work agent ${agent.id} stuck thinking for ${thinkingMinutes}m (attempt ${attempts + 1})`);
11387
11438
  try {
11388
11439
  if (attempts === 0) {
11389
- await execAsync15(`tmux send-keys -t "${agent.id}" Escape 2>/dev/null || true`);
11440
+ await execAsync16(`tmux send-keys -t "${agent.id}" Escape 2>/dev/null || true`);
11390
11441
  actions.push(`Stuck recovery: sent Escape to ${agent.id} (thinking ${thinkingMinutes}m)`);
11391
11442
  } else if (attempts === 1) {
11392
- await execAsync15(`tmux send-keys -t "${agent.id}" C-c 2>/dev/null || true`);
11443
+ await execAsync16(`tmux send-keys -t "${agent.id}" C-c 2>/dev/null || true`);
11393
11444
  actions.push(`Stuck recovery: sent Ctrl+C to ${agent.id} (thinking ${thinkingMinutes}m)`);
11394
11445
  } else {
11395
11446
  const launcherPath = join41(AGENTS_DIR, agent.id, "launcher.sh");
@@ -11400,9 +11451,9 @@ async function checkStuckWorkAgents() {
11400
11451
  actions.push(`Stuck recovery failed for ${agent.id}: missing launcher or workspace`);
11401
11452
  continue;
11402
11453
  }
11403
- await execAsync15(`tmux kill-session -t "${agent.id}" 2>/dev/null || true`);
11454
+ await execAsync16(`tmux kill-session -t "${agent.id}" 2>/dev/null || true`);
11404
11455
  await new Promise((r) => setTimeout(r, 1e3));
11405
- await execAsync15(
11456
+ await execAsync16(
11406
11457
  `tmux new-session -d -s "${agent.id}" -c "${workspace}" "bash ${launcherPath}"`,
11407
11458
  { encoding: "utf-8" }
11408
11459
  );
@@ -11438,7 +11489,7 @@ async function cleanupStaleAgentState() {
11438
11489
  const agentDir = join41(AGENTS_DIR, dir.name);
11439
11490
  try {
11440
11491
  try {
11441
- await execAsync15(`tmux has-session -t "${dir.name}" 2>/dev/null`);
11492
+ await execAsync16(`tmux has-session -t "${dir.name}" 2>/dev/null`);
11442
11493
  continue;
11443
11494
  } catch {
11444
11495
  }
@@ -11484,7 +11535,7 @@ async function checkOrphanedReviewStatuses() {
11484
11535
  if (!existsSync42(REVIEW_STATUS_FILE)) {
11485
11536
  return actions;
11486
11537
  }
11487
- const content = readFileSync35(REVIEW_STATUS_FILE, "utf-8");
11538
+ const content = readFileSync36(REVIEW_STATUS_FILE, "utf-8");
11488
11539
  const statuses = JSON.parse(content);
11489
11540
  const reviewAgentSession = getTmuxSessionName("review-agent");
11490
11541
  const reviewAgentRunning = sessionExists(reviewAgentSession);
@@ -11524,6 +11575,61 @@ async function checkOrphanedReviewStatuses() {
11524
11575
  }
11525
11576
  return actions;
11526
11577
  }
11578
+ var MERGE_STUCK_STALENESS_MS = 2 * 60 * 1e3;
11579
+ var MERGE_STUCK_COOLDOWN_MS = 10 * 60 * 1e3;
11580
+ var MERGE_STUCK_MAX_ATTEMPTS = 3;
11581
+ var mergeStuckCooldowns = /* @__PURE__ */ new Map();
11582
+ var mergeReadyNotifier = null;
11583
+ async function checkReadyForMergeStuck() {
11584
+ const actions = [];
11585
+ try {
11586
+ if (!existsSync42(REVIEW_STATUS_FILE)) {
11587
+ return actions;
11588
+ }
11589
+ const content = readFileSync36(REVIEW_STATUS_FILE, "utf-8");
11590
+ const statuses = JSON.parse(content);
11591
+ const now = Date.now();
11592
+ const state = loadState();
11593
+ const attemptCounts = state.mergeStuckAttempts ?? {};
11594
+ let stateModified = false;
11595
+ for (const [key, status] of Object.entries(statuses)) {
11596
+ if (!status.readyForMerge) continue;
11597
+ if (status.mergeStatus === "merging" || status.mergeStatus === "merged" || status.mergeStatus === "failed") continue;
11598
+ if (!status.updatedAt) continue;
11599
+ const statusAge = now - new Date(status.updatedAt).getTime();
11600
+ if (statusAge < MERGE_STUCK_STALENESS_MS) continue;
11601
+ const lastAttempt = mergeStuckCooldowns.get(key);
11602
+ if (lastAttempt && now - lastAttempt < MERGE_STUCK_COOLDOWN_MS) continue;
11603
+ const attempts = attemptCounts[key] ?? 0;
11604
+ if (attempts >= MERGE_STUCK_MAX_ATTEMPTS) {
11605
+ console.log(`[deacon] Merge stuck circuit breaker active for ${key} (${attempts}/${MERGE_STUCK_MAX_ATTEMPTS} attempts)`);
11606
+ continue;
11607
+ }
11608
+ const ageMin = Math.round((now - new Date(status.updatedAt).getTime()) / 6e4);
11609
+ console.warn(`[deacon] readyForMerge stuck for ${key} (age: ${ageMin}m, attempts: ${attempts}) \u2014 merge requires manual action via MERGE button`);
11610
+ mergeStuckCooldowns.set(key, now);
11611
+ attemptCounts[key] = attempts + 1;
11612
+ stateModified = true;
11613
+ const msg = `Stuck-merge: ${key} has been readyForMerge for ${ageMin}m \u2014 click MERGE to proceed`;
11614
+ if (mergeReadyNotifier) {
11615
+ mergeReadyNotifier(status.issueId ?? key);
11616
+ actions.push(msg);
11617
+ console.log(`[deacon] merge:ready notification sent for ${key}`);
11618
+ } else {
11619
+ actions.push(msg);
11620
+ console.warn(`[deacon] No mergeReadyNotifier registered \u2014 dashboard will not be notified for ${key}`);
11621
+ }
11622
+ }
11623
+ if (stateModified) {
11624
+ state.mergeStuckAttempts = attemptCounts;
11625
+ saveState(state);
11626
+ }
11627
+ } catch (error) {
11628
+ const msg = error instanceof Error ? error.message : String(error);
11629
+ console.error("[deacon] Error in checkReadyForMergeStuck:", msg);
11630
+ }
11631
+ return actions;
11632
+ }
11527
11633
  var deadEndCooldowns = /* @__PURE__ */ new Map();
11528
11634
  var DEAD_END_STALENESS_MS = 5 * 60 * 1e3;
11529
11635
  var DEAD_END_COOLDOWN_MS = 10 * 60 * 1e3;
@@ -11533,7 +11639,7 @@ async function checkDeadEndAgents() {
11533
11639
  if (!existsSync42(REVIEW_STATUS_FILE)) {
11534
11640
  return actions;
11535
11641
  }
11536
- const content = readFileSync35(REVIEW_STATUS_FILE, "utf-8");
11642
+ const content = readFileSync36(REVIEW_STATUS_FILE, "utf-8");
11537
11643
  const statuses = JSON.parse(content);
11538
11644
  const now = Date.now();
11539
11645
  for (const [key, status] of Object.entries(statuses)) {
@@ -11601,7 +11707,7 @@ async function checkFirstCompletionAgents() {
11601
11707
  const idleMs = now - lastActivity.getTime();
11602
11708
  if (idleMs < FIRST_COMPLETION_IDLE_MS) continue;
11603
11709
  try {
11604
- const { stdout: lastLines } = await execAsync15(
11710
+ const { stdout: lastLines } = await execAsync16(
11605
11711
  `tmux capture-pane -t "${agent.id}" -p -S -3 2>/dev/null || echo ""`,
11606
11712
  { encoding: "utf-8" }
11607
11713
  );
@@ -11618,7 +11724,7 @@ async function checkFirstCompletionAgents() {
11618
11724
  const issueKey = issueId.toLowerCase();
11619
11725
  if (existsSync42(REVIEW_STATUS_FILE)) {
11620
11726
  try {
11621
- const statuses = JSON.parse(readFileSync35(REVIEW_STATUS_FILE, "utf-8"));
11727
+ const statuses = JSON.parse(readFileSync36(REVIEW_STATUS_FILE, "utf-8"));
11622
11728
  const hasStatus = statuses[issueKey] || statuses[issueId] || statuses[issueId.toUpperCase()];
11623
11729
  if (hasStatus) {
11624
11730
  console.log(`[deacon] First-completion gate: skipping ${agent.id} \u2014 has review status entry (readyForMerge=${hasStatus.readyForMerge ?? false})`);
@@ -11645,7 +11751,7 @@ async function checkFirstCompletionAgents() {
11645
11751
  if (!agentState?.workspace || !existsSync42(agentState.workspace)) continue;
11646
11752
  let hasCommits = false;
11647
11753
  try {
11648
- const { stdout: gitLog } = await execAsync15(
11754
+ const { stdout: gitLog } = await execAsync16(
11649
11755
  "git log --oneline -3 2>/dev/null",
11650
11756
  { cwd: agentState.workspace }
11651
11757
  );
@@ -11655,7 +11761,7 @@ async function checkFirstCompletionAgents() {
11655
11761
  const subdirs = readdirSync17(agentState.workspace, { withFileTypes: true }).filter((d) => d.isDirectory() && !d.name.startsWith("."));
11656
11762
  for (const sub of subdirs) {
11657
11763
  try {
11658
- const { stdout: subLog } = await execAsync15(
11764
+ const { stdout: subLog } = await execAsync16(
11659
11765
  "git log --oneline -3 2>/dev/null",
11660
11766
  { cwd: join41(agentState.workspace, sub.name) }
11661
11767
  );
@@ -11692,6 +11798,58 @@ If you still have remaining tasks, continue working on them.`;
11692
11798
  }
11693
11799
  return actions;
11694
11800
  }
11801
+ async function patrolWorkAgentResolutions() {
11802
+ const actions = [];
11803
+ try {
11804
+ const agents = listRunningAgents();
11805
+ const specialists = getEnabledSpecialists();
11806
+ const specialistNames = new Set(specialists.map((s) => getTmuxSessionName(s.name)));
11807
+ for (const agent of agents) {
11808
+ if (!agent.id.startsWith("agent-") || specialistNames.has(agent.id)) continue;
11809
+ const runtimeState = getAgentRuntimeState(agent.id);
11810
+ if (!runtimeState?.resolution || runtimeState.resolution === "working" || runtimeState.resolution === "completed") continue;
11811
+ const resolution = runtimeState.resolution;
11812
+ const count = runtimeState.resolutionCount || 0;
11813
+ const issueId = (agent.issueId || agent.id.replace("agent-", "")).toUpperCase();
11814
+ if (resolution === "done" && count >= 2) {
11815
+ console.log(`[deacon] Auto-completing ${agent.id} (${issueId}): resolution=done, count=${count}`);
11816
+ try {
11817
+ const panBin = join41(PANOPTICON_HOME, "bin", "pan");
11818
+ const binExists = existsSync42(panBin);
11819
+ const bin = binExists ? panBin : "pan";
11820
+ await execFileAsync(bin, ["work", "done", issueId, "-c", "Auto-completed by Deacon: evidence showed work complete after 2 nudges"], {
11821
+ timeout: 3e4
11822
+ });
11823
+ saveAgentRuntimeState(agent.id, {
11824
+ resolution: "completed",
11825
+ resolutionCount: count + 1,
11826
+ resolutionUpdatedAt: (/* @__PURE__ */ new Date()).toISOString()
11827
+ });
11828
+ actions.push(`Deacon auto-completed ${issueId} (${agent.id}) after ${count} failed nudges`);
11829
+ addLog("action", `Auto-completed ${issueId}: evidence-complete, ${count} nudges exhausted`, void 0);
11830
+ } catch (err) {
11831
+ const msg = err instanceof Error ? err.message : String(err);
11832
+ console.error(`[deacon] Failed to auto-complete ${agent.id}:`, msg);
11833
+ actions.push(`Deacon auto-complete failed for ${agent.id}: ${msg}`);
11834
+ }
11835
+ } else if (resolution === "stuck" && count >= 3) {
11836
+ console.log(`[deacon] Poking stuck agent ${agent.id} (${issueId}): count=${count}`);
11837
+ try {
11838
+ const pokeMsg = `Deacon health check: you appear stuck. Please check your current task status, review any errors, and continue working. If work is complete, run: pan work done ${issueId} -c "Implementation complete"`;
11839
+ await sendKeysAsync(agent.id, pokeMsg);
11840
+ actions.push(`Deacon poked stuck agent ${agent.id} (${issueId})`);
11841
+ addLog("action", `Poked stuck agent ${issueId} (count=${count})`, void 0);
11842
+ } catch (err) {
11843
+ const msg = err instanceof Error ? err.message : String(err);
11844
+ console.error(`[deacon] Failed to poke ${agent.id}:`, msg);
11845
+ }
11846
+ }
11847
+ }
11848
+ } catch (error) {
11849
+ console.error("[deacon] Error in patrolWorkAgentResolutions:", error);
11850
+ }
11851
+ return actions;
11852
+ }
11695
11853
  async function runPatrol() {
11696
11854
  const state = loadState();
11697
11855
  state.patrolCycle++;
@@ -11712,6 +11870,8 @@ async function runPatrol() {
11712
11870
  if (killResult.success) {
11713
11871
  console.log(`[deacon] Auto-restarting ${specialist.name} with fresh session...`);
11714
11872
  clearSessionId(specialist.name);
11873
+ const specialistSession2 = getTmuxSessionName(specialist.name);
11874
+ saveAgentRuntimeState(specialistSession2, { state: "idle", lastActivity: (/* @__PURE__ */ new Date()).toISOString() });
11715
11875
  const wakeResult = await wakeSpecialist(specialist.name, "", {
11716
11876
  waitForReady: true,
11717
11877
  startIfNotRunning: true
@@ -11747,7 +11907,7 @@ async function runPatrol() {
11747
11907
  if (nextTask) {
11748
11908
  console.log(`[deacon] Auto-resuming suspended ${specialist.name} for queued task: ${nextTask.payload.issueId}`);
11749
11909
  try {
11750
- const { resumeAgent } = await import("../agents-5OPQKM5K.js");
11910
+ const { resumeAgent } = await import("../agents-HNMF52RM.js");
11751
11911
  const message = `# Queued Work
11752
11912
 
11753
11913
  Processing queued task: ${nextTask.payload.issueId}`;
@@ -11801,9 +11961,15 @@ Processing queued task: ${nextTask.payload.issueId}`;
11801
11961
  const deadEndActions = await checkDeadEndAgents();
11802
11962
  actions.push(...deadEndActions);
11803
11963
  for (const a of deadEndActions) addLog("action", a, state.patrolCycle);
11964
+ const mergeStuckActions = await checkReadyForMergeStuck();
11965
+ actions.push(...mergeStuckActions);
11966
+ for (const a of mergeStuckActions) addLog("action", a, state.patrolCycle);
11804
11967
  const firstCompletionActions = await checkFirstCompletionAgents();
11805
11968
  actions.push(...firstCompletionActions);
11806
11969
  for (const a of firstCompletionActions) addLog("action", a, state.patrolCycle);
11970
+ const resolutionActions = await patrolWorkAgentResolutions();
11971
+ actions.push(...resolutionActions);
11972
+ for (const a of resolutionActions) addLog("action", a, state.patrolCycle);
11807
11973
  const lazyActions = await checkAndCorrectLazyAgents();
11808
11974
  actions.push(...lazyActions);
11809
11975
  for (const a of lazyActions) addLog("action", a, state.patrolCycle);
@@ -11821,6 +11987,38 @@ Processing queued task: ${nextTask.payload.issueId}`;
11821
11987
  actions.push(massDeathCheck.message);
11822
11988
  addLog("error", massDeathCheck.message, state.patrolCycle);
11823
11989
  }
11990
+ try {
11991
+ const projectSpecialists = await getAllProjectSpecialistStatuses();
11992
+ for (const projSpec of projectSpecialists) {
11993
+ if (!projSpec.isRunning) {
11994
+ const runtimeState2 = getAgentRuntimeState(projSpec.tmuxSession);
11995
+ if (runtimeState2?.state === "active") {
11996
+ saveAgentRuntimeState(projSpec.tmuxSession, { state: "idle", lastActivity: (/* @__PURE__ */ new Date()).toISOString() });
11997
+ const msg = `Dead-session reset: per-project ${projSpec.specialistType} (${projSpec.projectKey}) was active but session is gone`;
11998
+ actions.push(msg);
11999
+ addLog("action", msg, state.patrolCycle);
12000
+ console.log(`[deacon] ${msg}`);
12001
+ }
12002
+ continue;
12003
+ }
12004
+ const runtimeState = getAgentRuntimeState(projSpec.tmuxSession);
12005
+ const isStuck = runtimeState?.state === "active" && runtimeState.lastActivity ? Date.now() - new Date(runtimeState.lastActivity).getTime() > 15 * 60 * 1e3 : false;
12006
+ if (isStuck) {
12007
+ addLog("warn", `Per-project ${projSpec.specialistType} (${projSpec.projectKey}) stuck, force-killing`, state.patrolCycle);
12008
+ console.log(`[deacon] Per-project ${projSpec.specialistType} (${projSpec.projectKey}) stuck, force-killing ${projSpec.tmuxSession}`);
12009
+ try {
12010
+ await execAsync16(`tmux kill-session -t "${projSpec.tmuxSession}"`);
12011
+ clearSessionId(projSpec.specialistType, projSpec.projectKey);
12012
+ saveAgentRuntimeState(projSpec.tmuxSession, { state: "idle", lastActivity: (/* @__PURE__ */ new Date()).toISOString() });
12013
+ actions.push(`Force-killed stuck per-project ${projSpec.specialistType} (${projSpec.projectKey})`);
12014
+ } catch {
12015
+ }
12016
+ }
12017
+ }
12018
+ } catch (error) {
12019
+ const msg = error instanceof Error ? error.message : String(error);
12020
+ console.error("[deacon] Error during per-project specialist patrol:", msg);
12021
+ }
11824
12022
  saveState(state);
11825
12023
  const result = {
11826
12024
  cycle: state.patrolCycle,
@@ -11885,7 +12083,7 @@ function getDeaconStatus() {
11885
12083
  // src/lib/cloister/service.ts
11886
12084
  init_paths();
11887
12085
  init_paths();
11888
- import { existsSync as existsSync43, writeFileSync as writeFileSync20, unlinkSync as unlinkSync5, readFileSync as readFileSync36, readdirSync as readdirSync18, renameSync as renameSync3 } from "fs";
12086
+ import { existsSync as existsSync43, writeFileSync as writeFileSync20, unlinkSync as unlinkSync5, readFileSync as readFileSync37, readdirSync as readdirSync18, renameSync as renameSync3 } from "fs";
11889
12087
  import { join as join42 } from "path";
11890
12088
  var CLOISTER_STATE_FILE = join42(PANOPTICON_HOME, "cloister.state");
11891
12089
  function writeStateFile(running, pid) {
@@ -11908,7 +12106,7 @@ function writeStateFile(running, pid) {
11908
12106
  function readStateFile() {
11909
12107
  try {
11910
12108
  if (existsSync43(CLOISTER_STATE_FILE)) {
11911
- const data = JSON.parse(readFileSync36(CLOISTER_STATE_FILE, "utf-8"));
12109
+ const data = JSON.parse(readFileSync37(CLOISTER_STATE_FILE, "utf-8"));
11912
12110
  if (data.pid) {
11913
12111
  try {
11914
12112
  process.kill(data.pid, 0);
@@ -11958,10 +12156,10 @@ var CloisterService = class {
11958
12156
  this.starting = true;
11959
12157
  console.log("\u{1F514} Starting Cloister agent watchdog...");
11960
12158
  try {
11961
- initHealthDatabase();
11962
- console.log(" \u2713 Health history database initialized");
12159
+ getDatabase();
12160
+ console.log(" \u2713 Panopticon database initialized");
11963
12161
  } catch (error) {
11964
- console.error(" \u2717 Failed to initialize health database:", error);
12162
+ console.error(" \u2717 Failed to initialize panopticon database:", error);
11965
12163
  }
11966
12164
  try {
11967
12165
  console.log(" \u2192 Checking specialists...");
@@ -12014,9 +12212,9 @@ var CloisterService = class {
12014
12212
  console.error("Failed to stop deacon:", error);
12015
12213
  }
12016
12214
  try {
12017
- closeHealthDatabase();
12215
+ closeDatabase();
12018
12216
  } catch (error) {
12019
- console.error("Failed to close health database:", error);
12217
+ console.error("Failed to close panopticon database:", error);
12020
12218
  }
12021
12219
  this.emit({ type: "stopped" });
12022
12220
  }
@@ -12133,7 +12331,7 @@ var CloisterService = class {
12133
12331
  const processedFile = join42(AGENTS_DIR, dir.name, "completed.processed");
12134
12332
  if (!existsSync43(completedFile) || existsSync43(processedFile)) continue;
12135
12333
  try {
12136
- const content = JSON.parse(readFileSync36(completedFile, "utf-8"));
12334
+ const content = JSON.parse(readFileSync37(completedFile, "utf-8"));
12137
12335
  const ageMs = Date.now() - new Date(content.timestamp).getTime();
12138
12336
  if (ageMs > 24 * 60 * 60 * 1e3) {
12139
12337
  console.log(`\u{1F514} Cloister: Skipping stale completion marker for ${dir.name} (${Math.floor(ageMs / 36e5)}h old)`);
@@ -12790,10 +12988,10 @@ init_esm_shims();
12790
12988
  // src/cli/commands/setup/hooks.ts
12791
12989
  init_esm_shims();
12792
12990
  import chalk39 from "chalk";
12793
- import { readFileSync as readFileSync37, writeFileSync as writeFileSync21, existsSync as existsSync44, mkdirSync as mkdirSync22, copyFileSync as copyFileSync2, chmodSync } from "fs";
12991
+ import { readFileSync as readFileSync38, writeFileSync as writeFileSync21, existsSync as existsSync44, mkdirSync as mkdirSync21, copyFileSync as copyFileSync2, chmodSync } from "fs";
12794
12992
  import { join as join43 } from "path";
12795
12993
  import { execSync as execSync5 } from "child_process";
12796
- import { homedir as homedir20 } from "os";
12994
+ import { homedir as homedir21 } from "os";
12797
12995
  function checkJqInstalled() {
12798
12996
  try {
12799
12997
  execSync5("which jq", { stdio: "pipe" });
@@ -12869,15 +13067,15 @@ async function setupHooksCommand() {
12869
13067
  } else {
12870
13068
  console.log(chalk39.green("\u2713 jq is installed"));
12871
13069
  }
12872
- const panopticonHome = join43(homedir20(), ".panopticon");
13070
+ const panopticonHome = join43(homedir21(), ".panopticon");
12873
13071
  const binDir = join43(panopticonHome, "bin");
12874
13072
  const heartbeatsDir = join43(panopticonHome, "heartbeats");
12875
13073
  if (!existsSync44(binDir)) {
12876
- mkdirSync22(binDir, { recursive: true });
13074
+ mkdirSync21(binDir, { recursive: true });
12877
13075
  console.log(chalk39.green("\u2713 Created ~/.panopticon/bin/"));
12878
13076
  }
12879
13077
  if (!existsSync44(heartbeatsDir)) {
12880
- mkdirSync22(heartbeatsDir, { recursive: true });
13078
+ mkdirSync21(heartbeatsDir, { recursive: true });
12881
13079
  console.log(chalk39.green("\u2713 Created ~/.panopticon/heartbeats/"));
12882
13080
  }
12883
13081
  const hookScripts = ["pre-tool-hook", "heartbeat-hook", "stop-hook", "specialist-stop-hook", "record-cost-event.js"];
@@ -12904,12 +13102,12 @@ async function setupHooksCommand() {
12904
13102
  chmodSync(scriptDest, 493);
12905
13103
  }
12906
13104
  console.log(chalk39.green("\u2713 Installed hook scripts (pre-tool, post-tool, stop, specialist-stop)"));
12907
- const claudeDir = join43(homedir20(), ".claude");
13105
+ const claudeDir = join43(homedir21(), ".claude");
12908
13106
  const settingsPath = join43(claudeDir, "settings.json");
12909
13107
  let settings = {};
12910
13108
  if (existsSync44(settingsPath)) {
12911
13109
  try {
12912
- const settingsContent = readFileSync37(settingsPath, "utf-8");
13110
+ const settingsContent = readFileSync38(settingsPath, "utf-8");
12913
13111
  settings = JSON.parse(settingsContent);
12914
13112
  console.log(chalk39.green("\u2713 Read existing Claude Code settings"));
12915
13113
  } catch (error) {
@@ -12919,7 +13117,7 @@ async function setupHooksCommand() {
12919
13117
  } else {
12920
13118
  console.log(chalk39.dim("No existing settings.json found, creating new file"));
12921
13119
  if (!existsSync44(claudeDir)) {
12922
- mkdirSync22(claudeDir, { recursive: true });
13120
+ mkdirSync21(claudeDir, { recursive: true });
12923
13121
  }
12924
13122
  }
12925
13123
  let python3Available = false;
@@ -12936,7 +13134,7 @@ async function setupHooksCommand() {
12936
13134
  let mcpConfig = {};
12937
13135
  try {
12938
13136
  if (existsSync44(mcpPath)) {
12939
- mcpConfig = JSON.parse(readFileSync37(mcpPath, "utf-8"));
13137
+ mcpConfig = JSON.parse(readFileSync38(mcpPath, "utf-8"));
12940
13138
  }
12941
13139
  } catch {
12942
13140
  mcpConfig = {};
@@ -13127,18 +13325,18 @@ init_specialists();
13127
13325
  init_paths();
13128
13326
  init_tmux();
13129
13327
  import chalk41 from "chalk";
13130
- import { exec as exec16 } from "child_process";
13131
- import { promisify as promisify16 } from "util";
13328
+ import { exec as exec17 } from "child_process";
13329
+ import { promisify as promisify17 } from "util";
13132
13330
  import { setTimeout as sleep2 } from "timers/promises";
13133
- import { existsSync as existsSync45, mkdirSync as mkdirSync23, writeFileSync as writeFileSync22 } from "fs";
13331
+ import { existsSync as existsSync45, mkdirSync as mkdirSync22, writeFileSync as writeFileSync22 } from "fs";
13134
13332
  import { join as join44 } from "path";
13135
- var execAsync16 = promisify16(exec16);
13333
+ var execAsync17 = promisify17(exec17);
13136
13334
  var TASKS_DIR = join44(PANOPTICON_HOME, "specialists", "tasks");
13137
13335
  function sendTask(tmuxSession, specialistName, task) {
13138
13336
  const isLargeTask = task.length > 500 || task.includes("\n");
13139
13337
  if (isLargeTask) {
13140
13338
  if (!existsSync45(TASKS_DIR)) {
13141
- mkdirSync23(TASKS_DIR, { recursive: true });
13339
+ mkdirSync22(TASKS_DIR, { recursive: true });
13142
13340
  }
13143
13341
  const taskFile = join44(TASKS_DIR, `${specialistName}-${Date.now()}.md`);
13144
13342
  writeFileSync22(taskFile, task, "utf-8");
@@ -13194,7 +13392,7 @@ Waking ${status.displayName}...
13194
13392
  console.log(chalk41.dim("Starting fresh session (no previous session found)"));
13195
13393
  }
13196
13394
  console.log(chalk41.dim(`Creating tmux session: ${tmuxSession}`));
13197
- await execAsync16(
13395
+ await execAsync17(
13198
13396
  `tmux new-session -d -s "${tmuxSession}" -c "${cwd}" "${claudeCmd}"`,
13199
13397
  { encoding: "utf-8" }
13200
13398
  );
@@ -13318,10 +13516,10 @@ function getAge(date) {
13318
13516
  init_esm_shims();
13319
13517
  init_specialists();
13320
13518
  import chalk43 from "chalk";
13321
- import { exec as exec17 } from "child_process";
13322
- import { promisify as promisify17 } from "util";
13519
+ import { exec as exec18 } from "child_process";
13520
+ import { promisify as promisify18 } from "util";
13323
13521
  import * as readline from "readline";
13324
- var execAsync17 = promisify17(exec17);
13522
+ var execAsync18 = promisify18(exec18);
13325
13523
  var ALL_SPECIALISTS = ["merge-agent", "review-agent", "test-agent"];
13326
13524
  async function resetCommand(name, options) {
13327
13525
  if (options.all) {
@@ -13371,7 +13569,7 @@ Resetting ${status.displayName}...
13371
13569
  if (status.isRunning) {
13372
13570
  console.log(chalk43.dim("Stopping tmux session..."));
13373
13571
  try {
13374
- await execAsync17(`tmux kill-session -t "${status.tmuxSession}"`, { encoding: "utf-8" });
13572
+ await execAsync18(`tmux kill-session -t "${status.tmuxSession}"`, { encoding: "utf-8" });
13375
13573
  console.log(chalk43.green("\u2713 Tmux session stopped"));
13376
13574
  } catch (error) {
13377
13575
  console.log(chalk43.yellow("\u26A0 Failed to stop tmux session (may not be running)"));
@@ -13425,7 +13623,7 @@ async function resetAllSpecialists(options) {
13425
13623
  const tmuxSession = getTmuxSessionName(specialistName);
13426
13624
  if (status.isRunning) {
13427
13625
  try {
13428
- await execAsync17(`tmux kill-session -t "${tmuxSession}"`, { encoding: "utf-8" });
13626
+ await execAsync18(`tmux kill-session -t "${tmuxSession}"`, { encoding: "utf-8" });
13429
13627
  console.log(chalk43.dim(` Stopped ${specialistName} tmux session`));
13430
13628
  } catch {
13431
13629
  }
@@ -13448,7 +13646,7 @@ init_esm_shims();
13448
13646
  init_specialists();
13449
13647
  init_paths();
13450
13648
  import chalk44 from "chalk";
13451
- import { existsSync as existsSync46, readFileSync as readFileSync38, writeFileSync as writeFileSync23 } from "fs";
13649
+ import { existsSync as existsSync46, readFileSync as readFileSync39, writeFileSync as writeFileSync23 } from "fs";
13452
13650
  import { join as join45 } from "path";
13453
13651
  import * as readline2 from "readline";
13454
13652
  var ALL_SPECIALISTS2 = ["merge-agent", "review-agent", "test-agent"];
@@ -13505,7 +13703,7 @@ ${metadata.displayName} Queue:
13505
13703
  const hookFile = join45(PANOPTICON_HOME, "agents", specialistName, "hook.json");
13506
13704
  if (existsSync46(hookFile)) {
13507
13705
  try {
13508
- const hook = JSON.parse(readFileSync38(hookFile, "utf-8"));
13706
+ const hook = JSON.parse(readFileSync39(hookFile, "utf-8"));
13509
13707
  hook.items = [];
13510
13708
  hook.lastChecked = (/* @__PURE__ */ new Date()).toISOString();
13511
13709
  writeFileSync23(hookFile, JSON.stringify(hook, null, 2), "utf-8");
@@ -13518,7 +13716,7 @@ ${metadata.displayName} Queue:
13518
13716
  if (options.resetStatus && issueIds.length > 0) {
13519
13717
  if (existsSync46(REVIEW_STATUS_FILE2)) {
13520
13718
  try {
13521
- const statuses = JSON.parse(readFileSync38(REVIEW_STATUS_FILE2, "utf-8"));
13719
+ const statuses = JSON.parse(readFileSync39(REVIEW_STATUS_FILE2, "utf-8"));
13522
13720
  let resetCount = 0;
13523
13721
  for (const issueId of issueIds) {
13524
13722
  const key = Object.keys(statuses).find((k) => k.toLowerCase() === issueId.toLowerCase());
@@ -13560,14 +13758,14 @@ function confirm2(question) {
13560
13758
  // src/cli/commands/specialists/done.ts
13561
13759
  init_esm_shims();
13562
13760
  import chalk45 from "chalk";
13563
- import { existsSync as existsSync47, readFileSync as readFileSync39, writeFileSync as writeFileSync24 } from "fs";
13761
+ import { existsSync as existsSync47, readFileSync as readFileSync40, writeFileSync as writeFileSync24 } from "fs";
13564
13762
  import { join as join46 } from "path";
13565
- import { homedir as homedir21 } from "os";
13566
- var REVIEW_STATUS_FILE3 = join46(homedir21(), ".panopticon", "review-status.json");
13763
+ import { homedir as homedir22 } from "os";
13764
+ var REVIEW_STATUS_FILE3 = join46(homedir22(), ".panopticon", "review-status.json");
13567
13765
  function loadReviewStatuses2() {
13568
13766
  try {
13569
13767
  if (existsSync47(REVIEW_STATUS_FILE3)) {
13570
- return JSON.parse(readFileSync39(REVIEW_STATUS_FILE3, "utf-8"));
13768
+ return JSON.parse(readFileSync40(REVIEW_STATUS_FILE3, "utf-8"));
13571
13769
  }
13572
13770
  } catch (error) {
13573
13771
  console.error(chalk45.yellow("Warning: Could not load review statuses"));
@@ -13677,12 +13875,12 @@ function formatStatus(status) {
13677
13875
  // src/cli/commands/specialists/logs.ts
13678
13876
  init_esm_shims();
13679
13877
  import { existsSync as existsSync48 } from "fs";
13680
- import { exec as exec18 } from "child_process";
13681
- import { promisify as promisify18 } from "util";
13682
- var execAsync18 = promisify18(exec18);
13878
+ import { exec as exec19 } from "child_process";
13879
+ import { promisify as promisify19 } from "util";
13880
+ var execAsync19 = promisify19(exec19);
13683
13881
  async function listLogsCommand(project2, type, options) {
13684
13882
  try {
13685
- const { listRunLogs } = await import("../specialist-logs-XJB5TCKJ.js");
13883
+ const { listRunLogs } = await import("../specialist-logs-CJKXM3SR.js");
13686
13884
  const limit = options.limit ? parseInt(options.limit) : 10;
13687
13885
  const runs = listRunLogs(project2, type, { limit });
13688
13886
  if (options.json) {
@@ -13727,7 +13925,7 @@ View a specific run: pan specialists logs ${project2} ${type} <runId>
13727
13925
  }
13728
13926
  async function viewLogCommand(project2, type, runId, options) {
13729
13927
  try {
13730
- const { getRunLog, parseLogMetadata, getRunLogPath } = await import("../specialist-logs-XJB5TCKJ.js");
13928
+ const { getRunLog, parseLogMetadata, getRunLogPath } = await import("../specialist-logs-CJKXM3SR.js");
13731
13929
  const content = getRunLog(project2, type, runId);
13732
13930
  if (!content) {
13733
13931
  console.error(`\u274C Run log not found: ${runId}`);
@@ -13740,7 +13938,7 @@ async function viewLogCommand(project2, type, runId, options) {
13740
13938
  }
13741
13939
  const logPath = getRunLogPath(project2, type, runId);
13742
13940
  try {
13743
- await execAsync18(`less -R "${logPath}"`);
13941
+ await execAsync19(`less -R "${logPath}"`);
13744
13942
  } catch {
13745
13943
  console.log(content);
13746
13944
  }
@@ -13751,8 +13949,8 @@ async function viewLogCommand(project2, type, runId, options) {
13751
13949
  }
13752
13950
  async function tailLogCommand(project2, type) {
13753
13951
  try {
13754
- const { getRunLogPath } = await import("../specialist-logs-XJB5TCKJ.js");
13755
- const { getProjectSpecialistMetadata } = await import("../specialists-5LBRHYFA.js");
13952
+ const { getRunLogPath } = await import("../specialist-logs-CJKXM3SR.js");
13953
+ const { getProjectSpecialistMetadata } = await import("../specialists-NXYD4Z62.js");
13756
13954
  const metadata = getProjectSpecialistMetadata(project2, type);
13757
13955
  if (!metadata.currentRun) {
13758
13956
  console.error(`\u274C No active run for ${project2}/${type}`);
@@ -13821,7 +14019,7 @@ async function cleanupLogsCommand(projectOrAll, type, options) {
13821
14019
  console.log(" Use --force to confirm.");
13822
14020
  process.exit(1);
13823
14021
  }
13824
- const { cleanupAllLogs } = await import("../specialist-logs-XJB5TCKJ.js");
14022
+ const { cleanupAllLogs } = await import("../specialist-logs-CJKXM3SR.js");
13825
14023
  console.log("\u{1F9F9} Cleaning up old logs for all projects...\n");
13826
14024
  const results = cleanupAllLogs();
13827
14025
  console.log(`
@@ -13848,8 +14046,8 @@ async function cleanupLogsCommand(projectOrAll, type, options) {
13848
14046
  console.log(" Use --force to confirm.");
13849
14047
  process.exit(1);
13850
14048
  }
13851
- const { cleanupOldLogs } = await import("../specialist-logs-XJB5TCKJ.js");
13852
- const { getSpecialistRetention } = await import("../projects-CFX3RTDL.js");
14049
+ const { cleanupOldLogs } = await import("../specialist-logs-CJKXM3SR.js");
14050
+ const { getSpecialistRetention } = await import("../projects-KVM3MN3Y.js");
13853
14051
  const retention = getSpecialistRetention(projectOrAll);
13854
14052
  console.log(`\u{1F9F9} Cleaning up old logs for ${projectOrAll}/${type}...`);
13855
14053
  console.log(` Retention: ${retention.max_days} days or ${retention.max_runs} runs
@@ -13887,11 +14085,11 @@ import ora19 from "ora";
13887
14085
  // src/lib/convoy.ts
13888
14086
  init_esm_shims();
13889
14087
  init_tmux();
13890
- import { existsSync as existsSync49, mkdirSync as mkdirSync24, writeFileSync as writeFileSync25, readFileSync as readFileSync41, readdirSync as readdirSync19 } from "fs";
14088
+ import { existsSync as existsSync49, mkdirSync as mkdirSync23, writeFileSync as writeFileSync25, readFileSync as readFileSync42, readdirSync as readdirSync19 } from "fs";
13891
14089
  import { join as join47 } from "path";
13892
- import { homedir as homedir22 } from "os";
13893
- import { exec as exec19 } from "child_process";
13894
- import { promisify as promisify19 } from "util";
14090
+ import { homedir as homedir23 } from "os";
14091
+ import { exec as exec20 } from "child_process";
14092
+ import { promisify as promisify20 } from "util";
13895
14093
  import { parse as parseYaml } from "yaml";
13896
14094
 
13897
14095
  // src/lib/convoy-templates.ts
@@ -13996,8 +14194,8 @@ function getExecutionOrder(template) {
13996
14194
  // src/lib/convoy.ts
13997
14195
  init_paths();
13998
14196
  init_work_type_router();
13999
- var execAsync19 = promisify19(exec19);
14000
- var CONVOY_DIR = join47(homedir22(), ".panopticon", "convoys");
14197
+ var execAsync20 = promisify20(exec20);
14198
+ var CONVOY_DIR = join47(homedir23(), ".panopticon", "convoys");
14001
14199
  function getConvoyStateFile(convoyId) {
14002
14200
  return join47(CONVOY_DIR, `${convoyId}.json`);
14003
14201
  }
@@ -14006,7 +14204,7 @@ function getConvoyOutputDir(convoyId, template) {
14006
14204
  return join47(process.cwd(), baseDir, convoyId);
14007
14205
  }
14008
14206
  function saveConvoyState(state) {
14009
- mkdirSync24(CONVOY_DIR, { recursive: true });
14207
+ mkdirSync23(CONVOY_DIR, { recursive: true });
14010
14208
  writeFileSync25(getConvoyStateFile(state.id), JSON.stringify(state, null, 2));
14011
14209
  }
14012
14210
  function loadConvoyState(convoyId) {
@@ -14015,7 +14213,7 @@ function loadConvoyState(convoyId) {
14015
14213
  return void 0;
14016
14214
  }
14017
14215
  try {
14018
- const content = readFileSync41(stateFile, "utf-8");
14216
+ const content = readFileSync42(stateFile, "utf-8");
14019
14217
  return JSON.parse(content);
14020
14218
  } catch {
14021
14219
  return void 0;
@@ -14047,7 +14245,7 @@ function parseAgentTemplate(templatePath) {
14047
14245
  if (!existsSync49(templatePath)) {
14048
14246
  throw new Error(`Agent template not found: ${templatePath}`);
14049
14247
  }
14050
- const content = readFileSync41(templatePath, "utf-8");
14248
+ const content = readFileSync42(templatePath, "utf-8");
14051
14249
  const frontmatterMatch = content.match(/^---\n([\s\S]+?)\n---\n([\s\S]*)$/);
14052
14250
  if (!frontmatterMatch) {
14053
14251
  throw new Error(`Invalid agent template format (missing frontmatter): ${templatePath}`);
@@ -14111,7 +14309,7 @@ ${context.issueId ? `**Issue ID**: ${context.issueId}` : ""}
14111
14309
 
14112
14310
  `;
14113
14311
  prompt = contextInstructions + prompt;
14114
- mkdirSync24(convoy.outputDir, { recursive: true });
14312
+ mkdirSync23(convoy.outputDir, { recursive: true });
14115
14313
  const promptFile = join47(convoy.outputDir, `${role}-prompt.md`);
14116
14314
  writeFileSync25(promptFile, prompt);
14117
14315
  const claudeCmd = `claude --dangerously-skip-permissions --model ${model}`;
@@ -14123,10 +14321,10 @@ ${context.issueId ? `**Issue ID**: ${context.issueId}` : ""}
14123
14321
  }
14124
14322
  });
14125
14323
  await new Promise((resolve2) => setTimeout(resolve2, 1500));
14126
- await execAsync19(`tmux load-buffer "${promptFile}"`);
14127
- await execAsync19(`tmux paste-buffer -t ${agentState.tmuxSession}`);
14324
+ await execAsync20(`tmux load-buffer "${promptFile}"`);
14325
+ await execAsync20(`tmux paste-buffer -t ${agentState.tmuxSession}`);
14128
14326
  await new Promise((resolve2) => setTimeout(resolve2, 500));
14129
- await execAsync19(`tmux send-keys -t ${agentState.tmuxSession} Enter`);
14327
+ await execAsync20(`tmux send-keys -t ${agentState.tmuxSession} Enter`);
14130
14328
  agentState.status = "running";
14131
14329
  agentState.startedAt = (/* @__PURE__ */ new Date()).toISOString();
14132
14330
  saveConvoyState(convoy);
@@ -14139,7 +14337,7 @@ async function startConvoy(templateName, context) {
14139
14337
  const timestamp = Date.now();
14140
14338
  const convoyId = `convoy-${templateName}-${timestamp}`;
14141
14339
  const outputDir = getConvoyOutputDir(convoyId, template);
14142
- mkdirSync24(outputDir, { recursive: true });
14340
+ mkdirSync23(outputDir, { recursive: true });
14143
14341
  const state = {
14144
14342
  id: convoyId,
14145
14343
  template: templateName,
@@ -14187,7 +14385,7 @@ async function executePhase(convoy, template, phaseAgents, context) {
14187
14385
  for (const depRole of deps) {
14188
14386
  const depAgent = convoy.agents.find((a) => a.role === depRole);
14189
14387
  if (depAgent?.outputFile && existsSync49(depAgent.outputFile)) {
14190
- agentContext[`${depRole}_output`] = readFileSync41(depAgent.outputFile, "utf-8");
14388
+ agentContext[`${depRole}_output`] = readFileSync42(depAgent.outputFile, "utf-8");
14191
14389
  }
14192
14390
  }
14193
14391
  spawnPromises.push(spawnConvoyAgent(convoy, agent, agentState, agentContext));
@@ -14496,7 +14694,7 @@ function registerConvoyCommands(program2) {
14496
14694
  init_esm_shims();
14497
14695
  init_projects();
14498
14696
  import chalk50 from "chalk";
14499
- import { existsSync as existsSync50, readFileSync as readFileSync42, symlinkSync as symlinkSync2, mkdirSync as mkdirSync25, readdirSync as readdirSync20, statSync as statSync10 } from "fs";
14697
+ import { existsSync as existsSync50, readFileSync as readFileSync43, symlinkSync as symlinkSync2, mkdirSync as mkdirSync24, readdirSync as readdirSync20, statSync as statSync10 } from "fs";
14500
14698
  import { join as join48, resolve, dirname as dirname14 } from "path";
14501
14699
  import { fileURLToPath as fileURLToPath4 } from "url";
14502
14700
  var __filename5 = fileURLToPath4(import.meta.url);
@@ -14506,7 +14704,7 @@ function installGitHooks(gitDir) {
14506
14704
  const hooksTarget = join48(gitDir, "hooks");
14507
14705
  let installed = 0;
14508
14706
  if (!existsSync50(hooksTarget)) {
14509
- mkdirSync25(hooksTarget, { recursive: true });
14707
+ mkdirSync24(hooksTarget, { recursive: true });
14510
14708
  }
14511
14709
  if (!existsSync50(BUNDLED_HOOKS_DIR)) {
14512
14710
  return 0;
@@ -14558,7 +14756,7 @@ async function projectAddCommand(projectPath, options = {}) {
14558
14756
  if (!linearTeam) {
14559
14757
  const projectToml = join48(fullPath, ".panopticon", "project.toml");
14560
14758
  if (existsSync50(projectToml)) {
14561
- const content = readFileSync42(projectToml, "utf-8");
14759
+ const content = readFileSync43(projectToml, "utf-8");
14562
14760
  const match = content.match(/team\s*=\s*"([^"]+)"/);
14563
14761
  if (match) linearTeam = match[1];
14564
14762
  }
@@ -14607,7 +14805,7 @@ async function projectAddCommand(projectPath, options = {}) {
14607
14805
  }
14608
14806
  const isPolyrepo = !hasRootGit && subRepos.length > 0;
14609
14807
  try {
14610
- const { preTrustDirectory } = await import("../workspace-manager-E434Z45T.js");
14808
+ const { preTrustDirectory } = await import("../workspace-manager-ALBR62AS.js");
14611
14809
  preTrustDirectory(fullPath);
14612
14810
  } catch {
14613
14811
  }
@@ -14791,9 +14989,9 @@ Project: ${foundKey}
14791
14989
  init_esm_shims();
14792
14990
  init_paths();
14793
14991
  import chalk51 from "chalk";
14794
- import { existsSync as existsSync51, readdirSync as readdirSync21, readFileSync as readFileSync43 } from "fs";
14992
+ import { existsSync as existsSync51, readdirSync as readdirSync21, readFileSync as readFileSync44 } from "fs";
14795
14993
  import { execSync as execSync6 } from "child_process";
14796
- import { homedir as homedir23 } from "os";
14994
+ import { homedir as homedir24 } from "os";
14797
14995
  import { join as join49 } from "path";
14798
14996
  function checkCommand3(cmd) {
14799
14997
  try {
@@ -14880,7 +15078,7 @@ async function doctorCommand() {
14880
15078
  fix: "Install Claude Code first"
14881
15079
  });
14882
15080
  }
14883
- const envFile = join49(homedir23(), ".panopticon.env");
15081
+ const envFile = join49(homedir24(), ".panopticon.env");
14884
15082
  if (existsSync51(envFile)) {
14885
15083
  checks.push({ name: "Config File", status: "ok", message: "~/.panopticon.env exists" });
14886
15084
  } else {
@@ -14894,7 +15092,7 @@ async function doctorCommand() {
14894
15092
  if (process.env.LINEAR_API_KEY) {
14895
15093
  checks.push({ name: "LINEAR_API_KEY", status: "ok", message: "Set in environment" });
14896
15094
  } else if (existsSync51(envFile)) {
14897
- const content = readFileSync43(envFile, "utf-8");
15095
+ const content = readFileSync44(envFile, "utf-8");
14898
15096
  if (content.includes("LINEAR_API_KEY")) {
14899
15097
  checks.push({ name: "LINEAR_API_KEY", status: "ok", message: "Set in config file" });
14900
15098
  } else {
@@ -14962,7 +15160,7 @@ init_esm_shims();
14962
15160
  init_config();
14963
15161
  import { execSync as execSync7 } from "child_process";
14964
15162
  import chalk52 from "chalk";
14965
- import { readFileSync as readFileSync44 } from "fs";
15163
+ import { readFileSync as readFileSync45 } from "fs";
14966
15164
  import { fileURLToPath as fileURLToPath5 } from "url";
14967
15165
  import { dirname as dirname15, join as join50 } from "path";
14968
15166
  function getCurrentVersion() {
@@ -14970,7 +15168,7 @@ function getCurrentVersion() {
14970
15168
  const __filename6 = fileURLToPath5(import.meta.url);
14971
15169
  const __dirname6 = dirname15(__filename6);
14972
15170
  const pkgPath = join50(__dirname6, "..", "..", "..", "package.json");
14973
- const pkg = JSON.parse(readFileSync44(pkgPath, "utf-8"));
15171
+ const pkg = JSON.parse(readFileSync45(pkgPath, "utf-8"));
14974
15172
  return pkg.version;
14975
15173
  } catch {
14976
15174
  return "unknown";
@@ -15056,11 +15254,11 @@ init_esm_shims();
15056
15254
  init_projects();
15057
15255
  import chalk53 from "chalk";
15058
15256
  import ora21 from "ora";
15059
- import { existsSync as existsSync52, readFileSync as readFileSync45, writeFileSync as writeFileSync26, mkdirSync as mkdirSync26, statSync as statSync11 } from "fs";
15257
+ import { existsSync as existsSync52, readFileSync as readFileSync46, writeFileSync as writeFileSync26, mkdirSync as mkdirSync25, statSync as statSync11 } from "fs";
15060
15258
  import { join as join51, dirname as dirname16 } from "path";
15061
- import { exec as exec20 } from "child_process";
15062
- import { promisify as promisify20 } from "util";
15063
- var execAsync20 = promisify20(exec20);
15259
+ import { exec as exec21 } from "child_process";
15260
+ import { promisify as promisify21 } from "util";
15261
+ var execAsync21 = promisify21(exec21);
15064
15262
  function loadFullProjects() {
15065
15263
  const config2 = loadProjectsConfig();
15066
15264
  const projects = config2.projects;
@@ -15076,12 +15274,12 @@ function findFullProjectByTeam(teamPrefix) {
15076
15274
  ) || null;
15077
15275
  }
15078
15276
  function registerDbCommands(program2) {
15079
- const db2 = program2.command("db").description("Database seeding and management");
15080
- db2.command("snapshot").description("Create a database snapshot from an external source").option("--project <key>", "Project key (e.g., myn)").option("--output <path>", "Output file path").option("--sanitize", "Run sanitization script after snapshot").action(snapshotCommand);
15081
- db2.command("seed <workspaceOrIssue>").description("Seed a workspace database with the configured seed file").option("--force", "Force reseed even if already initialized").option("--file <path>", "Override seed file path").action(seedCommand);
15082
- db2.command("status").description("Check database status for a workspace").argument("[workspaceOrIssue]", "Workspace folder or issue ID").action(statusCommand5);
15083
- db2.command("clean <file>").description("Clean kubectl/stderr garbage from a pg_dump file").option("--output <path>", "Output file (default: overwrite input)").option("--dry-run", "Show what would be cleaned without modifying").action(cleanCommand);
15084
- db2.command("config").description("Show database configuration for a project").argument("[project]", "Project key").action(configCommand);
15277
+ const db = program2.command("db").description("Database seeding and management");
15278
+ db.command("snapshot").description("Create a database snapshot from an external source").option("--project <key>", "Project key (e.g., myn)").option("--output <path>", "Output file path").option("--sanitize", "Run sanitization script after snapshot").action(snapshotCommand);
15279
+ db.command("seed <workspaceOrIssue>").description("Seed a workspace database with the configured seed file").option("--force", "Force reseed even if already initialized").option("--file <path>", "Override seed file path").action(seedCommand);
15280
+ db.command("status").description("Check database status for a workspace").argument("[workspaceOrIssue]", "Workspace folder or issue ID").action(statusCommand5);
15281
+ db.command("clean <file>").description("Clean kubectl/stderr garbage from a pg_dump file").option("--output <path>", "Output file (default: overwrite input)").option("--dry-run", "Show what would be cleaned without modifying").action(cleanCommand);
15282
+ db.command("config").description("Show database configuration for a project").argument("[project]", "Project key").action(configCommand);
15085
15283
  }
15086
15284
  async function snapshotCommand(options) {
15087
15285
  const spinner = ora21("Creating database snapshot...").start();
@@ -15122,7 +15320,7 @@ async function snapshotCommand(options) {
15122
15320
  const outputPath = options.output || dbConfig.seed_file || join51(projectConfig.path, "infra", "seed", "seed.sql");
15123
15321
  const outputDir = dirname16(outputPath);
15124
15322
  if (!existsSync52(outputDir)) {
15125
- mkdirSync26(outputDir, { recursive: true });
15323
+ mkdirSync25(outputDir, { recursive: true });
15126
15324
  }
15127
15325
  spinner.text = "Running snapshot command...";
15128
15326
  let snapshotCmd;
@@ -15142,10 +15340,10 @@ async function snapshotCommand(options) {
15142
15340
  }
15143
15341
  const fullCmd = `${snapshotCmd} > "${outputPath}" 2>&1`;
15144
15342
  try {
15145
- await execAsync20(fullCmd, { timeout: 3e5 });
15343
+ await execAsync21(fullCmd, { timeout: 3e5 });
15146
15344
  } catch (error) {
15147
15345
  if (existsSync52(outputPath)) {
15148
- const content2 = readFileSync45(outputPath, "utf-8");
15346
+ const content2 = readFileSync46(outputPath, "utf-8");
15149
15347
  if (content2.includes("PostgreSQL database dump")) {
15150
15348
  spinner.warn("Snapshot completed with warnings (stderr captured)");
15151
15349
  console.log(chalk53.dim(" Run `pan db clean` to remove stderr noise from the file"));
@@ -15158,7 +15356,7 @@ async function snapshotCommand(options) {
15158
15356
  return;
15159
15357
  }
15160
15358
  }
15161
- const content = readFileSync45(outputPath, "utf-8");
15359
+ const content = readFileSync46(outputPath, "utf-8");
15162
15360
  if (content.includes("Defaulted container") || content.includes("Unable to use a TTY")) {
15163
15361
  spinner.text = "Cleaning kubectl output from snapshot...";
15164
15362
  await cleanFile(outputPath);
@@ -15166,7 +15364,7 @@ async function snapshotCommand(options) {
15166
15364
  if (options.sanitize && dbConfig.seed_command) {
15167
15365
  spinner.text = "Running sanitization...";
15168
15366
  try {
15169
- await execAsync20(dbConfig.seed_command, { cwd: projectConfig.path });
15367
+ await execAsync21(dbConfig.seed_command, { cwd: projectConfig.path });
15170
15368
  } catch (error) {
15171
15369
  spinner.warn(`Sanitization warning: ${error.message}`);
15172
15370
  }
@@ -15206,7 +15404,7 @@ async function seedCommand(workspaceOrIssue, options) {
15206
15404
  const containerName = dbConfig?.container_name?.replace("{{PROJECT}}", projectName) || `${projectName}-postgres-1`;
15207
15405
  spinner.text = `Finding database container ${containerName}...`;
15208
15406
  try {
15209
- const { stdout } = await execAsync20(`docker ps --filter "name=${containerName}" --format "{{.Names}}"`);
15407
+ const { stdout } = await execAsync21(`docker ps --filter "name=${containerName}" --format "{{.Names}}"`);
15210
15408
  if (!stdout.trim()) {
15211
15409
  spinner.fail(`Database container not running: ${containerName}`);
15212
15410
  console.log(chalk53.dim("\nStart the workspace containers first:"));
@@ -15219,7 +15417,7 @@ async function seedCommand(workspaceOrIssue, options) {
15219
15417
  }
15220
15418
  if (!options.force) {
15221
15419
  try {
15222
- const { stdout } = await execAsync20(
15420
+ const { stdout } = await execAsync21(
15223
15421
  `docker exec ${containerName} psql -U postgres -d myn -c "SELECT count(*) FROM flyway_schema_history" -t 2>/dev/null`
15224
15422
  );
15225
15423
  const count = parseInt(stdout.trim(), 10);
@@ -15233,7 +15431,7 @@ async function seedCommand(workspaceOrIssue, options) {
15233
15431
  if (options.force) {
15234
15432
  spinner.text = "Dropping existing database...";
15235
15433
  try {
15236
- await execAsync20(
15434
+ await execAsync21(
15237
15435
  `docker exec ${containerName} psql -U postgres -c "DROP DATABASE IF EXISTS myn; CREATE DATABASE myn;"`
15238
15436
  );
15239
15437
  } catch (error) {
@@ -15241,10 +15439,10 @@ async function seedCommand(workspaceOrIssue, options) {
15241
15439
  }
15242
15440
  }
15243
15441
  spinner.text = "Copying seed file to container...";
15244
- await execAsync20(`docker cp "${seedFile}" ${containerName}:/tmp/seed.sql`);
15442
+ await execAsync21(`docker cp "${seedFile}" ${containerName}:/tmp/seed.sql`);
15245
15443
  spinner.text = "Executing seed...";
15246
15444
  try {
15247
- await execAsync20(`docker exec ${containerName} psql -U postgres -d myn -f /tmp/seed.sql`, {
15445
+ await execAsync21(`docker exec ${containerName} psql -U postgres -d myn -f /tmp/seed.sql`, {
15248
15446
  timeout: 6e5
15249
15447
  // 10 minute timeout for large seeds
15250
15448
  });
@@ -15254,10 +15452,10 @@ async function seedCommand(workspaceOrIssue, options) {
15254
15452
  return;
15255
15453
  }
15256
15454
  }
15257
- await execAsync20(`docker exec ${containerName} rm /tmp/seed.sql`);
15455
+ await execAsync21(`docker exec ${containerName} rm /tmp/seed.sql`);
15258
15456
  spinner.succeed("Database seeded successfully");
15259
15457
  try {
15260
- const { stdout } = await execAsync20(
15458
+ const { stdout } = await execAsync21(
15261
15459
  `docker exec ${containerName} psql -U postgres -d myn -c "SELECT version, description FROM flyway_schema_history ORDER BY installed_rank DESC LIMIT 3" -t`
15262
15460
  );
15263
15461
  console.log(chalk53.dim("\nRecent migrations:"));
@@ -15287,7 +15485,7 @@ async function statusCommand5(workspaceOrIssue) {
15287
15485
  const projects = loadFullProjects();
15288
15486
  projectConfig = projects.find((p) => cwd.startsWith(p.path));
15289
15487
  if (projectConfig) {
15290
- const { stdout } = await execAsync20(
15488
+ const { stdout } = await execAsync21(
15291
15489
  `docker ps --filter "name=${projectConfig.name?.toLowerCase().replace(/\s+/g, "-")}" --filter "name=postgres" --format "{{.Names}}" | head -1`
15292
15490
  );
15293
15491
  containerName = stdout.trim();
@@ -15300,7 +15498,7 @@ async function statusCommand5(workspaceOrIssue) {
15300
15498
  return;
15301
15499
  }
15302
15500
  spinner.text = `Checking container ${containerName}...`;
15303
- const { stdout: containerStatus } = await execAsync20(
15501
+ const { stdout: containerStatus } = await execAsync21(
15304
15502
  `docker ps --filter "name=${containerName}" --format "{{.Status}}"`
15305
15503
  );
15306
15504
  if (!containerStatus.trim()) {
@@ -15310,7 +15508,7 @@ async function statusCommand5(workspaceOrIssue) {
15310
15508
  spinner.succeed(`Container: ${containerName}`);
15311
15509
  console.log(chalk53.dim(` Status: ${containerStatus.trim()}`));
15312
15510
  try {
15313
- const { stdout: version } = await execAsync20(
15511
+ const { stdout: version } = await execAsync21(
15314
15512
  `docker exec ${containerName} psql -U postgres -d myn -c "SELECT version, description FROM flyway_schema_history ORDER BY installed_rank DESC LIMIT 1" -t`
15315
15513
  );
15316
15514
  const [ver, desc] = version.trim().split("|").map((s) => s.trim());
@@ -15319,14 +15517,14 @@ async function statusCommand5(workspaceOrIssue) {
15319
15517
  console.log(chalk53.yellow(" Flyway: Not initialized"));
15320
15518
  }
15321
15519
  try {
15322
- const { stdout: tableCount } = await execAsync20(
15520
+ const { stdout: tableCount } = await execAsync21(
15323
15521
  `docker exec ${containerName} psql -U postgres -d myn -c "SELECT count(*) FROM information_schema.tables WHERE table_schema = 'public'" -t`
15324
15522
  );
15325
15523
  console.log(chalk53.dim(` Tables: ${tableCount.trim()}`));
15326
15524
  } catch {
15327
15525
  }
15328
15526
  try {
15329
- const { stdout: dbSize } = await execAsync20(
15527
+ const { stdout: dbSize } = await execAsync21(
15330
15528
  `docker exec ${containerName} psql -U postgres -d myn -c "SELECT pg_size_pretty(pg_database_size('myn'))" -t`
15331
15529
  );
15332
15530
  console.log(chalk53.dim(` Size: ${dbSize.trim()}`));
@@ -15343,7 +15541,7 @@ async function cleanCommand(file, options) {
15343
15541
  spinner.fail(`File not found: ${file}`);
15344
15542
  return;
15345
15543
  }
15346
- const content = readFileSync45(file, "utf-8");
15544
+ const content = readFileSync46(file, "utf-8");
15347
15545
  const lines = content.split("\n");
15348
15546
  const patternsToRemove = [
15349
15547
  /^Defaulted container/,
@@ -15413,7 +15611,7 @@ async function cleanCommand(file, options) {
15413
15611
  }
15414
15612
  }
15415
15613
  async function cleanFile(filePath) {
15416
- const content = readFileSync45(filePath, "utf-8");
15614
+ const content = readFileSync46(filePath, "utf-8");
15417
15615
  const lines = content.split("\n");
15418
15616
  let startIndex = 0;
15419
15617
  for (let i = 0; i < lines.length; i++) {
@@ -15496,17 +15694,17 @@ async function configCommand(project2) {
15496
15694
  init_esm_shims();
15497
15695
  import chalk54 from "chalk";
15498
15696
  import ora22 from "ora";
15499
- import { existsSync as existsSync53, readFileSync as readFileSync46 } from "fs";
15697
+ import { existsSync as existsSync53, readFileSync as readFileSync47 } from "fs";
15500
15698
  import { join as join52 } from "path";
15501
- import { exec as exec21, execSync as execSync8 } from "child_process";
15502
- import { promisify as promisify21 } from "util";
15699
+ import { exec as exec22, execSync as execSync8 } from "child_process";
15700
+ import { promisify as promisify22 } from "util";
15503
15701
  import { platform } from "os";
15504
- var execAsync21 = promisify21(exec21);
15702
+ var execAsync22 = promisify22(exec22);
15505
15703
  function detectPlatform2() {
15506
15704
  const os = platform();
15507
15705
  if (os === "linux") {
15508
15706
  try {
15509
- const release = readFileSync46("/proc/version", "utf8").toLowerCase();
15707
+ const release = readFileSync47("/proc/version", "utf8").toLowerCase();
15510
15708
  if (release.includes("microsoft") || release.includes("wsl")) {
15511
15709
  return "wsl";
15512
15710
  }
@@ -15518,7 +15716,7 @@ function detectPlatform2() {
15518
15716
  }
15519
15717
  async function isBdAvailable() {
15520
15718
  try {
15521
- await execAsync21("which bd", { encoding: "utf-8" });
15719
+ await execAsync22("which bd", { encoding: "utf-8" });
15522
15720
  return true;
15523
15721
  } catch {
15524
15722
  return false;
@@ -15527,7 +15725,7 @@ async function isBdAvailable() {
15527
15725
  async function getOldClosedCount(cwd, days) {
15528
15726
  try {
15529
15727
  const seconds = days * 24 * 60 * 60;
15530
- const { stdout } = await execAsync21(
15728
+ const { stdout } = await execAsync22(
15531
15729
  `bd list --status closed --json 2>/dev/null | jq '[.[] | select(.closed_at != null) | select((now - (.closed_at | fromdateiso8601)) > ${seconds})] | length' 2>/dev/null || echo "0"`,
15532
15730
  { cwd, encoding: "utf-8" }
15533
15731
  );
@@ -15563,7 +15761,7 @@ async function compactCommand(options) {
15563
15761
  console.log("");
15564
15762
  console.log(chalk54.bold("Beads that would be compacted:"));
15565
15763
  try {
15566
- const { stdout: beadsList } = await execAsync21(
15764
+ const { stdout: beadsList } = await execAsync22(
15567
15765
  `bd list --status closed --json 2>/dev/null | jq -r '.[] | select(.closed_at != null) | select((now - (.closed_at | fromdateiso8601)) > ${days * 24 * 60 * 60}) | " - \\(.id): \\(.title)"' 2>/dev/null`,
15568
15766
  { cwd, encoding: "utf-8" }
15569
15767
  );
@@ -15574,10 +15772,10 @@ async function compactCommand(options) {
15574
15772
  return;
15575
15773
  }
15576
15774
  spinner.text = "Running compaction...";
15577
- await execAsync21(`bd admin compact --days ${days}`, { cwd, encoding: "utf-8" });
15775
+ await execAsync22(`bd admin compact --days ${days}`, { cwd, encoding: "utf-8" });
15578
15776
  spinner.succeed(`Compacted ${count} beads older than ${days} days`);
15579
15777
  try {
15580
- await execAsync21(`git diff --quiet .beads/`, { cwd, encoding: "utf-8" });
15778
+ await execAsync22(`git diff --quiet .beads/`, { cwd, encoding: "utf-8" });
15581
15779
  console.log(chalk54.dim("No changes to commit (beads already up to date)"));
15582
15780
  } catch {
15583
15781
  console.log("");
@@ -15611,17 +15809,17 @@ async function statsCommand() {
15611
15809
  }
15612
15810
  const spinner = ora22("Gathering beads statistics...").start();
15613
15811
  try {
15614
- const { stdout: totalRaw } = await execAsync21(`bd list --limit 0 --json 2>/dev/null | jq 'length'`, {
15812
+ const { stdout: totalRaw } = await execAsync22(`bd list --limit 0 --json 2>/dev/null | jq 'length'`, {
15615
15813
  cwd,
15616
15814
  encoding: "utf-8"
15617
15815
  });
15618
15816
  const total = parseInt(totalRaw.trim(), 10) || 0;
15619
- const { stdout: openRaw } = await execAsync21(`bd list --status open --limit 0 --json 2>/dev/null | jq 'length'`, {
15817
+ const { stdout: openRaw } = await execAsync22(`bd list --status open --limit 0 --json 2>/dev/null | jq 'length'`, {
15620
15818
  cwd,
15621
15819
  encoding: "utf-8"
15622
15820
  });
15623
15821
  const open = parseInt(openRaw.trim(), 10) || 0;
15624
- const { stdout: closedRaw } = await execAsync21(`bd list --status closed --limit 0 --json 2>/dev/null | jq 'length'`, {
15822
+ const { stdout: closedRaw } = await execAsync22(`bd list --status closed --limit 0 --json 2>/dev/null | jq 'length'`, {
15625
15823
  cwd,
15626
15824
  encoding: "utf-8"
15627
15825
  });
@@ -15666,7 +15864,7 @@ async function upgradeCommand(checkOnly = false) {
15666
15864
  console.log(chalk54.dim("Checking beads version..."));
15667
15865
  let currentVersion = "not installed";
15668
15866
  try {
15669
- const { stdout } = await execAsync21("bd --version", { encoding: "utf-8" });
15867
+ const { stdout } = await execAsync22("bd --version", { encoding: "utf-8" });
15670
15868
  const match = stdout.match(/(\d+\.\d+\.\d+)/);
15671
15869
  if (match) {
15672
15870
  currentVersion = match[1];
@@ -15675,7 +15873,7 @@ async function upgradeCommand(checkOnly = false) {
15675
15873
  }
15676
15874
  let latestVersion = "unknown";
15677
15875
  try {
15678
- const { stdout } = await execAsync21(
15876
+ const { stdout } = await execAsync22(
15679
15877
  "curl -sL https://api.github.com/repos/steveyegge/beads/releases/latest | jq -r .tag_name",
15680
15878
  { encoding: "utf-8" }
15681
15879
  );
@@ -15724,7 +15922,7 @@ async function upgradeCommand(checkOnly = false) {
15724
15922
  spinner.succeed("beads upgraded via install script");
15725
15923
  }
15726
15924
  try {
15727
- const { stdout } = await execAsync21("bd --version", { encoding: "utf-8" });
15925
+ const { stdout } = await execAsync22("bd --version", { encoding: "utf-8" });
15728
15926
  const match = stdout.match(/(\d+\.\d+\.\d+)/);
15729
15927
  if (match) {
15730
15928
  console.log(chalk54.green(`
@@ -16231,12 +16429,12 @@ init_esm_shims();
16231
16429
  init_config();
16232
16430
  import chalk59 from "chalk";
16233
16431
  import ora27 from "ora";
16234
- import { exec as exec22 } from "child_process";
16235
- import { promisify as promisify22 } from "util";
16236
- import { existsSync as existsSync54, readFileSync as readFileSync47 } from "fs";
16432
+ import { exec as exec23 } from "child_process";
16433
+ import { promisify as promisify23 } from "util";
16434
+ import { existsSync as existsSync54, readFileSync as readFileSync48 } from "fs";
16237
16435
  import { join as join53 } from "path";
16238
- import { homedir as homedir24 } from "os";
16239
- var execAsync22 = promisify22(exec22);
16436
+ import { homedir as homedir25 } from "os";
16437
+ var execAsync23 = promisify23(exec23);
16240
16438
  async function setupCommand() {
16241
16439
  console.log("");
16242
16440
  console.log(chalk59.bold("\u{1F680} Remote Workspace Setup"));
@@ -16247,7 +16445,7 @@ async function setupCommand() {
16247
16445
  console.log("");
16248
16446
  console.log(chalk59.bold(" Step 2: SSH Key Configuration"));
16249
16447
  console.log("");
16250
- const sshDir = join53(homedir24(), ".ssh");
16448
+ const sshDir = join53(homedir25(), ".ssh");
16251
16449
  const defaultKeyPath = join53(sshDir, "id_ed25519");
16252
16450
  const rsaKeyPath = join53(sshDir, "id_rsa");
16253
16451
  let sshKeyExists = false;
@@ -16271,7 +16469,7 @@ async function setupCommand() {
16271
16469
  if (sshKeyExists) {
16272
16470
  const pubKeyPath = `${keyPath}.pub`;
16273
16471
  if (existsSync54(pubKeyPath)) {
16274
- const pubKey = readFileSync47(pubKeyPath, "utf8").trim();
16472
+ const pubKey = readFileSync48(pubKeyPath, "utf8").trim();
16275
16473
  console.log("");
16276
16474
  console.log(" Your public key (add this to exe.dev if not already):");
16277
16475
  console.log("");
@@ -16299,7 +16497,7 @@ async function setupCommand() {
16299
16497
  if (sshKeyExists) {
16300
16498
  const pubKeyPath = `${keyPath}.pub`;
16301
16499
  if (existsSync54(pubKeyPath)) {
16302
- const pubKey = readFileSync47(pubKeyPath, "utf8").trim();
16500
+ const pubKey = readFileSync48(pubKeyPath, "utf8").trim();
16303
16501
  console.log(chalk59.dim(` ${pubKey}`));
16304
16502
  }
16305
16503
  } else {
@@ -16385,8 +16583,8 @@ import chalk60 from "chalk";
16385
16583
  // src/lib/env-loader.ts
16386
16584
  init_esm_shims();
16387
16585
  import { join as join54 } from "path";
16388
- import { homedir as homedir25 } from "os";
16389
- var ENV_FILE_PATH = join54(homedir25(), ".panopticon.env");
16586
+ import { homedir as homedir26 } from "os";
16587
+ var ENV_FILE_PATH = join54(homedir26(), ".panopticon.env");
16390
16588
  function getShadowModeFromEnv() {
16391
16589
  const value = process.env.SHADOW_MODE;
16392
16590
  if (!value) return false;
@@ -16480,11 +16678,422 @@ Shadowed issues: ${shadowedIssues.length}`));
16480
16678
  }
16481
16679
  }
16482
16680
 
16681
+ // src/cli/commands/cost.ts
16682
+ init_esm_shims();
16683
+ init_cost();
16684
+ import { Command } from "commander";
16685
+ import chalk61 from "chalk";
16686
+
16687
+ // src/lib/costs/sync-wal.ts
16688
+ init_esm_shims();
16689
+ init_projects();
16690
+ import { existsSync as existsSync55 } from "fs";
16691
+ import { readdir, readFile } from "fs/promises";
16692
+ import { join as join55 } from "path";
16693
+
16694
+ // src/lib/database/cost-events-db.ts
16695
+ init_esm_shims();
16696
+ init_database();
16697
+ function insertCostEvents(events, sourceFile) {
16698
+ const db = getDatabase();
16699
+ let inserted = 0;
16700
+ let duplicates = 0;
16701
+ const insert = db.prepare(`
16702
+ INSERT OR IGNORE INTO cost_events (
16703
+ ts, agent_id, issue_id, session_type, provider, model,
16704
+ input, output, cache_read, cache_write, cost, request_id,
16705
+ tldr_interceptions, tldr_bypasses, tldr_tokens_saved, tldr_bypass_reasons,
16706
+ source_file
16707
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
16708
+ `);
16709
+ const insertMany = db.transaction((evs) => {
16710
+ for (const ev of evs) {
16711
+ const result = insert.run(
16712
+ ev.ts,
16713
+ ev.agentId,
16714
+ ev.issueId,
16715
+ ev.sessionType || "unknown",
16716
+ ev.provider || "anthropic",
16717
+ ev.model,
16718
+ ev.input,
16719
+ ev.output,
16720
+ ev.cacheRead,
16721
+ ev.cacheWrite,
16722
+ ev.cost,
16723
+ ev.requestId ?? null,
16724
+ ev.tldrInterceptions ?? null,
16725
+ ev.tldrBypasses ?? null,
16726
+ ev.tldrTokensSaved ?? null,
16727
+ ev.tldrBypassReasons ? JSON.stringify(ev.tldrBypassReasons) : null,
16728
+ sourceFile ?? null
16729
+ );
16730
+ if (result.changes > 0) {
16731
+ inserted++;
16732
+ } else {
16733
+ duplicates++;
16734
+ }
16735
+ }
16736
+ });
16737
+ insertMany(events);
16738
+ return { inserted, duplicates };
16739
+ }
16740
+
16741
+ // src/lib/costs/sync-wal.ts
16742
+ var DEFAULT_EVENTS_SUBDIR = ".panopticon/events";
16743
+ async function syncWalFromAllProjects() {
16744
+ const result = {
16745
+ imported: 0,
16746
+ duplicates: 0,
16747
+ filesScanned: 0,
16748
+ byProject: {},
16749
+ errors: []
16750
+ };
16751
+ const projects = listProjects();
16752
+ for (const { key, config: config2 } of projects) {
16753
+ const repoPath = config2.events_repo ?? config2.path;
16754
+ const eventsSubdir = config2.events_path ?? DEFAULT_EVENTS_SUBDIR;
16755
+ const eventsDir = join55(repoPath, eventsSubdir);
16756
+ if (!existsSync55(eventsDir)) continue;
16757
+ const projectStats = { imported: 0, duplicates: 0, files: 0 };
16758
+ let files;
16759
+ try {
16760
+ files = (await readdir(eventsDir)).filter((f) => f.endsWith(".jsonl"));
16761
+ } catch (err) {
16762
+ result.errors.push(`${key}: failed to read events dir: ${err}`);
16763
+ continue;
16764
+ }
16765
+ for (const file of files) {
16766
+ const filePath = join55(eventsDir, file);
16767
+ const events = await parseWalFile(filePath, result.errors);
16768
+ if (events.length === 0) continue;
16769
+ try {
16770
+ const { inserted, duplicates } = insertCostEvents(events, filePath);
16771
+ projectStats.imported += inserted;
16772
+ projectStats.duplicates += duplicates;
16773
+ projectStats.files++;
16774
+ result.filesScanned++;
16775
+ } catch (err) {
16776
+ result.errors.push(`${key}/${file}: import failed: ${err}`);
16777
+ }
16778
+ }
16779
+ if (projectStats.files > 0 || projectStats.imported > 0) {
16780
+ result.byProject[key] = projectStats;
16781
+ result.imported += projectStats.imported;
16782
+ result.duplicates += projectStats.duplicates;
16783
+ }
16784
+ }
16785
+ return result;
16786
+ }
16787
+ async function parseWalFile(filePath, errors) {
16788
+ let content;
16789
+ try {
16790
+ content = await readFile(filePath, "utf-8");
16791
+ } catch (err) {
16792
+ errors.push(`Failed to read ${filePath}: ${err}`);
16793
+ return [];
16794
+ }
16795
+ const events = [];
16796
+ const lines = content.split("\n").filter((l) => l.trim());
16797
+ for (const line of lines) {
16798
+ try {
16799
+ const event = JSON.parse(line);
16800
+ if (event.ts && event.agentId && event.issueId && event.model) {
16801
+ events.push(event);
16802
+ }
16803
+ } catch {
16804
+ }
16805
+ }
16806
+ return events;
16807
+ }
16808
+
16809
+ // src/cli/commands/cost.ts
16810
+ async function runCostSync() {
16811
+ try {
16812
+ console.log(chalk61.bold("Syncing cost events from project WAL files..."));
16813
+ const result = await syncWalFromAllProjects();
16814
+ if (result.filesScanned === 0) {
16815
+ console.log(chalk61.yellow("No WAL files found. Make sure projects are registered and have cost events."));
16816
+ return;
16817
+ }
16818
+ console.log();
16819
+ console.log(`Files scanned: ${result.filesScanned}`);
16820
+ console.log(`Imported: ${chalk61.green(result.imported)} new events`);
16821
+ console.log(`Duplicates: ${chalk61.dim(result.duplicates)} skipped`);
16822
+ if (Object.keys(result.byProject).length > 0) {
16823
+ console.log();
16824
+ console.log(chalk61.bold("By Project:"));
16825
+ for (const [project2, stats] of Object.entries(result.byProject)) {
16826
+ console.log(` ${project2}: ${chalk61.green(stats.imported)} imported, ${stats.files} file(s)`);
16827
+ }
16828
+ }
16829
+ if (result.errors.length > 0) {
16830
+ console.log();
16831
+ console.log(chalk61.yellow(`Warnings (${result.errors.length}):`));
16832
+ for (const err of result.errors) {
16833
+ console.log(` ${chalk61.dim(err)}`);
16834
+ }
16835
+ }
16836
+ } catch (error) {
16837
+ console.error(chalk61.red("Error:"), error instanceof Error ? error.message : String(error));
16838
+ process.exit(1);
16839
+ }
16840
+ }
16841
+ function createCostCommand() {
16842
+ const cost = new Command("cost").description("Track and report AI usage costs");
16843
+ cost.command("today").description("Show today's cost summary").option("-d, --detail", "Show individual entries").action((options) => {
16844
+ try {
16845
+ const summary = getDailySummary();
16846
+ console.log(chalk61.bold("Today's Cost Summary"));
16847
+ console.log();
16848
+ console.log(`Total Cost: ${chalk61.green(formatCost(summary.totalCost))}`);
16849
+ console.log(`API Calls: ${summary.entryCount}`);
16850
+ console.log(`Tokens: ${summary.totalTokens.total.toLocaleString()}`);
16851
+ console.log(` Input: ${summary.totalTokens.input.toLocaleString()}`);
16852
+ console.log(` Output: ${summary.totalTokens.output.toLocaleString()}`);
16853
+ console.log();
16854
+ if (Object.keys(summary.byProvider).length > 0) {
16855
+ console.log(chalk61.bold("By Provider"));
16856
+ for (const [provider, cost2] of Object.entries(summary.byProvider)) {
16857
+ console.log(` ${provider}: ${formatCost(cost2)}`);
16858
+ }
16859
+ console.log();
16860
+ }
16861
+ if (Object.keys(summary.byModel).length > 0) {
16862
+ console.log(chalk61.bold("By Model"));
16863
+ for (const [model, cost2] of Object.entries(summary.byModel)) {
16864
+ console.log(` ${model}: ${formatCost(cost2)}`);
16865
+ }
16866
+ console.log();
16867
+ }
16868
+ if (options.detail) {
16869
+ const entries = readTodayCosts();
16870
+ if (entries.length > 0) {
16871
+ console.log(chalk61.bold("Entries"));
16872
+ for (const entry of entries.slice(-10)) {
16873
+ const time = new Date(entry.timestamp).toLocaleTimeString();
16874
+ console.log(` ${chalk61.dim(time)} ${entry.model} ${formatCost(entry.cost)} ${entry.operation}`);
16875
+ }
16876
+ if (entries.length > 10) {
16877
+ console.log(chalk61.dim(` ... and ${entries.length - 10} more`));
16878
+ }
16879
+ }
16880
+ }
16881
+ } catch (error) {
16882
+ console.error(chalk61.red("Error:"), error instanceof Error ? error.message : String(error));
16883
+ process.exit(1);
16884
+ }
16885
+ });
16886
+ cost.command("week").description("Show weekly cost summary").action(() => {
16887
+ try {
16888
+ const summary = getWeeklySummary();
16889
+ console.log(chalk61.bold("Weekly Cost Summary"));
16890
+ console.log(chalk61.dim(`${summary.period.start} to ${summary.period.end}`));
16891
+ console.log();
16892
+ console.log(`Total Cost: ${chalk61.green(formatCost(summary.totalCost))}`);
16893
+ console.log(`API Calls: ${summary.entryCount}`);
16894
+ console.log(`Tokens: ${summary.totalTokens.total.toLocaleString()}`);
16895
+ console.log();
16896
+ if (Object.keys(summary.byProvider).length > 0) {
16897
+ console.log(chalk61.bold("By Provider"));
16898
+ for (const [provider, cost2] of Object.entries(summary.byProvider)) {
16899
+ console.log(` ${provider}: ${formatCost(cost2)}`);
16900
+ }
16901
+ console.log();
16902
+ }
16903
+ if (Object.keys(summary.byIssue).length > 0) {
16904
+ console.log(chalk61.bold("Top Issues by Cost"));
16905
+ const sorted = Object.entries(summary.byIssue).sort(([, a], [, b]) => b - a).slice(0, 5);
16906
+ for (const [issue, cost2] of sorted) {
16907
+ console.log(` ${issue}: ${formatCost(cost2)}`);
16908
+ }
16909
+ }
16910
+ } catch (error) {
16911
+ console.error(chalk61.red("Error:"), error instanceof Error ? error.message : String(error));
16912
+ process.exit(1);
16913
+ }
16914
+ });
16915
+ cost.command("month").description("Show monthly cost summary").action(() => {
16916
+ try {
16917
+ const summary = getMonthlySummary();
16918
+ console.log(chalk61.bold("Monthly Cost Summary"));
16919
+ console.log(chalk61.dim(`${summary.period.start} to ${summary.period.end}`));
16920
+ console.log();
16921
+ console.log(`Total Cost: ${chalk61.green(formatCost(summary.totalCost))}`);
16922
+ console.log(`API Calls: ${summary.entryCount}`);
16923
+ console.log(`Tokens: ${summary.totalTokens.total.toLocaleString()}`);
16924
+ console.log();
16925
+ if (Object.keys(summary.byProvider).length > 0) {
16926
+ console.log(chalk61.bold("By Provider"));
16927
+ for (const [provider, cost2] of Object.entries(summary.byProvider)) {
16928
+ console.log(` ${provider}: ${formatCost(cost2)}`);
16929
+ }
16930
+ console.log();
16931
+ }
16932
+ if (Object.keys(summary.byModel).length > 0) {
16933
+ console.log(chalk61.bold("By Model"));
16934
+ for (const [model, cost2] of Object.entries(summary.byModel)) {
16935
+ console.log(` ${model}: ${formatCost(cost2)}`);
16936
+ }
16937
+ console.log();
16938
+ }
16939
+ if (Object.keys(summary.byIssue).length > 0) {
16940
+ console.log(chalk61.bold("Top 10 Issues by Cost"));
16941
+ const sorted = Object.entries(summary.byIssue).sort(([, a], [, b]) => b - a).slice(0, 10);
16942
+ for (const [issue, cost2] of sorted) {
16943
+ console.log(` ${issue}: ${formatCost(cost2)}`);
16944
+ }
16945
+ }
16946
+ } catch (error) {
16947
+ console.error(chalk61.red("Error:"), error instanceof Error ? error.message : String(error));
16948
+ process.exit(1);
16949
+ }
16950
+ });
16951
+ cost.command("report").description("Generate a cost report").option("-s, --start <date>", "Start date (YYYY-MM-DD)").option("-e, --end <date>", "End date (YYYY-MM-DD)").action((options) => {
16952
+ try {
16953
+ const end = options.end || (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
16954
+ const start = options.start || (() => {
16955
+ const d = /* @__PURE__ */ new Date();
16956
+ d.setDate(d.getDate() - 30);
16957
+ return d.toISOString().split("T")[0];
16958
+ })();
16959
+ const report = generateReport(start, end);
16960
+ console.log(report);
16961
+ } catch (error) {
16962
+ console.error(chalk61.red("Error:"), error instanceof Error ? error.message : String(error));
16963
+ process.exit(1);
16964
+ }
16965
+ });
16966
+ cost.command("issue <issueId>").description("Show costs for a specific issue").option("-d, --days <n>", "Number of days to look back", "30").action((issueId, options) => {
16967
+ try {
16968
+ const entries = readIssueCosts(issueId, parseInt(options.days, 10));
16969
+ if (entries.length === 0) {
16970
+ console.log(chalk61.dim("No costs found for issue:"), issueId);
16971
+ return;
16972
+ }
16973
+ const summary = summarizeCosts(entries);
16974
+ console.log(chalk61.bold(`Costs for ${issueId}`));
16975
+ console.log();
16976
+ console.log(`Total Cost: ${chalk61.green(formatCost(summary.totalCost))}`);
16977
+ console.log(`API Calls: ${summary.entryCount}`);
16978
+ console.log(`Tokens: ${summary.totalTokens.total.toLocaleString()}`);
16979
+ console.log();
16980
+ if (Object.keys(summary.byModel).length > 0) {
16981
+ console.log(chalk61.bold("By Model"));
16982
+ for (const [model, cost2] of Object.entries(summary.byModel)) {
16983
+ console.log(` ${model}: ${formatCost(cost2)}`);
16984
+ }
16985
+ }
16986
+ } catch (error) {
16987
+ console.error(chalk61.red("Error:"), error instanceof Error ? error.message : String(error));
16988
+ process.exit(1);
16989
+ }
16990
+ });
16991
+ const budget = cost.command("budget").description("Manage cost budgets");
16992
+ budget.command("create <name>").description("Create a cost budget").option("-l, --limit <amount>", "Budget limit in USD", "100").option("-t, --type <type>", "Budget type (daily, monthly, project, issue, feature)", "monthly").option("-a, --alert <threshold>", "Alert threshold (0-1)", "0.8").action((name, options) => {
16993
+ try {
16994
+ const newBudget = createBudget({
16995
+ name,
16996
+ type: options.type,
16997
+ limit: parseFloat(options.limit),
16998
+ currency: "USD",
16999
+ alertThreshold: parseFloat(options.alert),
17000
+ enabled: true
17001
+ });
17002
+ console.log(chalk61.green("\u2713 Budget created"));
17003
+ console.log(` ID: ${chalk61.cyan(newBudget.id)}`);
17004
+ console.log(` Name: ${newBudget.name}`);
17005
+ console.log(` Type: ${newBudget.type}`);
17006
+ console.log(` Limit: ${formatCost(newBudget.limit)}`);
17007
+ console.log(` Alert at: ${(newBudget.alertThreshold * 100).toFixed(0)}%`);
17008
+ } catch (error) {
17009
+ console.error(chalk61.red("Error:"), error instanceof Error ? error.message : String(error));
17010
+ process.exit(1);
17011
+ }
17012
+ });
17013
+ budget.command("list").description("List all budgets").action(() => {
17014
+ try {
17015
+ const budgets = getAllBudgets();
17016
+ if (budgets.length === 0) {
17017
+ console.log(chalk61.dim("No budgets configured"));
17018
+ console.log(chalk61.dim('Create one with: pan cost budget create "Monthly Limit" --limit 100'));
17019
+ return;
17020
+ }
17021
+ console.log(chalk61.bold("Budgets"));
17022
+ console.log();
17023
+ for (const b of budgets) {
17024
+ const status = checkBudget(b.id);
17025
+ const percentStr = `${(status.percentUsed * 100).toFixed(0)}%`;
17026
+ let statusColor = chalk61.green;
17027
+ if (status.exceeded) {
17028
+ statusColor = chalk61.red;
17029
+ } else if (status.alert) {
17030
+ statusColor = chalk61.yellow;
17031
+ }
17032
+ console.log(`${b.enabled ? "\u25CF" : "\u25CB"} ${chalk61.cyan(b.id)} ${b.name}`);
17033
+ console.log(` Type: ${b.type}`);
17034
+ console.log(` Limit: ${formatCost(b.limit)}`);
17035
+ console.log(` Spent: ${statusColor(formatCost(b.spent))} (${statusColor(percentStr)})`);
17036
+ console.log(` Remaining: ${formatCost(status.remaining)}`);
17037
+ console.log();
17038
+ }
17039
+ } catch (error) {
17040
+ console.error(chalk61.red("Error:"), error instanceof Error ? error.message : String(error));
17041
+ process.exit(1);
17042
+ }
17043
+ });
17044
+ budget.command("check <id>").description("Check budget status").action((id) => {
17045
+ try {
17046
+ const status = checkBudget(id);
17047
+ if (!status.budget) {
17048
+ console.log(chalk61.red("Budget not found:"), id);
17049
+ process.exit(1);
17050
+ }
17051
+ const b = status.budget;
17052
+ const percentStr = `${(status.percentUsed * 100).toFixed(0)}%`;
17053
+ let statusColor = chalk61.green;
17054
+ let statusText = "OK";
17055
+ if (status.exceeded) {
17056
+ statusColor = chalk61.red;
17057
+ statusText = "EXCEEDED";
17058
+ } else if (status.alert) {
17059
+ statusColor = chalk61.yellow;
17060
+ statusText = "WARNING";
17061
+ }
17062
+ console.log(chalk61.bold(b.name));
17063
+ console.log();
17064
+ console.log(`Status: ${statusColor(statusText)}`);
17065
+ console.log(`Limit: ${formatCost(b.limit)}`);
17066
+ console.log(`Spent: ${statusColor(formatCost(b.spent))} (${statusColor(percentStr)})`);
17067
+ console.log(`Remaining: ${formatCost(status.remaining)}`);
17068
+ console.log(`Alert Threshold: ${(b.alertThreshold * 100).toFixed(0)}%`);
17069
+ } catch (error) {
17070
+ console.error(chalk61.red("Error:"), error instanceof Error ? error.message : String(error));
17071
+ process.exit(1);
17072
+ }
17073
+ });
17074
+ budget.command("delete <id>").description("Delete a budget").action((id) => {
17075
+ try {
17076
+ const success = deleteBudget(id);
17077
+ if (success) {
17078
+ console.log(chalk61.green("\u2713 Budget deleted"));
17079
+ } else {
17080
+ console.log(chalk61.red("Budget not found:"), id);
17081
+ process.exit(1);
17082
+ }
17083
+ } catch (error) {
17084
+ console.error(chalk61.red("Error:"), error instanceof Error ? error.message : String(error));
17085
+ process.exit(1);
17086
+ }
17087
+ });
17088
+ cost.command("sync").description("Import cost events from per-project WAL files into the local database").action(runCostSync);
17089
+ return cost;
17090
+ }
17091
+
16483
17092
  // src/cli/index.ts
16484
- var PANOPTICON_ENV_FILE = join55(homedir26(), ".panopticon.env");
16485
- if (existsSync55(PANOPTICON_ENV_FILE)) {
17093
+ var PANOPTICON_ENV_FILE = join56(homedir27(), ".panopticon.env");
17094
+ if (existsSync56(PANOPTICON_ENV_FILE)) {
16486
17095
  try {
16487
- const envContent = readFileSync48(PANOPTICON_ENV_FILE, "utf-8");
17096
+ const envContent = readFileSync49(PANOPTICON_ENV_FILE, "utf-8");
16488
17097
  for (const line of envContent.split("\n")) {
16489
17098
  const trimmed = line.trim();
16490
17099
  if (!trimmed || trimmed.startsWith("#")) continue;
@@ -16500,8 +17109,8 @@ if (existsSync55(PANOPTICON_ENV_FILE)) {
16500
17109
  console.warn("Warning: Failed to load ~/.panopticon.env:", error.message);
16501
17110
  }
16502
17111
  }
16503
- var program = new Command();
16504
- program.name("pan").description("Multi-agent orchestration for AI coding assistants").version(JSON.parse(readFileSync48(join55(import.meta.dirname, "../../package.json"), "utf-8")).version);
17112
+ var program = new Command2();
17113
+ program.name("pan").description("Multi-agent orchestration for AI coding assistants").version(JSON.parse(readFileSync49(join56(import.meta.dirname, "../../package.json"), "utf-8")).version);
16505
17114
  program.command("init").description("Initialize Panopticon (~/.panopticon/)").action(initCommand);
16506
17115
  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);
16507
17116
  program.command("restore [timestamp]").description("Restore from backup").action(restoreCommand);
@@ -16525,58 +17134,58 @@ program.command("migrate-config").description("Migrate from settings.json to con
16525
17134
  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);
16526
17135
  program.command("up").description("Start dashboard (and Traefik if enabled)").option("--detach", "Run in background").option("--skip-traefik", "Skip Traefik startup").action(async (options) => {
16527
17136
  const { spawn: spawn2, execSync: execSync9 } = await import("child_process");
16528
- const { join: join56, dirname: dirname17 } = await import("path");
17137
+ const { join: join57, dirname: dirname17 } = await import("path");
16529
17138
  const { fileURLToPath: fileURLToPath6 } = await import("url");
16530
- const { readFileSync: readFileSync49, existsSync: existsSync56 } = await import("fs");
17139
+ const { readFileSync: readFileSync50, existsSync: existsSync57 } = await import("fs");
16531
17140
  const { parse } = await import("@iarna/toml");
16532
17141
  const __dirname6 = dirname17(fileURLToPath6(import.meta.url));
16533
- const bundledServer = join56(__dirname6, "..", "dashboard", "server.js");
16534
- const srcDashboard = join56(__dirname6, "..", "..", "src", "dashboard");
16535
- const configFile = join56(process.env.HOME || "", ".panopticon", "config.toml");
17142
+ const bundledServer = join57(__dirname6, "..", "dashboard", "server.js");
17143
+ const srcDashboard = join57(__dirname6, "..", "..", "src", "dashboard");
17144
+ const configFile = join57(process.env.HOME || "", ".panopticon", "config.toml");
16536
17145
  let traefikEnabled = false;
16537
17146
  let traefikDomain = "pan.localhost";
16538
17147
  let dashboardPort = 3010;
16539
17148
  let dashboardApiPort = 3011;
16540
- if (existsSync56(configFile)) {
17149
+ if (existsSync57(configFile)) {
16541
17150
  try {
16542
- const configContent = readFileSync49(configFile, "utf-8");
17151
+ const configContent = readFileSync50(configFile, "utf-8");
16543
17152
  const config2 = parse(configContent);
16544
17153
  traefikEnabled = config2.traefik?.enabled === true;
16545
17154
  traefikDomain = config2.traefik?.domain || "pan.localhost";
16546
17155
  dashboardPort = config2.dashboard?.port || 3010;
16547
17156
  dashboardApiPort = config2.dashboard?.api_port || 3011;
16548
17157
  } catch (error) {
16549
- console.log(chalk61.yellow("Warning: Could not read config.toml"));
17158
+ console.log(chalk62.yellow("Warning: Could not read config.toml"));
16550
17159
  }
16551
17160
  }
16552
- console.log(chalk61.bold("Starting Panopticon...\n"));
17161
+ console.log(chalk62.bold("Starting Panopticon...\n"));
16553
17162
  if (traefikEnabled && !options.skipTraefik) {
16554
17163
  try {
16555
- const { generatePanopticonTraefikConfig: generatePanopticonTraefikConfig2, ensureProjectCerts: ensureProjectCerts2, generateTlsConfig: generateTlsConfig2, cleanupStaleTlsSections } = await import("../traefik-WFMQX2LY.js");
17164
+ const { generatePanopticonTraefikConfig: generatePanopticonTraefikConfig2, ensureProjectCerts: ensureProjectCerts2, generateTlsConfig: generateTlsConfig2, cleanupStaleTlsSections } = await import("../traefik-5GL3Q7DJ.js");
16556
17165
  cleanupStaleTlsSections();
16557
17166
  if (generatePanopticonTraefikConfig2()) {
16558
- console.log(chalk61.dim(" Regenerated Traefik config from template"));
17167
+ console.log(chalk62.dim(" Regenerated Traefik config from template"));
16559
17168
  }
16560
17169
  const generatedDomains = ensureProjectCerts2();
16561
17170
  for (const domain of generatedDomains) {
16562
- console.log(chalk61.dim(` Generated wildcard cert for *.${domain}`));
17171
+ console.log(chalk62.dim(` Generated wildcard cert for *.${domain}`));
16563
17172
  }
16564
17173
  if (generateTlsConfig2()) {
16565
- console.log(chalk61.dim(" Generated TLS config (tls.yml)"));
17174
+ console.log(chalk62.dim(" Generated TLS config (tls.yml)"));
16566
17175
  }
16567
17176
  } catch {
16568
- console.log(chalk61.yellow("Warning: Could not regenerate Traefik config"));
17177
+ console.log(chalk62.yellow("Warning: Could not regenerate Traefik config"));
16569
17178
  }
16570
17179
  try {
16571
17180
  const { ensureBaseDomain: ensureBaseDomain2, detectDnsSyncMethod: detectDnsSyncMethod2, syncDnsToWindows: syncDnsToWindows2 } = await import("../dns-7BDJSD3E.js");
16572
- const dnsMethod = (existsSync56(configFile) ? parse(readFileSync49(configFile, "utf-8")).traefik?.dns_sync_method : null) || detectDnsSyncMethod2();
17181
+ const dnsMethod = (existsSync57(configFile) ? parse(readFileSync50(configFile, "utf-8")).traefik?.dns_sync_method : null) || detectDnsSyncMethod2();
16573
17182
  ensureBaseDomain2(dnsMethod, traefikDomain);
16574
17183
  if (dnsMethod === "wsl2hosts") {
16575
17184
  syncDnsToWindows2().catch(() => {
16576
17185
  });
16577
17186
  }
16578
17187
  } catch {
16579
- console.log(chalk61.yellow(`Warning: Could not ensure DNS for ${traefikDomain}`));
17188
+ console.log(chalk62.yellow(`Warning: Could not ensure DNS for ${traefikDomain}`));
16580
17189
  }
16581
17190
  } else if (!traefikEnabled) {
16582
17191
  try {
@@ -16585,19 +17194,19 @@ program.command("up").description("Start dashboard (and Traefik if enabled)").op
16585
17194
  { encoding: "utf-8" }
16586
17195
  ).trim();
16587
17196
  if (containerCheck.includes("panopticon-traefik")) {
16588
- console.log(chalk61.yellow("\u26A0 Traefik container is running but traefik.enabled is not set in config"));
16589
- console.log(chalk61.yellow(" Run `pan install` to configure Traefik, or `pan down` to stop it\n"));
17197
+ console.log(chalk62.yellow("\u26A0 Traefik container is running but traefik.enabled is not set in config"));
17198
+ console.log(chalk62.yellow(" Run `pan install` to configure Traefik, or `pan down` to stop it\n"));
16590
17199
  }
16591
17200
  } catch {
16592
17201
  }
16593
17202
  }
16594
17203
  if (traefikEnabled && !options.skipTraefik) {
16595
- const traefikDir = join56(process.env.HOME || "", ".panopticon", "traefik");
16596
- if (existsSync56(traefikDir)) {
17204
+ const traefikDir = join57(process.env.HOME || "", ".panopticon", "traefik");
17205
+ if (existsSync57(traefikDir)) {
16597
17206
  try {
16598
- const composeFile = join56(traefikDir, "docker-compose.yml");
16599
- if (existsSync56(composeFile)) {
16600
- const content = readFileSync49(composeFile, "utf-8");
17207
+ const composeFile = join57(traefikDir, "docker-compose.yml");
17208
+ if (existsSync57(composeFile)) {
17209
+ const content = readFileSync50(composeFile, "utf-8");
16601
17210
  if (!content.includes("external: true") && content.includes("panopticon:")) {
16602
17211
  const patched = content.replace(
16603
17212
  /networks:\s*\n\s*panopticon:\s*\n\s*name: panopticon\s*\n\s*driver: bridge/,
@@ -16605,43 +17214,43 @@ program.command("up").description("Start dashboard (and Traefik if enabled)").op
16605
17214
  );
16606
17215
  const { writeFileSync: writeFileSync27 } = await import("fs");
16607
17216
  writeFileSync27(composeFile, patched);
16608
- console.log(chalk61.dim(" (migrated network config)"));
17217
+ console.log(chalk62.dim(" (migrated network config)"));
16609
17218
  }
16610
17219
  }
16611
- console.log(chalk61.dim("Starting Traefik..."));
17220
+ console.log(chalk62.dim("Starting Traefik..."));
16612
17221
  execSync9("docker compose up -d", {
16613
17222
  cwd: traefikDir,
16614
17223
  stdio: "pipe"
16615
17224
  });
16616
- console.log(chalk61.green("\u2713 Traefik started"));
16617
- console.log(chalk61.dim(` Dashboard: https://traefik.${traefikDomain}:8080
17225
+ console.log(chalk62.green("\u2713 Traefik started"));
17226
+ console.log(chalk62.dim(` Dashboard: https://traefik.${traefikDomain}:8080
16618
17227
  `));
16619
17228
  } catch (error) {
16620
- console.log(chalk61.yellow("\u26A0 Failed to start Traefik (continuing anyway)"));
16621
- console.log(chalk61.dim(" Run with --skip-traefik to suppress this message\n"));
17229
+ console.log(chalk62.yellow("\u26A0 Failed to start Traefik (continuing anyway)"));
17230
+ console.log(chalk62.dim(" Run with --skip-traefik to suppress this message\n"));
16622
17231
  }
16623
17232
  }
16624
17233
  }
16625
- const isProduction = existsSync56(bundledServer);
16626
- const isDevelopment = existsSync56(srcDashboard);
17234
+ const isProduction = existsSync57(bundledServer);
17235
+ const isDevelopment = existsSync57(srcDashboard);
16627
17236
  if (!isProduction && !isDevelopment) {
16628
- console.error(chalk61.red("Error: Dashboard not found"));
16629
- console.error(chalk61.dim("This may be a corrupted installation. Try reinstalling panopticon-cli."));
17237
+ console.error(chalk62.red("Error: Dashboard not found"));
17238
+ console.error(chalk62.dim("This may be a corrupted installation. Try reinstalling panopticon-cli."));
16630
17239
  process.exit(1);
16631
17240
  }
16632
17241
  if (isDevelopment && !isProduction) {
16633
17242
  try {
16634
17243
  execSync9("npm --version", { stdio: "pipe" });
16635
17244
  } catch {
16636
- console.error(chalk61.red("Error: npm not found in PATH"));
16637
- console.error(chalk61.dim("Make sure Node.js and npm are installed and in your PATH"));
17245
+ console.error(chalk62.red("Error: npm not found in PATH"));
17246
+ console.error(chalk62.dim("Make sure Node.js and npm are installed and in your PATH"));
16638
17247
  process.exit(1);
16639
17248
  }
16640
17249
  }
16641
17250
  if (isProduction) {
16642
- console.log(chalk61.dim("Starting dashboard (bundled mode)..."));
17251
+ console.log(chalk62.dim("Starting dashboard (bundled mode)..."));
16643
17252
  } else {
16644
- console.log(chalk61.dim("Starting dashboard (development mode)..."));
17253
+ console.log(chalk62.dim("Starting dashboard (development mode)..."));
16645
17254
  }
16646
17255
  if (options.detach) {
16647
17256
  const child = isProduction ? spawn2("node", [bundledServer], {
@@ -16657,7 +17266,7 @@ program.command("up").description("Start dashboard (and Traefik if enabled)").op
16657
17266
  let hasError = false;
16658
17267
  child.on("error", (err) => {
16659
17268
  hasError = true;
16660
- console.error(chalk61.red("Failed to start dashboard in background:"), err.message);
17269
+ console.error(chalk62.red("Failed to start dashboard in background:"), err.message);
16661
17270
  process.exit(1);
16662
17271
  });
16663
17272
  setTimeout(() => {
@@ -16665,23 +17274,23 @@ program.command("up").description("Start dashboard (and Traefik if enabled)").op
16665
17274
  child.unref();
16666
17275
  }
16667
17276
  }, 100);
16668
- console.log(chalk61.green("\u2713 Dashboard started in background"));
17277
+ console.log(chalk62.green("\u2713 Dashboard started in background"));
16669
17278
  if (traefikEnabled) {
16670
- console.log(` Frontend: ${chalk61.cyan(`https://${traefikDomain}`)}`);
16671
- console.log(` API: ${chalk61.cyan(`https://${traefikDomain}/api`)}`);
17279
+ console.log(` Frontend: ${chalk62.cyan(`https://${traefikDomain}`)}`);
17280
+ console.log(` API: ${chalk62.cyan(`https://${traefikDomain}/api`)}`);
16672
17281
  } else {
16673
- console.log(` Frontend: ${chalk61.cyan(`http://localhost:${dashboardPort}`)}`);
16674
- console.log(` API: ${chalk61.cyan(`http://localhost:${dashboardApiPort}`)}`);
17282
+ console.log(` Frontend: ${chalk62.cyan(`http://localhost:${dashboardPort}`)}`);
17283
+ console.log(` API: ${chalk62.cyan(`http://localhost:${dashboardApiPort}`)}`);
16675
17284
  }
16676
17285
  } else {
16677
17286
  if (traefikEnabled) {
16678
- console.log(` Frontend: ${chalk61.cyan(`https://${traefikDomain}`)}`);
16679
- console.log(` API: ${chalk61.cyan(`https://${traefikDomain}/api`)}`);
17287
+ console.log(` Frontend: ${chalk62.cyan(`https://${traefikDomain}`)}`);
17288
+ console.log(` API: ${chalk62.cyan(`https://${traefikDomain}/api`)}`);
16680
17289
  } else {
16681
- console.log(` Frontend: ${chalk61.cyan(`http://localhost:${dashboardPort}`)}`);
16682
- console.log(` API: ${chalk61.cyan(`http://localhost:${dashboardApiPort}`)}`);
17290
+ console.log(` Frontend: ${chalk62.cyan(`http://localhost:${dashboardPort}`)}`);
17291
+ console.log(` API: ${chalk62.cyan(`http://localhost:${dashboardApiPort}`)}`);
16683
17292
  }
16684
- console.log(chalk61.dim("\nPress Ctrl+C to stop\n"));
17293
+ console.log(chalk62.dim("\nPress Ctrl+C to stop\n"));
16685
17294
  const child = isProduction ? spawn2("node", [bundledServer], {
16686
17295
  stdio: "inherit",
16687
17296
  env: { ...process.env, DASHBOARD_PORT: String(dashboardPort) }
@@ -16691,41 +17300,41 @@ program.command("up").description("Start dashboard (and Traefik if enabled)").op
16691
17300
  shell: true
16692
17301
  });
16693
17302
  child.on("error", (err) => {
16694
- console.error(chalk61.red("Failed to start dashboard:"), err.message);
17303
+ console.error(chalk62.red("Failed to start dashboard:"), err.message);
16695
17304
  process.exit(1);
16696
17305
  });
16697
17306
  }
16698
17307
  try {
16699
17308
  const { getTldrDaemonService: getTldrDaemonService2 } = await import("../tldr-daemon-T3THOUGT.js");
16700
17309
  const projectRoot = process.cwd();
16701
- const venvPath = join56(projectRoot, ".venv");
16702
- if (existsSync56(venvPath)) {
16703
- console.log(chalk61.dim("\nStarting TLDR daemon for project root..."));
17310
+ const venvPath = join57(projectRoot, ".venv");
17311
+ if (existsSync57(venvPath)) {
17312
+ console.log(chalk62.dim("\nStarting TLDR daemon for project root..."));
16704
17313
  const tldrService = getTldrDaemonService2(projectRoot, venvPath);
16705
17314
  await tldrService.start(true);
16706
- console.log(chalk61.green("\u2713 TLDR daemon started"));
17315
+ console.log(chalk62.green("\u2713 TLDR daemon started"));
16707
17316
  } else {
16708
- console.log(chalk61.dim("\nSkipping TLDR daemon (no .venv found)"));
16709
- console.log(chalk61.dim(" Run setup to create venv with llm-tldr"));
17317
+ console.log(chalk62.dim("\nSkipping TLDR daemon (no .venv found)"));
17318
+ console.log(chalk62.dim(" Run setup to create venv with llm-tldr"));
16710
17319
  }
16711
17320
  } catch (error) {
16712
- console.log(chalk61.yellow("\u26A0 Failed to start TLDR daemon:"), error?.message || String(error));
16713
- console.log(chalk61.dim(" TLDR will be unavailable but dashboard will work normally"));
17321
+ console.log(chalk62.yellow("\u26A0 Failed to start TLDR daemon:"), error?.message || String(error));
17322
+ console.log(chalk62.dim(" TLDR will be unavailable but dashboard will work normally"));
16714
17323
  }
16715
17324
  });
16716
17325
  program.command("down").description("Stop dashboard (and Traefik if enabled)").option("--skip-traefik", "Skip Traefik shutdown").action(async (options) => {
16717
17326
  const { execSync: execSync9 } = await import("child_process");
16718
- const { join: join56 } = await import("path");
16719
- const { readFileSync: readFileSync49, existsSync: existsSync56 } = await import("fs");
17327
+ const { join: join57 } = await import("path");
17328
+ const { readFileSync: readFileSync50, existsSync: existsSync57 } = await import("fs");
16720
17329
  const { parse } = await import("@iarna/toml");
16721
- console.log(chalk61.bold("Stopping Panopticon...\n"));
16722
- const configFile = join56(process.env.HOME || "", ".panopticon", "config.toml");
17330
+ console.log(chalk62.bold("Stopping Panopticon...\n"));
17331
+ const configFile = join57(process.env.HOME || "", ".panopticon", "config.toml");
16723
17332
  let traefikEnabled = false;
16724
17333
  let dashboardPort = 3010;
16725
17334
  let dashboardApiPort = 3011;
16726
- if (existsSync56(configFile)) {
17335
+ if (existsSync57(configFile)) {
16727
17336
  try {
16728
- const configContent = readFileSync49(configFile, "utf-8");
17337
+ const configContent = readFileSync50(configFile, "utf-8");
16729
17338
  const config2 = parse(configContent);
16730
17339
  traefikEnabled = config2.traefik?.enabled === true;
16731
17340
  dashboardPort = config2.dashboard?.port || 3010;
@@ -16733,44 +17342,44 @@ program.command("down").description("Stop dashboard (and Traefik if enabled)").o
16733
17342
  } catch (error) {
16734
17343
  }
16735
17344
  }
16736
- console.log(chalk61.dim("Stopping dashboard..."));
17345
+ console.log(chalk62.dim("Stopping dashboard..."));
16737
17346
  try {
16738
17347
  execSync9(`lsof -ti:${dashboardPort} | xargs kill -9 2>/dev/null || true`, { stdio: "pipe" });
16739
17348
  execSync9(`lsof -ti:${dashboardApiPort} | xargs kill -9 2>/dev/null || true`, { stdio: "pipe" });
16740
- console.log(chalk61.green("\u2713 Dashboard stopped"));
17349
+ console.log(chalk62.green("\u2713 Dashboard stopped"));
16741
17350
  } catch {
16742
- console.log(chalk61.dim(" No dashboard processes found"));
17351
+ console.log(chalk62.dim(" No dashboard processes found"));
16743
17352
  }
16744
17353
  if (traefikEnabled && !options.skipTraefik) {
16745
- const traefikDir = join56(process.env.HOME || "", ".panopticon", "traefik");
16746
- if (existsSync56(traefikDir)) {
16747
- console.log(chalk61.dim("Stopping Traefik..."));
17354
+ const traefikDir = join57(process.env.HOME || "", ".panopticon", "traefik");
17355
+ if (existsSync57(traefikDir)) {
17356
+ console.log(chalk62.dim("Stopping Traefik..."));
16748
17357
  try {
16749
17358
  execSync9("docker compose down", {
16750
17359
  cwd: traefikDir,
16751
17360
  stdio: "pipe"
16752
17361
  });
16753
- console.log(chalk61.green("\u2713 Traefik stopped"));
17362
+ console.log(chalk62.green("\u2713 Traefik stopped"));
16754
17363
  } catch (error) {
16755
- console.log(chalk61.yellow("\u26A0 Failed to stop Traefik"));
17364
+ console.log(chalk62.yellow("\u26A0 Failed to stop Traefik"));
16756
17365
  }
16757
17366
  }
16758
17367
  }
16759
17368
  try {
16760
17369
  const { getTldrDaemonService: getTldrDaemonService2 } = await import("../tldr-daemon-T3THOUGT.js");
16761
- const { exec: exec23 } = await import("child_process");
16762
- const { promisify: promisify23 } = await import("util");
16763
- const execAsync23 = promisify23(exec23);
17370
+ const { exec: exec24 } = await import("child_process");
17371
+ const { promisify: promisify24 } = await import("util");
17372
+ const execAsync24 = promisify24(exec24);
16764
17373
  const projectRoot = process.cwd();
16765
- const venvPath = join56(projectRoot, ".venv");
16766
- if (existsSync56(venvPath)) {
16767
- console.log(chalk61.dim("\nStopping TLDR daemon..."));
17374
+ const venvPath = join57(projectRoot, ".venv");
17375
+ if (existsSync57(venvPath)) {
17376
+ console.log(chalk62.dim("\nStopping TLDR daemon..."));
16768
17377
  const tldrService = getTldrDaemonService2(projectRoot, venvPath);
16769
17378
  await tldrService.stop();
16770
- console.log(chalk61.green("\u2713 TLDR daemon stopped"));
17379
+ console.log(chalk62.green("\u2713 TLDR daemon stopped"));
16771
17380
  }
16772
17381
  } catch (error) {
16773
- console.log(chalk61.dim(" (TLDR daemon not running)"));
17382
+ console.log(chalk62.dim(" (TLDR daemon not running)"));
16774
17383
  }
16775
17384
  console.log("");
16776
17385
  });
@@ -16782,5 +17391,9 @@ project.command("remove <nameOrPath>").description("Remove a project from the re
16782
17391
  project.command("init").description("Initialize projects.yaml with example configuration").action(projectInitCommand);
16783
17392
  program.command("doctor").description("Check system health and dependencies").action(doctorCommand);
16784
17393
  program.command("update").description("Update Panopticon to latest version").option("--check", "Only check for updates, don't install").option("--force", "Force update even if on latest").action(updateCommand2);
16785
- program.parse();
17394
+ program.addCommand(createCostCommand());
17395
+ program.command("sync-costs").description("Import cost events from per-project WAL files (alias for: pan cost sync)").action(async () => {
17396
+ await program.parseAsync(["cost", "sync"], { from: "user" });
17397
+ });
17398
+ await program.parseAsync();
16786
17399
  //# sourceMappingURL=index.js.map