@cuongtran001/kanna 1.2.0 → 1.3.1

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.
Files changed (196) hide show
  1. package/dist/client/assets/{arc-BLk1CZKB.js → arc-DXtN3EWJ.js} +1 -1
  2. package/dist/client/assets/{architectureDiagram-3BPJPVTR-B01ZQYte.js → architectureDiagram-3BPJPVTR-CIGqW1z-.js} +1 -1
  3. package/dist/client/assets/{blockDiagram-GPEHLZMM-diL6a3cq.js → blockDiagram-GPEHLZMM-BafNS_H6.js} +1 -1
  4. package/dist/client/assets/{c4Diagram-AAUBKEIU-BY1verL8.js → c4Diagram-AAUBKEIU-YdAgRSis.js} +1 -1
  5. package/dist/client/assets/channel-BEYTrUAm.js +1 -0
  6. package/dist/client/assets/{chunk-2J33WTMH-psdZuajw.js → chunk-2J33WTMH-eiP5y5G-.js} +1 -1
  7. package/dist/client/assets/{chunk-4BX2VUAB-CrwQVBc5.js → chunk-4BX2VUAB-CvLGzPMF.js} +1 -1
  8. package/dist/client/assets/{chunk-55IACEB6-9-aj4jKO.js → chunk-55IACEB6-Y-S9Fb8A.js} +1 -1
  9. package/dist/client/assets/{chunk-727SXJPM-1FNK9_nB.js → chunk-727SXJPM-6sJH1RI9.js} +1 -1
  10. package/dist/client/assets/{chunk-AQP2D5EJ-DFhvd_Ek.js → chunk-AQP2D5EJ-hV1r_IXO.js} +1 -1
  11. package/dist/client/assets/{chunk-FMBD7UC4-B7MLrDa7.js → chunk-FMBD7UC4-CC3L28S5.js} +1 -1
  12. package/dist/client/assets/{chunk-ND2GUHAM-D4Df05QE.js → chunk-ND2GUHAM-BZ3czhdH.js} +1 -1
  13. package/dist/client/assets/{chunk-QZHKN3VN-Iq17Dznm.js → chunk-QZHKN3VN-Bjyd6Yr-.js} +1 -1
  14. package/dist/client/assets/classDiagram-4FO5ZUOK-CvnF5Ud6.js +1 -0
  15. package/dist/client/assets/classDiagram-v2-Q7XG4LA2-CvnF5Ud6.js +1 -0
  16. package/dist/client/assets/{cose-bilkent-S5V4N54A-BmaZhtXy.js → cose-bilkent-S5V4N54A-CfxdDghE.js} +1 -1
  17. package/dist/client/assets/{dagre-BM42HDAG-BCmpfnfP.js → dagre-BM42HDAG-B8CddsbB.js} +1 -1
  18. package/dist/client/assets/{diagram-2AECGRRQ-BIEcKowS.js → diagram-2AECGRRQ-F0yS0fVB.js} +1 -1
  19. package/dist/client/assets/{diagram-5GNKFQAL-CRBABBfv.js → diagram-5GNKFQAL-cW0i0qlh.js} +1 -1
  20. package/dist/client/assets/{diagram-KO2AKTUF-BRxUk16N.js → diagram-KO2AKTUF-BzAKMeJT.js} +1 -1
  21. package/dist/client/assets/{diagram-LMA3HP47-DPBs4xl1.js → diagram-LMA3HP47-qdC4XqIl.js} +1 -1
  22. package/dist/client/assets/{diagram-OG6HWLK6-BE9rgVv8.js → diagram-OG6HWLK6-CGcpi6Dh.js} +1 -1
  23. package/dist/client/assets/{erDiagram-TEJ5UH35-D3vgA1xJ.js → erDiagram-TEJ5UH35-CxNTDicM.js} +1 -1
  24. package/dist/client/assets/{flowDiagram-I6XJVG4X-Cgu-8XXC.js → flowDiagram-I6XJVG4X-qkGa5TqY.js} +1 -1
  25. package/dist/client/assets/{ganttDiagram-6RSMTGT7-CgjXVyTR.js → ganttDiagram-6RSMTGT7-D9mVPoQi.js} +1 -1
  26. package/dist/client/assets/{gitGraphDiagram-PVQCEYII-zD5D8B-i.js → gitGraphDiagram-PVQCEYII-DA0grAXe.js} +1 -1
  27. package/dist/client/assets/{index-DBTdhMbJ.js → index-BqUY1Bm4.js} +1 -1
  28. package/dist/client/assets/index-CLsOWXff.css +32 -0
  29. package/dist/client/assets/{index-zQLLeAfl.js → index-TU1k6ov6.js} +253 -243
  30. package/dist/client/assets/{infoDiagram-5YYISTIA-BaFHqT4u.js → infoDiagram-5YYISTIA-CGljtLs3.js} +1 -1
  31. package/dist/client/assets/{ishikawaDiagram-YF4QCWOH-2yAC1br2.js → ishikawaDiagram-YF4QCWOH-CtC0xrmG.js} +1 -1
  32. package/dist/client/assets/{journeyDiagram-JHISSGLW-DacQ-ND5.js → journeyDiagram-JHISSGLW-URLnVZeN.js} +1 -1
  33. package/dist/client/assets/{kanban-definition-UN3LZRKU-Qlbwm3z4.js → kanban-definition-UN3LZRKU-C2Q6aqQh.js} +1 -1
  34. package/dist/client/assets/{linear-DeafqpgO.js → linear-ByKHtsIx.js} +1 -1
  35. package/dist/client/assets/{mermaid.core-KYiCPJDj.js → mermaid.core-Dl_f2KLc.js} +4 -4
  36. package/dist/client/assets/{mindmap-definition-RKZ34NQL-B1V-T6d3.js → mindmap-definition-RKZ34NQL-CaJ9IDLI.js} +1 -1
  37. package/dist/client/assets/{pieDiagram-4H26LBE5-DOF9OFRu.js → pieDiagram-4H26LBE5-mmYaf2Oa.js} +1 -1
  38. package/dist/client/assets/{quadrantDiagram-W4KKPZXB-lH1mB7ko.js → quadrantDiagram-W4KKPZXB-B_PO6OCC.js} +1 -1
  39. package/dist/client/assets/{requirementDiagram-4Y6WPE33-BaTsJ_sS.js → requirementDiagram-4Y6WPE33-M4huwJt1.js} +1 -1
  40. package/dist/client/assets/{sankeyDiagram-5OEKKPKP-Pfxib5iI.js → sankeyDiagram-5OEKKPKP-B0hb1sWh.js} +1 -1
  41. package/dist/client/assets/{sequenceDiagram-3UESZ5HK-BQMEKkBC.js → sequenceDiagram-3UESZ5HK-BAqSptnA.js} +1 -1
  42. package/dist/client/assets/{stateDiagram-AJRCARHV-CuldNnzp.js → stateDiagram-AJRCARHV-DTNkvPi5.js} +1 -1
  43. package/dist/client/assets/stateDiagram-v2-BHNVJYJU-CVN3oGTF.js +1 -0
  44. package/dist/client/assets/{timeline-definition-PNZ67QCA-DP7u0YAg.js → timeline-definition-PNZ67QCA-MgAjHz8Q.js} +1 -1
  45. package/dist/client/assets/{vennDiagram-CIIHVFJN-QlrjpNeQ.js → vennDiagram-CIIHVFJN-DmKoO51W.js} +1 -1
  46. package/dist/client/assets/{wardley-L42UT6IY-BqdJ_sAM.js → wardley-L42UT6IY-CWcyHnYs.js} +1 -1
  47. package/dist/client/assets/{wardleyDiagram-YWT4CUSO-Wi3kaQ5X.js → wardleyDiagram-YWT4CUSO-BGIGmFP9.js} +1 -1
  48. package/dist/client/assets/{xychartDiagram-2RQKCTM6-D-KXMyCG.js → xychartDiagram-2RQKCTM6-D20YIvEl.js} +1 -1
  49. package/dist/client/index.html +2 -2
  50. package/package.json +1 -1
  51. package/src/server/agent-coordinator-types.ts +158 -0
  52. package/src/server/agent-coordinator.ts +919 -0
  53. package/src/server/agent-deps-builders.ts +425 -0
  54. package/src/server/agent.oauth-rotation.test.ts +101 -0
  55. package/src/server/agent.test.ts +24 -8
  56. package/src/server/agent.ts +35 -4963
  57. package/src/server/auto-continue/read-model.test.ts +2 -2
  58. package/src/server/auto-continue/read-model.ts +4 -1
  59. package/src/server/claude-autocontinue-commands.test.ts +494 -0
  60. package/src/server/claude-autocontinue-commands.ts +256 -0
  61. package/src/server/claude-cancel-handler.test.ts +472 -0
  62. package/src/server/claude-cancel-handler.ts +241 -0
  63. package/src/server/claude-chat-management.test.ts +395 -0
  64. package/src/server/claude-chat-management.ts +251 -0
  65. package/src/server/claude-harness-stream.test.ts +273 -0
  66. package/src/server/claude-harness-stream.ts +158 -0
  67. package/src/server/claude-loop-orch-commands.test.ts +579 -0
  68. package/src/server/claude-loop-orch-commands.ts +466 -0
  69. package/src/server/claude-message-normalizer.test.ts +213 -0
  70. package/src/server/claude-message-normalizer.ts +436 -0
  71. package/src/server/claude-prompt-helpers.test.ts +248 -0
  72. package/src/server/claude-prompt-helpers.ts +139 -0
  73. package/src/server/claude-sdk-queue.test.ts +169 -0
  74. package/src/server/claude-sdk-queue.ts +82 -0
  75. package/src/server/claude-send-command.test.ts +414 -0
  76. package/src/server/claude-send-command.ts +420 -0
  77. package/src/server/claude-session-config-helpers.test.ts +279 -0
  78. package/src/server/claude-session-config-helpers.ts +151 -0
  79. package/src/server/claude-session-config.test.ts +250 -0
  80. package/src/server/claude-session-config.ts +159 -0
  81. package/src/server/claude-session-error-handler.test.ts +498 -0
  82. package/src/server/claude-session-error-handler.ts +361 -0
  83. package/src/server/claude-session-lifecycle.test.ts +669 -0
  84. package/src/server/claude-session-lifecycle.ts +283 -0
  85. package/src/server/claude-session-rebuild.test.ts +224 -0
  86. package/src/server/claude-session-rebuild.ts +101 -0
  87. package/src/server/claude-session-runner.test.ts +523 -0
  88. package/src/server/claude-session-runner.ts +521 -0
  89. package/src/server/claude-session-spawner.test.ts +401 -0
  90. package/src/server/claude-session-spawner.ts +401 -0
  91. package/src/server/claude-session-start.test.ts +205 -0
  92. package/src/server/claude-session-start.ts +277 -0
  93. package/src/server/claude-session-state-queries.test.ts +344 -0
  94. package/src/server/claude-session-state-queries.ts +160 -0
  95. package/src/server/claude-session-state.ts +104 -0
  96. package/src/server/claude-slash-commands.test.ts +277 -0
  97. package/src/server/claude-slash-commands.ts +234 -0
  98. package/src/server/claude-spawn-helpers.test.ts +221 -0
  99. package/src/server/claude-spawn-helpers.ts +179 -0
  100. package/src/server/claude-steer-log.test.ts +243 -0
  101. package/src/server/claude-steer-log.ts +58 -0
  102. package/src/server/claude-subagent-tool-response.test.ts +213 -0
  103. package/src/server/claude-subagent-tool-response.ts +165 -0
  104. package/src/server/claude-subagent-wiring.test.ts +263 -0
  105. package/src/server/claude-subagent-wiring.ts +324 -0
  106. package/src/server/claude-tool-respond.test.ts +321 -0
  107. package/src/server/claude-tool-respond.ts +142 -0
  108. package/src/server/claude-turn-runner.test.ts +310 -0
  109. package/src/server/claude-turn-runner.ts +228 -0
  110. package/src/server/claude-turn-starter.test.ts +266 -0
  111. package/src/server/claude-turn-starter.ts +566 -0
  112. package/src/server/claude-usage-math.test.ts +169 -0
  113. package/src/server/claude-usage-math.ts +132 -0
  114. package/src/server/diff-store-branch-ops.adapter.test.ts +332 -0
  115. package/src/server/diff-store-branch-ops.adapter.ts +602 -0
  116. package/src/server/diff-store-commit-ops.adapter.test.ts +358 -0
  117. package/src/server/diff-store-commit-ops.adapter.ts +258 -0
  118. package/src/server/diff-store-errors.ts +85 -0
  119. package/src/server/diff-store-file-ops.adapter.ts +212 -0
  120. package/src/server/diff-store-git-branch.adapter.ts +686 -0
  121. package/src/server/diff-store-io.adapter.ts +27 -0
  122. package/src/server/diff-store-parse.test.ts +212 -0
  123. package/src/server/diff-store-parse.ts +122 -0
  124. package/src/server/diff-store-state.ts +82 -0
  125. package/src/server/diff-store.ts +76 -1854
  126. package/src/server/event-store-apply.test.ts +280 -0
  127. package/src/server/event-store-apply.ts +132 -0
  128. package/src/server/event-store-chat-lifecycle.test.ts +408 -0
  129. package/src/server/event-store-chat-lifecycle.ts +441 -0
  130. package/src/server/event-store-entity-write.ts +356 -0
  131. package/src/server/event-store-helpers.test.ts +230 -0
  132. package/src/server/event-store-helpers.ts +235 -0
  133. package/src/server/event-store-init.ts +276 -0
  134. package/src/server/event-store-messages.adapter.test.ts +365 -0
  135. package/src/server/event-store-messages.adapter.ts +213 -0
  136. package/src/server/event-store-orch-subscription.ts +124 -0
  137. package/src/server/event-store-orch.test.ts +462 -0
  138. package/src/server/event-store-orch.ts +312 -0
  139. package/src/server/event-store-peripheral-events.adapter.test.ts +254 -0
  140. package/src/server/event-store-peripheral-events.adapter.ts +105 -0
  141. package/src/server/event-store-snapshot.test.ts +597 -0
  142. package/src/server/event-store-snapshot.ts +658 -0
  143. package/src/server/event-store-subagent.test.ts +408 -0
  144. package/src/server/event-store-subagent.ts +198 -0
  145. package/src/server/event-store-tool-requests.test.ts +210 -0
  146. package/src/server/event-store-tool-requests.ts +131 -0
  147. package/src/server/event-store-transcript-write.adapter.ts +285 -0
  148. package/src/server/event-store-write-ops.test.ts +554 -0
  149. package/src/server/event-store-write-ops.ts +529 -0
  150. package/src/server/event-store.test.ts +35 -0
  151. package/src/server/event-store.ts +251 -2284
  152. package/src/server/events.ts +3 -0
  153. package/src/server/harness-types.ts +15 -1
  154. package/src/server/oauth-errors.ts +19 -0
  155. package/src/server/read-models.ts +26 -1
  156. package/src/server/subagent-orchestrator.ts +3 -0
  157. package/src/server/ws-router-agent-ctrl.test.ts +187 -0
  158. package/src/server/ws-router-agent-ctrl.ts +137 -0
  159. package/src/server/ws-router-broadcast.ts +488 -0
  160. package/src/server/ws-router-chat.test.ts +461 -0
  161. package/src/server/ws-router-chat.ts +279 -0
  162. package/src/server/ws-router-defaults.test.ts +283 -0
  163. package/src/server/ws-router-defaults.ts +434 -0
  164. package/src/server/ws-router-diff.test.ts +167 -0
  165. package/src/server/ws-router-diff.ts +250 -0
  166. package/src/server/ws-router-envelope.ts +329 -0
  167. package/src/server/ws-router-misc.test.ts +422 -0
  168. package/src/server/ws-router-misc.ts +265 -0
  169. package/src/server/ws-router-orch.test.ts +190 -0
  170. package/src/server/ws-router-orch.ts +93 -0
  171. package/src/server/ws-router-project.test.ts +403 -0
  172. package/src/server/ws-router-project.ts +256 -0
  173. package/src/server/ws-router-push.test.ts +193 -0
  174. package/src/server/ws-router-push.ts +120 -0
  175. package/src/server/ws-router-settings.test.ts +173 -0
  176. package/src/server/ws-router-settings.ts +416 -0
  177. package/src/server/ws-router-skills.test.ts +194 -0
  178. package/src/server/ws-router-skills.ts +204 -0
  179. package/src/server/ws-router-utils.ts +224 -0
  180. package/src/server/ws-router.ts +339 -2254
  181. package/src/shared/app-settings-types.ts +365 -0
  182. package/src/shared/core-types.ts +27 -0
  183. package/src/shared/git-diff-types.ts +165 -0
  184. package/src/shared/loop-progress.test.ts +106 -0
  185. package/src/shared/loop-progress.ts +88 -0
  186. package/src/shared/mcp-types.ts +105 -0
  187. package/src/shared/provider-model-types.ts +437 -0
  188. package/src/shared/subagent-types.ts +198 -0
  189. package/src/shared/tool-call-types.ts +314 -0
  190. package/src/shared/transcript-types.ts +270 -0
  191. package/src/shared/types.ts +27 -1668
  192. package/dist/client/assets/channel-BMkhjiLI.js +0 -1
  193. package/dist/client/assets/classDiagram-4FO5ZUOK-C5Kg1XOD.js +0 -1
  194. package/dist/client/assets/classDiagram-v2-Q7XG4LA2-C5Kg1XOD.js +0 -1
  195. package/dist/client/assets/index-H6C-f2Vp.css +0 -32
  196. package/dist/client/assets/stateDiagram-v2-BHNVJYJU-Ca2U11af.js +0 -1
@@ -0,0 +1,212 @@
1
+ import path from "node:path"
2
+ import {
3
+ formatGitFailure,
4
+ getDiffFile,
5
+ makeTempDir,
6
+ readTextFileOrThrow,
7
+ rmPathRecursive,
8
+ runGit,
9
+ statOrNull,
10
+ writeTextFile,
11
+ } from "./diff-store-io.adapter"
12
+ import {
13
+ countTextLines,
14
+ getContentDigest,
15
+ parseNumstatValue,
16
+ parseStatusPaths,
17
+ } from "./diff-store-parse"
18
+ import type { DirtyPathEntry } from "./diff-store-parse"
19
+ import { inferProjectFileContentType } from "./uploads"
20
+ import type { ChatDiffFile } from "../shared/types"
21
+
22
+ export type { DirtyPathEntry }
23
+
24
+ export async function listDirtyPaths(repoRoot: string): Promise<DirtyPathEntry[]> {
25
+ const status = await runGit(["status", "--short", "--untracked-files=all"], repoRoot)
26
+ if (status.exitCode !== 0) {
27
+ throw new Error(status.stderr.trim() || "Failed to read git status")
28
+ }
29
+
30
+ const paths = parseStatusPaths(status.stdout)
31
+ return paths
32
+ }
33
+
34
+ export async function readWorktreeFile(repoRoot: string, relativePath: string): Promise<string | null> {
35
+ const absolutePath = path.join(repoRoot, relativePath)
36
+ const fileInfo = await statOrNull(absolutePath)
37
+ if (!fileInfo?.isFile()) {
38
+ return null
39
+ }
40
+
41
+ return await readTextFileOrThrow(absolutePath)
42
+ }
43
+
44
+ export async function readBaseFile(repoRoot: string, baseCommit: string | null, relativePath: string): Promise<string | null> {
45
+ if (!baseCommit) {
46
+ return null
47
+ }
48
+
49
+ const result = await runGit(["show", `${baseCommit}:${relativePath}`], repoRoot)
50
+ if (result.exitCode !== 0) {
51
+ return null
52
+ }
53
+ return result.stdout
54
+ }
55
+
56
+ export async function createPatch(beforePathLabel: string, afterPathLabel: string, beforeText: string | null, afterText: string | null): Promise<string> {
57
+ const tempDir = await makeTempDir("kanna-diff-")
58
+ const beforePath = path.join(tempDir, "before")
59
+ const afterPath = path.join(tempDir, "after")
60
+
61
+ try {
62
+ await writeTextFile(beforePath, beforeText ?? "")
63
+ await writeTextFile(afterPath, afterText ?? "")
64
+
65
+ const result = await runGit(
66
+ [
67
+ "diff",
68
+ "--no-index",
69
+ "--no-ext-diff",
70
+ "--text",
71
+ "--unified=3",
72
+ "--src-prefix=a/",
73
+ "--dst-prefix=b/",
74
+ "before",
75
+ "after",
76
+ ],
77
+ tempDir
78
+ )
79
+
80
+ if (result.exitCode !== 0 && result.exitCode !== 1) {
81
+ throw new Error(result.stderr.trim() || `Failed to build patch for ${afterPathLabel}`)
82
+ }
83
+
84
+ return result.stdout
85
+ .replace("diff --git a/before b/after", `diff --git a/${beforePathLabel} b/${afterPathLabel}`)
86
+ .replace("--- a/before", `--- a/${beforePathLabel}`)
87
+ .replace("+++ b/after", `+++ b/${afterPathLabel}`)
88
+ } finally {
89
+ await rmPathRecursive(tempDir)
90
+ }
91
+ }
92
+
93
+ export async function getTrackedDiffStats(repoRoot: string, baseCommit: string | null): Promise<Map<string, { additions: number; deletions: number }>> {
94
+ const statsByPath = new Map<string, { additions: number; deletions: number }>()
95
+ if (!baseCommit) {
96
+ return statsByPath
97
+ }
98
+
99
+ const result = await runGit(["diff", "--numstat", "-z", "-M", baseCommit], repoRoot)
100
+ if (result.exitCode !== 0) {
101
+ throw new Error(result.stderr.trim() || "Failed to read git diff stats")
102
+ }
103
+
104
+ const NULL_BYTE = "\u0000"
105
+ const tokens = result.stdout.split(NULL_BYTE)
106
+ for (let index = 0; index < tokens.length;) {
107
+ const header = tokens[index++] ?? ""
108
+ if (!header) continue
109
+
110
+ const [additionsValue, deletionsValue, pathValue = ""] = header.split("\t")
111
+ if (typeof additionsValue !== "string" || typeof deletionsValue !== "string") continue
112
+
113
+ if (pathValue) {
114
+ statsByPath.set(pathValue, {
115
+ additions: parseNumstatValue(additionsValue),
116
+ deletions: parseNumstatValue(deletionsValue),
117
+ })
118
+ continue
119
+ }
120
+
121
+ index += 1
122
+ const nextPath = tokens[index++] ?? ""
123
+ if (!nextPath) continue
124
+ statsByPath.set(nextPath, {
125
+ additions: parseNumstatValue(additionsValue),
126
+ deletions: parseNumstatValue(deletionsValue),
127
+ })
128
+ }
129
+
130
+ return statsByPath
131
+ }
132
+
133
+ export async function computeCurrentFiles(repoRoot: string, baseCommit: string | null): Promise<ChatDiffFile[]> {
134
+ const currentDirtyPaths = await listDirtyPaths(repoRoot)
135
+ const trackedStatsByPath = await getTrackedDiffStats(repoRoot, baseCommit)
136
+ const files: ChatDiffFile[] = []
137
+
138
+ for (const entry of currentDirtyPaths) {
139
+ const relativePath = entry.path
140
+ const beforePath = entry.previousPath ?? relativePath
141
+ const beforeText = await readBaseFile(repoRoot, baseCommit, beforePath)
142
+ const afterText = await readWorktreeFile(repoRoot, relativePath)
143
+ const absolutePath = path.join(repoRoot, relativePath)
144
+ const fileInfo = await statOrNull(absolutePath)
145
+ const file = fileInfo?.isFile() ? getDiffFile(absolutePath) : null
146
+ const mimeType = file ? inferProjectFileContentType(relativePath, file.type) : undefined
147
+ const size = fileInfo?.isFile() ? fileInfo.size : undefined
148
+
149
+ if (beforeText === afterText && entry.changeType !== "renamed") {
150
+ continue
151
+ }
152
+
153
+ const trackedStats = trackedStatsByPath.get(relativePath)
154
+ const additions = trackedStats?.additions ?? countTextLines(afterText)
155
+ const deletions = trackedStats?.deletions ?? 0
156
+ files.push({
157
+ path: relativePath,
158
+ changeType: entry.changeType,
159
+ isUntracked: entry.isUntracked,
160
+ additions,
161
+ deletions,
162
+ patchDigest: getContentDigest({
163
+ changeType: entry.changeType,
164
+ beforePath,
165
+ afterPath: relativePath,
166
+ beforeText,
167
+ afterText,
168
+ }),
169
+ mimeType,
170
+ size,
171
+ })
172
+ }
173
+
174
+ return files
175
+ }
176
+
177
+ export async function findDirtyPath(repoRoot: string, relativePath: string): Promise<DirtyPathEntry | undefined> {
178
+ const dirtyPaths = await listDirtyPaths(repoRoot)
179
+ return dirtyPaths.find((entry) => entry.path === relativePath)
180
+ }
181
+
182
+ export async function discardAddedPath(repoRoot: string, repoHasHead: boolean, relativePath: string): Promise<void> {
183
+ if (repoHasHead) {
184
+ const resetResult = await runGit(["reset", "--quiet", "HEAD", "--", relativePath], repoRoot)
185
+ if (resetResult.exitCode !== 0) {
186
+ throw new Error(formatGitFailure(resetResult) || "Failed to unstage added file")
187
+ }
188
+ } else {
189
+ const rmCachedResult = await runGit(["rm", "--cached", "--force", "--", relativePath], repoRoot)
190
+ if (rmCachedResult.exitCode !== 0) {
191
+ throw new Error(formatGitFailure(rmCachedResult) || "Failed to unstage added file")
192
+ }
193
+ }
194
+ }
195
+
196
+ export async function discardRenamedPath(repoRoot: string, entry: DirtyPathEntry): Promise<void> {
197
+ if (!entry.previousPath) {
198
+ throw new Error(`Missing previous path for renamed file: ${entry.path}`)
199
+ }
200
+
201
+ const resetResult = await runGit(["reset", "--quiet", "HEAD", "--", entry.path], repoRoot)
202
+ if (resetResult.exitCode !== 0) {
203
+ throw new Error(formatGitFailure(resetResult) || "Failed to unstage renamed file")
204
+ }
205
+
206
+ const restoreResult = await runGit(["restore", "--staged", "--worktree", "--source=HEAD", "--", entry.previousPath], repoRoot)
207
+ if (restoreResult.exitCode !== 0) {
208
+ throw new Error(formatGitFailure(restoreResult) || "Failed to restore renamed file")
209
+ }
210
+
211
+ await rmPathRecursive(path.join(repoRoot, entry.path))
212
+ }