@norman-else/dsh-claude 0.1.24 → 0.1.25

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/lib/index.mjs CHANGED
@@ -1,6 +1,6 @@
1
- import { C as CLAUDE_UPDATE_PATH, S as CLAUDE_UPDATE_CHECK_PATH, T as TASK_TOOL_NAMES, _ as CLAUDE_PROJECTION_PATH, a as latestClaudeTasks, b as CLAUDE_REPOSITORY_SETUP_PATH, c as normalizeTasksEvent, d as CLAUDE_ACTIVITY_EVENT, f as CLAUDE_CODE_PRESET_ID, g as CLAUDE_GLOBAL_SETTINGS_PATH, h as CLAUDE_DOCTOR_PATH, i as latestClaudeSessionBinding, l as redactText, m as CLAUDE_CODE_PROVIDER_IDS, n as currentClaudeActivityCursor, o as normalizeActivity, p as CLAUDE_CODE_PROVIDER, r as latestClaudeContextUsage, s as normalizeContextUsage, t as boundText, u as safeDetail, v as CLAUDE_REPOSITORY_ACTION_PATH, x as CLAUDE_REVIEW_COMMENT_PATH, y as CLAUDE_REPOSITORY_FEEDBACK_PATH } from "./events-DhrAr5gh.mjs";
2
- import { n as projectClaudeCommands, t as CLAUDE_COMMANDS_SERVICE } from "./command-bridge-Br25ODdm.mjs";
3
- import { a as resolveClaudeExecutable, n as ensureManagedPreset, o as runClaudeDoctor, t as ManagedPresetConflictError } from "./preset-installer-B_Cb0RG6.mjs";
1
+ import { C as CLAUDE_REVIEW_COMMENT_PATH, D as TASK_TOOL_NAMES, S as CLAUDE_REPOSITORY_STATUS_PATH, T as CLAUDE_UPDATE_PATH, _ as CLAUDE_JIRA_PATH, a as latestClaudeTasks, b as CLAUDE_REPOSITORY_FEEDBACK_PATH, c as normalizeTasksEvent, d as CLAUDE_ACTIVITY_EVENT, f as CLAUDE_CODE_PRESET_ID, g as CLAUDE_GLOBAL_SETTINGS_PATH, h as CLAUDE_DOCTOR_PATH, i as latestClaudeSessionBinding, l as redactText, m as CLAUDE_CODE_PROVIDER_IDS, n as currentClaudeActivityCursor, o as normalizeActivity, p as CLAUDE_CODE_PROVIDER, r as latestClaudeContextUsage, s as normalizeContextUsage, t as boundText, u as safeDetail, v as CLAUDE_PROJECTION_PATH, w as CLAUDE_UPDATE_CHECK_PATH, x as CLAUDE_REPOSITORY_SETUP_PATH, y as CLAUDE_REPOSITORY_ACTION_PATH } from "./events-BhU0iJUi.mjs";
2
+ import { n as projectClaudeCommands, t as CLAUDE_COMMANDS_SERVICE } from "./command-bridge-B48egrCr.mjs";
3
+ import { a as resolveClaudeExecutable, n as ensureManagedPreset, o as runClaudeDoctor, t as ManagedPresetConflictError } from "./preset-installer-Bzw8tuGI.mjs";
4
4
  import z from "@deepseek-ai/schemastery";
5
5
  import { createHash, randomUUID } from "node:crypto";
6
6
  import { chmod, mkdir, opendir, readFile, realpath, rename, rm, stat, writeFile } from "node:fs/promises";
@@ -25,18 +25,18 @@ function emptyProjection() {
25
25
  activities: []
26
26
  };
27
27
  }
28
- function record$6(value) {
28
+ function record$8(value) {
29
29
  return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
30
30
  }
31
31
  function finiteInteger(value) {
32
32
  return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
33
33
  }
34
- function string$3(value, max) {
34
+ function string$4(value, max) {
35
35
  return typeof value === "string" && value.length > 0 && value.length <= max;
36
36
  }
37
37
  function binding(value) {
38
- const input = record$6(value);
39
- if (input === void 0 || !string$3(input.claudeSessionId, 512) || !string$3(input.sdkVersion, 128) || !string$3(input.cwd, 4096) || input.cliVersion !== void 0 && !string$3(input.cliVersion, 128)) return void 0;
38
+ const input = record$8(value);
39
+ if (input === void 0 || !string$4(input.claudeSessionId, 512) || !string$4(input.sdkVersion, 128) || !string$4(input.cwd, 4096) || input.cliVersion !== void 0 && !string$4(input.cliVersion, 128)) return void 0;
40
40
  return {
41
41
  claudeSessionId: input.claudeSessionId,
42
42
  sdkVersion: input.sdkVersion,
@@ -65,22 +65,22 @@ const ACTIVITY_PHASES = /* @__PURE__ */ new Set([
65
65
  "failed"
66
66
  ]);
67
67
  function activity(value) {
68
- const input = record$6(value);
68
+ const input = record$8(value);
69
69
  if (input === void 0 || !finiteInteger(input.turn) || !finiteInteger(input.step) || !finiteInteger(input.ordinal) || typeof input.kind !== "string" || !ACTIVITY_KINDS.has(input.kind) || input.phase !== void 0 && (typeof input.phase !== "string" || !ACTIVITY_PHASES.has(input.phase))) return void 0;
70
70
  return normalizeActivity(input);
71
71
  }
72
72
  function contextUsage(value) {
73
- const input = record$6(value);
73
+ const input = record$8(value);
74
74
  if (input === void 0 || !Array.isArray(input.categories)) return void 0;
75
75
  return normalizeContextUsage(input);
76
76
  }
77
77
  function tasks(value) {
78
- const input = record$6(value);
78
+ const input = record$8(value);
79
79
  if (input === void 0 || !Array.isArray(input.tasks)) return void 0;
80
80
  return normalizeTasksEvent(input.tasks);
81
81
  }
82
82
  function parseClaudeSidecar(value) {
83
- const input = record$6(value);
83
+ const input = record$8(value);
84
84
  if (input === void 0 || input.schemaVersion !== SIDECAR_SCHEMA_VERSION || !finiteInteger(input.revision) || !Array.isArray(input.activities) || input.activities.length > MAX_ACTIVITIES) throw new Error("dsh-claude: invalid sidecar document");
85
85
  const activities = input.activities.map(activity);
86
86
  if (activities.some((item) => item === void 0)) throw new Error("dsh-claude: invalid sidecar activity");
@@ -653,10 +653,10 @@ function createUserQuestionBridge(userQuestions, activeContext) {
653
653
  }
654
654
  //#endregion
655
655
  //#region src/sdk-messages.ts
656
- function record$5(value) {
656
+ function record$7(value) {
657
657
  return value !== null && typeof value === "object" ? value : void 0;
658
658
  }
659
- function string$2(value) {
659
+ function string$3(value) {
660
660
  return typeof value === "string" ? value : void 0;
661
661
  }
662
662
  function taskUsageOf(usage) {
@@ -668,7 +668,7 @@ function taskUsageOf(usage) {
668
668
  return Object.keys(normalized).length === 0 ? void 0 : normalized;
669
669
  }
670
670
  function resultUsage(message) {
671
- const usage = record$5(message.usage);
671
+ const usage = record$7(message.usage);
672
672
  const normalized = {};
673
673
  if (usage !== void 0) {
674
674
  if (typeof usage.input_tokens === "number") normalized.inputTokens = usage.input_tokens;
@@ -680,26 +680,26 @@ function resultUsage(message) {
680
680
  return normalized;
681
681
  }
682
682
  function normalizeAssistant(message) {
683
- const content = record$5(message.message)?.content;
683
+ const content = record$7(message.message)?.content;
684
684
  if (!Array.isArray(content)) return [{
685
685
  kind: "protocol-error",
686
686
  title: "Malformed Claude assistant message",
687
687
  detail: message
688
688
  }];
689
- const parentToolUseId = string$2(message.parent_tool_use_id);
689
+ const parentToolUseId = string$3(message.parent_tool_use_id);
690
690
  const normalized = [];
691
691
  for (const item of content) {
692
- const block = record$5(item);
692
+ const block = record$7(item);
693
693
  if (block === void 0) continue;
694
694
  if (block.type === "text") {
695
- const text = string$2(block.text);
695
+ const text = string$3(block.text);
696
696
  if (text !== void 0 && text.length > 0) normalized.push({
697
697
  kind: "assistant-text",
698
698
  text,
699
699
  ...parentToolUseId === void 0 ? {} : { parentToolUseId }
700
700
  });
701
701
  } else if (block.type === "thinking") {
702
- const text = string$2(block.thinking);
702
+ const text = string$3(block.thinking);
703
703
  if (text !== void 0 && text.length > 0) normalized.push({
704
704
  kind: "thinking",
705
705
  text,
@@ -707,8 +707,8 @@ function normalizeAssistant(message) {
707
707
  ...parentToolUseId === void 0 ? {} : { parentToolUseId }
708
708
  });
709
709
  } else if (block.type === "tool_use") {
710
- const toolUseId = string$2(block.id);
711
- const toolName = string$2(block.name);
710
+ const toolUseId = string$3(block.id);
711
+ const toolName = string$3(block.name);
712
712
  if (toolUseId !== void 0 && toolName !== void 0) normalized.push({
713
713
  kind: "tool-call",
714
714
  toolUseId,
@@ -722,19 +722,19 @@ function normalizeAssistant(message) {
722
722
  }
723
723
  function normalizeUser(message) {
724
724
  if (message.isReplay === true) return [];
725
- const content = record$5(message.message)?.content;
725
+ const content = record$7(message.message)?.content;
726
726
  if (typeof content === "string") return [];
727
727
  if (!Array.isArray(content)) return [{
728
728
  kind: "protocol-error",
729
729
  title: "Malformed Claude user message",
730
730
  detail: message
731
731
  }];
732
- const parentToolUseId = string$2(message.parent_tool_use_id);
732
+ const parentToolUseId = string$3(message.parent_tool_use_id);
733
733
  const normalized = [];
734
734
  for (const item of content) {
735
- const block = record$5(item);
735
+ const block = record$7(item);
736
736
  if (block?.type !== "tool_result") continue;
737
- const toolUseId = string$2(block.tool_use_id);
737
+ const toolUseId = string$3(block.tool_use_id);
738
738
  if (toolUseId === void 0) continue;
739
739
  normalized.push({
740
740
  kind: "tool-result",
@@ -747,11 +747,11 @@ function normalizeUser(message) {
747
747
  return normalized;
748
748
  }
749
749
  function normalizeSystem(message) {
750
- const subtype = string$2(message.subtype);
750
+ const subtype = string$3(message.subtype);
751
751
  if (subtype === "init") {
752
- const sessionId = string$2(message.session_id);
753
- const cliVersion = string$2(message.claude_code_version);
754
- const cwd = string$2(message.cwd);
752
+ const sessionId = string$3(message.session_id);
753
+ const cliVersion = string$3(message.claude_code_version);
754
+ const cwd = string$3(message.cwd);
755
755
  return sessionId !== void 0 && cliVersion !== void 0 && cwd !== void 0 ? [{
756
756
  kind: "init",
757
757
  sessionId,
@@ -780,20 +780,20 @@ function normalizeSystem(message) {
780
780
  title: `Claude session ${String(message.state)}`
781
781
  }];
782
782
  if (subtype === "permission_denied") {
783
- const toolUseId = string$2(message.tool_use_id);
784
- const toolName = string$2(message.tool_name);
783
+ const toolUseId = string$3(message.tool_use_id);
784
+ const toolName = string$3(message.tool_name);
785
785
  if (toolUseId !== void 0 && toolName !== void 0) return [{
786
786
  kind: "permission-denied",
787
787
  toolUseId,
788
788
  toolName,
789
- summary: string$2(message.message) ?? "Claude Code denied the action"
789
+ summary: string$3(message.message) ?? "Claude Code denied the action"
790
790
  }];
791
791
  }
792
792
  if (subtype === "task_started") {
793
- const taskId = string$2(message.task_id);
794
- const description = string$2(message.description);
795
- const subagentType = string$2(message.subagent_type);
796
- const taskType = string$2(message.task_type);
793
+ const taskId = string$3(message.task_id);
794
+ const description = string$3(message.description);
795
+ const subagentType = string$3(message.subagent_type);
796
+ const taskType = string$3(message.task_type);
797
797
  return [{
798
798
  kind: "subagent",
799
799
  title: description ?? taskId ?? "Claude subagent started",
@@ -808,12 +808,12 @@ function normalizeSystem(message) {
808
808
  }];
809
809
  }
810
810
  if (subtype === "task_progress") {
811
- const taskId = string$2(message.task_id);
812
- const description = string$2(message.description);
813
- const summary = string$2(message.summary);
814
- const subagentType = string$2(message.subagent_type);
815
- const lastToolName = string$2(message.last_tool_name);
816
- const usage = taskUsageOf(record$5(message.usage));
811
+ const taskId = string$3(message.task_id);
812
+ const description = string$3(message.description);
813
+ const summary = string$3(message.summary);
814
+ const subagentType = string$3(message.subagent_type);
815
+ const lastToolName = string$3(message.last_tool_name);
816
+ const usage = taskUsageOf(record$7(message.usage));
817
817
  return [{
818
818
  kind: "subagent",
819
819
  title: summary ?? description ?? "Claude subagent update",
@@ -829,11 +829,11 @@ function normalizeSystem(message) {
829
829
  }];
830
830
  }
831
831
  if (subtype === "task_updated") {
832
- const patch = record$5(message.patch);
833
- const status = string$2(patch?.status);
834
- const taskId = string$2(message.task_id);
835
- const description = string$2(patch?.description);
836
- const error = string$2(patch?.error);
832
+ const patch = record$7(message.patch);
833
+ const status = string$3(patch?.status);
834
+ const taskId = string$3(message.task_id);
835
+ const description = string$3(patch?.description);
836
+ const error = string$3(patch?.error);
837
837
  const taskStatus = status === void 0 ? void 0 : status === "killed" ? "killed" : status === "completed" ? "completed" : status === "failed" ? "failed" : "running";
838
838
  return [{
839
839
  kind: "subagent",
@@ -849,10 +849,10 @@ function normalizeSystem(message) {
849
849
  if (subtype === "task_notification") {
850
850
  const failed = message.status === "failed";
851
851
  const stopped = message.status === "stopped" || message.status === "cancelled";
852
- const taskId = string$2(message.task_id);
853
- const summary = string$2(message.summary);
852
+ const taskId = string$3(message.task_id);
853
+ const summary = string$3(message.summary);
854
854
  const taskStatus = failed ? "failed" : stopped ? "stopped" : "completed";
855
- const usage = taskUsageOf(record$5(message.usage));
855
+ const usage = taskUsageOf(record$7(message.usage));
856
856
  return [{
857
857
  kind: "subagent",
858
858
  title: summary ?? taskId ?? "Claude subagent finished",
@@ -867,10 +867,10 @@ function normalizeSystem(message) {
867
867
  if (subtype === "background_tasks_changed") return [{
868
868
  kind: "background-tasks",
869
869
  tasks: (Array.isArray(message.tasks) ? message.tasks : []).flatMap((item) => {
870
- const entry = record$5(item);
871
- const taskId = string$2(entry?.task_id);
872
- const description = string$2(entry?.description);
873
- const taskType = string$2(entry?.task_type);
870
+ const entry = record$7(item);
871
+ const taskId = string$3(entry?.task_id);
872
+ const description = string$3(entry?.description);
873
+ const taskType = string$3(entry?.task_type);
874
874
  if (taskId === void 0 || description === void 0) return [];
875
875
  return [{
876
876
  taskId,
@@ -886,7 +886,7 @@ function normalizeSystem(message) {
886
886
  }];
887
887
  if (subtype === "informational" || subtype === "notification" || subtype === "local_command_output") return [{
888
888
  kind: message.level === "warning" ? "warning" : "status",
889
- title: string$2(message.content) ?? string$2(message.text) ?? "Claude Code notice",
889
+ title: string$3(message.content) ?? string$3(message.text) ?? "Claude Code notice",
890
890
  detail: message
891
891
  }];
892
892
  if (subtype?.startsWith("hook_") === true || subtype === "plugin_install") return [{
@@ -910,12 +910,12 @@ const RESULT_ERROR_SUBTYPES = /* @__PURE__ */ new Set([
910
910
  function normalizeSdkMessage(message) {
911
911
  const value = message;
912
912
  if (value.type === "stream_event") {
913
- const event = record$5(value.event);
914
- const parentToolUseId = string$2(value.parent_tool_use_id);
913
+ const event = record$7(value.event);
914
+ const parentToolUseId = string$3(value.parent_tool_use_id);
915
915
  if (event?.type === "content_block_delta") {
916
- const delta = record$5(event.delta);
916
+ const delta = record$7(event.delta);
917
917
  if (delta?.type === "text_delta") {
918
- const text = string$2(delta.text);
918
+ const text = string$3(delta.text);
919
919
  return text === void 0 ? [] : [{
920
920
  kind: "text-delta",
921
921
  text,
@@ -923,7 +923,7 @@ function normalizeSdkMessage(message) {
923
923
  }];
924
924
  }
925
925
  if (delta?.type === "thinking_delta") {
926
- const text = string$2(delta.thinking);
926
+ const text = string$3(delta.thinking);
927
927
  return text === void 0 ? [] : [{
928
928
  kind: "thinking",
929
929
  text,
@@ -938,7 +938,7 @@ function normalizeSdkMessage(message) {
938
938
  if (value.type === "user") return normalizeUser(value);
939
939
  if (value.type === "system") return normalizeSystem(value);
940
940
  if (value.type === "result") {
941
- const sessionId = string$2(value.session_id);
941
+ const sessionId = string$3(value.session_id);
942
942
  if (sessionId === void 0 || value.subtype !== "success" && !RESULT_ERROR_SUBTYPES.has(String(value.subtype))) return [{
943
943
  kind: "protocol-error",
944
944
  title: "Malformed Claude result message",
@@ -946,11 +946,11 @@ function normalizeSdkMessage(message) {
946
946
  }];
947
947
  const success = value.subtype === "success" && value.is_error !== true;
948
948
  const errors = Array.isArray(value.errors) ? value.errors.filter((item) => typeof item === "string") : void 0;
949
- const terminalReason = string$2(value.terminal_reason);
950
- const userMessageUuid = string$2(value.user_message_uuid);
951
- const permissionDenials = Array.isArray(value.permission_denials) ? value.permission_denials.map((item) => record$5(item)).filter((item) => item !== void 0).map((item) => {
952
- const toolName = string$2(item.tool_name);
953
- const toolUseId = string$2(item.tool_use_id);
949
+ const terminalReason = string$3(value.terminal_reason);
950
+ const userMessageUuid = string$3(value.user_message_uuid);
951
+ const permissionDenials = Array.isArray(value.permission_denials) ? value.permission_denials.map((item) => record$7(item)).filter((item) => item !== void 0).map((item) => {
952
+ const toolName = string$3(item.tool_name);
953
+ const toolUseId = string$3(item.tool_use_id);
954
954
  return toolName === void 0 || toolUseId === void 0 ? void 0 : {
955
955
  toolName,
956
956
  toolUseId
@@ -974,7 +974,7 @@ function normalizeSdkMessage(message) {
974
974
  detail: value.error ?? value.output
975
975
  }];
976
976
  if (value.type === "rate_limit_event") {
977
- const status = string$2(record$5(value.rate_limit_info)?.status);
977
+ const status = string$3(record$7(value.rate_limit_info)?.status);
978
978
  return [{
979
979
  kind: "status",
980
980
  title: status !== void 0 && status !== "allowed" ? "Claude rate limit is blocking requests" : "Claude rate limit status changed",
@@ -2000,7 +2000,7 @@ var ClaudeSupervisor = class {
2000
2000
  //#region src/review-comments.ts
2001
2001
  const MAX_COMMENTS_PER_SESSION = 50;
2002
2002
  const MAX_TEXT_CHARS$1 = 2e3;
2003
- const MAX_PATH_CHARS$1 = 1024;
2003
+ const MAX_PATH_CHARS$2 = 1024;
2004
2004
  const MAX_LINE = 1e7;
2005
2005
  var ReviewCommentError = class extends Error {
2006
2006
  code;
@@ -2019,7 +2019,7 @@ var ReviewCommentStore = class {
2019
2019
  add(sessionId, input) {
2020
2020
  const path = typeof input.path === "string" ? input.path.trim() : "";
2021
2021
  const text = typeof input.text === "string" ? input.text.trim() : "";
2022
- if (path.length === 0 || path.length > MAX_PATH_CHARS$1 || /[\0\r\n]/u.test(path)) throw new ReviewCommentError("invalid-request", "The comment path is invalid.");
2022
+ if (path.length === 0 || path.length > MAX_PATH_CHARS$2 || /[\0\r\n]/u.test(path)) throw new ReviewCommentError("invalid-request", "The comment path is invalid.");
2023
2023
  if (text.length === 0 || text.length > MAX_TEXT_CHARS$1 || text.includes("\0")) throw new ReviewCommentError("invalid-request", "The comment text is invalid.");
2024
2024
  if (typeof input.line !== "number" || !Number.isSafeInteger(input.line) || input.line < 1 || input.line > MAX_LINE) throw new ReviewCommentError("invalid-request", "The comment line is invalid.");
2025
2025
  if (!validSide(input.side)) throw new ReviewCommentError("invalid-request", "The comment side is invalid.");
@@ -2781,14 +2781,14 @@ function parseGitHubRemote(value) {
2781
2781
  if (match?.[1] === void 0 || match[2] === void 0) return void 0;
2782
2782
  return `${match[1]}/${match[2]}`;
2783
2783
  }
2784
- function record$4(value) {
2784
+ function record$6(value) {
2785
2785
  return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
2786
2786
  }
2787
2787
  function aggregateChecks(value) {
2788
2788
  if (!Array.isArray(value) || value.length === 0) return "none";
2789
2789
  let pending = false;
2790
2790
  for (const item of value) {
2791
- const check = record$4(item);
2791
+ const check = record$6(item);
2792
2792
  if (check === void 0) continue;
2793
2793
  const conclusion = typeof check.conclusion === "string" ? check.conclusion.toUpperCase() : void 0;
2794
2794
  const status = typeof check.status === "string" ? check.status.toUpperCase() : void 0;
@@ -2812,7 +2812,7 @@ function reviewState(value) {
2812
2812
  return "none";
2813
2813
  }
2814
2814
  function parsePullRequest(value) {
2815
- const input = record$4(value);
2815
+ const input = record$6(value);
2816
2816
  if (input === void 0 || !Number.isSafeInteger(input.number) || Number(input.number) <= 0 || typeof input.title !== "string" || typeof input.url !== "string") return void 0;
2817
2817
  let url;
2818
2818
  try {
@@ -2833,7 +2833,7 @@ function parsePullRequest(value) {
2833
2833
  review: reviewState(input.reviewDecision),
2834
2834
  checks: aggregateChecks(input.statusCheckRollup),
2835
2835
  ...typeof input.mergeStateStatus === "string" ? { mergeState: bounded(input.mergeStateStatus) } : {},
2836
- ...typeof record$4(input.author)?.login === "string" ? { author: bounded(String(record$4(input.author)?.login)) } : {},
2836
+ ...typeof record$6(input.author)?.login === "string" ? { author: bounded(String(record$6(input.author)?.login)) } : {},
2837
2837
  ...typeof input.createdAt === "string" && Number.isFinite(Date.parse(input.createdAt)) ? { createdAt: new Date(input.createdAt).toISOString() } : {},
2838
2838
  ...typeof input.mergedAt === "string" && Number.isFinite(Date.parse(input.mergedAt)) ? { mergedAt: new Date(input.mergedAt).toISOString() } : {},
2839
2839
  ...typeof input.baseRefName === "string" && bounded(input.baseRefName).length > 0 ? { baseBranch: bounded(input.baseRefName) } : {}
@@ -3099,7 +3099,7 @@ var RepositoryStatusService = class {
3099
3099
  const MAX_OUTPUT_BYTES$2 = 131072;
3100
3100
  const GIT_TIMEOUT_MS$2 = 1e4;
3101
3101
  const GIT_FETCH_TIMEOUT_MS = 6e4;
3102
- const MAX_PATH_CHARS = 4096;
3102
+ const MAX_PATH_CHARS$1 = 4096;
3103
3103
  const MAX_BRANCH_CHARS = 512;
3104
3104
  const LEASE_SCHEMA_VERSION = 1;
3105
3105
  const CLEANUP_GRACE_MS = 12e4;
@@ -3123,7 +3123,7 @@ async function collect$2(handle) {
3123
3123
  };
3124
3124
  }
3125
3125
  function safePath(value) {
3126
- if (value.length === 0 || value.length > MAX_PATH_CHARS || !isAbsolute(value) || value.includes("\0")) throw new RepositorySetupError("invalid-path", "The workspace path is invalid.");
3126
+ if (value.length === 0 || value.length > MAX_PATH_CHARS$1 || !isAbsolute(value) || value.includes("\0")) throw new RepositorySetupError("invalid-path", "The workspace path is invalid.");
3127
3127
  return resolve(value);
3128
3128
  }
3129
3129
  function safeBranch(value) {
@@ -3224,10 +3224,73 @@ var RepositorySetupService = class {
3224
3224
  const remote = info.remoteBranches.includes(branch);
3225
3225
  if (!local && !remote) throw new RepositorySetupError("branch-not-found", "The selected local or remote-tracking branch does not exist.");
3226
3226
  const requestedBranch = explicitBranchName === void 0 ? void 0 : safeBranch(explicitBranchName);
3227
- if (useWorktree) return this.#createWorktree(info.root, branch, local ? `refs/heads/${branch}` : `refs/remotes/${branch}`, requestedBranch, progress);
3227
+ if (useWorktree) return this.#createWorktree(info.root, branch, local ? `refs/heads/${branch}` : `refs/remotes/${branch}`, requestedBranch, requestedBranch !== void 0 && info.branches.includes(requestedBranch), progress);
3228
3228
  progress("switching-branch");
3229
3229
  return !local && remote ? this.#checkoutRemote(info, branch) : this.#checkout(info, branch);
3230
3230
  }
3231
+ /** Tear down a merged branch: remove a plugin worktree (and its lease and
3232
+ * branch), or switch a plain checkout back to the base branch and delete
3233
+ * the merged branch. Refuses dirty trees. */
3234
+ async cleanupMerged(pathValue, baseBranch) {
3235
+ const path = safePath(pathValue);
3236
+ const base = safeBranch(baseBranch);
3237
+ const git = await this.#git();
3238
+ const status = await this.#run(git, [
3239
+ "status",
3240
+ "--porcelain=v1",
3241
+ "--untracked-files=normal"
3242
+ ], path);
3243
+ if (status.exitCode !== 0 || status.lossy) throw new RepositorySetupError("repository-unavailable", "The repository state is unavailable.");
3244
+ if (status.stdout.trim().length > 0) throw new RepositorySetupError("dirty-workspace", "Commit or stash workspace changes before cleaning up.");
3245
+ const lease = (await this.#readLeases()).find((item) => comparablePath(item.path) === comparablePath(path));
3246
+ if (lease !== void 0) return this.#serialize(async () => {
3247
+ if ((await this.#run(git, [
3248
+ "worktree",
3249
+ "remove",
3250
+ "--",
3251
+ lease.path
3252
+ ], lease.root)).exitCode !== 0) throw new RepositorySetupError("worktree-remove-failed", "Git could not remove the worktree.");
3253
+ await this.#run(git, [
3254
+ "branch",
3255
+ "-D",
3256
+ "--",
3257
+ lease.branch
3258
+ ], lease.root).catch(() => void 0);
3259
+ const current = await this.#readLeases();
3260
+ await this.#writeLeases(current.filter((item) => item.id !== lease.id));
3261
+ return {
3262
+ mode: "worktree",
3263
+ root: lease.root,
3264
+ branch: lease.branch
3265
+ };
3266
+ });
3267
+ const root = await this.#repositoryRoot(git, path);
3268
+ const head = await this.#run(git, [
3269
+ "symbolic-ref",
3270
+ "--quiet",
3271
+ "--short",
3272
+ "HEAD"
3273
+ ], root);
3274
+ const branch = head.exitCode === 0 ? head.stdout.trim() : "";
3275
+ if (branch.length === 0 || branch === base) throw new RepositorySetupError("nothing-to-clean", "The checkout is already on the base branch.");
3276
+ if ((await this.#run(git, [
3277
+ "switch",
3278
+ "--",
3279
+ base
3280
+ ], root)).exitCode !== 0) throw new RepositorySetupError("checkout-failed", "Git could not switch to the base branch.");
3281
+ await this.#run(git, [
3282
+ "branch",
3283
+ "-D",
3284
+ "--",
3285
+ branch
3286
+ ], root).catch(() => void 0);
3287
+ await this.#run(git, ["pull", "--ff-only"], root, GIT_FETCH_TIMEOUT_MS).catch(() => void 0);
3288
+ return {
3289
+ mode: "checkout",
3290
+ root,
3291
+ branch
3292
+ };
3293
+ }
3231
3294
  bindLease(leaseId, sessionId) {
3232
3295
  if (leaseId.length === 0 || leaseId.length > 128 || sessionId.length === 0 || sessionId.length > 1024) return Promise.reject(new RepositorySetupError("invalid-lease", "The worktree lease is invalid."));
3233
3296
  return this.#serialize(async () => {
@@ -3336,8 +3399,8 @@ var RepositorySetupService = class {
3336
3399
  };
3337
3400
  }
3338
3401
  async #checkout(info, branch) {
3339
- if (info.dirty) throw new RepositorySetupError("dirty-workspace", "Commit or stash workspace changes before switching branches.");
3340
3402
  if (info.current !== branch) {
3403
+ if (info.dirty) throw new RepositorySetupError("dirty-workspace", "Commit or stash workspace changes before switching branches.");
3341
3404
  const git = await this.#git();
3342
3405
  const worktrees = await this.#run(git, [
3343
3406
  "worktree",
@@ -3360,7 +3423,7 @@ var RepositorySetupService = class {
3360
3423
  branch
3361
3424
  };
3362
3425
  }
3363
- async #createWorktree(root, baseBranch, baseRef, explicitBranchName, progress) {
3426
+ async #createWorktree(root, baseBranch, baseRef, explicitBranchName, reuseExistingBranch, progress) {
3364
3427
  const git = await this.#git();
3365
3428
  progress("fetching");
3366
3429
  const fetched = await this.#run(git, [
@@ -3377,14 +3440,25 @@ var RepositorySetupService = class {
3377
3440
  const path = join(this.#worktreeRoot, `${slug(basename(root), "repository")}-${stamp}-${suffix}`);
3378
3441
  progress("creating-worktree");
3379
3442
  await mkdir(this.#worktreeRoot, { recursive: true });
3380
- if ((await this.#run(git, [
3443
+ if (reuseExistingBranch) await this.#run(git, ["worktree", "prune"], root).catch(() => void 0);
3444
+ const created = await this.#run(git, reuseExistingBranch ? [
3445
+ "worktree",
3446
+ "add",
3447
+ "--",
3448
+ path,
3449
+ branch
3450
+ ] : [
3381
3451
  "worktree",
3382
3452
  "add",
3383
3453
  "-b",
3384
3454
  branch,
3385
3455
  path,
3386
3456
  baseRef
3387
- ], root)).exitCode !== 0) throw new RepositorySetupError("worktree-failed", "Git could not create the worktree.");
3457
+ ], root);
3458
+ if (created.exitCode !== 0) {
3459
+ const detail = created.stderr.split(/\r?\n/u).map((line) => line.trim()).filter((line) => line.length > 0).at(-1);
3460
+ throw new RepositorySetupError("worktree-failed", detail === void 0 ? "Git could not create the worktree." : `Git could not create the worktree: ${detail}`);
3461
+ }
3388
3462
  const item = {
3389
3463
  id: randomUUID(),
3390
3464
  root,
@@ -3407,6 +3481,12 @@ var RepositorySetupService = class {
3407
3481
  "--",
3408
3482
  path
3409
3483
  ], root).catch(() => void 0);
3484
+ if (!reuseExistingBranch) await this.#run(git, [
3485
+ "branch",
3486
+ "-D",
3487
+ "--",
3488
+ branch
3489
+ ], root).catch(() => void 0);
3410
3490
  throw error;
3411
3491
  }
3412
3492
  return {
@@ -3920,24 +4000,24 @@ var RepositoryActionService = class {
3920
4000
  };
3921
4001
  //#endregion
3922
4002
  //#region src/repository-setup-routes.ts
3923
- const MAX_BODY_BYTES$2 = 16384;
3924
- function record$3(value) {
4003
+ const MAX_BODY_BYTES$3 = 16384;
4004
+ function record$5(value) {
3925
4005
  return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
3926
4006
  }
3927
- async function readJson$2(req) {
4007
+ async function readJson$3(req) {
3928
4008
  const chunks = [];
3929
4009
  let size = 0;
3930
4010
  for await (const chunk of req) {
3931
4011
  const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
3932
4012
  size += buffer.length;
3933
- if (size > MAX_BODY_BYTES$2) throw new RepositorySetupError("body-too-large", "The request body is too large.");
4013
+ if (size > MAX_BODY_BYTES$3) throw new RepositorySetupError("body-too-large", "The request body is too large.");
3934
4014
  chunks.push(buffer);
3935
4015
  }
3936
- const value = record$3(JSON.parse(Buffer.concat(chunks).toString("utf8")));
4016
+ const value = record$5(JSON.parse(Buffer.concat(chunks).toString("utf8")));
3937
4017
  if (value === void 0) throw new RepositorySetupError("invalid-request", "The request body is invalid.");
3938
4018
  return value;
3939
4019
  }
3940
- function string$1(input, key) {
4020
+ function string$2(input, key) {
3941
4021
  const value = input[key];
3942
4022
  if (typeof value !== "string") throw new RepositorySetupError("invalid-request", `The ${key} field is required.`);
3943
4023
  return value;
@@ -3965,7 +4045,7 @@ async function streamSetup(res, service, input) {
3965
4045
  try {
3966
4046
  ndjson(res, {
3967
4047
  type: "complete",
3968
- result: await service.setup(string$1(input, "cwd"), string$1(input, "branch"), input.worktree, optionalString$1(input, "branchName"), (stage) => {
4048
+ result: await service.setup(string$2(input, "cwd"), string$2(input, "branch"), input.worktree, optionalString$1(input, "branchName"), (stage) => {
3969
4049
  if (!ended) ndjson(res, {
3970
4050
  type: "progress",
3971
4051
  stage
@@ -4001,15 +4081,20 @@ function registerRepositorySetupRoute(ctx, service) {
4001
4081
  }
4002
4082
  if (pathname === "/plugins/dsh-claude/repository/setup") {
4003
4083
  if (req.method !== "POST") return json(res, 405, { error: "method not allowed" });
4004
- const input = await readJson$2(req);
4084
+ const input = await readJson$3(req);
4005
4085
  if (typeof input.worktree !== "boolean") throw new RepositorySetupError("invalid-request", "The worktree field is required.");
4006
4086
  await streamSetup(res, service, input);
4007
4087
  return;
4008
4088
  }
4089
+ if (pathname === `/plugins/dsh-claude/repository/setup/cleanup`) {
4090
+ if (req.method !== "POST") return json(res, 405, { error: "method not allowed" });
4091
+ const input = await readJson$3(req);
4092
+ return json(res, 200, await service.cleanupMerged(string$2(input, "path"), string$2(input, "baseBranch")));
4093
+ }
4009
4094
  if (pathname === `/plugins/dsh-claude/repository/setup/bind`) {
4010
4095
  if (req.method !== "POST") return json(res, 405, { error: "method not allowed" });
4011
- const input = await readJson$2(req);
4012
- await service.bindLease(string$1(input, "leaseId"), string$1(input, "sessionId"));
4096
+ const input = await readJson$3(req);
4097
+ await service.bindLease(string$2(input, "leaseId"), string$2(input, "sessionId"));
4013
4098
  return json(res, 200, { ok: true });
4014
4099
  }
4015
4100
  return json(res, 404, { error: "not found" });
@@ -4026,7 +4111,7 @@ function registerRepositorySetupRoute(ctx, service) {
4026
4111
  }
4027
4112
  //#endregion
4028
4113
  //#region src/repository-action-routes.ts
4029
- const MAX_BODY_BYTES$1 = 16384;
4114
+ const MAX_BODY_BYTES$2 = 16384;
4030
4115
  const MAX_SESSION_ID_CHARS$2 = 1024;
4031
4116
  const ACTIONS = /* @__PURE__ */ new Set([
4032
4117
  "commit",
@@ -4036,19 +4121,19 @@ const ACTIONS = /* @__PURE__ */ new Set([
4036
4121
  "merge-pr",
4037
4122
  "update-branch"
4038
4123
  ]);
4039
- function record$2(value) {
4124
+ function record$4(value) {
4040
4125
  return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
4041
4126
  }
4042
- async function readJson$1(req) {
4127
+ async function readJson$2(req) {
4043
4128
  const chunks = [];
4044
4129
  let size = 0;
4045
4130
  for await (const chunk of req) {
4046
4131
  const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
4047
4132
  size += buffer.length;
4048
- if (size > MAX_BODY_BYTES$1) throw new RepositoryActionError("body-too-large", "The request body is too large.");
4133
+ if (size > MAX_BODY_BYTES$2) throw new RepositoryActionError("body-too-large", "The request body is too large.");
4049
4134
  chunks.push(buffer);
4050
4135
  }
4051
- const value = record$2(JSON.parse(Buffer.concat(chunks).toString("utf8")));
4136
+ const value = record$4(JSON.parse(Buffer.concat(chunks).toString("utf8")));
4052
4137
  if (value === void 0) throw new RepositoryActionError("invalid-request", "The request body is invalid.");
4053
4138
  return value;
4054
4139
  }
@@ -4057,7 +4142,7 @@ function sessionId$1(url) {
4057
4142
  if (value === null || value.length === 0 || value.length > MAX_SESSION_ID_CHARS$2) throw new RepositoryActionError("invalid-session", "The session is invalid.");
4058
4143
  return value;
4059
4144
  }
4060
- function string(input, key) {
4145
+ function string$1(input, key) {
4061
4146
  const value = input[key];
4062
4147
  if (typeof value !== "string") throw new RepositoryActionError("invalid-request", `The ${key} field is required.`);
4063
4148
  return value;
@@ -4073,8 +4158,8 @@ function actionRequest(input) {
4073
4158
  if (typeof action !== "string" || !ACTIONS.has(action) || typeof input.includeUnstaged !== "boolean") throw new RepositoryActionError("invalid-request", "The repository action is invalid.");
4074
4159
  return {
4075
4160
  action,
4076
- fingerprint: string(input, "fingerprint"),
4077
- message: action === "push" || action === "merge-pr" || action === "update-branch" ? optionalString(input, "message") ?? "" : string(input, "message"),
4161
+ fingerprint: string$1(input, "fingerprint"),
4162
+ message: action === "push" || action === "merge-pr" || action === "update-branch" ? optionalString(input, "message") ?? "" : string$1(input, "message"),
4078
4163
  includeUnstaged: input.includeUnstaged,
4079
4164
  ...optionalString(input, "prTitle") === void 0 ? {} : { prTitle: optionalString(input, "prTitle") },
4080
4165
  ...optionalString(input, "prBody") === void 0 ? {} : { prBody: optionalString(input, "prBody") },
@@ -4103,12 +4188,12 @@ function registerRepositoryActionRoute(ctx, service, cwdForSession) {
4103
4188
  }
4104
4189
  if (url.pathname === `/plugins/dsh-claude/repository/action/message`) {
4105
4190
  if (req.method !== "POST") return json(res, 405, { error: "method not allowed" });
4106
- const input = await readJson$1(req);
4107
- return json(res, 200, { message: await service.generateMessage(cwd, string(input, "fingerprint")) });
4191
+ const input = await readJson$2(req);
4192
+ return json(res, 200, { message: await service.generateMessage(cwd, string$1(input, "fingerprint")) });
4108
4193
  }
4109
4194
  if (url.pathname === "/plugins/dsh-claude/repository/action") {
4110
4195
  if (req.method !== "POST") return json(res, 405, { error: "method not allowed" });
4111
- return json(res, 200, await service.execute(cwd, actionRequest(await readJson$1(req))));
4196
+ return json(res, 200, await service.execute(cwd, actionRequest(await readJson$2(req))));
4112
4197
  }
4113
4198
  return json(res, 404, { error: "not found" });
4114
4199
  } catch (error) {
@@ -4152,14 +4237,14 @@ async function collect(handle) {
4152
4237
  lossy: stdout?.lossy === true
4153
4238
  };
4154
4239
  }
4155
- function record$1(value) {
4240
+ function record$3(value) {
4156
4241
  return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
4157
4242
  }
4158
4243
  function parseReviewComments(value) {
4159
4244
  if (!Array.isArray(value)) return [];
4160
4245
  const comments = [];
4161
4246
  for (const item of value) {
4162
- const input = record$1(item);
4247
+ const input = record$3(item);
4163
4248
  if (input === void 0 || !Number.isSafeInteger(input.id)) continue;
4164
4249
  const body = typeof input.body === "string" ? input.body.trim() : "";
4165
4250
  const path = typeof input.path === "string" ? input.path : "";
@@ -4171,7 +4256,7 @@ function parseReviewComments(value) {
4171
4256
  path,
4172
4257
  ...line === void 0 ? {} : { line },
4173
4258
  side: input.side === "LEFT" ? "old" : "new",
4174
- author: typeof record$1(input.user)?.login === "string" ? String(record$1(input.user)?.login) : "unknown",
4259
+ author: typeof record$3(input.user)?.login === "string" ? String(record$3(input.user)?.login) : "unknown",
4175
4260
  body: body.slice(0, MAX_COMMENT_CHARS),
4176
4261
  url
4177
4262
  });
@@ -4188,7 +4273,7 @@ function parseFailingChecks(value) {
4188
4273
  if (!Array.isArray(value)) return [];
4189
4274
  const failing = [];
4190
4275
  for (const item of value) {
4191
- const input = record$1(item);
4276
+ const input = record$3(item);
4192
4277
  if (input === void 0 || input.bucket !== "fail" || typeof input.name !== "string") continue;
4193
4278
  failing.push({
4194
4279
  name: input.name,
@@ -4339,6 +4424,315 @@ function registerPullRequestFeedbackRoute(ctx, service, cwdForSession) {
4339
4424
  }), "dsh-claude: pull request feedback route");
4340
4425
  }
4341
4426
  //#endregion
4427
+ //#region src/repository-status-routes.ts
4428
+ const MAX_PATH_CHARS = 4096;
4429
+ /** Read-only repository status for an arbitrary directory (the overview panel
4430
+ * aggregates every Claude session's checkout through this). */
4431
+ function registerRepositoryStatusRoute(ctx, service) {
4432
+ ctx.effect(() => ctx.webServer.register({
4433
+ kind: "exact",
4434
+ path: CLAUDE_REPOSITORY_STATUS_PATH,
4435
+ handler: async (req, res) => {
4436
+ if (!trustedRequest(req)) return json(res, 403, { error: "forbidden" });
4437
+ if (req.method !== "GET") return json(res, 405, { error: "method not allowed" });
4438
+ const cwd = new URL(req.url ?? "/", "http://localhost").searchParams.get("cwd");
4439
+ if (cwd === null || cwd.length === 0 || cwd.length > MAX_PATH_CHARS || !isAbsolute(cwd) || cwd.includes("\0")) return json(res, 400, {
4440
+ error: "invalid-request",
4441
+ message: "The cwd query parameter is invalid."
4442
+ });
4443
+ try {
4444
+ return json(res, 200, await service.inspect(cwd));
4445
+ } catch {
4446
+ return json(res, 500, {
4447
+ error: "repository-status-unavailable",
4448
+ message: "Repository status is unavailable."
4449
+ });
4450
+ }
4451
+ }
4452
+ }), "dsh-claude: repository status route");
4453
+ }
4454
+ //#endregion
4455
+ //#region src/jira.ts
4456
+ const REQUEST_TIMEOUT_MS = 15e3;
4457
+ const MAX_RESULTS = 20;
4458
+ const MAX_QUERY_CHARS = 200;
4459
+ const MAX_STORE_BYTES = 65536;
4460
+ var JiraError = class extends Error {
4461
+ code;
4462
+ constructor(code, message) {
4463
+ super(message);
4464
+ this.name = "JiraError";
4465
+ this.code = code;
4466
+ }
4467
+ };
4468
+ function record$2(value) {
4469
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
4470
+ }
4471
+ /** Jira Cloud sites are origins; Data Center may carry a context path. */
4472
+ function normalizeSiteUrl(value) {
4473
+ let url;
4474
+ try {
4475
+ url = new URL(value.trim());
4476
+ } catch {
4477
+ throw new JiraError("invalid-site", "The Jira site URL is invalid.");
4478
+ }
4479
+ if (url.protocol !== "https:") throw new JiraError("invalid-site", "The Jira site URL must use https.");
4480
+ return `${url.origin}${url.pathname.replace(/\/+$/u, "")}`;
4481
+ }
4482
+ function ticketKeyOf(query) {
4483
+ const match = /^([A-Za-z][A-Za-z0-9_]*)-(\d+)$/u.exec(query.trim());
4484
+ return match === null ? void 0 : `${match[1].toUpperCase()}-${match[2]}`;
4485
+ }
4486
+ function buildJql(query) {
4487
+ const trimmed = query.trim().slice(0, MAX_QUERY_CHARS);
4488
+ if (trimmed.length === 0) return "statusCategory != Done ORDER BY updated DESC";
4489
+ const key = ticketKeyOf(trimmed);
4490
+ if (key !== void 0) return `key = "${key}"`;
4491
+ return `text ~ "${trimmed.replaceAll("\\", "\\\\").replaceAll("\"", "\\\"")}*" ORDER BY updated DESC`;
4492
+ }
4493
+ function parseTickets(value, siteUrl) {
4494
+ const issues = record$2(value)?.issues;
4495
+ if (!Array.isArray(issues)) return [];
4496
+ const tickets = [];
4497
+ for (const item of issues) {
4498
+ const issue = record$2(item);
4499
+ const fields = record$2(issue?.fields);
4500
+ if (issue === void 0 || typeof issue.key !== "string" || fields === void 0) continue;
4501
+ const status = record$2(fields.status)?.name;
4502
+ const type = record$2(fields.issuetype)?.name;
4503
+ tickets.push({
4504
+ key: issue.key,
4505
+ summary: typeof fields.summary === "string" ? fields.summary.slice(0, 256) : "",
4506
+ url: `${siteUrl}/browse/${issue.key}`,
4507
+ ...typeof status === "string" ? { status } : {},
4508
+ ...typeof type === "string" ? { type } : {}
4509
+ });
4510
+ if (tickets.length >= MAX_RESULTS) break;
4511
+ }
4512
+ return tickets;
4513
+ }
4514
+ function statusOf(store) {
4515
+ if (store === void 0) return { connected: false };
4516
+ return {
4517
+ connected: true,
4518
+ siteUrl: store.siteUrl,
4519
+ email: store.email,
4520
+ ...store.displayName === void 0 ? {} : { displayName: store.displayName }
4521
+ };
4522
+ }
4523
+ /** Jira Cloud access through the user's API token (Basic auth). */
4524
+ var JiraService = class {
4525
+ #storePath;
4526
+ #fetch;
4527
+ constructor(options = {}) {
4528
+ this.#storePath = options.storePath ?? dshHomePath("plugins", "dsh-claude", "jira.json");
4529
+ this.#fetch = options.fetch ?? globalThis.fetch;
4530
+ }
4531
+ async status() {
4532
+ return statusOf(await this.#read());
4533
+ }
4534
+ async connect(input) {
4535
+ const siteUrl = normalizeSiteUrl(input.siteUrl);
4536
+ const email = input.email.trim();
4537
+ const apiToken = input.apiToken.trim();
4538
+ if (email.length === 0 || email.length > 320 || apiToken.length === 0 || apiToken.length > 4096) throw new JiraError("invalid-credentials", "An account email and API token are required.");
4539
+ const connection = {
4540
+ siteUrl,
4541
+ email,
4542
+ apiToken
4543
+ };
4544
+ const myself = record$2(await this.#json(connection, "/rest/api/3/myself"));
4545
+ const displayName = typeof myself?.displayName === "string" ? myself.displayName : void 0;
4546
+ const accountId = typeof myself?.accountId === "string" ? myself.accountId : void 0;
4547
+ const store = {
4548
+ ...connection,
4549
+ ...displayName === void 0 ? {} : { displayName },
4550
+ ...accountId === void 0 ? {} : { accountId }
4551
+ };
4552
+ await this.#write(store);
4553
+ return statusOf(store);
4554
+ }
4555
+ /** Assign a ticket to the connected account (used once a ticket's worktree exists). */
4556
+ async assignToMe(key) {
4557
+ const store = await this.#read();
4558
+ if (store === void 0) throw new JiraError("not-connected", "Connect Jira in Settings first.");
4559
+ const ticket = ticketKeyOf(key);
4560
+ if (ticket === void 0) throw new JiraError("invalid-request", "The ticket key is invalid.");
4561
+ let accountId = store.accountId;
4562
+ if (accountId === void 0) {
4563
+ const myself = record$2(await this.#json(store, "/rest/api/3/myself"));
4564
+ if (typeof myself?.accountId !== "string") throw new JiraError("jira-failed", "The Jira account id is unavailable.");
4565
+ accountId = myself.accountId;
4566
+ await this.#write({
4567
+ ...store,
4568
+ accountId
4569
+ });
4570
+ }
4571
+ await this.#json(store, `/rest/api/3/issue/${ticket}/assignee`, {
4572
+ method: "PUT",
4573
+ body: { accountId }
4574
+ });
4575
+ }
4576
+ async disconnect() {
4577
+ await rm(this.#storePath, { force: true });
4578
+ }
4579
+ async search(query) {
4580
+ const store = await this.#read();
4581
+ if (store === void 0) throw new JiraError("not-connected", "Connect Jira in Settings first.");
4582
+ const params = new URLSearchParams({
4583
+ jql: buildJql(query),
4584
+ maxResults: String(MAX_RESULTS),
4585
+ fields: "summary,status,issuetype"
4586
+ });
4587
+ let body;
4588
+ try {
4589
+ body = await this.#json(store, `/rest/api/3/search/jql?${params.toString()}`);
4590
+ } catch (error) {
4591
+ if (!(error instanceof JiraError && error.code === "not-found")) throw error;
4592
+ body = await this.#json(store, `/rest/api/3/search?${params.toString()}`);
4593
+ }
4594
+ return parseTickets(body, store.siteUrl);
4595
+ }
4596
+ async #json(connection, path, init = {}) {
4597
+ let response;
4598
+ try {
4599
+ response = await this.#fetch(`${connection.siteUrl}${path}`, {
4600
+ method: init.method ?? "GET",
4601
+ headers: {
4602
+ accept: "application/json",
4603
+ authorization: `Basic ${Buffer.from(`${connection.email}:${connection.apiToken}`).toString("base64")}`,
4604
+ ...init.body === void 0 ? {} : { "content-type": "application/json" }
4605
+ },
4606
+ ...init.body === void 0 ? {} : { body: JSON.stringify(init.body) },
4607
+ signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS)
4608
+ });
4609
+ } catch {
4610
+ throw new JiraError("unreachable", "The Jira site could not be reached.");
4611
+ }
4612
+ if (response.status === 401 || response.status === 403) throw new JiraError("unauthorized", "Jira rejected the credentials.");
4613
+ if (response.status === 404) throw new JiraError("not-found", "The Jira endpoint was not found.");
4614
+ if (!response.ok) throw new JiraError("jira-failed", `Jira responded with HTTP ${response.status}.`);
4615
+ if (response.status === 204) return void 0;
4616
+ try {
4617
+ return await response.json();
4618
+ } catch {
4619
+ throw new JiraError("jira-failed", "Jira returned an invalid response.");
4620
+ }
4621
+ }
4622
+ async #read() {
4623
+ let text;
4624
+ try {
4625
+ text = await readFile(this.#storePath, "utf8");
4626
+ } catch (error) {
4627
+ if (error.code === "ENOENT") return void 0;
4628
+ throw error;
4629
+ }
4630
+ if (Buffer.byteLength(text) > MAX_STORE_BYTES) return void 0;
4631
+ const input = record$2(JSON.parse(text));
4632
+ if (input === void 0 || typeof input.siteUrl !== "string" || typeof input.email !== "string" || typeof input.apiToken !== "string") return void 0;
4633
+ return {
4634
+ siteUrl: input.siteUrl,
4635
+ email: input.email,
4636
+ apiToken: input.apiToken,
4637
+ ...typeof input.displayName === "string" ? { displayName: input.displayName } : {},
4638
+ ...typeof input.accountId === "string" ? { accountId: input.accountId } : {}
4639
+ };
4640
+ }
4641
+ async #write(store) {
4642
+ await mkdir(dirname(this.#storePath), {
4643
+ recursive: true,
4644
+ mode: 448
4645
+ });
4646
+ const temporary = `${this.#storePath}.${process.pid}.${randomUUID()}.tmp`;
4647
+ try {
4648
+ await writeFile(temporary, `${JSON.stringify(store, null, 2)}\n`, {
4649
+ encoding: "utf8",
4650
+ mode: 384,
4651
+ flag: "wx"
4652
+ });
4653
+ await chmod(temporary, 384);
4654
+ await rename(temporary, this.#storePath);
4655
+ } finally {
4656
+ await rm(temporary, { force: true }).catch(() => void 0);
4657
+ }
4658
+ }
4659
+ };
4660
+ //#endregion
4661
+ //#region src/jira-routes.ts
4662
+ const MAX_BODY_BYTES$1 = 8192;
4663
+ function record$1(value) {
4664
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
4665
+ }
4666
+ async function readJson$1(req) {
4667
+ const chunks = [];
4668
+ let size = 0;
4669
+ for await (const chunk of req) {
4670
+ const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
4671
+ size += buffer.length;
4672
+ if (size > MAX_BODY_BYTES$1) throw new JiraError("body-too-large", "The request body is too large.");
4673
+ chunks.push(buffer);
4674
+ }
4675
+ const value = record$1(JSON.parse(Buffer.concat(chunks).toString("utf8")));
4676
+ if (value === void 0) throw new JiraError("invalid-request", "The request body is invalid.");
4677
+ return value;
4678
+ }
4679
+ function string(input, key) {
4680
+ const value = input[key];
4681
+ if (typeof value !== "string") throw new JiraError("invalid-request", `The ${key} field is required.`);
4682
+ return value;
4683
+ }
4684
+ function registerJiraRoute(ctx, service) {
4685
+ ctx.effect(() => ctx.webServer.register({
4686
+ kind: "prefix",
4687
+ path: CLAUDE_JIRA_PATH,
4688
+ handler: async (req, res) => {
4689
+ if (!trustedRequest(req)) return json(res, 403, { error: "forbidden" });
4690
+ const url = new URL(req.url ?? "/", "http://localhost");
4691
+ try {
4692
+ if (url.pathname === `/plugins/dsh-claude/jira/status`) {
4693
+ if (req.method !== "GET") return json(res, 405, { error: "method not allowed" });
4694
+ return json(res, 200, await service.status());
4695
+ }
4696
+ if (url.pathname === `/plugins/dsh-claude/jira/connect`) {
4697
+ if (req.method !== "POST") return json(res, 405, { error: "method not allowed" });
4698
+ const input = await readJson$1(req);
4699
+ return json(res, 200, await service.connect({
4700
+ siteUrl: string(input, "siteUrl"),
4701
+ email: string(input, "email"),
4702
+ apiToken: string(input, "apiToken")
4703
+ }));
4704
+ }
4705
+ if (url.pathname === `/plugins/dsh-claude/jira/disconnect`) {
4706
+ if (req.method !== "POST") return json(res, 405, { error: "method not allowed" });
4707
+ await service.disconnect();
4708
+ return json(res, 200, { connected: false });
4709
+ }
4710
+ if (url.pathname === `/plugins/dsh-claude/jira/assign`) {
4711
+ if (req.method !== "POST") return json(res, 405, { error: "method not allowed" });
4712
+ const input = await readJson$1(req);
4713
+ await service.assignToMe(string(input, "key"));
4714
+ return json(res, 200, { assigned: true });
4715
+ }
4716
+ if (url.pathname === `/plugins/dsh-claude/jira/search`) {
4717
+ if (req.method !== "GET") return json(res, 405, { error: "method not allowed" });
4718
+ return json(res, 200, { tickets: await service.search(url.searchParams.get("query") ?? "") });
4719
+ }
4720
+ return json(res, 404, { error: "not found" });
4721
+ } catch (error) {
4722
+ if (error instanceof JiraError) return json(res, 409, {
4723
+ error: error.code,
4724
+ message: error.message
4725
+ });
4726
+ if (error instanceof SyntaxError) return json(res, 400, { error: "invalid-json" });
4727
+ return json(res, 500, {
4728
+ error: "jira-unavailable",
4729
+ message: "Jira is unavailable."
4730
+ });
4731
+ }
4732
+ }
4733
+ }), "dsh-claude: jira route");
4734
+ }
4735
+ //#endregion
4342
4736
  //#region src/review-comment-routes.ts
4343
4737
  const MAX_BODY_BYTES = 16384;
4344
4738
  const MAX_SESSION_ID_CHARS = 1024;
@@ -5196,6 +5590,8 @@ async function apply(ctx, config) {
5196
5590
  onUpdated: applyLimitOverrides
5197
5591
  });
5198
5592
  registerRepositorySetupRoute(webCtx, repositorySetup);
5593
+ registerRepositoryStatusRoute(webCtx, repositoryStatus);
5594
+ registerJiraRoute(webCtx, new JiraService());
5199
5595
  const repositoryActions = new RepositoryActionService(webCtx.subprocess, supervisorConfig.executablePath, (cwd) => repositoryStatus.invalidate(cwd));
5200
5596
  const cwdForClaudeSession = (sessionId) => {
5201
5597
  const agent = webCtx.agents.get(sessionId);