@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/INSTALL.md +55 -55
- package/LICENSE +21 -21
- package/README.md +82 -82
- package/cordis.patch.yml +12 -12
- package/legacy-preset/agent.cordis.yml +11 -11
- package/legacy-preset/preset.yml +4 -4
- package/lib/bin.mjs +1 -1
- package/lib/bin.mjs.map +1 -1
- package/lib/client.d.ts +33 -0
- package/lib/client.js +1085 -181
- package/lib/client.js.map +1 -1
- package/lib/{command-bridge-Br25ODdm.mjs → command-bridge-B48egrCr.mjs} +2 -2
- package/lib/command-bridge-B48egrCr.mjs.map +1 -0
- package/lib/{events-DhrAr5gh.mjs → events-BhU0iJUi.mjs} +4 -2
- package/lib/events-BhU0iJUi.mjs.map +1 -0
- package/lib/index.mjs +501 -105
- package/lib/index.mjs.map +1 -1
- package/lib/{preset-installer-B_Cb0RG6.mjs → preset-installer-Bzw8tuGI.mjs} +2 -2
- package/lib/preset-installer-Bzw8tuGI.mjs.map +1 -0
- package/lib/preset-route.mjs +2 -2
- package/lib/preset-route.mjs.map +1 -1
- package/package.json +181 -181
- package/preset/claude/agent.cordis.yml +11 -11
- package/preset/claude/preset.yml +4 -4
- package/lib/command-bridge-Br25ODdm.mjs.map +0 -1
- package/lib/events-DhrAr5gh.mjs.map +0 -1
- package/lib/preset-installer-B_Cb0RG6.mjs.map +0 -1
package/lib/index.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { C as
|
|
2
|
-
import { n as projectClaudeCommands, t as CLAUDE_COMMANDS_SERVICE } from "./command-bridge-
|
|
3
|
-
import { a as resolveClaudeExecutable, n as ensureManagedPreset, o as runClaudeDoctor, t as ManagedPresetConflictError } from "./preset-installer-
|
|
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$
|
|
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$
|
|
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$
|
|
39
|
-
if (input === void 0 || !string$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
656
|
+
function record$7(value) {
|
|
657
657
|
return value !== null && typeof value === "object" ? value : void 0;
|
|
658
658
|
}
|
|
659
|
-
function string$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
692
|
+
const block = record$7(item);
|
|
693
693
|
if (block === void 0) continue;
|
|
694
694
|
if (block.type === "text") {
|
|
695
|
-
const text = string$
|
|
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$
|
|
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$
|
|
711
|
-
const toolName = string$
|
|
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$
|
|
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$
|
|
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$
|
|
735
|
+
const block = record$7(item);
|
|
736
736
|
if (block?.type !== "tool_result") continue;
|
|
737
|
-
const toolUseId = string$
|
|
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$
|
|
750
|
+
const subtype = string$3(message.subtype);
|
|
751
751
|
if (subtype === "init") {
|
|
752
|
-
const sessionId = string$
|
|
753
|
-
const cliVersion = string$
|
|
754
|
-
const cwd = string$
|
|
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$
|
|
784
|
-
const toolName = string$
|
|
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$
|
|
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$
|
|
794
|
-
const description = string$
|
|
795
|
-
const subagentType = string$
|
|
796
|
-
const taskType = string$
|
|
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$
|
|
812
|
-
const description = string$
|
|
813
|
-
const summary = string$
|
|
814
|
-
const subagentType = string$
|
|
815
|
-
const lastToolName = string$
|
|
816
|
-
const usage = taskUsageOf(record$
|
|
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$
|
|
833
|
-
const status = string$
|
|
834
|
-
const taskId = string$
|
|
835
|
-
const description = string$
|
|
836
|
-
const error = string$
|
|
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$
|
|
853
|
-
const summary = string$
|
|
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$
|
|
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$
|
|
871
|
-
const taskId = string$
|
|
872
|
-
const description = string$
|
|
873
|
-
const taskType = string$
|
|
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$
|
|
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$
|
|
914
|
-
const parentToolUseId = string$
|
|
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$
|
|
916
|
+
const delta = record$7(event.delta);
|
|
917
917
|
if (delta?.type === "text_delta") {
|
|
918
|
-
const text = string$
|
|
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$
|
|
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$
|
|
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$
|
|
950
|
-
const userMessageUuid = string$
|
|
951
|
-
const permissionDenials = Array.isArray(value.permission_denials) ? value.permission_denials.map((item) => record$
|
|
952
|
-
const toolName = string$
|
|
953
|
-
const toolUseId = string$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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 (
|
|
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)
|
|
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$
|
|
3924
|
-
function record$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
4012
|
-
await service.bindLease(string$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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);
|