panopticon-cli 0.5.9 → 0.5.11
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/dist/{agents-M2ZOZL3P.js → agents-RL2KUSP3.js} +3 -3
- package/dist/{archive-planning-U3AZAKWI.js → archive-planning-54J6EP6A.js} +3 -3
- package/dist/{chunk-WEQW3EAT.js → chunk-F4XS2FQN.js} +3 -2
- package/dist/chunk-F4XS2FQN.js.map +1 -0
- package/dist/{chunk-OJF4QS3S.js → chunk-GIW2TUWI.js} +2 -2
- package/dist/{chunk-GM22HPYS.js → chunk-H7T35QDO.js} +21 -3
- package/dist/chunk-H7T35QDO.js.map +1 -0
- package/dist/{chunk-MJXYTGK5.js → chunk-JZWCL5S5.js} +2 -2
- package/dist/{chunk-3WDSD2VK.js → chunk-NLN3ZLCN.js} +186 -88
- package/dist/chunk-NLN3ZLCN.js.map +1 -0
- package/dist/{chunk-QQ27EVBD.js → chunk-OMOEGJDB.js} +3 -3
- package/dist/{chunk-4R6ATXYI.js → chunk-PFA5XE2V.js} +1 -37
- package/dist/chunk-PFA5XE2V.js.map +1 -0
- package/dist/{chunk-6OYUJ4AJ.js → chunk-R47UJWF6.js} +2 -2
- package/dist/{chunk-KPGVCGST.js → chunk-S7EJ2OLR.js} +10 -4
- package/dist/chunk-S7EJ2OLR.js.map +1 -0
- package/dist/{chunk-R4KPLLRB.js → chunk-SFX3BG6N.js} +1 -1
- package/dist/chunk-SFX3BG6N.js.map +1 -0
- package/dist/clean-planning-V4SSVU26.js +9 -0
- package/dist/cli/index.js +1111 -901
- package/dist/cli/index.js.map +1 -1
- package/dist/close-issue-5OMOP2FU.js +9 -0
- package/dist/compact-beads-YQDVF6FQ.js +9 -0
- package/dist/dashboard/prompts/merge-agent.md +11 -0
- package/dist/dashboard/prompts/review-agent.md +9 -0
- package/dist/dashboard/prompts/test-agent.md +9 -0
- package/dist/dashboard/prompts/work-agent.md +10 -2
- package/dist/dashboard/public/assets/index-5hYjhhGn.js +826 -0
- package/dist/dashboard/public/assets/index-DIFh3T1V.css +32 -0
- package/dist/dashboard/public/index.html +2 -2
- package/dist/dashboard/server.js +2405 -1754
- package/dist/index.d.ts +8 -3
- package/dist/index.js +3 -3
- package/dist/{label-cleanup-4HJVX6NP.js → label-cleanup-4IVZIPGK.js} +2 -2
- package/dist/{merge-agent-756U4NPX.js → merge-agent-7L7MWJEC.js} +12 -12
- package/dist/{specialist-context-UBVUUFJV.js → specialist-context-L37RF6Z5.js} +3 -3
- package/dist/{specialist-logs-FQRI3AIS.js → specialist-logs-B7UC3UDO.js} +3 -3
- package/dist/{specialists-CXRGSJY3.js → specialists-X4OGA7WX.js} +3 -3
- package/dist/{workspace-manager-OWHLR5BL.js → workspace-manager-6RP5A5HF.js} +2 -2
- package/package.json +1 -1
- package/skills/pan-new-project/SKILL.md +1 -1
- package/skills/pan-oversee/SKILL.md +45 -10
- package/skills/plan/SKILL.md +336 -0
- package/dist/chunk-3WDSD2VK.js.map +0 -1
- package/dist/chunk-4R6ATXYI.js.map +0 -1
- package/dist/chunk-GM22HPYS.js.map +0 -1
- package/dist/chunk-KPGVCGST.js.map +0 -1
- package/dist/chunk-R4KPLLRB.js.map +0 -1
- package/dist/chunk-WEQW3EAT.js.map +0 -1
- package/dist/clean-planning-7Z5YY64X.js +0 -9
- package/dist/close-issue-CTZK777I.js +0 -9
- package/dist/compact-beads-72SHALOL.js +0 -9
- package/dist/dashboard/public/assets/index-Bx4NCn9A.css +0 -32
- package/dist/dashboard/public/assets/index-DqPey4Of.js +0 -756
- package/skills/opus-plan/SKILL.md +0 -400
- /package/dist/{agents-M2ZOZL3P.js.map → agents-RL2KUSP3.js.map} +0 -0
- /package/dist/{archive-planning-U3AZAKWI.js.map → archive-planning-54J6EP6A.js.map} +0 -0
- /package/dist/{chunk-OJF4QS3S.js.map → chunk-GIW2TUWI.js.map} +0 -0
- /package/dist/{chunk-MJXYTGK5.js.map → chunk-JZWCL5S5.js.map} +0 -0
- /package/dist/{chunk-QQ27EVBD.js.map → chunk-OMOEGJDB.js.map} +0 -0
- /package/dist/{chunk-6OYUJ4AJ.js.map → chunk-R47UJWF6.js.map} +0 -0
- /package/dist/{clean-planning-7Z5YY64X.js.map → clean-planning-V4SSVU26.js.map} +0 -0
- /package/dist/{close-issue-CTZK777I.js.map → close-issue-5OMOP2FU.js.map} +0 -0
- /package/dist/{compact-beads-72SHALOL.js.map → compact-beads-YQDVF6FQ.js.map} +0 -0
- /package/dist/{label-cleanup-4HJVX6NP.js.map → label-cleanup-4IVZIPGK.js.map} +0 -0
- /package/dist/{merge-agent-756U4NPX.js.map → merge-agent-7L7MWJEC.js.map} +0 -0
- /package/dist/{specialist-context-UBVUUFJV.js.map → specialist-context-L37RF6Z5.js.map} +0 -0
- /package/dist/{specialist-logs-FQRI3AIS.js.map → specialist-logs-B7UC3UDO.js.map} +0 -0
- /package/dist/{specialists-CXRGSJY3.js.map → specialists-X4OGA7WX.js.map} +0 -0
- /package/dist/{workspace-manager-OWHLR5BL.js.map → workspace-manager-6RP5A5HF.js.map} +0 -0
package/dist/cli/index.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
closeIssue
|
|
4
|
-
} from "../chunk-
|
|
5
|
-
import "../chunk-
|
|
4
|
+
} from "../chunk-GIW2TUWI.js";
|
|
5
|
+
import "../chunk-JZWCL5S5.js";
|
|
6
6
|
import {
|
|
7
7
|
cleanupTemplateFiles,
|
|
8
8
|
ensureProjectCerts,
|
|
@@ -54,28 +54,32 @@ import {
|
|
|
54
54
|
getTmuxSessionName,
|
|
55
55
|
getWeeklySummary,
|
|
56
56
|
init_cost,
|
|
57
|
+
init_io,
|
|
57
58
|
init_jsonl_parser,
|
|
58
59
|
init_specialists,
|
|
59
60
|
parseClaudeSession,
|
|
60
61
|
readIssueCosts,
|
|
61
62
|
readTodayCosts,
|
|
63
|
+
readWorkspacePlan,
|
|
62
64
|
recordWake,
|
|
63
65
|
setSessionId,
|
|
64
66
|
spawnEphemeralSpecialist,
|
|
65
67
|
summarizeCosts,
|
|
68
|
+
updateItemStatus,
|
|
69
|
+
updateSubItemStatus,
|
|
66
70
|
wakeSpecialistOrQueue
|
|
67
|
-
} from "../chunk-
|
|
71
|
+
} from "../chunk-NLN3ZLCN.js";
|
|
68
72
|
import "../chunk-JQBV3Q2W.js";
|
|
69
73
|
import {
|
|
70
74
|
archivePlanning,
|
|
71
75
|
findWorkspacePath
|
|
72
|
-
} from "../chunk-
|
|
73
|
-
import "../chunk-
|
|
76
|
+
} from "../chunk-R47UJWF6.js";
|
|
77
|
+
import "../chunk-F4XS2FQN.js";
|
|
74
78
|
import {
|
|
75
79
|
stepFailed,
|
|
76
80
|
stepOk,
|
|
77
81
|
stepSkipped
|
|
78
|
-
} from "../chunk-
|
|
82
|
+
} from "../chunk-SFX3BG6N.js";
|
|
79
83
|
import {
|
|
80
84
|
getTldrDaemonService,
|
|
81
85
|
getTldrMetrics,
|
|
@@ -88,7 +92,7 @@ import {
|
|
|
88
92
|
init_workspace_manager,
|
|
89
93
|
mergeSkillsIntoWorkspace,
|
|
90
94
|
removeWorkspace
|
|
91
|
-
} from "../chunk-
|
|
95
|
+
} from "../chunk-S7EJ2OLR.js";
|
|
92
96
|
import {
|
|
93
97
|
detectDnsSyncMethod,
|
|
94
98
|
detectPlatform,
|
|
@@ -130,7 +134,7 @@ import {
|
|
|
130
134
|
saveSessionId,
|
|
131
135
|
spawnAgent,
|
|
132
136
|
stopAgent
|
|
133
|
-
} from "../chunk-
|
|
137
|
+
} from "../chunk-OMOEGJDB.js";
|
|
134
138
|
import {
|
|
135
139
|
checkHook,
|
|
136
140
|
clearHook,
|
|
@@ -141,7 +145,7 @@ import {
|
|
|
141
145
|
popFromHook,
|
|
142
146
|
pushToHook,
|
|
143
147
|
sendMail
|
|
144
|
-
} from "../chunk-
|
|
148
|
+
} from "../chunk-PFA5XE2V.js";
|
|
145
149
|
import {
|
|
146
150
|
createShadowState,
|
|
147
151
|
getPendingSyncCount,
|
|
@@ -163,14 +167,14 @@ import {
|
|
|
163
167
|
getAliasInstructions,
|
|
164
168
|
getShellRcFile,
|
|
165
169
|
listBackups,
|
|
166
|
-
|
|
170
|
+
migrateStalePersonalContent,
|
|
167
171
|
planHooksSync,
|
|
168
172
|
planSync,
|
|
169
173
|
refreshCache,
|
|
170
174
|
restoreBackup,
|
|
171
175
|
syncHooks,
|
|
172
176
|
syncStatusline
|
|
173
|
-
} from "../chunk-
|
|
177
|
+
} from "../chunk-H7T35QDO.js";
|
|
174
178
|
import "../chunk-AQXETQHW.js";
|
|
175
179
|
import {
|
|
176
180
|
init_settings,
|
|
@@ -247,8 +251,8 @@ import {
|
|
|
247
251
|
|
|
248
252
|
// src/cli/index.ts
|
|
249
253
|
init_esm_shims();
|
|
250
|
-
import { readFileSync as
|
|
251
|
-
import { join as
|
|
254
|
+
import { readFileSync as readFileSync48, existsSync as existsSync54 } from "fs";
|
|
255
|
+
import { join as join54 } from "path";
|
|
252
256
|
import { homedir as homedir24 } from "os";
|
|
253
257
|
import { Command as Command2 } from "commander";
|
|
254
258
|
import chalk63 from "chalk";
|
|
@@ -594,7 +598,7 @@ async function syncCommand(options) {
|
|
|
594
598
|
if (cleanupResult.cleaned.length > 0) {
|
|
595
599
|
console.log(chalk2.dim(`Removed ${cleanupResult.total} legacy runtime symlink(s): ${cleanupResult.cleaned.join(", ")}`));
|
|
596
600
|
}
|
|
597
|
-
const migration =
|
|
601
|
+
const migration = migrateStalePersonalContent();
|
|
598
602
|
if (migration.removedSymlinks.length > 0) {
|
|
599
603
|
console.log(chalk2.cyan(`Migrated: removed ${migration.removedSymlinks.length} Panopticon symlink(s) from ~/.claude/`));
|
|
600
604
|
if (migration.preservedUserContent.length > 0) {
|
|
@@ -1119,6 +1123,31 @@ This workspace was created by Panopticon. Use \`bd\` commands to track your work
|
|
|
1119
1123
|
|
|
1120
1124
|
// src/lib/cloister/work-agent-prompt.ts
|
|
1121
1125
|
init_projects();
|
|
1126
|
+
init_io();
|
|
1127
|
+
|
|
1128
|
+
// src/lib/vbrief/acceptance-criteria.ts
|
|
1129
|
+
init_esm_shims();
|
|
1130
|
+
init_io();
|
|
1131
|
+
function extractACFromDocument(doc) {
|
|
1132
|
+
const criteria = [];
|
|
1133
|
+
for (const item of doc.plan.items) {
|
|
1134
|
+
if (!item.subItems) continue;
|
|
1135
|
+
for (const sub of item.subItems) {
|
|
1136
|
+
if (sub.metadata?.kind === "acceptance_criterion") {
|
|
1137
|
+
criteria.push({
|
|
1138
|
+
itemId: item.id,
|
|
1139
|
+
itemTitle: item.title,
|
|
1140
|
+
subItemId: sub.id,
|
|
1141
|
+
title: sub.title,
|
|
1142
|
+
status: sub.status
|
|
1143
|
+
});
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
}
|
|
1147
|
+
return criteria;
|
|
1148
|
+
}
|
|
1149
|
+
|
|
1150
|
+
// src/lib/cloister/work-agent-prompt.ts
|
|
1122
1151
|
init_config();
|
|
1123
1152
|
init_factory();
|
|
1124
1153
|
init_interface();
|
|
@@ -1368,6 +1397,7 @@ function readBeadsTasks(workspacePath, projectRoot, issueId) {
|
|
|
1368
1397
|
const normalizedId = issueId.toLowerCase();
|
|
1369
1398
|
const stateContent = readPlanningContext(workspacePath);
|
|
1370
1399
|
const beadsIds = stateContent ? extractBeadsIdsFromState(stateContent) : [];
|
|
1400
|
+
const acByTitle = buildACLookupByTitle(workspacePath);
|
|
1371
1401
|
const beadsPaths = [
|
|
1372
1402
|
join7(workspacePath, ".beads", "issues.jsonl"),
|
|
1373
1403
|
join7(projectRoot, ".beads", "issues.jsonl")
|
|
@@ -1382,11 +1412,16 @@ function readBeadsTasks(workspacePath, projectRoot, issueId) {
|
|
|
1382
1412
|
try {
|
|
1383
1413
|
const task = JSON.parse(line);
|
|
1384
1414
|
if (seenIds.has(task.id)) continue;
|
|
1385
|
-
const
|
|
1386
|
-
const isMatch = beadsIds.includes(task.id) ||
|
|
1415
|
+
const labels = task.labels || task.tags || [];
|
|
1416
|
+
const isMatch = beadsIds.includes(task.id) || labels.some((t) => t.toLowerCase().includes(normalizedId)) || task.title?.toLowerCase().includes(normalizedId);
|
|
1387
1417
|
if (isMatch) {
|
|
1388
1418
|
seenIds.add(task.id);
|
|
1389
1419
|
tasks.push(`- [${task.status || "open"}] ${task.title} (${task.id})`);
|
|
1420
|
+
const beadAC = matchBeadToAC(task.title, acByTitle);
|
|
1421
|
+
for (const ac of beadAC) {
|
|
1422
|
+
const check = ac.status === "completed" ? "x" : " ";
|
|
1423
|
+
tasks.push(` - [${check}] AC: ${ac.title}`);
|
|
1424
|
+
}
|
|
1390
1425
|
}
|
|
1391
1426
|
} catch {
|
|
1392
1427
|
}
|
|
@@ -1396,6 +1431,27 @@ function readBeadsTasks(workspacePath, projectRoot, issueId) {
|
|
|
1396
1431
|
}
|
|
1397
1432
|
return tasks;
|
|
1398
1433
|
}
|
|
1434
|
+
function buildACLookupByTitle(workspacePath) {
|
|
1435
|
+
const lookup = /* @__PURE__ */ new Map();
|
|
1436
|
+
const doc = readWorkspacePlan(workspacePath);
|
|
1437
|
+
if (!doc) return lookup;
|
|
1438
|
+
const criteria = extractACFromDocument(doc);
|
|
1439
|
+
for (const ac of criteria) {
|
|
1440
|
+
const key = ac.itemTitle.toLowerCase();
|
|
1441
|
+
let list = lookup.get(key);
|
|
1442
|
+
if (!list) {
|
|
1443
|
+
list = [];
|
|
1444
|
+
lookup.set(key, list);
|
|
1445
|
+
}
|
|
1446
|
+
list.push({ title: ac.title, status: ac.status });
|
|
1447
|
+
}
|
|
1448
|
+
return lookup;
|
|
1449
|
+
}
|
|
1450
|
+
function matchBeadToAC(beadTitle, acByTitle) {
|
|
1451
|
+
if (acByTitle.size === 0 || !beadTitle) return [];
|
|
1452
|
+
const stripped = beadTitle.replace(/^[A-Z]+-\d+:\s*/, "");
|
|
1453
|
+
return acByTitle.get(stripped.toLowerCase()) || [];
|
|
1454
|
+
}
|
|
1399
1455
|
function buildPolyrepoContext(issueId, workspacePath) {
|
|
1400
1456
|
const teamPrefix = extractTeamPrefix(issueId);
|
|
1401
1457
|
const projectConfig = teamPrefix ? findProjectByTeam(teamPrefix) : null;
|
|
@@ -1688,8 +1744,8 @@ function findProjectRoot(issueId, labels = []) {
|
|
|
1688
1744
|
}
|
|
1689
1745
|
function hasBeadsTasks(workspacePath) {
|
|
1690
1746
|
try {
|
|
1691
|
-
const { execSync:
|
|
1692
|
-
const output =
|
|
1747
|
+
const { execSync: execSync9 } = __require("child_process");
|
|
1748
|
+
const output = execSync9("bd list --json --limit 1", {
|
|
1693
1749
|
cwd: workspacePath,
|
|
1694
1750
|
encoding: "utf-8",
|
|
1695
1751
|
timeout: 1e4,
|
|
@@ -1756,23 +1812,46 @@ async function issueCommand(id, options) {
|
|
|
1756
1812
|
process.exit(1);
|
|
1757
1813
|
}
|
|
1758
1814
|
try {
|
|
1759
|
-
const { execSync:
|
|
1760
|
-
const branch =
|
|
1815
|
+
const { execSync: execSync9 } = await import("child_process");
|
|
1816
|
+
const branch = execSync9("git branch --show-current", {
|
|
1761
1817
|
cwd: workspace,
|
|
1762
1818
|
encoding: "utf8"
|
|
1763
1819
|
}).trim();
|
|
1764
1820
|
if (branch === "main" || branch === "master") {
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1821
|
+
const { readdirSync: readdirSync22, statSync: statSync12 } = await import("fs");
|
|
1822
|
+
let hasFeatureBranch = false;
|
|
1823
|
+
try {
|
|
1824
|
+
const entries = readdirSync22(workspace);
|
|
1825
|
+
for (const entry of entries) {
|
|
1826
|
+
const subPath = join8(workspace, entry);
|
|
1827
|
+
if (statSync12(subPath).isDirectory() && existsSync8(join8(subPath, ".git"))) {
|
|
1828
|
+
const subBranch = execSync9("git branch --show-current", {
|
|
1829
|
+
cwd: subPath,
|
|
1830
|
+
encoding: "utf8"
|
|
1831
|
+
}).trim();
|
|
1832
|
+
if (subBranch !== "main" && subBranch !== "master" && subBranch.length > 0) {
|
|
1833
|
+
hasFeatureBranch = true;
|
|
1834
|
+
spinner.text = `Found polyrepo workspace \u2014 sub-repo ${entry} on branch: ${subBranch}`;
|
|
1835
|
+
break;
|
|
1836
|
+
}
|
|
1837
|
+
}
|
|
1838
|
+
}
|
|
1839
|
+
} catch {
|
|
1840
|
+
}
|
|
1841
|
+
if (!hasFeatureBranch) {
|
|
1842
|
+
spinner.fail(`Workspace is on ${branch} branch`);
|
|
1843
|
+
console.log("");
|
|
1844
|
+
console.log(chalk6.red("CRITICAL: Work agents must NOT run on main/master branch."));
|
|
1845
|
+
console.log(chalk6.red("This bypasses the entire review/test/merge workflow."));
|
|
1846
|
+
console.log("");
|
|
1847
|
+
console.log(chalk6.bold("To fix:"));
|
|
1848
|
+
console.log(` 1. Create a proper workspace: ${chalk6.cyan(`pan workspace ${id}`)}`);
|
|
1849
|
+
console.log(` 2. Or checkout a feature branch: ${chalk6.cyan(`git checkout -b feature/${normalizedId}`)}`);
|
|
1850
|
+
process.exit(1);
|
|
1851
|
+
}
|
|
1852
|
+
} else {
|
|
1853
|
+
spinner.text = `Found workspace on branch: ${branch}`;
|
|
1774
1854
|
}
|
|
1775
|
-
spinner.text = `Found workspace on branch: ${branch}`;
|
|
1776
1855
|
} catch (e) {
|
|
1777
1856
|
spinner.warn("Could not verify branch - ensure you are NOT on main");
|
|
1778
1857
|
}
|
|
@@ -1806,8 +1885,8 @@ async function issueCommand(id, options) {
|
|
|
1806
1885
|
if (!hasPlanningDir) {
|
|
1807
1886
|
spinner.text = `Auto-creating bead for simple issue ${id}...`;
|
|
1808
1887
|
try {
|
|
1809
|
-
const { execSync:
|
|
1810
|
-
|
|
1888
|
+
const { execSync: execSync9 } = __require("child_process");
|
|
1889
|
+
execSync9(`bd create "${id}: Implement issue" --type task -l "${id.toLowerCase()},difficulty:simple"`, {
|
|
1811
1890
|
cwd: workspace,
|
|
1812
1891
|
encoding: "utf-8",
|
|
1813
1892
|
timeout: 1e4,
|
|
@@ -2348,11 +2427,94 @@ init_agents();
|
|
|
2348
2427
|
init_paths();
|
|
2349
2428
|
import chalk12 from "chalk";
|
|
2350
2429
|
import ora6 from "ora";
|
|
2351
|
-
import { existsSync as
|
|
2352
|
-
import { join as
|
|
2430
|
+
import { existsSync as existsSync13, writeFileSync as writeFileSync4, readFileSync as readFileSync11, mkdirSync as mkdirSync4, readdirSync as readdirSync11 } from "fs";
|
|
2431
|
+
import { join as join13 } from "path";
|
|
2353
2432
|
import { homedir as homedir5 } from "os";
|
|
2433
|
+
import { exec as exec2 } from "child_process";
|
|
2434
|
+
import { promisify as promisify2 } from "util";
|
|
2435
|
+
|
|
2436
|
+
// src/lib/vbrief/beads.ts
|
|
2437
|
+
init_esm_shims();
|
|
2438
|
+
init_io();
|
|
2354
2439
|
import { exec } from "child_process";
|
|
2355
2440
|
import { promisify } from "util";
|
|
2441
|
+
import { existsSync as existsSync12, readFileSync as readFileSync10 } from "fs";
|
|
2442
|
+
import { join as join12 } from "path";
|
|
2443
|
+
var execAsync = promisify(exec);
|
|
2444
|
+
function syncBeadStatusToVBrief(beadId, workspacePath, status = "completed") {
|
|
2445
|
+
try {
|
|
2446
|
+
const doc = readWorkspacePlan(workspacePath);
|
|
2447
|
+
if (!doc) return null;
|
|
2448
|
+
const beadsFile = join12(workspacePath, ".beads", "issues.jsonl");
|
|
2449
|
+
if (!existsSync12(beadsFile)) return null;
|
|
2450
|
+
const lines = readFileSync10(beadsFile, "utf-8").split("\n").filter(Boolean);
|
|
2451
|
+
let beadTitle = null;
|
|
2452
|
+
for (const line of lines) {
|
|
2453
|
+
try {
|
|
2454
|
+
const bead = JSON.parse(line);
|
|
2455
|
+
if (bead.id === beadId && bead.title) {
|
|
2456
|
+
beadTitle = bead.title;
|
|
2457
|
+
break;
|
|
2458
|
+
}
|
|
2459
|
+
} catch {
|
|
2460
|
+
}
|
|
2461
|
+
}
|
|
2462
|
+
if (!beadTitle) return null;
|
|
2463
|
+
const planId = doc.plan.id;
|
|
2464
|
+
const prefix = `${planId}: `;
|
|
2465
|
+
const itemTitle = beadTitle.startsWith(prefix) ? beadTitle.slice(prefix.length) : beadTitle;
|
|
2466
|
+
const itemTitleLower = itemTitle.toLowerCase();
|
|
2467
|
+
const matchingItem = doc.plan.items.find(
|
|
2468
|
+
(i) => i.title.toLowerCase() === itemTitleLower
|
|
2469
|
+
);
|
|
2470
|
+
if (!matchingItem) return null;
|
|
2471
|
+
updateItemStatus(workspacePath, matchingItem.id, status);
|
|
2472
|
+
if (status === "completed" && matchingItem.subItems) {
|
|
2473
|
+
for (const sub of matchingItem.subItems) {
|
|
2474
|
+
if (sub.metadata?.kind === "acceptance_criterion" && sub.status !== "completed") {
|
|
2475
|
+
updateSubItemStatus(workspacePath, matchingItem.id, sub.id, "completed");
|
|
2476
|
+
}
|
|
2477
|
+
}
|
|
2478
|
+
}
|
|
2479
|
+
console.log(`[vbrief-sync] Updated item "${matchingItem.id}" to "${status}" from bead ${beadId}`);
|
|
2480
|
+
return matchingItem.id;
|
|
2481
|
+
} catch (err) {
|
|
2482
|
+
console.warn(`[vbrief-sync] Failed to sync bead ${beadId}: ${err.message}`);
|
|
2483
|
+
return null;
|
|
2484
|
+
}
|
|
2485
|
+
}
|
|
2486
|
+
function getVBriefACStatus(workspacePath) {
|
|
2487
|
+
const doc = readWorkspacePlan(workspacePath);
|
|
2488
|
+
if (!doc) return null;
|
|
2489
|
+
const allCriteria = extractACFromDocument(doc);
|
|
2490
|
+
if (allCriteria.length === 0) return null;
|
|
2491
|
+
const itemMap = /* @__PURE__ */ new Map();
|
|
2492
|
+
for (const ac of allCriteria) {
|
|
2493
|
+
let item = itemMap.get(ac.itemId);
|
|
2494
|
+
if (!item) {
|
|
2495
|
+
item = { itemId: ac.itemId, itemTitle: ac.itemTitle, completed: 0, pending: 0, total: 0, criteria: [] };
|
|
2496
|
+
itemMap.set(ac.itemId, item);
|
|
2497
|
+
}
|
|
2498
|
+
item.total++;
|
|
2499
|
+
item.criteria.push(ac);
|
|
2500
|
+
if (ac.status === "completed" || ac.status === "cancelled") {
|
|
2501
|
+
item.completed++;
|
|
2502
|
+
} else {
|
|
2503
|
+
item.pending++;
|
|
2504
|
+
}
|
|
2505
|
+
}
|
|
2506
|
+
const items = Array.from(itemMap.values());
|
|
2507
|
+
const totalCompleted = items.reduce((sum, i) => sum + i.completed, 0);
|
|
2508
|
+
const totalPending = items.reduce((sum, i) => sum + i.pending, 0);
|
|
2509
|
+
const totalCount = totalCompleted + totalPending;
|
|
2510
|
+
return {
|
|
2511
|
+
allCompleted: totalPending === 0,
|
|
2512
|
+
items,
|
|
2513
|
+
totalCompleted,
|
|
2514
|
+
totalPending,
|
|
2515
|
+
totalCount
|
|
2516
|
+
};
|
|
2517
|
+
}
|
|
2356
2518
|
|
|
2357
2519
|
// src/core/state-mapping.ts
|
|
2358
2520
|
init_esm_shims();
|
|
@@ -2484,11 +2646,11 @@ function cleanupWorkflowLabels(currentLabels, targetState) {
|
|
|
2484
2646
|
}
|
|
2485
2647
|
|
|
2486
2648
|
// src/cli/commands/work/done.ts
|
|
2487
|
-
var
|
|
2649
|
+
var execAsync2 = promisify2(exec2);
|
|
2488
2650
|
function getLinearApiKey3() {
|
|
2489
|
-
const envFile =
|
|
2490
|
-
if (
|
|
2491
|
-
const content =
|
|
2651
|
+
const envFile = join13(homedir5(), ".panopticon.env");
|
|
2652
|
+
if (existsSync13(envFile)) {
|
|
2653
|
+
const content = readFileSync11(envFile, "utf-8");
|
|
2492
2654
|
const match = content.match(/LINEAR_API_KEY=(.+)/);
|
|
2493
2655
|
if (match) return match[1].trim();
|
|
2494
2656
|
}
|
|
@@ -2529,9 +2691,9 @@ ${comment}`
|
|
|
2529
2691
|
}
|
|
2530
2692
|
}
|
|
2531
2693
|
function getGitHubConfig() {
|
|
2532
|
-
const envFile =
|
|
2533
|
-
if (!
|
|
2534
|
-
const content =
|
|
2694
|
+
const envFile = join13(homedir5(), ".panopticon.env");
|
|
2695
|
+
if (!existsSync13(envFile)) return null;
|
|
2696
|
+
const content = readFileSync11(envFile, "utf-8");
|
|
2535
2697
|
const tokenMatch = content.match(/GITHUB_TOKEN=(.+)/);
|
|
2536
2698
|
if (!tokenMatch) return null;
|
|
2537
2699
|
const token = tokenMatch[1].trim();
|
|
@@ -2587,13 +2749,13 @@ async function doneCommand(id, options = {}) {
|
|
|
2587
2749
|
const issueId = id.replace(/^agent-/i, "").toUpperCase();
|
|
2588
2750
|
const agentId = `agent-${issueId.toLowerCase()}`;
|
|
2589
2751
|
if (!options.force) {
|
|
2590
|
-
const { getAgentState: getAgentState2 } = await import("../agents-
|
|
2752
|
+
const { getAgentState: getAgentState2 } = await import("../agents-RL2KUSP3.js");
|
|
2591
2753
|
const agentState = getAgentState2(agentId);
|
|
2592
2754
|
const workspacePath = agentState?.workspace;
|
|
2593
|
-
if (workspacePath &&
|
|
2755
|
+
if (workspacePath && existsSync13(workspacePath)) {
|
|
2594
2756
|
const failures = [];
|
|
2595
2757
|
try {
|
|
2596
|
-
const { stdout } = await
|
|
2758
|
+
const { stdout } = await execAsync2(
|
|
2597
2759
|
`bd list --status open -l "${issueId.toLowerCase()}" --limit 0 --json`,
|
|
2598
2760
|
{ cwd: workspacePath }
|
|
2599
2761
|
);
|
|
@@ -2608,10 +2770,10 @@ async function doneCommand(id, options = {}) {
|
|
|
2608
2770
|
}
|
|
2609
2771
|
} catch {
|
|
2610
2772
|
}
|
|
2611
|
-
const hasTopLevelGit =
|
|
2773
|
+
const hasTopLevelGit = existsSync13(join13(workspacePath, ".git"));
|
|
2612
2774
|
if (hasTopLevelGit) {
|
|
2613
2775
|
try {
|
|
2614
|
-
const { stdout } = await
|
|
2776
|
+
const { stdout } = await execAsync2("git status --porcelain", { cwd: workspacePath });
|
|
2615
2777
|
if (stdout.trim()) {
|
|
2616
2778
|
failures.push(" Uncommitted changes:");
|
|
2617
2779
|
for (const line of stdout.trim().split("\n")) {
|
|
@@ -2625,10 +2787,10 @@ async function doneCommand(id, options = {}) {
|
|
|
2625
2787
|
const entries = readdirSync11(workspacePath, { withFileTypes: true });
|
|
2626
2788
|
for (const entry of entries) {
|
|
2627
2789
|
if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
|
|
2628
|
-
const subPath =
|
|
2629
|
-
if (!
|
|
2790
|
+
const subPath = join13(workspacePath, entry.name);
|
|
2791
|
+
if (!existsSync13(join13(subPath, ".git"))) continue;
|
|
2630
2792
|
try {
|
|
2631
|
-
const { stdout } = await
|
|
2793
|
+
const { stdout } = await execAsync2("git status --porcelain", { cwd: subPath });
|
|
2632
2794
|
if (stdout.trim()) {
|
|
2633
2795
|
failures.push(` Uncommitted changes in ${entry.name}/:`);
|
|
2634
2796
|
for (const line of stdout.trim().split("\n")) {
|
|
@@ -2641,6 +2803,22 @@ async function doneCommand(id, options = {}) {
|
|
|
2641
2803
|
} catch {
|
|
2642
2804
|
}
|
|
2643
2805
|
}
|
|
2806
|
+
try {
|
|
2807
|
+
const acStatus = getVBriefACStatus(workspacePath);
|
|
2808
|
+
if (acStatus && !acStatus.allCompleted) {
|
|
2809
|
+
failures.push(` Incomplete acceptance criteria (${acStatus.totalPending}/${acStatus.totalCount}):`);
|
|
2810
|
+
for (const item of acStatus.items) {
|
|
2811
|
+
if (item.pending > 0) {
|
|
2812
|
+
for (const ac of item.criteria) {
|
|
2813
|
+
if (ac.status !== "completed" && ac.status !== "cancelled") {
|
|
2814
|
+
failures.push(` - [ ] ${ac.title} (${item.itemTitle})`);
|
|
2815
|
+
}
|
|
2816
|
+
}
|
|
2817
|
+
}
|
|
2818
|
+
}
|
|
2819
|
+
}
|
|
2820
|
+
} catch {
|
|
2821
|
+
}
|
|
2644
2822
|
if (failures.length > 0) {
|
|
2645
2823
|
console.error(chalk12.red(`
|
|
2646
2824
|
\u2716 Work completion checks failed for ${issueId}:
|
|
@@ -2656,6 +2834,31 @@ async function doneCommand(id, options = {}) {
|
|
|
2656
2834
|
}
|
|
2657
2835
|
}
|
|
2658
2836
|
}
|
|
2837
|
+
{
|
|
2838
|
+
const { getAgentState: getAgentState2 } = await import("../agents-RL2KUSP3.js");
|
|
2839
|
+
const agentState = getAgentState2(`agent-${issueId.toLowerCase()}`);
|
|
2840
|
+
const workspacePath = agentState?.workspace;
|
|
2841
|
+
if (workspacePath && existsSync13(workspacePath)) {
|
|
2842
|
+
try {
|
|
2843
|
+
const { stdout } = await execAsync2(
|
|
2844
|
+
`bd list --status closed -l "${issueId.toLowerCase()}" --json --limit 0`,
|
|
2845
|
+
{ cwd: workspacePath, encoding: "utf-8" }
|
|
2846
|
+
);
|
|
2847
|
+
const closedBeads = JSON.parse(stdout || "[]");
|
|
2848
|
+
let synced = 0;
|
|
2849
|
+
for (const bead of closedBeads) {
|
|
2850
|
+
if (bead.id) {
|
|
2851
|
+
const itemId = syncBeadStatusToVBrief(bead.id, workspacePath, "completed");
|
|
2852
|
+
if (itemId) synced++;
|
|
2853
|
+
}
|
|
2854
|
+
}
|
|
2855
|
+
if (synced > 0) {
|
|
2856
|
+
console.log(chalk12.dim(` Synced ${synced} closed bead(s) to vBRIEF AC status`));
|
|
2857
|
+
}
|
|
2858
|
+
} catch {
|
|
2859
|
+
}
|
|
2860
|
+
}
|
|
2861
|
+
}
|
|
2659
2862
|
const spinner = ora6("Marking work as done...").start();
|
|
2660
2863
|
try {
|
|
2661
2864
|
let trackerUpdated = false;
|
|
@@ -2689,7 +2892,7 @@ async function doneCommand(id, options = {}) {
|
|
|
2689
2892
|
console.log(chalk12.dim(" LINEAR_API_KEY not set - skipping status update"));
|
|
2690
2893
|
}
|
|
2691
2894
|
}
|
|
2692
|
-
const { getAgentState: getAgentState2, saveAgentState: saveAgentState2 } = await import("../agents-
|
|
2895
|
+
const { getAgentState: getAgentState2, saveAgentState: saveAgentState2 } = await import("../agents-RL2KUSP3.js");
|
|
2693
2896
|
const existingState = getAgentState2(agentId);
|
|
2694
2897
|
if (existingState) {
|
|
2695
2898
|
existingState.status = "stopped";
|
|
@@ -2700,8 +2903,8 @@ async function doneCommand(id, options = {}) {
|
|
|
2700
2903
|
state: "idle",
|
|
2701
2904
|
lastActivity: (/* @__PURE__ */ new Date()).toISOString()
|
|
2702
2905
|
});
|
|
2703
|
-
mkdirSync4(
|
|
2704
|
-
const completedFile =
|
|
2906
|
+
mkdirSync4(join13(AGENTS_DIR, agentId), { recursive: true });
|
|
2907
|
+
const completedFile = join13(AGENTS_DIR, agentId, "completed");
|
|
2705
2908
|
writeFileSync4(completedFile, JSON.stringify({
|
|
2706
2909
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2707
2910
|
trackerUpdated,
|
|
@@ -2828,18 +3031,18 @@ init_esm_shims();
|
|
|
2828
3031
|
import chalk13 from "chalk";
|
|
2829
3032
|
import ora7 from "ora";
|
|
2830
3033
|
import inquirer2 from "inquirer";
|
|
2831
|
-
import { readFileSync as
|
|
2832
|
-
import { join as
|
|
3034
|
+
import { readFileSync as readFileSync13, existsSync as existsSync15 } from "fs";
|
|
3035
|
+
import { join as join15, dirname as dirname5 } from "path";
|
|
2833
3036
|
import { homedir as homedir6 } from "os";
|
|
2834
3037
|
|
|
2835
3038
|
// src/lib/planning/plan-utils.ts
|
|
2836
3039
|
init_esm_shims();
|
|
2837
|
-
import { existsSync as
|
|
2838
|
-
import { join as
|
|
2839
|
-
import { exec as
|
|
2840
|
-
import { promisify as
|
|
3040
|
+
import { existsSync as existsSync14, mkdirSync as mkdirSync5, writeFileSync as writeFileSync5 } from "fs";
|
|
3041
|
+
import { join as join14 } from "path";
|
|
3042
|
+
import { exec as exec3, spawn } from "child_process";
|
|
3043
|
+
import { promisify as promisify3 } from "util";
|
|
2841
3044
|
init_paths();
|
|
2842
|
-
var
|
|
3045
|
+
var execAsync3 = promisify3(exec3);
|
|
2843
3046
|
async function findPRDFiles(issueId, cwd) {
|
|
2844
3047
|
const found = [];
|
|
2845
3048
|
const searchRoot = cwd || process.cwd();
|
|
@@ -2847,19 +3050,19 @@ async function findPRDFiles(issueId, cwd) {
|
|
|
2847
3050
|
found.push(getPRDDraftPath(issueId));
|
|
2848
3051
|
}
|
|
2849
3052
|
const searchPaths = [
|
|
2850
|
-
|
|
2851
|
-
|
|
2852
|
-
|
|
2853
|
-
|
|
3053
|
+
join14(PROJECT_DOCS_SUBDIR, PROJECT_PRDS_SUBDIR, PROJECT_PRDS_ACTIVE_SUBDIR),
|
|
3054
|
+
join14(PROJECT_DOCS_SUBDIR, PROJECT_PRDS_SUBDIR, "planned"),
|
|
3055
|
+
join14(PROJECT_DOCS_SUBDIR, PROJECT_PRDS_SUBDIR),
|
|
3056
|
+
join14(PROJECT_DOCS_SUBDIR, "prd"),
|
|
2854
3057
|
PROJECT_PRDS_SUBDIR,
|
|
2855
3058
|
PROJECT_DOCS_SUBDIR
|
|
2856
3059
|
];
|
|
2857
3060
|
const issueIdLower = issueId.toLowerCase();
|
|
2858
3061
|
for (const searchPath of searchPaths) {
|
|
2859
|
-
const fullPath =
|
|
2860
|
-
if (!
|
|
3062
|
+
const fullPath = join14(searchRoot, searchPath);
|
|
3063
|
+
if (!existsSync14(fullPath)) continue;
|
|
2861
3064
|
try {
|
|
2862
|
-
const { stdout: result } = await
|
|
3065
|
+
const { stdout: result } = await execAsync3(
|
|
2863
3066
|
`find "${fullPath}" -type f -name "*.md" 2>/dev/null | xargs grep -l -i "${issueIdLower}" 2>/dev/null || true`,
|
|
2864
3067
|
{ encoding: "utf-8" }
|
|
2865
3068
|
);
|
|
@@ -2991,139 +3194,29 @@ function generateStateContent(issue, decisions, tasks) {
|
|
|
2991
3194
|
lines.push("");
|
|
2992
3195
|
return lines.join("\n");
|
|
2993
3196
|
}
|
|
2994
|
-
function
|
|
2995
|
-
const
|
|
2996
|
-
const lines = [
|
|
2997
|
-
`# Workspace: ${issue.identifier}`,
|
|
2998
|
-
"",
|
|
2999
|
-
`> ${issue.title}`,
|
|
3000
|
-
"",
|
|
3001
|
-
"## Quick Links",
|
|
3002
|
-
"",
|
|
3003
|
-
`- [Linear Issue](${issue.url})`
|
|
3004
|
-
];
|
|
3005
|
-
for (const prd of prdFiles) {
|
|
3006
|
-
const relativePath = prd.replace(searchRoot + "/", "");
|
|
3007
|
-
lines.push(`- [PRD](${relativePath})`);
|
|
3008
|
-
}
|
|
3009
|
-
lines.push("");
|
|
3010
|
-
lines.push("## Context Files");
|
|
3011
|
-
lines.push("");
|
|
3012
|
-
lines.push("- `STATE.md` - Current progress and decisions");
|
|
3013
|
-
lines.push("- `WORKSPACE.md` - This file");
|
|
3014
|
-
lines.push("");
|
|
3015
|
-
lines.push("## Beads");
|
|
3016
|
-
lines.push("");
|
|
3017
|
-
lines.push("Check current task status:");
|
|
3018
|
-
lines.push("```bash");
|
|
3019
|
-
lines.push("bd ready # Next actionable task");
|
|
3020
|
-
lines.push(`bd list --tag ${issue.identifier} # All tasks for this issue`);
|
|
3021
|
-
lines.push("```");
|
|
3022
|
-
lines.push("");
|
|
3023
|
-
lines.push("## Agent Instructions");
|
|
3024
|
-
lines.push("");
|
|
3025
|
-
lines.push("1. Run `bd ready` to get next task");
|
|
3026
|
-
lines.push("2. Complete the task following relevant skills");
|
|
3027
|
-
lines.push('3. Run `bd close "<task name>" --reason "..."` when done');
|
|
3028
|
-
lines.push("4. Update STATE.md with progress");
|
|
3029
|
-
lines.push("5. Repeat until all tasks complete");
|
|
3030
|
-
lines.push("");
|
|
3031
|
-
lines.push("## CRITICAL: Work Completion Requirements");
|
|
3032
|
-
lines.push("");
|
|
3033
|
-
lines.push("**You are NOT done until ALL of these are true:**");
|
|
3034
|
-
lines.push("");
|
|
3035
|
-
lines.push("1. **Tests pass** - Run the full test suite");
|
|
3036
|
-
lines.push('2. **All changes committed** - `git status` shows "nothing to commit"');
|
|
3037
|
-
lines.push("3. **Pushed to remote** - `git push`");
|
|
3038
|
-
lines.push("");
|
|
3039
|
-
lines.push("**Uncommitted changes = NOT COMPLETE.**");
|
|
3040
|
-
lines.push("");
|
|
3041
|
-
return lines.join("\n");
|
|
3042
|
-
}
|
|
3043
|
-
function estimateTaskDifficulty(task) {
|
|
3044
|
-
if (task.difficulty) return task.difficulty;
|
|
3045
|
-
const combined = `${task.name} ${task.description || ""}`.toLowerCase();
|
|
3046
|
-
const expertPatterns = ["architecture", "security", "performance optimization", "distributed", "auth system", "redesign"];
|
|
3047
|
-
if (expertPatterns.some((p) => combined.includes(p))) return "expert";
|
|
3048
|
-
const complexPatterns = ["refactor", "migration", "overhaul", "rewrite", "integrate", "multi-system"];
|
|
3049
|
-
if (complexPatterns.some((p) => combined.includes(p))) return "complex";
|
|
3050
|
-
const mediumPatterns = ["implement", "feature", "endpoint", "component", "service", "integration", "add tests"];
|
|
3051
|
-
if (mediumPatterns.some((p) => combined.includes(p))) return "medium";
|
|
3052
|
-
const trivialPatterns = ["typo", "rename", "comment", "documentation", "readme", "formatting"];
|
|
3053
|
-
if (trivialPatterns.some((p) => combined.includes(p))) return "trivial";
|
|
3054
|
-
return "simple";
|
|
3055
|
-
}
|
|
3056
|
-
async function createBeadsTasks(issue, tasks, cwd) {
|
|
3057
|
-
const created = [];
|
|
3058
|
-
const errors = [];
|
|
3059
|
-
const taskIds = /* @__PURE__ */ new Map();
|
|
3060
|
-
const workDir = cwd || process.cwd();
|
|
3061
|
-
try {
|
|
3062
|
-
await execAsync2("which bd", { encoding: "utf-8" });
|
|
3063
|
-
} catch {
|
|
3064
|
-
return { success: false, created: [], errors: ["bd (beads) CLI not found in PATH"] };
|
|
3065
|
-
}
|
|
3066
|
-
for (const task of tasks) {
|
|
3067
|
-
const fullName = `${issue.identifier}: ${task.name}`;
|
|
3068
|
-
try {
|
|
3069
|
-
const difficulty = estimateTaskDifficulty(task);
|
|
3070
|
-
const escapedName = fullName.replace(/"/g, '\\"');
|
|
3071
|
-
let cmd = `bd create "${escapedName}" --type task -l "${issue.identifier},linear,difficulty:${difficulty}"`;
|
|
3072
|
-
if (task.dependsOn) {
|
|
3073
|
-
const depName = `${issue.identifier}: ${task.dependsOn}`;
|
|
3074
|
-
const depId = taskIds.get(depName);
|
|
3075
|
-
if (depId) {
|
|
3076
|
-
cmd += ` --deps "blocks:${depId}"`;
|
|
3077
|
-
}
|
|
3078
|
-
}
|
|
3079
|
-
if (task.description) {
|
|
3080
|
-
const escapedDesc = task.description.replace(/"/g, '\\"');
|
|
3081
|
-
cmd += ` -d "${escapedDesc}"`;
|
|
3082
|
-
}
|
|
3083
|
-
const { stdout: result } = await execAsync2(cmd, { encoding: "utf-8", cwd: workDir });
|
|
3084
|
-
const idMatch = result.match(/bd-[a-f0-9]+/i) || result.match(/([a-f0-9-]{8,})/i);
|
|
3085
|
-
if (idMatch) {
|
|
3086
|
-
taskIds.set(fullName, idMatch[0]);
|
|
3087
|
-
}
|
|
3088
|
-
created.push(fullName);
|
|
3089
|
-
} catch (error) {
|
|
3090
|
-
const errMsg = error.stderr?.toString() || error.message;
|
|
3091
|
-
errors.push(`Failed to create "${task.name}": ${errMsg.split("\n")[0]}`);
|
|
3092
|
-
}
|
|
3093
|
-
}
|
|
3094
|
-
if (created.length > 0) {
|
|
3095
|
-
try {
|
|
3096
|
-
await execAsync2("bd flush", { encoding: "utf-8", cwd: workDir });
|
|
3097
|
-
} catch {
|
|
3098
|
-
}
|
|
3099
|
-
}
|
|
3100
|
-
return { success: errors.length === 0, created, errors };
|
|
3101
|
-
}
|
|
3102
|
-
function writePlanFiles(projectPath, stateContent, workspaceContent) {
|
|
3103
|
-
const planningDir = join13(projectPath, ".planning");
|
|
3197
|
+
function writePlanFiles(projectPath, stateContent) {
|
|
3198
|
+
const planningDir = join14(projectPath, ".planning");
|
|
3104
3199
|
mkdirSync5(planningDir, { recursive: true });
|
|
3105
|
-
const statePath =
|
|
3106
|
-
const workspacePath = join13(planningDir, "WORKSPACE.md");
|
|
3200
|
+
const statePath = join14(planningDir, "STATE.md");
|
|
3107
3201
|
writeFileSync5(statePath, stateContent);
|
|
3108
|
-
|
|
3109
|
-
return { statePath, workspacePath };
|
|
3202
|
+
return { statePath };
|
|
3110
3203
|
}
|
|
3111
3204
|
async function copyToPRDDirectory(projectPath, issue, content, options) {
|
|
3112
|
-
const prdDir =
|
|
3205
|
+
const prdDir = join14(projectPath, PROJECT_DOCS_SUBDIR, PROJECT_PRDS_SUBDIR, PROJECT_PRDS_ACTIVE_SUBDIR);
|
|
3113
3206
|
try {
|
|
3114
3207
|
mkdirSync5(prdDir, { recursive: true });
|
|
3115
3208
|
const filename = `${issue.identifier.toLowerCase()}-plan.md`;
|
|
3116
|
-
const prdPath =
|
|
3209
|
+
const prdPath = join14(prdDir, filename);
|
|
3117
3210
|
writeFileSync5(prdPath, content);
|
|
3118
3211
|
let committed = false;
|
|
3119
3212
|
if (options?.commitAndPush) {
|
|
3120
3213
|
try {
|
|
3121
|
-
const relativePrdPath =
|
|
3122
|
-
await
|
|
3214
|
+
const relativePrdPath = join14(PROJECT_DOCS_SUBDIR, PROJECT_PRDS_SUBDIR, PROJECT_PRDS_ACTIVE_SUBDIR, filename);
|
|
3215
|
+
await execAsync3(`git add ${relativePrdPath}`, { cwd: projectPath, encoding: "utf-8" });
|
|
3123
3216
|
try {
|
|
3124
|
-
await
|
|
3217
|
+
await execAsync3("git diff --cached --quiet", { cwd: projectPath, encoding: "utf-8" });
|
|
3125
3218
|
} catch {
|
|
3126
|
-
await
|
|
3219
|
+
await execAsync3(`git commit -m "docs: add ${issue.identifier} PRD to active"`, {
|
|
3127
3220
|
cwd: projectPath,
|
|
3128
3221
|
encoding: "utf-8"
|
|
3129
3222
|
});
|
|
@@ -3141,33 +3234,28 @@ async function copyToPRDDirectory(projectPath, issue, content, options) {
|
|
|
3141
3234
|
}
|
|
3142
3235
|
}
|
|
3143
3236
|
async function executePlan(issue, tasks, decisions, projectPath, options) {
|
|
3144
|
-
const prdFiles = options?.prdFiles || [];
|
|
3145
3237
|
const stateContent = generateStateContent(issue, decisions, tasks);
|
|
3146
|
-
const
|
|
3147
|
-
const { statePath, workspacePath } = writePlanFiles(projectPath, stateContent, workspaceContent);
|
|
3238
|
+
const { statePath } = writePlanFiles(projectPath, stateContent);
|
|
3148
3239
|
const { prdPath, committed } = await copyToPRDDirectory(
|
|
3149
3240
|
projectPath,
|
|
3150
3241
|
issue,
|
|
3151
3242
|
stateContent,
|
|
3152
3243
|
{ commitAndPush: options?.commitAndPush ?? false }
|
|
3153
3244
|
);
|
|
3154
|
-
const beads = await createBeadsTasks(issue, tasks, projectPath);
|
|
3155
3245
|
return {
|
|
3156
3246
|
files: {
|
|
3157
3247
|
state: statePath,
|
|
3158
|
-
workspace: workspacePath,
|
|
3159
3248
|
prd: prdPath || void 0
|
|
3160
3249
|
},
|
|
3161
|
-
prdCommitted: committed
|
|
3162
|
-
beads
|
|
3250
|
+
prdCommitted: committed
|
|
3163
3251
|
};
|
|
3164
3252
|
}
|
|
3165
3253
|
|
|
3166
3254
|
// src/cli/commands/work/plan.ts
|
|
3167
3255
|
function getLinearApiKey4() {
|
|
3168
|
-
const envFile =
|
|
3169
|
-
if (
|
|
3170
|
-
const content =
|
|
3256
|
+
const envFile = join15(homedir6(), ".panopticon.env");
|
|
3257
|
+
if (existsSync15(envFile)) {
|
|
3258
|
+
const content = readFileSync13(envFile, "utf-8");
|
|
3171
3259
|
const match = content.match(/LINEAR_API_KEY=(.+)/);
|
|
3172
3260
|
if (match) return match[1].trim();
|
|
3173
3261
|
}
|
|
@@ -3473,16 +3561,6 @@ async function planCommand(id, options = {}) {
|
|
|
3473
3561
|
prdFiles
|
|
3474
3562
|
});
|
|
3475
3563
|
spinnerCreate.succeed("Context files created");
|
|
3476
|
-
if (result.beads.created.length > 0) {
|
|
3477
|
-
if (result.beads.success) {
|
|
3478
|
-
console.log(chalk13.green(`Created ${result.beads.created.length} Beads tasks`));
|
|
3479
|
-
} else {
|
|
3480
|
-
console.log(chalk13.yellow(`Created ${result.beads.created.length} Beads tasks with errors`));
|
|
3481
|
-
for (const error of result.beads.errors) {
|
|
3482
|
-
console.log(chalk13.red(` - ${error}`));
|
|
3483
|
-
}
|
|
3484
|
-
}
|
|
3485
|
-
}
|
|
3486
3564
|
if (result.files.prd) {
|
|
3487
3565
|
console.log(chalk13.dim(`Plan copied to: ${result.files.prd.replace(process.cwd() + "/", "")}`));
|
|
3488
3566
|
}
|
|
@@ -3492,8 +3570,7 @@ async function planCommand(id, options = {}) {
|
|
|
3492
3570
|
complexity,
|
|
3493
3571
|
tasks,
|
|
3494
3572
|
decisions,
|
|
3495
|
-
files: result.files
|
|
3496
|
-
beads: result.beads
|
|
3573
|
+
files: result.files
|
|
3497
3574
|
}, null, 2));
|
|
3498
3575
|
return;
|
|
3499
3576
|
}
|
|
@@ -3504,9 +3581,8 @@ async function planCommand(id, options = {}) {
|
|
|
3504
3581
|
console.log("");
|
|
3505
3582
|
console.log(chalk13.bold("Files created:"));
|
|
3506
3583
|
console.log(` ${chalk13.cyan(result.files.state.replace(process.cwd() + "/", ""))}`);
|
|
3507
|
-
console.log(` ${chalk13.cyan(result.files.workspace.replace(process.cwd() + "/", ""))}`);
|
|
3508
3584
|
console.log("");
|
|
3509
|
-
console.log(chalk13.bold("
|
|
3585
|
+
console.log(chalk13.bold("Tasks:"));
|
|
3510
3586
|
for (const task of tasks) {
|
|
3511
3587
|
console.log(` ${chalk13.dim("\u25CB")} ${issueData.identifier}: ${task.name}`);
|
|
3512
3588
|
}
|
|
@@ -3739,8 +3815,8 @@ init_esm_shims();
|
|
|
3739
3815
|
init_config();
|
|
3740
3816
|
import chalk15 from "chalk";
|
|
3741
3817
|
import ora9 from "ora";
|
|
3742
|
-
import { readFileSync as
|
|
3743
|
-
import { join as
|
|
3818
|
+
import { readFileSync as readFileSync14, writeFileSync as writeFileSync6, existsSync as existsSync16, mkdirSync as mkdirSync6 } from "fs";
|
|
3819
|
+
import { join as join16 } from "path";
|
|
3744
3820
|
import { homedir as homedir7 } from "os";
|
|
3745
3821
|
function getTrackerConfig2(trackerType) {
|
|
3746
3822
|
const config2 = loadConfig();
|
|
@@ -3759,13 +3835,13 @@ function getTrackerConfig2(trackerType) {
|
|
|
3759
3835
|
};
|
|
3760
3836
|
}
|
|
3761
3837
|
function getTriageStatePath() {
|
|
3762
|
-
return
|
|
3838
|
+
return join16(homedir7(), ".panopticon", "triage-state.json");
|
|
3763
3839
|
}
|
|
3764
3840
|
function loadTriageState() {
|
|
3765
3841
|
const path = getTriageStatePath();
|
|
3766
|
-
if (
|
|
3842
|
+
if (existsSync16(path)) {
|
|
3767
3843
|
try {
|
|
3768
|
-
return JSON.parse(
|
|
3844
|
+
return JSON.parse(readFileSync14(path, "utf-8"));
|
|
3769
3845
|
} catch {
|
|
3770
3846
|
return { dismissed: [], created: {} };
|
|
3771
3847
|
}
|
|
@@ -3773,8 +3849,8 @@ function loadTriageState() {
|
|
|
3773
3849
|
return { dismissed: [], created: {} };
|
|
3774
3850
|
}
|
|
3775
3851
|
function saveTriageState(state) {
|
|
3776
|
-
const dir =
|
|
3777
|
-
if (!
|
|
3852
|
+
const dir = join16(homedir7(), ".panopticon");
|
|
3853
|
+
if (!existsSync16(dir)) {
|
|
3778
3854
|
mkdirSync6(dir, { recursive: true });
|
|
3779
3855
|
}
|
|
3780
3856
|
const path = getTriageStatePath();
|
|
@@ -4159,23 +4235,23 @@ import chalk19 from "chalk";
|
|
|
4159
4235
|
// src/lib/context.ts
|
|
4160
4236
|
init_esm_shims();
|
|
4161
4237
|
init_paths();
|
|
4162
|
-
import { existsSync as
|
|
4163
|
-
import { join as
|
|
4238
|
+
import { existsSync as existsSync17, mkdirSync as mkdirSync7, readFileSync as readFileSync15, writeFileSync as writeFileSync7, appendFileSync, readdirSync as readdirSync12 } from "fs";
|
|
4239
|
+
import { join as join17 } from "path";
|
|
4164
4240
|
function getStateFile(agentId) {
|
|
4165
|
-
return
|
|
4241
|
+
return join17(AGENTS_DIR, agentId, "STATE.md");
|
|
4166
4242
|
}
|
|
4167
4243
|
function readAgentState(agentId) {
|
|
4168
4244
|
const stateFile = getStateFile(agentId);
|
|
4169
|
-
if (!
|
|
4245
|
+
if (!existsSync17(stateFile)) return null;
|
|
4170
4246
|
try {
|
|
4171
|
-
const content =
|
|
4247
|
+
const content = readFileSync15(stateFile, "utf-8");
|
|
4172
4248
|
return parseStateMd(content);
|
|
4173
4249
|
} catch {
|
|
4174
4250
|
return null;
|
|
4175
4251
|
}
|
|
4176
4252
|
}
|
|
4177
4253
|
function writeAgentState(agentId, state) {
|
|
4178
|
-
const dir =
|
|
4254
|
+
const dir = join17(AGENTS_DIR, agentId);
|
|
4179
4255
|
mkdirSync7(dir, { recursive: true });
|
|
4180
4256
|
const content = generateStateMd(state);
|
|
4181
4257
|
writeFileSync7(getStateFile(agentId), content);
|
|
@@ -4252,14 +4328,14 @@ function parseStateMd(content) {
|
|
|
4252
4328
|
return state;
|
|
4253
4329
|
}
|
|
4254
4330
|
function getSummaryFile(agentId) {
|
|
4255
|
-
return
|
|
4331
|
+
return join17(AGENTS_DIR, agentId, "SUMMARY.md");
|
|
4256
4332
|
}
|
|
4257
4333
|
function appendSummary(agentId, summary) {
|
|
4258
|
-
const dir =
|
|
4334
|
+
const dir = join17(AGENTS_DIR, agentId);
|
|
4259
4335
|
mkdirSync7(dir, { recursive: true });
|
|
4260
4336
|
const summaryFile = getSummaryFile(agentId);
|
|
4261
4337
|
const content = generateSummaryEntry(summary);
|
|
4262
|
-
if (
|
|
4338
|
+
if (existsSync17(summaryFile)) {
|
|
4263
4339
|
appendFileSync(summaryFile, "\n---\n\n" + content);
|
|
4264
4340
|
} else {
|
|
4265
4341
|
writeFileSync7(summaryFile, "# Work Summaries\n\n" + content);
|
|
@@ -4300,14 +4376,14 @@ function generateSummaryEntry(summary) {
|
|
|
4300
4376
|
return lines.join("\n");
|
|
4301
4377
|
}
|
|
4302
4378
|
function getHistoryDir(agentId) {
|
|
4303
|
-
return
|
|
4379
|
+
return join17(AGENTS_DIR, agentId, "history");
|
|
4304
4380
|
}
|
|
4305
4381
|
function logHistory(agentId, action, details) {
|
|
4306
4382
|
const historyDir = getHistoryDir(agentId);
|
|
4307
4383
|
mkdirSync7(historyDir, { recursive: true });
|
|
4308
4384
|
const date = /* @__PURE__ */ new Date();
|
|
4309
4385
|
const dateStr = date.toISOString().split("T")[0];
|
|
4310
|
-
const historyFile =
|
|
4386
|
+
const historyFile = join17(historyDir, `${dateStr}.log`);
|
|
4311
4387
|
const timestamp = date.toISOString();
|
|
4312
4388
|
const detailsStr = details ? ` ${JSON.stringify(details)}` : "";
|
|
4313
4389
|
const logLine = `[${timestamp}] ${action}${detailsStr}
|
|
@@ -4316,13 +4392,13 @@ function logHistory(agentId, action, details) {
|
|
|
4316
4392
|
}
|
|
4317
4393
|
function searchHistory(agentId, pattern) {
|
|
4318
4394
|
const historyDir = getHistoryDir(agentId);
|
|
4319
|
-
if (!
|
|
4395
|
+
if (!existsSync17(historyDir)) return [];
|
|
4320
4396
|
const results = [];
|
|
4321
4397
|
const regex = new RegExp(pattern, "i");
|
|
4322
4398
|
const files = readdirSync12(historyDir).filter((f) => f.endsWith(".log"));
|
|
4323
4399
|
files.sort().reverse();
|
|
4324
4400
|
for (const file of files) {
|
|
4325
|
-
const content =
|
|
4401
|
+
const content = readFileSync15(join17(historyDir, file), "utf-8");
|
|
4326
4402
|
const lines = content.split("\n");
|
|
4327
4403
|
for (const line of lines) {
|
|
4328
4404
|
if (regex.test(line)) {
|
|
@@ -4334,13 +4410,13 @@ function searchHistory(agentId, pattern) {
|
|
|
4334
4410
|
}
|
|
4335
4411
|
function getRecentHistory(agentId, limit = 20) {
|
|
4336
4412
|
const historyDir = getHistoryDir(agentId);
|
|
4337
|
-
if (!
|
|
4413
|
+
if (!existsSync17(historyDir)) return [];
|
|
4338
4414
|
const results = [];
|
|
4339
4415
|
const files = readdirSync12(historyDir).filter((f) => f.endsWith(".log"));
|
|
4340
4416
|
files.sort().reverse();
|
|
4341
4417
|
for (const file of files) {
|
|
4342
4418
|
if (results.length >= limit) break;
|
|
4343
|
-
const content =
|
|
4419
|
+
const content = readFileSync15(join17(historyDir, file), "utf-8");
|
|
4344
4420
|
const lines = content.split("\n").filter((l) => l.trim());
|
|
4345
4421
|
for (const line of lines.reverse()) {
|
|
4346
4422
|
if (results.length >= limit) break;
|
|
@@ -4353,28 +4429,28 @@ function estimateTokens(text) {
|
|
|
4353
4429
|
return Math.ceil(text.length / 4);
|
|
4354
4430
|
}
|
|
4355
4431
|
function getMaterializedDir(agentId) {
|
|
4356
|
-
return
|
|
4432
|
+
return join17(AGENTS_DIR, agentId, "materialized");
|
|
4357
4433
|
}
|
|
4358
4434
|
function listMaterialized(agentId) {
|
|
4359
4435
|
const dir = getMaterializedDir(agentId);
|
|
4360
|
-
if (!
|
|
4436
|
+
if (!existsSync17(dir)) return [];
|
|
4361
4437
|
return readdirSync12(dir).filter((f) => f.endsWith(".md")).map((f) => {
|
|
4362
4438
|
const match = f.match(/^(.+)-(\d+)\.md$/);
|
|
4363
4439
|
if (!match) return null;
|
|
4364
4440
|
return {
|
|
4365
4441
|
tool: match[1],
|
|
4366
4442
|
timestamp: parseInt(match[2], 10),
|
|
4367
|
-
file:
|
|
4443
|
+
file: join17(dir, f)
|
|
4368
4444
|
};
|
|
4369
4445
|
}).filter(Boolean);
|
|
4370
4446
|
}
|
|
4371
4447
|
function readMaterialized(filepath) {
|
|
4372
|
-
if (!
|
|
4373
|
-
return
|
|
4448
|
+
if (!existsSync17(filepath)) return null;
|
|
4449
|
+
return readFileSync15(filepath, "utf-8");
|
|
4374
4450
|
}
|
|
4375
4451
|
|
|
4376
4452
|
// src/cli/commands/work/context.ts
|
|
4377
|
-
import { readFileSync as
|
|
4453
|
+
import { readFileSync as readFileSync16, existsSync as existsSync18 } from "fs";
|
|
4378
4454
|
async function contextCommand(action, arg1, arg2, options = {}) {
|
|
4379
4455
|
const agentId = process.env.PANOPTICON_AGENT_ID || arg1 || "default";
|
|
4380
4456
|
switch (action) {
|
|
@@ -4490,7 +4566,7 @@ History matches for "${pattern}":
|
|
|
4490
4566
|
}
|
|
4491
4567
|
case "materialize": {
|
|
4492
4568
|
const filepath = arg1;
|
|
4493
|
-
if (filepath &&
|
|
4569
|
+
if (filepath && existsSync18(filepath)) {
|
|
4494
4570
|
const content = readMaterialized(filepath);
|
|
4495
4571
|
if (content) {
|
|
4496
4572
|
console.log(content);
|
|
@@ -4518,8 +4594,8 @@ History matches for "${pattern}":
|
|
|
4518
4594
|
return;
|
|
4519
4595
|
}
|
|
4520
4596
|
let text = target;
|
|
4521
|
-
if (
|
|
4522
|
-
text =
|
|
4597
|
+
if (existsSync18(target)) {
|
|
4598
|
+
text = readFileSync16(target, "utf-8");
|
|
4523
4599
|
}
|
|
4524
4600
|
const tokens = estimateTokens(text);
|
|
4525
4601
|
console.log(`Estimated tokens: ${chalk19.cyan(tokens.toLocaleString())}`);
|
|
@@ -4547,17 +4623,17 @@ import chalk20 from "chalk";
|
|
|
4547
4623
|
init_esm_shims();
|
|
4548
4624
|
init_paths();
|
|
4549
4625
|
init_agents();
|
|
4550
|
-
import { existsSync as
|
|
4551
|
-
import { join as
|
|
4552
|
-
import { exec as
|
|
4553
|
-
import { promisify as
|
|
4554
|
-
var
|
|
4626
|
+
import { existsSync as existsSync19, mkdirSync as mkdirSync8, readFileSync as readFileSync17, writeFileSync as writeFileSync8 } from "fs";
|
|
4627
|
+
import { join as join18 } from "path";
|
|
4628
|
+
import { exec as exec4 } from "child_process";
|
|
4629
|
+
import { promisify as promisify4 } from "util";
|
|
4630
|
+
var execAsync4 = promisify4(exec4);
|
|
4555
4631
|
var DEFAULT_PING_TIMEOUT_MS = 30 * 1e3;
|
|
4556
4632
|
var DEFAULT_CONSECUTIVE_FAILURES = 3;
|
|
4557
4633
|
var DEFAULT_COOLDOWN_MS = 5 * 60 * 1e3;
|
|
4558
4634
|
var DEFAULT_CHECK_INTERVAL_MS = 30 * 1e3;
|
|
4559
4635
|
function getHealthFile(agentId) {
|
|
4560
|
-
return
|
|
4636
|
+
return join18(AGENTS_DIR, agentId, "health.json");
|
|
4561
4637
|
}
|
|
4562
4638
|
function getAgentHealth(agentId) {
|
|
4563
4639
|
const healthFile = getHealthFile(agentId);
|
|
@@ -4569,9 +4645,9 @@ function getAgentHealth(agentId) {
|
|
|
4569
4645
|
recoveryCount: 0,
|
|
4570
4646
|
inCooldown: false
|
|
4571
4647
|
};
|
|
4572
|
-
if (
|
|
4648
|
+
if (existsSync19(healthFile)) {
|
|
4573
4649
|
try {
|
|
4574
|
-
const stored = JSON.parse(
|
|
4650
|
+
const stored = JSON.parse(readFileSync17(healthFile, "utf-8"));
|
|
4575
4651
|
return { ...defaultHealth, ...stored };
|
|
4576
4652
|
} catch {
|
|
4577
4653
|
}
|
|
@@ -4579,13 +4655,13 @@ function getAgentHealth(agentId) {
|
|
|
4579
4655
|
return defaultHealth;
|
|
4580
4656
|
}
|
|
4581
4657
|
function saveAgentHealth(health) {
|
|
4582
|
-
const dir =
|
|
4658
|
+
const dir = join18(AGENTS_DIR, health.agentId);
|
|
4583
4659
|
mkdirSync8(dir, { recursive: true });
|
|
4584
4660
|
writeFileSync8(getHealthFile(health.agentId), JSON.stringify(health, null, 2));
|
|
4585
4661
|
}
|
|
4586
4662
|
async function isAgentAlive(agentId) {
|
|
4587
4663
|
try {
|
|
4588
|
-
await
|
|
4664
|
+
await execAsync4(`tmux has-session -t "${agentId}" 2>/dev/null`, { encoding: "utf-8" });
|
|
4589
4665
|
return true;
|
|
4590
4666
|
} catch {
|
|
4591
4667
|
return false;
|
|
@@ -4695,14 +4771,14 @@ async function runHealthCheck(config2 = {
|
|
|
4695
4771
|
};
|
|
4696
4772
|
let sessions = [];
|
|
4697
4773
|
try {
|
|
4698
|
-
const { stdout: output } = await
|
|
4774
|
+
const { stdout: output } = await execAsync4(
|
|
4699
4775
|
'tmux list-sessions -F "#{session_name}" 2>/dev/null || true',
|
|
4700
4776
|
{ encoding: "utf-8" }
|
|
4701
4777
|
);
|
|
4702
4778
|
sessions = output.trim().split("\n").filter((s) => s.startsWith("agent-"));
|
|
4703
4779
|
} catch {
|
|
4704
4780
|
}
|
|
4705
|
-
if (
|
|
4781
|
+
if (existsSync19(AGENTS_DIR)) {
|
|
4706
4782
|
const { readdirSync: readdirSync22 } = await import("fs");
|
|
4707
4783
|
const dirs = readdirSync22(AGENTS_DIR, { withFileTypes: true }).filter((d) => d.isDirectory() && d.name.startsWith("agent-")).map((d) => d.name);
|
|
4708
4784
|
for (const dir of dirs) {
|
|
@@ -4940,15 +5016,15 @@ init_esm_shims();
|
|
|
4940
5016
|
import chalk21 from "chalk";
|
|
4941
5017
|
import ora11 from "ora";
|
|
4942
5018
|
import inquirer3 from "inquirer";
|
|
4943
|
-
import { existsSync as
|
|
4944
|
-
import { join as
|
|
5019
|
+
import { existsSync as existsSync21, readFileSync as readFileSync19 } from "fs";
|
|
5020
|
+
import { join as join20, dirname as dirname6 } from "path";
|
|
4945
5021
|
import { homedir as homedir8 } from "os";
|
|
4946
5022
|
import { LinearClient } from "@linear/sdk";
|
|
4947
5023
|
|
|
4948
5024
|
// src/lib/reopen.ts
|
|
4949
5025
|
init_esm_shims();
|
|
4950
|
-
import { existsSync as
|
|
4951
|
-
import { join as
|
|
5026
|
+
import { existsSync as existsSync20, readFileSync as readFileSync18, appendFileSync as appendFileSync2 } from "fs";
|
|
5027
|
+
import { join as join19 } from "path";
|
|
4952
5028
|
|
|
4953
5029
|
// src/dashboard/server/review-status.ts
|
|
4954
5030
|
init_esm_shims();
|
|
@@ -5014,9 +5090,9 @@ function reopenWorkspaceState(issueId, workspacePath, options = {}) {
|
|
|
5014
5090
|
result.queueItemsRemoved[specialistName] = removed;
|
|
5015
5091
|
}
|
|
5016
5092
|
}
|
|
5017
|
-
const statePath =
|
|
5018
|
-
if (
|
|
5019
|
-
const previousContent =
|
|
5093
|
+
const statePath = join19(workspacePath, ".planning", "STATE.md");
|
|
5094
|
+
if (existsSync20(statePath)) {
|
|
5095
|
+
const previousContent = readFileSync18(statePath, "utf-8");
|
|
5020
5096
|
const lastStatusMatch = previousContent.match(/\*\*STATUS:\s*([^*\n]+)\*\*/);
|
|
5021
5097
|
const previousStatus = lastStatusMatch ? lastStatusMatch[1].trim() : "Unknown";
|
|
5022
5098
|
const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
@@ -5052,9 +5128,9 @@ function reopenWorkspaceState(issueId, workspacePath, options = {}) {
|
|
|
5052
5128
|
// src/cli/commands/work/reopen.ts
|
|
5053
5129
|
init_projects();
|
|
5054
5130
|
function getLinearApiKey5() {
|
|
5055
|
-
const envFile =
|
|
5056
|
-
if (
|
|
5057
|
-
const content =
|
|
5131
|
+
const envFile = join20(homedir8(), ".panopticon.env");
|
|
5132
|
+
if (existsSync21(envFile)) {
|
|
5133
|
+
const content = readFileSync19(envFile, "utf-8");
|
|
5058
5134
|
const match = content.match(/LINEAR_API_KEY=(.+)/);
|
|
5059
5135
|
if (match) return match[1].trim();
|
|
5060
5136
|
}
|
|
@@ -5127,15 +5203,15 @@ function findLocalWorkspace2(issueId, startDir) {
|
|
|
5127
5203
|
const normalizedId = issueId.toLowerCase();
|
|
5128
5204
|
const resolved = resolveProjectFromIssue(issueId, []);
|
|
5129
5205
|
if (resolved) {
|
|
5130
|
-
const workspacePath =
|
|
5131
|
-
if (
|
|
5206
|
+
const workspacePath = join20(resolved.projectPath, "workspaces", `feature-${normalizedId}`);
|
|
5207
|
+
if (existsSync21(workspacePath)) return workspacePath;
|
|
5132
5208
|
}
|
|
5133
5209
|
let dir = startDir ?? process.cwd();
|
|
5134
5210
|
for (let i = 0; i < 10; i++) {
|
|
5135
|
-
const workspacesDir =
|
|
5136
|
-
if (
|
|
5137
|
-
const workspacePath =
|
|
5138
|
-
if (
|
|
5211
|
+
const workspacesDir = join20(dir, "workspaces");
|
|
5212
|
+
if (existsSync21(workspacesDir)) {
|
|
5213
|
+
const workspacePath = join20(workspacesDir, `feature-${normalizedId}`);
|
|
5214
|
+
if (existsSync21(workspacePath)) return workspacePath;
|
|
5139
5215
|
}
|
|
5140
5216
|
const parent = dirname6(dir);
|
|
5141
5217
|
if (parent === dir) break;
|
|
@@ -5262,7 +5338,7 @@ Previous state: ${issue.state}`
|
|
|
5262
5338
|
console.log(chalk21.green(`\u2713 ${issue.identifier} reopened and ready for re-work`));
|
|
5263
5339
|
console.log("");
|
|
5264
5340
|
try {
|
|
5265
|
-
const { getAgentState: getAgentState2 } = await import("../agents-
|
|
5341
|
+
const { getAgentState: getAgentState2 } = await import("../agents-RL2KUSP3.js");
|
|
5266
5342
|
const agentId = `agent-${id.toLowerCase()}`;
|
|
5267
5343
|
const agentState = getAgentState2(agentId);
|
|
5268
5344
|
const agentRunning = agentState?.status === "running" || agentState?.status === "starting";
|
|
@@ -5379,12 +5455,12 @@ init_agents();
|
|
|
5379
5455
|
init_tmux();
|
|
5380
5456
|
import chalk24 from "chalk";
|
|
5381
5457
|
import { homedir as homedir9 } from "os";
|
|
5382
|
-
import { join as
|
|
5383
|
-
import { existsSync as
|
|
5384
|
-
import { exec as
|
|
5385
|
-
import { promisify as
|
|
5458
|
+
import { join as join21 } from "path";
|
|
5459
|
+
import { existsSync as existsSync22, rmSync, readFileSync as readFileSync20 } from "fs";
|
|
5460
|
+
import { exec as exec5 } from "child_process";
|
|
5461
|
+
import { promisify as promisify5 } from "util";
|
|
5386
5462
|
init_config();
|
|
5387
|
-
var
|
|
5463
|
+
var execAsync5 = promisify5(exec5);
|
|
5388
5464
|
async function wipeCommand(issueId, options) {
|
|
5389
5465
|
const issueLower = issueId.toLowerCase();
|
|
5390
5466
|
const cleanupLog = [];
|
|
@@ -5470,10 +5546,10 @@ async function wipeCommand(issueId, options) {
|
|
|
5470
5546
|
}
|
|
5471
5547
|
}
|
|
5472
5548
|
const agentDirs = [
|
|
5473
|
-
|
|
5549
|
+
join21(homedir9(), ".panopticon", "agents", `agent-${issueLower}`)
|
|
5474
5550
|
];
|
|
5475
5551
|
for (const dir of agentDirs) {
|
|
5476
|
-
if (
|
|
5552
|
+
if (existsSync22(dir)) {
|
|
5477
5553
|
rmSync(dir, { recursive: true, force: true });
|
|
5478
5554
|
cleanupLog.push(`Deleted agent state: ${dir}`);
|
|
5479
5555
|
console.log(chalk24.green(` \u2713 Deleted agent state: ${dir.replace(homedir9(), "~")}`));
|
|
@@ -5481,11 +5557,11 @@ async function wipeCommand(issueId, options) {
|
|
|
5481
5557
|
}
|
|
5482
5558
|
let projectPath;
|
|
5483
5559
|
const prefix = issueId.split("-")[0].toUpperCase();
|
|
5484
|
-
const projectsYamlPath =
|
|
5485
|
-
if (
|
|
5560
|
+
const projectsYamlPath = join21(homedir9(), ".panopticon", "projects.yaml");
|
|
5561
|
+
if (existsSync22(projectsYamlPath)) {
|
|
5486
5562
|
try {
|
|
5487
5563
|
const yaml2 = await import("js-yaml");
|
|
5488
|
-
const projectsConfig = yaml2.load(
|
|
5564
|
+
const projectsConfig = yaml2.load(readFileSync20(projectsYamlPath, "utf-8"));
|
|
5489
5565
|
for (const [, config2] of Object.entries(projectsConfig.projects || {})) {
|
|
5490
5566
|
const projConfig = config2;
|
|
5491
5567
|
if (projConfig.linear_team?.toUpperCase() === prefix) {
|
|
@@ -5496,20 +5572,72 @@ async function wipeCommand(issueId, options) {
|
|
|
5496
5572
|
} catch (e) {
|
|
5497
5573
|
}
|
|
5498
5574
|
}
|
|
5575
|
+
if (projectPath) {
|
|
5576
|
+
const workspacePath = join21(projectPath, "workspaces", `feature-${issueLower}`);
|
|
5577
|
+
const workspaceBeadsDir = join21(workspacePath, ".beads");
|
|
5578
|
+
if (existsSync22(workspaceBeadsDir)) {
|
|
5579
|
+
try {
|
|
5580
|
+
const { stdout: listOutput } = await execAsync5(
|
|
5581
|
+
`bd list --json -l "${issueLower}" --limit 0`,
|
|
5582
|
+
{ encoding: "utf-8", cwd: workspacePath, timeout: 15e3 }
|
|
5583
|
+
);
|
|
5584
|
+
const beads = JSON.parse(listOutput || "[]");
|
|
5585
|
+
if (Array.isArray(beads) && beads.length > 0) {
|
|
5586
|
+
const ids = beads.map((b) => b.id).filter(Boolean);
|
|
5587
|
+
for (const id of ids) {
|
|
5588
|
+
try {
|
|
5589
|
+
await execAsync5(`bd delete ${id} --force`, { encoding: "utf-8", cwd: workspacePath, timeout: 1e4 });
|
|
5590
|
+
} catch {
|
|
5591
|
+
}
|
|
5592
|
+
}
|
|
5593
|
+
cleanupLog.push(`Deleted ${ids.length} beads for ${issueLower}`);
|
|
5594
|
+
console.log(chalk24.green(` \u2713 Deleted ${ids.length} beads for ${issueLower}`));
|
|
5595
|
+
}
|
|
5596
|
+
} catch {
|
|
5597
|
+
}
|
|
5598
|
+
}
|
|
5599
|
+
const projJsonl = join21(projectPath, ".beads", "issues.jsonl");
|
|
5600
|
+
if (existsSync22(projJsonl)) {
|
|
5601
|
+
try {
|
|
5602
|
+
const content = readFileSync20(projJsonl, "utf-8");
|
|
5603
|
+
const lines = content.split("\n");
|
|
5604
|
+
const issueUpper = issueLower.toUpperCase();
|
|
5605
|
+
const filtered = lines.filter((line) => {
|
|
5606
|
+
if (!line.trim()) return true;
|
|
5607
|
+
try {
|
|
5608
|
+
const entry = JSON.parse(line);
|
|
5609
|
+
const title = (entry.title || "").toUpperCase();
|
|
5610
|
+
const issue = (entry.issue || "").toUpperCase();
|
|
5611
|
+
return !title.includes(issueUpper) && issue !== issueUpper;
|
|
5612
|
+
} catch {
|
|
5613
|
+
return true;
|
|
5614
|
+
}
|
|
5615
|
+
});
|
|
5616
|
+
const removed = lines.length - filtered.length;
|
|
5617
|
+
if (removed > 0) {
|
|
5618
|
+
const { writeFileSync: writeFileSync27 } = await import("fs");
|
|
5619
|
+
writeFileSync27(projJsonl, filtered.join("\n"));
|
|
5620
|
+
cleanupLog.push(`Removed ${removed} beads entries from project JSONL`);
|
|
5621
|
+
console.log(chalk24.green(` \u2713 Removed ${removed} beads entries from project JSONL`));
|
|
5622
|
+
}
|
|
5623
|
+
} catch {
|
|
5624
|
+
}
|
|
5625
|
+
}
|
|
5626
|
+
}
|
|
5499
5627
|
if (options.workspace && projectPath) {
|
|
5500
|
-
const workspacePath =
|
|
5501
|
-
if (
|
|
5628
|
+
const workspacePath = join21(projectPath, "workspaces", `feature-${issueLower}`);
|
|
5629
|
+
if (existsSync22(workspacePath)) {
|
|
5502
5630
|
try {
|
|
5503
5631
|
const gitDirs = ["api", "frontend", "fe", "."];
|
|
5504
5632
|
for (const gitDir of gitDirs) {
|
|
5505
|
-
const gitPath =
|
|
5506
|
-
if (
|
|
5507
|
-
await
|
|
5633
|
+
const gitPath = join21(projectPath, gitDir);
|
|
5634
|
+
if (existsSync22(join21(gitPath, ".git"))) {
|
|
5635
|
+
await execAsync5(`cd "${gitPath}" && git worktree remove "${workspacePath}" --force 2>/dev/null || true`);
|
|
5508
5636
|
}
|
|
5509
5637
|
}
|
|
5510
5638
|
} catch (e) {
|
|
5511
5639
|
}
|
|
5512
|
-
if (
|
|
5640
|
+
if (existsSync22(workspacePath)) {
|
|
5513
5641
|
rmSync(workspacePath, { recursive: true, force: true });
|
|
5514
5642
|
}
|
|
5515
5643
|
cleanupLog.push(`Deleted workspace: ${workspacePath}`);
|
|
@@ -5567,14 +5695,14 @@ import ora12 from "ora";
|
|
|
5567
5695
|
|
|
5568
5696
|
// src/lib/shadow-utils.ts
|
|
5569
5697
|
init_esm_shims();
|
|
5570
|
-
import { existsSync as
|
|
5571
|
-
import { join as
|
|
5698
|
+
import { existsSync as existsSync23, readFileSync as readFileSync21 } from "fs";
|
|
5699
|
+
import { join as join22 } from "path";
|
|
5572
5700
|
import { homedir as homedir10 } from "os";
|
|
5573
5701
|
import chalk25 from "chalk";
|
|
5574
5702
|
function getLinearApiKey6() {
|
|
5575
|
-
const envFile =
|
|
5576
|
-
if (
|
|
5577
|
-
const content =
|
|
5703
|
+
const envFile = join22(homedir10(), ".panopticon.env");
|
|
5704
|
+
if (existsSync23(envFile)) {
|
|
5705
|
+
const content = readFileSync21(envFile, "utf-8");
|
|
5578
5706
|
const match = content.match(/LINEAR_API_KEY=(.+)/);
|
|
5579
5707
|
if (match) return match[1].trim();
|
|
5580
5708
|
}
|
|
@@ -5673,10 +5801,10 @@ init_esm_shims();
|
|
|
5673
5801
|
import chalk27 from "chalk";
|
|
5674
5802
|
import ora13 from "ora";
|
|
5675
5803
|
import inquirer4 from "inquirer";
|
|
5676
|
-
import { existsSync as
|
|
5677
|
-
import { join as
|
|
5804
|
+
import { existsSync as existsSync24, readFileSync as readFileSync22, writeFileSync as writeFileSync9, mkdirSync as mkdirSync9 } from "fs";
|
|
5805
|
+
import { join as join23, dirname as dirname7 } from "path";
|
|
5678
5806
|
import { homedir as homedir11 } from "os";
|
|
5679
|
-
var SYNC_QUEUE_FILE =
|
|
5807
|
+
var SYNC_QUEUE_FILE = join23(homedir11(), ".panopticon", "sync-queue.json");
|
|
5680
5808
|
async function syncToLinear(apiKey, issueId, targetState) {
|
|
5681
5809
|
try {
|
|
5682
5810
|
const { LinearClient: LinearClient2 } = await import("@linear/sdk");
|
|
@@ -5847,11 +5975,11 @@ async function syncCommand2(id, options = {}) {
|
|
|
5847
5975
|
}
|
|
5848
5976
|
}
|
|
5849
5977
|
function loadSyncQueue() {
|
|
5850
|
-
if (!
|
|
5978
|
+
if (!existsSync24(SYNC_QUEUE_FILE)) {
|
|
5851
5979
|
return [];
|
|
5852
5980
|
}
|
|
5853
5981
|
try {
|
|
5854
|
-
const content =
|
|
5982
|
+
const content = readFileSync22(SYNC_QUEUE_FILE, "utf-8");
|
|
5855
5983
|
return JSON.parse(content);
|
|
5856
5984
|
} catch (error) {
|
|
5857
5985
|
console.error(chalk27.yellow("Warning: Failed to load sync queue"));
|
|
@@ -5860,7 +5988,7 @@ function loadSyncQueue() {
|
|
|
5860
5988
|
}
|
|
5861
5989
|
function saveSyncQueue(queue) {
|
|
5862
5990
|
const dir = dirname7(SYNC_QUEUE_FILE);
|
|
5863
|
-
if (!
|
|
5991
|
+
if (!existsSync24(dir)) {
|
|
5864
5992
|
mkdirSync9(dir, { recursive: true });
|
|
5865
5993
|
}
|
|
5866
5994
|
try {
|
|
@@ -5987,8 +6115,8 @@ async function refreshCommand(id, options = {}) {
|
|
|
5987
6115
|
init_esm_shims();
|
|
5988
6116
|
init_tldr_daemon();
|
|
5989
6117
|
import chalk29 from "chalk";
|
|
5990
|
-
import { existsSync as
|
|
5991
|
-
import { join as
|
|
6118
|
+
import { existsSync as existsSync25, readdirSync as readdirSync14, readFileSync as readFileSync23, statSync as statSync5 } from "fs";
|
|
6119
|
+
import { join as join24 } from "path";
|
|
5992
6120
|
async function tldrCommand(action, workspace, options = {}) {
|
|
5993
6121
|
switch (action) {
|
|
5994
6122
|
case "status":
|
|
@@ -6011,19 +6139,19 @@ async function tldrCommand(action, workspace, options = {}) {
|
|
|
6011
6139
|
}
|
|
6012
6140
|
async function statusCommand2(options) {
|
|
6013
6141
|
const projectRoot = process.cwd();
|
|
6014
|
-
const venvPath =
|
|
6142
|
+
const venvPath = join24(projectRoot, ".venv");
|
|
6015
6143
|
const results = [];
|
|
6016
|
-
if (
|
|
6144
|
+
if (existsSync25(venvPath)) {
|
|
6017
6145
|
const service = getTldrDaemonService(projectRoot, venvPath);
|
|
6018
6146
|
const status = await service.getStatus();
|
|
6019
|
-
const tldrPath =
|
|
6147
|
+
const tldrPath = join24(projectRoot, ".tldr");
|
|
6020
6148
|
let indexAge = "N/A";
|
|
6021
6149
|
let fileCount = "N/A";
|
|
6022
|
-
if (
|
|
6150
|
+
if (existsSync25(tldrPath)) {
|
|
6023
6151
|
try {
|
|
6024
|
-
const langPath =
|
|
6025
|
-
if (
|
|
6026
|
-
const langData = JSON.parse(
|
|
6152
|
+
const langPath = join24(tldrPath, "languages.json");
|
|
6153
|
+
if (existsSync25(langPath)) {
|
|
6154
|
+
const langData = JSON.parse(readFileSync23(langPath, "utf-8"));
|
|
6027
6155
|
if (langData.timestamp) {
|
|
6028
6156
|
const ageMs = Date.now() - langData.timestamp * 1e3;
|
|
6029
6157
|
const ageDays = Math.floor(ageMs / (1e3 * 60 * 60 * 24));
|
|
@@ -6036,9 +6164,9 @@ async function statusCommand2(options) {
|
|
|
6036
6164
|
const ageDays = Math.floor(ageMs / (1e3 * 60 * 60 * 24));
|
|
6037
6165
|
indexAge = ageDays === 0 ? "today" : `${ageDays}d ago`;
|
|
6038
6166
|
}
|
|
6039
|
-
const cgPath =
|
|
6040
|
-
if (
|
|
6041
|
-
const cg = JSON.parse(
|
|
6167
|
+
const cgPath = join24(tldrPath, "cache", "call_graph.json");
|
|
6168
|
+
if (existsSync25(cgPath)) {
|
|
6169
|
+
const cg = JSON.parse(readFileSync23(cgPath, "utf-8"));
|
|
6042
6170
|
if (Array.isArray(cg.edges)) {
|
|
6043
6171
|
const files = /* @__PURE__ */ new Set();
|
|
6044
6172
|
for (const e of cg.edges) {
|
|
@@ -6060,23 +6188,23 @@ async function statusCommand2(options) {
|
|
|
6060
6188
|
fileCount
|
|
6061
6189
|
});
|
|
6062
6190
|
}
|
|
6063
|
-
const workspacesDir =
|
|
6064
|
-
if (
|
|
6191
|
+
const workspacesDir = join24(projectRoot, "workspaces");
|
|
6192
|
+
if (existsSync25(workspacesDir)) {
|
|
6065
6193
|
const workspaces = readdirSync14(workspacesDir, { withFileTypes: true }).filter((d) => d.isDirectory() && d.name.startsWith("feature-"));
|
|
6066
6194
|
for (const ws of workspaces) {
|
|
6067
|
-
const wsPath =
|
|
6068
|
-
const wsVenvPath =
|
|
6069
|
-
if (
|
|
6195
|
+
const wsPath = join24(workspacesDir, ws.name);
|
|
6196
|
+
const wsVenvPath = join24(wsPath, ".venv");
|
|
6197
|
+
if (existsSync25(wsVenvPath)) {
|
|
6070
6198
|
const service = getTldrDaemonService(wsPath, wsVenvPath);
|
|
6071
6199
|
const status = await service.getStatus();
|
|
6072
|
-
const tldrPath =
|
|
6200
|
+
const tldrPath = join24(wsPath, ".tldr");
|
|
6073
6201
|
let indexAge = "N/A";
|
|
6074
6202
|
let fileCount = "N/A";
|
|
6075
|
-
if (
|
|
6203
|
+
if (existsSync25(tldrPath)) {
|
|
6076
6204
|
try {
|
|
6077
|
-
const langPath =
|
|
6078
|
-
if (
|
|
6079
|
-
const langData = JSON.parse(
|
|
6205
|
+
const langPath = join24(tldrPath, "languages.json");
|
|
6206
|
+
if (existsSync25(langPath)) {
|
|
6207
|
+
const langData = JSON.parse(readFileSync23(langPath, "utf-8"));
|
|
6080
6208
|
if (langData.timestamp) {
|
|
6081
6209
|
const ageMs = Date.now() - langData.timestamp * 1e3;
|
|
6082
6210
|
const ageHours = Math.floor(ageMs / (1e3 * 60 * 60));
|
|
@@ -6089,9 +6217,9 @@ async function statusCommand2(options) {
|
|
|
6089
6217
|
const ageHours = Math.floor(ageMs / (1e3 * 60 * 60));
|
|
6090
6218
|
indexAge = ageHours === 0 ? "now" : ageHours < 24 ? `${ageHours}h ago` : `${Math.floor(ageHours / 24)}d ago`;
|
|
6091
6219
|
}
|
|
6092
|
-
const cgPath =
|
|
6093
|
-
if (
|
|
6094
|
-
const cg = JSON.parse(
|
|
6220
|
+
const cgPath = join24(tldrPath, "cache", "call_graph.json");
|
|
6221
|
+
if (existsSync25(cgPath)) {
|
|
6222
|
+
const cg = JSON.parse(readFileSync23(cgPath, "utf-8"));
|
|
6095
6223
|
if (Array.isArray(cg.edges)) {
|
|
6096
6224
|
const files = /* @__PURE__ */ new Set();
|
|
6097
6225
|
for (const e of cg.edges) {
|
|
@@ -6141,13 +6269,13 @@ async function statusCommand2(options) {
|
|
|
6141
6269
|
async function startCommand(workspace, options) {
|
|
6142
6270
|
const projectRoot = process.cwd();
|
|
6143
6271
|
if (workspace) {
|
|
6144
|
-
const wsPath =
|
|
6145
|
-
const venvPath =
|
|
6146
|
-
if (!
|
|
6272
|
+
const wsPath = join24(projectRoot, "workspaces", workspace);
|
|
6273
|
+
const venvPath = join24(wsPath, ".venv");
|
|
6274
|
+
if (!existsSync25(wsPath)) {
|
|
6147
6275
|
console.error(chalk29.red(`Error: Workspace not found: ${workspace}`));
|
|
6148
6276
|
process.exit(1);
|
|
6149
6277
|
}
|
|
6150
|
-
if (!
|
|
6278
|
+
if (!existsSync25(venvPath)) {
|
|
6151
6279
|
console.error(chalk29.red(`Error: No .venv found in workspace: ${workspace}`));
|
|
6152
6280
|
console.error(chalk29.dim("Workspace needs to be recreated with TLDR support"));
|
|
6153
6281
|
process.exit(1);
|
|
@@ -6158,8 +6286,8 @@ async function startCommand(workspace, options) {
|
|
|
6158
6286
|
console.log(chalk29.green(`\u2713 Started TLDR daemon for ${workspace}`));
|
|
6159
6287
|
}
|
|
6160
6288
|
} else {
|
|
6161
|
-
const venvPath =
|
|
6162
|
-
if (!
|
|
6289
|
+
const venvPath = join24(projectRoot, ".venv");
|
|
6290
|
+
if (!existsSync25(venvPath)) {
|
|
6163
6291
|
console.error(chalk29.red("Error: No .venv found in project root"));
|
|
6164
6292
|
console.error(chalk29.dim("Run `pan setup` to configure TLDR"));
|
|
6165
6293
|
process.exit(1);
|
|
@@ -6174,13 +6302,13 @@ async function startCommand(workspace, options) {
|
|
|
6174
6302
|
async function stopCommand(workspace, options) {
|
|
6175
6303
|
const projectRoot = process.cwd();
|
|
6176
6304
|
if (workspace) {
|
|
6177
|
-
const wsPath =
|
|
6178
|
-
const venvPath =
|
|
6179
|
-
if (!
|
|
6305
|
+
const wsPath = join24(projectRoot, "workspaces", workspace);
|
|
6306
|
+
const venvPath = join24(wsPath, ".venv");
|
|
6307
|
+
if (!existsSync25(wsPath)) {
|
|
6180
6308
|
console.error(chalk29.red(`Error: Workspace not found: ${workspace}`));
|
|
6181
6309
|
process.exit(1);
|
|
6182
6310
|
}
|
|
6183
|
-
if (!
|
|
6311
|
+
if (!existsSync25(venvPath)) {
|
|
6184
6312
|
console.error(chalk29.red(`Error: No .venv found in workspace: ${workspace}`));
|
|
6185
6313
|
process.exit(1);
|
|
6186
6314
|
}
|
|
@@ -6190,8 +6318,8 @@ async function stopCommand(workspace, options) {
|
|
|
6190
6318
|
console.log(chalk29.green(`\u2713 Stopped TLDR daemon for ${workspace}`));
|
|
6191
6319
|
}
|
|
6192
6320
|
} else {
|
|
6193
|
-
const venvPath =
|
|
6194
|
-
if (!
|
|
6321
|
+
const venvPath = join24(projectRoot, ".venv");
|
|
6322
|
+
if (!existsSync25(venvPath)) {
|
|
6195
6323
|
console.error(chalk29.red("Error: No .venv found in project root"));
|
|
6196
6324
|
process.exit(1);
|
|
6197
6325
|
}
|
|
@@ -6205,13 +6333,13 @@ async function stopCommand(workspace, options) {
|
|
|
6205
6333
|
async function warmCommand(workspace, options) {
|
|
6206
6334
|
const projectRoot = process.cwd();
|
|
6207
6335
|
if (workspace) {
|
|
6208
|
-
const wsPath =
|
|
6209
|
-
const venvPath =
|
|
6210
|
-
if (!
|
|
6336
|
+
const wsPath = join24(projectRoot, "workspaces", workspace);
|
|
6337
|
+
const venvPath = join24(wsPath, ".venv");
|
|
6338
|
+
if (!existsSync25(wsPath)) {
|
|
6211
6339
|
console.error(chalk29.red(`Error: Workspace not found: ${workspace}`));
|
|
6212
6340
|
process.exit(1);
|
|
6213
6341
|
}
|
|
6214
|
-
if (!
|
|
6342
|
+
if (!existsSync25(venvPath)) {
|
|
6215
6343
|
console.error(chalk29.red(`Error: No .venv found in workspace: ${workspace}`));
|
|
6216
6344
|
process.exit(1);
|
|
6217
6345
|
}
|
|
@@ -6225,8 +6353,8 @@ async function warmCommand(workspace, options) {
|
|
|
6225
6353
|
console.log(chalk29.green(`\u2713 Index warming complete for ${workspace}`));
|
|
6226
6354
|
}
|
|
6227
6355
|
} else {
|
|
6228
|
-
const venvPath =
|
|
6229
|
-
if (!
|
|
6356
|
+
const venvPath = join24(projectRoot, ".venv");
|
|
6357
|
+
if (!existsSync25(venvPath)) {
|
|
6230
6358
|
console.error(chalk29.red("Error: No .venv found in project root"));
|
|
6231
6359
|
console.error(chalk29.dim("Run `pan setup` to configure TLDR"));
|
|
6232
6360
|
process.exit(1);
|
|
@@ -6309,8 +6437,8 @@ async function syncMainCommand(id) {
|
|
|
6309
6437
|
// src/cli/commands/work/close-out.ts
|
|
6310
6438
|
init_esm_shims();
|
|
6311
6439
|
import chalk31 from "chalk";
|
|
6312
|
-
import { existsSync as
|
|
6313
|
-
import { join as
|
|
6440
|
+
import { existsSync as existsSync28, readFileSync as readFileSync25 } from "fs";
|
|
6441
|
+
import { join as join27 } from "path";
|
|
6314
6442
|
import { homedir as homedir12 } from "os";
|
|
6315
6443
|
|
|
6316
6444
|
// src/lib/lifecycle/index.ts
|
|
@@ -6320,11 +6448,11 @@ init_esm_shims();
|
|
|
6320
6448
|
init_esm_shims();
|
|
6321
6449
|
init_paths();
|
|
6322
6450
|
init_tmux();
|
|
6323
|
-
import { existsSync as
|
|
6324
|
-
import { join as
|
|
6325
|
-
import { exec as
|
|
6326
|
-
import { promisify as
|
|
6327
|
-
var
|
|
6451
|
+
import { existsSync as existsSync26, rmSync as rmSync2, unlinkSync as unlinkSync2 } from "fs";
|
|
6452
|
+
import { join as join25, basename as basename5 } from "path";
|
|
6453
|
+
import { exec as exec6 } from "child_process";
|
|
6454
|
+
import { promisify as promisify6 } from "util";
|
|
6455
|
+
var execAsync6 = promisify6(exec6);
|
|
6328
6456
|
async function killTmuxSessions(issueLower) {
|
|
6329
6457
|
const step = "teardown:tmux-sessions";
|
|
6330
6458
|
const patterns = [
|
|
@@ -6338,7 +6466,7 @@ async function killTmuxSessions(issueLower) {
|
|
|
6338
6466
|
for (const session of patterns) {
|
|
6339
6467
|
if (sessionExists(session)) {
|
|
6340
6468
|
try {
|
|
6341
|
-
await
|
|
6469
|
+
await execAsync6(`tmux kill-session -t ${session}`);
|
|
6342
6470
|
killed++;
|
|
6343
6471
|
} catch {
|
|
6344
6472
|
}
|
|
@@ -6351,8 +6479,8 @@ async function killTmuxSessions(issueLower) {
|
|
|
6351
6479
|
}
|
|
6352
6480
|
async function stopTldrDaemon(workspacePath) {
|
|
6353
6481
|
const step = "teardown:tldr-daemon";
|
|
6354
|
-
const venvPath =
|
|
6355
|
-
if (!
|
|
6482
|
+
const venvPath = join25(workspacePath, ".venv");
|
|
6483
|
+
if (!existsSync26(venvPath)) {
|
|
6356
6484
|
return stepSkipped(step, ["No .venv found"]);
|
|
6357
6485
|
}
|
|
6358
6486
|
try {
|
|
@@ -6367,20 +6495,95 @@ async function stopTldrDaemon(workspacePath) {
|
|
|
6367
6495
|
async function stopDocker(workspacePath, projectName, issueLower) {
|
|
6368
6496
|
const step = "teardown:docker";
|
|
6369
6497
|
try {
|
|
6370
|
-
const { stopWorkspaceDocker } = await import("../workspace-manager-
|
|
6498
|
+
const { stopWorkspaceDocker } = await import("../workspace-manager-6RP5A5HF.js");
|
|
6371
6499
|
await stopWorkspaceDocker(workspacePath, projectName, issueLower);
|
|
6372
6500
|
return stepOk(step, ["Stopped Docker containers"]);
|
|
6373
6501
|
} catch {
|
|
6374
6502
|
return stepSkipped(step, ["Docker cleanup skipped (not running or failed)"]);
|
|
6375
6503
|
}
|
|
6376
6504
|
}
|
|
6505
|
+
async function syncWorkspaceBeads(projectPath, workspacePath, issueLower) {
|
|
6506
|
+
const step = "teardown:sync-beads";
|
|
6507
|
+
const workspaceBeadsDir = join25(workspacePath, ".beads");
|
|
6508
|
+
if (!existsSync26(workspaceBeadsDir)) {
|
|
6509
|
+
return stepSkipped(step, ["No .beads directory in workspace"]);
|
|
6510
|
+
}
|
|
6511
|
+
try {
|
|
6512
|
+
const { stdout: exportOutput } = await execAsync6(
|
|
6513
|
+
"bd export --output .beads/issues-export.jsonl 2>&1 || true",
|
|
6514
|
+
{ cwd: workspacePath, encoding: "utf-8", timeout: 15e3 }
|
|
6515
|
+
);
|
|
6516
|
+
const exportPath = join25(workspacePath, ".beads", "issues-export.jsonl");
|
|
6517
|
+
if (!existsSync26(exportPath)) {
|
|
6518
|
+
await execAsync6("bd sync 2>&1 || true", { cwd: workspacePath, encoding: "utf-8", timeout: 15e3 });
|
|
6519
|
+
}
|
|
6520
|
+
try {
|
|
6521
|
+
await execAsync6(
|
|
6522
|
+
`bd import "${join25(workspacePath, ".beads", "issues.jsonl")}" 2>&1 || true`,
|
|
6523
|
+
{ cwd: projectPath, encoding: "utf-8", timeout: 15e3 }
|
|
6524
|
+
);
|
|
6525
|
+
return stepOk(step, [`Synced workspace beads to project root for ${issueLower}`]);
|
|
6526
|
+
} catch {
|
|
6527
|
+
const { readFileSync: readFileSync49, appendFileSync: appendFileSync4 } = await import("fs");
|
|
6528
|
+
const wsJsonl = join25(workspacePath, ".beads", "issues.jsonl");
|
|
6529
|
+
const projJsonl = join25(projectPath, ".beads", "issues.jsonl");
|
|
6530
|
+
if (existsSync26(wsJsonl) && existsSync26(projJsonl)) {
|
|
6531
|
+
const wsContent = readFileSync49(wsJsonl, "utf-8");
|
|
6532
|
+
const issuePattern = issueLower.replace("-", "[-_]");
|
|
6533
|
+
const relevantLines = wsContent.split("\n").filter(
|
|
6534
|
+
(line) => line.trim() && new RegExp(issuePattern, "i").test(line)
|
|
6535
|
+
);
|
|
6536
|
+
if (relevantLines.length > 0) {
|
|
6537
|
+
appendFileSync4(projJsonl, "\n" + relevantLines.join("\n"));
|
|
6538
|
+
return stepOk(step, [`Appended ${relevantLines.length} beads entries for ${issueLower} to project JSONL`]);
|
|
6539
|
+
}
|
|
6540
|
+
}
|
|
6541
|
+
return stepSkipped(step, ["No beads to sync or import not available"]);
|
|
6542
|
+
}
|
|
6543
|
+
} catch (err) {
|
|
6544
|
+
return stepFailed(step, `Failed to sync workspace beads: ${err.message}`);
|
|
6545
|
+
}
|
|
6546
|
+
}
|
|
6547
|
+
async function clearProjectBeads(projectPath, issueLower) {
|
|
6548
|
+
const step = "teardown:clear-beads";
|
|
6549
|
+
const projJsonl = join25(projectPath, ".beads", "issues.jsonl");
|
|
6550
|
+
if (!existsSync26(projJsonl)) {
|
|
6551
|
+
return stepSkipped(step, ["No .beads/issues.jsonl in project root"]);
|
|
6552
|
+
}
|
|
6553
|
+
try {
|
|
6554
|
+
const { readFileSync: readFileSync49, writeFileSync: writeFileSync27 } = await import("fs");
|
|
6555
|
+
const content = readFileSync49(projJsonl, "utf-8");
|
|
6556
|
+
const lines = content.split("\n");
|
|
6557
|
+
const issueUpper = issueLower.toUpperCase();
|
|
6558
|
+
const before = lines.length;
|
|
6559
|
+
const filtered = lines.filter((line) => {
|
|
6560
|
+
if (!line.trim()) return true;
|
|
6561
|
+
try {
|
|
6562
|
+
const entry = JSON.parse(line);
|
|
6563
|
+
const title = (entry.title || "").toUpperCase();
|
|
6564
|
+
const issue = (entry.issue || "").toUpperCase();
|
|
6565
|
+
return !title.includes(issueUpper) && issue !== issueUpper;
|
|
6566
|
+
} catch {
|
|
6567
|
+
return true;
|
|
6568
|
+
}
|
|
6569
|
+
});
|
|
6570
|
+
const removed = before - filtered.length;
|
|
6571
|
+
if (removed > 0) {
|
|
6572
|
+
writeFileSync27(projJsonl, filtered.join("\n"));
|
|
6573
|
+
return stepOk(step, [`Removed ${removed} beads entries for ${issueLower} from project JSONL`]);
|
|
6574
|
+
}
|
|
6575
|
+
return stepSkipped(step, [`No beads entries found for ${issueLower}`]);
|
|
6576
|
+
} catch (err) {
|
|
6577
|
+
return stepFailed(step, `Failed to clear beads: ${err.message}`);
|
|
6578
|
+
}
|
|
6579
|
+
}
|
|
6377
6580
|
async function removeWorktree(projectPath, workspacePath) {
|
|
6378
6581
|
const step = "teardown:worktree";
|
|
6379
|
-
if (!
|
|
6582
|
+
if (!existsSync26(workspacePath)) {
|
|
6380
6583
|
return stepSkipped(step, ["Workspace directory does not exist"]);
|
|
6381
6584
|
}
|
|
6382
6585
|
try {
|
|
6383
|
-
await
|
|
6586
|
+
await execAsync6(`git worktree remove "${workspacePath}" --force`, { cwd: projectPath });
|
|
6384
6587
|
return stepOk(step, ["Removed git worktree"]);
|
|
6385
6588
|
} catch {
|
|
6386
6589
|
try {
|
|
@@ -6394,12 +6597,12 @@ async function removeWorktree(projectPath, workspacePath) {
|
|
|
6394
6597
|
async function removeAgentState(issueLower) {
|
|
6395
6598
|
const step = "teardown:agent-state";
|
|
6396
6599
|
const dirs = [
|
|
6397
|
-
|
|
6398
|
-
|
|
6600
|
+
join25(AGENTS_DIR, `agent-${issueLower}`),
|
|
6601
|
+
join25(AGENTS_DIR, `planning-${issueLower}`)
|
|
6399
6602
|
];
|
|
6400
6603
|
let removed = 0;
|
|
6401
6604
|
for (const dir of dirs) {
|
|
6402
|
-
if (
|
|
6605
|
+
if (existsSync26(dir)) {
|
|
6403
6606
|
rmSync2(dir, { recursive: true, force: true });
|
|
6404
6607
|
removed++;
|
|
6405
6608
|
}
|
|
@@ -6414,13 +6617,13 @@ async function deleteBranches(projectPath, issueLower) {
|
|
|
6414
6617
|
const branchName = `feature/${issueLower}`;
|
|
6415
6618
|
const details = [];
|
|
6416
6619
|
try {
|
|
6417
|
-
await
|
|
6620
|
+
await execAsync6(`git branch -D "${branchName}"`, { cwd: projectPath, encoding: "utf-8" });
|
|
6418
6621
|
details.push(`Deleted local branch ${branchName}`);
|
|
6419
6622
|
} catch {
|
|
6420
6623
|
details.push(`Local branch ${branchName} not found (already deleted)`);
|
|
6421
6624
|
}
|
|
6422
6625
|
try {
|
|
6423
|
-
await
|
|
6626
|
+
await execAsync6(`git push origin --delete "${branchName}"`, { cwd: projectPath, encoding: "utf-8" });
|
|
6424
6627
|
details.push(`Deleted remote branch ${branchName}`);
|
|
6425
6628
|
} catch {
|
|
6426
6629
|
details.push(`Remote branch ${branchName} not found (already deleted)`);
|
|
@@ -6442,8 +6645,8 @@ async function clearShadowState(issueId) {
|
|
|
6442
6645
|
}
|
|
6443
6646
|
async function clearLegacyPlanningDir(projectPath, issueLower) {
|
|
6444
6647
|
const step = "teardown:legacy-planning-dir";
|
|
6445
|
-
const legacyDir =
|
|
6446
|
-
if (
|
|
6648
|
+
const legacyDir = join25(projectPath, ".planning", issueLower);
|
|
6649
|
+
if (existsSync26(legacyDir)) {
|
|
6447
6650
|
rmSync2(legacyDir, { recursive: true, force: true });
|
|
6448
6651
|
return stepOk(step, [`Deleted legacy planning dir: ${legacyDir}`]);
|
|
6449
6652
|
}
|
|
@@ -6451,8 +6654,8 @@ async function clearLegacyPlanningDir(projectPath, issueLower) {
|
|
|
6451
6654
|
}
|
|
6452
6655
|
async function clearPlanningMarker(workspacePath) {
|
|
6453
6656
|
const step = "teardown:planning-marker";
|
|
6454
|
-
const markerPath =
|
|
6455
|
-
if (
|
|
6657
|
+
const markerPath = join25(workspacePath, ".planning", ".planning-complete");
|
|
6658
|
+
if (existsSync26(markerPath)) {
|
|
6456
6659
|
unlinkSync2(markerPath);
|
|
6457
6660
|
return stepOk(step, ["Cleared .planning-complete marker"]);
|
|
6458
6661
|
}
|
|
@@ -6503,7 +6706,7 @@ async function teardownWorkspace(ctx, opts = {}) {
|
|
|
6503
6706
|
results.push(await killTmuxSessions(issueLower));
|
|
6504
6707
|
results.push(await clearShadowState(ctx.issueId));
|
|
6505
6708
|
results.push(await clearLegacyPlanningDir(ctx.projectPath, issueLower));
|
|
6506
|
-
if (workspacePath &&
|
|
6709
|
+
if (workspacePath && existsSync26(workspacePath)) {
|
|
6507
6710
|
if (shouldDeleteWorkspace) {
|
|
6508
6711
|
results.push(await stopTldrDaemon(workspacePath));
|
|
6509
6712
|
}
|
|
@@ -6511,6 +6714,11 @@ async function teardownWorkspace(ctx, opts = {}) {
|
|
|
6511
6714
|
results.push(await stopDocker(workspacePath, projName, issueLower));
|
|
6512
6715
|
}
|
|
6513
6716
|
results.push(await clearPlanningMarker(workspacePath));
|
|
6717
|
+
if (opts.clearBeads) {
|
|
6718
|
+
results.push(await clearProjectBeads(ctx.projectPath, issueLower));
|
|
6719
|
+
} else if (shouldDeleteWorkspace) {
|
|
6720
|
+
results.push(await syncWorkspaceBeads(ctx.projectPath, workspacePath, issueLower));
|
|
6721
|
+
}
|
|
6514
6722
|
if (shouldDeleteWorkspace && (opts.workspaceConfig?.tunnel || opts.workspaceConfig?.hume)) {
|
|
6515
6723
|
const placeholders = buildPlaceholders(ctx, opts, workspacePath);
|
|
6516
6724
|
if (opts.workspaceConfig.tunnel) {
|
|
@@ -6536,11 +6744,11 @@ async function teardownWorkspace(ctx, opts = {}) {
|
|
|
6536
6744
|
// src/lib/lifecycle/workflows.ts
|
|
6537
6745
|
init_esm_shims();
|
|
6538
6746
|
init_paths();
|
|
6539
|
-
import { existsSync as
|
|
6540
|
-
import { join as
|
|
6541
|
-
import { exec as
|
|
6542
|
-
import { promisify as
|
|
6543
|
-
var
|
|
6747
|
+
import { existsSync as existsSync27, readFileSync as readFileSync24 } from "fs";
|
|
6748
|
+
import { join as join26 } from "path";
|
|
6749
|
+
import { exec as exec7 } from "child_process";
|
|
6750
|
+
import { promisify as promisify7 } from "util";
|
|
6751
|
+
var execAsync7 = promisify7(exec7);
|
|
6544
6752
|
function buildResult(workflow, issueId, steps, startTime) {
|
|
6545
6753
|
return {
|
|
6546
6754
|
workflow,
|
|
@@ -6591,19 +6799,19 @@ async function verifyBranchMerged(ctx) {
|
|
|
6591
6799
|
}
|
|
6592
6800
|
} catch {
|
|
6593
6801
|
}
|
|
6594
|
-
const { stdout: branchExists } = await
|
|
6802
|
+
const { stdout: branchExists } = await execAsync7(
|
|
6595
6803
|
`git branch --list "${branchName}" 2>/dev/null || true`,
|
|
6596
6804
|
{ cwd: ctx.projectPath, encoding: "utf-8" }
|
|
6597
6805
|
);
|
|
6598
6806
|
if (branchExists.trim()) {
|
|
6599
6807
|
try {
|
|
6600
|
-
await
|
|
6808
|
+
await execAsync7(
|
|
6601
6809
|
`git merge-base --is-ancestor ${branchName} main`,
|
|
6602
6810
|
{ cwd: ctx.projectPath, encoding: "utf-8" }
|
|
6603
6811
|
);
|
|
6604
6812
|
return stepOk(step, ["All commits merged to main"]);
|
|
6605
6813
|
} catch {
|
|
6606
|
-
const { stdout: unmerged } = await
|
|
6814
|
+
const { stdout: unmerged } = await execAsync7(
|
|
6607
6815
|
`git log main..${branchName} --oneline 2>/dev/null || true`,
|
|
6608
6816
|
{ cwd: ctx.projectPath, encoding: "utf-8" }
|
|
6609
6817
|
);
|
|
@@ -6611,21 +6819,21 @@ async function verifyBranchMerged(ctx) {
|
|
|
6611
6819
|
return stepFailed(step, `${count} unmerged commit(s) on ${branchName}. Merge before closing out.`);
|
|
6612
6820
|
}
|
|
6613
6821
|
}
|
|
6614
|
-
const { stdout: remoteBranch } = await
|
|
6822
|
+
const { stdout: remoteBranch } = await execAsync7(
|
|
6615
6823
|
`git ls-remote --heads origin "${branchName}" 2>/dev/null || true`,
|
|
6616
6824
|
{ cwd: ctx.projectPath, encoding: "utf-8" }
|
|
6617
6825
|
);
|
|
6618
6826
|
if (remoteBranch.trim()) {
|
|
6619
|
-
await
|
|
6827
|
+
await execAsync7(`git fetch origin ${branchName}`, { cwd: ctx.projectPath }).catch(() => {
|
|
6620
6828
|
});
|
|
6621
6829
|
try {
|
|
6622
|
-
await
|
|
6830
|
+
await execAsync7(
|
|
6623
6831
|
`git merge-base --is-ancestor origin/${branchName} main`,
|
|
6624
6832
|
{ cwd: ctx.projectPath, encoding: "utf-8" }
|
|
6625
6833
|
);
|
|
6626
6834
|
return stepOk(step, ["Remote branch fully merged"]);
|
|
6627
6835
|
} catch {
|
|
6628
|
-
const { stdout: remoteUnmerged } = await
|
|
6836
|
+
const { stdout: remoteUnmerged } = await execAsync7(
|
|
6629
6837
|
`git log main..origin/${branchName} --oneline 2>/dev/null || true`,
|
|
6630
6838
|
{ cwd: ctx.projectPath, encoding: "utf-8" }
|
|
6631
6839
|
);
|
|
@@ -6646,9 +6854,9 @@ async function clearReviewStatusStep(issueId) {
|
|
|
6646
6854
|
return stepOk(step, ["Review status cleared"]);
|
|
6647
6855
|
} catch {
|
|
6648
6856
|
try {
|
|
6649
|
-
const statusFile =
|
|
6650
|
-
if (
|
|
6651
|
-
const data = JSON.parse(
|
|
6857
|
+
const statusFile = join26(PANOPTICON_HOME, "review-status.json");
|
|
6858
|
+
if (existsSync27(statusFile)) {
|
|
6859
|
+
const data = JSON.parse(readFileSync24(statusFile, "utf-8"));
|
|
6652
6860
|
const upperKey = issueId.toUpperCase();
|
|
6653
6861
|
if (data[upperKey]) {
|
|
6654
6862
|
delete data[upperKey];
|
|
@@ -6666,9 +6874,9 @@ async function clearReviewStatusStep(issueId) {
|
|
|
6666
6874
|
// src/cli/commands/work/close-out.ts
|
|
6667
6875
|
init_projects();
|
|
6668
6876
|
function getGitHubConfig2() {
|
|
6669
|
-
const envFile =
|
|
6670
|
-
if (!
|
|
6671
|
-
const content =
|
|
6877
|
+
const envFile = join27(homedir12(), ".panopticon.env");
|
|
6878
|
+
if (!existsSync28(envFile)) return null;
|
|
6879
|
+
const content = readFileSync25(envFile, "utf-8");
|
|
6672
6880
|
const reposMatch = content.match(/GITHUB_REPOS=(.+)/);
|
|
6673
6881
|
if (!reposMatch) return null;
|
|
6674
6882
|
const repoStr = reposMatch[1].trim();
|
|
@@ -6775,13 +6983,13 @@ Running close-out for ${issueUpper}...
|
|
|
6775
6983
|
// src/cli/commands/work/linear-states.ts
|
|
6776
6984
|
init_esm_shims();
|
|
6777
6985
|
import chalk32 from "chalk";
|
|
6778
|
-
import { readFileSync as
|
|
6986
|
+
import { readFileSync as readFileSync26, existsSync as existsSync29 } from "fs";
|
|
6779
6987
|
import { homedir as homedir13 } from "os";
|
|
6780
|
-
import { join as
|
|
6988
|
+
import { join as join28 } from "path";
|
|
6781
6989
|
function getLinearApiKey8() {
|
|
6782
|
-
const envFile =
|
|
6783
|
-
if (
|
|
6784
|
-
const content =
|
|
6990
|
+
const envFile = join28(homedir13(), ".panopticon.env");
|
|
6991
|
+
if (existsSync29(envFile)) {
|
|
6992
|
+
const content = readFileSync26(envFile, "utf-8");
|
|
6785
6993
|
const match = content.match(/LINEAR_API_KEY=(.+)/);
|
|
6786
6994
|
if (match) return match[1].trim();
|
|
6787
6995
|
}
|
|
@@ -6970,8 +7178,8 @@ function registerWorkCommands(program2) {
|
|
|
6970
7178
|
init_esm_shims();
|
|
6971
7179
|
import chalk33 from "chalk";
|
|
6972
7180
|
import ora16 from "ora";
|
|
6973
|
-
import { existsSync as
|
|
6974
|
-
import { join as
|
|
7181
|
+
import { existsSync as existsSync30, writeFileSync as writeFileSync10, rmSync as rmSync3, readFileSync as readFileSync27, realpathSync } from "fs";
|
|
7182
|
+
import { join as join29, basename as basename6, resolve } from "path";
|
|
6975
7183
|
|
|
6976
7184
|
// src/lib/worktree.ts
|
|
6977
7185
|
init_esm_shims();
|
|
@@ -7034,13 +7242,13 @@ init_agents();
|
|
|
7034
7242
|
init_projects();
|
|
7035
7243
|
init_workspace_manager();
|
|
7036
7244
|
init_config();
|
|
7037
|
-
import { exec as
|
|
7038
|
-
import { promisify as
|
|
7245
|
+
import { exec as exec8 } from "child_process";
|
|
7246
|
+
import { promisify as promisify8 } from "util";
|
|
7039
7247
|
import { homedir as homedir14 } from "os";
|
|
7040
|
-
var
|
|
7248
|
+
var execAsync8 = promisify8(exec8);
|
|
7041
7249
|
async function getBeadsVersion() {
|
|
7042
7250
|
try {
|
|
7043
|
-
const { stdout } = await
|
|
7251
|
+
const { stdout } = await execAsync8("bd --version", { encoding: "utf-8" });
|
|
7044
7252
|
const match = stdout.match(/(\d+)\.(\d+)\.(\d+)/);
|
|
7045
7253
|
if (match) {
|
|
7046
7254
|
const [, , minor, patch] = match.map(Number);
|
|
@@ -7064,23 +7272,23 @@ async function initializeWorkspaceBeads(workspacePath, issueId) {
|
|
|
7064
7272
|
try {
|
|
7065
7273
|
const beadsVersion = await getBeadsVersion();
|
|
7066
7274
|
if (beadsVersion >= 4701) {
|
|
7067
|
-
const
|
|
7275
|
+
const issueLabel = issueId.toLowerCase();
|
|
7068
7276
|
const title = `${issueId.toUpperCase()}: Implementation`;
|
|
7069
|
-
const { stdout } = await
|
|
7070
|
-
`bd create --title "${title}" --priority 1 --type task --labels "${
|
|
7277
|
+
const { stdout } = await execAsync8(
|
|
7278
|
+
`bd create --title "${title}" --priority 1 --type task --labels "${issueLabel}" 2>&1`,
|
|
7071
7279
|
{ cwd: workspacePath, encoding: "utf-8" }
|
|
7072
7280
|
);
|
|
7073
7281
|
const match = stdout.match(/([a-z]+-[a-z0-9]+)/);
|
|
7074
7282
|
return { success: true, beadId: match?.[1] };
|
|
7075
7283
|
} else {
|
|
7076
|
-
const beadsDir =
|
|
7077
|
-
if (
|
|
7284
|
+
const beadsDir = join29(workspacePath, ".beads");
|
|
7285
|
+
if (existsSync30(beadsDir)) {
|
|
7078
7286
|
rmSync3(beadsDir, { recursive: true, force: true });
|
|
7079
7287
|
}
|
|
7080
7288
|
const prefix = "workspace";
|
|
7081
|
-
await
|
|
7289
|
+
await execAsync8(`bd init --prefix ${prefix}`, { cwd: workspacePath, encoding: "utf-8" });
|
|
7082
7290
|
const title = `${issueId.toUpperCase()}: Implementation`;
|
|
7083
|
-
const { stdout } = await
|
|
7291
|
+
const { stdout } = await execAsync8(
|
|
7084
7292
|
`bd create --title "${title}" --priority 1 --type task --json`,
|
|
7085
7293
|
{ cwd: workspacePath, encoding: "utf-8" }
|
|
7086
7294
|
);
|
|
@@ -7203,7 +7411,7 @@ async function createCommand(issueId, options) {
|
|
|
7203
7411
|
const dockerFlag = options.docker ? " --docker" : "";
|
|
7204
7412
|
const cmd = `${projectConfig.workspace_command} ${normalizedId}${dockerFlag}`;
|
|
7205
7413
|
try {
|
|
7206
|
-
const { stdout } = await
|
|
7414
|
+
const { stdout } = await execAsync8(cmd, {
|
|
7207
7415
|
cwd: projectConfig.path,
|
|
7208
7416
|
encoding: "utf-8",
|
|
7209
7417
|
timeout: options.docker ? 3e5 : 12e4
|
|
@@ -7239,8 +7447,8 @@ async function createCommand(issueId, options) {
|
|
|
7239
7447
|
projectRoot = process.cwd();
|
|
7240
7448
|
}
|
|
7241
7449
|
}
|
|
7242
|
-
const workspacesDir =
|
|
7243
|
-
const workspacePath =
|
|
7450
|
+
const workspacesDir = join29(projectRoot, "workspaces");
|
|
7451
|
+
const workspacePath = join29(workspacesDir, folderName);
|
|
7244
7452
|
if (options.dryRun) {
|
|
7245
7453
|
spinner.info("Dry run mode");
|
|
7246
7454
|
console.log("");
|
|
@@ -7253,11 +7461,11 @@ async function createCommand(issueId, options) {
|
|
|
7253
7461
|
console.log(` Branch: ${chalk33.cyan(branchName)}`);
|
|
7254
7462
|
return;
|
|
7255
7463
|
}
|
|
7256
|
-
if (
|
|
7464
|
+
if (existsSync30(workspacePath)) {
|
|
7257
7465
|
spinner.fail(`Workspace already exists: ${workspacePath}`);
|
|
7258
7466
|
process.exit(1);
|
|
7259
7467
|
}
|
|
7260
|
-
if (!
|
|
7468
|
+
if (!existsSync30(join29(projectRoot, ".git"))) {
|
|
7261
7469
|
spinner.fail("Not a git repository. Run this from the project root.");
|
|
7262
7470
|
process.exit(1);
|
|
7263
7471
|
}
|
|
@@ -7266,7 +7474,7 @@ async function createCommand(issueId, options) {
|
|
|
7266
7474
|
const resolvedWorkspace = resolve(workspacePath);
|
|
7267
7475
|
const resolvedPlanning = resolve(resolvedWorkspace, ".planning");
|
|
7268
7476
|
const isUnderWorkspacesDir = resolvedWorkspace.match(/\/workspaces\/feature-[a-z0-9-]+$/);
|
|
7269
|
-
if (isUnderWorkspacesDir && resolvedPlanning ===
|
|
7477
|
+
if (isUnderWorkspacesDir && resolvedPlanning === join29(resolvedWorkspace, ".planning") && existsSync30(join29(resolvedWorkspace, ".git")) && existsSync30(resolvedPlanning)) {
|
|
7270
7478
|
rmSync3(resolvedPlanning, { recursive: true, force: true });
|
|
7271
7479
|
console.log(" Removed stale .planning/ directory from previous issue");
|
|
7272
7480
|
}
|
|
@@ -7288,7 +7496,7 @@ async function createCommand(issueId, options) {
|
|
|
7288
7496
|
BEAD_ID: workspaceBeadId
|
|
7289
7497
|
};
|
|
7290
7498
|
const claudeMd = generateClaudeMd(projectRoot, variables);
|
|
7291
|
-
writeFileSync10(
|
|
7499
|
+
writeFileSync10(join29(workspacePath, "CLAUDE.md"), claudeMd);
|
|
7292
7500
|
let skillsResult = { added: [], updated: [], skipped: [], overlayed: [] };
|
|
7293
7501
|
if (options.skills !== false) {
|
|
7294
7502
|
spinner.text = "Merging skills and agents...";
|
|
@@ -7298,20 +7506,20 @@ async function createCommand(issueId, options) {
|
|
|
7298
7506
|
let dockerError;
|
|
7299
7507
|
if (options.docker) {
|
|
7300
7508
|
const composeLocations = [
|
|
7301
|
-
|
|
7302
|
-
|
|
7303
|
-
|
|
7304
|
-
|
|
7305
|
-
|
|
7306
|
-
|
|
7307
|
-
|
|
7509
|
+
join29(workspacePath, "docker-compose.yml"),
|
|
7510
|
+
join29(workspacePath, "docker-compose.yaml"),
|
|
7511
|
+
join29(workspacePath, ".devcontainer", "docker-compose.yml"),
|
|
7512
|
+
join29(workspacePath, ".devcontainer", "docker-compose.yaml"),
|
|
7513
|
+
join29(workspacePath, ".devcontainer", "docker-compose.devcontainer.yml"),
|
|
7514
|
+
join29(workspacePath, ".devcontainer", "compose.yml"),
|
|
7515
|
+
join29(workspacePath, ".devcontainer", "compose.yaml")
|
|
7308
7516
|
];
|
|
7309
|
-
const composeFile = composeLocations.find((f) =>
|
|
7517
|
+
const composeFile = composeLocations.find((f) => existsSync30(f));
|
|
7310
7518
|
if (composeFile) {
|
|
7311
7519
|
spinner.text = "Starting Docker containers...";
|
|
7312
7520
|
try {
|
|
7313
|
-
const composeDir =
|
|
7314
|
-
await
|
|
7521
|
+
const composeDir = join29(composeFile, "..");
|
|
7522
|
+
await execAsync8(`docker compose -f "${composeFile}" up -d --build`, {
|
|
7315
7523
|
cwd: composeDir,
|
|
7316
7524
|
encoding: "utf-8",
|
|
7317
7525
|
timeout: 3e5
|
|
@@ -7377,15 +7585,15 @@ async function listCommand2(options) {
|
|
|
7377
7585
|
const workspaces2 = [];
|
|
7378
7586
|
if (isPolyrepo && config2.workspace?.repos) {
|
|
7379
7587
|
for (const repo of config2.workspace.repos) {
|
|
7380
|
-
const repoPath =
|
|
7381
|
-
if (!
|
|
7588
|
+
const repoPath = join29(config2.path, repo.path);
|
|
7589
|
+
if (!existsSync30(join29(repoPath, ".git"))) continue;
|
|
7382
7590
|
const repoWorktrees = listWorktrees(repoPath);
|
|
7383
7591
|
for (const wt of repoWorktrees) {
|
|
7384
7592
|
if (wt.path.includes("/workspaces/") || wt.path.includes("\\workspaces\\")) {
|
|
7385
7593
|
const parts = wt.path.split("/workspaces/");
|
|
7386
7594
|
if (parts.length > 1) {
|
|
7387
7595
|
const workspaceDir = parts[1].split("/")[0];
|
|
7388
|
-
const canonicalPath =
|
|
7596
|
+
const canonicalPath = join29(config2.path, "workspaces", workspaceDir);
|
|
7389
7597
|
if (!workspaces2.some((w) => w.path === canonicalPath)) {
|
|
7390
7598
|
workspaces2.push({ ...wt, path: canonicalPath });
|
|
7391
7599
|
}
|
|
@@ -7394,7 +7602,7 @@ async function listCommand2(options) {
|
|
|
7394
7602
|
}
|
|
7395
7603
|
}
|
|
7396
7604
|
} else {
|
|
7397
|
-
if (!
|
|
7605
|
+
if (!existsSync30(join29(config2.path, ".git"))) continue;
|
|
7398
7606
|
const worktrees2 = listWorktrees(config2.path);
|
|
7399
7607
|
for (const wt of worktrees2) {
|
|
7400
7608
|
if (wt.path.includes("/workspaces/") || wt.path.includes("\\workspaces\\")) {
|
|
@@ -7434,7 +7642,7 @@ ${proj.projectName}
|
|
|
7434
7642
|
return;
|
|
7435
7643
|
}
|
|
7436
7644
|
const projectRoot = process.cwd();
|
|
7437
|
-
if (!
|
|
7645
|
+
if (!existsSync30(join29(projectRoot, ".git"))) {
|
|
7438
7646
|
console.error(chalk33.red("Not a git repository."));
|
|
7439
7647
|
if (projects.length > 0) {
|
|
7440
7648
|
console.log(chalk33.dim("Tip: Use --all to list workspaces across all registered projects."));
|
|
@@ -7504,7 +7712,7 @@ async function destroyCommand(issueId, options) {
|
|
|
7504
7712
|
spinner.text = "Running custom remove command...";
|
|
7505
7713
|
const cmd = `${projectConfig.workspace_remove_command} ${normalizedId}`;
|
|
7506
7714
|
try {
|
|
7507
|
-
const { stdout } = await
|
|
7715
|
+
const { stdout } = await execAsync8(cmd, {
|
|
7508
7716
|
cwd: projectConfig.path,
|
|
7509
7717
|
encoding: "utf-8",
|
|
7510
7718
|
timeout: 12e4
|
|
@@ -7530,17 +7738,17 @@ async function destroyCommand(issueId, options) {
|
|
|
7530
7738
|
projectRoot = process.cwd();
|
|
7531
7739
|
}
|
|
7532
7740
|
}
|
|
7533
|
-
const workspacePath =
|
|
7534
|
-
if (!
|
|
7535
|
-
const cwdPath =
|
|
7536
|
-
if (projectRoot !== process.cwd() &&
|
|
7741
|
+
const workspacePath = join29(projectRoot, "workspaces", folderName);
|
|
7742
|
+
if (!existsSync30(workspacePath)) {
|
|
7743
|
+
const cwdPath = join29(process.cwd(), "workspaces", folderName);
|
|
7744
|
+
if (projectRoot !== process.cwd() && existsSync30(cwdPath)) {
|
|
7537
7745
|
projectRoot = process.cwd();
|
|
7538
7746
|
} else {
|
|
7539
7747
|
spinner.fail(`Workspace not found: ${workspacePath}`);
|
|
7540
7748
|
process.exit(1);
|
|
7541
7749
|
}
|
|
7542
7750
|
}
|
|
7543
|
-
const finalWorkspacePath =
|
|
7751
|
+
const finalWorkspacePath = join29(projectRoot, "workspaces", folderName);
|
|
7544
7752
|
spinner.text = "Removing git worktree...";
|
|
7545
7753
|
removeWorktree2(projectRoot, finalWorkspacePath);
|
|
7546
7754
|
spinner.succeed(`Workspace destroyed: ${folderName}`);
|
|
@@ -7578,7 +7786,7 @@ async function createRemoteWorkspace(issueId, normalizedId, branchName, spinner,
|
|
|
7578
7786
|
}
|
|
7579
7787
|
if (!projectId) {
|
|
7580
7788
|
try {
|
|
7581
|
-
const { stdout } = await
|
|
7789
|
+
const { stdout } = await execAsync8("git remote get-url origin", {
|
|
7582
7790
|
cwd: projectRoot,
|
|
7583
7791
|
encoding: "utf-8"
|
|
7584
7792
|
});
|
|
@@ -7603,7 +7811,7 @@ async function createRemoteWorkspace(issueId, normalizedId, branchName, spinner,
|
|
|
7603
7811
|
const vmInfo = await fly.createVm(vmName);
|
|
7604
7812
|
let repoUrl = "";
|
|
7605
7813
|
try {
|
|
7606
|
-
const { stdout } = await
|
|
7814
|
+
const { stdout } = await execAsync8("git remote get-url origin", {
|
|
7607
7815
|
cwd: projectRoot,
|
|
7608
7816
|
encoding: "utf-8"
|
|
7609
7817
|
});
|
|
@@ -7624,13 +7832,13 @@ async function createRemoteWorkspace(issueId, normalizedId, branchName, spinner,
|
|
|
7624
7832
|
await fly.ssh(vmName, `ssh-keyscan -t ed25519,rsa ${gitHost} >> ~/.ssh/known_hosts 2>/dev/null`);
|
|
7625
7833
|
}
|
|
7626
7834
|
const sshKeyPaths = [
|
|
7627
|
-
|
|
7628
|
-
|
|
7629
|
-
|
|
7835
|
+
join29(homedir14(), ".panopticon", "ssh", "exe-dev-key"),
|
|
7836
|
+
join29(homedir14(), ".ssh", "id_ed25519"),
|
|
7837
|
+
join29(homedir14(), ".ssh", "id_rsa")
|
|
7630
7838
|
];
|
|
7631
|
-
const sshKeyPath = sshKeyPaths.find((p) =>
|
|
7839
|
+
const sshKeyPath = sshKeyPaths.find((p) => existsSync30(p));
|
|
7632
7840
|
if (sshKeyPath) {
|
|
7633
|
-
const sshKeyBase64 = Buffer.from(
|
|
7841
|
+
const sshKeyBase64 = Buffer.from(readFileSync27(sshKeyPath, "utf-8")).toString("base64");
|
|
7634
7842
|
const keyFilename = sshKeyPath.includes("id_rsa") ? "id_rsa" : "id_ed25519";
|
|
7635
7843
|
await fly.ssh(vmName, `echo '${sshKeyBase64}' | base64 -d > ~/.ssh/${keyFilename} && chmod 600 ~/.ssh/${keyFilename}`);
|
|
7636
7844
|
}
|
|
@@ -7646,11 +7854,11 @@ async function createRemoteWorkspace(issueId, normalizedId, branchName, spinner,
|
|
|
7646
7854
|
await fly.ssh(vmName, "mkdir -p ~/workspace");
|
|
7647
7855
|
for (const repo of projectConfig.workspace.repos) {
|
|
7648
7856
|
spinner.text = `Cloning ${repo.name}...`;
|
|
7649
|
-
const rawRepoPath =
|
|
7650
|
-
const actualRepoPath =
|
|
7857
|
+
const rawRepoPath = join29(projectRoot, repo.path);
|
|
7858
|
+
const actualRepoPath = existsSync30(rawRepoPath) ? realpathSync(rawRepoPath) : rawRepoPath;
|
|
7651
7859
|
let repoRemoteUrl;
|
|
7652
7860
|
try {
|
|
7653
|
-
const { stdout } = await
|
|
7861
|
+
const { stdout } = await execAsync8("git remote get-url origin", {
|
|
7654
7862
|
cwd: actualRepoPath,
|
|
7655
7863
|
encoding: "utf-8"
|
|
7656
7864
|
});
|
|
@@ -7686,7 +7894,7 @@ async function createRemoteWorkspace(issueId, normalizedId, branchName, spinner,
|
|
|
7686
7894
|
spinner.text = "Configuring Claude Code...";
|
|
7687
7895
|
await fly.ssh(vmName, `mkdir -p ~/.claude`);
|
|
7688
7896
|
try {
|
|
7689
|
-
const { stdout: credentials } = await
|
|
7897
|
+
const { stdout: credentials } = await execAsync8(
|
|
7690
7898
|
'security find-generic-password -s "Claude Code-credentials" -w 2>/dev/null',
|
|
7691
7899
|
{ encoding: "utf-8" }
|
|
7692
7900
|
);
|
|
@@ -7777,8 +7985,8 @@ async function migrateCommand(issueId, options) {
|
|
|
7777
7985
|
}
|
|
7778
7986
|
spinner.text = "Syncing beads...";
|
|
7779
7987
|
try {
|
|
7780
|
-
await
|
|
7781
|
-
await
|
|
7988
|
+
await execAsync8("bd sync", { encoding: "utf-8" });
|
|
7989
|
+
await execAsync8('git add .beads/ && git commit -m "Sync beads before migration" && git push', { encoding: "utf-8" });
|
|
7782
7990
|
} catch {
|
|
7783
7991
|
}
|
|
7784
7992
|
const branchName = `feature/${normalizedId}`;
|
|
@@ -7906,8 +8114,8 @@ async function destroyRemoteWorkspace(issueId, normalizedId, metadata, spinner,
|
|
|
7906
8114
|
await fly.ssh(metadata.vmName, `tmux kill-session -t ${agentId} 2>/dev/null || true`);
|
|
7907
8115
|
spinner.text = "Deleting VM...";
|
|
7908
8116
|
await fly.deleteVm(metadata.vmName);
|
|
7909
|
-
const metadataFile =
|
|
7910
|
-
if (
|
|
8117
|
+
const metadataFile = join29(WORKSPACES_DIR, `${normalizedId}.yaml`);
|
|
8118
|
+
if (existsSync30(metadataFile)) {
|
|
7911
8119
|
rmSync3(metadataFile);
|
|
7912
8120
|
}
|
|
7913
8121
|
spinner.succeed(`Remote workspace ${issueId} destroyed`);
|
|
@@ -7933,9 +8141,9 @@ async function updateCommand(issueId, options) {
|
|
|
7933
8141
|
process.exit(1);
|
|
7934
8142
|
}
|
|
7935
8143
|
const workspaceConfig = projectConfig.workspace;
|
|
7936
|
-
const workspacesDir =
|
|
7937
|
-
const workspacePath =
|
|
7938
|
-
if (!
|
|
8144
|
+
const workspacesDir = join29(projectConfig.path, workspaceConfig?.workspaces_dir || "workspaces");
|
|
8145
|
+
const workspacePath = join29(workspacesDir, folderName);
|
|
8146
|
+
if (!existsSync30(workspacePath)) {
|
|
7939
8147
|
spinner.fail(`Workspace not found: ${workspacePath}`);
|
|
7940
8148
|
process.exit(1);
|
|
7941
8149
|
}
|
|
@@ -7955,7 +8163,7 @@ async function updateCommand(issueId, options) {
|
|
|
7955
8163
|
const result = mergeSkillsIntoWorkspace(workspacePath);
|
|
7956
8164
|
if (workspaceConfig?.agent?.template_dir && (workspaceConfig.agent.copy_dirs || workspaceConfig.agent.symlinks)) {
|
|
7957
8165
|
spinner.text = "Applying project template overlay...";
|
|
7958
|
-
const templateDir =
|
|
8166
|
+
const templateDir = join29(projectConfig.path, workspaceConfig.agent.template_dir);
|
|
7959
8167
|
const overlayed = applyProjectTemplateOverlay(workspacePath, templateDir);
|
|
7960
8168
|
result.overlayed = overlayed;
|
|
7961
8169
|
}
|
|
@@ -7987,12 +8195,12 @@ import ora17 from "ora";
|
|
|
7987
8195
|
// src/lib/test-runner.ts
|
|
7988
8196
|
init_esm_shims();
|
|
7989
8197
|
init_workspace_config();
|
|
7990
|
-
import { existsSync as
|
|
7991
|
-
import { join as
|
|
7992
|
-
import { exec as
|
|
7993
|
-
import { promisify as
|
|
8198
|
+
import { existsSync as existsSync31, mkdirSync as mkdirSync12, writeFileSync as writeFileSync11 } from "fs";
|
|
8199
|
+
import { join as join30, basename as basename7 } from "path";
|
|
8200
|
+
import { exec as exec9 } from "child_process";
|
|
8201
|
+
import { promisify as promisify9 } from "util";
|
|
7994
8202
|
import { homedir as homedir15 } from "os";
|
|
7995
|
-
var
|
|
8203
|
+
var execAsync9 = promisify9(exec9);
|
|
7996
8204
|
function formatDuration(seconds) {
|
|
7997
8205
|
const minutes = Math.floor(seconds / 60);
|
|
7998
8206
|
const secs = seconds % 60;
|
|
@@ -8042,8 +8250,8 @@ function parseTestOutput(output, type) {
|
|
|
8042
8250
|
return { passed, failed };
|
|
8043
8251
|
}
|
|
8044
8252
|
async function runTestSuite(testName, testConfig, workspacePath, placeholders, reportsDir, timestamp) {
|
|
8045
|
-
const testPath =
|
|
8046
|
-
const logFile =
|
|
8253
|
+
const testPath = join30(workspacePath, testConfig.path);
|
|
8254
|
+
const logFile = join30(reportsDir, `${testName}-${timestamp}.log`);
|
|
8047
8255
|
const result = {
|
|
8048
8256
|
name: testName,
|
|
8049
8257
|
status: "pending",
|
|
@@ -8066,7 +8274,7 @@ async function runTestSuite(testName, testConfig, workspacePath, placeholders, r
|
|
|
8066
8274
|
}
|
|
8067
8275
|
const startTime = Date.now();
|
|
8068
8276
|
try {
|
|
8069
|
-
const { stdout, stderr } = await
|
|
8277
|
+
const { stdout, stderr } = await execAsync9(command, {
|
|
8070
8278
|
cwd: testPath,
|
|
8071
8279
|
env,
|
|
8072
8280
|
timeout: 6e5,
|
|
@@ -8135,10 +8343,10 @@ function generateReport2(result) {
|
|
|
8135
8343
|
async function sendNotification(result) {
|
|
8136
8344
|
const title = `Tests (${result.target}): ${result.overallStatus === "passed" ? "\u2705 All Passed" : "\u274C Failed"}`;
|
|
8137
8345
|
const message = result.overallStatus === "passed" ? "All test suites passed" : `${result.totalFailures} suite(s) failed. Check report: ${result.reportFile}`;
|
|
8138
|
-
const notifyScript =
|
|
8139
|
-
if (
|
|
8346
|
+
const notifyScript = join30(homedir15(), ".panopticon", "bin", "notify-complete");
|
|
8347
|
+
if (existsSync31(notifyScript)) {
|
|
8140
8348
|
try {
|
|
8141
|
-
await
|
|
8349
|
+
await execAsync9(`"${notifyScript}" "${result.target}" "${message}"`);
|
|
8142
8350
|
} catch {
|
|
8143
8351
|
}
|
|
8144
8352
|
}
|
|
@@ -8154,12 +8362,12 @@ async function runTests(options) {
|
|
|
8154
8362
|
let target;
|
|
8155
8363
|
let baseUrl;
|
|
8156
8364
|
if (featureName) {
|
|
8157
|
-
const workspacesDir =
|
|
8365
|
+
const workspacesDir = join30(projectConfig.path, workspaceConfig?.workspaces_dir || "workspaces");
|
|
8158
8366
|
const featureFolder2 = `feature-${featureName}`;
|
|
8159
|
-
workspacePath =
|
|
8367
|
+
workspacePath = join30(workspacesDir, featureFolder2);
|
|
8160
8368
|
target = featureFolder2;
|
|
8161
8369
|
baseUrl = workspaceConfig?.dns?.domain ? `https://${featureFolder2}.${workspaceConfig.dns.domain}` : `http://localhost:3000`;
|
|
8162
|
-
if (!
|
|
8370
|
+
if (!existsSync31(workspacePath)) {
|
|
8163
8371
|
throw new Error(`Workspace not found: ${workspacePath}`);
|
|
8164
8372
|
}
|
|
8165
8373
|
} else {
|
|
@@ -8176,13 +8384,13 @@ async function runTests(options) {
|
|
|
8176
8384
|
DOMAIN: workspaceConfig?.dns?.domain || "localhost",
|
|
8177
8385
|
PROJECT_NAME: basename7(projectConfig.path),
|
|
8178
8386
|
PROJECT_PATH: projectConfig.path,
|
|
8179
|
-
PROJECTS_DIR:
|
|
8387
|
+
PROJECTS_DIR: join30(projectConfig.path, ".."),
|
|
8180
8388
|
WORKSPACE_PATH: workspacePath
|
|
8181
8389
|
};
|
|
8182
|
-
const reportsDir =
|
|
8390
|
+
const reportsDir = join30(projectConfig.path, "reports");
|
|
8183
8391
|
mkdirSync12(reportsDir, { recursive: true });
|
|
8184
8392
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
|
|
8185
|
-
const reportFile =
|
|
8393
|
+
const reportFile = join30(reportsDir, `test-run-${target}-${timestamp}.md`);
|
|
8186
8394
|
const result = {
|
|
8187
8395
|
target,
|
|
8188
8396
|
baseUrl,
|
|
@@ -8346,21 +8554,21 @@ import chalk35 from "chalk";
|
|
|
8346
8554
|
import ora18 from "ora";
|
|
8347
8555
|
import inquirer5 from "inquirer";
|
|
8348
8556
|
import { execSync as execSync4 } from "child_process";
|
|
8349
|
-
import { existsSync as
|
|
8350
|
-
import { join as
|
|
8557
|
+
import { existsSync as existsSync32, mkdirSync as mkdirSync13, writeFileSync as writeFileSync12, readFileSync as readFileSync28, copyFileSync, readdirSync as readdirSync15, statSync as statSync6 } from "fs";
|
|
8558
|
+
import { join as join31 } from "path";
|
|
8351
8559
|
import { homedir as homedir16 } from "os";
|
|
8352
8560
|
function registerInstallCommand(program2) {
|
|
8353
8561
|
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);
|
|
8354
8562
|
}
|
|
8355
8563
|
function copyDirectoryRecursive(source, dest) {
|
|
8356
|
-
if (!
|
|
8564
|
+
if (!existsSync32(source)) {
|
|
8357
8565
|
throw new Error(`Source directory not found: ${source}`);
|
|
8358
8566
|
}
|
|
8359
8567
|
mkdirSync13(dest, { recursive: true });
|
|
8360
8568
|
const entries = readdirSync15(source);
|
|
8361
8569
|
for (const entry of entries) {
|
|
8362
|
-
const sourcePath =
|
|
8363
|
-
const destPath =
|
|
8570
|
+
const sourcePath = join31(source, entry);
|
|
8571
|
+
const destPath = join31(dest, entry);
|
|
8364
8572
|
const stat = statSync6(sourcePath);
|
|
8365
8573
|
if (stat.isDirectory()) {
|
|
8366
8574
|
copyDirectoryRecursive(sourcePath, destPath);
|
|
@@ -8460,7 +8668,7 @@ function checkPrerequisites() {
|
|
|
8460
8668
|
message: hasJq ? "installed" : "not found",
|
|
8461
8669
|
fix: "apt install jq / brew install jq"
|
|
8462
8670
|
});
|
|
8463
|
-
const hasTtyd = checkCommand2("ttyd") ||
|
|
8671
|
+
const hasTtyd = checkCommand2("ttyd") || existsSync32(join31(homedir16(), "bin", "ttyd"));
|
|
8464
8672
|
results.push({
|
|
8465
8673
|
name: "ttyd",
|
|
8466
8674
|
passed: hasTtyd,
|
|
@@ -8536,9 +8744,9 @@ async function installCommand(options) {
|
|
|
8536
8744
|
execSync4("brew install mkcert", { stdio: "pipe", timeout: 12e4 });
|
|
8537
8745
|
spinner.succeed("mkcert installed via Homebrew");
|
|
8538
8746
|
} else {
|
|
8539
|
-
const binDir =
|
|
8747
|
+
const binDir = join31(homedir16(), ".local", "bin");
|
|
8540
8748
|
mkdirSync13(binDir, { recursive: true });
|
|
8541
|
-
const mkcertPath =
|
|
8749
|
+
const mkcertPath = join31(binDir, "mkcert");
|
|
8542
8750
|
const arch = process.arch === "x64" ? "amd64" : process.arch;
|
|
8543
8751
|
execSync4(`curl -sL "https://github.com/FiloSottile/mkcert/releases/latest/download/mkcert-v1.4.4-linux-${arch}" -o "${mkcertPath}" && chmod +x "${mkcertPath}"`, {
|
|
8544
8752
|
stdio: "pipe",
|
|
@@ -8557,14 +8765,14 @@ async function installCommand(options) {
|
|
|
8557
8765
|
execSync4("mkcert -install", { stdio: "pipe" });
|
|
8558
8766
|
spinner.succeed("mkcert CA installed");
|
|
8559
8767
|
spinner.start("Generating wildcard certificates...");
|
|
8560
|
-
const traefikCertFile =
|
|
8561
|
-
const traefikKeyFile =
|
|
8768
|
+
const traefikCertFile = join31(TRAEFIK_CERTS_DIR, "_wildcard.pan.localhost.pem");
|
|
8769
|
+
const traefikKeyFile = join31(TRAEFIK_CERTS_DIR, "_wildcard.pan.localhost-key.pem");
|
|
8562
8770
|
execSync4(
|
|
8563
8771
|
`mkcert -cert-file "${traefikCertFile}" -key-file "${traefikKeyFile}" "pan.localhost" "*.pan.localhost" "*.localhost" localhost 127.0.0.1 ::1`,
|
|
8564
8772
|
{ stdio: "pipe" }
|
|
8565
8773
|
);
|
|
8566
|
-
const legacyCertFile =
|
|
8567
|
-
const legacyKeyFile =
|
|
8774
|
+
const legacyCertFile = join31(CERTS_DIR, "localhost.pem");
|
|
8775
|
+
const legacyKeyFile = join31(CERTS_DIR, "localhost-key.pem");
|
|
8568
8776
|
copyFileSync(traefikCertFile, legacyCertFile);
|
|
8569
8777
|
copyFileSync(traefikKeyFile, legacyKeyFile);
|
|
8570
8778
|
spinner.succeed("Wildcard certificates generated (*.pan.localhost, *.localhost)");
|
|
@@ -8582,13 +8790,13 @@ async function installCommand(options) {
|
|
|
8582
8790
|
spinner.info("Skipping mkcert (not installed)");
|
|
8583
8791
|
}
|
|
8584
8792
|
}
|
|
8585
|
-
const hasTtyd = checkCommand2("ttyd") ||
|
|
8793
|
+
const hasTtyd = checkCommand2("ttyd") || existsSync32(join31(homedir16(), "bin", "ttyd"));
|
|
8586
8794
|
if (!hasTtyd) {
|
|
8587
8795
|
spinner.start("Installing ttyd (web terminal)...");
|
|
8588
8796
|
try {
|
|
8589
|
-
const binDir =
|
|
8797
|
+
const binDir = join31(homedir16(), "bin");
|
|
8590
8798
|
mkdirSync13(binDir, { recursive: true });
|
|
8591
|
-
const ttydPath =
|
|
8799
|
+
const ttydPath = join31(binDir, "ttyd");
|
|
8592
8800
|
const plat2 = detectPlatform();
|
|
8593
8801
|
let downloadUrl = "";
|
|
8594
8802
|
if (plat2 === "darwin") {
|
|
@@ -8700,9 +8908,9 @@ async function installCommand(options) {
|
|
|
8700
8908
|
if (!hasOxNow) {
|
|
8701
8909
|
spinner.start("Installing SageOx CLI (ox)...");
|
|
8702
8910
|
try {
|
|
8703
|
-
const binDir =
|
|
8911
|
+
const binDir = join31(homedir16(), ".local", "bin");
|
|
8704
8912
|
mkdirSync13(binDir, { recursive: true });
|
|
8705
|
-
const oxPath =
|
|
8913
|
+
const oxPath = join31(binDir, "ox");
|
|
8706
8914
|
const arch = process.arch === "x64" ? "amd64" : process.arch;
|
|
8707
8915
|
const plat2 = detectPlatform();
|
|
8708
8916
|
const platform2 = plat2 === "darwin" ? "darwin" : "linux";
|
|
@@ -8721,7 +8929,7 @@ async function installCommand(options) {
|
|
|
8721
8929
|
if (!options.minimal) {
|
|
8722
8930
|
spinner.start("Setting up Traefik configuration...");
|
|
8723
8931
|
try {
|
|
8724
|
-
if (!
|
|
8932
|
+
if (!existsSync32(join31(TRAEFIK_DIR, "docker-compose.yml"))) {
|
|
8725
8933
|
copyDirectoryRecursive(SOURCE_TRAEFIK_TEMPLATES, TRAEFIK_DIR);
|
|
8726
8934
|
cleanupTemplateFiles();
|
|
8727
8935
|
spinner.succeed("Traefik configuration created from templates");
|
|
@@ -8734,9 +8942,9 @@ async function installCommand(options) {
|
|
|
8734
8942
|
if (generateTlsConfig()) {
|
|
8735
8943
|
spinner.succeed("TLS config generated (tls.yml)");
|
|
8736
8944
|
}
|
|
8737
|
-
const existingCompose =
|
|
8738
|
-
if (
|
|
8739
|
-
const content =
|
|
8945
|
+
const existingCompose = join31(TRAEFIK_DIR, "docker-compose.yml");
|
|
8946
|
+
if (existsSync32(existingCompose)) {
|
|
8947
|
+
const content = readFileSync28(existingCompose, "utf-8");
|
|
8740
8948
|
if (content.includes("panopticon:") && !content.includes("external: true")) {
|
|
8741
8949
|
const patched = content.replace(
|
|
8742
8950
|
/networks:\s*\n\s*panopticon:\s*\n\s*name: panopticon\s*\n\s*driver: bridge/,
|
|
@@ -8751,8 +8959,8 @@ async function installCommand(options) {
|
|
|
8751
8959
|
console.log(chalk35.yellow("You can set up Traefik manually later"));
|
|
8752
8960
|
}
|
|
8753
8961
|
}
|
|
8754
|
-
const configFile =
|
|
8755
|
-
const configExists =
|
|
8962
|
+
const configFile = join31(PANOPTICON_HOME, "config.toml");
|
|
8963
|
+
const configExists = existsSync32(configFile);
|
|
8756
8964
|
if (!configExists) {
|
|
8757
8965
|
spinner.start("Creating default config...");
|
|
8758
8966
|
} else {
|
|
@@ -8993,10 +9201,10 @@ init_esm_shims();
|
|
|
8993
9201
|
init_agents();
|
|
8994
9202
|
init_tmux();
|
|
8995
9203
|
init_jsonl_parser();
|
|
8996
|
-
import { existsSync as
|
|
8997
|
-
import { join as
|
|
9204
|
+
import { existsSync as existsSync33, readFileSync as readFileSync29, statSync as statSync7, mkdirSync as mkdirSync14, writeFileSync as writeFileSync13 } from "fs";
|
|
9205
|
+
import { join as join32 } from "path";
|
|
8998
9206
|
import { homedir as homedir17 } from "os";
|
|
8999
|
-
var CLAUDE_PROJECTS_DIR =
|
|
9207
|
+
var CLAUDE_PROJECTS_DIR = join32(homedir17(), ".claude", "projects");
|
|
9000
9208
|
var ClaudeCodeRuntime = class {
|
|
9001
9209
|
name = "claude-code";
|
|
9002
9210
|
/**
|
|
@@ -9006,15 +9214,15 @@ var ClaudeCodeRuntime = class {
|
|
|
9006
9214
|
* We need to find the project directory that contains sessions for this workspace.
|
|
9007
9215
|
*/
|
|
9008
9216
|
getProjectDirForWorkspace(workspace) {
|
|
9009
|
-
if (!
|
|
9217
|
+
if (!existsSync33(CLAUDE_PROJECTS_DIR)) {
|
|
9010
9218
|
return null;
|
|
9011
9219
|
}
|
|
9012
9220
|
const projectDirs = getProjectDirs();
|
|
9013
9221
|
for (const projectDir of projectDirs) {
|
|
9014
|
-
const indexPath =
|
|
9015
|
-
if (
|
|
9222
|
+
const indexPath = join32(projectDir, "sessions-index.json");
|
|
9223
|
+
if (existsSync33(indexPath)) {
|
|
9016
9224
|
try {
|
|
9017
|
-
const indexContent =
|
|
9225
|
+
const indexContent = readFileSync29(indexPath, "utf-8");
|
|
9018
9226
|
if (indexContent.includes(workspace)) {
|
|
9019
9227
|
return projectDir;
|
|
9020
9228
|
}
|
|
@@ -9028,12 +9236,12 @@ var ClaudeCodeRuntime = class {
|
|
|
9028
9236
|
* Get the active session ID for an agent from the sessions index
|
|
9029
9237
|
*/
|
|
9030
9238
|
getActiveSessionId(projectDir) {
|
|
9031
|
-
const indexPath =
|
|
9032
|
-
if (!
|
|
9239
|
+
const indexPath = join32(projectDir, "sessions-index.json");
|
|
9240
|
+
if (!existsSync33(indexPath)) {
|
|
9033
9241
|
return null;
|
|
9034
9242
|
}
|
|
9035
9243
|
try {
|
|
9036
|
-
const indexContent =
|
|
9244
|
+
const indexContent = readFileSync29(indexPath, "utf-8");
|
|
9037
9245
|
const index = JSON.parse(indexContent);
|
|
9038
9246
|
if (index.sessions && Array.isArray(index.sessions)) {
|
|
9039
9247
|
const sessions = index.sessions;
|
|
@@ -9068,8 +9276,8 @@ var ClaudeCodeRuntime = class {
|
|
|
9068
9276
|
}
|
|
9069
9277
|
const sessionId = this.getActiveSessionId(projectDir);
|
|
9070
9278
|
if (sessionId) {
|
|
9071
|
-
const sessionPath =
|
|
9072
|
-
if (
|
|
9279
|
+
const sessionPath = join32(projectDir, `${sessionId}.jsonl`);
|
|
9280
|
+
if (existsSync33(sessionPath)) {
|
|
9073
9281
|
return sessionPath;
|
|
9074
9282
|
}
|
|
9075
9283
|
}
|
|
@@ -9082,7 +9290,7 @@ var ClaudeCodeRuntime = class {
|
|
|
9082
9290
|
*/
|
|
9083
9291
|
getLastActivity(agentId) {
|
|
9084
9292
|
const sessionPath = this.getSessionPath(agentId);
|
|
9085
|
-
if (!sessionPath || !
|
|
9293
|
+
if (!sessionPath || !existsSync33(sessionPath)) {
|
|
9086
9294
|
return null;
|
|
9087
9295
|
}
|
|
9088
9296
|
try {
|
|
@@ -9096,12 +9304,12 @@ var ClaudeCodeRuntime = class {
|
|
|
9096
9304
|
* Read active heartbeat file if it exists
|
|
9097
9305
|
*/
|
|
9098
9306
|
getActiveHeartbeat(agentId) {
|
|
9099
|
-
const heartbeatPath =
|
|
9100
|
-
if (!
|
|
9307
|
+
const heartbeatPath = join32(homedir17(), ".panopticon", "heartbeats", `${agentId}.json`);
|
|
9308
|
+
if (!existsSync33(heartbeatPath)) {
|
|
9101
9309
|
return null;
|
|
9102
9310
|
}
|
|
9103
9311
|
try {
|
|
9104
|
-
const content =
|
|
9312
|
+
const content = readFileSync29(heartbeatPath, "utf-8");
|
|
9105
9313
|
const data = JSON.parse(content);
|
|
9106
9314
|
const timestamp = new Date(data.timestamp);
|
|
9107
9315
|
const now = /* @__PURE__ */ new Date();
|
|
@@ -9205,11 +9413,11 @@ var ClaudeCodeRuntime = class {
|
|
|
9205
9413
|
throw new Error(`Agent ${agentId} is not running`);
|
|
9206
9414
|
}
|
|
9207
9415
|
await sendKeysAsync(agentId, message);
|
|
9208
|
-
const mailDir =
|
|
9416
|
+
const mailDir = join32(getAgentDir(agentId), "mail");
|
|
9209
9417
|
mkdirSync14(mailDir, { recursive: true });
|
|
9210
9418
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
9211
9419
|
writeFileSync13(
|
|
9212
|
-
|
|
9420
|
+
join32(mailDir, `${timestamp}.md`),
|
|
9213
9421
|
`# Message
|
|
9214
9422
|
|
|
9215
9423
|
${message}
|
|
@@ -9367,9 +9575,9 @@ init_agents();
|
|
|
9367
9575
|
// src/lib/cloister/triggers.ts
|
|
9368
9576
|
init_esm_shims();
|
|
9369
9577
|
init_config2();
|
|
9370
|
-
import { exec as
|
|
9371
|
-
import { promisify as
|
|
9372
|
-
var
|
|
9578
|
+
import { exec as exec10 } from "child_process";
|
|
9579
|
+
import { promisify as promisify10 } from "util";
|
|
9580
|
+
var execAsync10 = promisify10(exec10);
|
|
9373
9581
|
function checkStuckEscalation(health, currentModel, config2) {
|
|
9374
9582
|
const conf = config2 || loadCloisterConfig();
|
|
9375
9583
|
const stuckConfig = conf.handoffs?.auto_triggers?.stuck_escalation;
|
|
@@ -9493,7 +9701,7 @@ async function checkTaskCompletion(issueId, config2) {
|
|
|
9493
9701
|
};
|
|
9494
9702
|
}
|
|
9495
9703
|
try {
|
|
9496
|
-
const { stdout: output } = await
|
|
9704
|
+
const { stdout: output } = await execAsync10(`bd list --json -l ${issueId.toLowerCase()} --status closed`, {
|
|
9497
9705
|
encoding: "utf-8"
|
|
9498
9706
|
});
|
|
9499
9707
|
const tasks = JSON.parse(output);
|
|
@@ -9501,7 +9709,7 @@ async function checkTaskCompletion(issueId, config2) {
|
|
|
9501
9709
|
(t) => t.title.toLowerCase().includes("implement") || t.labels?.includes("implementation")
|
|
9502
9710
|
);
|
|
9503
9711
|
if (implementTask) {
|
|
9504
|
-
const { stdout: openOutput } = await
|
|
9712
|
+
const { stdout: openOutput } = await execAsync10(`bd list --json -l ${issueId.toLowerCase()} --status open`, {
|
|
9505
9713
|
encoding: "utf-8"
|
|
9506
9714
|
});
|
|
9507
9715
|
const openTasks = JSON.parse(openOutput);
|
|
@@ -9546,15 +9754,15 @@ async function checkAllTriggers(agentId, workspace, issueId, currentModel, healt
|
|
|
9546
9754
|
init_esm_shims();
|
|
9547
9755
|
init_agents();
|
|
9548
9756
|
import { writeFileSync as writeFileSync14, mkdirSync as mkdirSync15 } from "fs";
|
|
9549
|
-
import { join as
|
|
9757
|
+
import { join as join34 } from "path";
|
|
9550
9758
|
|
|
9551
9759
|
// src/lib/cloister/handoff-context.ts
|
|
9552
9760
|
init_esm_shims();
|
|
9553
|
-
import { existsSync as
|
|
9554
|
-
import { join as
|
|
9555
|
-
import { exec as
|
|
9556
|
-
import { promisify as
|
|
9557
|
-
var
|
|
9761
|
+
import { existsSync as existsSync34, readFileSync as readFileSync30 } from "fs";
|
|
9762
|
+
import { join as join33 } from "path";
|
|
9763
|
+
import { exec as exec11 } from "child_process";
|
|
9764
|
+
import { promisify as promisify11 } from "util";
|
|
9765
|
+
var execAsync11 = promisify11(exec11);
|
|
9558
9766
|
async function captureHandoffContext(agentState, targetModel, reason) {
|
|
9559
9767
|
const context = {
|
|
9560
9768
|
issueId: agentState.issueId,
|
|
@@ -9575,13 +9783,13 @@ async function captureHandoffContext(agentState, targetModel, reason) {
|
|
|
9575
9783
|
}
|
|
9576
9784
|
async function captureFiles(context, workspace) {
|
|
9577
9785
|
try {
|
|
9578
|
-
const stateFile =
|
|
9579
|
-
if (
|
|
9580
|
-
context.stateFile =
|
|
9786
|
+
const stateFile = join33(workspace, ".planning/STATE.md");
|
|
9787
|
+
if (existsSync34(stateFile)) {
|
|
9788
|
+
context.stateFile = readFileSync30(stateFile, "utf-8");
|
|
9581
9789
|
}
|
|
9582
|
-
const claudeMd =
|
|
9583
|
-
if (
|
|
9584
|
-
context.claudeMd =
|
|
9790
|
+
const claudeMd = join33(workspace, "CLAUDE.md");
|
|
9791
|
+
if (existsSync34(claudeMd)) {
|
|
9792
|
+
context.claudeMd = readFileSync30(claudeMd, "utf-8");
|
|
9585
9793
|
}
|
|
9586
9794
|
} catch (error) {
|
|
9587
9795
|
console.error("Error capturing files:", error);
|
|
@@ -9589,17 +9797,17 @@ async function captureFiles(context, workspace) {
|
|
|
9589
9797
|
}
|
|
9590
9798
|
async function captureGitState(context, workspace) {
|
|
9591
9799
|
try {
|
|
9592
|
-
const { stdout: branch } = await
|
|
9800
|
+
const { stdout: branch } = await execAsync11("git branch --show-current", {
|
|
9593
9801
|
cwd: workspace,
|
|
9594
9802
|
encoding: "utf-8"
|
|
9595
9803
|
});
|
|
9596
9804
|
context.gitBranch = branch.trim();
|
|
9597
|
-
const { stdout: status } = await
|
|
9805
|
+
const { stdout: status } = await execAsync11("git status --porcelain", {
|
|
9598
9806
|
cwd: workspace,
|
|
9599
9807
|
encoding: "utf-8"
|
|
9600
9808
|
});
|
|
9601
9809
|
context.uncommittedFiles = status.split("\n").filter((line) => line.trim()).map((line) => line.substring(3));
|
|
9602
|
-
const { stdout: lastCommit } = await
|
|
9810
|
+
const { stdout: lastCommit } = await execAsync11("git log -1 --oneline", {
|
|
9603
9811
|
cwd: workspace,
|
|
9604
9812
|
encoding: "utf-8"
|
|
9605
9813
|
});
|
|
@@ -9611,7 +9819,7 @@ async function captureGitState(context, workspace) {
|
|
|
9611
9819
|
async function captureBeadsTasks(context, issueId) {
|
|
9612
9820
|
try {
|
|
9613
9821
|
const label = issueId.toLowerCase();
|
|
9614
|
-
const { stdout: output } = await
|
|
9822
|
+
const { stdout: output } = await execAsync11(`bd list --json -l ${label}`, {
|
|
9615
9823
|
encoding: "utf-8"
|
|
9616
9824
|
});
|
|
9617
9825
|
const tasks = JSON.parse(output);
|
|
@@ -9772,9 +9980,9 @@ async function performKillAndSpawn(state, options) {
|
|
|
9772
9980
|
const context = await captureHandoffContext(state, options.targetModel, options.reason);
|
|
9773
9981
|
stopAgent(state.id);
|
|
9774
9982
|
const prompt = buildHandoffPrompt(context, options.additionalInstructions);
|
|
9775
|
-
const handoffDir =
|
|
9983
|
+
const handoffDir = join34(getAgentDir(state.id), "handoffs");
|
|
9776
9984
|
mkdirSync15(handoffDir, { recursive: true });
|
|
9777
|
-
const handoffFile =
|
|
9985
|
+
const handoffFile = join34(handoffDir, `handoff-${Date.now()}.md`);
|
|
9778
9986
|
writeFileSync14(handoffFile, prompt);
|
|
9779
9987
|
const newState = await spawnAgent({
|
|
9780
9988
|
issueId: state.issueId,
|
|
@@ -9883,12 +10091,12 @@ function sleep(ms) {
|
|
|
9883
10091
|
// src/lib/cloister/handoff-logger.ts
|
|
9884
10092
|
init_esm_shims();
|
|
9885
10093
|
init_paths();
|
|
9886
|
-
import { existsSync as
|
|
9887
|
-
import { join as
|
|
9888
|
-
var HANDOFF_LOG_FILE =
|
|
10094
|
+
import { existsSync as existsSync36, mkdirSync as mkdirSync16, appendFileSync as appendFileSync3, readFileSync as readFileSync31, writeFileSync as writeFileSync15 } from "fs";
|
|
10095
|
+
import { join as join35 } from "path";
|
|
10096
|
+
var HANDOFF_LOG_FILE = join35(PANOPTICON_HOME, "logs", "handoffs.jsonl");
|
|
9889
10097
|
function ensureLogDir() {
|
|
9890
|
-
const logDir =
|
|
9891
|
-
if (!
|
|
10098
|
+
const logDir = join35(PANOPTICON_HOME, "logs");
|
|
10099
|
+
if (!existsSync36(logDir)) {
|
|
9892
10100
|
mkdirSync16(logDir, { recursive: true });
|
|
9893
10101
|
}
|
|
9894
10102
|
}
|
|
@@ -9932,8 +10140,8 @@ function createHandoffEvent(agentId, issueId, context, trigger, success, errorMe
|
|
|
9932
10140
|
// src/lib/cloister/fpp-violations.ts
|
|
9933
10141
|
init_esm_shims();
|
|
9934
10142
|
init_hooks();
|
|
9935
|
-
import { readFileSync as
|
|
9936
|
-
import { join as
|
|
10143
|
+
import { readFileSync as readFileSync32, existsSync as existsSync37, writeFileSync as writeFileSync16, mkdirSync as mkdirSync17, unlinkSync as unlinkSync3 } from "fs";
|
|
10144
|
+
import { join as join36, dirname as dirname10 } from "path";
|
|
9937
10145
|
init_paths();
|
|
9938
10146
|
var DEFAULT_FPP_CONFIG = {
|
|
9939
10147
|
hook_idle_minutes: 5,
|
|
@@ -9941,13 +10149,13 @@ var DEFAULT_FPP_CONFIG = {
|
|
|
9941
10149
|
review_pending_minutes: 15,
|
|
9942
10150
|
max_nudges: 3
|
|
9943
10151
|
};
|
|
9944
|
-
var VIOLATIONS_DATA_FILE =
|
|
10152
|
+
var VIOLATIONS_DATA_FILE = join36(PANOPTICON_HOME, "fpp-violations.json");
|
|
9945
10153
|
function loadViolations() {
|
|
9946
|
-
if (!
|
|
10154
|
+
if (!existsSync37(VIOLATIONS_DATA_FILE)) {
|
|
9947
10155
|
return /* @__PURE__ */ new Map();
|
|
9948
10156
|
}
|
|
9949
10157
|
try {
|
|
9950
|
-
const fileContent =
|
|
10158
|
+
const fileContent = readFileSync32(VIOLATIONS_DATA_FILE, "utf-8");
|
|
9951
10159
|
const persisted = JSON.parse(fileContent);
|
|
9952
10160
|
return new Map(persisted.violations || []);
|
|
9953
10161
|
} catch (error) {
|
|
@@ -9958,7 +10166,7 @@ function loadViolations() {
|
|
|
9958
10166
|
function saveViolations(violations) {
|
|
9959
10167
|
try {
|
|
9960
10168
|
const dir = dirname10(VIOLATIONS_DATA_FILE);
|
|
9961
|
-
if (!
|
|
10169
|
+
if (!existsSync37(dir)) {
|
|
9962
10170
|
mkdirSync17(dir, { recursive: true });
|
|
9963
10171
|
}
|
|
9964
10172
|
const persisted = {
|
|
@@ -9966,7 +10174,7 @@ function saveViolations(violations) {
|
|
|
9966
10174
|
};
|
|
9967
10175
|
const tempFile = `${VIOLATIONS_DATA_FILE}.tmp`;
|
|
9968
10176
|
writeFileSync16(tempFile, JSON.stringify(persisted, null, 2));
|
|
9969
|
-
writeFileSync16(VIOLATIONS_DATA_FILE,
|
|
10177
|
+
writeFileSync16(VIOLATIONS_DATA_FILE, readFileSync32(tempFile));
|
|
9970
10178
|
try {
|
|
9971
10179
|
unlinkSync3(tempFile);
|
|
9972
10180
|
} catch (unlinkError) {
|
|
@@ -10056,11 +10264,11 @@ function clearOldViolations(hoursOld = 24) {
|
|
|
10056
10264
|
init_esm_shims();
|
|
10057
10265
|
init_paths();
|
|
10058
10266
|
init_config2();
|
|
10059
|
-
import { readFileSync as
|
|
10060
|
-
import { join as
|
|
10061
|
-
var COST_DATA_FILE =
|
|
10267
|
+
import { readFileSync as readFileSync33, existsSync as existsSync38, writeFileSync as writeFileSync17, mkdirSync as mkdirSync18, unlinkSync as unlinkSync4 } from "fs";
|
|
10268
|
+
import { join as join37, dirname as dirname11 } from "path";
|
|
10269
|
+
var COST_DATA_FILE = join37(PANOPTICON_HOME, "cost-data.json");
|
|
10062
10270
|
function loadCostData() {
|
|
10063
|
-
if (!
|
|
10271
|
+
if (!existsSync38(COST_DATA_FILE)) {
|
|
10064
10272
|
return {
|
|
10065
10273
|
perAgent: /* @__PURE__ */ new Map(),
|
|
10066
10274
|
perIssue: /* @__PURE__ */ new Map(),
|
|
@@ -10069,7 +10277,7 @@ function loadCostData() {
|
|
|
10069
10277
|
};
|
|
10070
10278
|
}
|
|
10071
10279
|
try {
|
|
10072
|
-
const fileContent =
|
|
10280
|
+
const fileContent = readFileSync33(COST_DATA_FILE, "utf-8");
|
|
10073
10281
|
const persisted = JSON.parse(fileContent);
|
|
10074
10282
|
return {
|
|
10075
10283
|
perAgent: new Map(Object.entries(persisted.perAgent || {})),
|
|
@@ -10090,7 +10298,7 @@ function loadCostData() {
|
|
|
10090
10298
|
function saveCostData(data) {
|
|
10091
10299
|
try {
|
|
10092
10300
|
const dir = dirname11(COST_DATA_FILE);
|
|
10093
|
-
if (!
|
|
10301
|
+
if (!existsSync38(dir)) {
|
|
10094
10302
|
mkdirSync18(dir, { recursive: true });
|
|
10095
10303
|
}
|
|
10096
10304
|
const persisted = {
|
|
@@ -10101,7 +10309,7 @@ function saveCostData(data) {
|
|
|
10101
10309
|
};
|
|
10102
10310
|
const tempFile = `${COST_DATA_FILE}.tmp`;
|
|
10103
10311
|
writeFileSync17(tempFile, JSON.stringify(persisted, null, 2));
|
|
10104
|
-
writeFileSync17(COST_DATA_FILE,
|
|
10312
|
+
writeFileSync17(COST_DATA_FILE, readFileSync33(tempFile));
|
|
10105
10313
|
try {
|
|
10106
10314
|
unlinkSync4(tempFile);
|
|
10107
10315
|
} catch (unlinkError) {
|
|
@@ -10222,12 +10430,12 @@ function getCostSummary() {
|
|
|
10222
10430
|
init_esm_shims();
|
|
10223
10431
|
init_paths();
|
|
10224
10432
|
import { writeFileSync as writeFileSync18 } from "fs";
|
|
10225
|
-
import { join as
|
|
10226
|
-
import { exec as
|
|
10227
|
-
import { promisify as
|
|
10433
|
+
import { join as join38 } from "path";
|
|
10434
|
+
import { exec as exec12 } from "child_process";
|
|
10435
|
+
import { promisify as promisify12 } from "util";
|
|
10228
10436
|
init_agents();
|
|
10229
10437
|
init_specialists();
|
|
10230
|
-
var
|
|
10438
|
+
var execAsync12 = promisify12(exec12);
|
|
10231
10439
|
var SESSION_ROTATION_THRESHOLD = 1e5;
|
|
10232
10440
|
var DEFAULT_MEMORY_TIERS = {
|
|
10233
10441
|
recent_summary: 100,
|
|
@@ -10249,7 +10457,7 @@ async function buildMergeAgentMemory(workingDir, tiers = DEFAULT_MEMORY_TIERS) {
|
|
|
10249
10457
|
const merges = [];
|
|
10250
10458
|
try {
|
|
10251
10459
|
const totalMerges = Math.max(tiers.recent_summary, tiers.recent_detailed, tiers.recent_full);
|
|
10252
|
-
const { stdout: gitLog } = await
|
|
10460
|
+
const { stdout: gitLog } = await execAsync12(
|
|
10253
10461
|
`git log --merges --format="%H|%s|%an|%ad|%D" -n ${totalMerges}`,
|
|
10254
10462
|
{ cwd: workingDir, encoding: "utf-8" }
|
|
10255
10463
|
);
|
|
@@ -10284,14 +10492,14 @@ async function buildMergeAgentMemory(workingDir, tiers = DEFAULT_MEMORY_TIERS) {
|
|
|
10284
10492
|
if (merge.branch) memory += `- Branch: ${merge.branch}
|
|
10285
10493
|
`;
|
|
10286
10494
|
try {
|
|
10287
|
-
const { stdout: filesOutput } = await
|
|
10495
|
+
const { stdout: filesOutput } = await execAsync12(`git show --name-only --format= ${merge.hash}`, {
|
|
10288
10496
|
cwd: workingDir,
|
|
10289
10497
|
encoding: "utf-8"
|
|
10290
10498
|
});
|
|
10291
10499
|
const files = filesOutput.trim().split("\n").filter((f) => f);
|
|
10292
10500
|
memory += `- Files changed: ${files.length}
|
|
10293
10501
|
`;
|
|
10294
|
-
const { stdout: diff } = await
|
|
10502
|
+
const { stdout: diff } = await execAsync12(`git show ${merge.hash} --stat`, {
|
|
10295
10503
|
cwd: workingDir,
|
|
10296
10504
|
encoding: "utf-8",
|
|
10297
10505
|
maxBuffer: 10 * 1024 * 1024
|
|
@@ -10362,13 +10570,13 @@ async function rotateSpecialistSession(specialistName, workingDir) {
|
|
|
10362
10570
|
let memoryFile;
|
|
10363
10571
|
if (specialistName === "merge-agent" && workingDir) {
|
|
10364
10572
|
memoryContent = await buildMergeAgentMemory(workingDir);
|
|
10365
|
-
memoryFile =
|
|
10573
|
+
memoryFile = join38(PANOPTICON_HOME, `merge-agent-memory-${Date.now()}.md`);
|
|
10366
10574
|
writeFileSync18(memoryFile, memoryContent);
|
|
10367
10575
|
console.log(`Built memory file: ${memoryFile}`);
|
|
10368
10576
|
}
|
|
10369
10577
|
const tmuxSession = getTmuxSessionName(specialistName);
|
|
10370
10578
|
try {
|
|
10371
|
-
await
|
|
10579
|
+
await execAsync12(`tmux kill-session -t "${tmuxSession}"`);
|
|
10372
10580
|
console.log(`Killed session: ${tmuxSession}`);
|
|
10373
10581
|
} catch (error) {
|
|
10374
10582
|
console.log(`Session ${tmuxSession} not found or already killed`);
|
|
@@ -10418,14 +10626,14 @@ init_config2();
|
|
|
10418
10626
|
init_specialists();
|
|
10419
10627
|
init_agents();
|
|
10420
10628
|
init_tmux();
|
|
10421
|
-
import { readFileSync as
|
|
10422
|
-
import { join as
|
|
10423
|
-
import { exec as
|
|
10424
|
-
import { promisify as
|
|
10629
|
+
import { readFileSync as readFileSync35, writeFileSync as writeFileSync19, existsSync as existsSync40, mkdirSync as mkdirSync19, readdirSync as readdirSync17, statSync as statSync8, rmSync as rmSync4 } from "fs";
|
|
10630
|
+
import { join as join39 } from "path";
|
|
10631
|
+
import { exec as exec13, execFile } from "child_process";
|
|
10632
|
+
import { promisify as promisify13 } from "util";
|
|
10425
10633
|
import { homedir as homedir18 } from "os";
|
|
10426
|
-
var
|
|
10427
|
-
var execFileAsync =
|
|
10428
|
-
var REVIEW_STATUS_FILE =
|
|
10634
|
+
var execAsync13 = promisify13(exec13);
|
|
10635
|
+
var execFileAsync = promisify13(execFile);
|
|
10636
|
+
var REVIEW_STATUS_FILE = join39(homedir18(), ".panopticon", "review-status.json");
|
|
10429
10637
|
var DEFAULT_CONFIG = {
|
|
10430
10638
|
pingTimeoutMs: 3e4,
|
|
10431
10639
|
// How long to wait for response
|
|
@@ -10440,15 +10648,15 @@ var DEFAULT_CONFIG = {
|
|
|
10440
10648
|
massDeathWindowMs: 6e4
|
|
10441
10649
|
// 1 minute window for mass death detection
|
|
10442
10650
|
};
|
|
10443
|
-
var DEACON_DIR =
|
|
10444
|
-
var STATE_FILE =
|
|
10445
|
-
var CONFIG_FILE2 =
|
|
10651
|
+
var DEACON_DIR = join39(PANOPTICON_HOME, "deacon");
|
|
10652
|
+
var STATE_FILE = join39(DEACON_DIR, "health-state.json");
|
|
10653
|
+
var CONFIG_FILE2 = join39(DEACON_DIR, "config.json");
|
|
10446
10654
|
var deaconInterval = null;
|
|
10447
10655
|
var config = { ...DEFAULT_CONFIG };
|
|
10448
10656
|
function loadConfig3() {
|
|
10449
10657
|
try {
|
|
10450
|
-
if (
|
|
10451
|
-
const content =
|
|
10658
|
+
if (existsSync40(CONFIG_FILE2)) {
|
|
10659
|
+
const content = readFileSync35(CONFIG_FILE2, "utf-8");
|
|
10452
10660
|
const loaded = JSON.parse(content);
|
|
10453
10661
|
config = { ...DEFAULT_CONFIG, ...loaded };
|
|
10454
10662
|
}
|
|
@@ -10458,15 +10666,15 @@ function loadConfig3() {
|
|
|
10458
10666
|
return config;
|
|
10459
10667
|
}
|
|
10460
10668
|
function ensureDeaconDir() {
|
|
10461
|
-
if (!
|
|
10669
|
+
if (!existsSync40(DEACON_DIR)) {
|
|
10462
10670
|
mkdirSync19(DEACON_DIR, { recursive: true });
|
|
10463
10671
|
}
|
|
10464
10672
|
}
|
|
10465
10673
|
function loadState() {
|
|
10466
10674
|
ensureDeaconDir();
|
|
10467
10675
|
try {
|
|
10468
|
-
if (
|
|
10469
|
-
const content =
|
|
10676
|
+
if (existsSync40(STATE_FILE)) {
|
|
10677
|
+
const content = readFileSync35(STATE_FILE, "utf-8");
|
|
10470
10678
|
return JSON.parse(content);
|
|
10471
10679
|
}
|
|
10472
10680
|
} catch (error) {
|
|
@@ -10561,7 +10769,7 @@ async function checkAndSuspendIdleAgents() {
|
|
|
10561
10769
|
try {
|
|
10562
10770
|
const sessionId = runtimeState.sessionId || `session-${agent.id}`;
|
|
10563
10771
|
saveSessionId(agent.id, sessionId);
|
|
10564
|
-
await
|
|
10772
|
+
await execAsync13(`tmux kill-session -t "${agent.id}" 2>/dev/null || true`);
|
|
10565
10773
|
saveAgentRuntimeState(agent.id, {
|
|
10566
10774
|
state: "suspended",
|
|
10567
10775
|
suspendedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -10604,7 +10812,7 @@ async function checkLazyAgent(sessionName) {
|
|
|
10604
10812
|
if (Date.now() - lastSent < LAZY_COOLDOWN_MS) {
|
|
10605
10813
|
return { isLazy: false };
|
|
10606
10814
|
}
|
|
10607
|
-
const { stdout } = await
|
|
10815
|
+
const { stdout } = await execAsync13(
|
|
10608
10816
|
`tmux capture-pane -t "${sessionName}" -p -S -20 2>/dev/null || echo ""`,
|
|
10609
10817
|
{ encoding: "utf-8" }
|
|
10610
10818
|
);
|
|
@@ -10639,11 +10847,11 @@ async function checkLazyAgent(sessionName) {
|
|
|
10639
10847
|
}
|
|
10640
10848
|
async function sendAntiLazyMessage(sessionName) {
|
|
10641
10849
|
try {
|
|
10642
|
-
await
|
|
10850
|
+
await execAsync13(
|
|
10643
10851
|
`tmux send-keys -t "${sessionName}" "${ANTI_LAZY_MESSAGE.replace(/"/g, '\\"')}"`,
|
|
10644
10852
|
{ encoding: "utf-8" }
|
|
10645
10853
|
);
|
|
10646
|
-
await
|
|
10854
|
+
await execAsync13(`tmux send-keys -t "${sessionName}" Enter`, { encoding: "utf-8" });
|
|
10647
10855
|
lazyMessageCooldowns.set(sessionName, Date.now());
|
|
10648
10856
|
console.log(`[deacon] Sent anti-lazy message to ${sessionName}`);
|
|
10649
10857
|
return true;
|
|
@@ -10658,10 +10866,10 @@ function isIssueCompletedOrInReview(agentId) {
|
|
|
10658
10866
|
const match = agentId.match(/agent-([a-z]+-\d+)/i);
|
|
10659
10867
|
if (!match) return false;
|
|
10660
10868
|
const issueId = match[1].toUpperCase();
|
|
10661
|
-
if (!
|
|
10869
|
+
if (!existsSync40(REVIEW_STATUS_FILE)) {
|
|
10662
10870
|
return false;
|
|
10663
10871
|
}
|
|
10664
|
-
const content =
|
|
10872
|
+
const content = readFileSync35(REVIEW_STATUS_FILE, "utf-8");
|
|
10665
10873
|
const statuses = JSON.parse(content);
|
|
10666
10874
|
const status = statuses[issueId];
|
|
10667
10875
|
if (!status) {
|
|
@@ -10713,7 +10921,7 @@ var ACTIVE_STATUS_PATTERNS = [
|
|
|
10713
10921
|
];
|
|
10714
10922
|
async function isAgentActiveInTmux(sessionName) {
|
|
10715
10923
|
try {
|
|
10716
|
-
const { stdout } = await
|
|
10924
|
+
const { stdout } = await execAsync13(
|
|
10717
10925
|
`tmux capture-pane -t "${sessionName}" -p -S -5 2>/dev/null || echo ""`,
|
|
10718
10926
|
{ encoding: "utf-8" }
|
|
10719
10927
|
);
|
|
@@ -10759,7 +10967,7 @@ async function checkStuckWorkAgents() {
|
|
|
10759
10967
|
}
|
|
10760
10968
|
let tmuxOutput;
|
|
10761
10969
|
try {
|
|
10762
|
-
const { stdout } = await
|
|
10970
|
+
const { stdout } = await execAsync13(
|
|
10763
10971
|
`tmux capture-pane -t "${agent.id}" -p -S -10 2>/dev/null || echo ""`,
|
|
10764
10972
|
{ encoding: "utf-8" }
|
|
10765
10973
|
);
|
|
@@ -10780,23 +10988,23 @@ async function checkStuckWorkAgents() {
|
|
|
10780
10988
|
console.log(`[deacon] Work agent ${agent.id} stuck thinking for ${thinkingMinutes}m (attempt ${attempts + 1})`);
|
|
10781
10989
|
try {
|
|
10782
10990
|
if (attempts === 0) {
|
|
10783
|
-
await
|
|
10991
|
+
await execAsync13(`tmux send-keys -t "${agent.id}" Escape 2>/dev/null || true`);
|
|
10784
10992
|
actions.push(`Stuck recovery: sent Escape to ${agent.id} (thinking ${thinkingMinutes}m)`);
|
|
10785
10993
|
} else if (attempts === 1) {
|
|
10786
|
-
await
|
|
10994
|
+
await execAsync13(`tmux send-keys -t "${agent.id}" C-c 2>/dev/null || true`);
|
|
10787
10995
|
actions.push(`Stuck recovery: sent Ctrl+C to ${agent.id} (thinking ${thinkingMinutes}m)`);
|
|
10788
10996
|
} else {
|
|
10789
|
-
const launcherPath =
|
|
10997
|
+
const launcherPath = join39(AGENTS_DIR, agent.id, "launcher.sh");
|
|
10790
10998
|
const agentState = getAgentState(agent.id);
|
|
10791
10999
|
const workspace = agentState?.workspace;
|
|
10792
|
-
if (!
|
|
11000
|
+
if (!existsSync40(launcherPath) || !workspace) {
|
|
10793
11001
|
console.error(`[deacon] Cannot respawn ${agent.id}: missing launcher.sh or workspace`);
|
|
10794
11002
|
actions.push(`Stuck recovery failed for ${agent.id}: missing launcher or workspace`);
|
|
10795
11003
|
continue;
|
|
10796
11004
|
}
|
|
10797
|
-
await
|
|
11005
|
+
await execAsync13(`tmux kill-session -t "${agent.id}" 2>/dev/null || true`);
|
|
10798
11006
|
await new Promise((r) => setTimeout(r, 1e3));
|
|
10799
|
-
await
|
|
11007
|
+
await execAsync13(
|
|
10800
11008
|
`tmux new-session -d -s "${agent.id}" -c "${workspace}" "bash ${launcherPath}"`,
|
|
10801
11009
|
{ encoding: "utf-8" }
|
|
10802
11010
|
);
|
|
@@ -10823,22 +11031,22 @@ async function cleanupStaleAgentState() {
|
|
|
10823
11031
|
const retentionDays = cloisterConfig.retention?.agent_state_days ?? 30;
|
|
10824
11032
|
const retentionMs = retentionDays * 24 * 60 * 60 * 1e3;
|
|
10825
11033
|
const now = Date.now();
|
|
10826
|
-
if (!
|
|
11034
|
+
if (!existsSync40(AGENTS_DIR)) {
|
|
10827
11035
|
return actions;
|
|
10828
11036
|
}
|
|
10829
11037
|
try {
|
|
10830
11038
|
const dirs = readdirSync17(AGENTS_DIR, { withFileTypes: true }).filter((d) => d.isDirectory());
|
|
10831
11039
|
for (const dir of dirs) {
|
|
10832
|
-
const agentDir =
|
|
11040
|
+
const agentDir = join39(AGENTS_DIR, dir.name);
|
|
10833
11041
|
try {
|
|
10834
11042
|
try {
|
|
10835
|
-
await
|
|
11043
|
+
await execAsync13(`tmux has-session -t "${dir.name}" 2>/dev/null`);
|
|
10836
11044
|
continue;
|
|
10837
11045
|
} catch {
|
|
10838
11046
|
}
|
|
10839
|
-
const stateFile =
|
|
11047
|
+
const stateFile = join39(agentDir, "state.json");
|
|
10840
11048
|
let mtime;
|
|
10841
|
-
if (
|
|
11049
|
+
if (existsSync40(stateFile)) {
|
|
10842
11050
|
mtime = statSync8(stateFile).mtimeMs;
|
|
10843
11051
|
} else {
|
|
10844
11052
|
mtime = statSync8(agentDir).mtimeMs;
|
|
@@ -10847,8 +11055,8 @@ async function cleanupStaleAgentState() {
|
|
|
10847
11055
|
if (ageMs < retentionMs) {
|
|
10848
11056
|
continue;
|
|
10849
11057
|
}
|
|
10850
|
-
const completedFile =
|
|
10851
|
-
if (
|
|
11058
|
+
const completedFile = join39(agentDir, "completed");
|
|
11059
|
+
if (existsSync40(completedFile)) {
|
|
10852
11060
|
const completedAge = now - statSync8(completedFile).mtimeMs;
|
|
10853
11061
|
if (completedAge < 7 * 24 * 60 * 60 * 1e3) {
|
|
10854
11062
|
continue;
|
|
@@ -10875,10 +11083,10 @@ async function cleanupStaleAgentState() {
|
|
|
10875
11083
|
async function checkOrphanedReviewStatuses() {
|
|
10876
11084
|
const actions = [];
|
|
10877
11085
|
try {
|
|
10878
|
-
if (!
|
|
11086
|
+
if (!existsSync40(REVIEW_STATUS_FILE)) {
|
|
10879
11087
|
return actions;
|
|
10880
11088
|
}
|
|
10881
|
-
const content =
|
|
11089
|
+
const content = readFileSync35(REVIEW_STATUS_FILE, "utf-8");
|
|
10882
11090
|
const statuses = JSON.parse(content);
|
|
10883
11091
|
const activeReviewSessions = /* @__PURE__ */ new Set();
|
|
10884
11092
|
const activeTestSessions = /* @__PURE__ */ new Set();
|
|
@@ -10945,7 +11153,7 @@ async function checkOrphanedReviewStatuses() {
|
|
|
10945
11153
|
const { resolveProjectFromIssue: resolveProjectFromIssue2 } = await import("../projects-BPGM6IFB.js");
|
|
10946
11154
|
const resolved = resolveProjectFromIssue2(issueId);
|
|
10947
11155
|
if (resolved) {
|
|
10948
|
-
const { spawnEphemeralSpecialist: spawnEphemeralSpecialist2 } = await import("../specialists-
|
|
11156
|
+
const { spawnEphemeralSpecialist: spawnEphemeralSpecialist2 } = await import("../specialists-X4OGA7WX.js");
|
|
10949
11157
|
const result = await spawnEphemeralSpecialist2(resolved.projectKey, "test-agent", {
|
|
10950
11158
|
issueId,
|
|
10951
11159
|
workspace,
|
|
@@ -11007,10 +11215,10 @@ var mergeReadyNotifier = null;
|
|
|
11007
11215
|
async function checkReadyForMergeStuck() {
|
|
11008
11216
|
const actions = [];
|
|
11009
11217
|
try {
|
|
11010
|
-
if (!
|
|
11218
|
+
if (!existsSync40(REVIEW_STATUS_FILE)) {
|
|
11011
11219
|
return actions;
|
|
11012
11220
|
}
|
|
11013
|
-
const content =
|
|
11221
|
+
const content = readFileSync35(REVIEW_STATUS_FILE, "utf-8");
|
|
11014
11222
|
const statuses = JSON.parse(content);
|
|
11015
11223
|
const now = Date.now();
|
|
11016
11224
|
const state = loadState();
|
|
@@ -11060,10 +11268,10 @@ var DEAD_END_COOLDOWN_MS = 10 * 60 * 1e3;
|
|
|
11060
11268
|
async function checkDeadEndAgents() {
|
|
11061
11269
|
const actions = [];
|
|
11062
11270
|
try {
|
|
11063
|
-
if (!
|
|
11271
|
+
if (!existsSync40(REVIEW_STATUS_FILE)) {
|
|
11064
11272
|
return actions;
|
|
11065
11273
|
}
|
|
11066
|
-
const content =
|
|
11274
|
+
const content = readFileSync35(REVIEW_STATUS_FILE, "utf-8");
|
|
11067
11275
|
const statuses = JSON.parse(content);
|
|
11068
11276
|
const now = Date.now();
|
|
11069
11277
|
for (const [key, status] of Object.entries(statuses)) {
|
|
@@ -11123,15 +11331,15 @@ async function checkFirstCompletionAgents() {
|
|
|
11123
11331
|
const agentId = agent.id;
|
|
11124
11332
|
if (!agentId || !agentId.startsWith("agent-") || !agent.tmuxActive) continue;
|
|
11125
11333
|
if (agentId.startsWith("specialist-")) continue;
|
|
11126
|
-
const completedFile =
|
|
11127
|
-
if (
|
|
11334
|
+
const completedFile = join39(AGENTS_DIR, agent.id, "completed");
|
|
11335
|
+
if (existsSync40(completedFile)) continue;
|
|
11128
11336
|
const runtimeState = getAgentRuntimeState(agent.id);
|
|
11129
11337
|
if (!runtimeState || runtimeState.state !== "idle") continue;
|
|
11130
11338
|
const lastActivity = new Date(runtimeState.lastActivity);
|
|
11131
11339
|
const idleMs = now - lastActivity.getTime();
|
|
11132
11340
|
if (idleMs < FIRST_COMPLETION_IDLE_MS) continue;
|
|
11133
11341
|
try {
|
|
11134
|
-
const { stdout: lastLines } = await
|
|
11342
|
+
const { stdout: lastLines } = await execAsync13(
|
|
11135
11343
|
`tmux capture-pane -t "${agent.id}" -p -S -3 2>/dev/null || echo ""`,
|
|
11136
11344
|
{ encoding: "utf-8" }
|
|
11137
11345
|
);
|
|
@@ -11146,9 +11354,9 @@ async function checkFirstCompletionAgents() {
|
|
|
11146
11354
|
if (lastNudge && now - lastNudge < FIRST_COMPLETION_COOLDOWN_MS) continue;
|
|
11147
11355
|
const issueId = agent.issueId || agent.id.replace("agent-", "").toUpperCase();
|
|
11148
11356
|
const issueKey = issueId.toLowerCase();
|
|
11149
|
-
if (
|
|
11357
|
+
if (existsSync40(REVIEW_STATUS_FILE)) {
|
|
11150
11358
|
try {
|
|
11151
|
-
const statuses = JSON.parse(
|
|
11359
|
+
const statuses = JSON.parse(readFileSync35(REVIEW_STATUS_FILE, "utf-8"));
|
|
11152
11360
|
const hasStatus = statuses[issueKey] || statuses[issueId] || statuses[issueId.toUpperCase()];
|
|
11153
11361
|
if (hasStatus) {
|
|
11154
11362
|
console.log(`[deacon] First-completion gate: skipping ${agent.id} \u2014 has review status entry (readyForMerge=${hasStatus.readyForMerge ?? false})`);
|
|
@@ -11159,8 +11367,8 @@ async function checkFirstCompletionAgents() {
|
|
|
11159
11367
|
}
|
|
11160
11368
|
const agentStateForGate = getAgentState(agent.id);
|
|
11161
11369
|
if (agentStateForGate?.workspace) {
|
|
11162
|
-
const feedbackDir =
|
|
11163
|
-
if (
|
|
11370
|
+
const feedbackDir = join39(agentStateForGate.workspace, ".planning", "feedback");
|
|
11371
|
+
if (existsSync40(feedbackDir)) {
|
|
11164
11372
|
try {
|
|
11165
11373
|
const feedbackFiles = readdirSync17(feedbackDir);
|
|
11166
11374
|
if (feedbackFiles.length > 0) {
|
|
@@ -11172,10 +11380,10 @@ async function checkFirstCompletionAgents() {
|
|
|
11172
11380
|
}
|
|
11173
11381
|
}
|
|
11174
11382
|
const agentState = getAgentState(agent.id);
|
|
11175
|
-
if (!agentState?.workspace || !
|
|
11383
|
+
if (!agentState?.workspace || !existsSync40(agentState.workspace)) continue;
|
|
11176
11384
|
let hasCommits = false;
|
|
11177
11385
|
try {
|
|
11178
|
-
const { stdout: gitLog } = await
|
|
11386
|
+
const { stdout: gitLog } = await execAsync13(
|
|
11179
11387
|
"git log --oneline -3 2>/dev/null",
|
|
11180
11388
|
{ cwd: agentState.workspace }
|
|
11181
11389
|
);
|
|
@@ -11185,9 +11393,9 @@ async function checkFirstCompletionAgents() {
|
|
|
11185
11393
|
const subdirs = readdirSync17(agentState.workspace, { withFileTypes: true }).filter((d) => d.isDirectory() && !d.name.startsWith("."));
|
|
11186
11394
|
for (const sub of subdirs) {
|
|
11187
11395
|
try {
|
|
11188
|
-
const { stdout: subLog } = await
|
|
11396
|
+
const { stdout: subLog } = await execAsync13(
|
|
11189
11397
|
"git log --oneline -3 2>/dev/null",
|
|
11190
|
-
{ cwd:
|
|
11398
|
+
{ cwd: join39(agentState.workspace, sub.name) }
|
|
11191
11399
|
);
|
|
11192
11400
|
if (subLog.trim().length > 0) {
|
|
11193
11401
|
hasCommits = true;
|
|
@@ -11237,8 +11445,8 @@ async function patrolWorkAgentResolutions() {
|
|
|
11237
11445
|
if (resolution === "done" && count >= 2) {
|
|
11238
11446
|
console.log(`[deacon] Auto-completing ${agent.id} (${issueId}): resolution=done, count=${count}`);
|
|
11239
11447
|
try {
|
|
11240
|
-
const panBin =
|
|
11241
|
-
const binExists =
|
|
11448
|
+
const panBin = join39(PANOPTICON_HOME, "bin", "pan");
|
|
11449
|
+
const binExists = existsSync40(panBin);
|
|
11242
11450
|
const bin = binExists ? panBin : "pan";
|
|
11243
11451
|
await execFileAsync(bin, ["work", "done", issueId, "-c", "Auto-completed by Deacon: evidence showed work complete after 2 nudges"], {
|
|
11244
11452
|
timeout: 3e4
|
|
@@ -11281,8 +11489,8 @@ async function checkSpecialistQueues() {
|
|
|
11281
11489
|
spawnEphemeralSpecialist: spawnEphemeralSpecialist2,
|
|
11282
11490
|
getTmuxSessionName: getTmuxSessionName2,
|
|
11283
11491
|
isRunning: isRunning3
|
|
11284
|
-
} = await import("../specialists-
|
|
11285
|
-
const { getAgentRuntimeState: getAgentRuntimeState2 } = await import("../agents-
|
|
11492
|
+
} = await import("../specialists-X4OGA7WX.js");
|
|
11493
|
+
const { getAgentRuntimeState: getAgentRuntimeState2 } = await import("../agents-RL2KUSP3.js");
|
|
11286
11494
|
const { resolveProjectFromIssue: resolveProjectFromIssue2 } = await import("../projects-BPGM6IFB.js");
|
|
11287
11495
|
const specialistTypes = ["review-agent", "test-agent", "inspect-agent", "uat-agent"];
|
|
11288
11496
|
for (const specialistType of specialistTypes) {
|
|
@@ -11304,10 +11512,10 @@ async function checkSpecialistQueues() {
|
|
|
11304
11512
|
if (lastActivityAge > tenMinutes) {
|
|
11305
11513
|
console.log(`[deacon] Stale specialist detected: ${tmuxSession} (last activity: ${Math.round(lastActivityAge / 6e4)}m ago) \u2014 killing and treating as idle`);
|
|
11306
11514
|
try {
|
|
11307
|
-
const { exec:
|
|
11308
|
-
const { promisify:
|
|
11309
|
-
const
|
|
11310
|
-
await
|
|
11515
|
+
const { exec: exec24 } = await import("child_process");
|
|
11516
|
+
const { promisify: promisify24 } = await import("util");
|
|
11517
|
+
const execAsync24 = promisify24(exec24);
|
|
11518
|
+
await execAsync24(`tmux kill-session -t "${tmuxSession}"`, { encoding: "utf-8" }).catch(() => {
|
|
11311
11519
|
});
|
|
11312
11520
|
} catch {
|
|
11313
11521
|
}
|
|
@@ -11317,7 +11525,7 @@ async function checkSpecialistQueues() {
|
|
|
11317
11525
|
if (!isIdle) continue;
|
|
11318
11526
|
console.log(`[deacon] Dispatching queued ${specialistType} work for ${issueId} (project: ${resolved.projectKey})`);
|
|
11319
11527
|
try {
|
|
11320
|
-
const { findWorkspacePath: findWorkspacePath3 } = await import("../archive-planning-
|
|
11528
|
+
const { findWorkspacePath: findWorkspacePath3 } = await import("../archive-planning-54J6EP6A.js");
|
|
11321
11529
|
const workspacePath = findWorkspacePath3(resolved.projectPath, issueId.toLowerCase());
|
|
11322
11530
|
const queuePayload = item.payload;
|
|
11323
11531
|
await spawnEphemeralSpecialist2(resolved.projectKey, specialistType, {
|
|
@@ -11395,15 +11603,15 @@ async function runPatrol() {
|
|
|
11395
11603
|
if (projSpec.specialistType === "merge-agent" && runtimeState2.currentIssue) {
|
|
11396
11604
|
const issueId = runtimeState2.currentIssue;
|
|
11397
11605
|
try {
|
|
11398
|
-
if (!
|
|
11399
|
-
const statuses = JSON.parse(
|
|
11606
|
+
if (!existsSync40(REVIEW_STATUS_FILE)) continue;
|
|
11607
|
+
const statuses = JSON.parse(readFileSync35(REVIEW_STATUS_FILE, "utf-8"));
|
|
11400
11608
|
const rs = statuses[issueId];
|
|
11401
11609
|
if (rs?.mergeStatus === "merging") {
|
|
11402
11610
|
const { resolveProjectFromIssue: resolveProjectFromIssue2 } = await import("../projects-BPGM6IFB.js");
|
|
11403
11611
|
const resolved = resolveProjectFromIssue2(issueId);
|
|
11404
11612
|
if (resolved) {
|
|
11405
11613
|
const branch = `feature/${issueId.toLowerCase()}`;
|
|
11406
|
-
const { stdout } = await
|
|
11614
|
+
const { stdout } = await execAsync13(
|
|
11407
11615
|
`git -C "${resolved.projectPath}" log --oneline origin/main --grep="Merge branch '${branch}'" 2>/dev/null | head -1`,
|
|
11408
11616
|
{ encoding: "utf-8" }
|
|
11409
11617
|
);
|
|
@@ -11412,7 +11620,7 @@ async function runPatrol() {
|
|
|
11412
11620
|
statuses[issueId].mergeStatus = "merged";
|
|
11413
11621
|
statuses[issueId].readyForMerge = false;
|
|
11414
11622
|
writeFileSync19(REVIEW_STATUS_FILE, JSON.stringify(statuses, null, 2), "utf-8");
|
|
11415
|
-
const { postMergeLifecycle } = await import("../merge-agent-
|
|
11623
|
+
const { postMergeLifecycle } = await import("../merge-agent-7L7MWJEC.js");
|
|
11416
11624
|
postMergeLifecycle(issueId, resolved.projectPath).catch(
|
|
11417
11625
|
(err) => console.warn(`[deacon] postMergeLifecycle failed for ${issueId}: ${err}`)
|
|
11418
11626
|
);
|
|
@@ -11437,7 +11645,7 @@ async function runPatrol() {
|
|
|
11437
11645
|
addLog("warn", `Per-project ${projSpec.specialistType} (${projSpec.projectKey}) stuck, force-killing`, state.patrolCycle);
|
|
11438
11646
|
console.log(`[deacon] Per-project ${projSpec.specialistType} (${projSpec.projectKey}) stuck, force-killing ${projSpec.tmuxSession}`);
|
|
11439
11647
|
try {
|
|
11440
|
-
await
|
|
11648
|
+
await execAsync13(`tmux kill-session -t "${projSpec.tmuxSession}"`);
|
|
11441
11649
|
saveAgentRuntimeState(projSpec.tmuxSession, { state: "idle", lastActivity: (/* @__PURE__ */ new Date()).toISOString() });
|
|
11442
11650
|
actions.push(`Force-killed stuck per-project ${projSpec.specialistType} (${projSpec.projectKey})`);
|
|
11443
11651
|
} catch {
|
|
@@ -11512,9 +11720,9 @@ function getDeaconStatus() {
|
|
|
11512
11720
|
// src/lib/cloister/service.ts
|
|
11513
11721
|
init_paths();
|
|
11514
11722
|
init_paths();
|
|
11515
|
-
import { existsSync as
|
|
11516
|
-
import { join as
|
|
11517
|
-
var CLOISTER_STATE_FILE =
|
|
11723
|
+
import { existsSync as existsSync41, writeFileSync as writeFileSync20, unlinkSync as unlinkSync5, readFileSync as readFileSync36, readdirSync as readdirSync18, renameSync as renameSync3 } from "fs";
|
|
11724
|
+
import { join as join40 } from "path";
|
|
11725
|
+
var CLOISTER_STATE_FILE = join40(PANOPTICON_HOME, "cloister.state");
|
|
11518
11726
|
function writeStateFile(running, pid) {
|
|
11519
11727
|
try {
|
|
11520
11728
|
if (running) {
|
|
@@ -11524,7 +11732,7 @@ function writeStateFile(running, pid) {
|
|
|
11524
11732
|
startedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
11525
11733
|
}));
|
|
11526
11734
|
} else {
|
|
11527
|
-
if (
|
|
11735
|
+
if (existsSync41(CLOISTER_STATE_FILE)) {
|
|
11528
11736
|
unlinkSync5(CLOISTER_STATE_FILE);
|
|
11529
11737
|
}
|
|
11530
11738
|
}
|
|
@@ -11534,8 +11742,8 @@ function writeStateFile(running, pid) {
|
|
|
11534
11742
|
}
|
|
11535
11743
|
function readStateFile() {
|
|
11536
11744
|
try {
|
|
11537
|
-
if (
|
|
11538
|
-
const data = JSON.parse(
|
|
11745
|
+
if (existsSync41(CLOISTER_STATE_FILE)) {
|
|
11746
|
+
const data = JSON.parse(readFileSync36(CLOISTER_STATE_FILE, "utf-8"));
|
|
11539
11747
|
if (data.pid) {
|
|
11540
11748
|
try {
|
|
11541
11749
|
process.kill(data.pid, 0);
|
|
@@ -11741,14 +11949,14 @@ var CloisterService = class {
|
|
|
11741
11949
|
*/
|
|
11742
11950
|
async checkCompletionMarkers() {
|
|
11743
11951
|
try {
|
|
11744
|
-
if (!
|
|
11952
|
+
if (!existsSync41(AGENTS_DIR)) return;
|
|
11745
11953
|
const agentDirs = readdirSync18(AGENTS_DIR, { withFileTypes: true }).filter((d) => d.isDirectory() && d.name.startsWith("agent-"));
|
|
11746
11954
|
for (const dir of agentDirs) {
|
|
11747
|
-
const completedFile =
|
|
11748
|
-
const processedFile =
|
|
11749
|
-
if (!
|
|
11955
|
+
const completedFile = join40(AGENTS_DIR, dir.name, "completed");
|
|
11956
|
+
const processedFile = join40(AGENTS_DIR, dir.name, "completed.processed");
|
|
11957
|
+
if (!existsSync41(completedFile) || existsSync41(processedFile)) continue;
|
|
11750
11958
|
try {
|
|
11751
|
-
const content = JSON.parse(
|
|
11959
|
+
const content = JSON.parse(readFileSync36(completedFile, "utf-8"));
|
|
11752
11960
|
const ageMs = Date.now() - new Date(content.timestamp).getTime();
|
|
11753
11961
|
if (ageMs > 24 * 60 * 60 * 1e3) {
|
|
11754
11962
|
console.log(`\u{1F514} Cloister: Skipping stale completion marker for ${dir.name} (${Math.floor(ageMs / 36e5)}h old)`);
|
|
@@ -12425,8 +12633,8 @@ init_esm_shims();
|
|
|
12425
12633
|
// src/cli/commands/setup/hooks.ts
|
|
12426
12634
|
init_esm_shims();
|
|
12427
12635
|
import chalk39 from "chalk";
|
|
12428
|
-
import { readFileSync as
|
|
12429
|
-
import { join as
|
|
12636
|
+
import { readFileSync as readFileSync37, writeFileSync as writeFileSync21, existsSync as existsSync42, mkdirSync as mkdirSync20, copyFileSync as copyFileSync2, chmodSync } from "fs";
|
|
12637
|
+
import { join as join41 } from "path";
|
|
12430
12638
|
import { execSync as execSync5 } from "child_process";
|
|
12431
12639
|
import { homedir as homedir19 } from "os";
|
|
12432
12640
|
function checkJqInstalled() {
|
|
@@ -12504,14 +12712,14 @@ async function setupHooksCommand() {
|
|
|
12504
12712
|
} else {
|
|
12505
12713
|
console.log(chalk39.green("\u2713 jq is installed"));
|
|
12506
12714
|
}
|
|
12507
|
-
const panopticonHome =
|
|
12508
|
-
const binDir =
|
|
12509
|
-
const heartbeatsDir =
|
|
12510
|
-
if (!
|
|
12715
|
+
const panopticonHome = join41(homedir19(), ".panopticon");
|
|
12716
|
+
const binDir = join41(panopticonHome, "bin");
|
|
12717
|
+
const heartbeatsDir = join41(panopticonHome, "heartbeats");
|
|
12718
|
+
if (!existsSync42(binDir)) {
|
|
12511
12719
|
mkdirSync20(binDir, { recursive: true });
|
|
12512
12720
|
console.log(chalk39.green("\u2713 Created ~/.panopticon/bin/"));
|
|
12513
12721
|
}
|
|
12514
|
-
if (!
|
|
12722
|
+
if (!existsSync42(heartbeatsDir)) {
|
|
12515
12723
|
mkdirSync20(heartbeatsDir, { recursive: true });
|
|
12516
12724
|
console.log(chalk39.green("\u2713 Created ~/.panopticon/heartbeats/"));
|
|
12517
12725
|
}
|
|
@@ -12520,13 +12728,13 @@ async function setupHooksCommand() {
|
|
|
12520
12728
|
const { dirname: dirname17 } = await import("path");
|
|
12521
12729
|
const __dirname7 = dirname17(fileURLToPath7(import.meta.url));
|
|
12522
12730
|
for (const scriptName of hookScripts) {
|
|
12523
|
-
const devSource =
|
|
12524
|
-
const installedSource =
|
|
12525
|
-
const scriptDest =
|
|
12731
|
+
const devSource = join41(process.cwd(), "scripts", scriptName);
|
|
12732
|
+
const installedSource = join41(__dirname7, "..", "..", "..", "scripts", scriptName);
|
|
12733
|
+
const scriptDest = join41(binDir, scriptName);
|
|
12526
12734
|
let sourcePath = null;
|
|
12527
|
-
if (
|
|
12735
|
+
if (existsSync42(devSource)) {
|
|
12528
12736
|
sourcePath = devSource;
|
|
12529
|
-
} else if (
|
|
12737
|
+
} else if (existsSync42(installedSource)) {
|
|
12530
12738
|
sourcePath = installedSource;
|
|
12531
12739
|
}
|
|
12532
12740
|
if (!sourcePath) {
|
|
@@ -12539,12 +12747,12 @@ async function setupHooksCommand() {
|
|
|
12539
12747
|
chmodSync(scriptDest, 493);
|
|
12540
12748
|
}
|
|
12541
12749
|
console.log(chalk39.green("\u2713 Installed hook scripts (pre-tool, post-tool, stop, specialist-stop)"));
|
|
12542
|
-
const claudeDir =
|
|
12543
|
-
const settingsPath =
|
|
12750
|
+
const claudeDir = join41(homedir19(), ".claude");
|
|
12751
|
+
const settingsPath = join41(claudeDir, "settings.json");
|
|
12544
12752
|
let settings = {};
|
|
12545
|
-
if (
|
|
12753
|
+
if (existsSync42(settingsPath)) {
|
|
12546
12754
|
try {
|
|
12547
|
-
const settingsContent =
|
|
12755
|
+
const settingsContent = readFileSync37(settingsPath, "utf-8");
|
|
12548
12756
|
settings = JSON.parse(settingsContent);
|
|
12549
12757
|
console.log(chalk39.green("\u2713 Read existing Claude Code settings"));
|
|
12550
12758
|
} catch (error) {
|
|
@@ -12553,7 +12761,7 @@ async function setupHooksCommand() {
|
|
|
12553
12761
|
}
|
|
12554
12762
|
} else {
|
|
12555
12763
|
console.log(chalk39.dim("No existing settings.json found, creating new file"));
|
|
12556
|
-
if (!
|
|
12764
|
+
if (!existsSync42(claudeDir)) {
|
|
12557
12765
|
mkdirSync20(claudeDir, { recursive: true });
|
|
12558
12766
|
}
|
|
12559
12767
|
}
|
|
@@ -12567,11 +12775,11 @@ async function setupHooksCommand() {
|
|
|
12567
12775
|
console.log(chalk39.dim(" Install Python3 to enable token-efficient code analysis\n"));
|
|
12568
12776
|
}
|
|
12569
12777
|
if (python3Available) {
|
|
12570
|
-
const mcpPath =
|
|
12778
|
+
const mcpPath = join41(dirname17(settingsPath), "mcp.json");
|
|
12571
12779
|
let mcpConfig = {};
|
|
12572
12780
|
try {
|
|
12573
|
-
if (
|
|
12574
|
-
mcpConfig = JSON.parse(
|
|
12781
|
+
if (existsSync42(mcpPath)) {
|
|
12782
|
+
mcpConfig = JSON.parse(readFileSync37(mcpPath, "utf-8"));
|
|
12575
12783
|
}
|
|
12576
12784
|
} catch {
|
|
12577
12785
|
mcpConfig = {};
|
|
@@ -12606,7 +12814,7 @@ async function setupHooksCommand() {
|
|
|
12606
12814
|
hooks: [
|
|
12607
12815
|
{
|
|
12608
12816
|
type: "command",
|
|
12609
|
-
command:
|
|
12817
|
+
command: join41(binDir, "pre-tool-hook")
|
|
12610
12818
|
}
|
|
12611
12819
|
]
|
|
12612
12820
|
});
|
|
@@ -12616,7 +12824,7 @@ async function setupHooksCommand() {
|
|
|
12616
12824
|
hooks: [
|
|
12617
12825
|
{
|
|
12618
12826
|
type: "command",
|
|
12619
|
-
command:
|
|
12827
|
+
command: join41(binDir, "tldr-read-enforcer")
|
|
12620
12828
|
}
|
|
12621
12829
|
]
|
|
12622
12830
|
});
|
|
@@ -12629,7 +12837,7 @@ async function setupHooksCommand() {
|
|
|
12629
12837
|
hooks: [
|
|
12630
12838
|
{
|
|
12631
12839
|
type: "command",
|
|
12632
|
-
command:
|
|
12840
|
+
command: join41(binDir, "heartbeat-hook")
|
|
12633
12841
|
}
|
|
12634
12842
|
]
|
|
12635
12843
|
});
|
|
@@ -12639,7 +12847,7 @@ async function setupHooksCommand() {
|
|
|
12639
12847
|
hooks: [
|
|
12640
12848
|
{
|
|
12641
12849
|
type: "command",
|
|
12642
|
-
command:
|
|
12850
|
+
command: join41(binDir, "tldr-post-edit")
|
|
12643
12851
|
}
|
|
12644
12852
|
]
|
|
12645
12853
|
});
|
|
@@ -12652,7 +12860,7 @@ async function setupHooksCommand() {
|
|
|
12652
12860
|
hooks: [
|
|
12653
12861
|
{
|
|
12654
12862
|
type: "command",
|
|
12655
|
-
command:
|
|
12863
|
+
command: join41(binDir, "stop-hook")
|
|
12656
12864
|
}
|
|
12657
12865
|
]
|
|
12658
12866
|
});
|
|
@@ -12762,20 +12970,20 @@ init_specialists();
|
|
|
12762
12970
|
init_paths();
|
|
12763
12971
|
init_tmux();
|
|
12764
12972
|
import chalk41 from "chalk";
|
|
12765
|
-
import { exec as
|
|
12766
|
-
import { promisify as
|
|
12973
|
+
import { exec as exec14 } from "child_process";
|
|
12974
|
+
import { promisify as promisify14 } from "util";
|
|
12767
12975
|
import { setTimeout as sleep2 } from "timers/promises";
|
|
12768
|
-
import { existsSync as
|
|
12769
|
-
import { join as
|
|
12770
|
-
var
|
|
12771
|
-
var TASKS_DIR =
|
|
12976
|
+
import { existsSync as existsSync43, mkdirSync as mkdirSync21, writeFileSync as writeFileSync22 } from "fs";
|
|
12977
|
+
import { join as join42 } from "path";
|
|
12978
|
+
var execAsync14 = promisify14(exec14);
|
|
12979
|
+
var TASKS_DIR = join42(PANOPTICON_HOME, "specialists", "tasks");
|
|
12772
12980
|
function sendTask(tmuxSession, specialistName, task) {
|
|
12773
12981
|
const isLargeTask = task.length > 500 || task.includes("\n");
|
|
12774
12982
|
if (isLargeTask) {
|
|
12775
|
-
if (!
|
|
12983
|
+
if (!existsSync43(TASKS_DIR)) {
|
|
12776
12984
|
mkdirSync21(TASKS_DIR, { recursive: true });
|
|
12777
12985
|
}
|
|
12778
|
-
const taskFile =
|
|
12986
|
+
const taskFile = join42(TASKS_DIR, `${specialistName}-${Date.now()}.md`);
|
|
12779
12987
|
writeFileSync22(taskFile, task, "utf-8");
|
|
12780
12988
|
const shortMessage = `Read and execute the task in: ${taskFile}`;
|
|
12781
12989
|
sendKeys(tmuxSession, shortMessage);
|
|
@@ -12829,7 +13037,7 @@ Waking ${status.displayName}...
|
|
|
12829
13037
|
console.log(chalk41.dim("Starting fresh session (no previous session found)"));
|
|
12830
13038
|
}
|
|
12831
13039
|
console.log(chalk41.dim(`Creating tmux session: ${tmuxSession}`));
|
|
12832
|
-
await
|
|
13040
|
+
await execAsync14(
|
|
12833
13041
|
`tmux new-session -d -s "${tmuxSession}" -c "${cwd}" "${claudeCmd}"`,
|
|
12834
13042
|
{ encoding: "utf-8" }
|
|
12835
13043
|
);
|
|
@@ -12953,10 +13161,10 @@ function getAge(date) {
|
|
|
12953
13161
|
init_esm_shims();
|
|
12954
13162
|
init_specialists();
|
|
12955
13163
|
import chalk43 from "chalk";
|
|
12956
|
-
import { exec as
|
|
12957
|
-
import { promisify as
|
|
13164
|
+
import { exec as exec15 } from "child_process";
|
|
13165
|
+
import { promisify as promisify15 } from "util";
|
|
12958
13166
|
import * as readline from "readline";
|
|
12959
|
-
var
|
|
13167
|
+
var execAsync15 = promisify15(exec15);
|
|
12960
13168
|
var ALL_SPECIALISTS = ["merge-agent", "review-agent", "test-agent", "inspect-agent", "uat-agent"];
|
|
12961
13169
|
async function resetCommand(name, options) {
|
|
12962
13170
|
if (options.all) {
|
|
@@ -13006,7 +13214,7 @@ Resetting ${status.displayName}...
|
|
|
13006
13214
|
if (status.isRunning) {
|
|
13007
13215
|
console.log(chalk43.dim("Stopping tmux session..."));
|
|
13008
13216
|
try {
|
|
13009
|
-
await
|
|
13217
|
+
await execAsync15(`tmux kill-session -t "${status.tmuxSession}"`, { encoding: "utf-8" });
|
|
13010
13218
|
console.log(chalk43.green("\u2713 Tmux session stopped"));
|
|
13011
13219
|
} catch (error) {
|
|
13012
13220
|
console.log(chalk43.yellow("\u26A0 Failed to stop tmux session (may not be running)"));
|
|
@@ -13060,7 +13268,7 @@ async function resetAllSpecialists(options) {
|
|
|
13060
13268
|
const tmuxSession = getTmuxSessionName(specialistName);
|
|
13061
13269
|
if (status.isRunning) {
|
|
13062
13270
|
try {
|
|
13063
|
-
await
|
|
13271
|
+
await execAsync15(`tmux kill-session -t "${tmuxSession}"`, { encoding: "utf-8" });
|
|
13064
13272
|
console.log(chalk43.dim(` Stopped ${specialistName} tmux session`));
|
|
13065
13273
|
} catch {
|
|
13066
13274
|
}
|
|
@@ -13083,11 +13291,11 @@ init_esm_shims();
|
|
|
13083
13291
|
init_specialists();
|
|
13084
13292
|
init_paths();
|
|
13085
13293
|
import chalk44 from "chalk";
|
|
13086
|
-
import { existsSync as
|
|
13087
|
-
import { join as
|
|
13294
|
+
import { existsSync as existsSync44, readFileSync as readFileSync38, writeFileSync as writeFileSync23 } from "fs";
|
|
13295
|
+
import { join as join43 } from "path";
|
|
13088
13296
|
import * as readline2 from "readline";
|
|
13089
13297
|
var ALL_SPECIALISTS2 = ["merge-agent", "review-agent", "test-agent", "inspect-agent", "uat-agent"];
|
|
13090
|
-
var REVIEW_STATUS_FILE2 =
|
|
13298
|
+
var REVIEW_STATUS_FILE2 = join43(PANOPTICON_HOME, "review-status.json");
|
|
13091
13299
|
async function clearQueueCommand(name, options) {
|
|
13092
13300
|
if (!ALL_SPECIALISTS2.includes(name)) {
|
|
13093
13301
|
console.log(chalk44.red(`
|
|
@@ -13137,10 +13345,10 @@ ${metadata.displayName} Queue:
|
|
|
13137
13345
|
issueIds.push(payload.issueId);
|
|
13138
13346
|
}
|
|
13139
13347
|
}
|
|
13140
|
-
const hookFile =
|
|
13141
|
-
if (
|
|
13348
|
+
const hookFile = join43(PANOPTICON_HOME, "agents", specialistName, "hook.json");
|
|
13349
|
+
if (existsSync44(hookFile)) {
|
|
13142
13350
|
try {
|
|
13143
|
-
const hook = JSON.parse(
|
|
13351
|
+
const hook = JSON.parse(readFileSync38(hookFile, "utf-8"));
|
|
13144
13352
|
hook.items = [];
|
|
13145
13353
|
hook.lastChecked = (/* @__PURE__ */ new Date()).toISOString();
|
|
13146
13354
|
writeFileSync23(hookFile, JSON.stringify(hook, null, 2), "utf-8");
|
|
@@ -13151,9 +13359,9 @@ ${metadata.displayName} Queue:
|
|
|
13151
13359
|
}
|
|
13152
13360
|
}
|
|
13153
13361
|
if (options.resetStatus && issueIds.length > 0) {
|
|
13154
|
-
if (
|
|
13362
|
+
if (existsSync44(REVIEW_STATUS_FILE2)) {
|
|
13155
13363
|
try {
|
|
13156
|
-
const statuses = JSON.parse(
|
|
13364
|
+
const statuses = JSON.parse(readFileSync38(REVIEW_STATUS_FILE2, "utf-8"));
|
|
13157
13365
|
let resetCount = 0;
|
|
13158
13366
|
for (const issueId of issueIds) {
|
|
13159
13367
|
const key = Object.keys(statuses).find((k) => k.toLowerCase() === issueId.toLowerCase());
|
|
@@ -13306,13 +13514,13 @@ function formatStatus(status) {
|
|
|
13306
13514
|
|
|
13307
13515
|
// src/cli/commands/specialists/logs.ts
|
|
13308
13516
|
init_esm_shims();
|
|
13309
|
-
import { existsSync as
|
|
13310
|
-
import { exec as
|
|
13311
|
-
import { promisify as
|
|
13312
|
-
var
|
|
13517
|
+
import { existsSync as existsSync45 } from "fs";
|
|
13518
|
+
import { exec as exec16 } from "child_process";
|
|
13519
|
+
import { promisify as promisify16 } from "util";
|
|
13520
|
+
var execAsync16 = promisify16(exec16);
|
|
13313
13521
|
async function listLogsCommand(project2, type, options) {
|
|
13314
13522
|
try {
|
|
13315
|
-
const { listRunLogs } = await import("../specialist-logs-
|
|
13523
|
+
const { listRunLogs } = await import("../specialist-logs-B7UC3UDO.js");
|
|
13316
13524
|
const limit = options.limit ? parseInt(options.limit) : 10;
|
|
13317
13525
|
const runs = listRunLogs(project2, type, { limit });
|
|
13318
13526
|
if (options.json) {
|
|
@@ -13357,7 +13565,7 @@ View a specific run: pan specialists logs ${project2} ${type} <runId>
|
|
|
13357
13565
|
}
|
|
13358
13566
|
async function viewLogCommand(project2, type, runId, options) {
|
|
13359
13567
|
try {
|
|
13360
|
-
const { getRunLog, parseLogMetadata, getRunLogPath } = await import("../specialist-logs-
|
|
13568
|
+
const { getRunLog, parseLogMetadata, getRunLogPath } = await import("../specialist-logs-B7UC3UDO.js");
|
|
13361
13569
|
const content = getRunLog(project2, type, runId);
|
|
13362
13570
|
if (!content) {
|
|
13363
13571
|
console.error(`\u274C Run log not found: ${runId}`);
|
|
@@ -13370,7 +13578,7 @@ async function viewLogCommand(project2, type, runId, options) {
|
|
|
13370
13578
|
}
|
|
13371
13579
|
const logPath = getRunLogPath(project2, type, runId);
|
|
13372
13580
|
try {
|
|
13373
|
-
await
|
|
13581
|
+
await execAsync16(`less -R "${logPath}"`);
|
|
13374
13582
|
} catch {
|
|
13375
13583
|
console.log(content);
|
|
13376
13584
|
}
|
|
@@ -13381,15 +13589,15 @@ async function viewLogCommand(project2, type, runId, options) {
|
|
|
13381
13589
|
}
|
|
13382
13590
|
async function tailLogCommand(project2, type) {
|
|
13383
13591
|
try {
|
|
13384
|
-
const { getRunLogPath } = await import("../specialist-logs-
|
|
13385
|
-
const { getProjectSpecialistMetadata } = await import("../specialists-
|
|
13592
|
+
const { getRunLogPath } = await import("../specialist-logs-B7UC3UDO.js");
|
|
13593
|
+
const { getProjectSpecialistMetadata } = await import("../specialists-X4OGA7WX.js");
|
|
13386
13594
|
const metadata = getProjectSpecialistMetadata(project2, type);
|
|
13387
13595
|
if (!metadata.currentRun) {
|
|
13388
13596
|
console.error(`\u274C No active run for ${project2}/${type}`);
|
|
13389
13597
|
process.exit(1);
|
|
13390
13598
|
}
|
|
13391
13599
|
const logPath = getRunLogPath(project2, type, metadata.currentRun);
|
|
13392
|
-
if (!
|
|
13600
|
+
if (!existsSync45(logPath)) {
|
|
13393
13601
|
console.error(`\u274C Log file not found: ${logPath}`);
|
|
13394
13602
|
process.exit(1);
|
|
13395
13603
|
}
|
|
@@ -13451,7 +13659,7 @@ async function cleanupLogsCommand(projectOrAll, type, options) {
|
|
|
13451
13659
|
console.log(" Use --force to confirm.");
|
|
13452
13660
|
process.exit(1);
|
|
13453
13661
|
}
|
|
13454
|
-
const { cleanupAllLogs } = await import("../specialist-logs-
|
|
13662
|
+
const { cleanupAllLogs } = await import("../specialist-logs-B7UC3UDO.js");
|
|
13455
13663
|
console.log("\u{1F9F9} Cleaning up old logs for all projects...\n");
|
|
13456
13664
|
const results = cleanupAllLogs();
|
|
13457
13665
|
console.log(`
|
|
@@ -13478,7 +13686,7 @@ async function cleanupLogsCommand(projectOrAll, type, options) {
|
|
|
13478
13686
|
console.log(" Use --force to confirm.");
|
|
13479
13687
|
process.exit(1);
|
|
13480
13688
|
}
|
|
13481
|
-
const { cleanupOldLogs } = await import("../specialist-logs-
|
|
13689
|
+
const { cleanupOldLogs } = await import("../specialist-logs-B7UC3UDO.js");
|
|
13482
13690
|
const { getSpecialistRetention } = await import("../projects-BPGM6IFB.js");
|
|
13483
13691
|
const retention = getSpecialistRetention(projectOrAll);
|
|
13484
13692
|
console.log(`\u{1F9F9} Cleaning up old logs for ${projectOrAll}/${type}...`);
|
|
@@ -13517,11 +13725,11 @@ import ora19 from "ora";
|
|
|
13517
13725
|
// src/lib/convoy.ts
|
|
13518
13726
|
init_esm_shims();
|
|
13519
13727
|
init_tmux();
|
|
13520
|
-
import { existsSync as
|
|
13521
|
-
import { join as
|
|
13728
|
+
import { existsSync as existsSync46, mkdirSync as mkdirSync22, writeFileSync as writeFileSync24, readFileSync as readFileSync40, readdirSync as readdirSync19 } from "fs";
|
|
13729
|
+
import { join as join44 } from "path";
|
|
13522
13730
|
import { homedir as homedir20 } from "os";
|
|
13523
|
-
import { exec as
|
|
13524
|
-
import { promisify as
|
|
13731
|
+
import { exec as exec17 } from "child_process";
|
|
13732
|
+
import { promisify as promisify17 } from "util";
|
|
13525
13733
|
import { parse as parseYaml } from "yaml";
|
|
13526
13734
|
|
|
13527
13735
|
// src/lib/convoy-templates.ts
|
|
@@ -13626,14 +13834,14 @@ function getExecutionOrder(template) {
|
|
|
13626
13834
|
// src/lib/convoy.ts
|
|
13627
13835
|
init_paths();
|
|
13628
13836
|
init_work_type_router();
|
|
13629
|
-
var
|
|
13630
|
-
var CONVOY_DIR =
|
|
13837
|
+
var execAsync17 = promisify17(exec17);
|
|
13838
|
+
var CONVOY_DIR = join44(homedir20(), ".panopticon", "convoys");
|
|
13631
13839
|
function getConvoyStateFile(convoyId) {
|
|
13632
|
-
return
|
|
13840
|
+
return join44(CONVOY_DIR, `${convoyId}.json`);
|
|
13633
13841
|
}
|
|
13634
13842
|
function getConvoyOutputDir(convoyId, template) {
|
|
13635
13843
|
const baseDir = template.config?.outputDir || ".panopticon/convoy-output";
|
|
13636
|
-
return
|
|
13844
|
+
return join44(process.cwd(), baseDir, convoyId);
|
|
13637
13845
|
}
|
|
13638
13846
|
function saveConvoyState(state) {
|
|
13639
13847
|
mkdirSync22(CONVOY_DIR, { recursive: true });
|
|
@@ -13641,11 +13849,11 @@ function saveConvoyState(state) {
|
|
|
13641
13849
|
}
|
|
13642
13850
|
function loadConvoyState(convoyId) {
|
|
13643
13851
|
const stateFile = getConvoyStateFile(convoyId);
|
|
13644
|
-
if (!
|
|
13852
|
+
if (!existsSync46(stateFile)) {
|
|
13645
13853
|
return void 0;
|
|
13646
13854
|
}
|
|
13647
13855
|
try {
|
|
13648
|
-
const content =
|
|
13856
|
+
const content = readFileSync40(stateFile, "utf-8");
|
|
13649
13857
|
return JSON.parse(content);
|
|
13650
13858
|
} catch {
|
|
13651
13859
|
return void 0;
|
|
@@ -13655,7 +13863,7 @@ function getConvoyStatus(convoyId) {
|
|
|
13655
13863
|
return loadConvoyState(convoyId);
|
|
13656
13864
|
}
|
|
13657
13865
|
function listConvoys(filter) {
|
|
13658
|
-
if (!
|
|
13866
|
+
if (!existsSync46(CONVOY_DIR)) {
|
|
13659
13867
|
return [];
|
|
13660
13868
|
}
|
|
13661
13869
|
const files = readdirSync19(CONVOY_DIR).filter((f) => f.endsWith(".json"));
|
|
@@ -13674,10 +13882,10 @@ function listConvoys(filter) {
|
|
|
13674
13882
|
);
|
|
13675
13883
|
}
|
|
13676
13884
|
function parseAgentTemplate(templatePath) {
|
|
13677
|
-
if (!
|
|
13885
|
+
if (!existsSync46(templatePath)) {
|
|
13678
13886
|
throw new Error(`Agent template not found: ${templatePath}`);
|
|
13679
13887
|
}
|
|
13680
|
-
const content =
|
|
13888
|
+
const content = readFileSync40(templatePath, "utf-8");
|
|
13681
13889
|
const frontmatterMatch = content.match(/^---\n([\s\S]+?)\n---\n([\s\S]*)$/);
|
|
13682
13890
|
if (!frontmatterMatch) {
|
|
13683
13891
|
throw new Error(`Invalid agent template format (missing frontmatter): ${templatePath}`);
|
|
@@ -13703,7 +13911,7 @@ function mapConvoyRoleToWorkType(role) {
|
|
|
13703
13911
|
}
|
|
13704
13912
|
async function spawnConvoyAgent(convoy, agent, agentState, context) {
|
|
13705
13913
|
const { role, subagent } = agent;
|
|
13706
|
-
const templatePath =
|
|
13914
|
+
const templatePath = join44(AGENTS_DIR, `${subagent}.md`);
|
|
13707
13915
|
const template = parseAgentTemplate(templatePath);
|
|
13708
13916
|
let model = template.model;
|
|
13709
13917
|
try {
|
|
@@ -13742,7 +13950,7 @@ ${context.issueId ? `**Issue ID**: ${context.issueId}` : ""}
|
|
|
13742
13950
|
`;
|
|
13743
13951
|
prompt = contextInstructions + prompt;
|
|
13744
13952
|
mkdirSync22(convoy.outputDir, { recursive: true });
|
|
13745
|
-
const promptFile =
|
|
13953
|
+
const promptFile = join44(convoy.outputDir, `${role}-prompt.md`);
|
|
13746
13954
|
writeFileSync24(promptFile, prompt);
|
|
13747
13955
|
const claudeCmd = `claude --dangerously-skip-permissions --model ${model}`;
|
|
13748
13956
|
createSession(agentState.tmuxSession, convoy.context.projectPath, claudeCmd, {
|
|
@@ -13753,10 +13961,10 @@ ${context.issueId ? `**Issue ID**: ${context.issueId}` : ""}
|
|
|
13753
13961
|
}
|
|
13754
13962
|
});
|
|
13755
13963
|
await new Promise((resolve3) => setTimeout(resolve3, 1500));
|
|
13756
|
-
await
|
|
13757
|
-
await
|
|
13964
|
+
await execAsync17(`tmux load-buffer "${promptFile}"`);
|
|
13965
|
+
await execAsync17(`tmux paste-buffer -t ${agentState.tmuxSession}`);
|
|
13758
13966
|
await new Promise((resolve3) => setTimeout(resolve3, 500));
|
|
13759
|
-
await
|
|
13967
|
+
await execAsync17(`tmux send-keys -t ${agentState.tmuxSession} Enter`);
|
|
13760
13968
|
agentState.status = "running";
|
|
13761
13969
|
agentState.startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
13762
13970
|
saveConvoyState(convoy);
|
|
@@ -13781,7 +13989,7 @@ async function startConvoy(templateName, context) {
|
|
|
13781
13989
|
};
|
|
13782
13990
|
for (const agent of template.agents) {
|
|
13783
13991
|
const tmuxSession = `${convoyId}-${agent.role}`;
|
|
13784
|
-
const outputFile =
|
|
13992
|
+
const outputFile = join44(outputDir, `${agent.role}.md`);
|
|
13785
13993
|
state.agents.push({
|
|
13786
13994
|
role: agent.role,
|
|
13787
13995
|
subagent: agent.subagent,
|
|
@@ -13816,8 +14024,8 @@ async function executePhase(convoy, template, phaseAgents, context) {
|
|
|
13816
14024
|
const agentContext = { ...context };
|
|
13817
14025
|
for (const depRole of deps) {
|
|
13818
14026
|
const depAgent = convoy.agents.find((a) => a.role === depRole);
|
|
13819
|
-
if (depAgent?.outputFile &&
|
|
13820
|
-
agentContext[`${depRole}_output`] =
|
|
14027
|
+
if (depAgent?.outputFile && existsSync46(depAgent.outputFile)) {
|
|
14028
|
+
agentContext[`${depRole}_output`] = readFileSync40(depAgent.outputFile, "utf-8");
|
|
13821
14029
|
}
|
|
13822
14030
|
}
|
|
13823
14031
|
spawnPromises.push(spawnConvoyAgent(convoy, agent, agentState, agentContext));
|
|
@@ -13880,7 +14088,7 @@ function updateAgentStatuses(convoy) {
|
|
|
13880
14088
|
agent.status = "completed";
|
|
13881
14089
|
agent.completedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
13882
14090
|
updated = true;
|
|
13883
|
-
if (agent.outputFile &&
|
|
14091
|
+
if (agent.outputFile && existsSync46(agent.outputFile)) {
|
|
13884
14092
|
agent.exitCode = 0;
|
|
13885
14093
|
} else {
|
|
13886
14094
|
agent.exitCode = 1;
|
|
@@ -14126,30 +14334,30 @@ function registerConvoyCommands(program2) {
|
|
|
14126
14334
|
init_esm_shims();
|
|
14127
14335
|
init_projects();
|
|
14128
14336
|
import chalk50 from "chalk";
|
|
14129
|
-
import { existsSync as
|
|
14130
|
-
import { join as
|
|
14337
|
+
import { existsSync as existsSync47, readFileSync as readFileSync41, symlinkSync as symlinkSync2, mkdirSync as mkdirSync23, readdirSync as readdirSync20, statSync as statSync10 } from "fs";
|
|
14338
|
+
import { join as join45, resolve as resolve2, dirname as dirname13 } from "path";
|
|
14131
14339
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
14132
14340
|
var __filename5 = fileURLToPath4(import.meta.url);
|
|
14133
14341
|
var __dirname5 = dirname13(__filename5);
|
|
14134
|
-
var BUNDLED_HOOKS_DIR =
|
|
14342
|
+
var BUNDLED_HOOKS_DIR = join45(__dirname5, "..", "..", "scripts", "git-hooks");
|
|
14135
14343
|
function installGitHooks(gitDir) {
|
|
14136
|
-
const hooksTarget =
|
|
14344
|
+
const hooksTarget = join45(gitDir, "hooks");
|
|
14137
14345
|
let installed = 0;
|
|
14138
|
-
if (!
|
|
14346
|
+
if (!existsSync47(hooksTarget)) {
|
|
14139
14347
|
mkdirSync23(hooksTarget, { recursive: true });
|
|
14140
14348
|
}
|
|
14141
|
-
if (!
|
|
14349
|
+
if (!existsSync47(BUNDLED_HOOKS_DIR)) {
|
|
14142
14350
|
return 0;
|
|
14143
14351
|
}
|
|
14144
14352
|
try {
|
|
14145
14353
|
const hooks = readdirSync20(BUNDLED_HOOKS_DIR).filter((f) => {
|
|
14146
|
-
const p =
|
|
14147
|
-
return
|
|
14354
|
+
const p = join45(BUNDLED_HOOKS_DIR, f);
|
|
14355
|
+
return existsSync47(p) && statSync10(p).isFile();
|
|
14148
14356
|
});
|
|
14149
14357
|
for (const hook of hooks) {
|
|
14150
|
-
const source =
|
|
14151
|
-
const target =
|
|
14152
|
-
if (
|
|
14358
|
+
const source = join45(BUNDLED_HOOKS_DIR, hook);
|
|
14359
|
+
const target = join45(hooksTarget, hook);
|
|
14360
|
+
if (existsSync47(target)) {
|
|
14153
14361
|
try {
|
|
14154
14362
|
const { readlinkSync: readlinkSync2 } = __require("fs");
|
|
14155
14363
|
if (readlinkSync2(target) === source) {
|
|
@@ -14158,7 +14366,7 @@ function installGitHooks(gitDir) {
|
|
|
14158
14366
|
} catch {
|
|
14159
14367
|
}
|
|
14160
14368
|
}
|
|
14161
|
-
if (
|
|
14369
|
+
if (existsSync47(target)) {
|
|
14162
14370
|
const { renameSync: renameSync4 } = __require("fs");
|
|
14163
14371
|
renameSync4(target, `${target}.backup`);
|
|
14164
14372
|
}
|
|
@@ -14171,7 +14379,7 @@ function installGitHooks(gitDir) {
|
|
|
14171
14379
|
}
|
|
14172
14380
|
async function projectAddCommand(projectPath, options = {}) {
|
|
14173
14381
|
const fullPath = resolve2(projectPath);
|
|
14174
|
-
if (!
|
|
14382
|
+
if (!existsSync47(fullPath)) {
|
|
14175
14383
|
console.log(chalk50.red(`Path does not exist: ${fullPath}`));
|
|
14176
14384
|
return;
|
|
14177
14385
|
}
|
|
@@ -14186,9 +14394,9 @@ async function projectAddCommand(projectPath, options = {}) {
|
|
|
14186
14394
|
}
|
|
14187
14395
|
let linearTeam = options.linearTeam;
|
|
14188
14396
|
if (!linearTeam) {
|
|
14189
|
-
const projectToml =
|
|
14190
|
-
if (
|
|
14191
|
-
const content =
|
|
14397
|
+
const projectToml = join45(fullPath, ".panopticon", "project.toml");
|
|
14398
|
+
if (existsSync47(projectToml)) {
|
|
14399
|
+
const content = readFileSync41(projectToml, "utf-8");
|
|
14192
14400
|
const match = content.match(/team\s*=\s*"([^"]+)"/);
|
|
14193
14401
|
if (match) linearTeam = match[1];
|
|
14194
14402
|
}
|
|
@@ -14214,19 +14422,19 @@ async function projectAddCommand(projectPath, options = {}) {
|
|
|
14214
14422
|
console.log(chalk50.dim(` Rally project: ${options.rallyProject}`));
|
|
14215
14423
|
}
|
|
14216
14424
|
console.log("");
|
|
14217
|
-
const hasDevcontainer =
|
|
14218
|
-
const hasInfra =
|
|
14219
|
-
const hasDevcontainerTemplate =
|
|
14220
|
-
const hasRootGit =
|
|
14425
|
+
const hasDevcontainer = existsSync47(join45(fullPath, ".devcontainer"));
|
|
14426
|
+
const hasInfra = existsSync47(join45(fullPath, "infra"));
|
|
14427
|
+
const hasDevcontainerTemplate = existsSync47(join45(fullPath, "infra", ".devcontainer-template")) || existsSync47(join45(fullPath, ".devcontainer-template"));
|
|
14428
|
+
const hasRootGit = existsSync47(join45(fullPath, ".git"));
|
|
14221
14429
|
const subRepos = [];
|
|
14222
14430
|
if (!hasRootGit) {
|
|
14223
14431
|
const { readdirSync: readdirSync22, statSync: statSync12 } = await import("fs");
|
|
14224
14432
|
try {
|
|
14225
14433
|
const entries = readdirSync22(fullPath);
|
|
14226
14434
|
for (const entry of entries) {
|
|
14227
|
-
const entryPath =
|
|
14435
|
+
const entryPath = join45(fullPath, entry);
|
|
14228
14436
|
try {
|
|
14229
|
-
if (statSync12(entryPath).isDirectory() &&
|
|
14437
|
+
if (statSync12(entryPath).isDirectory() && existsSync47(join45(entryPath, ".git"))) {
|
|
14230
14438
|
subRepos.push(entry);
|
|
14231
14439
|
}
|
|
14232
14440
|
} catch {
|
|
@@ -14237,19 +14445,19 @@ async function projectAddCommand(projectPath, options = {}) {
|
|
|
14237
14445
|
}
|
|
14238
14446
|
const isPolyrepo = !hasRootGit && subRepos.length > 0;
|
|
14239
14447
|
try {
|
|
14240
|
-
const { preTrustDirectory } = await import("../workspace-manager-
|
|
14448
|
+
const { preTrustDirectory } = await import("../workspace-manager-6RP5A5HF.js");
|
|
14241
14449
|
preTrustDirectory(fullPath);
|
|
14242
14450
|
} catch {
|
|
14243
14451
|
}
|
|
14244
14452
|
let hooksInstalled = 0;
|
|
14245
14453
|
if (hasRootGit) {
|
|
14246
|
-
hooksInstalled = installGitHooks(
|
|
14454
|
+
hooksInstalled = installGitHooks(join45(fullPath, ".git"));
|
|
14247
14455
|
if (hooksInstalled > 0) {
|
|
14248
14456
|
console.log(chalk50.green(`\u2713 Installed ${hooksInstalled} git hook(s) for branch protection`));
|
|
14249
14457
|
}
|
|
14250
14458
|
} else if (isPolyrepo) {
|
|
14251
14459
|
for (const repo of subRepos) {
|
|
14252
|
-
const count = installGitHooks(
|
|
14460
|
+
const count = installGitHooks(join45(fullPath, repo, ".git"));
|
|
14253
14461
|
hooksInstalled += count;
|
|
14254
14462
|
}
|
|
14255
14463
|
if (hooksInstalled > 0) {
|
|
@@ -14321,7 +14529,7 @@ async function projectListCommand(options = {}) {
|
|
|
14321
14529
|
}
|
|
14322
14530
|
console.log(chalk50.bold("\nRegistered Projects:\n"));
|
|
14323
14531
|
for (const { key, config: config2 } of projects) {
|
|
14324
|
-
const exists =
|
|
14532
|
+
const exists = existsSync47(config2.path);
|
|
14325
14533
|
const statusIcon = exists ? chalk50.green("\u2713") : chalk50.red("\u2717");
|
|
14326
14534
|
console.log(`${statusIcon} ${chalk50.bold(config2.name)} ${chalk50.dim(`(${key})`)}`);
|
|
14327
14535
|
console.log(` ${chalk50.dim(config2.path)}`);
|
|
@@ -14355,7 +14563,7 @@ async function projectRemoveCommand(nameOrPath) {
|
|
|
14355
14563
|
console.log(chalk50.dim(`Use 'pan project list' to see registered projects.`));
|
|
14356
14564
|
}
|
|
14357
14565
|
async function projectInitCommand() {
|
|
14358
|
-
if (
|
|
14566
|
+
if (existsSync47(PROJECTS_CONFIG_FILE)) {
|
|
14359
14567
|
console.log(chalk50.yellow(`Config already exists: ${PROJECTS_CONFIG_FILE}`));
|
|
14360
14568
|
return;
|
|
14361
14569
|
}
|
|
@@ -14389,7 +14597,7 @@ async function projectShowCommand(keyOrName) {
|
|
|
14389
14597
|
console.log(chalk50.dim(`Use 'pan project list' to see registered projects.`));
|
|
14390
14598
|
process.exit(1);
|
|
14391
14599
|
}
|
|
14392
|
-
const pathExists =
|
|
14600
|
+
const pathExists = existsSync47(found.path);
|
|
14393
14601
|
const pathStatus = pathExists ? chalk50.green("\u2713") : chalk50.red("\u2717");
|
|
14394
14602
|
console.log(chalk50.bold(`
|
|
14395
14603
|
Project: ${foundKey}
|
|
@@ -14421,10 +14629,10 @@ Project: ${foundKey}
|
|
|
14421
14629
|
init_esm_shims();
|
|
14422
14630
|
init_paths();
|
|
14423
14631
|
import chalk51 from "chalk";
|
|
14424
|
-
import { existsSync as
|
|
14632
|
+
import { existsSync as existsSync48, readdirSync as readdirSync21, readFileSync as readFileSync42 } from "fs";
|
|
14425
14633
|
import { execSync as execSync6 } from "child_process";
|
|
14426
14634
|
import { homedir as homedir21 } from "os";
|
|
14427
|
-
import { join as
|
|
14635
|
+
import { join as join46 } from "path";
|
|
14428
14636
|
function checkCommand3(cmd) {
|
|
14429
14637
|
try {
|
|
14430
14638
|
execSync6(`which ${cmd}`, { encoding: "utf-8", stdio: "pipe" });
|
|
@@ -14434,10 +14642,10 @@ function checkCommand3(cmd) {
|
|
|
14434
14642
|
}
|
|
14435
14643
|
}
|
|
14436
14644
|
function checkDirectory(path) {
|
|
14437
|
-
return
|
|
14645
|
+
return existsSync48(path);
|
|
14438
14646
|
}
|
|
14439
14647
|
function countItems(path) {
|
|
14440
|
-
if (!
|
|
14648
|
+
if (!existsSync48(path)) return 0;
|
|
14441
14649
|
try {
|
|
14442
14650
|
return readdirSync21(path).length;
|
|
14443
14651
|
} catch {
|
|
@@ -14488,8 +14696,8 @@ async function doctorCommand() {
|
|
|
14488
14696
|
}
|
|
14489
14697
|
}
|
|
14490
14698
|
if (checkDirectory(CLAUDE_DIR)) {
|
|
14491
|
-
const skillsCount = countItems(
|
|
14492
|
-
const commandsCount = countItems(
|
|
14699
|
+
const skillsCount = countItems(join46(CLAUDE_DIR, "skills"));
|
|
14700
|
+
const commandsCount = countItems(join46(CLAUDE_DIR, "commands"));
|
|
14493
14701
|
checks.push({
|
|
14494
14702
|
name: "Claude Code Skills",
|
|
14495
14703
|
status: skillsCount > 0 ? "ok" : "warn",
|
|
@@ -14510,8 +14718,8 @@ async function doctorCommand() {
|
|
|
14510
14718
|
fix: "Install Claude Code first"
|
|
14511
14719
|
});
|
|
14512
14720
|
}
|
|
14513
|
-
const envFile =
|
|
14514
|
-
if (
|
|
14721
|
+
const envFile = join46(homedir21(), ".panopticon.env");
|
|
14722
|
+
if (existsSync48(envFile)) {
|
|
14515
14723
|
checks.push({ name: "Config File", status: "ok", message: "~/.panopticon.env exists" });
|
|
14516
14724
|
} else {
|
|
14517
14725
|
checks.push({
|
|
@@ -14523,8 +14731,8 @@ async function doctorCommand() {
|
|
|
14523
14731
|
}
|
|
14524
14732
|
if (process.env.LINEAR_API_KEY) {
|
|
14525
14733
|
checks.push({ name: "LINEAR_API_KEY", status: "ok", message: "Set in environment" });
|
|
14526
|
-
} else if (
|
|
14527
|
-
const content =
|
|
14734
|
+
} else if (existsSync48(envFile)) {
|
|
14735
|
+
const content = readFileSync42(envFile, "utf-8");
|
|
14528
14736
|
if (content.includes("LINEAR_API_KEY")) {
|
|
14529
14737
|
checks.push({ name: "LINEAR_API_KEY", status: "ok", message: "Set in config file" });
|
|
14530
14738
|
} else {
|
|
@@ -14592,15 +14800,15 @@ init_esm_shims();
|
|
|
14592
14800
|
init_config();
|
|
14593
14801
|
import { execSync as execSync7 } from "child_process";
|
|
14594
14802
|
import chalk52 from "chalk";
|
|
14595
|
-
import { readFileSync as
|
|
14803
|
+
import { readFileSync as readFileSync43 } from "fs";
|
|
14596
14804
|
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
14597
|
-
import { dirname as dirname14, join as
|
|
14805
|
+
import { dirname as dirname14, join as join47 } from "path";
|
|
14598
14806
|
function getCurrentVersion() {
|
|
14599
14807
|
try {
|
|
14600
14808
|
const __filename7 = fileURLToPath5(import.meta.url);
|
|
14601
14809
|
const __dirname7 = dirname14(__filename7);
|
|
14602
|
-
const pkgPath =
|
|
14603
|
-
const pkg = JSON.parse(
|
|
14810
|
+
const pkgPath = join47(__dirname7, "..", "..", "..", "package.json");
|
|
14811
|
+
const pkg = JSON.parse(readFileSync43(pkgPath, "utf-8"));
|
|
14604
14812
|
return pkg.version;
|
|
14605
14813
|
} catch {
|
|
14606
14814
|
return "unknown";
|
|
@@ -14686,11 +14894,11 @@ init_esm_shims();
|
|
|
14686
14894
|
init_projects();
|
|
14687
14895
|
import chalk53 from "chalk";
|
|
14688
14896
|
import ora21 from "ora";
|
|
14689
|
-
import { existsSync as
|
|
14690
|
-
import { join as
|
|
14691
|
-
import { exec as
|
|
14692
|
-
import { promisify as
|
|
14693
|
-
var
|
|
14897
|
+
import { existsSync as existsSync49, readFileSync as readFileSync44, writeFileSync as writeFileSync25, mkdirSync as mkdirSync24, statSync as statSync11 } from "fs";
|
|
14898
|
+
import { join as join48, dirname as dirname15 } from "path";
|
|
14899
|
+
import { exec as exec18 } from "child_process";
|
|
14900
|
+
import { promisify as promisify18 } from "util";
|
|
14901
|
+
var execAsync18 = promisify18(exec18);
|
|
14694
14902
|
function loadFullProjects() {
|
|
14695
14903
|
const config2 = loadProjectsConfig();
|
|
14696
14904
|
const projects = config2.projects;
|
|
@@ -14749,9 +14957,9 @@ async function snapshotCommand(options) {
|
|
|
14749
14957
|
`));
|
|
14750
14958
|
return;
|
|
14751
14959
|
}
|
|
14752
|
-
const outputPath = options.output || dbConfig.seed_file ||
|
|
14960
|
+
const outputPath = options.output || dbConfig.seed_file || join48(projectConfig.path, "infra", "seed", "seed.sql");
|
|
14753
14961
|
const outputDir = dirname15(outputPath);
|
|
14754
|
-
if (!
|
|
14962
|
+
if (!existsSync49(outputDir)) {
|
|
14755
14963
|
mkdirSync24(outputDir, { recursive: true });
|
|
14756
14964
|
}
|
|
14757
14965
|
spinner.text = "Running snapshot command...";
|
|
@@ -14772,10 +14980,10 @@ async function snapshotCommand(options) {
|
|
|
14772
14980
|
}
|
|
14773
14981
|
const fullCmd = `${snapshotCmd} > "${outputPath}" 2>&1`;
|
|
14774
14982
|
try {
|
|
14775
|
-
await
|
|
14983
|
+
await execAsync18(fullCmd, { timeout: 3e5 });
|
|
14776
14984
|
} catch (error) {
|
|
14777
|
-
if (
|
|
14778
|
-
const content2 =
|
|
14985
|
+
if (existsSync49(outputPath)) {
|
|
14986
|
+
const content2 = readFileSync44(outputPath, "utf-8");
|
|
14779
14987
|
if (content2.includes("PostgreSQL database dump")) {
|
|
14780
14988
|
spinner.warn("Snapshot completed with warnings (stderr captured)");
|
|
14781
14989
|
console.log(chalk53.dim(" Run `pan db clean` to remove stderr noise from the file"));
|
|
@@ -14788,7 +14996,7 @@ async function snapshotCommand(options) {
|
|
|
14788
14996
|
return;
|
|
14789
14997
|
}
|
|
14790
14998
|
}
|
|
14791
|
-
const content =
|
|
14999
|
+
const content = readFileSync44(outputPath, "utf-8");
|
|
14792
15000
|
if (content.includes("Defaulted container") || content.includes("Unable to use a TTY")) {
|
|
14793
15001
|
spinner.text = "Cleaning kubectl output from snapshot...";
|
|
14794
15002
|
await cleanFile(outputPath);
|
|
@@ -14796,7 +15004,7 @@ async function snapshotCommand(options) {
|
|
|
14796
15004
|
if (options.sanitize && dbConfig.seed_command) {
|
|
14797
15005
|
spinner.text = "Running sanitization...";
|
|
14798
15006
|
try {
|
|
14799
|
-
await
|
|
15007
|
+
await execAsync18(dbConfig.seed_command, { cwd: projectConfig.path });
|
|
14800
15008
|
} catch (error) {
|
|
14801
15009
|
spinner.warn(`Sanitization warning: ${error.message}`);
|
|
14802
15010
|
}
|
|
@@ -14820,14 +15028,14 @@ async function seedCommand(workspaceOrIssue, options) {
|
|
|
14820
15028
|
spinner.fail("Could not find project workspace configuration");
|
|
14821
15029
|
return;
|
|
14822
15030
|
}
|
|
14823
|
-
const workspacePath =
|
|
14824
|
-
if (!
|
|
15031
|
+
const workspacePath = join48(projectConfig.path, projectConfig.workspace.workspaces_dir || "workspaces", folderName);
|
|
15032
|
+
if (!existsSync49(workspacePath)) {
|
|
14825
15033
|
spinner.fail(`Workspace not found: ${workspacePath}`);
|
|
14826
15034
|
return;
|
|
14827
15035
|
}
|
|
14828
15036
|
const dbConfig = projectConfig.workspace.database;
|
|
14829
15037
|
const seedFile = options.file || dbConfig?.seed_file;
|
|
14830
|
-
if (!seedFile || !
|
|
15038
|
+
if (!seedFile || !existsSync49(seedFile)) {
|
|
14831
15039
|
spinner.fail(`Seed file not found: ${seedFile || "(not configured)"}`);
|
|
14832
15040
|
console.log(chalk53.dim("\nConfigure seed_file in projects.yaml or use --file"));
|
|
14833
15041
|
return;
|
|
@@ -14836,7 +15044,7 @@ async function seedCommand(workspaceOrIssue, options) {
|
|
|
14836
15044
|
const containerName = dbConfig?.container_name?.replace("{{PROJECT}}", projectName) || `${projectName}-postgres-1`;
|
|
14837
15045
|
spinner.text = `Finding database container ${containerName}...`;
|
|
14838
15046
|
try {
|
|
14839
|
-
const { stdout } = await
|
|
15047
|
+
const { stdout } = await execAsync18(`docker ps --filter "name=${containerName}" --format "{{.Names}}"`);
|
|
14840
15048
|
if (!stdout.trim()) {
|
|
14841
15049
|
spinner.fail(`Database container not running: ${containerName}`);
|
|
14842
15050
|
console.log(chalk53.dim("\nStart the workspace containers first:"));
|
|
@@ -14849,7 +15057,7 @@ async function seedCommand(workspaceOrIssue, options) {
|
|
|
14849
15057
|
}
|
|
14850
15058
|
if (!options.force) {
|
|
14851
15059
|
try {
|
|
14852
|
-
const { stdout } = await
|
|
15060
|
+
const { stdout } = await execAsync18(
|
|
14853
15061
|
`docker exec ${containerName} psql -U postgres -d myn -c "SELECT count(*) FROM flyway_schema_history" -t 2>/dev/null`
|
|
14854
15062
|
);
|
|
14855
15063
|
const count = parseInt(stdout.trim(), 10);
|
|
@@ -14863,7 +15071,7 @@ async function seedCommand(workspaceOrIssue, options) {
|
|
|
14863
15071
|
if (options.force) {
|
|
14864
15072
|
spinner.text = "Dropping existing database...";
|
|
14865
15073
|
try {
|
|
14866
|
-
await
|
|
15074
|
+
await execAsync18(
|
|
14867
15075
|
`docker exec ${containerName} psql -U postgres -c "DROP DATABASE IF EXISTS myn; CREATE DATABASE myn;"`
|
|
14868
15076
|
);
|
|
14869
15077
|
} catch (error) {
|
|
@@ -14871,10 +15079,10 @@ async function seedCommand(workspaceOrIssue, options) {
|
|
|
14871
15079
|
}
|
|
14872
15080
|
}
|
|
14873
15081
|
spinner.text = "Copying seed file to container...";
|
|
14874
|
-
await
|
|
15082
|
+
await execAsync18(`docker cp "${seedFile}" ${containerName}:/tmp/seed.sql`);
|
|
14875
15083
|
spinner.text = "Executing seed...";
|
|
14876
15084
|
try {
|
|
14877
|
-
await
|
|
15085
|
+
await execAsync18(`docker exec ${containerName} psql -U postgres -d myn -f /tmp/seed.sql`, {
|
|
14878
15086
|
timeout: 6e5
|
|
14879
15087
|
// 10 minute timeout for large seeds
|
|
14880
15088
|
});
|
|
@@ -14884,10 +15092,10 @@ async function seedCommand(workspaceOrIssue, options) {
|
|
|
14884
15092
|
return;
|
|
14885
15093
|
}
|
|
14886
15094
|
}
|
|
14887
|
-
await
|
|
15095
|
+
await execAsync18(`docker exec ${containerName} rm /tmp/seed.sql`);
|
|
14888
15096
|
spinner.succeed("Database seeded successfully");
|
|
14889
15097
|
try {
|
|
14890
|
-
const { stdout } = await
|
|
15098
|
+
const { stdout } = await execAsync18(
|
|
14891
15099
|
`docker exec ${containerName} psql -U postgres -d myn -c "SELECT version, description FROM flyway_schema_history ORDER BY installed_rank DESC LIMIT 3" -t`
|
|
14892
15100
|
);
|
|
14893
15101
|
console.log(chalk53.dim("\nRecent migrations:"));
|
|
@@ -14917,7 +15125,7 @@ async function statusCommand5(workspaceOrIssue) {
|
|
|
14917
15125
|
const projects = loadFullProjects();
|
|
14918
15126
|
projectConfig = projects.find((p) => cwd.startsWith(p.path));
|
|
14919
15127
|
if (projectConfig) {
|
|
14920
|
-
const { stdout } = await
|
|
15128
|
+
const { stdout } = await execAsync18(
|
|
14921
15129
|
`docker ps --filter "name=${projectConfig.name?.toLowerCase().replace(/\s+/g, "-")}" --filter "name=postgres" --format "{{.Names}}" | head -1`
|
|
14922
15130
|
);
|
|
14923
15131
|
containerName = stdout.trim();
|
|
@@ -14930,7 +15138,7 @@ async function statusCommand5(workspaceOrIssue) {
|
|
|
14930
15138
|
return;
|
|
14931
15139
|
}
|
|
14932
15140
|
spinner.text = `Checking container ${containerName}...`;
|
|
14933
|
-
const { stdout: containerStatus } = await
|
|
15141
|
+
const { stdout: containerStatus } = await execAsync18(
|
|
14934
15142
|
`docker ps --filter "name=${containerName}" --format "{{.Status}}"`
|
|
14935
15143
|
);
|
|
14936
15144
|
if (!containerStatus.trim()) {
|
|
@@ -14940,7 +15148,7 @@ async function statusCommand5(workspaceOrIssue) {
|
|
|
14940
15148
|
spinner.succeed(`Container: ${containerName}`);
|
|
14941
15149
|
console.log(chalk53.dim(` Status: ${containerStatus.trim()}`));
|
|
14942
15150
|
try {
|
|
14943
|
-
const { stdout: version } = await
|
|
15151
|
+
const { stdout: version } = await execAsync18(
|
|
14944
15152
|
`docker exec ${containerName} psql -U postgres -d myn -c "SELECT version, description FROM flyway_schema_history ORDER BY installed_rank DESC LIMIT 1" -t`
|
|
14945
15153
|
);
|
|
14946
15154
|
const [ver, desc] = version.trim().split("|").map((s) => s.trim());
|
|
@@ -14949,14 +15157,14 @@ async function statusCommand5(workspaceOrIssue) {
|
|
|
14949
15157
|
console.log(chalk53.yellow(" Flyway: Not initialized"));
|
|
14950
15158
|
}
|
|
14951
15159
|
try {
|
|
14952
|
-
const { stdout: tableCount } = await
|
|
15160
|
+
const { stdout: tableCount } = await execAsync18(
|
|
14953
15161
|
`docker exec ${containerName} psql -U postgres -d myn -c "SELECT count(*) FROM information_schema.tables WHERE table_schema = 'public'" -t`
|
|
14954
15162
|
);
|
|
14955
15163
|
console.log(chalk53.dim(` Tables: ${tableCount.trim()}`));
|
|
14956
15164
|
} catch {
|
|
14957
15165
|
}
|
|
14958
15166
|
try {
|
|
14959
|
-
const { stdout: dbSize } = await
|
|
15167
|
+
const { stdout: dbSize } = await execAsync18(
|
|
14960
15168
|
`docker exec ${containerName} psql -U postgres -d myn -c "SELECT pg_size_pretty(pg_database_size('myn'))" -t`
|
|
14961
15169
|
);
|
|
14962
15170
|
console.log(chalk53.dim(` Size: ${dbSize.trim()}`));
|
|
@@ -14969,11 +15177,11 @@ async function statusCommand5(workspaceOrIssue) {
|
|
|
14969
15177
|
async function cleanCommand(file, options) {
|
|
14970
15178
|
const spinner = ora21("Cleaning database dump file...").start();
|
|
14971
15179
|
try {
|
|
14972
|
-
if (!
|
|
15180
|
+
if (!existsSync49(file)) {
|
|
14973
15181
|
spinner.fail(`File not found: ${file}`);
|
|
14974
15182
|
return;
|
|
14975
15183
|
}
|
|
14976
|
-
const content =
|
|
15184
|
+
const content = readFileSync44(file, "utf-8");
|
|
14977
15185
|
const lines = content.split("\n");
|
|
14978
15186
|
const patternsToRemove = [
|
|
14979
15187
|
/^Defaulted container/,
|
|
@@ -15043,7 +15251,7 @@ async function cleanCommand(file, options) {
|
|
|
15043
15251
|
}
|
|
15044
15252
|
}
|
|
15045
15253
|
async function cleanFile(filePath) {
|
|
15046
|
-
const content =
|
|
15254
|
+
const content = readFileSync44(filePath, "utf-8");
|
|
15047
15255
|
const lines = content.split("\n");
|
|
15048
15256
|
let startIndex = 0;
|
|
15049
15257
|
for (let i = 0; i < lines.length; i++) {
|
|
@@ -15097,7 +15305,7 @@ async function configCommand(project2) {
|
|
|
15097
15305
|
return;
|
|
15098
15306
|
}
|
|
15099
15307
|
if (dbConfig.seed_file) {
|
|
15100
|
-
const exists =
|
|
15308
|
+
const exists = existsSync49(dbConfig.seed_file);
|
|
15101
15309
|
console.log(` Seed file: ${dbConfig.seed_file}`);
|
|
15102
15310
|
console.log(chalk53.dim(` Status: ${exists ? chalk53.green("exists") : chalk53.red("not found")}`));
|
|
15103
15311
|
if (exists) {
|
|
@@ -15126,17 +15334,17 @@ async function configCommand(project2) {
|
|
|
15126
15334
|
init_esm_shims();
|
|
15127
15335
|
import chalk54 from "chalk";
|
|
15128
15336
|
import ora22 from "ora";
|
|
15129
|
-
import { existsSync as
|
|
15130
|
-
import { join as
|
|
15131
|
-
import { exec as
|
|
15132
|
-
import { promisify as
|
|
15337
|
+
import { existsSync as existsSync50, readFileSync as readFileSync45 } from "fs";
|
|
15338
|
+
import { join as join49 } from "path";
|
|
15339
|
+
import { exec as exec19, execSync as execSync8 } from "child_process";
|
|
15340
|
+
import { promisify as promisify19 } from "util";
|
|
15133
15341
|
import { platform } from "os";
|
|
15134
|
-
var
|
|
15342
|
+
var execAsync19 = promisify19(exec19);
|
|
15135
15343
|
function detectPlatform2() {
|
|
15136
15344
|
const os = platform();
|
|
15137
15345
|
if (os === "linux") {
|
|
15138
15346
|
try {
|
|
15139
|
-
const release =
|
|
15347
|
+
const release = readFileSync45("/proc/version", "utf8").toLowerCase();
|
|
15140
15348
|
if (release.includes("microsoft") || release.includes("wsl")) {
|
|
15141
15349
|
return "wsl";
|
|
15142
15350
|
}
|
|
@@ -15148,7 +15356,7 @@ function detectPlatform2() {
|
|
|
15148
15356
|
}
|
|
15149
15357
|
async function isBdAvailable() {
|
|
15150
15358
|
try {
|
|
15151
|
-
await
|
|
15359
|
+
await execAsync19("which bd", { encoding: "utf-8" });
|
|
15152
15360
|
return true;
|
|
15153
15361
|
} catch {
|
|
15154
15362
|
return false;
|
|
@@ -15157,7 +15365,7 @@ async function isBdAvailable() {
|
|
|
15157
15365
|
async function getOldClosedCount(cwd, days) {
|
|
15158
15366
|
try {
|
|
15159
15367
|
const seconds = days * 24 * 60 * 60;
|
|
15160
|
-
const { stdout } = await
|
|
15368
|
+
const { stdout } = await execAsync19(
|
|
15161
15369
|
`bd list --status closed --json 2>/dev/null | jq '[.[] | select(.closed_at != null) | select((now - (.closed_at | fromdateiso8601)) > ${seconds})] | length' 2>/dev/null || echo "0"`,
|
|
15162
15370
|
{ cwd, encoding: "utf-8" }
|
|
15163
15371
|
);
|
|
@@ -15174,8 +15382,8 @@ async function compactCommand(options) {
|
|
|
15174
15382
|
console.log(chalk54.dim("Install beads: https://github.com/steveyegge/beads"));
|
|
15175
15383
|
process.exit(1);
|
|
15176
15384
|
}
|
|
15177
|
-
const beadsDir =
|
|
15178
|
-
if (!
|
|
15385
|
+
const beadsDir = join49(cwd, ".beads");
|
|
15386
|
+
if (!existsSync50(beadsDir)) {
|
|
15179
15387
|
console.error(chalk54.red("Error: No .beads directory found in current directory"));
|
|
15180
15388
|
console.log(chalk54.dim("Run bd init to initialize beads"));
|
|
15181
15389
|
process.exit(1);
|
|
@@ -15193,7 +15401,7 @@ async function compactCommand(options) {
|
|
|
15193
15401
|
console.log("");
|
|
15194
15402
|
console.log(chalk54.bold("Beads that would be compacted:"));
|
|
15195
15403
|
try {
|
|
15196
|
-
const { stdout: beadsList } = await
|
|
15404
|
+
const { stdout: beadsList } = await execAsync19(
|
|
15197
15405
|
`bd list --status closed --json 2>/dev/null | jq -r '.[] | select(.closed_at != null) | select((now - (.closed_at | fromdateiso8601)) > ${days * 24 * 60 * 60}) | " - \\(.id): \\(.title)"' 2>/dev/null`,
|
|
15198
15406
|
{ cwd, encoding: "utf-8" }
|
|
15199
15407
|
);
|
|
@@ -15204,10 +15412,10 @@ async function compactCommand(options) {
|
|
|
15204
15412
|
return;
|
|
15205
15413
|
}
|
|
15206
15414
|
spinner.text = "Running compaction...";
|
|
15207
|
-
await
|
|
15415
|
+
await execAsync19(`bd admin compact --days ${days}`, { cwd, encoding: "utf-8" });
|
|
15208
15416
|
spinner.succeed(`Compacted ${count} beads older than ${days} days`);
|
|
15209
15417
|
try {
|
|
15210
|
-
await
|
|
15418
|
+
await execAsync19(`git diff --quiet .beads/`, { cwd, encoding: "utf-8" });
|
|
15211
15419
|
console.log(chalk54.dim("No changes to commit (beads already up to date)"));
|
|
15212
15420
|
} catch {
|
|
15213
15421
|
console.log("");
|
|
@@ -15234,24 +15442,24 @@ async function statsCommand() {
|
|
|
15234
15442
|
console.error(chalk54.red("Error: bd (beads) CLI not found"));
|
|
15235
15443
|
process.exit(1);
|
|
15236
15444
|
}
|
|
15237
|
-
const beadsDir =
|
|
15238
|
-
if (!
|
|
15445
|
+
const beadsDir = join49(cwd, ".beads");
|
|
15446
|
+
if (!existsSync50(beadsDir)) {
|
|
15239
15447
|
console.error(chalk54.red("Error: No .beads directory found"));
|
|
15240
15448
|
process.exit(1);
|
|
15241
15449
|
}
|
|
15242
15450
|
const spinner = ora22("Gathering beads statistics...").start();
|
|
15243
15451
|
try {
|
|
15244
|
-
const { stdout: totalRaw } = await
|
|
15452
|
+
const { stdout: totalRaw } = await execAsync19(`bd list --limit 0 --json 2>/dev/null | jq 'length'`, {
|
|
15245
15453
|
cwd,
|
|
15246
15454
|
encoding: "utf-8"
|
|
15247
15455
|
});
|
|
15248
15456
|
const total = parseInt(totalRaw.trim(), 10) || 0;
|
|
15249
|
-
const { stdout: openRaw } = await
|
|
15457
|
+
const { stdout: openRaw } = await execAsync19(`bd list --status open --limit 0 --json 2>/dev/null | jq 'length'`, {
|
|
15250
15458
|
cwd,
|
|
15251
15459
|
encoding: "utf-8"
|
|
15252
15460
|
});
|
|
15253
15461
|
const open = parseInt(openRaw.trim(), 10) || 0;
|
|
15254
|
-
const { stdout: closedRaw } = await
|
|
15462
|
+
const { stdout: closedRaw } = await execAsync19(`bd list --status closed --limit 0 --json 2>/dev/null | jq 'length'`, {
|
|
15255
15463
|
cwd,
|
|
15256
15464
|
encoding: "utf-8"
|
|
15257
15465
|
});
|
|
@@ -15296,7 +15504,7 @@ async function upgradeCommand(checkOnly = false) {
|
|
|
15296
15504
|
console.log(chalk54.dim("Checking beads version..."));
|
|
15297
15505
|
let currentVersion = "not installed";
|
|
15298
15506
|
try {
|
|
15299
|
-
const { stdout } = await
|
|
15507
|
+
const { stdout } = await execAsync19("bd --version", { encoding: "utf-8" });
|
|
15300
15508
|
const match = stdout.match(/(\d+\.\d+\.\d+)/);
|
|
15301
15509
|
if (match) {
|
|
15302
15510
|
currentVersion = match[1];
|
|
@@ -15305,7 +15513,7 @@ async function upgradeCommand(checkOnly = false) {
|
|
|
15305
15513
|
}
|
|
15306
15514
|
let latestVersion = "unknown";
|
|
15307
15515
|
try {
|
|
15308
|
-
const { stdout } = await
|
|
15516
|
+
const { stdout } = await execAsync19(
|
|
15309
15517
|
"curl -sL https://api.github.com/repos/steveyegge/beads/releases/latest | jq -r .tag_name",
|
|
15310
15518
|
{ encoding: "utf-8" }
|
|
15311
15519
|
);
|
|
@@ -15354,7 +15562,7 @@ async function upgradeCommand(checkOnly = false) {
|
|
|
15354
15562
|
spinner.succeed("beads upgraded via install script");
|
|
15355
15563
|
}
|
|
15356
15564
|
try {
|
|
15357
|
-
const { stdout } = await
|
|
15565
|
+
const { stdout } = await execAsync19("bd --version", { encoding: "utf-8" });
|
|
15358
15566
|
const match = stdout.match(/(\d+\.\d+\.\d+)/);
|
|
15359
15567
|
if (match) {
|
|
15360
15568
|
console.log(chalk54.green(`
|
|
@@ -15567,9 +15775,9 @@ init_esm_shims();
|
|
|
15567
15775
|
init_config();
|
|
15568
15776
|
import chalk57 from "chalk";
|
|
15569
15777
|
import ora25 from "ora";
|
|
15570
|
-
import { exec as
|
|
15571
|
-
import { promisify as
|
|
15572
|
-
var
|
|
15778
|
+
import { exec as exec20 } from "child_process";
|
|
15779
|
+
import { promisify as promisify20 } from "util";
|
|
15780
|
+
var execAsync20 = promisify20(exec20);
|
|
15573
15781
|
async function initCommand2(options) {
|
|
15574
15782
|
const config2 = loadConfig();
|
|
15575
15783
|
if (!config2.remote?.enabled) {
|
|
@@ -15763,9 +15971,9 @@ init_esm_shims();
|
|
|
15763
15971
|
init_config();
|
|
15764
15972
|
import chalk59 from "chalk";
|
|
15765
15973
|
import ora27 from "ora";
|
|
15766
|
-
import { exec as
|
|
15767
|
-
import { promisify as
|
|
15768
|
-
var
|
|
15974
|
+
import { exec as exec21 } from "child_process";
|
|
15975
|
+
import { promisify as promisify21 } from "util";
|
|
15976
|
+
var execAsync21 = promisify21(exec21);
|
|
15769
15977
|
async function setupCommand() {
|
|
15770
15978
|
console.log("");
|
|
15771
15979
|
console.log(chalk59.bold("\u{1F680} Remote Workspace Setup"));
|
|
@@ -15774,7 +15982,7 @@ async function setupCommand() {
|
|
|
15774
15982
|
const spinner = ora27("Checking flyctl installation...").start();
|
|
15775
15983
|
let flyInstalled = false;
|
|
15776
15984
|
try {
|
|
15777
|
-
await
|
|
15985
|
+
await execAsync21("fly version", { timeout: 1e4 });
|
|
15778
15986
|
flyInstalled = true;
|
|
15779
15987
|
spinner.succeed("flyctl is installed");
|
|
15780
15988
|
} catch {
|
|
@@ -15796,7 +16004,7 @@ async function setupCommand() {
|
|
|
15796
16004
|
console.log(` ${chalk59.green("\u2713")} Authenticated with Fly.io`);
|
|
15797
16005
|
if (flyInstalled) {
|
|
15798
16006
|
try {
|
|
15799
|
-
const { stdout } = await
|
|
16007
|
+
const { stdout } = await execAsync21("fly auth whoami", { timeout: 1e4 });
|
|
15800
16008
|
console.log(` ${chalk59.dim(" User: " + stdout.trim())}`);
|
|
15801
16009
|
} catch {
|
|
15802
16010
|
}
|
|
@@ -15879,9 +16087,9 @@ import chalk60 from "chalk";
|
|
|
15879
16087
|
|
|
15880
16088
|
// src/lib/env-loader.ts
|
|
15881
16089
|
init_esm_shims();
|
|
15882
|
-
import { join as
|
|
16090
|
+
import { join as join50 } from "path";
|
|
15883
16091
|
import { homedir as homedir22 } from "os";
|
|
15884
|
-
var ENV_FILE_PATH =
|
|
16092
|
+
var ENV_FILE_PATH = join50(homedir22(), ".panopticon.env");
|
|
15885
16093
|
function getShadowModeFromEnv() {
|
|
15886
16094
|
const value = process.env.SHADOW_MODE;
|
|
15887
16095
|
if (!value) return false;
|
|
@@ -15982,30 +16190,32 @@ import chalk61 from "chalk";
|
|
|
15982
16190
|
|
|
15983
16191
|
// src/lib/cloister/inspect-agent.ts
|
|
15984
16192
|
init_esm_shims();
|
|
15985
|
-
import { readFileSync as
|
|
15986
|
-
import { join as
|
|
16193
|
+
import { readFileSync as readFileSync47, existsSync as existsSync52 } from "fs";
|
|
16194
|
+
import { join as join52, dirname as dirname16 } from "path";
|
|
15987
16195
|
import { fileURLToPath as fileURLToPath6 } from "url";
|
|
15988
|
-
import { exec as
|
|
15989
|
-
import { promisify as
|
|
16196
|
+
import { exec as exec23 } from "child_process";
|
|
16197
|
+
import { promisify as promisify23 } from "util";
|
|
15990
16198
|
|
|
15991
16199
|
// src/lib/cloister/inspect-checkpoints.ts
|
|
15992
16200
|
init_esm_shims();
|
|
15993
|
-
import { existsSync as
|
|
15994
|
-
import { join as
|
|
16201
|
+
import { existsSync as existsSync51, readFileSync as readFileSync46, writeFileSync as writeFileSync26, mkdirSync as mkdirSync25 } from "fs";
|
|
16202
|
+
import { join as join51 } from "path";
|
|
15995
16203
|
import { homedir as homedir23 } from "os";
|
|
15996
|
-
import {
|
|
15997
|
-
|
|
16204
|
+
import { exec as exec22 } from "child_process";
|
|
16205
|
+
import { promisify as promisify22 } from "util";
|
|
16206
|
+
var execAsync22 = promisify22(exec22);
|
|
16207
|
+
var PANOPTICON_HOME2 = join51(homedir23(), ".panopticon");
|
|
15998
16208
|
function getCheckpointDir(projectKey) {
|
|
15999
|
-
return
|
|
16209
|
+
return join51(PANOPTICON_HOME2, "specialists", projectKey, "inspect-agent", "checkpoints");
|
|
16000
16210
|
}
|
|
16001
16211
|
function getCheckpointPath(projectKey, issueId) {
|
|
16002
|
-
return
|
|
16212
|
+
return join51(getCheckpointDir(projectKey), `${issueId.toUpperCase()}.json`);
|
|
16003
16213
|
}
|
|
16004
16214
|
function loadCheckpoints(projectKey, issueId) {
|
|
16005
16215
|
const filePath = getCheckpointPath(projectKey, issueId);
|
|
16006
|
-
if (!
|
|
16216
|
+
if (!existsSync51(filePath)) return null;
|
|
16007
16217
|
try {
|
|
16008
|
-
return JSON.parse(
|
|
16218
|
+
return JSON.parse(readFileSync46(filePath, "utf-8"));
|
|
16009
16219
|
} catch {
|
|
16010
16220
|
return null;
|
|
16011
16221
|
}
|
|
@@ -16015,28 +16225,28 @@ function getLastCheckpoint(projectKey, issueId) {
|
|
|
16015
16225
|
if (!data || data.checkpoints.length === 0) return null;
|
|
16016
16226
|
return data.checkpoints[data.checkpoints.length - 1];
|
|
16017
16227
|
}
|
|
16018
|
-
function getDiffBase(projectKey, issueId, workspacePath) {
|
|
16228
|
+
async function getDiffBase(projectKey, issueId, workspacePath) {
|
|
16019
16229
|
const lastCheckpoint = getLastCheckpoint(projectKey, issueId);
|
|
16020
16230
|
if (lastCheckpoint) {
|
|
16021
16231
|
return lastCheckpoint.commitSha;
|
|
16022
16232
|
}
|
|
16023
16233
|
try {
|
|
16024
|
-
const
|
|
16234
|
+
const { stdout } = await execAsync22("git merge-base main HEAD", {
|
|
16025
16235
|
cwd: workspacePath,
|
|
16026
16236
|
encoding: "utf-8"
|
|
16027
|
-
})
|
|
16028
|
-
return
|
|
16237
|
+
});
|
|
16238
|
+
return stdout.trim();
|
|
16029
16239
|
} catch {
|
|
16030
16240
|
return "main";
|
|
16031
16241
|
}
|
|
16032
16242
|
}
|
|
16033
|
-
function getDiffStats(workspacePath, diffBase) {
|
|
16243
|
+
async function getDiffStats(workspacePath, diffBase) {
|
|
16034
16244
|
try {
|
|
16035
|
-
const
|
|
16245
|
+
const { stdout } = await execAsync22(`git diff --stat ${diffBase}...HEAD`, {
|
|
16036
16246
|
cwd: workspacePath,
|
|
16037
16247
|
encoding: "utf-8"
|
|
16038
|
-
})
|
|
16039
|
-
return
|
|
16248
|
+
});
|
|
16249
|
+
return stdout.trim() || "No changes detected";
|
|
16040
16250
|
} catch {
|
|
16041
16251
|
return "Unable to compute diff stats";
|
|
16042
16252
|
}
|
|
@@ -16045,12 +16255,12 @@ function getDiffStats(workspacePath, diffBase) {
|
|
|
16045
16255
|
// src/lib/cloister/inspect-agent.ts
|
|
16046
16256
|
init_specialists();
|
|
16047
16257
|
init_review_status();
|
|
16048
|
-
var
|
|
16258
|
+
var execAsync23 = promisify23(exec23);
|
|
16049
16259
|
var __filename6 = fileURLToPath6(import.meta.url);
|
|
16050
16260
|
var __dirname6 = dirname16(__filename6);
|
|
16051
16261
|
async function getBeadDescription(beadId, workspacePath) {
|
|
16052
16262
|
try {
|
|
16053
|
-
const { stdout } = await
|
|
16263
|
+
const { stdout } = await execAsync23(`bd show ${beadId} --json`, {
|
|
16054
16264
|
cwd: workspacePath,
|
|
16055
16265
|
encoding: "utf-8"
|
|
16056
16266
|
});
|
|
@@ -16064,7 +16274,7 @@ async function getBeadDescription(beadId, workspacePath) {
|
|
|
16064
16274
|
return parts.join("\n\n") || `Bead ${beadId} (no description available)`;
|
|
16065
16275
|
} catch {
|
|
16066
16276
|
try {
|
|
16067
|
-
const { stdout } = await
|
|
16277
|
+
const { stdout } = await execAsync23(`bd show ${beadId}`, {
|
|
16068
16278
|
cwd: workspacePath,
|
|
16069
16279
|
encoding: "utf-8"
|
|
16070
16280
|
});
|
|
@@ -16084,8 +16294,8 @@ function detectCompileCommand(workspacePath) {
|
|
|
16084
16294
|
];
|
|
16085
16295
|
for (const check of checks) {
|
|
16086
16296
|
for (const subdir of ["", "fe", "api", "frontend", "backend"]) {
|
|
16087
|
-
const checkPath = subdir ?
|
|
16088
|
-
if (
|
|
16297
|
+
const checkPath = subdir ? join52(workspacePath, subdir, check.file) : join52(workspacePath, check.file);
|
|
16298
|
+
if (existsSync52(checkPath)) {
|
|
16089
16299
|
const cwd = subdir ? `cd ${subdir} && ` : "";
|
|
16090
16300
|
return `${cwd}${check.command}`;
|
|
16091
16301
|
}
|
|
@@ -16094,14 +16304,14 @@ function detectCompileCommand(workspacePath) {
|
|
|
16094
16304
|
return 'echo "No compile command detected \u2014 skipping compile check"';
|
|
16095
16305
|
}
|
|
16096
16306
|
async function buildInspectPrompt(context) {
|
|
16097
|
-
const templatePath =
|
|
16098
|
-
if (!
|
|
16307
|
+
const templatePath = join52(__dirname6, "prompts", "inspect-agent.md");
|
|
16308
|
+
if (!existsSync52(templatePath)) {
|
|
16099
16309
|
throw new Error(`Inspect agent prompt template not found at ${templatePath}`);
|
|
16100
16310
|
}
|
|
16101
|
-
const template =
|
|
16311
|
+
const template = readFileSync47(templatePath, "utf-8");
|
|
16102
16312
|
const beadDescription = await getBeadDescription(context.beadId, context.workspace);
|
|
16103
|
-
const diffBase = getDiffBase(context.projectKey, context.issueId, context.workspace);
|
|
16104
|
-
const diffStats = getDiffStats(context.workspace, diffBase);
|
|
16313
|
+
const diffBase = await getDiffBase(context.projectKey, context.issueId, context.workspace);
|
|
16314
|
+
const diffStats = await getDiffStats(context.workspace, diffBase);
|
|
16105
16315
|
const compileCommand = detectCompileCommand(context.workspace);
|
|
16106
16316
|
const apiUrl = process.env.DASHBOARD_URL || `http://localhost:${process.env.API_PORT || process.env.PORT || "3011"}`;
|
|
16107
16317
|
const prompt = template.replace(/\{\{apiUrl\}\}/g, apiUrl).replace(/\{\{projectPath\}\}/g, context.projectPath).replace(/\{\{issueId\}\}/g, context.issueId).replace(/\{\{beadId\}\}/g, context.beadId).replace(/\{\{workspacePath\}\}/g, context.workspace).replace(/\{\{checkpoint\}\}/g, diffBase.substring(0, 8)).replace(/\{\{diffBase\}\}/g, diffBase).replace(/\{\{diffStats\}\}/g, diffStats).replace(/\{\{beadDescription\}\}/g, beadDescription).replace(/\{\{compileCommand\}\}/g, compileCommand).replace(/\{\{resultStatus\}\}/g, "${RESULT_STATUS}").replace(/\{\{resultNotes\}\}/g, "${RESULT_NOTES}");
|
|
@@ -16144,10 +16354,10 @@ async function inspectCommand(issueId, options) {
|
|
|
16144
16354
|
}
|
|
16145
16355
|
let workspacePath = options.workspace;
|
|
16146
16356
|
if (!workspacePath) {
|
|
16147
|
-
const { join:
|
|
16148
|
-
const { existsSync:
|
|
16149
|
-
const candidatePath =
|
|
16150
|
-
if (
|
|
16357
|
+
const { join: join55 } = await import("path");
|
|
16358
|
+
const { existsSync: existsSync55 } = await import("fs");
|
|
16359
|
+
const candidatePath = join55(project2.projectPath, "workspaces", `feature-${normalizedIssueId.toLowerCase()}`);
|
|
16360
|
+
if (existsSync55(candidatePath)) {
|
|
16151
16361
|
workspacePath = candidatePath;
|
|
16152
16362
|
}
|
|
16153
16363
|
}
|
|
@@ -16156,8 +16366,8 @@ async function inspectCommand(issueId, options) {
|
|
|
16156
16366
|
console.error(chalk61.dim("Provide --workspace <path> or ensure a workspace exists for this issue"));
|
|
16157
16367
|
process.exit(1);
|
|
16158
16368
|
}
|
|
16159
|
-
const diffBase = getDiffBase(project2.projectKey, normalizedIssueId, workspacePath);
|
|
16160
|
-
const diffStats = getDiffStats(workspacePath, diffBase);
|
|
16369
|
+
const diffBase = await getDiffBase(project2.projectKey, normalizedIssueId, workspacePath);
|
|
16370
|
+
const diffStats = await getDiffStats(workspacePath, diffBase);
|
|
16161
16371
|
console.log("");
|
|
16162
16372
|
console.log(chalk61.bold("Requesting inspection"));
|
|
16163
16373
|
console.log(chalk61.dim(` Issue: ${normalizedIssueId}`));
|
|
@@ -16202,9 +16412,9 @@ import chalk62 from "chalk";
|
|
|
16202
16412
|
// src/lib/costs/sync-wal.ts
|
|
16203
16413
|
init_esm_shims();
|
|
16204
16414
|
init_projects();
|
|
16205
|
-
import { existsSync as
|
|
16415
|
+
import { existsSync as existsSync53 } from "fs";
|
|
16206
16416
|
import { readdir, readFile } from "fs/promises";
|
|
16207
|
-
import { join as
|
|
16417
|
+
import { join as join53 } from "path";
|
|
16208
16418
|
|
|
16209
16419
|
// src/lib/database/cost-events-db.ts
|
|
16210
16420
|
init_esm_shims();
|
|
@@ -16269,8 +16479,8 @@ async function syncWalFromAllProjects() {
|
|
|
16269
16479
|
for (const { key, config: config2 } of projects) {
|
|
16270
16480
|
const repoPath = config2.events_repo ?? config2.path;
|
|
16271
16481
|
const eventsSubdir = config2.events_path ?? DEFAULT_EVENTS_SUBDIR;
|
|
16272
|
-
const eventsDir =
|
|
16273
|
-
if (!
|
|
16482
|
+
const eventsDir = join53(repoPath, eventsSubdir);
|
|
16483
|
+
if (!existsSync53(eventsDir)) continue;
|
|
16274
16484
|
const projectStats = { imported: 0, duplicates: 0, files: 0 };
|
|
16275
16485
|
let files;
|
|
16276
16486
|
try {
|
|
@@ -16280,7 +16490,7 @@ async function syncWalFromAllProjects() {
|
|
|
16280
16490
|
continue;
|
|
16281
16491
|
}
|
|
16282
16492
|
for (const file of files) {
|
|
16283
|
-
const filePath =
|
|
16493
|
+
const filePath = join53(eventsDir, file);
|
|
16284
16494
|
const events = await parseWalFile(filePath, result.errors);
|
|
16285
16495
|
if (events.length === 0) continue;
|
|
16286
16496
|
try {
|
|
@@ -16607,10 +16817,10 @@ function createCostCommand() {
|
|
|
16607
16817
|
}
|
|
16608
16818
|
|
|
16609
16819
|
// src/cli/index.ts
|
|
16610
|
-
var PANOPTICON_ENV_FILE =
|
|
16611
|
-
if (
|
|
16820
|
+
var PANOPTICON_ENV_FILE = join54(homedir24(), ".panopticon.env");
|
|
16821
|
+
if (existsSync54(PANOPTICON_ENV_FILE)) {
|
|
16612
16822
|
try {
|
|
16613
|
-
const envContent =
|
|
16823
|
+
const envContent = readFileSync48(PANOPTICON_ENV_FILE, "utf-8");
|
|
16614
16824
|
for (const line of envContent.split("\n")) {
|
|
16615
16825
|
const trimmed = line.trim();
|
|
16616
16826
|
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
@@ -16627,7 +16837,7 @@ if (existsSync53(PANOPTICON_ENV_FILE)) {
|
|
|
16627
16837
|
}
|
|
16628
16838
|
}
|
|
16629
16839
|
var program = new Command2();
|
|
16630
|
-
program.name("pan").description("Multi-agent orchestration for AI coding assistants").version(JSON.parse(
|
|
16840
|
+
program.name("pan").description("Multi-agent orchestration for AI coding assistants").version(JSON.parse(readFileSync48(join54(import.meta.dirname, "../../package.json"), "utf-8")).version);
|
|
16631
16841
|
program.command("init").description("Initialize Panopticon (~/.panopticon/)").action(initCommand);
|
|
16632
16842
|
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);
|
|
16633
16843
|
program.command("restore [timestamp]").description("Restore from backup").action(restoreCommand);
|
|
@@ -16651,22 +16861,22 @@ registerInspectCommand(program);
|
|
|
16651
16861
|
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);
|
|
16652
16862
|
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);
|
|
16653
16863
|
program.command("up").description("Start dashboard (and Traefik if enabled)").option("--detach", "Run in background").option("--skip-traefik", "Skip Traefik startup").action(async (options) => {
|
|
16654
|
-
const { spawn: spawn2, execSync:
|
|
16655
|
-
const { join:
|
|
16864
|
+
const { spawn: spawn2, execSync: execSync9 } = await import("child_process");
|
|
16865
|
+
const { join: join55, dirname: dirname17 } = await import("path");
|
|
16656
16866
|
const { fileURLToPath: fileURLToPath7 } = await import("url");
|
|
16657
|
-
const { readFileSync:
|
|
16867
|
+
const { readFileSync: readFileSync49, existsSync: existsSync55 } = await import("fs");
|
|
16658
16868
|
const { parse } = await import("@iarna/toml");
|
|
16659
16869
|
const __dirname7 = dirname17(fileURLToPath7(import.meta.url));
|
|
16660
|
-
const bundledServer =
|
|
16661
|
-
const srcDashboard =
|
|
16662
|
-
const configFile =
|
|
16870
|
+
const bundledServer = join55(__dirname7, "..", "dashboard", "server.js");
|
|
16871
|
+
const srcDashboard = join55(__dirname7, "..", "..", "src", "dashboard");
|
|
16872
|
+
const configFile = join55(process.env.HOME || "", ".panopticon", "config.toml");
|
|
16663
16873
|
let traefikEnabled = false;
|
|
16664
16874
|
let traefikDomain = "pan.localhost";
|
|
16665
16875
|
let dashboardPort = 3010;
|
|
16666
16876
|
let dashboardApiPort = 3011;
|
|
16667
|
-
if (
|
|
16877
|
+
if (existsSync55(configFile)) {
|
|
16668
16878
|
try {
|
|
16669
|
-
const configContent =
|
|
16879
|
+
const configContent = readFileSync49(configFile, "utf-8");
|
|
16670
16880
|
const config2 = parse(configContent);
|
|
16671
16881
|
traefikEnabled = config2.traefik?.enabled === true;
|
|
16672
16882
|
traefikDomain = config2.traefik?.domain || "pan.localhost";
|
|
@@ -16696,7 +16906,7 @@ program.command("up").description("Start dashboard (and Traefik if enabled)").op
|
|
|
16696
16906
|
}
|
|
16697
16907
|
try {
|
|
16698
16908
|
const { ensureBaseDomain: ensureBaseDomain2, detectDnsSyncMethod: detectDnsSyncMethod2, syncDnsToWindows: syncDnsToWindows2 } = await import("../dns-7BDJSD3E.js");
|
|
16699
|
-
const dnsMethod = (
|
|
16909
|
+
const dnsMethod = (existsSync55(configFile) ? parse(readFileSync49(configFile, "utf-8")).traefik?.dns_sync_method : null) || detectDnsSyncMethod2();
|
|
16700
16910
|
ensureBaseDomain2(dnsMethod, traefikDomain);
|
|
16701
16911
|
if (dnsMethod === "wsl2hosts") {
|
|
16702
16912
|
syncDnsToWindows2().catch(() => {
|
|
@@ -16707,7 +16917,7 @@ program.command("up").description("Start dashboard (and Traefik if enabled)").op
|
|
|
16707
16917
|
}
|
|
16708
16918
|
} else if (!traefikEnabled) {
|
|
16709
16919
|
try {
|
|
16710
|
-
const containerCheck =
|
|
16920
|
+
const containerCheck = execSync9(
|
|
16711
16921
|
'docker ps --filter "name=panopticon-traefik" --format "{{.Names}}" 2>/dev/null',
|
|
16712
16922
|
{ encoding: "utf-8" }
|
|
16713
16923
|
).trim();
|
|
@@ -16719,12 +16929,12 @@ program.command("up").description("Start dashboard (and Traefik if enabled)").op
|
|
|
16719
16929
|
}
|
|
16720
16930
|
}
|
|
16721
16931
|
if (traefikEnabled && !options.skipTraefik) {
|
|
16722
|
-
const traefikDir =
|
|
16723
|
-
if (
|
|
16932
|
+
const traefikDir = join55(process.env.HOME || "", ".panopticon", "traefik");
|
|
16933
|
+
if (existsSync55(traefikDir)) {
|
|
16724
16934
|
try {
|
|
16725
|
-
const composeFile =
|
|
16726
|
-
if (
|
|
16727
|
-
const content =
|
|
16935
|
+
const composeFile = join55(traefikDir, "docker-compose.yml");
|
|
16936
|
+
if (existsSync55(composeFile)) {
|
|
16937
|
+
const content = readFileSync49(composeFile, "utf-8");
|
|
16728
16938
|
if (!content.includes("external: true") && content.includes("panopticon:")) {
|
|
16729
16939
|
const patched = content.replace(
|
|
16730
16940
|
/networks:\s*\n\s*panopticon:\s*\n\s*name: panopticon\s*\n\s*driver: bridge/,
|
|
@@ -16736,7 +16946,7 @@ program.command("up").description("Start dashboard (and Traefik if enabled)").op
|
|
|
16736
16946
|
}
|
|
16737
16947
|
}
|
|
16738
16948
|
console.log(chalk63.dim("Starting Traefik..."));
|
|
16739
|
-
|
|
16949
|
+
execSync9("docker compose up -d", {
|
|
16740
16950
|
cwd: traefikDir,
|
|
16741
16951
|
stdio: "pipe"
|
|
16742
16952
|
});
|
|
@@ -16749,8 +16959,8 @@ program.command("up").description("Start dashboard (and Traefik if enabled)").op
|
|
|
16749
16959
|
}
|
|
16750
16960
|
}
|
|
16751
16961
|
}
|
|
16752
|
-
const isProduction =
|
|
16753
|
-
const isDevelopment =
|
|
16962
|
+
const isProduction = existsSync55(bundledServer);
|
|
16963
|
+
const isDevelopment = existsSync55(srcDashboard);
|
|
16754
16964
|
if (!isProduction && !isDevelopment) {
|
|
16755
16965
|
console.error(chalk63.red("Error: Dashboard not found"));
|
|
16756
16966
|
console.error(chalk63.dim("This may be a corrupted installation. Try reinstalling panopticon-cli."));
|
|
@@ -16758,7 +16968,7 @@ program.command("up").description("Start dashboard (and Traefik if enabled)").op
|
|
|
16758
16968
|
}
|
|
16759
16969
|
if (isDevelopment && !isProduction) {
|
|
16760
16970
|
try {
|
|
16761
|
-
|
|
16971
|
+
execSync9("npm --version", { stdio: "pipe" });
|
|
16762
16972
|
} catch {
|
|
16763
16973
|
console.error(chalk63.red("Error: npm not found in PATH"));
|
|
16764
16974
|
console.error(chalk63.dim("Make sure Node.js and npm are installed and in your PATH"));
|
|
@@ -16825,8 +17035,8 @@ program.command("up").description("Start dashboard (and Traefik if enabled)").op
|
|
|
16825
17035
|
try {
|
|
16826
17036
|
const { getTldrDaemonService: getTldrDaemonService2 } = await import("../tldr-daemon-T3THOUGT.js");
|
|
16827
17037
|
const projectRoot = process.cwd();
|
|
16828
|
-
const venvPath =
|
|
16829
|
-
if (
|
|
17038
|
+
const venvPath = join55(projectRoot, ".venv");
|
|
17039
|
+
if (existsSync55(venvPath)) {
|
|
16830
17040
|
console.log(chalk63.dim("\nStarting TLDR daemon for project root..."));
|
|
16831
17041
|
const tldrService = getTldrDaemonService2(projectRoot, venvPath);
|
|
16832
17042
|
await tldrService.start(true);
|
|
@@ -16841,18 +17051,18 @@ program.command("up").description("Start dashboard (and Traefik if enabled)").op
|
|
|
16841
17051
|
}
|
|
16842
17052
|
});
|
|
16843
17053
|
program.command("down").description("Stop dashboard (and Traefik if enabled)").option("--skip-traefik", "Skip Traefik shutdown").action(async (options) => {
|
|
16844
|
-
const { execSync:
|
|
16845
|
-
const { join:
|
|
16846
|
-
const { readFileSync:
|
|
17054
|
+
const { execSync: execSync9 } = await import("child_process");
|
|
17055
|
+
const { join: join55 } = await import("path");
|
|
17056
|
+
const { readFileSync: readFileSync49, existsSync: existsSync55 } = await import("fs");
|
|
16847
17057
|
const { parse } = await import("@iarna/toml");
|
|
16848
17058
|
console.log(chalk63.bold("Stopping Panopticon...\n"));
|
|
16849
|
-
const configFile =
|
|
17059
|
+
const configFile = join55(process.env.HOME || "", ".panopticon", "config.toml");
|
|
16850
17060
|
let traefikEnabled = false;
|
|
16851
17061
|
let dashboardPort = 3010;
|
|
16852
17062
|
let dashboardApiPort = 3011;
|
|
16853
|
-
if (
|
|
17063
|
+
if (existsSync55(configFile)) {
|
|
16854
17064
|
try {
|
|
16855
|
-
const configContent =
|
|
17065
|
+
const configContent = readFileSync49(configFile, "utf-8");
|
|
16856
17066
|
const config2 = parse(configContent);
|
|
16857
17067
|
traefikEnabled = config2.traefik?.enabled === true;
|
|
16858
17068
|
dashboardPort = config2.dashboard?.port || 3010;
|
|
@@ -16862,18 +17072,18 @@ program.command("down").description("Stop dashboard (and Traefik if enabled)").o
|
|
|
16862
17072
|
}
|
|
16863
17073
|
console.log(chalk63.dim("Stopping dashboard..."));
|
|
16864
17074
|
try {
|
|
16865
|
-
|
|
16866
|
-
|
|
17075
|
+
execSync9(`lsof -ti:${dashboardPort} | xargs kill -9 2>/dev/null || true`, { stdio: "pipe" });
|
|
17076
|
+
execSync9(`lsof -ti:${dashboardApiPort} | xargs kill -9 2>/dev/null || true`, { stdio: "pipe" });
|
|
16867
17077
|
console.log(chalk63.green("\u2713 Dashboard stopped"));
|
|
16868
17078
|
} catch {
|
|
16869
17079
|
console.log(chalk63.dim(" No dashboard processes found"));
|
|
16870
17080
|
}
|
|
16871
17081
|
if (traefikEnabled && !options.skipTraefik) {
|
|
16872
|
-
const traefikDir =
|
|
16873
|
-
if (
|
|
17082
|
+
const traefikDir = join55(process.env.HOME || "", ".panopticon", "traefik");
|
|
17083
|
+
if (existsSync55(traefikDir)) {
|
|
16874
17084
|
console.log(chalk63.dim("Stopping Traefik..."));
|
|
16875
17085
|
try {
|
|
16876
|
-
|
|
17086
|
+
execSync9("docker compose down", {
|
|
16877
17087
|
cwd: traefikDir,
|
|
16878
17088
|
stdio: "pipe"
|
|
16879
17089
|
});
|
|
@@ -16885,12 +17095,12 @@ program.command("down").description("Stop dashboard (and Traefik if enabled)").o
|
|
|
16885
17095
|
}
|
|
16886
17096
|
try {
|
|
16887
17097
|
const { getTldrDaemonService: getTldrDaemonService2 } = await import("../tldr-daemon-T3THOUGT.js");
|
|
16888
|
-
const { exec:
|
|
16889
|
-
const { promisify:
|
|
16890
|
-
const
|
|
17098
|
+
const { exec: exec24 } = await import("child_process");
|
|
17099
|
+
const { promisify: promisify24 } = await import("util");
|
|
17100
|
+
const execAsync24 = promisify24(exec24);
|
|
16891
17101
|
const projectRoot = process.cwd();
|
|
16892
|
-
const venvPath =
|
|
16893
|
-
if (
|
|
17102
|
+
const venvPath = join55(projectRoot, ".venv");
|
|
17103
|
+
if (existsSync55(venvPath)) {
|
|
16894
17104
|
console.log(chalk63.dim("\nStopping TLDR daemon..."));
|
|
16895
17105
|
const tldrService = getTldrDaemonService2(projectRoot, venvPath);
|
|
16896
17106
|
await tldrService.stop();
|