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