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