panopticon-cli 0.4.32 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +96 -210
- package/dist/{agents-BDFHF4T3.js → agents-E43Y3HNU.js} +10 -7
- package/dist/chunk-7SN4L4PH.js +150 -0
- package/dist/chunk-7SN4L4PH.js.map +1 -0
- package/dist/{chunk-2NIAOCIC.js → chunk-AAFQANKW.js} +358 -97
- package/dist/chunk-AAFQANKW.js.map +1 -0
- package/dist/chunk-AQXETQHW.js +113 -0
- package/dist/chunk-AQXETQHW.js.map +1 -0
- package/dist/chunk-B3PF6JPQ.js +212 -0
- package/dist/chunk-B3PF6JPQ.js.map +1 -0
- package/dist/chunk-CFCUOV3Q.js +669 -0
- package/dist/chunk-CFCUOV3Q.js.map +1 -0
- package/dist/chunk-CWELWPWQ.js +32 -0
- package/dist/chunk-CWELWPWQ.js.map +1 -0
- package/dist/chunk-DI7ABPNQ.js +352 -0
- package/dist/chunk-DI7ABPNQ.js.map +1 -0
- package/dist/{chunk-VU4FLXV5.js → chunk-FQ66DECN.js} +31 -4
- package/dist/chunk-FQ66DECN.js.map +1 -0
- package/dist/{chunk-VIWUCJ4V.js → chunk-FTCPTHIJ.js} +57 -432
- package/dist/chunk-FTCPTHIJ.js.map +1 -0
- package/dist/{review-status-GWQYY77L.js → chunk-GFP3PIPB.js} +14 -7
- package/dist/chunk-GFP3PIPB.js.map +1 -0
- package/dist/chunk-GR6ZZMCX.js +816 -0
- package/dist/chunk-GR6ZZMCX.js.map +1 -0
- package/dist/chunk-HJSM6E6U.js +1038 -0
- package/dist/chunk-HJSM6E6U.js.map +1 -0
- package/dist/{chunk-XP2DXWYP.js → chunk-HZT2AOPN.js} +164 -39
- package/dist/chunk-HZT2AOPN.js.map +1 -0
- package/dist/chunk-JQBV3Q2W.js +29 -0
- package/dist/chunk-JQBV3Q2W.js.map +1 -0
- package/dist/{chunk-BWGFN44T.js → chunk-JT4O4YVM.js} +28 -16
- package/dist/chunk-JT4O4YVM.js.map +1 -0
- package/dist/chunk-NTO3EDB3.js +600 -0
- package/dist/chunk-NTO3EDB3.js.map +1 -0
- package/dist/{chunk-JY7R7V4G.js → chunk-OMNXYPXC.js} +2 -2
- package/dist/chunk-OMNXYPXC.js.map +1 -0
- package/dist/chunk-PELXV435.js +215 -0
- package/dist/chunk-PELXV435.js.map +1 -0
- package/dist/chunk-PPRFKTVC.js +154 -0
- package/dist/chunk-PPRFKTVC.js.map +1 -0
- package/dist/chunk-WQG2TYCB.js +677 -0
- package/dist/chunk-WQG2TYCB.js.map +1 -0
- package/dist/{chunk-HCTJFIJJ.js → chunk-YLPSQAM2.js} +2 -2
- package/dist/{chunk-HCTJFIJJ.js.map → chunk-YLPSQAM2.js.map} +1 -1
- package/dist/{chunk-6HXKTOD7.js → chunk-ZTFNYOC7.js} +53 -38
- package/dist/chunk-ZTFNYOC7.js.map +1 -0
- package/dist/cli/index.js +5103 -3165
- package/dist/cli/index.js.map +1 -1
- package/dist/{config-BOAMSKTF.js → config-4CJNUE3O.js} +7 -3
- package/dist/dashboard/prompts/merge-agent.md +217 -0
- package/dist/dashboard/prompts/review-agent.md +409 -0
- package/dist/dashboard/prompts/sync-main.md +84 -0
- package/dist/dashboard/prompts/test-agent.md +283 -0
- package/dist/dashboard/prompts/work-agent.md +249 -0
- package/dist/dashboard/public/assets/index-BxpjweAL.css +32 -0
- package/dist/dashboard/public/assets/index-DQHkwvvJ.js +743 -0
- package/dist/dashboard/public/index.html +2 -2
- package/dist/dashboard/server.js +17619 -4044
- package/dist/{dns-L3L2BB27.js → dns-7BDJSD3E.js} +4 -2
- package/dist/{feedback-writer-AAKF5BTK.js → feedback-writer-LVZ5TFYZ.js} +8 -4
- package/dist/feedback-writer-LVZ5TFYZ.js.map +1 -0
- package/dist/hume-WMAUBBV2.js +13 -0
- package/dist/index.d.ts +162 -40
- package/dist/index.js +67 -23
- package/dist/index.js.map +1 -1
- package/dist/{projects-VXRUCMLM.js → projects-JEIVIYC6.js} +3 -3
- package/dist/rally-RKFSWC7E.js +10 -0
- package/dist/{remote-agents-Z3R2A5BN.js → remote-agents-TFSMW7GN.js} +2 -2
- package/dist/{remote-workspace-2G6V2KNP.js → remote-workspace-AHVHQEES.js} +8 -8
- package/dist/review-status-EPFG4XM7.js +19 -0
- package/dist/shadow-state-5MDP6YXH.js +30 -0
- package/dist/shadow-state-5MDP6YXH.js.map +1 -0
- package/dist/{specialist-context-N32QBNNQ.js → specialist-context-ZC6A4M3I.js} +8 -7
- package/dist/{specialist-context-N32QBNNQ.js.map → specialist-context-ZC6A4M3I.js.map} +1 -1
- package/dist/{specialist-logs-GF3YV4KL.js → specialist-logs-KLGJCEUL.js} +7 -6
- package/dist/specialist-logs-KLGJCEUL.js.map +1 -0
- package/dist/{specialists-JBIW6MP4.js → specialists-O4HWDJL5.js} +7 -6
- package/dist/specialists-O4HWDJL5.js.map +1 -0
- package/dist/tldr-daemon-T3THOUGT.js +21 -0
- package/dist/tldr-daemon-T3THOUGT.js.map +1 -0
- package/dist/traefik-QN7R5I6V.js +19 -0
- package/dist/traefik-QN7R5I6V.js.map +1 -0
- package/dist/tunnel-W2GZBLEV.js +13 -0
- package/dist/tunnel-W2GZBLEV.js.map +1 -0
- package/dist/workspace-manager-IE4JL2JP.js +22 -0
- package/dist/workspace-manager-IE4JL2JP.js.map +1 -0
- package/package.json +2 -2
- package/scripts/heartbeat-hook +37 -10
- package/scripts/patches/llm-tldr-tsx-support.py +109 -0
- package/scripts/pre-tool-hook +26 -15
- package/scripts/record-cost-event.js +177 -43
- package/scripts/record-cost-event.ts +87 -3
- package/scripts/statusline.sh +169 -0
- package/scripts/stop-hook +21 -11
- package/scripts/tldr-post-edit +72 -0
- package/scripts/tldr-read-enforcer +275 -0
- package/scripts/work-agent-stop-hook +137 -0
- package/skills/check-merged/SKILL.md +143 -0
- package/skills/crash-investigation/SKILL.md +301 -0
- package/skills/github-cli/SKILL.md +185 -0
- package/skills/myn-standards/SKILL.md +351 -0
- package/skills/pan-reopen/SKILL.md +65 -0
- package/skills/pan-sync-main/SKILL.md +87 -0
- package/skills/pan-tldr/SKILL.md +149 -0
- package/skills/react-best-practices/SKILL.md +125 -0
- package/skills/spec-readiness/REPORT-TEMPLATE.md +158 -0
- package/skills/spec-readiness/SCORING-REFERENCE.md +369 -0
- package/skills/spec-readiness/SKILL.md +400 -0
- package/skills/spec-readiness-setup/SKILL.md +361 -0
- package/skills/workspace-status/SKILL.md +56 -0
- package/skills/write-spec/SKILL.md +138 -0
- package/templates/traefik/dynamic/panopticon.yml.template +0 -5
- package/templates/traefik/traefik.yml +0 -8
- package/dist/chunk-2NIAOCIC.js.map +0 -1
- package/dist/chunk-3XAB4IXF.js +0 -51
- package/dist/chunk-3XAB4IXF.js.map +0 -1
- package/dist/chunk-6HXKTOD7.js.map +0 -1
- package/dist/chunk-BBCUK6N2.js +0 -241
- package/dist/chunk-BBCUK6N2.js.map +0 -1
- package/dist/chunk-BWGFN44T.js.map +0 -1
- package/dist/chunk-ELK6Q7QI.js +0 -545
- package/dist/chunk-ELK6Q7QI.js.map +0 -1
- package/dist/chunk-JY7R7V4G.js.map +0 -1
- package/dist/chunk-LYSBSZYV.js +0 -1523
- package/dist/chunk-LYSBSZYV.js.map +0 -1
- package/dist/chunk-VIWUCJ4V.js.map +0 -1
- package/dist/chunk-VU4FLXV5.js.map +0 -1
- package/dist/chunk-XP2DXWYP.js.map +0 -1
- package/dist/dashboard/public/assets/index-C7X6LP5Z.css +0 -32
- package/dist/dashboard/public/assets/index-ClYqpcAJ.js +0 -645
- package/dist/feedback-writer-AAKF5BTK.js.map +0 -1
- package/dist/review-status-GWQYY77L.js.map +0 -1
- package/dist/traefik-CUJM6K5Z.js +0 -12
- /package/dist/{agents-BDFHF4T3.js.map → agents-E43Y3HNU.js.map} +0 -0
- /package/dist/{config-BOAMSKTF.js.map → config-4CJNUE3O.js.map} +0 -0
- /package/dist/{dns-L3L2BB27.js.map → dns-7BDJSD3E.js.map} +0 -0
- /package/dist/{projects-VXRUCMLM.js.map → hume-WMAUBBV2.js.map} +0 -0
- /package/dist/{remote-agents-Z3R2A5BN.js.map → projects-JEIVIYC6.js.map} +0 -0
- /package/dist/{specialist-logs-GF3YV4KL.js.map → rally-RKFSWC7E.js.map} +0 -0
- /package/dist/{specialists-JBIW6MP4.js.map → remote-agents-TFSMW7GN.js.map} +0 -0
- /package/dist/{remote-workspace-2G6V2KNP.js.map → remote-workspace-AHVHQEES.js.map} +0 -0
- /package/dist/{traefik-CUJM6K5Z.js.map → review-status-EPFG4XM7.js.map} +0 -0
package/dist/chunk-ELK6Q7QI.js
DELETED
|
@@ -1,545 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
init_factory,
|
|
3
|
-
init_github,
|
|
4
|
-
init_gitlab,
|
|
5
|
-
init_interface,
|
|
6
|
-
init_linear
|
|
7
|
-
} from "./chunk-LYSBSZYV.js";
|
|
8
|
-
import {
|
|
9
|
-
AGENTS_DIR,
|
|
10
|
-
BACKUPS_DIR,
|
|
11
|
-
BIN_DIR,
|
|
12
|
-
COMMANDS_DIR,
|
|
13
|
-
SKILLS_DIR,
|
|
14
|
-
SOURCE_DEV_SKILLS_DIR,
|
|
15
|
-
SOURCE_SCRIPTS_DIR,
|
|
16
|
-
SYNC_TARGETS,
|
|
17
|
-
init_paths,
|
|
18
|
-
isDevMode
|
|
19
|
-
} from "./chunk-6HXKTOD7.js";
|
|
20
|
-
import {
|
|
21
|
-
init_esm_shims
|
|
22
|
-
} from "./chunk-ZHC57RCV.js";
|
|
23
|
-
|
|
24
|
-
// src/lib/shell.ts
|
|
25
|
-
init_esm_shims();
|
|
26
|
-
import { existsSync, readFileSync, appendFileSync } from "fs";
|
|
27
|
-
import { homedir } from "os";
|
|
28
|
-
import { join } from "path";
|
|
29
|
-
function detectShell() {
|
|
30
|
-
const shell = process.env.SHELL || "";
|
|
31
|
-
if (shell.includes("zsh")) return "zsh";
|
|
32
|
-
if (shell.includes("bash")) return "bash";
|
|
33
|
-
if (shell.includes("fish")) return "fish";
|
|
34
|
-
return "unknown";
|
|
35
|
-
}
|
|
36
|
-
function getShellRcFile(shell) {
|
|
37
|
-
const home = homedir();
|
|
38
|
-
switch (shell) {
|
|
39
|
-
case "zsh":
|
|
40
|
-
return join(home, ".zshrc");
|
|
41
|
-
case "bash":
|
|
42
|
-
const bashrc = join(home, ".bashrc");
|
|
43
|
-
if (existsSync(bashrc)) return bashrc;
|
|
44
|
-
return join(home, ".bash_profile");
|
|
45
|
-
case "fish":
|
|
46
|
-
return join(home, ".config", "fish", "config.fish");
|
|
47
|
-
default:
|
|
48
|
-
return null;
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
var ALIAS_LINE = 'alias pan="panopticon"';
|
|
52
|
-
var ALIAS_MARKER = "# Panopticon CLI alias";
|
|
53
|
-
function hasAlias(rcFile) {
|
|
54
|
-
if (!existsSync(rcFile)) return false;
|
|
55
|
-
const content = readFileSync(rcFile, "utf8");
|
|
56
|
-
return content.includes(ALIAS_MARKER) || content.includes(ALIAS_LINE);
|
|
57
|
-
}
|
|
58
|
-
function addAlias(rcFile) {
|
|
59
|
-
if (hasAlias(rcFile)) return;
|
|
60
|
-
const aliasBlock = `
|
|
61
|
-
${ALIAS_MARKER}
|
|
62
|
-
${ALIAS_LINE}
|
|
63
|
-
`;
|
|
64
|
-
appendFileSync(rcFile, aliasBlock, "utf8");
|
|
65
|
-
}
|
|
66
|
-
function getAliasInstructions(shell) {
|
|
67
|
-
const rcFile = getShellRcFile(shell);
|
|
68
|
-
if (!rcFile) {
|
|
69
|
-
return `Add this to your shell config:
|
|
70
|
-
${ALIAS_LINE}`;
|
|
71
|
-
}
|
|
72
|
-
return `Alias added to ${rcFile}. Run:
|
|
73
|
-
source ${rcFile}`;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
// src/lib/backup.ts
|
|
77
|
-
init_esm_shims();
|
|
78
|
-
init_paths();
|
|
79
|
-
import { existsSync as existsSync2, mkdirSync, readdirSync, cpSync, rmSync, lstatSync } from "fs";
|
|
80
|
-
import { join as join2, basename } from "path";
|
|
81
|
-
function createBackupTimestamp() {
|
|
82
|
-
return (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
83
|
-
}
|
|
84
|
-
function createBackup(sourceDirs) {
|
|
85
|
-
const timestamp = createBackupTimestamp();
|
|
86
|
-
const backupPath = join2(BACKUPS_DIR, timestamp);
|
|
87
|
-
mkdirSync(backupPath, { recursive: true });
|
|
88
|
-
const targets = [];
|
|
89
|
-
for (const sourceDir of sourceDirs) {
|
|
90
|
-
if (!existsSync2(sourceDir)) continue;
|
|
91
|
-
const targetName = basename(sourceDir);
|
|
92
|
-
const targetPath = join2(backupPath, targetName);
|
|
93
|
-
cpSync(sourceDir, targetPath, {
|
|
94
|
-
recursive: true,
|
|
95
|
-
filter: (src) => !lstatSync(src).isSymbolicLink()
|
|
96
|
-
});
|
|
97
|
-
targets.push(targetName);
|
|
98
|
-
}
|
|
99
|
-
return {
|
|
100
|
-
timestamp,
|
|
101
|
-
path: backupPath,
|
|
102
|
-
targets
|
|
103
|
-
};
|
|
104
|
-
}
|
|
105
|
-
function listBackups() {
|
|
106
|
-
if (!existsSync2(BACKUPS_DIR)) return [];
|
|
107
|
-
const entries = readdirSync(BACKUPS_DIR, { withFileTypes: true });
|
|
108
|
-
return entries.filter((e) => e.isDirectory()).map((e) => {
|
|
109
|
-
const backupPath = join2(BACKUPS_DIR, e.name);
|
|
110
|
-
const contents = readdirSync(backupPath);
|
|
111
|
-
return {
|
|
112
|
-
timestamp: e.name,
|
|
113
|
-
path: backupPath,
|
|
114
|
-
targets: contents
|
|
115
|
-
};
|
|
116
|
-
}).sort((a, b) => b.timestamp.localeCompare(a.timestamp));
|
|
117
|
-
}
|
|
118
|
-
function restoreBackup(timestamp, targetDirs) {
|
|
119
|
-
const backupPath = join2(BACKUPS_DIR, timestamp);
|
|
120
|
-
if (!existsSync2(backupPath)) {
|
|
121
|
-
throw new Error(`Backup not found: ${timestamp}`);
|
|
122
|
-
}
|
|
123
|
-
const contents = readdirSync(backupPath, { withFileTypes: true });
|
|
124
|
-
for (const entry of contents) {
|
|
125
|
-
if (!entry.isDirectory()) continue;
|
|
126
|
-
const sourcePath = join2(backupPath, entry.name);
|
|
127
|
-
const targetPath = targetDirs[entry.name];
|
|
128
|
-
if (!targetPath) continue;
|
|
129
|
-
if (existsSync2(targetPath)) {
|
|
130
|
-
rmSync(targetPath, { recursive: true });
|
|
131
|
-
}
|
|
132
|
-
cpSync(sourcePath, targetPath, { recursive: true });
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
function cleanOldBackups(keepCount = 10) {
|
|
136
|
-
const backups = listBackups();
|
|
137
|
-
if (backups.length <= keepCount) return 0;
|
|
138
|
-
const toRemove = backups.slice(keepCount);
|
|
139
|
-
let removed = 0;
|
|
140
|
-
for (const backup of toRemove) {
|
|
141
|
-
rmSync(backup.path, { recursive: true });
|
|
142
|
-
removed++;
|
|
143
|
-
}
|
|
144
|
-
return removed;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
// src/lib/sync.ts
|
|
148
|
-
init_esm_shims();
|
|
149
|
-
init_paths();
|
|
150
|
-
import { existsSync as existsSync3, mkdirSync as mkdirSync2, readdirSync as readdirSync2, symlinkSync, unlinkSync, lstatSync as lstatSync2, readlinkSync, rmSync as rmSync2, copyFileSync, chmodSync } from "fs";
|
|
151
|
-
import { join as join3 } from "path";
|
|
152
|
-
function removeTarget(targetPath) {
|
|
153
|
-
const stats = lstatSync2(targetPath);
|
|
154
|
-
if (stats.isDirectory() && !stats.isSymbolicLink()) {
|
|
155
|
-
rmSync2(targetPath, { recursive: true, force: true });
|
|
156
|
-
} else {
|
|
157
|
-
unlinkSync(targetPath);
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
function isPanopticonSymlink(targetPath) {
|
|
161
|
-
if (!existsSync3(targetPath)) return false;
|
|
162
|
-
try {
|
|
163
|
-
const stats = lstatSync2(targetPath);
|
|
164
|
-
if (!stats.isSymbolicLink()) return false;
|
|
165
|
-
const linkTarget = readlinkSync(targetPath);
|
|
166
|
-
return linkTarget.includes(".panopticon");
|
|
167
|
-
} catch {
|
|
168
|
-
return false;
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
function planSync(runtime) {
|
|
172
|
-
const targets = SYNC_TARGETS[runtime];
|
|
173
|
-
if (!targets) {
|
|
174
|
-
throw new Error(`Unknown sync target "${runtime}". Valid targets: ${Object.keys(SYNC_TARGETS).join(", ")}`);
|
|
175
|
-
}
|
|
176
|
-
const plan = {
|
|
177
|
-
runtime,
|
|
178
|
-
skills: [],
|
|
179
|
-
commands: [],
|
|
180
|
-
agents: [],
|
|
181
|
-
devSkills: []
|
|
182
|
-
};
|
|
183
|
-
if (existsSync3(SKILLS_DIR)) {
|
|
184
|
-
const skills = readdirSync2(SKILLS_DIR, { withFileTypes: true }).filter((d) => d.isDirectory());
|
|
185
|
-
for (const skill of skills) {
|
|
186
|
-
const sourcePath = join3(SKILLS_DIR, skill.name);
|
|
187
|
-
const targetPath = join3(targets.skills, skill.name);
|
|
188
|
-
let status = "new";
|
|
189
|
-
if (existsSync3(targetPath)) {
|
|
190
|
-
if (isPanopticonSymlink(targetPath)) {
|
|
191
|
-
status = "symlink";
|
|
192
|
-
} else {
|
|
193
|
-
status = "conflict";
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
plan.skills.push({ name: skill.name, sourcePath, targetPath, status });
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
if (isDevMode() && existsSync3(SOURCE_DEV_SKILLS_DIR)) {
|
|
200
|
-
const devSkills = readdirSync2(SOURCE_DEV_SKILLS_DIR, { withFileTypes: true }).filter((d) => d.isDirectory());
|
|
201
|
-
for (const skill of devSkills) {
|
|
202
|
-
const sourcePath = join3(SOURCE_DEV_SKILLS_DIR, skill.name);
|
|
203
|
-
const targetPath = join3(targets.skills, skill.name);
|
|
204
|
-
let status = "new";
|
|
205
|
-
if (existsSync3(targetPath)) {
|
|
206
|
-
if (isPanopticonSymlink(targetPath)) {
|
|
207
|
-
status = "symlink";
|
|
208
|
-
} else {
|
|
209
|
-
status = "conflict";
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
plan.devSkills.push({ name: skill.name, sourcePath, targetPath, status });
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
if (existsSync3(COMMANDS_DIR)) {
|
|
216
|
-
const commands = readdirSync2(COMMANDS_DIR, { withFileTypes: true }).filter((d) => d.isDirectory());
|
|
217
|
-
for (const cmd of commands) {
|
|
218
|
-
const sourcePath = join3(COMMANDS_DIR, cmd.name);
|
|
219
|
-
const targetPath = join3(targets.commands, cmd.name);
|
|
220
|
-
let status = "new";
|
|
221
|
-
if (existsSync3(targetPath)) {
|
|
222
|
-
if (isPanopticonSymlink(targetPath)) {
|
|
223
|
-
status = "symlink";
|
|
224
|
-
} else {
|
|
225
|
-
status = "conflict";
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
plan.commands.push({ name: cmd.name, sourcePath, targetPath, status });
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
if (existsSync3(AGENTS_DIR)) {
|
|
232
|
-
const agents = readdirSync2(AGENTS_DIR, { withFileTypes: true }).filter((entry) => entry.isFile() && entry.name.endsWith(".md"));
|
|
233
|
-
for (const agent of agents) {
|
|
234
|
-
const sourcePath = join3(AGENTS_DIR, agent.name);
|
|
235
|
-
const targetPath = join3(targets.agents, agent.name);
|
|
236
|
-
let status = "new";
|
|
237
|
-
if (existsSync3(targetPath)) {
|
|
238
|
-
if (isPanopticonSymlink(targetPath)) {
|
|
239
|
-
status = "symlink";
|
|
240
|
-
} else {
|
|
241
|
-
status = "conflict";
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
plan.agents.push({ name: agent.name, sourcePath, targetPath, status });
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
return plan;
|
|
248
|
-
}
|
|
249
|
-
function executeSync(runtime, options = {}) {
|
|
250
|
-
const targets = SYNC_TARGETS[runtime];
|
|
251
|
-
if (!targets) {
|
|
252
|
-
throw new Error(`Unknown sync target "${runtime}". Valid targets: ${Object.keys(SYNC_TARGETS).join(", ")}`);
|
|
253
|
-
}
|
|
254
|
-
const plan = planSync(runtime);
|
|
255
|
-
const result = {
|
|
256
|
-
created: [],
|
|
257
|
-
skipped: [],
|
|
258
|
-
conflicts: []
|
|
259
|
-
};
|
|
260
|
-
mkdirSync2(targets.skills, { recursive: true });
|
|
261
|
-
mkdirSync2(targets.commands, { recursive: true });
|
|
262
|
-
mkdirSync2(targets.agents, { recursive: true });
|
|
263
|
-
for (const item of plan.skills) {
|
|
264
|
-
if (options.dryRun) {
|
|
265
|
-
if (item.status === "new" || item.status === "symlink") {
|
|
266
|
-
result.created.push(item.name);
|
|
267
|
-
} else {
|
|
268
|
-
result.conflicts.push(item.name);
|
|
269
|
-
}
|
|
270
|
-
continue;
|
|
271
|
-
}
|
|
272
|
-
if (item.status === "conflict" && !options.force) {
|
|
273
|
-
result.conflicts.push(item.name);
|
|
274
|
-
continue;
|
|
275
|
-
}
|
|
276
|
-
if (existsSync3(item.targetPath)) {
|
|
277
|
-
removeTarget(item.targetPath);
|
|
278
|
-
}
|
|
279
|
-
symlinkSync(item.sourcePath, item.targetPath);
|
|
280
|
-
result.created.push(item.name);
|
|
281
|
-
}
|
|
282
|
-
for (const item of plan.commands) {
|
|
283
|
-
if (options.dryRun) {
|
|
284
|
-
if (item.status === "new" || item.status === "symlink") {
|
|
285
|
-
result.created.push(item.name);
|
|
286
|
-
} else {
|
|
287
|
-
result.conflicts.push(item.name);
|
|
288
|
-
}
|
|
289
|
-
continue;
|
|
290
|
-
}
|
|
291
|
-
if (item.status === "conflict" && !options.force) {
|
|
292
|
-
result.conflicts.push(item.name);
|
|
293
|
-
continue;
|
|
294
|
-
}
|
|
295
|
-
if (existsSync3(item.targetPath)) {
|
|
296
|
-
removeTarget(item.targetPath);
|
|
297
|
-
}
|
|
298
|
-
symlinkSync(item.sourcePath, item.targetPath);
|
|
299
|
-
result.created.push(item.name);
|
|
300
|
-
}
|
|
301
|
-
for (const item of plan.agents) {
|
|
302
|
-
if (options.dryRun) {
|
|
303
|
-
if (item.status === "new" || item.status === "symlink") {
|
|
304
|
-
result.created.push(item.name);
|
|
305
|
-
} else {
|
|
306
|
-
result.conflicts.push(item.name);
|
|
307
|
-
}
|
|
308
|
-
continue;
|
|
309
|
-
}
|
|
310
|
-
if (item.status === "conflict" && !options.force) {
|
|
311
|
-
result.conflicts.push(item.name);
|
|
312
|
-
continue;
|
|
313
|
-
}
|
|
314
|
-
if (existsSync3(item.targetPath)) {
|
|
315
|
-
removeTarget(item.targetPath);
|
|
316
|
-
}
|
|
317
|
-
symlinkSync(item.sourcePath, item.targetPath);
|
|
318
|
-
result.created.push(item.name);
|
|
319
|
-
}
|
|
320
|
-
for (const item of plan.devSkills) {
|
|
321
|
-
if (options.dryRun) {
|
|
322
|
-
if (item.status === "new" || item.status === "symlink") {
|
|
323
|
-
result.created.push(`${item.name} (dev)`);
|
|
324
|
-
} else {
|
|
325
|
-
result.conflicts.push(`${item.name} (dev)`);
|
|
326
|
-
}
|
|
327
|
-
continue;
|
|
328
|
-
}
|
|
329
|
-
if (item.status === "conflict" && !options.force) {
|
|
330
|
-
result.conflicts.push(`${item.name} (dev)`);
|
|
331
|
-
continue;
|
|
332
|
-
}
|
|
333
|
-
if (existsSync3(item.targetPath)) {
|
|
334
|
-
removeTarget(item.targetPath);
|
|
335
|
-
}
|
|
336
|
-
symlinkSync(item.sourcePath, item.targetPath);
|
|
337
|
-
result.created.push(`${item.name} (dev)`);
|
|
338
|
-
}
|
|
339
|
-
return result;
|
|
340
|
-
}
|
|
341
|
-
function planHooksSync() {
|
|
342
|
-
const hooks = [];
|
|
343
|
-
if (!existsSync3(SOURCE_SCRIPTS_DIR)) {
|
|
344
|
-
return hooks;
|
|
345
|
-
}
|
|
346
|
-
const scripts = readdirSync2(SOURCE_SCRIPTS_DIR, { withFileTypes: true }).filter((entry) => entry.isFile() && !entry.name.startsWith(".") && (!entry.name.includes(".") || entry.name.endsWith(".js")));
|
|
347
|
-
for (const script of scripts) {
|
|
348
|
-
const sourcePath = join3(SOURCE_SCRIPTS_DIR, script.name);
|
|
349
|
-
const targetPath = join3(BIN_DIR, script.name);
|
|
350
|
-
let status = "new";
|
|
351
|
-
if (existsSync3(targetPath)) {
|
|
352
|
-
status = "updated";
|
|
353
|
-
}
|
|
354
|
-
hooks.push({ name: script.name, sourcePath, targetPath, status });
|
|
355
|
-
}
|
|
356
|
-
return hooks;
|
|
357
|
-
}
|
|
358
|
-
function syncHooks() {
|
|
359
|
-
const result = { synced: [], errors: [] };
|
|
360
|
-
mkdirSync2(BIN_DIR, { recursive: true });
|
|
361
|
-
const hooks = planHooksSync();
|
|
362
|
-
for (const hook of hooks) {
|
|
363
|
-
try {
|
|
364
|
-
copyFileSync(hook.sourcePath, hook.targetPath);
|
|
365
|
-
chmodSync(hook.targetPath, 493);
|
|
366
|
-
result.synced.push(hook.name);
|
|
367
|
-
} catch (error) {
|
|
368
|
-
result.errors.push(`${hook.name}: ${error}`);
|
|
369
|
-
}
|
|
370
|
-
}
|
|
371
|
-
return result;
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
// src/lib/tracker/linking.ts
|
|
375
|
-
init_esm_shims();
|
|
376
|
-
import { readFileSync as readFileSync2, writeFileSync, existsSync as existsSync4, mkdirSync as mkdirSync3 } from "fs";
|
|
377
|
-
import { join as join4 } from "path";
|
|
378
|
-
import { homedir as homedir2 } from "os";
|
|
379
|
-
function parseIssueRef(ref) {
|
|
380
|
-
if (ref.startsWith("github#")) {
|
|
381
|
-
return { tracker: "github", ref: `#${ref.slice(7)}` };
|
|
382
|
-
}
|
|
383
|
-
if (ref.startsWith("gitlab#")) {
|
|
384
|
-
return { tracker: "gitlab", ref: `#${ref.slice(7)}` };
|
|
385
|
-
}
|
|
386
|
-
if (ref.startsWith("linear:")) {
|
|
387
|
-
return { tracker: "linear", ref: ref.slice(7) };
|
|
388
|
-
}
|
|
389
|
-
if (/^#\d+$/.test(ref)) {
|
|
390
|
-
return { tracker: "github", ref };
|
|
391
|
-
}
|
|
392
|
-
if (/^[A-Z]+-\d+$/i.test(ref)) {
|
|
393
|
-
return { tracker: "linear", ref: ref.toUpperCase() };
|
|
394
|
-
}
|
|
395
|
-
return null;
|
|
396
|
-
}
|
|
397
|
-
function formatIssueRef(ref, tracker) {
|
|
398
|
-
if (tracker === "github") {
|
|
399
|
-
return ref.startsWith("#") ? `github${ref}` : `github#${ref}`;
|
|
400
|
-
}
|
|
401
|
-
if (tracker === "gitlab") {
|
|
402
|
-
return ref.startsWith("#") ? `gitlab${ref}` : `gitlab#${ref}`;
|
|
403
|
-
}
|
|
404
|
-
return ref;
|
|
405
|
-
}
|
|
406
|
-
var LinkManager = class {
|
|
407
|
-
storePath;
|
|
408
|
-
store;
|
|
409
|
-
constructor(storePath) {
|
|
410
|
-
this.storePath = storePath ?? join4(homedir2(), ".panopticon", "links.json");
|
|
411
|
-
this.store = this.load();
|
|
412
|
-
}
|
|
413
|
-
load() {
|
|
414
|
-
if (existsSync4(this.storePath)) {
|
|
415
|
-
try {
|
|
416
|
-
const data = JSON.parse(readFileSync2(this.storePath, "utf-8"));
|
|
417
|
-
if (data.version === 1) {
|
|
418
|
-
return data;
|
|
419
|
-
}
|
|
420
|
-
} catch {
|
|
421
|
-
}
|
|
422
|
-
}
|
|
423
|
-
return { version: 1, links: [] };
|
|
424
|
-
}
|
|
425
|
-
save() {
|
|
426
|
-
const dir = join4(this.storePath, "..");
|
|
427
|
-
if (!existsSync4(dir)) {
|
|
428
|
-
mkdirSync3(dir, { recursive: true });
|
|
429
|
-
}
|
|
430
|
-
writeFileSync(this.storePath, JSON.stringify(this.store, null, 2));
|
|
431
|
-
}
|
|
432
|
-
/**
|
|
433
|
-
* Add a link between two issues
|
|
434
|
-
*/
|
|
435
|
-
addLink(source, target, direction = "related") {
|
|
436
|
-
const existing = this.store.links.find(
|
|
437
|
-
(l) => l.sourceIssueRef === source.ref && l.sourceTracker === source.tracker && l.targetIssueRef === target.ref && l.targetTracker === target.tracker
|
|
438
|
-
);
|
|
439
|
-
if (existing) {
|
|
440
|
-
if (existing.direction !== direction) {
|
|
441
|
-
existing.direction = direction;
|
|
442
|
-
this.save();
|
|
443
|
-
}
|
|
444
|
-
return existing;
|
|
445
|
-
}
|
|
446
|
-
const link = {
|
|
447
|
-
sourceIssueRef: source.ref,
|
|
448
|
-
sourceTracker: source.tracker,
|
|
449
|
-
targetIssueRef: target.ref,
|
|
450
|
-
targetTracker: target.tracker,
|
|
451
|
-
direction,
|
|
452
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
453
|
-
};
|
|
454
|
-
this.store.links.push(link);
|
|
455
|
-
this.save();
|
|
456
|
-
return link;
|
|
457
|
-
}
|
|
458
|
-
/**
|
|
459
|
-
* Remove a link between two issues
|
|
460
|
-
*/
|
|
461
|
-
removeLink(source, target) {
|
|
462
|
-
const index = this.store.links.findIndex(
|
|
463
|
-
(l) => l.sourceIssueRef === source.ref && l.sourceTracker === source.tracker && l.targetIssueRef === target.ref && l.targetTracker === target.tracker
|
|
464
|
-
);
|
|
465
|
-
if (index >= 0) {
|
|
466
|
-
this.store.links.splice(index, 1);
|
|
467
|
-
this.save();
|
|
468
|
-
return true;
|
|
469
|
-
}
|
|
470
|
-
return false;
|
|
471
|
-
}
|
|
472
|
-
/**
|
|
473
|
-
* Get all issues linked to a given issue
|
|
474
|
-
*/
|
|
475
|
-
getLinkedIssues(ref, tracker) {
|
|
476
|
-
return this.store.links.filter(
|
|
477
|
-
(l) => l.sourceIssueRef === ref && l.sourceTracker === tracker || l.targetIssueRef === ref && l.targetTracker === tracker
|
|
478
|
-
);
|
|
479
|
-
}
|
|
480
|
-
/**
|
|
481
|
-
* Get all links (for debugging/admin)
|
|
482
|
-
*/
|
|
483
|
-
getAllLinks() {
|
|
484
|
-
return [...this.store.links];
|
|
485
|
-
}
|
|
486
|
-
/**
|
|
487
|
-
* Find linked issue in another tracker
|
|
488
|
-
*/
|
|
489
|
-
findLinkedIssue(ref, sourceTracker, targetTracker) {
|
|
490
|
-
const asSource = this.store.links.find(
|
|
491
|
-
(l) => l.sourceIssueRef === ref && l.sourceTracker === sourceTracker && l.targetTracker === targetTracker
|
|
492
|
-
);
|
|
493
|
-
if (asSource) return asSource.targetIssueRef;
|
|
494
|
-
const asTarget = this.store.links.find(
|
|
495
|
-
(l) => l.targetIssueRef === ref && l.targetTracker === sourceTracker && l.sourceTracker === targetTracker
|
|
496
|
-
);
|
|
497
|
-
if (asTarget) return asTarget.sourceIssueRef;
|
|
498
|
-
return null;
|
|
499
|
-
}
|
|
500
|
-
/**
|
|
501
|
-
* Clear all links (for testing)
|
|
502
|
-
*/
|
|
503
|
-
clear() {
|
|
504
|
-
this.store.links = [];
|
|
505
|
-
this.save();
|
|
506
|
-
}
|
|
507
|
-
};
|
|
508
|
-
var _linkManager = null;
|
|
509
|
-
function getLinkManager() {
|
|
510
|
-
if (!_linkManager) {
|
|
511
|
-
_linkManager = new LinkManager();
|
|
512
|
-
}
|
|
513
|
-
return _linkManager;
|
|
514
|
-
}
|
|
515
|
-
|
|
516
|
-
// src/lib/tracker/index.ts
|
|
517
|
-
init_esm_shims();
|
|
518
|
-
init_interface();
|
|
519
|
-
init_linear();
|
|
520
|
-
init_github();
|
|
521
|
-
init_gitlab();
|
|
522
|
-
init_factory();
|
|
523
|
-
|
|
524
|
-
export {
|
|
525
|
-
detectShell,
|
|
526
|
-
getShellRcFile,
|
|
527
|
-
hasAlias,
|
|
528
|
-
addAlias,
|
|
529
|
-
getAliasInstructions,
|
|
530
|
-
createBackupTimestamp,
|
|
531
|
-
createBackup,
|
|
532
|
-
listBackups,
|
|
533
|
-
restoreBackup,
|
|
534
|
-
cleanOldBackups,
|
|
535
|
-
isPanopticonSymlink,
|
|
536
|
-
planSync,
|
|
537
|
-
executeSync,
|
|
538
|
-
planHooksSync,
|
|
539
|
-
syncHooks,
|
|
540
|
-
parseIssueRef,
|
|
541
|
-
formatIssueRef,
|
|
542
|
-
LinkManager,
|
|
543
|
-
getLinkManager
|
|
544
|
-
};
|
|
545
|
-
//# sourceMappingURL=chunk-ELK6Q7QI.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/lib/shell.ts","../src/lib/backup.ts","../src/lib/sync.ts","../src/lib/tracker/linking.ts","../src/lib/tracker/index.ts"],"sourcesContent":["import { existsSync, readFileSync, appendFileSync } from 'fs';\nimport { homedir } from 'os';\nimport { join } from 'path';\n\nexport type Shell = 'bash' | 'zsh' | 'fish' | 'unknown';\n\nexport function detectShell(): Shell {\n const shell = process.env.SHELL || '';\n\n if (shell.includes('zsh')) return 'zsh';\n if (shell.includes('bash')) return 'bash';\n if (shell.includes('fish')) return 'fish';\n\n return 'unknown';\n}\n\nexport function getShellRcFile(shell: Shell): string | null {\n const home = homedir();\n\n switch (shell) {\n case 'zsh':\n return join(home, '.zshrc');\n case 'bash':\n // Prefer .bashrc, fall back to .bash_profile\n const bashrc = join(home, '.bashrc');\n if (existsSync(bashrc)) return bashrc;\n return join(home, '.bash_profile');\n case 'fish':\n return join(home, '.config', 'fish', 'config.fish');\n default:\n return null;\n }\n}\n\nconst ALIAS_LINE = 'alias pan=\"panopticon\"';\nconst ALIAS_MARKER = '# Panopticon CLI alias';\n\nexport function hasAlias(rcFile: string): boolean {\n if (!existsSync(rcFile)) return false;\n\n const content = readFileSync(rcFile, 'utf8');\n return content.includes(ALIAS_MARKER) || content.includes(ALIAS_LINE);\n}\n\nexport function addAlias(rcFile: string): void {\n if (hasAlias(rcFile)) return;\n\n const aliasBlock = `\n${ALIAS_MARKER}\n${ALIAS_LINE}\n`;\n\n appendFileSync(rcFile, aliasBlock, 'utf8');\n}\n\nexport function getAliasInstructions(shell: Shell): string {\n const rcFile = getShellRcFile(shell);\n\n if (!rcFile) {\n return `Add this to your shell config:\\n ${ALIAS_LINE}`;\n }\n\n return `Alias added to ${rcFile}. Run:\\n source ${rcFile}`;\n}\n","import { existsSync, mkdirSync, readdirSync, cpSync, rmSync, lstatSync } from 'fs';\nimport { join, basename } from 'path';\nimport { BACKUPS_DIR } from './paths.js';\n\nexport interface BackupInfo {\n timestamp: string;\n path: string;\n targets: string[];\n}\n\nexport function createBackupTimestamp(): string {\n return new Date().toISOString().replace(/[:.]/g, '-');\n}\n\nexport function createBackup(sourceDirs: string[]): BackupInfo {\n const timestamp = createBackupTimestamp();\n const backupPath = join(BACKUPS_DIR, timestamp);\n\n mkdirSync(backupPath, { recursive: true });\n\n const targets: string[] = [];\n\n for (const sourceDir of sourceDirs) {\n if (!existsSync(sourceDir)) continue;\n\n const targetName = basename(sourceDir);\n const targetPath = join(backupPath, targetName);\n\n // Use filter to skip symlinks — sync targets (e.g. ~/.claude/skills/)\n // contain symlinks back into ~/.panopticon/skills/ which causes cpSync\n // to fail with \"cannot copy to a subdirectory of self\".\n cpSync(sourceDir, targetPath, {\n recursive: true,\n filter: (src) => !lstatSync(src).isSymbolicLink(),\n });\n targets.push(targetName);\n }\n\n return {\n timestamp,\n path: backupPath,\n targets,\n };\n}\n\nexport function listBackups(): BackupInfo[] {\n if (!existsSync(BACKUPS_DIR)) return [];\n\n const entries = readdirSync(BACKUPS_DIR, { withFileTypes: true });\n\n return entries\n .filter((e) => e.isDirectory())\n .map((e) => {\n const backupPath = join(BACKUPS_DIR, e.name);\n const contents = readdirSync(backupPath);\n\n return {\n timestamp: e.name,\n path: backupPath,\n targets: contents,\n };\n })\n .sort((a, b) => b.timestamp.localeCompare(a.timestamp));\n}\n\nexport function restoreBackup(timestamp: string, targetDirs: Record<string, string>): void {\n const backupPath = join(BACKUPS_DIR, timestamp);\n\n if (!existsSync(backupPath)) {\n throw new Error(`Backup not found: ${timestamp}`);\n }\n\n const contents = readdirSync(backupPath, { withFileTypes: true });\n\n for (const entry of contents) {\n if (!entry.isDirectory()) continue;\n\n const sourcePath = join(backupPath, entry.name);\n const targetPath = targetDirs[entry.name];\n\n if (!targetPath) continue;\n\n // Remove existing and restore from backup\n if (existsSync(targetPath)) {\n rmSync(targetPath, { recursive: true });\n }\n\n cpSync(sourcePath, targetPath, { recursive: true });\n }\n}\n\nexport function cleanOldBackups(keepCount: number = 10): number {\n const backups = listBackups();\n\n if (backups.length <= keepCount) return 0;\n\n const toRemove = backups.slice(keepCount);\n let removed = 0;\n\n for (const backup of toRemove) {\n rmSync(backup.path, { recursive: true });\n removed++;\n }\n\n return removed;\n}\n","import { existsSync, mkdirSync, readdirSync, symlinkSync, unlinkSync, lstatSync, readlinkSync, rmSync, copyFileSync, chmodSync } from 'fs';\nimport { join, basename } from 'path';\nimport { SKILLS_DIR, COMMANDS_DIR, AGENTS_DIR, BIN_DIR, SOURCE_SCRIPTS_DIR, SOURCE_DEV_SKILLS_DIR, SYNC_TARGETS, isDevMode, type Runtime } from './paths.js';\n\nexport interface SyncItem {\n name: string;\n sourcePath: string;\n targetPath: string;\n status: 'new' | 'exists' | 'conflict' | 'symlink';\n}\n\nexport interface SyncPlan {\n runtime: Runtime;\n skills: SyncItem[];\n commands: SyncItem[];\n agents: SyncItem[];\n devSkills: SyncItem[]; // Developer-only skills (only synced in dev mode)\n}\n\n/**\n * Remove a file, symlink, or directory safely\n */\nfunction removeTarget(targetPath: string): void {\n const stats = lstatSync(targetPath);\n if (stats.isDirectory() && !stats.isSymbolicLink()) {\n // It's a real directory, remove recursively\n rmSync(targetPath, { recursive: true, force: true });\n } else {\n // It's a file or symlink\n unlinkSync(targetPath);\n }\n}\n\n/**\n * Check if a path is a Panopticon-managed symlink\n */\nexport function isPanopticonSymlink(targetPath: string): boolean {\n if (!existsSync(targetPath)) return false;\n\n try {\n const stats = lstatSync(targetPath);\n if (!stats.isSymbolicLink()) return false;\n\n const linkTarget = readlinkSync(targetPath);\n // It's ours if it points to our skills/commands dir\n return linkTarget.includes('.panopticon');\n } catch {\n return false;\n }\n}\n\n/**\n * Plan what would be synced (dry run)\n */\nexport function planSync(runtime: Runtime): SyncPlan {\n const targets = SYNC_TARGETS[runtime];\n if (!targets) {\n throw new Error(`Unknown sync target \"${runtime}\". Valid targets: ${Object.keys(SYNC_TARGETS).join(', ')}`);\n }\n const plan: SyncPlan = {\n runtime,\n skills: [],\n commands: [],\n agents: [],\n devSkills: [],\n };\n\n // Plan skills sync\n if (existsSync(SKILLS_DIR)) {\n const skills = readdirSync(SKILLS_DIR, { withFileTypes: true })\n .filter((d) => d.isDirectory());\n\n for (const skill of skills) {\n const sourcePath = join(SKILLS_DIR, skill.name);\n const targetPath = join(targets.skills, skill.name);\n\n let status: SyncItem['status'] = 'new';\n\n if (existsSync(targetPath)) {\n if (isPanopticonSymlink(targetPath)) {\n status = 'symlink'; // Already managed by us\n } else {\n status = 'conflict'; // User content exists\n }\n }\n\n plan.skills.push({ name: skill.name, sourcePath, targetPath, status });\n }\n }\n\n // Plan dev-skills sync (only in dev mode)\n if (isDevMode() && existsSync(SOURCE_DEV_SKILLS_DIR)) {\n const devSkills = readdirSync(SOURCE_DEV_SKILLS_DIR, { withFileTypes: true })\n .filter((d) => d.isDirectory());\n\n for (const skill of devSkills) {\n const sourcePath = join(SOURCE_DEV_SKILLS_DIR, skill.name);\n const targetPath = join(targets.skills, skill.name);\n\n let status: SyncItem['status'] = 'new';\n\n if (existsSync(targetPath)) {\n if (isPanopticonSymlink(targetPath)) {\n status = 'symlink'; // Already managed by us\n } else {\n status = 'conflict'; // User content exists\n }\n }\n\n plan.devSkills.push({ name: skill.name, sourcePath, targetPath, status });\n }\n }\n\n // Plan commands sync\n if (existsSync(COMMANDS_DIR)) {\n const commands = readdirSync(COMMANDS_DIR, { withFileTypes: true })\n .filter((d) => d.isDirectory());\n\n for (const cmd of commands) {\n const sourcePath = join(COMMANDS_DIR, cmd.name);\n const targetPath = join(targets.commands, cmd.name);\n\n let status: SyncItem['status'] = 'new';\n\n if (existsSync(targetPath)) {\n if (isPanopticonSymlink(targetPath)) {\n status = 'symlink';\n } else {\n status = 'conflict';\n }\n }\n\n plan.commands.push({ name: cmd.name, sourcePath, targetPath, status });\n }\n }\n\n // Plan agents sync\n if (existsSync(AGENTS_DIR)) {\n const agents = readdirSync(AGENTS_DIR, { withFileTypes: true })\n .filter((entry) => entry.isFile() && entry.name.endsWith('.md'));\n\n for (const agent of agents) {\n const sourcePath = join(AGENTS_DIR, agent.name);\n const targetPath = join(targets.agents, agent.name);\n\n let status: SyncItem['status'] = 'new';\n\n if (existsSync(targetPath)) {\n if (isPanopticonSymlink(targetPath)) {\n status = 'symlink'; // Already managed by us\n } else {\n status = 'conflict'; // User content exists\n }\n }\n\n plan.agents.push({ name: agent.name, sourcePath, targetPath, status });\n }\n }\n\n return plan;\n}\n\nexport interface SyncOptions {\n force?: boolean;\n dryRun?: boolean;\n}\n\nexport interface SyncResult {\n created: string[];\n skipped: string[];\n conflicts: string[];\n}\n\n/**\n * Execute sync for a runtime\n */\nexport function executeSync(runtime: Runtime, options: SyncOptions = {}): SyncResult {\n const targets = SYNC_TARGETS[runtime];\n if (!targets) {\n throw new Error(`Unknown sync target \"${runtime}\". Valid targets: ${Object.keys(SYNC_TARGETS).join(', ')}`);\n }\n const plan = planSync(runtime);\n const result: SyncResult = {\n created: [],\n skipped: [],\n conflicts: [],\n };\n\n // Ensure target directories exist\n mkdirSync(targets.skills, { recursive: true });\n mkdirSync(targets.commands, { recursive: true });\n mkdirSync(targets.agents, { recursive: true });\n\n // Process skills\n for (const item of plan.skills) {\n if (options.dryRun) {\n if (item.status === 'new' || item.status === 'symlink') {\n result.created.push(item.name);\n } else {\n result.conflicts.push(item.name);\n }\n continue;\n }\n\n if (item.status === 'conflict' && !options.force) {\n result.conflicts.push(item.name);\n continue;\n }\n\n // Remove existing if force or if it's our symlink\n if (existsSync(item.targetPath)) {\n removeTarget(item.targetPath);\n }\n\n // Create symlink\n symlinkSync(item.sourcePath, item.targetPath);\n result.created.push(item.name);\n }\n\n // Process commands\n for (const item of plan.commands) {\n if (options.dryRun) {\n if (item.status === 'new' || item.status === 'symlink') {\n result.created.push(item.name);\n } else {\n result.conflicts.push(item.name);\n }\n continue;\n }\n\n if (item.status === 'conflict' && !options.force) {\n result.conflicts.push(item.name);\n continue;\n }\n\n if (existsSync(item.targetPath)) {\n removeTarget(item.targetPath);\n }\n\n symlinkSync(item.sourcePath, item.targetPath);\n result.created.push(item.name);\n }\n\n // Process agents\n for (const item of plan.agents) {\n if (options.dryRun) {\n if (item.status === 'new' || item.status === 'symlink') {\n result.created.push(item.name);\n } else {\n result.conflicts.push(item.name);\n }\n continue;\n }\n\n if (item.status === 'conflict' && !options.force) {\n result.conflicts.push(item.name);\n continue;\n }\n\n if (existsSync(item.targetPath)) {\n removeTarget(item.targetPath);\n }\n\n symlinkSync(item.sourcePath, item.targetPath);\n result.created.push(item.name);\n }\n\n // Process dev-skills (only in dev mode)\n for (const item of plan.devSkills) {\n if (options.dryRun) {\n if (item.status === 'new' || item.status === 'symlink') {\n result.created.push(`${item.name} (dev)`);\n } else {\n result.conflicts.push(`${item.name} (dev)`);\n }\n continue;\n }\n\n if (item.status === 'conflict' && !options.force) {\n result.conflicts.push(`${item.name} (dev)`);\n continue;\n }\n\n if (existsSync(item.targetPath)) {\n removeTarget(item.targetPath);\n }\n\n symlinkSync(item.sourcePath, item.targetPath);\n result.created.push(`${item.name} (dev)`);\n }\n\n return result;\n}\n\n/**\n * Hook item for sync planning\n */\nexport interface HookItem {\n name: string;\n sourcePath: string;\n targetPath: string;\n status: 'new' | 'updated' | 'current';\n}\n\n/**\n * Plan hooks sync (checks what would be updated)\n */\nexport function planHooksSync(): HookItem[] {\n const hooks: HookItem[] = [];\n\n if (!existsSync(SOURCE_SCRIPTS_DIR)) {\n return hooks;\n }\n\n // Sync hook scripts (no extension) and bundled JS scripts (.js)\n // Skip source files (.ts), shell helpers (.sh), and other non-hook files (.mjs)\n const scripts = readdirSync(SOURCE_SCRIPTS_DIR, { withFileTypes: true })\n .filter((entry) => entry.isFile() && !entry.name.startsWith('.')\n && (!entry.name.includes('.') || entry.name.endsWith('.js')));\n\n for (const script of scripts) {\n const sourcePath = join(SOURCE_SCRIPTS_DIR, script.name);\n const targetPath = join(BIN_DIR, script.name);\n\n let status: HookItem['status'] = 'new';\n\n if (existsSync(targetPath)) {\n // Could compare file contents/timestamps here for 'current' vs 'updated'\n // For now, always update to ensure latest version\n status = 'updated';\n }\n\n hooks.push({ name: script.name, sourcePath, targetPath, status });\n }\n\n return hooks;\n}\n\n/**\n * Sync hooks (copy scripts to ~/.panopticon/bin/)\n */\nexport function syncHooks(): { synced: string[]; errors: string[] } {\n const result = { synced: [] as string[], errors: [] as string[] };\n\n // Ensure bin directory exists\n mkdirSync(BIN_DIR, { recursive: true });\n\n const hooks = planHooksSync();\n\n for (const hook of hooks) {\n try {\n copyFileSync(hook.sourcePath, hook.targetPath);\n chmodSync(hook.targetPath, 0o755); // Make executable\n result.synced.push(hook.name);\n } catch (error) {\n result.errors.push(`${hook.name}: ${error}`);\n }\n }\n\n return result;\n}\n","/**\n * Cross-Tracker Linking\n *\n * Manages links between issues in different trackers.\n * Links are stored in a local JSON file for persistence.\n */\n\nimport { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs';\nimport { join } from 'path';\nimport { homedir } from 'os';\nimport type { TrackerType } from './interface.js';\n\n// Link direction types\nexport type LinkDirection = 'blocks' | 'blocked_by' | 'related' | 'duplicate_of';\n\n// A single link between two issues\nexport interface TrackerLink {\n sourceIssueRef: string; // e.g., \"MIN-630\"\n sourceTracker: TrackerType;\n targetIssueRef: string; // e.g., \"#42\"\n targetTracker: TrackerType;\n direction: LinkDirection;\n createdAt: string; // ISO timestamp\n}\n\n// Storage format\ninterface LinkStore {\n version: 1;\n links: TrackerLink[];\n}\n\n/**\n * Parse an issue reference to extract tracker and ID\n * Examples:\n * \"#42\" -> { tracker: \"github\", ref: \"#42\" }\n * \"github#42\" -> { tracker: \"github\", ref: \"#42\" }\n * \"MIN-630\" -> { tracker: \"linear\", ref: \"MIN-630\" }\n * \"gitlab#15\" -> { tracker: \"gitlab\", ref: \"#15\" }\n */\nexport function parseIssueRef(ref: string): { tracker: TrackerType; ref: string } | null {\n // Explicit tracker prefix\n if (ref.startsWith('github#')) {\n return { tracker: 'github', ref: `#${ref.slice(7)}` };\n }\n if (ref.startsWith('gitlab#')) {\n return { tracker: 'gitlab', ref: `#${ref.slice(7)}` };\n }\n if (ref.startsWith('linear:')) {\n return { tracker: 'linear', ref: ref.slice(7) };\n }\n\n // GitHub-style refs (#number)\n if (/^#\\d+$/.test(ref)) {\n return { tracker: 'github', ref };\n }\n\n // Linear-style refs (XXX-123)\n if (/^[A-Z]+-\\d+$/i.test(ref)) {\n return { tracker: 'linear', ref: ref.toUpperCase() };\n }\n\n return null;\n}\n\n/**\n * Format an issue ref with tracker prefix for display\n */\nexport function formatIssueRef(ref: string, tracker: TrackerType): string {\n if (tracker === 'github') {\n return ref.startsWith('#') ? `github${ref}` : `github#${ref}`;\n }\n if (tracker === 'gitlab') {\n return ref.startsWith('#') ? `gitlab${ref}` : `gitlab#${ref}`;\n }\n return ref; // Linear refs are already unique\n}\n\n/**\n * Link Manager for cross-tracker issue linking\n */\nexport class LinkManager {\n private storePath: string;\n private store: LinkStore;\n\n constructor(storePath?: string) {\n this.storePath = storePath ?? join(homedir(), '.panopticon', 'links.json');\n this.store = this.load();\n }\n\n private load(): LinkStore {\n if (existsSync(this.storePath)) {\n try {\n const data = JSON.parse(readFileSync(this.storePath, 'utf-8'));\n if (data.version === 1) {\n return data;\n }\n } catch {\n // Fall through to default\n }\n }\n return { version: 1, links: [] };\n }\n\n private save(): void {\n const dir = join(this.storePath, '..');\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true });\n }\n writeFileSync(this.storePath, JSON.stringify(this.store, null, 2));\n }\n\n /**\n * Add a link between two issues\n */\n addLink(\n source: { ref: string; tracker: TrackerType },\n target: { ref: string; tracker: TrackerType },\n direction: LinkDirection = 'related'\n ): TrackerLink {\n // Check if link already exists\n const existing = this.store.links.find(\n (l) =>\n l.sourceIssueRef === source.ref &&\n l.sourceTracker === source.tracker &&\n l.targetIssueRef === target.ref &&\n l.targetTracker === target.tracker\n );\n\n if (existing) {\n // Update direction if different\n if (existing.direction !== direction) {\n existing.direction = direction;\n this.save();\n }\n return existing;\n }\n\n const link: TrackerLink = {\n sourceIssueRef: source.ref,\n sourceTracker: source.tracker,\n targetIssueRef: target.ref,\n targetTracker: target.tracker,\n direction,\n createdAt: new Date().toISOString(),\n };\n\n this.store.links.push(link);\n this.save();\n return link;\n }\n\n /**\n * Remove a link between two issues\n */\n removeLink(\n source: { ref: string; tracker: TrackerType },\n target: { ref: string; tracker: TrackerType }\n ): boolean {\n const index = this.store.links.findIndex(\n (l) =>\n l.sourceIssueRef === source.ref &&\n l.sourceTracker === source.tracker &&\n l.targetIssueRef === target.ref &&\n l.targetTracker === target.tracker\n );\n\n if (index >= 0) {\n this.store.links.splice(index, 1);\n this.save();\n return true;\n }\n return false;\n }\n\n /**\n * Get all issues linked to a given issue\n */\n getLinkedIssues(ref: string, tracker: TrackerType): TrackerLink[] {\n return this.store.links.filter(\n (l) =>\n (l.sourceIssueRef === ref && l.sourceTracker === tracker) ||\n (l.targetIssueRef === ref && l.targetTracker === tracker)\n );\n }\n\n /**\n * Get all links (for debugging/admin)\n */\n getAllLinks(): TrackerLink[] {\n return [...this.store.links];\n }\n\n /**\n * Find linked issue in another tracker\n */\n findLinkedIssue(\n ref: string,\n sourceTracker: TrackerType,\n targetTracker: TrackerType\n ): string | null {\n // Check as source\n const asSource = this.store.links.find(\n (l) =>\n l.sourceIssueRef === ref &&\n l.sourceTracker === sourceTracker &&\n l.targetTracker === targetTracker\n );\n if (asSource) return asSource.targetIssueRef;\n\n // Check as target\n const asTarget = this.store.links.find(\n (l) =>\n l.targetIssueRef === ref &&\n l.targetTracker === sourceTracker &&\n l.sourceTracker === targetTracker\n );\n if (asTarget) return asTarget.sourceIssueRef;\n\n return null;\n }\n\n /**\n * Clear all links (for testing)\n */\n clear(): void {\n this.store.links = [];\n this.save();\n }\n}\n\n// Singleton instance\nlet _linkManager: LinkManager | null = null;\n\nexport function getLinkManager(): LinkManager {\n if (!_linkManager) {\n _linkManager = new LinkManager();\n }\n return _linkManager;\n}\n","/**\n * Issue Tracker Module\n *\n * Provides a unified interface for different issue tracking systems.\n */\n\n// Core interface and types\nexport type {\n IssueTracker,\n Issue,\n IssueFilters,\n IssueState,\n IssueUpdate,\n NewIssue,\n Comment,\n TrackerType,\n} from './interface.js';\n\nexport {\n NotImplementedError,\n IssueNotFoundError,\n TrackerAuthError,\n} from './interface.js';\n\n// Tracker implementations\nexport { LinearTracker } from './linear.js';\nexport { GitHubTracker } from './github.js';\nexport { GitLabTracker } from './gitlab.js';\n\n// Factory functions\nexport type { TrackerConfig } from './factory.js';\nexport {\n createTracker,\n createTrackerFromConfig,\n getPrimaryTracker,\n getSecondaryTracker,\n getAllTrackers,\n} from './factory.js';\n\n// Cross-tracker linking\nexport type { TrackerLink, LinkDirection } from './linking.js';\nexport {\n LinkManager,\n getLinkManager,\n parseIssueRef,\n formatIssueRef,\n} from './linking.js';\n"],"mappings":"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","names":["existsSync","join","existsSync","mkdirSync","readdirSync","lstatSync","rmSync","join","readFileSync","existsSync","mkdirSync","join","homedir"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/lib/projects.ts"],"sourcesContent":["/**\n * Project Registry - Multi-project support for Panopticon\n *\n * Maps Linear team prefixes and labels to project paths for workspace creation.\n */\n\nimport { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';\nimport { join } from 'path';\nimport { parse as parseYaml, stringify as stringifyYaml } from 'yaml';\nimport { PANOPTICON_HOME } from './paths.js';\n\nexport const PROJECTS_CONFIG_FILE = join(PANOPTICON_HOME, 'projects.yaml');\n\n/**\n * Issue routing rule - routes issues with certain labels to specific paths\n */\nexport interface IssueRoutingRule {\n labels?: string[];\n default?: boolean;\n path: string;\n}\n\n/**\n * Workspace configuration (imported from workspace-config.ts for full details)\n */\nexport interface WorkspaceConfig {\n type?: 'polyrepo' | 'monorepo';\n workspaces_dir?: string;\n repos?: Array<{ name: string; path: string; branch_prefix?: string }>;\n dns?: { domain: string; entries: string[]; sync_method?: 'wsl2hosts' | 'hosts_file' | 'dnsmasq' };\n ports?: Record<string, { range: [number, number] }>;\n docker?: { traefik?: string; compose_template?: string };\n database?: { seed_file?: string; container_name?: string; [key: string]: any };\n agent?: { template_dir: string; templates?: Array<{ source: string; target: string }>; symlinks?: string[] };\n env?: { template?: string; secrets_file?: string };\n services?: Array<{ name: string; path: string; start_command: string; docker_command?: string; health_url?: string; port?: number }>;\n}\n\n/**\n * Test configuration\n */\nexport interface TestConfig {\n type: string;\n path: string;\n command: string;\n container?: boolean;\n container_name?: string;\n env?: Record<string, string>;\n}\n\n/**\n * Specialist configuration for per-project specialists\n */\nexport interface SpecialistConfig {\n /** Number of recent runs to include in context digest (default: 5) */\n context_runs?: number;\n /** Model to use for generating context digests (null = same as specialist) */\n digest_model?: string | null;\n /** Log retention policy */\n retention?: {\n /** Maximum days to keep logs */\n max_days: number;\n /** Maximum number of runs to keep (whichever is more permissive) */\n max_runs: number;\n };\n /** Per-specialist prompt overrides */\n prompts?: {\n 'review-agent'?: string;\n 'test-agent'?: string;\n 'merge-agent'?: string;\n };\n}\n\n/**\n * Project configuration\n */\nexport interface ProjectConfig {\n name: string;\n path: string;\n linear_team?: string;\n issue_routing?: IssueRoutingRule[];\n /** Workspace configuration */\n workspace?: WorkspaceConfig;\n /** Test configuration by name */\n tests?: Record<string, TestConfig>;\n /** Custom command to create workspaces (e.g., infra/new-feature for MYN) */\n workspace_command?: string;\n /** Custom command to remove workspaces */\n workspace_remove_command?: string;\n /** Rally project OID (e.g., \"/project/822404704163\") for per-project Rally scoping */\n rally_project?: string;\n /** Specialist agent configuration */\n specialists?: SpecialistConfig;\n}\n\n/**\n * Full projects configuration file\n */\nexport interface ProjectsConfig {\n projects: Record<string, ProjectConfig>;\n}\n\n/**\n * Resolved project info for workspace creation\n */\nexport interface ResolvedProject {\n projectKey: string;\n projectName: string;\n projectPath: string;\n linearTeam?: string;\n}\n\n/**\n * Load projects configuration from ~/.panopticon/projects.yaml\n */\nexport function loadProjectsConfig(): ProjectsConfig {\n if (!existsSync(PROJECTS_CONFIG_FILE)) {\n return { projects: {} };\n }\n\n try {\n const content = readFileSync(PROJECTS_CONFIG_FILE, 'utf-8');\n const config = parseYaml(content) as ProjectsConfig;\n return config || { projects: {} };\n } catch (error: any) {\n console.error(`Failed to parse projects.yaml: ${error.message}`);\n return { projects: {} };\n }\n}\n\n/**\n * Save projects configuration\n */\nexport function saveProjectsConfig(config: ProjectsConfig): void {\n const dir = PANOPTICON_HOME;\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true });\n }\n\n const yaml = stringifyYaml(config, { indent: 2 });\n writeFileSync(PROJECTS_CONFIG_FILE, yaml, 'utf-8');\n}\n\n/**\n * Get a list of all registered projects\n */\nexport function listProjects(): Array<{ key: string; config: ProjectConfig }> {\n const config = loadProjectsConfig();\n return Object.entries(config.projects).map(([key, projectConfig]) => ({\n key,\n config: projectConfig,\n }));\n}\n\n/**\n * Add or update a project in the registry\n */\nexport function registerProject(key: string, projectConfig: ProjectConfig): void {\n const config = loadProjectsConfig();\n config.projects[key] = projectConfig;\n saveProjectsConfig(config);\n}\n\n/**\n * Remove a project from the registry\n */\nexport function unregisterProject(key: string): boolean {\n const config = loadProjectsConfig();\n if (config.projects[key]) {\n delete config.projects[key];\n saveProjectsConfig(config);\n return true;\n }\n return false;\n}\n\n/**\n * Extract Linear team prefix from an issue ID\n * E.g., \"MIN-123\" -> \"MIN\", \"PAN-456\" -> \"PAN\"\n */\nexport function extractTeamPrefix(issueId: string): string | null {\n const match = issueId.match(/^([A-Z]+)-\\d+$/i);\n return match ? match[1].toUpperCase() : null;\n}\n\n/**\n * Find project by Linear team prefix\n */\nexport function findProjectByTeam(teamPrefix: string): ProjectConfig | null {\n const config = loadProjectsConfig();\n\n for (const [, projectConfig] of Object.entries(config.projects)) {\n if (projectConfig.linear_team?.toUpperCase() === teamPrefix.toUpperCase()) {\n return projectConfig;\n }\n }\n\n return null;\n}\n\n/**\n * Resolve the correct project path for an issue based on labels\n *\n * @param project - The project config\n * @param labels - Array of label names from the Linear issue\n * @returns The resolved path (may differ from project.path based on routing rules)\n */\nexport function resolveProjectPath(project: ProjectConfig, labels: string[] = []): string {\n if (!project.issue_routing || project.issue_routing.length === 0) {\n return project.path;\n }\n\n // Normalize labels to lowercase for comparison\n const normalizedLabels = labels.map(l => l.toLowerCase());\n\n // First, check label-based routing rules\n for (const rule of project.issue_routing) {\n if (rule.labels && rule.labels.length > 0) {\n const ruleLabels = rule.labels.map(l => l.toLowerCase());\n const hasMatch = ruleLabels.some(label => normalizedLabels.includes(label));\n if (hasMatch) {\n return rule.path;\n }\n }\n }\n\n // Then, find default rule\n for (const rule of project.issue_routing) {\n if (rule.default) {\n return rule.path;\n }\n }\n\n // Fall back to project path\n return project.path;\n}\n\n/**\n * Resolve project from an issue ID (and optional labels)\n *\n * @param issueId - Linear issue ID (e.g., \"MIN-123\")\n * @param labels - Optional array of label names\n * @returns Resolved project info or null if not found\n */\nexport function resolveProjectFromIssue(\n issueId: string,\n labels: string[] = []\n): ResolvedProject | null {\n const teamPrefix = extractTeamPrefix(issueId);\n if (!teamPrefix) {\n return null;\n }\n\n const config = loadProjectsConfig();\n\n // Find project by team prefix\n for (const [key, projectConfig] of Object.entries(config.projects)) {\n if (projectConfig.linear_team?.toUpperCase() === teamPrefix) {\n const resolvedPath = resolveProjectPath(projectConfig, labels);\n return {\n projectKey: key,\n projectName: projectConfig.name,\n projectPath: resolvedPath,\n linearTeam: projectConfig.linear_team,\n };\n }\n }\n\n return null;\n}\n\n/**\n * Get a project by key\n */\nexport function getProject(key: string): ProjectConfig | null {\n const config = loadProjectsConfig();\n return config.projects[key] || null;\n}\n\n/**\n * Check if projects.yaml exists and has any projects\n */\nexport function hasProjects(): boolean {\n const config = loadProjectsConfig();\n return Object.keys(config.projects).length > 0;\n}\n\n/**\n * Create a default projects.yaml with example structure\n */\nexport function createDefaultProjectsConfig(): ProjectsConfig {\n const defaultConfig: ProjectsConfig = {\n projects: {\n // Example project - commented out in actual file\n },\n };\n\n return defaultConfig;\n}\n\n/**\n * Initialize projects.yaml with example configuration\n */\nexport function initializeProjectsConfig(): void {\n if (existsSync(PROJECTS_CONFIG_FILE)) {\n console.log(`Projects config already exists at ${PROJECTS_CONFIG_FILE}`);\n return;\n }\n\n const exampleYaml = `# Panopticon Project Registry\n# Maps Linear teams to project paths for workspace creation\n\nprojects:\n # Example: Mind Your Now project\n # myn:\n # name: \"Mind Your Now\"\n # path: /home/user/projects/myn\n # linear_team: MIN\n # issue_routing:\n # # Route docs/marketing issues to docs repo\n # - labels: [docs, marketing, seo, landing-pages]\n # path: /home/user/projects/myn/docs\n # # Default: main repo\n # - default: true\n # path: /home/user/projects/myn\n # specialists:\n # context_runs: 5\n # digest_model: null # Use same model as specialist\n # retention:\n # max_days: 30\n # max_runs: 50\n # prompts:\n # review-agent: |\n # Pay special attention to:\n # - Database migration safety\n # - API backward compatibility\n\n # Example: Panopticon itself\n # panopticon:\n # name: \"Panopticon\"\n # path: /home/user/projects/panopticon\n # linear_team: PAN\n`;\n\n const dir = PANOPTICON_HOME;\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true });\n }\n\n writeFileSync(PROJECTS_CONFIG_FILE, exampleYaml, 'utf-8');\n console.log(`Created example projects config at ${PROJECTS_CONFIG_FILE}`);\n}\n\n/**\n * Default specialist configuration values\n */\nconst DEFAULT_SPECIALIST_CONFIG: Required<SpecialistConfig> = {\n context_runs: 5,\n digest_model: null,\n retention: {\n max_days: 30,\n max_runs: 50,\n },\n prompts: {},\n};\n\n/**\n * Get specialist configuration for a project with defaults\n *\n * @param projectKey - Project key\n * @returns Specialist config with defaults applied\n */\nexport function getSpecialistConfig(projectKey: string): Required<SpecialistConfig> {\n const project = getProject(projectKey);\n\n if (!project || !project.specialists) {\n return DEFAULT_SPECIALIST_CONFIG;\n }\n\n return {\n context_runs: project.specialists.context_runs ?? DEFAULT_SPECIALIST_CONFIG.context_runs,\n digest_model: project.specialists.digest_model ?? DEFAULT_SPECIALIST_CONFIG.digest_model,\n retention: {\n max_days: project.specialists.retention?.max_days ?? DEFAULT_SPECIALIST_CONFIG.retention.max_days,\n max_runs: project.specialists.retention?.max_runs ?? DEFAULT_SPECIALIST_CONFIG.retention.max_runs,\n },\n prompts: project.specialists.prompts ?? DEFAULT_SPECIALIST_CONFIG.prompts,\n };\n}\n\n/**\n * Get retention policy for a project's specialists\n *\n * @param projectKey - Project key\n * @returns Retention policy\n */\nexport function getSpecialistRetention(projectKey: string): { max_days: number; max_runs: number } {\n const config = getSpecialistConfig(projectKey);\n return config.retention;\n}\n\n/**\n * Find all projects that have a rally_project configured.\n * Returns array of { key, config } for projects with Rally project OIDs.\n */\nexport function findProjectsByRallyProject(): Array<{ key: string; config: ProjectConfig }> {\n const config = loadProjectsConfig();\n return Object.entries(config.projects)\n .filter(([, projectConfig]) => !!projectConfig.rally_project)\n .map(([key, projectConfig]) => ({ key, config: projectConfig }));\n}\n\n/**\n * Get custom prompt override for a specialist (if configured)\n *\n * @param projectKey - Project key\n * @param specialistType - Specialist type\n * @returns Custom prompt or null if not configured\n */\nexport function getSpecialistPromptOverride(\n projectKey: string,\n specialistType: 'review-agent' | 'test-agent' | 'merge-agent'\n): string | null {\n const config = getSpecialistConfig(projectKey);\n return config.prompts[specialistType] || null;\n}\n"],"mappings":"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","names":[]}
|