panopticon-cli 0.4.28 → 0.4.31
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-ND4NKCK2.js → agents-GQDAKTEQ.js} +5 -4
- package/dist/{chunk-SIAUVHVO.js → chunk-3XAB4IXF.js} +4 -2
- package/dist/{chunk-SIAUVHVO.js.map → chunk-3XAB4IXF.js.map} +1 -1
- package/dist/chunk-ELK6Q7QI.js +545 -0
- package/dist/chunk-ELK6Q7QI.js.map +1 -0
- package/dist/{chunk-ZLB6G4NW.js → chunk-HNEWTIR3.js} +41 -9
- package/dist/chunk-HNEWTIR3.js.map +1 -0
- package/dist/chunk-LYSBSZYV.js +1523 -0
- package/dist/chunk-LYSBSZYV.js.map +1 -0
- package/dist/{chunk-4KNEZGKZ.js → chunk-TMXN7THF.js} +45 -24
- package/dist/chunk-TMXN7THF.js.map +1 -0
- package/dist/{chunk-ON5NIBGW.js → chunk-VIWUCJ4V.js} +37 -8
- package/dist/chunk-VIWUCJ4V.js.map +1 -0
- package/dist/{chunk-VTMXR7JF.js → chunk-VU4FLXV5.js} +47 -40
- package/dist/{chunk-VTMXR7JF.js.map → chunk-VU4FLXV5.js.map} +1 -1
- package/dist/cli/index.js +153 -86
- package/dist/cli/index.js.map +1 -1
- package/dist/{config-QWTS63TU.js → config-BOAMSKTF.js} +4 -2
- package/dist/dashboard/public/assets/{index--VPaQ2VU.css → index-C7X6LP5Z.css} +1 -1
- package/dist/dashboard/public/assets/{index-GYQaqwVS.js → index-izWbAt7V.js} +152 -152
- package/dist/dashboard/public/index.html +2 -2
- package/dist/dashboard/server.js +94706 -24017
- package/dist/feedback-writer-AAKF5BTK.js +111 -0
- package/dist/feedback-writer-AAKF5BTK.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +16 -14
- package/dist/index.js.map +1 -1
- package/dist/{remote-workspace-FNXLMNBG.js → remote-workspace-2G6V2KNP.js} +7 -5
- package/dist/{remote-workspace-FNXLMNBG.js.map → remote-workspace-2G6V2KNP.js.map} +1 -1
- package/dist/{specialist-context-WXO3FKIB.js → specialist-context-6SE5VRRC.js} +3 -3
- package/dist/{specialist-logs-SJWLETJT.js → specialist-logs-EXLOQHQ2.js} +3 -3
- package/dist/{specialists-5YJIDRW6.js → specialists-BRUHPAXE.js} +3 -3
- package/dist/{traefik-7OLLXUD7.js → traefik-CUJM6K5Z.js} +3 -3
- package/package.json +3 -2
- package/scripts/record-cost-event.js +243 -79
- package/scripts/record-cost-event.ts +128 -68
- package/templates/traefik/docker-compose.yml +7 -4
- package/templates/traefik/dynamic/panopticon.yml.template +3 -1
- package/dist/chunk-46DPNFMW.js +0 -278
- package/dist/chunk-46DPNFMW.js.map +0 -1
- package/dist/chunk-4KNEZGKZ.js.map +0 -1
- package/dist/chunk-ON5NIBGW.js.map +0 -1
- package/dist/chunk-SUMIHS2B.js +0 -1714
- package/dist/chunk-SUMIHS2B.js.map +0 -1
- package/dist/chunk-ZLB6G4NW.js.map +0 -1
- /package/dist/{agents-ND4NKCK2.js.map → agents-GQDAKTEQ.js.map} +0 -0
- /package/dist/{config-QWTS63TU.js.map → config-BOAMSKTF.js.map} +0 -0
- /package/dist/{specialist-context-WXO3FKIB.js.map → specialist-context-6SE5VRRC.js.map} +0 -0
- /package/dist/{specialist-logs-SJWLETJT.js.map → specialist-logs-EXLOQHQ2.js.map} +0 -0
- /package/dist/{specialists-5YJIDRW6.js.map → specialists-BRUHPAXE.js.map} +0 -0
- /package/dist/{traefik-7OLLXUD7.js.map → traefik-CUJM6K5Z.js.map} +0 -0
package/dist/chunk-SUMIHS2B.js
DELETED
|
@@ -1,1714 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
init_config_yaml,
|
|
3
|
-
loadConfig
|
|
4
|
-
} from "./chunk-BBCUK6N2.js";
|
|
5
|
-
import {
|
|
6
|
-
AGENTS_DIR,
|
|
7
|
-
BACKUPS_DIR,
|
|
8
|
-
BIN_DIR,
|
|
9
|
-
COMMANDS_DIR,
|
|
10
|
-
SKILLS_DIR,
|
|
11
|
-
SOURCE_DEV_SKILLS_DIR,
|
|
12
|
-
SOURCE_SCRIPTS_DIR,
|
|
13
|
-
SYNC_TARGETS,
|
|
14
|
-
init_paths,
|
|
15
|
-
isDevMode
|
|
16
|
-
} from "./chunk-6HXKTOD7.js";
|
|
17
|
-
import {
|
|
18
|
-
init_esm_shims
|
|
19
|
-
} from "./chunk-ZHC57RCV.js";
|
|
20
|
-
|
|
21
|
-
// src/lib/shell.ts
|
|
22
|
-
init_esm_shims();
|
|
23
|
-
import { existsSync, readFileSync, appendFileSync } from "fs";
|
|
24
|
-
import { homedir } from "os";
|
|
25
|
-
import { join } from "path";
|
|
26
|
-
function detectShell() {
|
|
27
|
-
const shell = process.env.SHELL || "";
|
|
28
|
-
if (shell.includes("zsh")) return "zsh";
|
|
29
|
-
if (shell.includes("bash")) return "bash";
|
|
30
|
-
if (shell.includes("fish")) return "fish";
|
|
31
|
-
return "unknown";
|
|
32
|
-
}
|
|
33
|
-
function getShellRcFile(shell) {
|
|
34
|
-
const home = homedir();
|
|
35
|
-
switch (shell) {
|
|
36
|
-
case "zsh":
|
|
37
|
-
return join(home, ".zshrc");
|
|
38
|
-
case "bash":
|
|
39
|
-
const bashrc = join(home, ".bashrc");
|
|
40
|
-
if (existsSync(bashrc)) return bashrc;
|
|
41
|
-
return join(home, ".bash_profile");
|
|
42
|
-
case "fish":
|
|
43
|
-
return join(home, ".config", "fish", "config.fish");
|
|
44
|
-
default:
|
|
45
|
-
return null;
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
var ALIAS_LINE = 'alias pan="panopticon"';
|
|
49
|
-
var ALIAS_MARKER = "# Panopticon CLI alias";
|
|
50
|
-
function hasAlias(rcFile) {
|
|
51
|
-
if (!existsSync(rcFile)) return false;
|
|
52
|
-
const content = readFileSync(rcFile, "utf8");
|
|
53
|
-
return content.includes(ALIAS_MARKER) || content.includes(ALIAS_LINE);
|
|
54
|
-
}
|
|
55
|
-
function addAlias(rcFile) {
|
|
56
|
-
if (hasAlias(rcFile)) return;
|
|
57
|
-
const aliasBlock = `
|
|
58
|
-
${ALIAS_MARKER}
|
|
59
|
-
${ALIAS_LINE}
|
|
60
|
-
`;
|
|
61
|
-
appendFileSync(rcFile, aliasBlock, "utf8");
|
|
62
|
-
}
|
|
63
|
-
function getAliasInstructions(shell) {
|
|
64
|
-
const rcFile = getShellRcFile(shell);
|
|
65
|
-
if (!rcFile) {
|
|
66
|
-
return `Add this to your shell config:
|
|
67
|
-
${ALIAS_LINE}`;
|
|
68
|
-
}
|
|
69
|
-
return `Alias added to ${rcFile}. Run:
|
|
70
|
-
source ${rcFile}`;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// src/lib/backup.ts
|
|
74
|
-
init_esm_shims();
|
|
75
|
-
init_paths();
|
|
76
|
-
import { existsSync as existsSync2, mkdirSync, readdirSync, cpSync, rmSync, lstatSync } from "fs";
|
|
77
|
-
import { join as join2, basename } from "path";
|
|
78
|
-
function createBackupTimestamp() {
|
|
79
|
-
return (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
80
|
-
}
|
|
81
|
-
function createBackup(sourceDirs) {
|
|
82
|
-
const timestamp = createBackupTimestamp();
|
|
83
|
-
const backupPath = join2(BACKUPS_DIR, timestamp);
|
|
84
|
-
mkdirSync(backupPath, { recursive: true });
|
|
85
|
-
const targets = [];
|
|
86
|
-
for (const sourceDir of sourceDirs) {
|
|
87
|
-
if (!existsSync2(sourceDir)) continue;
|
|
88
|
-
const targetName = basename(sourceDir);
|
|
89
|
-
const targetPath = join2(backupPath, targetName);
|
|
90
|
-
cpSync(sourceDir, targetPath, {
|
|
91
|
-
recursive: true,
|
|
92
|
-
filter: (src) => !lstatSync(src).isSymbolicLink()
|
|
93
|
-
});
|
|
94
|
-
targets.push(targetName);
|
|
95
|
-
}
|
|
96
|
-
return {
|
|
97
|
-
timestamp,
|
|
98
|
-
path: backupPath,
|
|
99
|
-
targets
|
|
100
|
-
};
|
|
101
|
-
}
|
|
102
|
-
function listBackups() {
|
|
103
|
-
if (!existsSync2(BACKUPS_DIR)) return [];
|
|
104
|
-
const entries = readdirSync(BACKUPS_DIR, { withFileTypes: true });
|
|
105
|
-
return entries.filter((e) => e.isDirectory()).map((e) => {
|
|
106
|
-
const backupPath = join2(BACKUPS_DIR, e.name);
|
|
107
|
-
const contents = readdirSync(backupPath);
|
|
108
|
-
return {
|
|
109
|
-
timestamp: e.name,
|
|
110
|
-
path: backupPath,
|
|
111
|
-
targets: contents
|
|
112
|
-
};
|
|
113
|
-
}).sort((a, b) => b.timestamp.localeCompare(a.timestamp));
|
|
114
|
-
}
|
|
115
|
-
function restoreBackup(timestamp, targetDirs) {
|
|
116
|
-
const backupPath = join2(BACKUPS_DIR, timestamp);
|
|
117
|
-
if (!existsSync2(backupPath)) {
|
|
118
|
-
throw new Error(`Backup not found: ${timestamp}`);
|
|
119
|
-
}
|
|
120
|
-
const contents = readdirSync(backupPath, { withFileTypes: true });
|
|
121
|
-
for (const entry of contents) {
|
|
122
|
-
if (!entry.isDirectory()) continue;
|
|
123
|
-
const sourcePath = join2(backupPath, entry.name);
|
|
124
|
-
const targetPath = targetDirs[entry.name];
|
|
125
|
-
if (!targetPath) continue;
|
|
126
|
-
if (existsSync2(targetPath)) {
|
|
127
|
-
rmSync(targetPath, { recursive: true });
|
|
128
|
-
}
|
|
129
|
-
cpSync(sourcePath, targetPath, { recursive: true });
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
function cleanOldBackups(keepCount = 10) {
|
|
133
|
-
const backups = listBackups();
|
|
134
|
-
if (backups.length <= keepCount) return 0;
|
|
135
|
-
const toRemove = backups.slice(keepCount);
|
|
136
|
-
let removed = 0;
|
|
137
|
-
for (const backup of toRemove) {
|
|
138
|
-
rmSync(backup.path, { recursive: true });
|
|
139
|
-
removed++;
|
|
140
|
-
}
|
|
141
|
-
return removed;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
// src/lib/sync.ts
|
|
145
|
-
init_esm_shims();
|
|
146
|
-
init_paths();
|
|
147
|
-
import { existsSync as existsSync3, mkdirSync as mkdirSync2, readdirSync as readdirSync2, symlinkSync, unlinkSync, lstatSync as lstatSync2, readlinkSync, rmSync as rmSync2, copyFileSync, chmodSync } from "fs";
|
|
148
|
-
import { join as join3 } from "path";
|
|
149
|
-
function removeTarget(targetPath) {
|
|
150
|
-
const stats = lstatSync2(targetPath);
|
|
151
|
-
if (stats.isDirectory() && !stats.isSymbolicLink()) {
|
|
152
|
-
rmSync2(targetPath, { recursive: true, force: true });
|
|
153
|
-
} else {
|
|
154
|
-
unlinkSync(targetPath);
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
function isPanopticonSymlink(targetPath) {
|
|
158
|
-
if (!existsSync3(targetPath)) return false;
|
|
159
|
-
try {
|
|
160
|
-
const stats = lstatSync2(targetPath);
|
|
161
|
-
if (!stats.isSymbolicLink()) return false;
|
|
162
|
-
const linkTarget = readlinkSync(targetPath);
|
|
163
|
-
return linkTarget.includes(".panopticon");
|
|
164
|
-
} catch {
|
|
165
|
-
return false;
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
function planSync(runtime) {
|
|
169
|
-
const targets = SYNC_TARGETS[runtime];
|
|
170
|
-
if (!targets) {
|
|
171
|
-
throw new Error(`Unknown sync target "${runtime}". Valid targets: ${Object.keys(SYNC_TARGETS).join(", ")}`);
|
|
172
|
-
}
|
|
173
|
-
const plan = {
|
|
174
|
-
runtime,
|
|
175
|
-
skills: [],
|
|
176
|
-
commands: [],
|
|
177
|
-
agents: [],
|
|
178
|
-
devSkills: []
|
|
179
|
-
};
|
|
180
|
-
if (existsSync3(SKILLS_DIR)) {
|
|
181
|
-
const skills = readdirSync2(SKILLS_DIR, { withFileTypes: true }).filter((d) => d.isDirectory());
|
|
182
|
-
for (const skill of skills) {
|
|
183
|
-
const sourcePath = join3(SKILLS_DIR, skill.name);
|
|
184
|
-
const targetPath = join3(targets.skills, skill.name);
|
|
185
|
-
let status = "new";
|
|
186
|
-
if (existsSync3(targetPath)) {
|
|
187
|
-
if (isPanopticonSymlink(targetPath)) {
|
|
188
|
-
status = "symlink";
|
|
189
|
-
} else {
|
|
190
|
-
status = "conflict";
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
plan.skills.push({ name: skill.name, sourcePath, targetPath, status });
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
if (isDevMode() && existsSync3(SOURCE_DEV_SKILLS_DIR)) {
|
|
197
|
-
const devSkills = readdirSync2(SOURCE_DEV_SKILLS_DIR, { withFileTypes: true }).filter((d) => d.isDirectory());
|
|
198
|
-
for (const skill of devSkills) {
|
|
199
|
-
const sourcePath = join3(SOURCE_DEV_SKILLS_DIR, skill.name);
|
|
200
|
-
const targetPath = join3(targets.skills, skill.name);
|
|
201
|
-
let status = "new";
|
|
202
|
-
if (existsSync3(targetPath)) {
|
|
203
|
-
if (isPanopticonSymlink(targetPath)) {
|
|
204
|
-
status = "symlink";
|
|
205
|
-
} else {
|
|
206
|
-
status = "conflict";
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
plan.devSkills.push({ name: skill.name, sourcePath, targetPath, status });
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
if (existsSync3(COMMANDS_DIR)) {
|
|
213
|
-
const commands = readdirSync2(COMMANDS_DIR, { withFileTypes: true }).filter((d) => d.isDirectory());
|
|
214
|
-
for (const cmd of commands) {
|
|
215
|
-
const sourcePath = join3(COMMANDS_DIR, cmd.name);
|
|
216
|
-
const targetPath = join3(targets.commands, cmd.name);
|
|
217
|
-
let status = "new";
|
|
218
|
-
if (existsSync3(targetPath)) {
|
|
219
|
-
if (isPanopticonSymlink(targetPath)) {
|
|
220
|
-
status = "symlink";
|
|
221
|
-
} else {
|
|
222
|
-
status = "conflict";
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
plan.commands.push({ name: cmd.name, sourcePath, targetPath, status });
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
if (existsSync3(AGENTS_DIR)) {
|
|
229
|
-
const agents = readdirSync2(AGENTS_DIR, { withFileTypes: true }).filter((entry) => entry.isFile() && entry.name.endsWith(".md"));
|
|
230
|
-
for (const agent of agents) {
|
|
231
|
-
const sourcePath = join3(AGENTS_DIR, agent.name);
|
|
232
|
-
const targetPath = join3(targets.agents, agent.name);
|
|
233
|
-
let status = "new";
|
|
234
|
-
if (existsSync3(targetPath)) {
|
|
235
|
-
if (isPanopticonSymlink(targetPath)) {
|
|
236
|
-
status = "symlink";
|
|
237
|
-
} else {
|
|
238
|
-
status = "conflict";
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
plan.agents.push({ name: agent.name, sourcePath, targetPath, status });
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
return plan;
|
|
245
|
-
}
|
|
246
|
-
function executeSync(runtime, options = {}) {
|
|
247
|
-
const targets = SYNC_TARGETS[runtime];
|
|
248
|
-
if (!targets) {
|
|
249
|
-
throw new Error(`Unknown sync target "${runtime}". Valid targets: ${Object.keys(SYNC_TARGETS).join(", ")}`);
|
|
250
|
-
}
|
|
251
|
-
const plan = planSync(runtime);
|
|
252
|
-
const result = {
|
|
253
|
-
created: [],
|
|
254
|
-
skipped: [],
|
|
255
|
-
conflicts: []
|
|
256
|
-
};
|
|
257
|
-
mkdirSync2(targets.skills, { recursive: true });
|
|
258
|
-
mkdirSync2(targets.commands, { recursive: true });
|
|
259
|
-
mkdirSync2(targets.agents, { recursive: true });
|
|
260
|
-
for (const item of plan.skills) {
|
|
261
|
-
if (options.dryRun) {
|
|
262
|
-
if (item.status === "new" || item.status === "symlink") {
|
|
263
|
-
result.created.push(item.name);
|
|
264
|
-
} else {
|
|
265
|
-
result.conflicts.push(item.name);
|
|
266
|
-
}
|
|
267
|
-
continue;
|
|
268
|
-
}
|
|
269
|
-
if (item.status === "conflict" && !options.force) {
|
|
270
|
-
result.conflicts.push(item.name);
|
|
271
|
-
continue;
|
|
272
|
-
}
|
|
273
|
-
if (existsSync3(item.targetPath)) {
|
|
274
|
-
removeTarget(item.targetPath);
|
|
275
|
-
}
|
|
276
|
-
symlinkSync(item.sourcePath, item.targetPath);
|
|
277
|
-
result.created.push(item.name);
|
|
278
|
-
}
|
|
279
|
-
for (const item of plan.commands) {
|
|
280
|
-
if (options.dryRun) {
|
|
281
|
-
if (item.status === "new" || item.status === "symlink") {
|
|
282
|
-
result.created.push(item.name);
|
|
283
|
-
} else {
|
|
284
|
-
result.conflicts.push(item.name);
|
|
285
|
-
}
|
|
286
|
-
continue;
|
|
287
|
-
}
|
|
288
|
-
if (item.status === "conflict" && !options.force) {
|
|
289
|
-
result.conflicts.push(item.name);
|
|
290
|
-
continue;
|
|
291
|
-
}
|
|
292
|
-
if (existsSync3(item.targetPath)) {
|
|
293
|
-
removeTarget(item.targetPath);
|
|
294
|
-
}
|
|
295
|
-
symlinkSync(item.sourcePath, item.targetPath);
|
|
296
|
-
result.created.push(item.name);
|
|
297
|
-
}
|
|
298
|
-
for (const item of plan.agents) {
|
|
299
|
-
if (options.dryRun) {
|
|
300
|
-
if (item.status === "new" || item.status === "symlink") {
|
|
301
|
-
result.created.push(item.name);
|
|
302
|
-
} else {
|
|
303
|
-
result.conflicts.push(item.name);
|
|
304
|
-
}
|
|
305
|
-
continue;
|
|
306
|
-
}
|
|
307
|
-
if (item.status === "conflict" && !options.force) {
|
|
308
|
-
result.conflicts.push(item.name);
|
|
309
|
-
continue;
|
|
310
|
-
}
|
|
311
|
-
if (existsSync3(item.targetPath)) {
|
|
312
|
-
removeTarget(item.targetPath);
|
|
313
|
-
}
|
|
314
|
-
symlinkSync(item.sourcePath, item.targetPath);
|
|
315
|
-
result.created.push(item.name);
|
|
316
|
-
}
|
|
317
|
-
for (const item of plan.devSkills) {
|
|
318
|
-
if (options.dryRun) {
|
|
319
|
-
if (item.status === "new" || item.status === "symlink") {
|
|
320
|
-
result.created.push(`${item.name} (dev)`);
|
|
321
|
-
} else {
|
|
322
|
-
result.conflicts.push(`${item.name} (dev)`);
|
|
323
|
-
}
|
|
324
|
-
continue;
|
|
325
|
-
}
|
|
326
|
-
if (item.status === "conflict" && !options.force) {
|
|
327
|
-
result.conflicts.push(`${item.name} (dev)`);
|
|
328
|
-
continue;
|
|
329
|
-
}
|
|
330
|
-
if (existsSync3(item.targetPath)) {
|
|
331
|
-
removeTarget(item.targetPath);
|
|
332
|
-
}
|
|
333
|
-
symlinkSync(item.sourcePath, item.targetPath);
|
|
334
|
-
result.created.push(`${item.name} (dev)`);
|
|
335
|
-
}
|
|
336
|
-
return result;
|
|
337
|
-
}
|
|
338
|
-
function planHooksSync() {
|
|
339
|
-
const hooks = [];
|
|
340
|
-
if (!existsSync3(SOURCE_SCRIPTS_DIR)) {
|
|
341
|
-
return hooks;
|
|
342
|
-
}
|
|
343
|
-
const scripts = readdirSync2(SOURCE_SCRIPTS_DIR, { withFileTypes: true }).filter((entry) => entry.isFile() && !entry.name.startsWith(".") && !entry.name.includes("."));
|
|
344
|
-
for (const script of scripts) {
|
|
345
|
-
const sourcePath = join3(SOURCE_SCRIPTS_DIR, script.name);
|
|
346
|
-
const targetPath = join3(BIN_DIR, script.name);
|
|
347
|
-
let status = "new";
|
|
348
|
-
if (existsSync3(targetPath)) {
|
|
349
|
-
status = "updated";
|
|
350
|
-
}
|
|
351
|
-
hooks.push({ name: script.name, sourcePath, targetPath, status });
|
|
352
|
-
}
|
|
353
|
-
return hooks;
|
|
354
|
-
}
|
|
355
|
-
function syncHooks() {
|
|
356
|
-
const result = { synced: [], errors: [] };
|
|
357
|
-
mkdirSync2(BIN_DIR, { recursive: true });
|
|
358
|
-
const hooks = planHooksSync();
|
|
359
|
-
for (const hook of hooks) {
|
|
360
|
-
try {
|
|
361
|
-
copyFileSync(hook.sourcePath, hook.targetPath);
|
|
362
|
-
chmodSync(hook.targetPath, 493);
|
|
363
|
-
result.synced.push(hook.name);
|
|
364
|
-
} catch (error) {
|
|
365
|
-
result.errors.push(`${hook.name}: ${error}`);
|
|
366
|
-
}
|
|
367
|
-
}
|
|
368
|
-
return result;
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
// src/lib/tracker/interface.ts
|
|
372
|
-
init_esm_shims();
|
|
373
|
-
var NotImplementedError = class extends Error {
|
|
374
|
-
constructor(feature) {
|
|
375
|
-
super(`Not implemented: ${feature}`);
|
|
376
|
-
this.name = "NotImplementedError";
|
|
377
|
-
}
|
|
378
|
-
};
|
|
379
|
-
var IssueNotFoundError = class extends Error {
|
|
380
|
-
constructor(id, tracker) {
|
|
381
|
-
super(`Issue not found: ${id} (tracker: ${tracker})`);
|
|
382
|
-
this.name = "IssueNotFoundError";
|
|
383
|
-
}
|
|
384
|
-
};
|
|
385
|
-
var TrackerAuthError = class extends Error {
|
|
386
|
-
constructor(tracker, message) {
|
|
387
|
-
super(`Authentication failed for ${tracker}: ${message}`);
|
|
388
|
-
this.name = "TrackerAuthError";
|
|
389
|
-
}
|
|
390
|
-
};
|
|
391
|
-
|
|
392
|
-
// src/lib/tracker/linear.ts
|
|
393
|
-
init_esm_shims();
|
|
394
|
-
import { LinearClient } from "@linear/sdk";
|
|
395
|
-
var STATE_MAP = {
|
|
396
|
-
backlog: "open",
|
|
397
|
-
unstarted: "open",
|
|
398
|
-
started: "in_progress",
|
|
399
|
-
completed: "closed",
|
|
400
|
-
canceled: "closed"
|
|
401
|
-
};
|
|
402
|
-
var LinearTracker = class {
|
|
403
|
-
name = "linear";
|
|
404
|
-
client;
|
|
405
|
-
defaultTeam;
|
|
406
|
-
constructor(apiKey, options) {
|
|
407
|
-
if (!apiKey) {
|
|
408
|
-
throw new TrackerAuthError("linear", "API key is required");
|
|
409
|
-
}
|
|
410
|
-
this.client = new LinearClient({ apiKey });
|
|
411
|
-
this.defaultTeam = options?.team;
|
|
412
|
-
}
|
|
413
|
-
async listIssues(filters) {
|
|
414
|
-
const team = filters?.team ?? this.defaultTeam;
|
|
415
|
-
const result = await this.client.issues({
|
|
416
|
-
first: filters?.limit ?? 50,
|
|
417
|
-
filter: {
|
|
418
|
-
team: team ? { key: { eq: team } } : void 0,
|
|
419
|
-
state: filters?.state ? { type: { eq: this.reverseMapState(filters.state) } } : filters?.includeClosed ? void 0 : { type: { neq: "completed" } },
|
|
420
|
-
labels: filters?.labels?.length ? { name: { in: filters.labels } } : void 0,
|
|
421
|
-
assignee: filters?.assignee ? { name: { containsIgnoreCase: filters.assignee } } : void 0
|
|
422
|
-
}
|
|
423
|
-
});
|
|
424
|
-
const issues = [];
|
|
425
|
-
for (const node of result.nodes) {
|
|
426
|
-
issues.push(await this.normalizeIssue(node));
|
|
427
|
-
}
|
|
428
|
-
return issues;
|
|
429
|
-
}
|
|
430
|
-
async getIssue(id) {
|
|
431
|
-
try {
|
|
432
|
-
const isUuid = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(id);
|
|
433
|
-
if (isUuid) {
|
|
434
|
-
const issue = await this.client.issue(id);
|
|
435
|
-
if (issue) {
|
|
436
|
-
return this.normalizeIssue(issue);
|
|
437
|
-
}
|
|
438
|
-
} else {
|
|
439
|
-
const match = id.match(/^([A-Z]+)-(\d+)$/i);
|
|
440
|
-
if (match) {
|
|
441
|
-
const [, teamKey, number] = match;
|
|
442
|
-
const results = await this.client.searchIssues(id, { first: 1 });
|
|
443
|
-
if (results.nodes.length > 0) {
|
|
444
|
-
return this.normalizeIssue(results.nodes[0]);
|
|
445
|
-
}
|
|
446
|
-
}
|
|
447
|
-
}
|
|
448
|
-
throw new IssueNotFoundError(id, "linear");
|
|
449
|
-
} catch (error) {
|
|
450
|
-
if (error instanceof IssueNotFoundError) throw error;
|
|
451
|
-
throw new IssueNotFoundError(id, "linear");
|
|
452
|
-
}
|
|
453
|
-
}
|
|
454
|
-
async updateIssue(id, update) {
|
|
455
|
-
const issue = await this.getIssue(id);
|
|
456
|
-
const updatePayload = {};
|
|
457
|
-
if (update.title !== void 0) {
|
|
458
|
-
updatePayload.title = update.title;
|
|
459
|
-
}
|
|
460
|
-
if (update.description !== void 0) {
|
|
461
|
-
updatePayload.description = update.description;
|
|
462
|
-
}
|
|
463
|
-
if (update.priority !== void 0) {
|
|
464
|
-
updatePayload.priority = update.priority;
|
|
465
|
-
}
|
|
466
|
-
if (update.dueDate !== void 0) {
|
|
467
|
-
updatePayload.dueDate = update.dueDate;
|
|
468
|
-
}
|
|
469
|
-
if (update.state !== void 0) {
|
|
470
|
-
await this.transitionIssue(id, update.state);
|
|
471
|
-
}
|
|
472
|
-
if (update.labels !== void 0) {
|
|
473
|
-
}
|
|
474
|
-
if (Object.keys(updatePayload).length > 0) {
|
|
475
|
-
await this.client.updateIssue(issue.id, updatePayload);
|
|
476
|
-
}
|
|
477
|
-
return this.getIssue(id);
|
|
478
|
-
}
|
|
479
|
-
async createIssue(newIssue) {
|
|
480
|
-
const team = newIssue.team ?? this.defaultTeam;
|
|
481
|
-
if (!team) {
|
|
482
|
-
throw new Error("Team is required to create an issue");
|
|
483
|
-
}
|
|
484
|
-
const teams = await this.client.teams({
|
|
485
|
-
filter: { key: { eq: team } }
|
|
486
|
-
});
|
|
487
|
-
if (teams.nodes.length === 0) {
|
|
488
|
-
throw new Error(`Team not found: ${team}`);
|
|
489
|
-
}
|
|
490
|
-
const teamId = teams.nodes[0].id;
|
|
491
|
-
const result = await this.client.createIssue({
|
|
492
|
-
teamId,
|
|
493
|
-
title: newIssue.title,
|
|
494
|
-
description: newIssue.description,
|
|
495
|
-
priority: newIssue.priority,
|
|
496
|
-
dueDate: newIssue.dueDate
|
|
497
|
-
});
|
|
498
|
-
const created = await result.issue;
|
|
499
|
-
if (!created) {
|
|
500
|
-
throw new Error("Failed to create issue");
|
|
501
|
-
}
|
|
502
|
-
return this.normalizeIssue(created);
|
|
503
|
-
}
|
|
504
|
-
async getComments(issueId) {
|
|
505
|
-
const issue = await this.client.issue(issueId);
|
|
506
|
-
const comments = await issue.comments();
|
|
507
|
-
return comments.nodes.map((c) => ({
|
|
508
|
-
id: c.id,
|
|
509
|
-
issueId,
|
|
510
|
-
body: c.body,
|
|
511
|
-
author: c.user?.then((u) => u?.name ?? "Unknown"),
|
|
512
|
-
// Simplified
|
|
513
|
-
createdAt: c.createdAt.toISOString(),
|
|
514
|
-
updatedAt: c.updatedAt.toISOString()
|
|
515
|
-
}));
|
|
516
|
-
}
|
|
517
|
-
async addComment(issueId, body) {
|
|
518
|
-
const result = await this.client.createComment({
|
|
519
|
-
issueId,
|
|
520
|
-
body
|
|
521
|
-
});
|
|
522
|
-
const comment = await result.comment;
|
|
523
|
-
if (!comment) {
|
|
524
|
-
throw new Error("Failed to create comment");
|
|
525
|
-
}
|
|
526
|
-
return {
|
|
527
|
-
id: comment.id,
|
|
528
|
-
issueId,
|
|
529
|
-
body: comment.body,
|
|
530
|
-
author: "Panopticon",
|
|
531
|
-
// Simplified
|
|
532
|
-
createdAt: comment.createdAt.toISOString(),
|
|
533
|
-
updatedAt: comment.updatedAt.toISOString()
|
|
534
|
-
};
|
|
535
|
-
}
|
|
536
|
-
async transitionIssue(id, state) {
|
|
537
|
-
const issue = await this.getIssue(id);
|
|
538
|
-
const linearIssue = await this.client.issue(issue.id);
|
|
539
|
-
const team = await linearIssue.team;
|
|
540
|
-
if (!team) {
|
|
541
|
-
throw new Error("Could not determine issue team");
|
|
542
|
-
}
|
|
543
|
-
const states = await team.states();
|
|
544
|
-
const targetStateType = this.reverseMapState(state);
|
|
545
|
-
const targetState = states.nodes.find((s) => s.type === targetStateType);
|
|
546
|
-
if (!targetState) {
|
|
547
|
-
throw new Error(`No state found matching type: ${targetStateType}`);
|
|
548
|
-
}
|
|
549
|
-
await this.client.updateIssue(issue.id, {
|
|
550
|
-
stateId: targetState.id
|
|
551
|
-
});
|
|
552
|
-
}
|
|
553
|
-
async linkPR(issueId, prUrl) {
|
|
554
|
-
const issue = await this.getIssue(issueId);
|
|
555
|
-
await this.client.createAttachment({
|
|
556
|
-
issueId: issue.id,
|
|
557
|
-
title: "Pull Request",
|
|
558
|
-
url: prUrl
|
|
559
|
-
});
|
|
560
|
-
}
|
|
561
|
-
async normalizeIssue(linearIssue) {
|
|
562
|
-
const state = await linearIssue.state;
|
|
563
|
-
const assignee = await linearIssue.assignee;
|
|
564
|
-
const labels = await linearIssue.labels();
|
|
565
|
-
let dueDate;
|
|
566
|
-
if (linearIssue.dueDate) {
|
|
567
|
-
dueDate = linearIssue.dueDate instanceof Date ? linearIssue.dueDate.toISOString() : String(linearIssue.dueDate);
|
|
568
|
-
}
|
|
569
|
-
return {
|
|
570
|
-
id: linearIssue.id,
|
|
571
|
-
ref: linearIssue.identifier,
|
|
572
|
-
title: linearIssue.title,
|
|
573
|
-
description: linearIssue.description ?? "",
|
|
574
|
-
state: this.mapState(state?.type ?? "backlog"),
|
|
575
|
-
labels: labels?.nodes?.map((l) => l.name) ?? [],
|
|
576
|
-
assignee: assignee?.name,
|
|
577
|
-
url: linearIssue.url,
|
|
578
|
-
tracker: "linear",
|
|
579
|
-
priority: linearIssue.priority,
|
|
580
|
-
dueDate,
|
|
581
|
-
createdAt: linearIssue.createdAt instanceof Date ? linearIssue.createdAt.toISOString() : String(linearIssue.createdAt),
|
|
582
|
-
updatedAt: linearIssue.updatedAt instanceof Date ? linearIssue.updatedAt.toISOString() : String(linearIssue.updatedAt)
|
|
583
|
-
};
|
|
584
|
-
}
|
|
585
|
-
mapState(linearState) {
|
|
586
|
-
return STATE_MAP[linearState] ?? "open";
|
|
587
|
-
}
|
|
588
|
-
reverseMapState(state) {
|
|
589
|
-
switch (state) {
|
|
590
|
-
case "open":
|
|
591
|
-
return "unstarted";
|
|
592
|
-
case "in_progress":
|
|
593
|
-
return "started";
|
|
594
|
-
case "closed":
|
|
595
|
-
return "completed";
|
|
596
|
-
default:
|
|
597
|
-
return "unstarted";
|
|
598
|
-
}
|
|
599
|
-
}
|
|
600
|
-
};
|
|
601
|
-
|
|
602
|
-
// src/lib/tracker/github.ts
|
|
603
|
-
init_esm_shims();
|
|
604
|
-
import { Octokit } from "@octokit/rest";
|
|
605
|
-
var GitHubTracker = class {
|
|
606
|
-
name = "github";
|
|
607
|
-
octokit;
|
|
608
|
-
owner;
|
|
609
|
-
repo;
|
|
610
|
-
constructor(token, owner, repo) {
|
|
611
|
-
if (!token) {
|
|
612
|
-
throw new TrackerAuthError("github", "Token is required");
|
|
613
|
-
}
|
|
614
|
-
if (!owner || !repo) {
|
|
615
|
-
throw new Error("GitHub owner and repo are required");
|
|
616
|
-
}
|
|
617
|
-
this.octokit = new Octokit({ auth: token });
|
|
618
|
-
this.owner = owner;
|
|
619
|
-
this.repo = repo;
|
|
620
|
-
}
|
|
621
|
-
async listIssues(filters) {
|
|
622
|
-
const state = this.mapStateToGitHub(filters?.state);
|
|
623
|
-
const response = await this.octokit.issues.listForRepo({
|
|
624
|
-
owner: this.owner,
|
|
625
|
-
repo: this.repo,
|
|
626
|
-
state: filters?.includeClosed ? "all" : state,
|
|
627
|
-
labels: filters?.labels?.join(",") || void 0,
|
|
628
|
-
assignee: filters?.assignee || void 0,
|
|
629
|
-
per_page: filters?.limit ?? 50
|
|
630
|
-
});
|
|
631
|
-
const issues = response.data.filter((item) => !item.pull_request);
|
|
632
|
-
return issues.map((issue) => this.normalizeIssue(issue));
|
|
633
|
-
}
|
|
634
|
-
async getIssue(id) {
|
|
635
|
-
try {
|
|
636
|
-
const issueNumber = parseInt(id.replace(/^#/, ""), 10);
|
|
637
|
-
if (isNaN(issueNumber)) {
|
|
638
|
-
throw new IssueNotFoundError(id, "github");
|
|
639
|
-
}
|
|
640
|
-
const { data: issue } = await this.octokit.issues.get({
|
|
641
|
-
owner: this.owner,
|
|
642
|
-
repo: this.repo,
|
|
643
|
-
issue_number: issueNumber
|
|
644
|
-
});
|
|
645
|
-
return this.normalizeIssue(issue);
|
|
646
|
-
} catch (error) {
|
|
647
|
-
if (error?.status === 404) {
|
|
648
|
-
throw new IssueNotFoundError(id, "github");
|
|
649
|
-
}
|
|
650
|
-
throw error;
|
|
651
|
-
}
|
|
652
|
-
}
|
|
653
|
-
async updateIssue(id, update) {
|
|
654
|
-
const issueNumber = parseInt(id.replace(/^#/, ""), 10);
|
|
655
|
-
const updatePayload = {};
|
|
656
|
-
if (update.title !== void 0) {
|
|
657
|
-
updatePayload.title = update.title;
|
|
658
|
-
}
|
|
659
|
-
if (update.description !== void 0) {
|
|
660
|
-
updatePayload.body = update.description;
|
|
661
|
-
}
|
|
662
|
-
if (update.state !== void 0) {
|
|
663
|
-
updatePayload.state = update.state === "closed" ? "closed" : "open";
|
|
664
|
-
}
|
|
665
|
-
if (update.labels !== void 0) {
|
|
666
|
-
updatePayload.labels = update.labels;
|
|
667
|
-
}
|
|
668
|
-
if (update.assignee !== void 0) {
|
|
669
|
-
updatePayload.assignees = update.assignee ? [update.assignee] : [];
|
|
670
|
-
}
|
|
671
|
-
await this.octokit.issues.update({
|
|
672
|
-
owner: this.owner,
|
|
673
|
-
repo: this.repo,
|
|
674
|
-
issue_number: issueNumber,
|
|
675
|
-
...updatePayload
|
|
676
|
-
});
|
|
677
|
-
return this.getIssue(id);
|
|
678
|
-
}
|
|
679
|
-
async createIssue(newIssue) {
|
|
680
|
-
const { data: issue } = await this.octokit.issues.create({
|
|
681
|
-
owner: this.owner,
|
|
682
|
-
repo: this.repo,
|
|
683
|
-
title: newIssue.title,
|
|
684
|
-
body: newIssue.description,
|
|
685
|
-
labels: newIssue.labels,
|
|
686
|
-
assignees: newIssue.assignee ? [newIssue.assignee] : void 0
|
|
687
|
-
});
|
|
688
|
-
return this.normalizeIssue(issue);
|
|
689
|
-
}
|
|
690
|
-
async getComments(issueId) {
|
|
691
|
-
const issueNumber = parseInt(issueId.replace(/^#/, ""), 10);
|
|
692
|
-
const { data: comments } = await this.octokit.issues.listComments({
|
|
693
|
-
owner: this.owner,
|
|
694
|
-
repo: this.repo,
|
|
695
|
-
issue_number: issueNumber
|
|
696
|
-
});
|
|
697
|
-
return comments.map((c) => ({
|
|
698
|
-
id: String(c.id),
|
|
699
|
-
issueId,
|
|
700
|
-
body: c.body ?? "",
|
|
701
|
-
author: c.user?.login ?? "Unknown",
|
|
702
|
-
createdAt: c.created_at,
|
|
703
|
-
updatedAt: c.updated_at
|
|
704
|
-
}));
|
|
705
|
-
}
|
|
706
|
-
async addComment(issueId, body) {
|
|
707
|
-
const issueNumber = parseInt(issueId.replace(/^#/, ""), 10);
|
|
708
|
-
const { data: comment } = await this.octokit.issues.createComment({
|
|
709
|
-
owner: this.owner,
|
|
710
|
-
repo: this.repo,
|
|
711
|
-
issue_number: issueNumber,
|
|
712
|
-
body
|
|
713
|
-
});
|
|
714
|
-
return {
|
|
715
|
-
id: String(comment.id),
|
|
716
|
-
issueId,
|
|
717
|
-
body: comment.body ?? "",
|
|
718
|
-
author: comment.user?.login ?? "Unknown",
|
|
719
|
-
createdAt: comment.created_at,
|
|
720
|
-
updatedAt: comment.updated_at
|
|
721
|
-
};
|
|
722
|
-
}
|
|
723
|
-
async transitionIssue(id, state) {
|
|
724
|
-
await this.updateIssue(id, { state });
|
|
725
|
-
}
|
|
726
|
-
async linkPR(issueId, prUrl) {
|
|
727
|
-
await this.addComment(
|
|
728
|
-
issueId,
|
|
729
|
-
`Linked Pull Request: ${prUrl}`
|
|
730
|
-
);
|
|
731
|
-
}
|
|
732
|
-
normalizeIssue(ghIssue) {
|
|
733
|
-
return {
|
|
734
|
-
id: String(ghIssue.id),
|
|
735
|
-
ref: `#${ghIssue.number}`,
|
|
736
|
-
title: ghIssue.title,
|
|
737
|
-
description: ghIssue.body ?? "",
|
|
738
|
-
state: this.mapStateFromGitHub(ghIssue.state),
|
|
739
|
-
labels: ghIssue.labels.map(
|
|
740
|
-
(l) => typeof l === "string" ? l : l.name
|
|
741
|
-
),
|
|
742
|
-
assignee: ghIssue.assignee?.login,
|
|
743
|
-
url: ghIssue.html_url,
|
|
744
|
-
tracker: "github",
|
|
745
|
-
priority: void 0,
|
|
746
|
-
// GitHub doesn't have priority
|
|
747
|
-
dueDate: void 0,
|
|
748
|
-
// GitHub doesn't have due dates on issues
|
|
749
|
-
createdAt: ghIssue.created_at,
|
|
750
|
-
updatedAt: ghIssue.updated_at
|
|
751
|
-
};
|
|
752
|
-
}
|
|
753
|
-
mapStateFromGitHub(ghState) {
|
|
754
|
-
return ghState === "closed" ? "closed" : "open";
|
|
755
|
-
}
|
|
756
|
-
mapStateToGitHub(state) {
|
|
757
|
-
if (!state) return "open";
|
|
758
|
-
if (state === "closed") return "closed";
|
|
759
|
-
return "open";
|
|
760
|
-
}
|
|
761
|
-
};
|
|
762
|
-
|
|
763
|
-
// src/lib/tracker/gitlab.ts
|
|
764
|
-
init_esm_shims();
|
|
765
|
-
var GitLabTracker = class {
|
|
766
|
-
constructor(token, projectId) {
|
|
767
|
-
this.token = token;
|
|
768
|
-
this.projectId = projectId;
|
|
769
|
-
}
|
|
770
|
-
name = "gitlab";
|
|
771
|
-
async listIssues(_filters) {
|
|
772
|
-
throw new NotImplementedError(
|
|
773
|
-
"GitLab tracker is not yet implemented. Coming soon!"
|
|
774
|
-
);
|
|
775
|
-
}
|
|
776
|
-
async getIssue(_id) {
|
|
777
|
-
throw new NotImplementedError(
|
|
778
|
-
"GitLab tracker is not yet implemented. Coming soon!"
|
|
779
|
-
);
|
|
780
|
-
}
|
|
781
|
-
async updateIssue(_id, _update) {
|
|
782
|
-
throw new NotImplementedError(
|
|
783
|
-
"GitLab tracker is not yet implemented. Coming soon!"
|
|
784
|
-
);
|
|
785
|
-
}
|
|
786
|
-
async createIssue(_issue) {
|
|
787
|
-
throw new NotImplementedError(
|
|
788
|
-
"GitLab tracker is not yet implemented. Coming soon!"
|
|
789
|
-
);
|
|
790
|
-
}
|
|
791
|
-
async getComments(_issueId) {
|
|
792
|
-
throw new NotImplementedError(
|
|
793
|
-
"GitLab tracker is not yet implemented. Coming soon!"
|
|
794
|
-
);
|
|
795
|
-
}
|
|
796
|
-
async addComment(_issueId, _body) {
|
|
797
|
-
throw new NotImplementedError(
|
|
798
|
-
"GitLab tracker is not yet implemented. Coming soon!"
|
|
799
|
-
);
|
|
800
|
-
}
|
|
801
|
-
async transitionIssue(_id, _state) {
|
|
802
|
-
throw new NotImplementedError(
|
|
803
|
-
"GitLab tracker is not yet implemented. Coming soon!"
|
|
804
|
-
);
|
|
805
|
-
}
|
|
806
|
-
async linkPR(_issueId, _prUrl) {
|
|
807
|
-
throw new NotImplementedError(
|
|
808
|
-
"GitLab tracker is not yet implemented. Coming soon!"
|
|
809
|
-
);
|
|
810
|
-
}
|
|
811
|
-
};
|
|
812
|
-
|
|
813
|
-
// src/lib/tracker/factory.ts
|
|
814
|
-
init_esm_shims();
|
|
815
|
-
|
|
816
|
-
// src/lib/tracker/rally.ts
|
|
817
|
-
init_esm_shims();
|
|
818
|
-
|
|
819
|
-
// src/lib/tracker/rally-api.ts
|
|
820
|
-
init_esm_shims();
|
|
821
|
-
var RallyRestApi = class {
|
|
822
|
-
apiKey;
|
|
823
|
-
server;
|
|
824
|
-
customHeaders;
|
|
825
|
-
constructor(config) {
|
|
826
|
-
this.apiKey = config.apiKey;
|
|
827
|
-
this.server = config.server || "https://rally1.rallydev.com";
|
|
828
|
-
this.customHeaders = config.requestOptions?.headers || {};
|
|
829
|
-
}
|
|
830
|
-
/**
|
|
831
|
-
* Query Rally artifacts
|
|
832
|
-
*/
|
|
833
|
-
async query(config) {
|
|
834
|
-
const params = new URLSearchParams();
|
|
835
|
-
if (config.query) {
|
|
836
|
-
params.set("query", config.query);
|
|
837
|
-
}
|
|
838
|
-
if (config.fetch && config.fetch.length > 0) {
|
|
839
|
-
params.set("fetch", config.fetch.join(","));
|
|
840
|
-
}
|
|
841
|
-
if (config.limit !== void 0) {
|
|
842
|
-
params.set("pagesize", String(config.limit));
|
|
843
|
-
}
|
|
844
|
-
if (config.workspace) {
|
|
845
|
-
params.set("workspace", config.workspace);
|
|
846
|
-
}
|
|
847
|
-
if (config.project) {
|
|
848
|
-
params.set("project", config.project);
|
|
849
|
-
if (config.projectScopeDown) {
|
|
850
|
-
params.set("projectScopeDown", "true");
|
|
851
|
-
}
|
|
852
|
-
}
|
|
853
|
-
if (config.order) {
|
|
854
|
-
params.set("order", config.order);
|
|
855
|
-
}
|
|
856
|
-
const url = `${this.server}/slm/webservice/v2.0/${config.type}?${params.toString()}`;
|
|
857
|
-
const response = await fetch(url, {
|
|
858
|
-
method: "GET",
|
|
859
|
-
headers: {
|
|
860
|
-
"ZSESSIONID": this.apiKey,
|
|
861
|
-
"Content-Type": "application/json",
|
|
862
|
-
...this.customHeaders
|
|
863
|
-
}
|
|
864
|
-
});
|
|
865
|
-
if (!response.ok) {
|
|
866
|
-
if (response.status === 401) {
|
|
867
|
-
throw new Error("Unauthorized: Invalid API key or insufficient permissions");
|
|
868
|
-
}
|
|
869
|
-
throw new Error(`Rally API query failed: ${response.status} ${response.statusText}`);
|
|
870
|
-
}
|
|
871
|
-
const result = await response.json();
|
|
872
|
-
if (result.QueryResult.Errors && result.QueryResult.Errors.length > 0) {
|
|
873
|
-
const errorDetail = result.QueryResult.Errors.join(", ");
|
|
874
|
-
const queryDetail = config.query ? ` (Query: ${config.query})` : "";
|
|
875
|
-
if (process.env.DEBUG?.includes("rally")) {
|
|
876
|
-
console.error("[Rally WSAPI] Query failed:", { query: config.query, errors: result.QueryResult.Errors });
|
|
877
|
-
}
|
|
878
|
-
throw new Error(`Rally API query failed: ${errorDetail}${queryDetail}`);
|
|
879
|
-
}
|
|
880
|
-
return result;
|
|
881
|
-
}
|
|
882
|
-
/**
|
|
883
|
-
* Create a Rally object
|
|
884
|
-
*/
|
|
885
|
-
async create(config) {
|
|
886
|
-
const url = `${this.server}/slm/webservice/v2.0/${config.type}/create`;
|
|
887
|
-
const body = {
|
|
888
|
-
[config.type]: config.data
|
|
889
|
-
};
|
|
890
|
-
const params = new URLSearchParams();
|
|
891
|
-
if (config.fetch && config.fetch.length > 0) {
|
|
892
|
-
params.set("fetch", config.fetch.join(","));
|
|
893
|
-
}
|
|
894
|
-
const finalUrl = params.toString() ? `${url}?${params.toString()}` : url;
|
|
895
|
-
const response = await fetch(finalUrl, {
|
|
896
|
-
method: "POST",
|
|
897
|
-
headers: {
|
|
898
|
-
"ZSESSIONID": this.apiKey,
|
|
899
|
-
"Content-Type": "application/json",
|
|
900
|
-
...this.customHeaders
|
|
901
|
-
},
|
|
902
|
-
body: JSON.stringify(body)
|
|
903
|
-
});
|
|
904
|
-
if (!response.ok) {
|
|
905
|
-
throw new Error(`Rally API create failed: ${response.status} ${response.statusText}`);
|
|
906
|
-
}
|
|
907
|
-
const result = await response.json();
|
|
908
|
-
if (result.CreateResult.Errors && result.CreateResult.Errors.length > 0) {
|
|
909
|
-
throw new Error(`Rally API create failed: ${result.CreateResult.Errors.join(", ")}`);
|
|
910
|
-
}
|
|
911
|
-
return result;
|
|
912
|
-
}
|
|
913
|
-
/**
|
|
914
|
-
* Update a Rally object
|
|
915
|
-
*/
|
|
916
|
-
async update(config) {
|
|
917
|
-
const objectId = config.ref.split("/").pop();
|
|
918
|
-
const url = `${this.server}/slm/webservice/v2.0/${config.type}/${objectId}`;
|
|
919
|
-
const body = {
|
|
920
|
-
[config.type]: config.data
|
|
921
|
-
};
|
|
922
|
-
const params = new URLSearchParams();
|
|
923
|
-
if (config.fetch && config.fetch.length > 0) {
|
|
924
|
-
params.set("fetch", config.fetch.join(","));
|
|
925
|
-
}
|
|
926
|
-
const finalUrl = params.toString() ? `${url}?${params.toString()}` : url;
|
|
927
|
-
const response = await fetch(finalUrl, {
|
|
928
|
-
method: "POST",
|
|
929
|
-
headers: {
|
|
930
|
-
"ZSESSIONID": this.apiKey,
|
|
931
|
-
"Content-Type": "application/json",
|
|
932
|
-
...this.customHeaders
|
|
933
|
-
},
|
|
934
|
-
body: JSON.stringify(body)
|
|
935
|
-
});
|
|
936
|
-
if (!response.ok) {
|
|
937
|
-
throw new Error(`Rally API update failed: ${response.status} ${response.statusText}`);
|
|
938
|
-
}
|
|
939
|
-
const result = await response.json();
|
|
940
|
-
if (result.OperationResult.Errors && result.OperationResult.Errors.length > 0) {
|
|
941
|
-
throw new Error(`Rally API update failed: ${result.OperationResult.Errors.join(", ")}`);
|
|
942
|
-
}
|
|
943
|
-
return result;
|
|
944
|
-
}
|
|
945
|
-
};
|
|
946
|
-
|
|
947
|
-
// src/lib/tracker/rally.ts
|
|
948
|
-
var STATE_MAP2 = {
|
|
949
|
-
// User Stories (ScheduleState)
|
|
950
|
-
"New": "open",
|
|
951
|
-
"Idea": "open",
|
|
952
|
-
"Defined": "open",
|
|
953
|
-
"In-Progress": "in_progress",
|
|
954
|
-
"Completed": "closed",
|
|
955
|
-
"Accepted": "closed",
|
|
956
|
-
// Defects (State)
|
|
957
|
-
"Submitted": "open",
|
|
958
|
-
"Open": "in_progress",
|
|
959
|
-
// "Open" defects are actively being worked
|
|
960
|
-
"Fixed": "closed",
|
|
961
|
-
"Closed": "closed",
|
|
962
|
-
// Features / PortfolioItems (State)
|
|
963
|
-
"Discovering": "open",
|
|
964
|
-
"Developing": "in_progress",
|
|
965
|
-
"Done": "closed"
|
|
966
|
-
};
|
|
967
|
-
var QUERYABLE_TYPES = [
|
|
968
|
-
{ type: "hierarchicalrequirement", stateField: "ScheduleState", closedStates: ["Completed", "Accepted"] },
|
|
969
|
-
{ type: "defect", stateField: "State", closedStates: ["Closed"] },
|
|
970
|
-
{ type: "task", stateField: "State", closedStates: ["Completed"] },
|
|
971
|
-
{ type: "portfolioitem/feature", stateField: "State", closedStates: ["Done"] }
|
|
972
|
-
];
|
|
973
|
-
var FETCH_FIELDS = [
|
|
974
|
-
"ObjectID",
|
|
975
|
-
"FormattedID",
|
|
976
|
-
"Name",
|
|
977
|
-
"Description",
|
|
978
|
-
"ScheduleState",
|
|
979
|
-
"State",
|
|
980
|
-
"Tags",
|
|
981
|
-
"Owner",
|
|
982
|
-
"Priority",
|
|
983
|
-
"DueDate",
|
|
984
|
-
"CreationDate",
|
|
985
|
-
"LastUpdateDate",
|
|
986
|
-
"Parent",
|
|
987
|
-
"PortfolioItem",
|
|
988
|
-
"_type"
|
|
989
|
-
];
|
|
990
|
-
var PRIORITY_MAP = {
|
|
991
|
-
"Resolve Immediately": 0,
|
|
992
|
-
High: 1,
|
|
993
|
-
Normal: 2,
|
|
994
|
-
Low: 3
|
|
995
|
-
};
|
|
996
|
-
var REVERSE_PRIORITY_MAP = {
|
|
997
|
-
0: "Resolve Immediately",
|
|
998
|
-
1: "High",
|
|
999
|
-
2: "Normal",
|
|
1000
|
-
3: "Low",
|
|
1001
|
-
4: "Low"
|
|
1002
|
-
};
|
|
1003
|
-
var RallyTracker = class {
|
|
1004
|
-
name = "rally";
|
|
1005
|
-
restApi;
|
|
1006
|
-
workspace;
|
|
1007
|
-
project;
|
|
1008
|
-
constructor(config) {
|
|
1009
|
-
if (!config.apiKey) {
|
|
1010
|
-
throw new TrackerAuthError("rally", "API key is required");
|
|
1011
|
-
}
|
|
1012
|
-
this.restApi = new RallyRestApi({
|
|
1013
|
-
apiKey: config.apiKey,
|
|
1014
|
-
server: config.server || "https://rally1.rallydev.com",
|
|
1015
|
-
requestOptions: {
|
|
1016
|
-
headers: {
|
|
1017
|
-
"X-RallyIntegrationType": "Panopticon",
|
|
1018
|
-
"X-RallyIntegrationName": "Panopticon CLI",
|
|
1019
|
-
"X-RallyIntegrationVendor": "Mind Your Now",
|
|
1020
|
-
"X-RallyIntegrationVersion": "0.2.0"
|
|
1021
|
-
}
|
|
1022
|
-
}
|
|
1023
|
-
});
|
|
1024
|
-
this.workspace = config.workspace;
|
|
1025
|
-
this.project = config.project;
|
|
1026
|
-
}
|
|
1027
|
-
/**
|
|
1028
|
-
* List issues by querying each artifact type separately and merging results.
|
|
1029
|
-
*
|
|
1030
|
-
* Rally WSAPI cannot apply ScheduleState filters across the generic Artifact
|
|
1031
|
-
* endpoint because not all subtypes have that field. We query each type with
|
|
1032
|
-
* its own state field, then merge and sort. (PAN-168)
|
|
1033
|
-
*/
|
|
1034
|
-
async listIssues(filters) {
|
|
1035
|
-
if (process.env.DEBUG?.includes("rally")) {
|
|
1036
|
-
console.debug("[Rally] Query filters:", JSON.stringify(filters));
|
|
1037
|
-
}
|
|
1038
|
-
const limit = filters?.limit ?? 50;
|
|
1039
|
-
let projectObjectId;
|
|
1040
|
-
if (this.project) {
|
|
1041
|
-
const match = this.project.match(/\/project\/(\d+)/);
|
|
1042
|
-
if (match) projectObjectId = match[1];
|
|
1043
|
-
}
|
|
1044
|
-
const queries = QUERYABLE_TYPES.map(async (artifactType) => {
|
|
1045
|
-
const queryString = this.buildQueryStringForType(filters, artifactType, projectObjectId);
|
|
1046
|
-
if (process.env.DEBUG?.includes("rally")) {
|
|
1047
|
-
console.debug(`[Rally] ${artifactType.type} query:`, queryString);
|
|
1048
|
-
}
|
|
1049
|
-
const query = {
|
|
1050
|
-
type: artifactType.type,
|
|
1051
|
-
fetch: FETCH_FIELDS,
|
|
1052
|
-
limit,
|
|
1053
|
-
query: queryString
|
|
1054
|
-
};
|
|
1055
|
-
if (this.workspace) {
|
|
1056
|
-
query.workspace = this.workspace;
|
|
1057
|
-
}
|
|
1058
|
-
if (this.project) {
|
|
1059
|
-
query.project = this.project;
|
|
1060
|
-
query.projectScopeDown = true;
|
|
1061
|
-
}
|
|
1062
|
-
try {
|
|
1063
|
-
const result = await this.queryRally(query);
|
|
1064
|
-
return result.Results.map((artifact) => this.normalizeIssue(artifact));
|
|
1065
|
-
} catch (error) {
|
|
1066
|
-
if (error.message?.includes("Unauthorized") || error.message?.includes("401")) {
|
|
1067
|
-
throw new TrackerAuthError("rally", "Invalid API key or insufficient permissions");
|
|
1068
|
-
}
|
|
1069
|
-
if (process.env.DEBUG?.includes("rally")) {
|
|
1070
|
-
console.debug(`[Rally] Failed to query ${artifactType.type}:`, error.message);
|
|
1071
|
-
}
|
|
1072
|
-
return [];
|
|
1073
|
-
}
|
|
1074
|
-
});
|
|
1075
|
-
const results = await Promise.all(queries);
|
|
1076
|
-
const allIssues = results.flat();
|
|
1077
|
-
allIssues.sort(
|
|
1078
|
-
(a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime()
|
|
1079
|
-
);
|
|
1080
|
-
return allIssues.slice(0, limit);
|
|
1081
|
-
}
|
|
1082
|
-
async getIssue(id) {
|
|
1083
|
-
try {
|
|
1084
|
-
const query = {
|
|
1085
|
-
type: "artifact",
|
|
1086
|
-
fetch: [
|
|
1087
|
-
"FormattedID",
|
|
1088
|
-
"Name",
|
|
1089
|
-
"Description",
|
|
1090
|
-
"ScheduleState",
|
|
1091
|
-
"State",
|
|
1092
|
-
"Tags",
|
|
1093
|
-
"Owner",
|
|
1094
|
-
"Priority",
|
|
1095
|
-
"DueDate",
|
|
1096
|
-
"CreationDate",
|
|
1097
|
-
"LastUpdateDate",
|
|
1098
|
-
"Parent",
|
|
1099
|
-
"_type"
|
|
1100
|
-
],
|
|
1101
|
-
query: `(FormattedID = "${id}")`
|
|
1102
|
-
};
|
|
1103
|
-
if (this.workspace) {
|
|
1104
|
-
query.workspace = this.workspace;
|
|
1105
|
-
}
|
|
1106
|
-
const result = await this.queryRally(query);
|
|
1107
|
-
if (!result.Results || result.Results.length === 0) {
|
|
1108
|
-
throw new IssueNotFoundError(id, "rally");
|
|
1109
|
-
}
|
|
1110
|
-
return this.normalizeIssue(result.Results[0]);
|
|
1111
|
-
} catch (error) {
|
|
1112
|
-
if (error instanceof IssueNotFoundError) throw error;
|
|
1113
|
-
throw new IssueNotFoundError(id, "rally");
|
|
1114
|
-
}
|
|
1115
|
-
}
|
|
1116
|
-
async updateIssue(id, update) {
|
|
1117
|
-
const issue = await this.getIssue(id);
|
|
1118
|
-
const query = {
|
|
1119
|
-
type: "artifact",
|
|
1120
|
-
fetch: ["ObjectID", "_ref", "_type"],
|
|
1121
|
-
query: `(FormattedID = "${id}")`
|
|
1122
|
-
};
|
|
1123
|
-
if (this.workspace) {
|
|
1124
|
-
query.workspace = this.workspace;
|
|
1125
|
-
}
|
|
1126
|
-
const result = await this.queryRally(query);
|
|
1127
|
-
if (!result.Results || result.Results.length === 0) {
|
|
1128
|
-
throw new IssueNotFoundError(id, "rally");
|
|
1129
|
-
}
|
|
1130
|
-
const artifact = result.Results[0];
|
|
1131
|
-
const updatePayload = {};
|
|
1132
|
-
if (update.title !== void 0) {
|
|
1133
|
-
updatePayload.Name = update.title;
|
|
1134
|
-
}
|
|
1135
|
-
if (update.description !== void 0) {
|
|
1136
|
-
updatePayload.Description = update.description;
|
|
1137
|
-
}
|
|
1138
|
-
if (update.state !== void 0) {
|
|
1139
|
-
const artifactType = (artifact._type || "").toLowerCase();
|
|
1140
|
-
const kind = artifactType.startsWith("portfolioitem") ? "feature" : artifactType === "defect" ? "defect" : "story";
|
|
1141
|
-
const rallyState = this.reverseMapState(update.state, kind);
|
|
1142
|
-
if (kind === "story") {
|
|
1143
|
-
updatePayload.ScheduleState = rallyState;
|
|
1144
|
-
} else {
|
|
1145
|
-
updatePayload.State = rallyState;
|
|
1146
|
-
}
|
|
1147
|
-
}
|
|
1148
|
-
if (update.priority !== void 0) {
|
|
1149
|
-
updatePayload.Priority = REVERSE_PRIORITY_MAP[update.priority] || "Normal";
|
|
1150
|
-
}
|
|
1151
|
-
if (update.dueDate !== void 0) {
|
|
1152
|
-
updatePayload.DueDate = update.dueDate;
|
|
1153
|
-
}
|
|
1154
|
-
if (Object.keys(updatePayload).length > 0) {
|
|
1155
|
-
await this.updateRally(artifact._type.toLowerCase(), artifact._ref, updatePayload);
|
|
1156
|
-
}
|
|
1157
|
-
return this.getIssue(id);
|
|
1158
|
-
}
|
|
1159
|
-
async createIssue(newIssue) {
|
|
1160
|
-
if (!this.project && !newIssue.team) {
|
|
1161
|
-
throw new Error("Project is required to create an issue. Set it in config or provide team field.");
|
|
1162
|
-
}
|
|
1163
|
-
const project = newIssue.team || this.project;
|
|
1164
|
-
const createPayload = {
|
|
1165
|
-
Name: newIssue.title,
|
|
1166
|
-
Description: newIssue.description || "",
|
|
1167
|
-
Project: project
|
|
1168
|
-
};
|
|
1169
|
-
if (newIssue.priority !== void 0) {
|
|
1170
|
-
createPayload.Priority = REVERSE_PRIORITY_MAP[newIssue.priority] || "Normal";
|
|
1171
|
-
}
|
|
1172
|
-
if (newIssue.dueDate) {
|
|
1173
|
-
createPayload.DueDate = newIssue.dueDate;
|
|
1174
|
-
}
|
|
1175
|
-
if (this.workspace) {
|
|
1176
|
-
createPayload.Workspace = this.workspace;
|
|
1177
|
-
}
|
|
1178
|
-
const result = await this.createRally("hierarchicalrequirement", createPayload);
|
|
1179
|
-
return this.getIssue(result.Object.FormattedID);
|
|
1180
|
-
}
|
|
1181
|
-
async getComments(issueId) {
|
|
1182
|
-
const issue = await this.getIssue(issueId);
|
|
1183
|
-
const query = {
|
|
1184
|
-
type: "artifact",
|
|
1185
|
-
fetch: ["ObjectID", "_ref", "Discussion"],
|
|
1186
|
-
query: `(FormattedID = "${issueId}")`
|
|
1187
|
-
};
|
|
1188
|
-
if (this.workspace) {
|
|
1189
|
-
query.workspace = this.workspace;
|
|
1190
|
-
}
|
|
1191
|
-
const result = await this.queryRally(query);
|
|
1192
|
-
if (!result.Results || result.Results.length === 0) {
|
|
1193
|
-
return [];
|
|
1194
|
-
}
|
|
1195
|
-
const artifact = result.Results[0];
|
|
1196
|
-
if (!artifact.Discussion) {
|
|
1197
|
-
return [];
|
|
1198
|
-
}
|
|
1199
|
-
const postsQuery = {
|
|
1200
|
-
type: "conversationpost",
|
|
1201
|
-
fetch: ["ObjectID", "Text", "User", "CreationDate", "PostNumber"],
|
|
1202
|
-
query: `(Discussion = "${artifact.Discussion._ref}")`,
|
|
1203
|
-
order: "PostNumber"
|
|
1204
|
-
};
|
|
1205
|
-
const postsResult = await this.queryRally(postsQuery);
|
|
1206
|
-
return (postsResult.Results || []).map((post) => ({
|
|
1207
|
-
id: post.ObjectID,
|
|
1208
|
-
issueId,
|
|
1209
|
-
body: post.Text || "",
|
|
1210
|
-
author: post.User?._refObjectName || "Unknown",
|
|
1211
|
-
createdAt: post.CreationDate,
|
|
1212
|
-
updatedAt: post.CreationDate
|
|
1213
|
-
// Rally doesn't track comment updates separately
|
|
1214
|
-
}));
|
|
1215
|
-
}
|
|
1216
|
-
async addComment(issueId, body) {
|
|
1217
|
-
const query = {
|
|
1218
|
-
type: "artifact",
|
|
1219
|
-
fetch: ["ObjectID", "_ref", "Discussion"],
|
|
1220
|
-
query: `(FormattedID = "${issueId}")`
|
|
1221
|
-
};
|
|
1222
|
-
if (this.workspace) {
|
|
1223
|
-
query.workspace = this.workspace;
|
|
1224
|
-
}
|
|
1225
|
-
const result = await this.queryRally(query);
|
|
1226
|
-
if (!result.Results || result.Results.length === 0) {
|
|
1227
|
-
throw new IssueNotFoundError(issueId, "rally");
|
|
1228
|
-
}
|
|
1229
|
-
const artifact = result.Results[0];
|
|
1230
|
-
let discussionRef = artifact.Discussion?._ref;
|
|
1231
|
-
if (!discussionRef) {
|
|
1232
|
-
const discussionResult = await this.createRally("conversationpost", {
|
|
1233
|
-
Artifact: artifact._ref,
|
|
1234
|
-
Text: body
|
|
1235
|
-
});
|
|
1236
|
-
return {
|
|
1237
|
-
id: discussionResult.Object.ObjectID,
|
|
1238
|
-
issueId,
|
|
1239
|
-
body,
|
|
1240
|
-
author: "Panopticon",
|
|
1241
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1242
|
-
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1243
|
-
};
|
|
1244
|
-
}
|
|
1245
|
-
const postResult = await this.createRally("conversationpost", {
|
|
1246
|
-
Artifact: artifact._ref,
|
|
1247
|
-
Text: body
|
|
1248
|
-
});
|
|
1249
|
-
return {
|
|
1250
|
-
id: postResult.Object.ObjectID,
|
|
1251
|
-
issueId,
|
|
1252
|
-
body,
|
|
1253
|
-
author: "Panopticon",
|
|
1254
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1255
|
-
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1256
|
-
};
|
|
1257
|
-
}
|
|
1258
|
-
async transitionIssue(id, state) {
|
|
1259
|
-
await this.updateIssue(id, { state });
|
|
1260
|
-
}
|
|
1261
|
-
async linkPR(issueId, prUrl) {
|
|
1262
|
-
await this.addComment(issueId, `Linked Pull Request: ${prUrl}`);
|
|
1263
|
-
}
|
|
1264
|
-
// Private helper methods
|
|
1265
|
-
/**
|
|
1266
|
-
* Build a Rally WSAPI query string for a specific artifact type.
|
|
1267
|
-
*
|
|
1268
|
-
* Each artifact type has its own state field:
|
|
1269
|
-
* - HierarchicalRequirement: ScheduleState (Defined, In-Progress, Completed, Accepted)
|
|
1270
|
-
* - Defect: State (Submitted, Open, Fixed, Closed)
|
|
1271
|
-
* - Task: State (Defined, In-Progress, Completed)
|
|
1272
|
-
*
|
|
1273
|
-
* Rally WSAPI v2.0 requires binary-nested AND/OR with outer parentheses.
|
|
1274
|
-
* (PAN-166, PAN-168)
|
|
1275
|
-
*/
|
|
1276
|
-
buildQueryStringForType(filters, artifactType, projectObjectId) {
|
|
1277
|
-
const conditions = [];
|
|
1278
|
-
if (projectObjectId) {
|
|
1279
|
-
conditions.push(`(Project.ObjectID = "${projectObjectId}")`);
|
|
1280
|
-
}
|
|
1281
|
-
if (filters?.state && !filters.includeClosed) {
|
|
1282
|
-
const kind = artifactType.type.startsWith("portfolioitem") ? "feature" : artifactType.type === "defect" ? "defect" : "story";
|
|
1283
|
-
const rallyState = this.reverseMapState(filters.state, kind);
|
|
1284
|
-
conditions.push(`(${artifactType.stateField} = "${rallyState}")`);
|
|
1285
|
-
}
|
|
1286
|
-
if (!filters?.includeClosed) {
|
|
1287
|
-
const closedConditions = artifactType.closedStates.map(
|
|
1288
|
-
(state) => `(${artifactType.stateField} != "${state}")`
|
|
1289
|
-
);
|
|
1290
|
-
const closedExpr = closedConditions.reduce(
|
|
1291
|
-
(acc, cond) => acc ? `(${acc} AND ${cond})` : cond,
|
|
1292
|
-
""
|
|
1293
|
-
);
|
|
1294
|
-
conditions.push(closedExpr);
|
|
1295
|
-
}
|
|
1296
|
-
if (filters?.assignee) {
|
|
1297
|
-
conditions.push(`(Owner.Name contains "${filters.assignee}")`);
|
|
1298
|
-
}
|
|
1299
|
-
if (filters?.labels && filters.labels.length > 0) {
|
|
1300
|
-
const labelConditions = filters.labels.map(
|
|
1301
|
-
(label) => `(Tags.Name contains "${label}")`
|
|
1302
|
-
);
|
|
1303
|
-
const labelExpr = labelConditions.reduce((acc, cond) => acc ? `(${acc} AND ${cond})` : cond, "");
|
|
1304
|
-
conditions.push(labelExpr);
|
|
1305
|
-
}
|
|
1306
|
-
if (filters?.query) {
|
|
1307
|
-
conditions.push(`((Name contains "${filters.query}") OR (Description contains "${filters.query}"))`);
|
|
1308
|
-
}
|
|
1309
|
-
return conditions.reduce((acc, cond) => acc ? `(${acc} AND ${cond})` : cond, "");
|
|
1310
|
-
}
|
|
1311
|
-
normalizeIssue(rallyArtifact) {
|
|
1312
|
-
const rawStateValue = rallyArtifact.ScheduleState || rallyArtifact.State || "Defined";
|
|
1313
|
-
const stateValue = typeof rawStateValue === "object" && rawStateValue !== null ? rawStateValue.Name || rawStateValue._refObjectName || "Defined" : rawStateValue;
|
|
1314
|
-
const state = this.mapState(stateValue);
|
|
1315
|
-
const labels = [];
|
|
1316
|
-
if (rallyArtifact.Tags && rallyArtifact.Tags._tagsNameArray) {
|
|
1317
|
-
for (const tag of rallyArtifact.Tags._tagsNameArray) {
|
|
1318
|
-
if (typeof tag === "string") {
|
|
1319
|
-
labels.push(tag);
|
|
1320
|
-
} else if (tag?.Name) {
|
|
1321
|
-
labels.push(tag.Name);
|
|
1322
|
-
}
|
|
1323
|
-
}
|
|
1324
|
-
}
|
|
1325
|
-
const priority = rallyArtifact.Priority ? PRIORITY_MAP[rallyArtifact.Priority] ?? 2 : void 0;
|
|
1326
|
-
const objectId = rallyArtifact.ObjectID || rallyArtifact.FormattedID;
|
|
1327
|
-
const artifactType = rallyArtifact._type || "artifact";
|
|
1328
|
-
const baseUrl = this.restApi.server.replace("/slm/webservice/", "");
|
|
1329
|
-
const url = `${baseUrl}/#/detail/${artifactType.toLowerCase()}/${objectId}`;
|
|
1330
|
-
let parentRef;
|
|
1331
|
-
if (rallyArtifact.PortfolioItem) {
|
|
1332
|
-
if (rallyArtifact.PortfolioItem.FormattedID) {
|
|
1333
|
-
parentRef = rallyArtifact.PortfolioItem.FormattedID;
|
|
1334
|
-
} else if (rallyArtifact.PortfolioItem._refObjectName) {
|
|
1335
|
-
parentRef = rallyArtifact.PortfolioItem._refObjectName;
|
|
1336
|
-
}
|
|
1337
|
-
} else if (rallyArtifact.Parent) {
|
|
1338
|
-
if (rallyArtifact.Parent.FormattedID) {
|
|
1339
|
-
parentRef = rallyArtifact.Parent.FormattedID;
|
|
1340
|
-
} else if (rallyArtifact.Parent._refObjectName) {
|
|
1341
|
-
parentRef = rallyArtifact.Parent._refObjectName;
|
|
1342
|
-
}
|
|
1343
|
-
}
|
|
1344
|
-
return {
|
|
1345
|
-
id: String(objectId),
|
|
1346
|
-
ref: rallyArtifact.FormattedID,
|
|
1347
|
-
title: rallyArtifact.Name || "",
|
|
1348
|
-
description: rallyArtifact.Description || "",
|
|
1349
|
-
state,
|
|
1350
|
-
labels,
|
|
1351
|
-
assignee: rallyArtifact.Owner?._refObjectName,
|
|
1352
|
-
url,
|
|
1353
|
-
tracker: "rally",
|
|
1354
|
-
priority,
|
|
1355
|
-
dueDate: rallyArtifact.DueDate,
|
|
1356
|
-
createdAt: rallyArtifact.CreationDate,
|
|
1357
|
-
updatedAt: rallyArtifact.LastUpdateDate,
|
|
1358
|
-
parentRef,
|
|
1359
|
-
artifactType,
|
|
1360
|
-
rawState: stateValue
|
|
1361
|
-
};
|
|
1362
|
-
}
|
|
1363
|
-
mapState(rallyState) {
|
|
1364
|
-
return STATE_MAP2[rallyState] ?? "open";
|
|
1365
|
-
}
|
|
1366
|
-
reverseMapState(state, kind = "story") {
|
|
1367
|
-
if (kind === "feature") {
|
|
1368
|
-
switch (state) {
|
|
1369
|
-
case "open":
|
|
1370
|
-
return "Discovering";
|
|
1371
|
-
case "in_progress":
|
|
1372
|
-
return "Developing";
|
|
1373
|
-
case "closed":
|
|
1374
|
-
return "Done";
|
|
1375
|
-
default:
|
|
1376
|
-
return "Discovering";
|
|
1377
|
-
}
|
|
1378
|
-
}
|
|
1379
|
-
if (kind === "defect") {
|
|
1380
|
-
switch (state) {
|
|
1381
|
-
case "open":
|
|
1382
|
-
return "Submitted";
|
|
1383
|
-
case "in_progress":
|
|
1384
|
-
return "Open";
|
|
1385
|
-
case "closed":
|
|
1386
|
-
return "Closed";
|
|
1387
|
-
default:
|
|
1388
|
-
return "Submitted";
|
|
1389
|
-
}
|
|
1390
|
-
}
|
|
1391
|
-
switch (state) {
|
|
1392
|
-
case "open":
|
|
1393
|
-
return "Defined";
|
|
1394
|
-
case "in_progress":
|
|
1395
|
-
return "In-Progress";
|
|
1396
|
-
case "closed":
|
|
1397
|
-
return "Completed";
|
|
1398
|
-
default:
|
|
1399
|
-
return "Defined";
|
|
1400
|
-
}
|
|
1401
|
-
}
|
|
1402
|
-
// Rally API wrapper methods
|
|
1403
|
-
async queryRally(queryConfig) {
|
|
1404
|
-
const result = await this.restApi.query(queryConfig);
|
|
1405
|
-
return {
|
|
1406
|
-
Results: result.QueryResult.Results,
|
|
1407
|
-
TotalResultCount: result.QueryResult.TotalResultCount
|
|
1408
|
-
};
|
|
1409
|
-
}
|
|
1410
|
-
async createRally(type, data) {
|
|
1411
|
-
const result = await this.restApi.create({
|
|
1412
|
-
type,
|
|
1413
|
-
data,
|
|
1414
|
-
fetch: ["FormattedID", "ObjectID", "_ref"]
|
|
1415
|
-
});
|
|
1416
|
-
return {
|
|
1417
|
-
Object: result.CreateResult.Object
|
|
1418
|
-
};
|
|
1419
|
-
}
|
|
1420
|
-
async updateRally(type, ref, data) {
|
|
1421
|
-
const result = await this.restApi.update({
|
|
1422
|
-
type,
|
|
1423
|
-
ref,
|
|
1424
|
-
data,
|
|
1425
|
-
fetch: ["FormattedID", "ObjectID"]
|
|
1426
|
-
});
|
|
1427
|
-
return {
|
|
1428
|
-
Object: result.OperationResult.Object
|
|
1429
|
-
};
|
|
1430
|
-
}
|
|
1431
|
-
};
|
|
1432
|
-
|
|
1433
|
-
// src/lib/tracker/factory.ts
|
|
1434
|
-
init_config_yaml();
|
|
1435
|
-
function getTrackerKeyFromConfig(trackerType) {
|
|
1436
|
-
try {
|
|
1437
|
-
const yamlConfig = loadConfig();
|
|
1438
|
-
return yamlConfig.trackerKeys[trackerType];
|
|
1439
|
-
} catch {
|
|
1440
|
-
return void 0;
|
|
1441
|
-
}
|
|
1442
|
-
}
|
|
1443
|
-
function createTracker(config) {
|
|
1444
|
-
switch (config.type) {
|
|
1445
|
-
case "linear": {
|
|
1446
|
-
const configKey = getTrackerKeyFromConfig("linear");
|
|
1447
|
-
const envKey = config.apiKeyEnv ? process.env[config.apiKeyEnv] : process.env.LINEAR_API_KEY;
|
|
1448
|
-
const apiKey = configKey || envKey;
|
|
1449
|
-
if (!apiKey) {
|
|
1450
|
-
throw new TrackerAuthError(
|
|
1451
|
-
"linear",
|
|
1452
|
-
`API key not found. Configure in Settings or set ${config.apiKeyEnv ?? "LINEAR_API_KEY"} environment variable.`
|
|
1453
|
-
);
|
|
1454
|
-
}
|
|
1455
|
-
return new LinearTracker(apiKey, { team: config.team });
|
|
1456
|
-
}
|
|
1457
|
-
case "github": {
|
|
1458
|
-
const configKey = getTrackerKeyFromConfig("github");
|
|
1459
|
-
const envToken = config.tokenEnv ? process.env[config.tokenEnv] : process.env.GITHUB_TOKEN;
|
|
1460
|
-
const token = configKey || envToken;
|
|
1461
|
-
if (!token) {
|
|
1462
|
-
throw new TrackerAuthError(
|
|
1463
|
-
"github",
|
|
1464
|
-
`Token not found. Configure in Settings or set ${config.tokenEnv ?? "GITHUB_TOKEN"} environment variable.`
|
|
1465
|
-
);
|
|
1466
|
-
}
|
|
1467
|
-
if (!config.owner || !config.repo) {
|
|
1468
|
-
throw new Error(
|
|
1469
|
-
"GitHub tracker requires owner and repo configuration"
|
|
1470
|
-
);
|
|
1471
|
-
}
|
|
1472
|
-
return new GitHubTracker(token, config.owner, config.repo);
|
|
1473
|
-
}
|
|
1474
|
-
case "gitlab": {
|
|
1475
|
-
const configKey = getTrackerKeyFromConfig("gitlab");
|
|
1476
|
-
const envToken = config.tokenEnv ? process.env[config.tokenEnv] : process.env.GITLAB_TOKEN;
|
|
1477
|
-
const token = configKey || envToken;
|
|
1478
|
-
if (!token) {
|
|
1479
|
-
throw new TrackerAuthError(
|
|
1480
|
-
"gitlab",
|
|
1481
|
-
`Token not found. Configure in Settings or set ${config.tokenEnv ?? "GITLAB_TOKEN"} environment variable.`
|
|
1482
|
-
);
|
|
1483
|
-
}
|
|
1484
|
-
if (!config.projectId) {
|
|
1485
|
-
throw new Error("GitLab tracker requires projectId configuration");
|
|
1486
|
-
}
|
|
1487
|
-
return new GitLabTracker(token, config.projectId);
|
|
1488
|
-
}
|
|
1489
|
-
case "rally": {
|
|
1490
|
-
const configKey = getTrackerKeyFromConfig("rally");
|
|
1491
|
-
const envKey = config.apiKeyEnv ? process.env[config.apiKeyEnv] : process.env.RALLY_API_KEY;
|
|
1492
|
-
const apiKey = configKey || envKey;
|
|
1493
|
-
if (!apiKey) {
|
|
1494
|
-
throw new TrackerAuthError(
|
|
1495
|
-
"rally",
|
|
1496
|
-
`API key not found. Configure in Settings or set ${config.apiKeyEnv ?? "RALLY_API_KEY"} environment variable.`
|
|
1497
|
-
);
|
|
1498
|
-
}
|
|
1499
|
-
return new RallyTracker({
|
|
1500
|
-
apiKey,
|
|
1501
|
-
server: config.server,
|
|
1502
|
-
workspace: config.workspace,
|
|
1503
|
-
project: config.project
|
|
1504
|
-
});
|
|
1505
|
-
}
|
|
1506
|
-
default:
|
|
1507
|
-
throw new Error(`Unknown tracker type: ${config.type}`);
|
|
1508
|
-
}
|
|
1509
|
-
}
|
|
1510
|
-
function createTrackerFromConfig(trackersConfig, trackerType) {
|
|
1511
|
-
const config = trackersConfig[trackerType];
|
|
1512
|
-
if (!config) {
|
|
1513
|
-
throw new Error(
|
|
1514
|
-
`No configuration found for tracker: ${trackerType}. Add [trackers.${trackerType}] to config.`
|
|
1515
|
-
);
|
|
1516
|
-
}
|
|
1517
|
-
return createTracker({ ...config, type: trackerType });
|
|
1518
|
-
}
|
|
1519
|
-
function getPrimaryTracker(trackersConfig) {
|
|
1520
|
-
return createTrackerFromConfig(trackersConfig, trackersConfig.primary);
|
|
1521
|
-
}
|
|
1522
|
-
function getSecondaryTracker(trackersConfig) {
|
|
1523
|
-
if (!trackersConfig.secondary) {
|
|
1524
|
-
return null;
|
|
1525
|
-
}
|
|
1526
|
-
return createTrackerFromConfig(trackersConfig, trackersConfig.secondary);
|
|
1527
|
-
}
|
|
1528
|
-
function getAllTrackers(trackersConfig) {
|
|
1529
|
-
const trackers = [getPrimaryTracker(trackersConfig)];
|
|
1530
|
-
const secondary = getSecondaryTracker(trackersConfig);
|
|
1531
|
-
if (secondary) {
|
|
1532
|
-
trackers.push(secondary);
|
|
1533
|
-
}
|
|
1534
|
-
return trackers;
|
|
1535
|
-
}
|
|
1536
|
-
|
|
1537
|
-
// src/lib/tracker/linking.ts
|
|
1538
|
-
init_esm_shims();
|
|
1539
|
-
import { readFileSync as readFileSync2, writeFileSync, existsSync as existsSync4, mkdirSync as mkdirSync3 } from "fs";
|
|
1540
|
-
import { join as join4 } from "path";
|
|
1541
|
-
import { homedir as homedir2 } from "os";
|
|
1542
|
-
function parseIssueRef(ref) {
|
|
1543
|
-
if (ref.startsWith("github#")) {
|
|
1544
|
-
return { tracker: "github", ref: `#${ref.slice(7)}` };
|
|
1545
|
-
}
|
|
1546
|
-
if (ref.startsWith("gitlab#")) {
|
|
1547
|
-
return { tracker: "gitlab", ref: `#${ref.slice(7)}` };
|
|
1548
|
-
}
|
|
1549
|
-
if (ref.startsWith("linear:")) {
|
|
1550
|
-
return { tracker: "linear", ref: ref.slice(7) };
|
|
1551
|
-
}
|
|
1552
|
-
if (/^#\d+$/.test(ref)) {
|
|
1553
|
-
return { tracker: "github", ref };
|
|
1554
|
-
}
|
|
1555
|
-
if (/^[A-Z]+-\d+$/i.test(ref)) {
|
|
1556
|
-
return { tracker: "linear", ref: ref.toUpperCase() };
|
|
1557
|
-
}
|
|
1558
|
-
return null;
|
|
1559
|
-
}
|
|
1560
|
-
function formatIssueRef(ref, tracker) {
|
|
1561
|
-
if (tracker === "github") {
|
|
1562
|
-
return ref.startsWith("#") ? `github${ref}` : `github#${ref}`;
|
|
1563
|
-
}
|
|
1564
|
-
if (tracker === "gitlab") {
|
|
1565
|
-
return ref.startsWith("#") ? `gitlab${ref}` : `gitlab#${ref}`;
|
|
1566
|
-
}
|
|
1567
|
-
return ref;
|
|
1568
|
-
}
|
|
1569
|
-
var LinkManager = class {
|
|
1570
|
-
storePath;
|
|
1571
|
-
store;
|
|
1572
|
-
constructor(storePath) {
|
|
1573
|
-
this.storePath = storePath ?? join4(homedir2(), ".panopticon", "links.json");
|
|
1574
|
-
this.store = this.load();
|
|
1575
|
-
}
|
|
1576
|
-
load() {
|
|
1577
|
-
if (existsSync4(this.storePath)) {
|
|
1578
|
-
try {
|
|
1579
|
-
const data = JSON.parse(readFileSync2(this.storePath, "utf-8"));
|
|
1580
|
-
if (data.version === 1) {
|
|
1581
|
-
return data;
|
|
1582
|
-
}
|
|
1583
|
-
} catch {
|
|
1584
|
-
}
|
|
1585
|
-
}
|
|
1586
|
-
return { version: 1, links: [] };
|
|
1587
|
-
}
|
|
1588
|
-
save() {
|
|
1589
|
-
const dir = join4(this.storePath, "..");
|
|
1590
|
-
if (!existsSync4(dir)) {
|
|
1591
|
-
mkdirSync3(dir, { recursive: true });
|
|
1592
|
-
}
|
|
1593
|
-
writeFileSync(this.storePath, JSON.stringify(this.store, null, 2));
|
|
1594
|
-
}
|
|
1595
|
-
/**
|
|
1596
|
-
* Add a link between two issues
|
|
1597
|
-
*/
|
|
1598
|
-
addLink(source, target, direction = "related") {
|
|
1599
|
-
const existing = this.store.links.find(
|
|
1600
|
-
(l) => l.sourceIssueRef === source.ref && l.sourceTracker === source.tracker && l.targetIssueRef === target.ref && l.targetTracker === target.tracker
|
|
1601
|
-
);
|
|
1602
|
-
if (existing) {
|
|
1603
|
-
if (existing.direction !== direction) {
|
|
1604
|
-
existing.direction = direction;
|
|
1605
|
-
this.save();
|
|
1606
|
-
}
|
|
1607
|
-
return existing;
|
|
1608
|
-
}
|
|
1609
|
-
const link = {
|
|
1610
|
-
sourceIssueRef: source.ref,
|
|
1611
|
-
sourceTracker: source.tracker,
|
|
1612
|
-
targetIssueRef: target.ref,
|
|
1613
|
-
targetTracker: target.tracker,
|
|
1614
|
-
direction,
|
|
1615
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1616
|
-
};
|
|
1617
|
-
this.store.links.push(link);
|
|
1618
|
-
this.save();
|
|
1619
|
-
return link;
|
|
1620
|
-
}
|
|
1621
|
-
/**
|
|
1622
|
-
* Remove a link between two issues
|
|
1623
|
-
*/
|
|
1624
|
-
removeLink(source, target) {
|
|
1625
|
-
const index = this.store.links.findIndex(
|
|
1626
|
-
(l) => l.sourceIssueRef === source.ref && l.sourceTracker === source.tracker && l.targetIssueRef === target.ref && l.targetTracker === target.tracker
|
|
1627
|
-
);
|
|
1628
|
-
if (index >= 0) {
|
|
1629
|
-
this.store.links.splice(index, 1);
|
|
1630
|
-
this.save();
|
|
1631
|
-
return true;
|
|
1632
|
-
}
|
|
1633
|
-
return false;
|
|
1634
|
-
}
|
|
1635
|
-
/**
|
|
1636
|
-
* Get all issues linked to a given issue
|
|
1637
|
-
*/
|
|
1638
|
-
getLinkedIssues(ref, tracker) {
|
|
1639
|
-
return this.store.links.filter(
|
|
1640
|
-
(l) => l.sourceIssueRef === ref && l.sourceTracker === tracker || l.targetIssueRef === ref && l.targetTracker === tracker
|
|
1641
|
-
);
|
|
1642
|
-
}
|
|
1643
|
-
/**
|
|
1644
|
-
* Get all links (for debugging/admin)
|
|
1645
|
-
*/
|
|
1646
|
-
getAllLinks() {
|
|
1647
|
-
return [...this.store.links];
|
|
1648
|
-
}
|
|
1649
|
-
/**
|
|
1650
|
-
* Find linked issue in another tracker
|
|
1651
|
-
*/
|
|
1652
|
-
findLinkedIssue(ref, sourceTracker, targetTracker) {
|
|
1653
|
-
const asSource = this.store.links.find(
|
|
1654
|
-
(l) => l.sourceIssueRef === ref && l.sourceTracker === sourceTracker && l.targetTracker === targetTracker
|
|
1655
|
-
);
|
|
1656
|
-
if (asSource) return asSource.targetIssueRef;
|
|
1657
|
-
const asTarget = this.store.links.find(
|
|
1658
|
-
(l) => l.targetIssueRef === ref && l.targetTracker === sourceTracker && l.sourceTracker === targetTracker
|
|
1659
|
-
);
|
|
1660
|
-
if (asTarget) return asTarget.sourceIssueRef;
|
|
1661
|
-
return null;
|
|
1662
|
-
}
|
|
1663
|
-
/**
|
|
1664
|
-
* Clear all links (for testing)
|
|
1665
|
-
*/
|
|
1666
|
-
clear() {
|
|
1667
|
-
this.store.links = [];
|
|
1668
|
-
this.save();
|
|
1669
|
-
}
|
|
1670
|
-
};
|
|
1671
|
-
var _linkManager = null;
|
|
1672
|
-
function getLinkManager() {
|
|
1673
|
-
if (!_linkManager) {
|
|
1674
|
-
_linkManager = new LinkManager();
|
|
1675
|
-
}
|
|
1676
|
-
return _linkManager;
|
|
1677
|
-
}
|
|
1678
|
-
|
|
1679
|
-
// src/lib/tracker/index.ts
|
|
1680
|
-
init_esm_shims();
|
|
1681
|
-
|
|
1682
|
-
export {
|
|
1683
|
-
detectShell,
|
|
1684
|
-
getShellRcFile,
|
|
1685
|
-
hasAlias,
|
|
1686
|
-
addAlias,
|
|
1687
|
-
getAliasInstructions,
|
|
1688
|
-
createBackupTimestamp,
|
|
1689
|
-
createBackup,
|
|
1690
|
-
listBackups,
|
|
1691
|
-
restoreBackup,
|
|
1692
|
-
cleanOldBackups,
|
|
1693
|
-
isPanopticonSymlink,
|
|
1694
|
-
planSync,
|
|
1695
|
-
executeSync,
|
|
1696
|
-
planHooksSync,
|
|
1697
|
-
syncHooks,
|
|
1698
|
-
NotImplementedError,
|
|
1699
|
-
IssueNotFoundError,
|
|
1700
|
-
TrackerAuthError,
|
|
1701
|
-
LinearTracker,
|
|
1702
|
-
GitHubTracker,
|
|
1703
|
-
GitLabTracker,
|
|
1704
|
-
createTracker,
|
|
1705
|
-
createTrackerFromConfig,
|
|
1706
|
-
getPrimaryTracker,
|
|
1707
|
-
getSecondaryTracker,
|
|
1708
|
-
getAllTrackers,
|
|
1709
|
-
parseIssueRef,
|
|
1710
|
-
formatIssueRef,
|
|
1711
|
-
LinkManager,
|
|
1712
|
-
getLinkManager
|
|
1713
|
-
};
|
|
1714
|
-
//# sourceMappingURL=chunk-SUMIHS2B.js.map
|