panopticon-cli 0.5.4 → 0.5.7
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-QXVDAW2M.js} +12 -9
- package/dist/archive-planning-U3AZAKWI.js +16 -0
- package/dist/{chunk-KBHRXV5T.js → chunk-43F4LDZ4.js} +3 -3
- package/dist/{chunk-KY2E2Q3T.js → chunk-4XR62WWV.js} +105 -46
- package/dist/chunk-4XR62WWV.js.map +1 -0
- 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-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-ID4OYXVH.js → chunk-TFPJD2I2.js} +112 -45
- package/dist/chunk-TFPJD2I2.js.map +1 -0
- 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-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 +1338 -2226
- 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-C7hJ5-o1.js +756 -0
- package/dist/dashboard/public/index.html +3 -2
- package/dist/dashboard/server.js +34720 -34297
- 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-O3TSBTLC.js +1725 -0
- package/dist/merge-agent-O3TSBTLC.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-IKG6VMNH.js} +7 -5
- package/dist/{specialist-context-C66TEMXS.js.map → specialist-context-IKG6VMNH.js.map} +1 -1
- package/dist/{specialist-logs-CJKXM3SR.js → specialist-logs-GFKUXCFG.js} +6 -4
- package/dist/{specialists-NXYD4Z62.js → specialists-XMFCFGYQ.js} +6 -4
- package/dist/specialists-XMFCFGYQ.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-QXVDAW2M.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-GFKUXCFG.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
|