panopticon-cli 0.5.4 → 0.5.6
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-HNMF52RM.js → agents-5HWTDR4S.js} +12 -9
- package/dist/archive-planning-U3AZAKWI.js +16 -0
- package/dist/{chunk-KBHRXV5T.js → chunk-43F4LDZ4.js} +3 -3
- package/dist/chunk-6OYUJ4AJ.js +146 -0
- package/dist/chunk-6OYUJ4AJ.js.map +1 -0
- package/dist/{chunk-MOPGR3CL.js → chunk-AAP4G6U7.js} +1 -1
- package/dist/chunk-AAP4G6U7.js.map +1 -0
- package/dist/{chunk-4HST45MO.js → chunk-BYWVPPAZ.js} +19 -12
- package/dist/chunk-BYWVPPAZ.js.map +1 -0
- package/dist/{chunk-CFCUOV3Q.js → chunk-DMRTN432.js} +4 -1
- package/dist/chunk-DMRTN432.js.map +1 -0
- package/dist/{chunk-HOGYHJ2G.js → chunk-DW3PKGIS.js} +2 -2
- package/dist/{chunk-KY2E2Q3T.js → chunk-FUUP55PE.js} +104 -46
- package/dist/chunk-FUUP55PE.js.map +1 -0
- package/dist/chunk-GUV2EPBG.js +692 -0
- package/dist/chunk-GUV2EPBG.js.map +1 -0
- package/dist/{chunk-44EOY2ZL.js → chunk-HHL3AWXA.js} +46 -2
- package/dist/chunk-HHL3AWXA.js.map +1 -0
- package/dist/{chunk-6N2KBSJA.js → chunk-IZIXJYXZ.js} +40 -6
- package/dist/chunk-IZIXJYXZ.js.map +1 -0
- package/dist/chunk-MJXYTGK5.js +64 -0
- package/dist/chunk-MJXYTGK5.js.map +1 -0
- package/dist/chunk-OJF4QS3S.js +269 -0
- package/dist/chunk-OJF4QS3S.js.map +1 -0
- package/dist/{chunk-FQ66DECN.js → chunk-QAJAJBFW.js} +1 -1
- package/dist/chunk-QAJAJBFW.js.map +1 -0
- package/dist/chunk-R4KPLLRB.js +36 -0
- package/dist/chunk-R4KPLLRB.js.map +1 -0
- package/dist/{chunk-DFNVHK3N.js → chunk-SUM2WVPF.js} +4 -4
- package/dist/{chunk-T7BBPDEJ.js → chunk-UKSGE6RH.js} +45 -15
- package/dist/chunk-UKSGE6RH.js.map +1 -0
- package/dist/chunk-W2OTF6OS.js +201 -0
- package/dist/chunk-W2OTF6OS.js.map +1 -0
- package/dist/chunk-WEQW3EAT.js +78 -0
- package/dist/chunk-WEQW3EAT.js.map +1 -0
- package/dist/{chunk-ID4OYXVH.js → chunk-WJJ3ZIQ6.js} +112 -45
- package/dist/chunk-WJJ3ZIQ6.js.map +1 -0
- package/dist/chunk-YAAT66RT.js +70 -0
- package/dist/chunk-YAAT66RT.js.map +1 -0
- package/dist/{chunk-RLZQB7HS.js → chunk-ZMJFEHGF.js} +13 -1
- package/dist/chunk-ZMJFEHGF.js.map +1 -0
- package/dist/{chunk-HRU7S4TA.js → chunk-ZN5RHWGR.js} +18 -208
- package/dist/{chunk-HRU7S4TA.js.map → chunk-ZN5RHWGR.js.map} +1 -1
- package/dist/{chunk-ZTYHZMEC.js → chunk-ZWZNEA26.js} +2 -2
- package/dist/clean-planning-7Z5YY64X.js +9 -0
- package/dist/cli/index.js +1301 -2142
- package/dist/cli/index.js.map +1 -1
- package/dist/close-issue-CTZK777I.js +9 -0
- package/dist/compact-beads-72SHALOL.js +9 -0
- package/dist/{config-4CJNUE3O.js → config-FFTMBVHM.js} +2 -2
- package/dist/dashboard/public/assets/{index-DSvt5pPn.css → index-Bx4NCn9A.css} +1 -1
- package/dist/dashboard/public/assets/index-Db9NOz4z.js +756 -0
- package/dist/dashboard/public/index.html +3 -2
- package/dist/dashboard/server.js +34714 -34296
- package/dist/{feedback-writer-T43PI5S2.js → feedback-writer-T2WCT6EZ.js} +2 -2
- package/dist/{hume-CKJJ3OUU.js → hume-GVTB5BKW.js} +3 -3
- package/dist/index.d.ts +24 -16
- package/dist/index.js +4 -4
- package/dist/label-cleanup-4HJVX6NP.js +103 -0
- package/dist/label-cleanup-4HJVX6NP.js.map +1 -0
- package/dist/merge-agent-WM7ZKUET.js +1725 -0
- package/dist/merge-agent-WM7ZKUET.js.map +1 -0
- package/dist/{projects-KVM3MN3Y.js → projects-3CRF57ZU.js} +2 -2
- package/dist/{rally-RKFSWC7E.js → rally-LBY24P4C.js} +2 -2
- package/dist/{remote-agents-ULPD6C5U.js → remote-agents-3NZPSHYG.js} +2 -3
- package/dist/{remote-workspace-XX6ARE6I.js → remote-workspace-M4IULGFZ.js} +24 -49
- package/dist/remote-workspace-M4IULGFZ.js.map +1 -0
- package/dist/{review-status-XKUKZF6J.js → review-status-J2YJGL3E.js} +2 -2
- package/dist/{specialist-context-C66TEMXS.js → specialist-context-74RQF5SR.js} +7 -5
- package/dist/{specialist-context-C66TEMXS.js.map → specialist-context-74RQF5SR.js.map} +1 -1
- package/dist/{specialist-logs-CJKXM3SR.js → specialist-logs-T5GW7CSU.js} +6 -4
- package/dist/{specialists-NXYD4Z62.js → specialists-HTYYFXHQ.js} +6 -4
- package/dist/specialists-HTYYFXHQ.js.map +1 -0
- package/dist/tmux-X2I5SAIJ.js +31 -0
- package/dist/tmux-X2I5SAIJ.js.map +1 -0
- package/dist/{traefik-5GL3Q7DJ.js → traefik-QXLZ4PO2.js} +4 -4
- package/dist/traefik-QXLZ4PO2.js.map +1 -0
- package/dist/{tunnel-BKC7KLBX.js → tunnel-7IOSRZVH.js} +3 -3
- package/dist/tunnel-7IOSRZVH.js.map +1 -0
- package/dist/{workspace-manager-ALBR62AS.js → workspace-manager-G6TTBPC3.js} +6 -6
- package/dist/workspace-manager-G6TTBPC3.js.map +1 -0
- package/package.json +2 -2
- package/scripts/build-cost-script.mjs +17 -0
- package/scripts/heartbeat-hook +28 -8
- package/scripts/record-cost-event.js +46 -7
- package/scripts/record-cost-event.ts +2 -1
- package/dist/chunk-44EOY2ZL.js.map +0 -1
- package/dist/chunk-4HST45MO.js.map +0 -1
- package/dist/chunk-565HZ6VV.js +0 -159
- package/dist/chunk-565HZ6VV.js.map +0 -1
- package/dist/chunk-6N2KBSJA.js.map +0 -1
- package/dist/chunk-CFCUOV3Q.js.map +0 -1
- package/dist/chunk-FQ66DECN.js.map +0 -1
- package/dist/chunk-ID4OYXVH.js.map +0 -1
- package/dist/chunk-KY2E2Q3T.js.map +0 -1
- package/dist/chunk-MOPGR3CL.js.map +0 -1
- package/dist/chunk-RLZQB7HS.js.map +0 -1
- package/dist/chunk-T7BBPDEJ.js.map +0 -1
- package/dist/chunk-ZDNQFWR5.js +0 -650
- package/dist/chunk-ZDNQFWR5.js.map +0 -1
- package/dist/dashboard/public/assets/index-DA6pnizT.js +0 -767
- package/dist/remote-workspace-XX6ARE6I.js.map +0 -1
- /package/dist/{agents-HNMF52RM.js.map → agents-5HWTDR4S.js.map} +0 -0
- /package/dist/{config-4CJNUE3O.js.map → archive-planning-U3AZAKWI.js.map} +0 -0
- /package/dist/{chunk-KBHRXV5T.js.map → chunk-43F4LDZ4.js.map} +0 -0
- /package/dist/{chunk-HOGYHJ2G.js.map → chunk-DW3PKGIS.js.map} +0 -0
- /package/dist/{chunk-DFNVHK3N.js.map → chunk-SUM2WVPF.js.map} +0 -0
- /package/dist/{chunk-ZTYHZMEC.js.map → chunk-ZWZNEA26.js.map} +0 -0
- /package/dist/{hume-CKJJ3OUU.js.map → clean-planning-7Z5YY64X.js.map} +0 -0
- /package/dist/{projects-KVM3MN3Y.js.map → close-issue-CTZK777I.js.map} +0 -0
- /package/dist/{rally-RKFSWC7E.js.map → compact-beads-72SHALOL.js.map} +0 -0
- /package/dist/{remote-agents-ULPD6C5U.js.map → config-FFTMBVHM.js.map} +0 -0
- /package/dist/{feedback-writer-T43PI5S2.js.map → feedback-writer-T2WCT6EZ.js.map} +0 -0
- /package/dist/{review-status-XKUKZF6J.js.map → hume-GVTB5BKW.js.map} +0 -0
- /package/dist/{specialist-logs-CJKXM3SR.js.map → projects-3CRF57ZU.js.map} +0 -0
- /package/dist/{specialists-NXYD4Z62.js.map → rally-LBY24P4C.js.map} +0 -0
- /package/dist/{traefik-5GL3Q7DJ.js.map → remote-agents-3NZPSHYG.js.map} +0 -0
- /package/dist/{tunnel-BKC7KLBX.js.map → review-status-J2YJGL3E.js.map} +0 -0
- /package/dist/{workspace-manager-ALBR62AS.js.map → specialist-logs-T5GW7CSU.js.map} +0 -0
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getLinearApiKey,
|
|
3
|
+
stepFailed,
|
|
4
|
+
stepOk,
|
|
5
|
+
stepSkipped
|
|
6
|
+
} from "./chunk-R4KPLLRB.js";
|
|
7
|
+
import {
|
|
8
|
+
init_esm_shims
|
|
9
|
+
} from "./chunk-ZHC57RCV.js";
|
|
10
|
+
|
|
11
|
+
// src/lib/lifecycle/close-issue.ts
|
|
12
|
+
init_esm_shims();
|
|
13
|
+
import { exec } from "child_process";
|
|
14
|
+
import { promisify } from "util";
|
|
15
|
+
var execAsync = promisify(exec);
|
|
16
|
+
var CLOSED_OUT_LABEL = "closed-out";
|
|
17
|
+
var CLOSED_OUT_COLOR = "1d4ed8";
|
|
18
|
+
var WORKFLOW_LABELS = ["in-progress", "in-review", "needs-close-out"];
|
|
19
|
+
async function closeIssue(ctx, opts = {}) {
|
|
20
|
+
const results = [];
|
|
21
|
+
const { applyLabel = true, labelOnly = false, comment } = opts;
|
|
22
|
+
if (!labelOnly) {
|
|
23
|
+
const closeResult = opts.tracker ? await closeViaTracker(ctx, opts.tracker, comment) : await closeViaDirect(ctx, comment);
|
|
24
|
+
results.push(closeResult);
|
|
25
|
+
if (!closeResult.success && !closeResult.skipped) {
|
|
26
|
+
return results;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
if (ctx.github) {
|
|
30
|
+
const prResult = await closeGitHubPr(ctx);
|
|
31
|
+
results.push(prResult);
|
|
32
|
+
}
|
|
33
|
+
if (applyLabel) {
|
|
34
|
+
const labelResult = await applyClosedOutLabel(ctx, opts.tracker);
|
|
35
|
+
results.push(labelResult);
|
|
36
|
+
}
|
|
37
|
+
return results;
|
|
38
|
+
}
|
|
39
|
+
async function closeViaTracker(ctx, tracker, comment) {
|
|
40
|
+
const step = "close-issue:transition";
|
|
41
|
+
try {
|
|
42
|
+
await tracker.transitionIssue(ctx.issueId, "closed");
|
|
43
|
+
if (comment) {
|
|
44
|
+
try {
|
|
45
|
+
await tracker.addComment(ctx.issueId, comment);
|
|
46
|
+
} catch {
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return stepOk(step, [`Closed ${ctx.issueId} via ${tracker.name} tracker`]);
|
|
50
|
+
} catch (err) {
|
|
51
|
+
return stepFailed(step, `Failed to close via tracker: ${err.message}`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
async function closeViaDirect(ctx, comment) {
|
|
55
|
+
const step = "close-issue:transition";
|
|
56
|
+
if (ctx.github) {
|
|
57
|
+
return closeGitHubDirect(ctx, comment);
|
|
58
|
+
}
|
|
59
|
+
if (ctx.rally) {
|
|
60
|
+
return closeRallyDirect(ctx);
|
|
61
|
+
}
|
|
62
|
+
const linearApiKey = getLinearApiKey();
|
|
63
|
+
if (linearApiKey) {
|
|
64
|
+
return closeLinearDirect(ctx, linearApiKey);
|
|
65
|
+
}
|
|
66
|
+
return stepFailed(step, "No tracker available and cannot determine issue type");
|
|
67
|
+
}
|
|
68
|
+
async function closeGitHubDirect(ctx, comment) {
|
|
69
|
+
const step = "close-issue:transition";
|
|
70
|
+
if (!ctx.github) {
|
|
71
|
+
return stepFailed(step, "GitHub config not provided");
|
|
72
|
+
}
|
|
73
|
+
const { owner, repo, number } = ctx.github;
|
|
74
|
+
try {
|
|
75
|
+
const commentArg = comment ? ` --comment "${comment.replace(/"/g, '\\"')}"` : "";
|
|
76
|
+
await execAsync(
|
|
77
|
+
`gh issue close ${number} --repo ${owner}/${repo}${commentArg}`,
|
|
78
|
+
{ encoding: "utf-8" }
|
|
79
|
+
);
|
|
80
|
+
return stepOk(step, [`Closed GitHub issue #${number} on ${owner}/${repo}`]);
|
|
81
|
+
} catch (err) {
|
|
82
|
+
return stepFailed(step, `gh issue close failed: ${err.message}`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
async function closeGitHubPr(ctx) {
|
|
86
|
+
const step = "close-issue:close-pr";
|
|
87
|
+
if (!ctx.github) {
|
|
88
|
+
return stepSkipped(step, ["Not a GitHub issue"]);
|
|
89
|
+
}
|
|
90
|
+
const { owner, repo } = ctx.github;
|
|
91
|
+
const issueLower = ctx.issueId.toLowerCase();
|
|
92
|
+
const branchName = `feature/${issueLower}`;
|
|
93
|
+
try {
|
|
94
|
+
const { stdout: prListRaw } = await execAsync(
|
|
95
|
+
`gh pr list --repo ${owner}/${repo} --head "${branchName}" --state open --json number --jq '.[0].number'`,
|
|
96
|
+
{ encoding: "utf-8" }
|
|
97
|
+
);
|
|
98
|
+
const prNumber = prListRaw.trim();
|
|
99
|
+
if (!prNumber) {
|
|
100
|
+
return stepSkipped(step, ["No open PR found for branch"]);
|
|
101
|
+
}
|
|
102
|
+
await execAsync(
|
|
103
|
+
`gh pr close ${prNumber} --repo ${owner}/${repo} --comment "Merged via Panopticon lifecycle"`,
|
|
104
|
+
{ encoding: "utf-8" }
|
|
105
|
+
);
|
|
106
|
+
return stepOk(step, [`Closed PR #${prNumber} on ${owner}/${repo}`]);
|
|
107
|
+
} catch (err) {
|
|
108
|
+
return stepSkipped(step, [`PR close failed (non-fatal): ${err.message}`]);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
var _linearRateLimitUntil = 0;
|
|
112
|
+
var LINEAR_RATE_LIMIT_COOLDOWN_MS = 60 * 60 * 1e3;
|
|
113
|
+
async function closeLinearDirect(ctx, apiKey) {
|
|
114
|
+
const step = "close-issue:transition";
|
|
115
|
+
if (Date.now() < _linearRateLimitUntil) {
|
|
116
|
+
const remainingMin = Math.ceil((_linearRateLimitUntil - Date.now()) / 6e4);
|
|
117
|
+
return stepFailed(step, `Linear rate limit cooldown active (${remainingMin}min remaining). Issue will be closed during close-out ceremony.`);
|
|
118
|
+
}
|
|
119
|
+
try {
|
|
120
|
+
const { LinearClient } = await import("@linear/sdk");
|
|
121
|
+
const client = new LinearClient({ apiKey });
|
|
122
|
+
const issueNumber = parseInt(ctx.issueId.split("-").pop() || "0", 10);
|
|
123
|
+
const issuePrefix = ctx.issueId.split("-")[0].toUpperCase();
|
|
124
|
+
const results = await client.issues({
|
|
125
|
+
filter: {
|
|
126
|
+
number: { eq: issueNumber },
|
|
127
|
+
team: { key: { eq: issuePrefix } }
|
|
128
|
+
},
|
|
129
|
+
first: 1
|
|
130
|
+
});
|
|
131
|
+
if (results.nodes.length === 0) {
|
|
132
|
+
return stepFailed(step, `Issue ${ctx.issueId} not found in Linear`);
|
|
133
|
+
}
|
|
134
|
+
const issue = results.nodes[0];
|
|
135
|
+
const team = await issue.team;
|
|
136
|
+
if (team) {
|
|
137
|
+
const states = await team.states();
|
|
138
|
+
const doneState = states.nodes.find((s) => s.name === "Done") || states.nodes.find((s) => s.type === "completed");
|
|
139
|
+
if (doneState) {
|
|
140
|
+
await issue.update({ stateId: doneState.id });
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return stepOk(step, [`Moved Linear issue ${ctx.issueId} to Done`]);
|
|
144
|
+
} catch (err) {
|
|
145
|
+
const message = err.message;
|
|
146
|
+
if (message.includes("Rate limit") || message.includes("rate limit") || message.includes("429")) {
|
|
147
|
+
_linearRateLimitUntil = Date.now() + LINEAR_RATE_LIMIT_COOLDOWN_MS;
|
|
148
|
+
console.warn(`[close-issue] Linear rate limit hit \u2014 circuit breaker activated for 1 hour`);
|
|
149
|
+
return stepFailed(step, `Linear rate limit exceeded. Circuit breaker activated \u2014 no Linear API calls for 1 hour. Issue will be closed during close-out ceremony.`);
|
|
150
|
+
}
|
|
151
|
+
return stepFailed(step, `Linear close failed: ${message}`);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
async function closeRallyDirect(ctx) {
|
|
155
|
+
const step = "close-issue:transition";
|
|
156
|
+
if (!ctx.rally) {
|
|
157
|
+
return stepFailed(step, "Rally config not provided");
|
|
158
|
+
}
|
|
159
|
+
try {
|
|
160
|
+
const { RallyTracker } = await import("./rally-LBY24P4C.js");
|
|
161
|
+
const tracker = new RallyTracker({
|
|
162
|
+
apiKey: ctx.rally.apiKey,
|
|
163
|
+
server: ctx.rally.server,
|
|
164
|
+
workspace: ctx.rally.workspace,
|
|
165
|
+
project: ctx.rally.project
|
|
166
|
+
});
|
|
167
|
+
await tracker.transitionIssue(ctx.issueId, "closed");
|
|
168
|
+
return stepOk(step, [`Closed Rally issue ${ctx.issueId}`]);
|
|
169
|
+
} catch (err) {
|
|
170
|
+
return stepFailed(step, `Rally close failed: ${err.message}`);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
async function applyClosedOutLabel(ctx, tracker) {
|
|
174
|
+
const step = "close-issue:label";
|
|
175
|
+
if (tracker) {
|
|
176
|
+
return applyLabelViaTracker(ctx, tracker);
|
|
177
|
+
}
|
|
178
|
+
if (ctx.github) {
|
|
179
|
+
return applyLabelGitHub(ctx);
|
|
180
|
+
}
|
|
181
|
+
const linearApiKey = getLinearApiKey();
|
|
182
|
+
if (linearApiKey) {
|
|
183
|
+
return applyLabelLinear(ctx, linearApiKey);
|
|
184
|
+
}
|
|
185
|
+
return stepSkipped(step, ["No tracker available for label management"]);
|
|
186
|
+
}
|
|
187
|
+
async function applyLabelViaTracker(ctx, tracker) {
|
|
188
|
+
const step = "close-issue:label";
|
|
189
|
+
try {
|
|
190
|
+
const issue = await tracker.getIssue(ctx.issueId);
|
|
191
|
+
const newLabels = issue.labels.filter((l) => !WORKFLOW_LABELS.includes(l));
|
|
192
|
+
if (!newLabels.includes(CLOSED_OUT_LABEL)) {
|
|
193
|
+
newLabels.push(CLOSED_OUT_LABEL);
|
|
194
|
+
}
|
|
195
|
+
await tracker.updateIssue(ctx.issueId, { labels: newLabels });
|
|
196
|
+
return stepOk(step, [`Applied '${CLOSED_OUT_LABEL}' label via ${tracker.name} tracker`]);
|
|
197
|
+
} catch (err) {
|
|
198
|
+
return stepSkipped(step, [`Label management failed (non-fatal): ${err.message}`]);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
async function applyLabelGitHub(ctx) {
|
|
202
|
+
const step = "close-issue:label";
|
|
203
|
+
if (!ctx.github) return stepSkipped(step);
|
|
204
|
+
const { owner, repo, number } = ctx.github;
|
|
205
|
+
try {
|
|
206
|
+
await execAsync(
|
|
207
|
+
`gh label create "${CLOSED_OUT_LABEL}" --repo ${owner}/${repo} --color "${CLOSED_OUT_COLOR}" --description "Verified and closed out" --force 2>/dev/null || true`,
|
|
208
|
+
{ encoding: "utf-8" }
|
|
209
|
+
);
|
|
210
|
+
await execAsync(
|
|
211
|
+
`gh issue edit ${number} --repo ${owner}/${repo} --add-label "${CLOSED_OUT_LABEL}"`,
|
|
212
|
+
{ encoding: "utf-8" }
|
|
213
|
+
);
|
|
214
|
+
for (const label of WORKFLOW_LABELS) {
|
|
215
|
+
await execAsync(
|
|
216
|
+
`gh issue edit ${number} --repo ${owner}/${repo} --remove-label "${label}" 2>/dev/null || true`,
|
|
217
|
+
{ encoding: "utf-8" }
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
return stepOk(step, [`Applied '${CLOSED_OUT_LABEL}' label on GitHub`]);
|
|
221
|
+
} catch (err) {
|
|
222
|
+
return stepSkipped(step, [`Label management failed (non-fatal): ${err.message}`]);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
async function applyLabelLinear(ctx, apiKey) {
|
|
226
|
+
const step = "close-issue:label";
|
|
227
|
+
try {
|
|
228
|
+
const { LinearClient } = await import("@linear/sdk");
|
|
229
|
+
const client = new LinearClient({ apiKey });
|
|
230
|
+
const issueNum = parseInt(ctx.issueId.split("-").pop() || "0", 10);
|
|
231
|
+
const teamKey = ctx.issueId.split("-")[0].toUpperCase();
|
|
232
|
+
const results = await client.issues({
|
|
233
|
+
filter: {
|
|
234
|
+
number: { eq: issueNum },
|
|
235
|
+
team: { key: { eq: teamKey } }
|
|
236
|
+
},
|
|
237
|
+
first: 1
|
|
238
|
+
});
|
|
239
|
+
if (results.nodes.length === 0) {
|
|
240
|
+
return stepSkipped(step, ["Issue not found for label management"]);
|
|
241
|
+
}
|
|
242
|
+
const issue = results.nodes[0];
|
|
243
|
+
const labels = await client.issueLabels({ filter: { name: { eq: CLOSED_OUT_LABEL } } });
|
|
244
|
+
let labelId;
|
|
245
|
+
if (labels.nodes.length > 0) {
|
|
246
|
+
labelId = labels.nodes[0].id;
|
|
247
|
+
} else {
|
|
248
|
+
const created = await client.createIssueLabel({ name: CLOSED_OUT_LABEL, color: `#${CLOSED_OUT_COLOR}` });
|
|
249
|
+
const createdLabel = await created.issueLabel;
|
|
250
|
+
labelId = createdLabel ? createdLabel.id : "";
|
|
251
|
+
}
|
|
252
|
+
if (labelId) {
|
|
253
|
+
const existingLabels = await issue.labels();
|
|
254
|
+
const labelIds = existingLabels.nodes.map((l) => l.id);
|
|
255
|
+
if (!labelIds.includes(labelId)) {
|
|
256
|
+
labelIds.push(labelId);
|
|
257
|
+
await issue.update({ labelIds });
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
return stepOk(step, [`Applied '${CLOSED_OUT_LABEL}' label on Linear`]);
|
|
261
|
+
} catch (err) {
|
|
262
|
+
return stepSkipped(step, [`Linear label management failed (non-fatal): ${err.message}`]);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
export {
|
|
267
|
+
closeIssue
|
|
268
|
+
};
|
|
269
|
+
//# sourceMappingURL=chunk-OJF4QS3S.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/lib/lifecycle/close-issue.ts"],"sourcesContent":["/**\n * close-issue — Transition issue to closed/done state + label management.\n *\n * Uses the IssueTracker abstraction when available, with fallback to\n * direct API calls for contexts where the tracker isn't set up (e.g.,\n * standalone CLI).\n *\n * Operations:\n * 1. Transition issue to closed state\n * 2. Add 'closed-out' label\n * 3. Remove workflow labels (in-progress, in-review, needs-close-out)\n * 4. Add completion comment\n */\n\nimport { exec } from 'child_process';\nimport { promisify } from 'util';\nimport type { IssueTracker } from '../tracker/interface.js';\nimport type { LifecycleContext, StepResult } from './types.js';\nimport { stepOk, stepSkipped, stepFailed, getLinearApiKey } from './types.js';\n\nconst execAsync = promisify(exec);\n\nconst CLOSED_OUT_LABEL = 'closed-out';\nconst CLOSED_OUT_COLOR = '1d4ed8';\nconst WORKFLOW_LABELS = ['in-progress', 'in-review', 'needs-close-out'];\n\n/** Options for close-issue */\nexport interface CloseIssueOptions {\n /** IssueTracker instance (preferred — uses abstraction layer) */\n tracker?: IssueTracker;\n /** Reason for closing */\n reason?: string;\n /** Comment to add when closing */\n comment?: string;\n /** Apply the closed-out label. Default: true */\n applyLabel?: boolean;\n /** Only apply label (skip state transition). Default: false */\n labelOnly?: boolean;\n}\n\n/**\n * Close an issue and manage labels.\n *\n * If a tracker is provided, uses the abstraction layer.\n * Otherwise, falls back to direct gh CLI (GitHub) or Linear SDK calls.\n */\nexport async function closeIssue(\n ctx: LifecycleContext,\n opts: CloseIssueOptions = {},\n): Promise<StepResult[]> {\n const results: StepResult[] = [];\n const { applyLabel = true, labelOnly = false, comment } = opts;\n\n // Step 1: Transition to closed (unless labelOnly)\n if (!labelOnly) {\n const closeResult = opts.tracker\n ? await closeViaTracker(ctx, opts.tracker, comment)\n : await closeViaDirect(ctx, comment);\n results.push(closeResult);\n\n // If close failed, don't bother with labels\n if (!closeResult.success && !closeResult.skipped) {\n return results;\n }\n }\n\n // Step 2: Close any open PR for the feature branch (GitHub only)\n if (ctx.github) {\n const prResult = await closeGitHubPr(ctx);\n results.push(prResult);\n }\n\n // Step 3: Apply closed-out label + remove workflow labels\n if (applyLabel) {\n const labelResult = await applyClosedOutLabel(ctx, opts.tracker);\n results.push(labelResult);\n }\n\n return results;\n}\n\n/**\n * Close via IssueTracker abstraction.\n */\nasync function closeViaTracker(\n ctx: LifecycleContext,\n tracker: IssueTracker,\n comment?: string,\n): Promise<StepResult> {\n const step = 'close-issue:transition';\n try {\n await tracker.transitionIssue(ctx.issueId, 'closed');\n if (comment) {\n try {\n await tracker.addComment(ctx.issueId, comment);\n } catch {\n // Non-fatal — comment is best-effort\n }\n }\n return stepOk(step, [`Closed ${ctx.issueId} via ${tracker.name} tracker`]);\n } catch (err) {\n return stepFailed(step, `Failed to close via tracker: ${(err as Error).message}`);\n }\n}\n\n/**\n * Close via direct API calls (fallback when no tracker configured).\n * Determines issue type from context and uses appropriate method.\n */\nasync function closeViaDirect(\n ctx: LifecycleContext,\n comment?: string,\n): Promise<StepResult> {\n const step = 'close-issue:transition';\n\n if (ctx.github) {\n return closeGitHubDirect(ctx, comment);\n }\n\n // Rally issue\n if (ctx.rally) {\n return closeRallyDirect(ctx);\n }\n\n // Try Linear\n const linearApiKey = getLinearApiKey();\n if (linearApiKey) {\n return closeLinearDirect(ctx, linearApiKey);\n }\n\n return stepFailed(step, 'No tracker available and cannot determine issue type');\n}\n\n/**\n * Close a GitHub issue via gh CLI.\n */\nasync function closeGitHubDirect(ctx: LifecycleContext, comment?: string): Promise<StepResult> {\n const step = 'close-issue:transition';\n if (!ctx.github) {\n return stepFailed(step, 'GitHub config not provided');\n }\n const { owner, repo, number } = ctx.github;\n try {\n const commentArg = comment ? ` --comment \"${comment.replace(/\"/g, '\\\\\"')}\"` : '';\n await execAsync(\n `gh issue close ${number} --repo ${owner}/${repo}${commentArg}`,\n { encoding: 'utf-8' },\n );\n return stepOk(step, [`Closed GitHub issue #${number} on ${owner}/${repo}`]);\n } catch (err) {\n return stepFailed(step, `gh issue close failed: ${(err as Error).message}`);\n }\n}\n\n/**\n * Close any open GitHub PR for the feature branch.\n */\nasync function closeGitHubPr(ctx: LifecycleContext): Promise<StepResult> {\n const step = 'close-issue:close-pr';\n if (!ctx.github) {\n return stepSkipped(step, ['Not a GitHub issue']);\n }\n const { owner, repo } = ctx.github;\n const issueLower = ctx.issueId.toLowerCase();\n const branchName = `feature/${issueLower}`;\n\n try {\n const { stdout: prListRaw } = await execAsync(\n `gh pr list --repo ${owner}/${repo} --head \"${branchName}\" --state open --json number --jq '.[0].number'`,\n { encoding: 'utf-8' },\n );\n const prNumber = prListRaw.trim();\n if (!prNumber) {\n return stepSkipped(step, ['No open PR found for branch']);\n }\n await execAsync(\n `gh pr close ${prNumber} --repo ${owner}/${repo} --comment \"Merged via Panopticon lifecycle\"`,\n { encoding: 'utf-8' },\n );\n return stepOk(step, [`Closed PR #${prNumber} on ${owner}/${repo}`]);\n } catch (err) {\n return stepSkipped(step, [`PR close failed (non-fatal): ${(err as Error).message}`]);\n }\n}\n\n/**\n * Rate limit circuit breaker for Linear API.\n * After hitting a rate limit, stop all Linear API calls for COOLDOWN_MS.\n * This prevents the 24,626-call storm that exhausted Linear's 5000 req/hr limit (PAN-328).\n */\nlet _linearRateLimitUntil = 0;\nconst LINEAR_RATE_LIMIT_COOLDOWN_MS = 60 * 60 * 1000; // 1 hour (matches Linear's 5000/hr window)\n\n/**\n * Close a Linear issue via SDK (find by identifier, transition to Done).\n */\nasync function closeLinearDirect(ctx: LifecycleContext, apiKey: string): Promise<StepResult> {\n const step = 'close-issue:transition';\n\n // Circuit breaker: if we recently hit a rate limit, fail fast without making API calls\n if (Date.now() < _linearRateLimitUntil) {\n const remainingMin = Math.ceil((_linearRateLimitUntil - Date.now()) / 60000);\n return stepFailed(step, `Linear rate limit cooldown active (${remainingMin}min remaining). Issue will be closed during close-out ceremony.`);\n }\n\n try {\n const { LinearClient } = await import('@linear/sdk');\n const client = new LinearClient({ apiKey });\n\n const issueNumber = parseInt(ctx.issueId.split('-').pop() || '0', 10);\n const issuePrefix = ctx.issueId.split('-')[0].toUpperCase();\n const results = await client.issues({\n filter: {\n number: { eq: issueNumber },\n team: { key: { eq: issuePrefix } },\n },\n first: 1,\n });\n\n if (results.nodes.length === 0) {\n return stepFailed(step, `Issue ${ctx.issueId} not found in Linear`);\n }\n\n const issue = results.nodes[0];\n const team = await issue.team;\n if (team) {\n const states = await team.states();\n const doneState = states.nodes.find(s => s.name === 'Done') ||\n states.nodes.find(s => s.type === 'completed');\n if (doneState) {\n await issue.update({ stateId: doneState.id });\n }\n }\n\n return stepOk(step, [`Moved Linear issue ${ctx.issueId} to Done`]);\n } catch (err) {\n const message = (err as Error).message;\n\n // Detect rate limit errors and activate circuit breaker\n if (message.includes('Rate limit') || message.includes('rate limit') || message.includes('429')) {\n _linearRateLimitUntil = Date.now() + LINEAR_RATE_LIMIT_COOLDOWN_MS;\n console.warn(`[close-issue] Linear rate limit hit — circuit breaker activated for 1 hour`);\n return stepFailed(step, `Linear rate limit exceeded. Circuit breaker activated — no Linear API calls for 1 hour. Issue will be closed during close-out ceremony.`);\n }\n\n return stepFailed(step, `Linear close failed: ${message}`);\n }\n}\n\n/**\n * Close a Rally issue via RallyTracker.\n */\nasync function closeRallyDirect(ctx: LifecycleContext): Promise<StepResult> {\n const step = 'close-issue:transition';\n if (!ctx.rally) {\n return stepFailed(step, 'Rally config not provided');\n }\n try {\n const { RallyTracker } = await import('../tracker/rally.js');\n const tracker = new RallyTracker({\n apiKey: ctx.rally.apiKey,\n server: ctx.rally.server,\n workspace: ctx.rally.workspace,\n project: ctx.rally.project,\n });\n await tracker.transitionIssue(ctx.issueId, 'closed');\n return stepOk(step, [`Closed Rally issue ${ctx.issueId}`]);\n } catch (err) {\n return stepFailed(step, `Rally close failed: ${(err as Error).message}`);\n }\n}\n\n/**\n * Apply 'closed-out' label and remove workflow labels.\n * Uses tracker if available, falls back to direct calls.\n */\nasync function applyClosedOutLabel(\n ctx: LifecycleContext,\n tracker?: IssueTracker,\n): Promise<StepResult> {\n const step = 'close-issue:label';\n\n if (tracker) {\n return applyLabelViaTracker(ctx, tracker);\n }\n\n if (ctx.github) {\n return applyLabelGitHub(ctx);\n }\n\n const linearApiKey = getLinearApiKey();\n if (linearApiKey) {\n return applyLabelLinear(ctx, linearApiKey);\n }\n\n return stepSkipped(step, ['No tracker available for label management']);\n}\n\nasync function applyLabelViaTracker(\n ctx: LifecycleContext,\n tracker: IssueTracker,\n): Promise<StepResult> {\n const step = 'close-issue:label';\n try {\n const issue = await tracker.getIssue(ctx.issueId);\n const newLabels = issue.labels.filter(l => !WORKFLOW_LABELS.includes(l));\n if (!newLabels.includes(CLOSED_OUT_LABEL)) {\n newLabels.push(CLOSED_OUT_LABEL);\n }\n await tracker.updateIssue(ctx.issueId, { labels: newLabels });\n return stepOk(step, [`Applied '${CLOSED_OUT_LABEL}' label via ${tracker.name} tracker`]);\n } catch (err) {\n // Label management is non-fatal\n return stepSkipped(step, [`Label management failed (non-fatal): ${(err as Error).message}`]);\n }\n}\n\nasync function applyLabelGitHub(ctx: LifecycleContext): Promise<StepResult> {\n const step = 'close-issue:label';\n if (!ctx.github) return stepSkipped(step);\n const { owner, repo, number } = ctx.github;\n\n try {\n // Ensure label exists\n await execAsync(\n `gh label create \"${CLOSED_OUT_LABEL}\" --repo ${owner}/${repo} --color \"${CLOSED_OUT_COLOR}\" --description \"Verified and closed out\" --force 2>/dev/null || true`,\n { encoding: 'utf-8' },\n );\n // Add label\n await execAsync(\n `gh issue edit ${number} --repo ${owner}/${repo} --add-label \"${CLOSED_OUT_LABEL}\"`,\n { encoding: 'utf-8' },\n );\n // Remove workflow labels\n for (const label of WORKFLOW_LABELS) {\n await execAsync(\n `gh issue edit ${number} --repo ${owner}/${repo} --remove-label \"${label}\" 2>/dev/null || true`,\n { encoding: 'utf-8' },\n );\n }\n return stepOk(step, [`Applied '${CLOSED_OUT_LABEL}' label on GitHub`]);\n } catch (err) {\n return stepSkipped(step, [`Label management failed (non-fatal): ${(err as Error).message}`]);\n }\n}\n\nasync function applyLabelLinear(ctx: LifecycleContext, apiKey: string): Promise<StepResult> {\n const step = 'close-issue:label';\n try {\n const { LinearClient } = await import('@linear/sdk');\n const client = new LinearClient({ apiKey });\n\n const issueNum = parseInt(ctx.issueId.split('-').pop() || '0', 10);\n const teamKey = ctx.issueId.split('-')[0].toUpperCase();\n const results = await client.issues({\n filter: {\n number: { eq: issueNum },\n team: { key: { eq: teamKey } },\n },\n first: 1,\n });\n if (results.nodes.length === 0) {\n return stepSkipped(step, ['Issue not found for label management']);\n }\n\n const issue = results.nodes[0];\n\n // Find or create closed-out label\n const labels = await client.issueLabels({ filter: { name: { eq: CLOSED_OUT_LABEL } } });\n let labelId: string;\n if (labels.nodes.length > 0) {\n labelId = labels.nodes[0].id;\n } else {\n const created = await client.createIssueLabel({ name: CLOSED_OUT_LABEL, color: `#${CLOSED_OUT_COLOR}` });\n const createdLabel = await created.issueLabel;\n labelId = createdLabel ? createdLabel.id : '';\n }\n\n if (labelId) {\n const existingLabels = await issue.labels();\n const labelIds = existingLabels.nodes.map(l => l.id);\n if (!labelIds.includes(labelId)) {\n labelIds.push(labelId);\n await issue.update({ labelIds });\n }\n }\n\n return stepOk(step, [`Applied '${CLOSED_OUT_LABEL}' label on Linear`]);\n } catch (err) {\n return stepSkipped(step, [`Linear label management failed (non-fatal): ${(err as Error).message}`]);\n }\n}\n\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/lib/config.ts"],"sourcesContent":["import { readFileSync, writeFileSync, existsSync } from 'fs';\nimport { join, dirname, parse as parsePath } from 'path';\nimport { homedir } from 'os';\nimport { parse, stringify } from '@iarna/toml';\nimport { CONFIG_FILE } from './paths.js';\nimport type { TrackerType } from './tracker/interface.js';\n\n// Individual tracker configuration\nexport interface LinearConfig {\n type: 'linear';\n api_key_env?: string; // Env var name for API key (default: LINEAR_API_KEY)\n team?: string; // Default team prefix (e.g., 'MIN')\n}\n\nexport interface GitHubConfig {\n type: 'github';\n token_env?: string; // Env var name for token (default: GITHUB_TOKEN)\n owner: string; // Repository owner\n repo: string; // Repository name\n}\n\nexport interface GitLabConfig {\n type: 'gitlab';\n token_env?: string; // Env var name for token (default: GITLAB_TOKEN)\n project_id: string; // GitLab project ID\n}\n\nexport interface RallyConfig {\n type: 'rally';\n api_key_env?: string; // Env var name for API key (default: RALLY_API_KEY)\n server?: string; // Rally server URL (default: rally1.rallydev.com)\n workspace?: string; // Rally workspace OID (e.g., \"/workspace/12345\")\n project?: string; // Rally project OID (e.g., \"/project/67890\")\n}\n\nexport type TrackerConfigItem = LinearConfig | GitHubConfig | GitLabConfig | RallyConfig;\n\nexport interface TrackersConfig {\n primary: TrackerType;\n secondary?: TrackerType;\n linear?: LinearConfig;\n github?: GitHubConfig;\n gitlab?: GitLabConfig;\n rally?: RallyConfig;\n}\n\nexport interface RemoteFlyConfig {\n /** Fly.io app name for workspace machines */\n app?: string;\n /** Fly.io org slug */\n org?: string;\n /** Default region (e.g. 'iad') */\n region?: string;\n /** Machine size (e.g. 'shared-cpu-2x') */\n vm_size?: string;\n /** Memory in MB */\n vm_memory?: number;\n /** Docker image for workspace machines */\n image?: string;\n /** Stop machine when agent is idle */\n auto_stop?: boolean;\n /** Seconds of inactivity before stop */\n auto_stop_timeout?: number;\n /** Env var name for API token (default: FLY_API_TOKEN) */\n api_token_env?: string;\n}\n\nexport interface RemoteConfig {\n /** Enable remote workspace support */\n enabled: boolean;\n /** Remote provider type */\n provider?: 'fly';\n /** Default location for new workspaces */\n default_location?: 'local' | 'remote';\n /** Auto-hibernate idle workspaces after N minutes (0 = disabled) */\n auto_hibernate_minutes?: number;\n /** Fly.io specific configuration */\n fly?: RemoteFlyConfig;\n}\n\nexport interface ShadowConfig {\n enabled: boolean;\n trackers: {\n linear: boolean;\n github: boolean;\n gitlab: boolean;\n rally: boolean;\n };\n}\n\nexport interface PanopticonConfig {\n panopticon: {\n version: string;\n };\n sync: {\n backup_before_sync: boolean;\n auto_sync?: boolean;\n strategy?: 'symlink' | 'copy';\n /** Parent directory where all projects live (e.g., ~/Projects).\n * Skills are placed at <devroot>/.claude/skills/ (project level).\n * Set to null or empty string to disable devroot skill placement. */\n devroot?: string | null;\n };\n trackers: TrackersConfig;\n dashboard: {\n port: number;\n api_port: number;\n };\n traefik: {\n enabled: boolean;\n dashboard_port?: number;\n domain?: string;\n dns_sync_method?: 'wsl2hosts' | 'hosts_file' | 'dnsmasq';\n };\n remote?: RemoteConfig;\n shadow: ShadowConfig;\n}\n\nconst DEFAULT_CONFIG: PanopticonConfig = {\n panopticon: {\n version: '1.0.0',\n },\n sync: {\n backup_before_sync: true,\n auto_sync: false,\n strategy: 'symlink',\n devroot: '~/Projects',\n },\n trackers: {\n primary: 'linear',\n linear: {\n type: 'linear',\n api_key_env: 'LINEAR_API_KEY',\n },\n },\n dashboard: {\n port: 3010,\n api_port: 3011,\n },\n traefik: {\n enabled: false,\n dashboard_port: 8080,\n domain: 'pan.localhost',\n },\n shadow: {\n enabled: false,\n trackers: {\n linear: false,\n github: false,\n gitlab: false,\n rally: false,\n },\n },\n};\n\n/**\n * Deep merge utility that recursively merges objects.\n * - Recursively merges nested objects\n * - Arrays in overrides replace defaults (not concatenated)\n * - User values take precedence over defaults\n */\nfunction deepMerge<T extends object>(defaults: T, overrides: Partial<T>): T {\n const result = { ...defaults };\n\n for (const key of Object.keys(overrides) as (keyof T)[]) {\n const defaultVal = defaults[key];\n const overrideVal = overrides[key];\n\n // Skip undefined values in overrides\n if (overrideVal === undefined) continue;\n\n // Deep merge if both values are non-array objects\n if (\n typeof defaultVal === 'object' &&\n defaultVal !== null &&\n !Array.isArray(defaultVal) &&\n typeof overrideVal === 'object' &&\n overrideVal !== null &&\n !Array.isArray(overrideVal)\n ) {\n result[key] = deepMerge(defaultVal, overrideVal as any);\n } else {\n // For primitives, arrays, or null - override wins\n result[key] = overrideVal as T[keyof T];\n }\n }\n\n return result;\n}\n\nexport function loadConfig(): PanopticonConfig {\n if (!existsSync(CONFIG_FILE)) {\n return DEFAULT_CONFIG;\n }\n\n try {\n const content = readFileSync(CONFIG_FILE, 'utf8');\n const parsed = parse(content) as unknown as Partial<PanopticonConfig>;\n return deepMerge(DEFAULT_CONFIG, parsed);\n } catch (error) {\n console.error('Warning: Failed to parse config, using defaults');\n return DEFAULT_CONFIG;\n }\n}\n\nexport function saveConfig(config: PanopticonConfig): void {\n const content = stringify(config as any);\n writeFileSync(CONFIG_FILE, content, 'utf8');\n}\n\nexport function getDefaultConfig(): PanopticonConfig {\n return JSON.parse(JSON.stringify(DEFAULT_CONFIG));\n}\n\n/**\n * Get the dashboard API base URL from config.\n * Reads from DASHBOARD_URL env var first, then config file, then defaults.\n */\nexport function getDashboardApiUrl(): string {\n if (process.env.DASHBOARD_URL) return process.env.DASHBOARD_URL;\n const config = loadConfig();\n const port = config.dashboard?.api_port || 3011;\n return `http://localhost:${port}`;\n}\n\n/**\n * Get the resolved devroot path from config.\n * Returns null if devroot is disabled (set to null or empty string).\n * Resolves ~ to home directory and validates the directory exists.\n */\nexport function getDevrootPath(): string | null {\n const config = loadConfig();\n const devroot = config.sync?.devroot;\n\n if (!devroot) return null;\n\n // Resolve ~ to home directory\n const resolved = devroot.startsWith('~/')\n ? join(homedir(), devroot.slice(2))\n : devroot;\n\n if (!existsSync(resolved)) return null;\n\n return resolved;\n}\n\n/**\n * Find the devroot for a given project path.\n * Tries config first, then walks up from projectPath looking for .claude/ directory.\n * Returns the project path itself as last resort.\n */\nexport function findDevrootForProject(projectPath: string): string {\n // 1. Explicit config takes priority\n const configured = getDevrootPath();\n if (configured) return configured;\n\n // 2. Walk up from project path to find nearest .claude/ directory\n let dir = projectPath;\n const root = parsePath(dir).root;\n while (dir !== root && dir !== homedir()) {\n const parent = dirname(dir);\n if (parent === dir) break;\n if (existsSync(join(parent, '.claude'))) {\n return parent;\n }\n dir = parent;\n }\n\n // 3. Fallback to project path itself\n return projectPath;\n}\n\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import {
|
|
2
|
+
init_esm_shims
|
|
3
|
+
} from "./chunk-ZHC57RCV.js";
|
|
4
|
+
|
|
5
|
+
// src/lib/lifecycle/types.ts
|
|
6
|
+
init_esm_shims();
|
|
7
|
+
import { existsSync, readFileSync } from "fs";
|
|
8
|
+
import { join } from "path";
|
|
9
|
+
import { homedir } from "os";
|
|
10
|
+
function stepOk(step, details) {
|
|
11
|
+
return { step, success: true, skipped: false, details };
|
|
12
|
+
}
|
|
13
|
+
function stepSkipped(step, details) {
|
|
14
|
+
return { step, success: true, skipped: true, details };
|
|
15
|
+
}
|
|
16
|
+
function stepFailed(step, error, details) {
|
|
17
|
+
return { step, success: false, skipped: false, error, details };
|
|
18
|
+
}
|
|
19
|
+
function getLinearApiKey() {
|
|
20
|
+
if (process.env.LINEAR_API_KEY) return process.env.LINEAR_API_KEY;
|
|
21
|
+
const envFile = join(homedir(), ".panopticon.env");
|
|
22
|
+
if (existsSync(envFile)) {
|
|
23
|
+
const content = readFileSync(envFile, "utf-8");
|
|
24
|
+
const match = content.match(/LINEAR_API_KEY=(.+)/);
|
|
25
|
+
if (match) return match[1].trim();
|
|
26
|
+
}
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export {
|
|
31
|
+
stepOk,
|
|
32
|
+
stepSkipped,
|
|
33
|
+
stepFailed,
|
|
34
|
+
getLinearApiKey
|
|
35
|
+
};
|
|
36
|
+
//# sourceMappingURL=chunk-R4KPLLRB.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/lib/lifecycle/types.ts"],"sourcesContent":["/**\n * Shared types for lifecycle operations.\n *\n * Every atomic operation returns a StepResult. Workflows compose\n * multiple operations and return a WorkflowResult.\n */\n\nimport { existsSync, readFileSync } from 'fs';\nimport { join } from 'path';\nimport { homedir } from 'os';\n\nexport interface StepResult {\n step: string;\n success: boolean;\n skipped: boolean; // true if operation was a no-op (idempotent)\n error?: string;\n details?: string[]; // human-readable log of what was done\n}\n\nexport interface WorkflowResult {\n workflow: 'approve' | 'close' | 'close-out' | 'deep-wipe';\n issueId: string;\n success: boolean; // true only if ALL non-skipped steps succeeded\n steps: StepResult[];\n duration: number; // ms\n}\n\n/** Context shared across lifecycle operations */\nexport interface LifecycleContext {\n issueId: string;\n projectPath: string;\n /** Project name (for Docker compose project naming + placeholders) */\n projectName?: string;\n /** GitHub issue metadata (populated for PAN- issues) */\n github?: {\n owner: string;\n repo: string;\n number: number;\n };\n /** Rally configuration (populated for Rally-tracked issues) */\n rally?: {\n apiKey: string;\n server?: string;\n workspace?: string;\n project?: string;\n };\n}\n\n/** Options for teardown-workspace */\nexport interface TeardownOptions {\n /** Delete feature branches (local + remote). Default: false */\n deleteBranches?: boolean;\n /** Skip Docker container cleanup. Default: false */\n skipDocker?: boolean;\n /** Delete workspace directory (worktree + files). Default: true */\n deleteWorkspace?: boolean;\n /** Project-specific workspace config for tunnel/Hume cleanup */\n workspaceConfig?: {\n tunnel?: any;\n hume?: any;\n dns?: { domain?: string };\n };\n /** Project name (for Docker compose project naming + placeholders) */\n projectName?: string;\n}\n\n/** Options for archive-planning */\nexport interface ArchiveOptions {\n /** Push git commits to remote after archiving. Default: true */\n pushToRemote?: boolean;\n}\n\n/** Options for the approve workflow */\nexport interface ApproveOptions {\n /** Skip the merge step (e.g. if already merged). Default: false */\n skipMerge?: boolean;\n /** Skip beads compaction. Default: false */\n skipBeadsCompaction?: boolean;\n}\n\n/** Options for the deep-wipe workflow */\nexport interface DeepWipeOptions {\n /** Delete workspace directory. Default: true */\n deleteWorkspace?: boolean;\n /** Delete git branches (local + remote). Default: true */\n deleteBranches?: boolean;\n /** Reset issue to backlog/open state. Default: true */\n resetIssue?: boolean;\n /** Project-specific workspace config for tunnel/Hume cleanup */\n workspaceConfig?: {\n tunnel?: any;\n hume?: any;\n dns?: { domain?: string };\n };\n /** Project name (for Docker compose project naming + placeholders) */\n projectName?: string;\n}\n\n/** Helper to create a successful step result */\nexport function stepOk(step: string, details?: string[]): StepResult {\n return { step, success: true, skipped: false, details };\n}\n\n/** Helper to create a skipped step result */\nexport function stepSkipped(step: string, details?: string[]): StepResult {\n return { step, success: true, skipped: true, details };\n}\n\n/** Helper to create a failed step result */\nexport function stepFailed(step: string, error: string, details?: string[]): StepResult {\n return { step, success: false, skipped: false, error, details };\n}\n\n/**\n * Get LINEAR_API_KEY from environment or .panopticon.env.\n * Shared across lifecycle modules.\n */\nexport function getLinearApiKey(): string | null {\n if (process.env.LINEAR_API_KEY) return process.env.LINEAR_API_KEY;\n const envFile = join(homedir(), '.panopticon.env');\n if (existsSync(envFile)) {\n const content = readFileSync(envFile, 'utf-8');\n const match = content.match(/LINEAR_API_KEY=(.+)/);\n if (match) return match[1].trim();\n }\n return null;\n}\n"],"mappings":";;;;;AAAA;AAOA,SAAS,YAAY,oBAAoB;AACzC,SAAS,YAAY;AACrB,SAAS,eAAe;AA0FjB,SAAS,OAAO,MAAc,SAAgC;AACnE,SAAO,EAAE,MAAM,SAAS,MAAM,SAAS,OAAO,QAAQ;AACxD;AAGO,SAAS,YAAY,MAAc,SAAgC;AACxE,SAAO,EAAE,MAAM,SAAS,MAAM,SAAS,MAAM,QAAQ;AACvD;AAGO,SAAS,WAAW,MAAc,OAAe,SAAgC;AACtF,SAAO,EAAE,MAAM,SAAS,OAAO,SAAS,OAAO,OAAO,QAAQ;AAChE;AAMO,SAAS,kBAAiC;AAC/C,MAAI,QAAQ,IAAI,eAAgB,QAAO,QAAQ,IAAI;AACnD,QAAM,UAAU,KAAK,QAAQ,GAAG,iBAAiB;AACjD,MAAI,WAAW,OAAO,GAAG;AACvB,UAAM,UAAU,aAAa,SAAS,OAAO;AAC7C,UAAM,QAAQ,QAAQ,MAAM,qBAAqB;AACjD,QAAI,MAAO,QAAO,MAAM,CAAC,EAAE,KAAK;AAAA,EAClC;AACA,SAAO;AACT;","names":[]}
|
|
@@ -13,14 +13,14 @@ import {
|
|
|
13
13
|
init_github,
|
|
14
14
|
init_gitlab,
|
|
15
15
|
init_linear
|
|
16
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-UKSGE6RH.js";
|
|
17
17
|
import {
|
|
18
18
|
getDevrootPath,
|
|
19
19
|
init_config
|
|
20
|
-
} from "./chunk-
|
|
20
|
+
} from "./chunk-QAJAJBFW.js";
|
|
21
21
|
import {
|
|
22
22
|
init_interface
|
|
23
|
-
} from "./chunk-
|
|
23
|
+
} from "./chunk-DMRTN432.js";
|
|
24
24
|
import {
|
|
25
25
|
BACKUPS_DIR,
|
|
26
26
|
BIN_DIR,
|
|
@@ -674,4 +674,4 @@ export {
|
|
|
674
674
|
LinkManager,
|
|
675
675
|
getLinkManager
|
|
676
676
|
};
|
|
677
|
-
//# sourceMappingURL=chunk-
|
|
677
|
+
//# sourceMappingURL=chunk-SUM2WVPF.js.map
|
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
TrackerAuthError,
|
|
10
10
|
init_interface,
|
|
11
11
|
init_rally
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-DMRTN432.js";
|
|
13
13
|
import {
|
|
14
14
|
__esm,
|
|
15
15
|
init_esm_shims
|
|
@@ -182,11 +182,23 @@ var init_linear = __esm({
|
|
|
182
182
|
throw new Error("Could not determine issue team");
|
|
183
183
|
}
|
|
184
184
|
const states = await team.states();
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
185
|
+
let targetState;
|
|
186
|
+
if (state === "in_review") {
|
|
187
|
+
targetState = states.nodes.find((s) => s.name.toLowerCase() === "in review");
|
|
188
|
+
if (!targetState) {
|
|
189
|
+
const startedStates = states.nodes.filter((s) => s.type === "started").sort((a, b) => (a.position ?? 0) - (b.position ?? 0));
|
|
190
|
+
targetState = startedStates[0];
|
|
191
|
+
if (!targetState) {
|
|
192
|
+
throw new Error('No "In Review" or "started" state found in Linear');
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
} else {
|
|
196
|
+
const targetStateType = this.reverseMapState(state);
|
|
197
|
+
const matchingStates = states.nodes.filter((s) => s.type === targetStateType).sort((a, b) => (a.position ?? 0) - (b.position ?? 0));
|
|
198
|
+
targetState = matchingStates[0];
|
|
199
|
+
if (!targetState) {
|
|
200
|
+
throw new Error(`No state found matching type: ${targetStateType}`);
|
|
201
|
+
}
|
|
190
202
|
}
|
|
191
203
|
await this.client.updateIssue(linearIssue.id, {
|
|
192
204
|
stateId: targetState.id
|
|
@@ -232,6 +244,7 @@ var init_linear = __esm({
|
|
|
232
244
|
case "open":
|
|
233
245
|
return "unstarted";
|
|
234
246
|
case "in_progress":
|
|
247
|
+
case "in_review":
|
|
235
248
|
return "started";
|
|
236
249
|
case "closed":
|
|
237
250
|
return "completed";
|
|
@@ -383,16 +396,33 @@ var init_github = __esm({
|
|
|
383
396
|
issue_number: issueNumber,
|
|
384
397
|
labels: ["in-progress"]
|
|
385
398
|
});
|
|
399
|
+
} else if (state === "in_review") {
|
|
400
|
+
await this.ensureLabelExists("in-review", "In review", "e4e669");
|
|
401
|
+
await this.octokit.issues.addLabels({
|
|
402
|
+
owner: this.owner,
|
|
403
|
+
repo: this.repo,
|
|
404
|
+
issue_number: issueNumber,
|
|
405
|
+
labels: ["in-review"]
|
|
406
|
+
});
|
|
407
|
+
await this.octokit.issues.removeLabel({
|
|
408
|
+
owner: this.owner,
|
|
409
|
+
repo: this.repo,
|
|
410
|
+
issue_number: issueNumber,
|
|
411
|
+
name: "in-progress"
|
|
412
|
+
}).catch(() => {
|
|
413
|
+
});
|
|
386
414
|
} else {
|
|
387
415
|
const issue = await this.getIssue(id);
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
416
|
+
for (const label of ["in-progress", "in-review"]) {
|
|
417
|
+
if (issue.labels?.includes(label)) {
|
|
418
|
+
await this.octokit.issues.removeLabel({
|
|
419
|
+
owner: this.owner,
|
|
420
|
+
repo: this.repo,
|
|
421
|
+
issue_number: issueNumber,
|
|
422
|
+
name: label
|
|
423
|
+
}).catch(() => {
|
|
424
|
+
});
|
|
425
|
+
}
|
|
396
426
|
}
|
|
397
427
|
await this.updateIssue(id, { state });
|
|
398
428
|
}
|
|
@@ -640,4 +670,4 @@ export {
|
|
|
640
670
|
getAllTrackers,
|
|
641
671
|
init_factory
|
|
642
672
|
};
|
|
643
|
-
//# sourceMappingURL=chunk-
|
|
673
|
+
//# sourceMappingURL=chunk-UKSGE6RH.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/lib/tracker/linear.ts","../src/lib/tracker/github.ts","../src/lib/tracker/gitlab.ts","../src/lib/tracker/factory.ts"],"sourcesContent":["/**\n * Linear Issue Tracker Adapter\n *\n * Implements IssueTracker interface for Linear.\n */\n\nimport { LinearClient } from '@linear/sdk';\nimport type {\n Issue,\n IssueFilters,\n IssueState,\n IssueTracker,\n IssueUpdate,\n NewIssue,\n Comment,\n TrackerType,\n} from './interface.js';\nimport { IssueNotFoundError, TrackerAuthError } from './interface.js';\n\n// Map Linear state types to our normalized states\nconst STATE_MAP: Record<string, IssueState> = {\n backlog: 'open',\n unstarted: 'open',\n started: 'in_progress',\n completed: 'closed',\n canceled: 'closed',\n};\n\nexport class LinearTracker implements IssueTracker {\n readonly name: TrackerType = 'linear';\n private client: LinearClient;\n private defaultTeam?: string;\n\n constructor(apiKey: string, options?: { team?: string }) {\n if (!apiKey) {\n throw new TrackerAuthError('linear', 'API key is required');\n }\n this.client = new LinearClient({ apiKey });\n this.defaultTeam = options?.team;\n }\n\n async listIssues(filters?: IssueFilters): Promise<Issue[]> {\n const team = filters?.team ?? this.defaultTeam;\n\n const result = await this.client.issues({\n first: filters?.limit ?? 50,\n filter: {\n team: team ? { key: { eq: team } } : undefined,\n state: filters?.state\n ? { type: { eq: this.reverseMapState(filters.state) } }\n : filters?.includeClosed\n ? undefined\n : { type: { neq: 'completed' } },\n labels: filters?.labels?.length\n ? { name: { in: filters.labels } }\n : undefined,\n assignee: filters?.assignee\n ? { name: { containsIgnoreCase: filters.assignee } }\n : undefined,\n },\n });\n\n const issues: Issue[] = [];\n for (const node of result.nodes) {\n issues.push(await this.normalizeIssue(node));\n }\n return issues;\n }\n\n async getIssue(id: string): Promise<Issue> {\n try {\n // Check if it's a UUID (36 chars with hyphens)\n 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);\n\n if (isUuid) {\n // Fetch directly by UUID\n const issue = await this.client.issue(id);\n if (issue) {\n return this.normalizeIssue(issue);\n }\n } else {\n // Parse identifier (e.g., MIN-630) and search\n const match = id.match(/^([A-Z]+)-(\\d+)$/i);\n if (match) {\n const [, teamKey, number] = match;\n // Use searchIssues which supports identifier matching\n const results = await this.client.searchIssues(id, { first: 1 });\n if (results.nodes.length > 0) {\n return this.normalizeIssue(results.nodes[0]);\n }\n }\n }\n\n throw new IssueNotFoundError(id, 'linear');\n } catch (error) {\n if (error instanceof IssueNotFoundError) throw error;\n throw new IssueNotFoundError(id, 'linear');\n }\n }\n\n async updateIssue(id: string, update: IssueUpdate): Promise<Issue> {\n const issue = await this.getIssue(id);\n\n const updatePayload: Record<string, unknown> = {};\n\n if (update.title !== undefined) {\n updatePayload.title = update.title;\n }\n if (update.description !== undefined) {\n updatePayload.description = update.description;\n }\n if (update.priority !== undefined) {\n updatePayload.priority = update.priority;\n }\n if (update.dueDate !== undefined) {\n updatePayload.dueDate = update.dueDate;\n }\n if (update.state !== undefined) {\n // Need to find the state ID - this is complex in Linear\n // For now, we'll use the transition method\n await this.transitionIssue(id, update.state);\n }\n if (update.labels !== undefined) {\n // Need to look up label IDs - complex operation\n // TODO: Implement label updates\n }\n\n if (Object.keys(updatePayload).length > 0) {\n await this.client.updateIssue(issue.id, updatePayload);\n }\n\n return this.getIssue(id);\n }\n\n async createIssue(newIssue: NewIssue): Promise<Issue> {\n const team = newIssue.team ?? this.defaultTeam;\n\n if (!team) {\n throw new Error('Team is required to create an issue');\n }\n\n // Get team ID from key\n const teams = await this.client.teams({\n filter: { key: { eq: team } },\n });\n\n if (teams.nodes.length === 0) {\n throw new Error(`Team not found: ${team}`);\n }\n\n const teamId = teams.nodes[0].id;\n\n const result = await this.client.createIssue({\n teamId,\n title: newIssue.title,\n description: newIssue.description,\n priority: newIssue.priority,\n dueDate: newIssue.dueDate,\n });\n\n const created = await result.issue;\n if (!created) {\n throw new Error('Failed to create issue');\n }\n\n return this.normalizeIssue(created);\n }\n\n async getComments(issueId: string): Promise<Comment[]> {\n const issue = await this.client.issue(issueId);\n const comments = await issue.comments();\n\n return comments.nodes.map((c) => ({\n id: c.id,\n issueId,\n body: c.body,\n author: c.user?.then((u) => u?.name ?? 'Unknown') as unknown as string, // Simplified\n createdAt: c.createdAt.toISOString(),\n updatedAt: c.updatedAt.toISOString(),\n }));\n }\n\n async addComment(issueId: string, body: string): Promise<Comment> {\n const result = await this.client.createComment({\n issueId,\n body,\n });\n\n const comment = await result.comment;\n if (!comment) {\n throw new Error('Failed to create comment');\n }\n\n return {\n id: comment.id,\n issueId,\n body: comment.body,\n author: 'Panopticon', // Simplified\n createdAt: comment.createdAt.toISOString(),\n updatedAt: comment.updatedAt.toISOString(),\n };\n }\n\n async transitionIssue(id: string, state: IssueState): Promise<void> {\n // Resolve the Linear issue directly (avoid normalizeIssue which may fail on SDK edge cases)\n let linearIssue: any;\n 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);\n if (isUuid) {\n linearIssue = await this.client.issue(id);\n } else {\n const results = await this.client.searchIssues(id, { first: 1 });\n if (results.nodes.length > 0) {\n linearIssue = results.nodes[0];\n } else {\n throw new IssueNotFoundError(id, 'linear');\n }\n }\n\n // Get workflow states for the issue's team\n const team = await linearIssue.team;\n if (!team) {\n throw new Error('Could not determine issue team');\n }\n\n const states = await team.states();\n\n let targetState: any;\n if (state === 'in_review') {\n // Find a state named \"In Review\" (case-insensitive) — more precise than matching by type,\n // since \"In Progress\" and \"In Review\" are both type \"started\" in Linear.\n targetState = states.nodes.find((s: any) => s.name.toLowerCase() === 'in review');\n if (!targetState) {\n // Fall back to lowest-position \"started\" state if no \"In Review\" state exists\n const startedStates = states.nodes\n .filter((s: any) => s.type === 'started')\n .sort((a: any, b: any) => (a.position ?? 0) - (b.position ?? 0));\n targetState = startedStates[0];\n if (!targetState) {\n throw new Error('No \"In Review\" or \"started\" state found in Linear');\n }\n }\n } else {\n const targetStateType = this.reverseMapState(state);\n\n // Find a state matching the target type.\n // Multiple states can share the same type (e.g., \"In Planning\", \"In Progress\", \"In Review\"\n // are all type \"started\"). Prefer the one with the lowest position (most basic/default state\n // for that type), which matches Linear's convention.\n const matchingStates = states.nodes\n .filter((s: any) => s.type === targetStateType)\n .sort((a: any, b: any) => (a.position ?? 0) - (b.position ?? 0));\n targetState = matchingStates[0];\n if (!targetState) {\n throw new Error(`No state found matching type: ${targetStateType}`);\n }\n }\n\n await this.client.updateIssue(linearIssue.id, {\n stateId: targetState.id,\n });\n }\n\n async linkPR(issueId: string, prUrl: string): Promise<void> {\n const issue = await this.getIssue(issueId);\n\n await this.client.createAttachment({\n issueId: issue.id,\n title: 'Pull Request',\n url: prUrl,\n });\n }\n\n private async normalizeIssue(linearIssue: any): Promise<Issue> {\n const state = await linearIssue.state;\n const assignee = await linearIssue.assignee;\n const labels = await linearIssue.labels();\n\n // Handle dueDate - can be Date, string, or undefined\n let dueDate: string | undefined;\n if (linearIssue.dueDate) {\n dueDate = linearIssue.dueDate instanceof Date\n ? linearIssue.dueDate.toISOString()\n : String(linearIssue.dueDate);\n }\n\n return {\n id: linearIssue.id,\n ref: linearIssue.identifier,\n title: linearIssue.title,\n description: linearIssue.description ?? '',\n state: this.mapState(state?.type ?? 'backlog'),\n labels: labels?.nodes?.map((l: any) => l.name) ?? [],\n assignee: assignee?.name,\n url: linearIssue.url,\n tracker: 'linear',\n priority: linearIssue.priority,\n dueDate,\n createdAt: linearIssue.createdAt instanceof Date\n ? linearIssue.createdAt.toISOString()\n : String(linearIssue.createdAt),\n updatedAt: linearIssue.updatedAt instanceof Date\n ? linearIssue.updatedAt.toISOString()\n : String(linearIssue.updatedAt),\n };\n }\n\n private mapState(linearState: string): IssueState {\n return STATE_MAP[linearState] ?? 'open';\n }\n\n private reverseMapState(state: IssueState): string {\n switch (state) {\n case 'open':\n return 'unstarted';\n case 'in_progress':\n case 'in_review':\n return 'started';\n case 'closed':\n return 'completed';\n default:\n return 'unstarted';\n }\n }\n}\n","/**\n * GitHub Issues Tracker Adapter\n *\n * Implements IssueTracker interface for GitHub Issues.\n */\n\nimport { Octokit } from '@octokit/rest';\nimport type {\n Issue,\n IssueFilters,\n IssueState,\n IssueTracker,\n IssueUpdate,\n NewIssue,\n Comment,\n TrackerType,\n} from './interface.js';\nimport { IssueNotFoundError, TrackerAuthError } from './interface.js';\n\n/**\n * Extract issue number from various formats: \"300\", \"#300\", \"PAN-300\"\n */\nfunction parseIssueNumber(id: string): number {\n const match = id.match(/(\\d+)$/);\n return match ? parseInt(match[1], 10) : NaN;\n}\n\nexport class GitHubTracker implements IssueTracker {\n readonly name: TrackerType = 'github';\n private octokit: Octokit;\n private owner: string;\n private repo: string;\n\n constructor(token: string, owner: string, repo: string) {\n if (!token) {\n throw new TrackerAuthError('github', 'Token is required');\n }\n if (!owner || !repo) {\n throw new Error('GitHub owner and repo are required');\n }\n\n this.octokit = new Octokit({ auth: token });\n this.owner = owner;\n this.repo = repo;\n }\n\n async listIssues(filters?: IssueFilters): Promise<Issue[]> {\n const state = this.mapStateToGitHub(filters?.state);\n\n const response = await this.octokit.issues.listForRepo({\n owner: this.owner,\n repo: this.repo,\n state: filters?.includeClosed ? 'all' : state,\n labels: filters?.labels?.join(',') || undefined,\n assignee: filters?.assignee || undefined,\n per_page: filters?.limit ?? 50,\n });\n\n // Filter out pull requests (GitHub API returns both)\n const issues = response.data.filter((item) => !item.pull_request);\n\n return issues.map((issue) => this.normalizeIssue(issue));\n }\n\n async getIssue(id: string): Promise<Issue> {\n try {\n // Parse the issue number from refs like \"#42\" or just \"42\"\n const issueNumber = parseIssueNumber(id);\n\n if (isNaN(issueNumber)) {\n throw new IssueNotFoundError(id, 'github');\n }\n\n const { data: issue } = await this.octokit.issues.get({\n owner: this.owner,\n repo: this.repo,\n issue_number: issueNumber,\n });\n\n return this.normalizeIssue(issue);\n } catch (error: any) {\n if (error?.status === 404) {\n throw new IssueNotFoundError(id, 'github');\n }\n throw error;\n }\n }\n\n async updateIssue(id: string, update: IssueUpdate): Promise<Issue> {\n const issueNumber = parseIssueNumber(id);\n\n const updatePayload: Record<string, unknown> = {};\n\n if (update.title !== undefined) {\n updatePayload.title = update.title;\n }\n if (update.description !== undefined) {\n updatePayload.body = update.description;\n }\n if (update.state !== undefined) {\n updatePayload.state = update.state === 'closed' ? 'closed' : 'open';\n }\n if (update.labels !== undefined) {\n updatePayload.labels = update.labels;\n }\n if (update.assignee !== undefined) {\n updatePayload.assignees = update.assignee ? [update.assignee] : [];\n }\n\n await this.octokit.issues.update({\n owner: this.owner,\n repo: this.repo,\n issue_number: issueNumber,\n ...updatePayload,\n });\n\n return this.getIssue(id);\n }\n\n async createIssue(newIssue: NewIssue): Promise<Issue> {\n const { data: issue } = await this.octokit.issues.create({\n owner: this.owner,\n repo: this.repo,\n title: newIssue.title,\n body: newIssue.description,\n labels: newIssue.labels,\n assignees: newIssue.assignee ? [newIssue.assignee] : undefined,\n });\n\n return this.normalizeIssue(issue);\n }\n\n async getComments(issueId: string): Promise<Comment[]> {\n const issueNumber = parseIssueNumber(issueId);\n\n const { data: comments } = await this.octokit.issues.listComments({\n owner: this.owner,\n repo: this.repo,\n issue_number: issueNumber,\n });\n\n return comments.map((c) => ({\n id: String(c.id),\n issueId,\n body: c.body ?? '',\n author: c.user?.login ?? 'Unknown',\n createdAt: c.created_at,\n updatedAt: c.updated_at,\n }));\n }\n\n async addComment(issueId: string, body: string): Promise<Comment> {\n const issueNumber = parseIssueNumber(issueId);\n\n const { data: comment } = await this.octokit.issues.createComment({\n owner: this.owner,\n repo: this.repo,\n issue_number: issueNumber,\n body,\n });\n\n return {\n id: String(comment.id),\n issueId,\n body: comment.body ?? '',\n author: comment.user?.login ?? 'Unknown',\n createdAt: comment.created_at,\n updatedAt: comment.updated_at,\n };\n }\n\n async transitionIssue(id: string, state: IssueState): Promise<void> {\n const issueNumber = parseIssueNumber(id);\n\n if (state === 'in_progress') {\n // GitHub has no native \"in progress\" state — use a label instead.\n await this.ensureLabelExists('in-progress', 'In progress', '0075ca');\n await this.octokit.issues.addLabels({\n owner: this.owner,\n repo: this.repo,\n issue_number: issueNumber,\n labels: ['in-progress'],\n });\n } else if (state === 'in_review') {\n // Swap in-progress label for in-review label\n await this.ensureLabelExists('in-review', 'In review', 'e4e669');\n await this.octokit.issues.addLabels({\n owner: this.owner,\n repo: this.repo,\n issue_number: issueNumber,\n labels: ['in-review'],\n });\n // Remove in-progress label if present\n await this.octokit.issues.removeLabel({\n owner: this.owner,\n repo: this.repo,\n issue_number: issueNumber,\n name: 'in-progress',\n }).catch(() => {/* label may not exist, ignore */});\n } else {\n // Remove in-progress and in-review labels when moving to open or closed\n const issue = await this.getIssue(id);\n for (const label of ['in-progress', 'in-review']) {\n if (issue.labels?.includes(label)) {\n await this.octokit.issues.removeLabel({\n owner: this.owner,\n repo: this.repo,\n issue_number: issueNumber,\n name: label,\n }).catch(() => {/* label may not exist, ignore */});\n }\n }\n await this.updateIssue(id, { state });\n }\n }\n\n /** Ensure a label exists in the repo, creating it if needed. */\n private async ensureLabelExists(name: string, description: string, color: string): Promise<void> {\n try {\n await this.octokit.issues.getLabel({ owner: this.owner, repo: this.repo, name });\n } catch {\n await this.octokit.issues.createLabel({\n owner: this.owner,\n repo: this.repo,\n name,\n description,\n color,\n }).catch(() => {/* race condition: another process created it first */});\n }\n }\n\n async linkPR(issueId: string, prUrl: string): Promise<void> {\n // GitHub auto-links PRs that mention issues\n // Add a comment with the PR link\n await this.addComment(\n issueId,\n `Linked Pull Request: ${prUrl}`\n );\n }\n\n private normalizeIssue(ghIssue: any): Issue {\n const labels: string[] = ghIssue.labels.map((l: any) =>\n typeof l === 'string' ? l : l.name\n );\n return {\n id: String(ghIssue.id),\n ref: `#${ghIssue.number}`,\n title: ghIssue.title,\n description: ghIssue.body ?? '',\n state: this.mapStateFromGitHub(ghIssue.state, labels),\n labels,\n assignee: ghIssue.assignee?.login,\n url: ghIssue.html_url,\n tracker: 'github',\n priority: undefined, // GitHub doesn't have priority\n dueDate: undefined, // GitHub doesn't have due dates on issues\n createdAt: ghIssue.created_at,\n updatedAt: ghIssue.updated_at,\n };\n }\n\n private mapStateFromGitHub(ghState: string, labels: string[] = []): IssueState {\n if (ghState === 'closed') return 'closed';\n if (labels.includes('in-progress')) return 'in_progress';\n return 'open';\n }\n\n private mapStateToGitHub(\n state?: IssueState\n ): 'open' | 'closed' | 'all' {\n if (!state) return 'open';\n if (state === 'closed') return 'closed';\n return 'open'; // Both 'open' and 'in_progress' map to 'open'\n }\n}\n","/**\n * GitLab Issues Tracker Adapter (Stub)\n *\n * Placeholder implementation for GitLab Issues support.\n * Full implementation will use @gitbeaker/rest.\n */\n\nimport type {\n Issue,\n IssueFilters,\n IssueState,\n IssueTracker,\n IssueUpdate,\n NewIssue,\n Comment,\n TrackerType,\n} from './interface.js';\nimport { NotImplementedError } from './interface.js';\n\nexport class GitLabTracker implements IssueTracker {\n readonly name: TrackerType = 'gitlab';\n\n constructor(\n private token: string,\n private projectId: string\n ) {\n // Stub - will initialize @gitbeaker client when implemented\n }\n\n async listIssues(_filters?: IssueFilters): Promise<Issue[]> {\n throw new NotImplementedError(\n 'GitLab tracker is not yet implemented. Coming soon!'\n );\n }\n\n async getIssue(_id: string): Promise<Issue> {\n throw new NotImplementedError(\n 'GitLab tracker is not yet implemented. Coming soon!'\n );\n }\n\n async updateIssue(_id: string, _update: IssueUpdate): Promise<Issue> {\n throw new NotImplementedError(\n 'GitLab tracker is not yet implemented. Coming soon!'\n );\n }\n\n async createIssue(_issue: NewIssue): Promise<Issue> {\n throw new NotImplementedError(\n 'GitLab tracker is not yet implemented. Coming soon!'\n );\n }\n\n async getComments(_issueId: string): Promise<Comment[]> {\n throw new NotImplementedError(\n 'GitLab tracker is not yet implemented. Coming soon!'\n );\n }\n\n async addComment(_issueId: string, _body: string): Promise<Comment> {\n throw new NotImplementedError(\n 'GitLab tracker is not yet implemented. Coming soon!'\n );\n }\n\n async transitionIssue(_id: string, _state: IssueState): Promise<void> {\n throw new NotImplementedError(\n 'GitLab tracker is not yet implemented. Coming soon!'\n );\n }\n\n async linkPR(_issueId: string, _prUrl: string): Promise<void> {\n throw new NotImplementedError(\n 'GitLab tracker is not yet implemented. Coming soon!'\n );\n }\n}\n","/**\n * Tracker Factory\n *\n * Creates appropriate tracker instances based on configuration.\n */\n\nimport type { IssueTracker, TrackerType } from './interface.js';\nimport { TrackerAuthError } from './interface.js';\nimport { LinearTracker } from './linear.js';\nimport { GitHubTracker } from './github.js';\nimport { GitLabTracker } from './gitlab.js';\nimport { RallyTracker } from './rally.js';\nimport type { TrackersConfig } from '../config.js';\nimport { loadConfig as loadYamlConfig } from '../config-yaml.js';\n\n// Configuration for a single tracker\nexport interface TrackerConfig {\n type: TrackerType;\n\n // Linear-specific\n apiKeyEnv?: string;\n team?: string;\n\n // GitHub-specific\n tokenEnv?: string;\n owner?: string;\n repo?: string;\n\n // GitLab-specific\n projectId?: string;\n\n // Rally-specific\n server?: string;\n workspace?: string;\n project?: string;\n}\n\n// Multi-tracker configuration (re-exported from config.ts)\n// Note: Use TrackersConfig from config.ts for full type with nested configs\n\n/**\n * Get tracker API key from config.yaml (Settings page).\n * This is checked FIRST — env vars are the fallback, not the other way around.\n */\nfunction getTrackerKeyFromConfig(trackerType: TrackerType): string | undefined {\n try {\n const { config: yamlConfig } = loadYamlConfig();\n return yamlConfig.trackerKeys[trackerType];\n } catch {\n return undefined;\n }\n}\n\n/**\n * Create a tracker instance from configuration.\n * Priority: config.yaml (Settings) > environment variable > custom env var name\n */\nexport function createTracker(config: TrackerConfig): IssueTracker {\n switch (config.type) {\n case 'linear': {\n const configKey = getTrackerKeyFromConfig('linear');\n const envKey = config.apiKeyEnv\n ? process.env[config.apiKeyEnv]\n : process.env.LINEAR_API_KEY;\n const apiKey = configKey || envKey;\n\n if (!apiKey) {\n throw new TrackerAuthError(\n 'linear',\n `API key not found. Configure in Settings or set ${config.apiKeyEnv ?? 'LINEAR_API_KEY'} environment variable.`\n );\n }\n\n return new LinearTracker(apiKey, { team: config.team });\n }\n\n case 'github': {\n const configKey = getTrackerKeyFromConfig('github');\n const envToken = config.tokenEnv\n ? process.env[config.tokenEnv]\n : process.env.GITHUB_TOKEN;\n const token = configKey || envToken;\n\n if (!token) {\n throw new TrackerAuthError(\n 'github',\n `Token not found. Configure in Settings or set ${config.tokenEnv ?? 'GITHUB_TOKEN'} environment variable.`\n );\n }\n\n if (!config.owner || !config.repo) {\n throw new Error(\n 'GitHub tracker requires owner and repo configuration'\n );\n }\n\n return new GitHubTracker(token, config.owner, config.repo);\n }\n\n case 'gitlab': {\n const configKey = getTrackerKeyFromConfig('gitlab');\n const envToken = config.tokenEnv\n ? process.env[config.tokenEnv]\n : process.env.GITLAB_TOKEN;\n const token = configKey || envToken;\n\n if (!token) {\n throw new TrackerAuthError(\n 'gitlab',\n `Token not found. Configure in Settings or set ${config.tokenEnv ?? 'GITLAB_TOKEN'} environment variable.`\n );\n }\n\n if (!config.projectId) {\n throw new Error('GitLab tracker requires projectId configuration');\n }\n\n return new GitLabTracker(token, config.projectId);\n }\n\n case 'rally': {\n const configKey = getTrackerKeyFromConfig('rally');\n const envKey = config.apiKeyEnv\n ? process.env[config.apiKeyEnv]\n : process.env.RALLY_API_KEY;\n const apiKey = configKey || envKey;\n\n if (!apiKey) {\n throw new TrackerAuthError(\n 'rally',\n `API key not found. Configure in Settings or set ${config.apiKeyEnv ?? 'RALLY_API_KEY'} environment variable.`\n );\n }\n\n return new RallyTracker({\n apiKey,\n server: config.server,\n workspace: config.workspace,\n project: config.project,\n });\n }\n\n default:\n throw new Error(`Unknown tracker type: ${config.type}`);\n }\n}\n\n/**\n * Create tracker from trackers configuration section\n */\nexport function createTrackerFromConfig(\n trackersConfig: TrackersConfig,\n trackerType: TrackerType\n): IssueTracker {\n const config = trackersConfig[trackerType];\n\n if (!config) {\n throw new Error(\n `No configuration found for tracker: ${trackerType}. Add [trackers.${trackerType}] to config.`\n );\n }\n\n return createTracker({ ...config, type: trackerType });\n}\n\n/**\n * Get the primary tracker from configuration\n */\nexport function getPrimaryTracker(trackersConfig: TrackersConfig): IssueTracker {\n return createTrackerFromConfig(trackersConfig, trackersConfig.primary);\n}\n\n/**\n * Get the secondary tracker from configuration (if configured)\n */\nexport function getSecondaryTracker(\n trackersConfig: TrackersConfig\n): IssueTracker | null {\n if (!trackersConfig.secondary) {\n return null;\n }\n return createTrackerFromConfig(trackersConfig, trackersConfig.secondary);\n}\n\n/**\n * Get all configured trackers\n */\nexport function getAllTrackers(trackersConfig: TrackersConfig): IssueTracker[] {\n const trackers: IssueTracker[] = [getPrimaryTracker(trackersConfig)];\n\n const secondary = getSecondaryTracker(trackersConfig);\n if (secondary) {\n trackers.push(secondary);\n }\n\n return trackers;\n}\n"],"mappings":"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","names":[]}
|