@g-abhishek/gitx 0.1.1 → 0.1.4

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 (161) hide show
  1. package/README.md +374 -3
  2. package/dist/ai/claudeAi.d.ts +35 -0
  3. package/dist/ai/claudeAi.d.ts.map +1 -0
  4. package/dist/ai/claudeAi.js +396 -0
  5. package/dist/ai/claudeAi.js.map +1 -0
  6. package/dist/ai/claudeCliAi.d.ts +27 -0
  7. package/dist/ai/claudeCliAi.d.ts.map +1 -0
  8. package/dist/ai/claudeCliAi.js +312 -0
  9. package/dist/ai/claudeCliAi.js.map +1 -0
  10. package/dist/ai/localClaudeAi.d.ts +2 -0
  11. package/dist/ai/localClaudeAi.d.ts.map +1 -0
  12. package/dist/ai/localClaudeAi.js +4 -0
  13. package/dist/ai/localClaudeAi.js.map +1 -0
  14. package/dist/ai/mockAi.d.ts +8 -1
  15. package/dist/ai/mockAi.d.ts.map +1 -1
  16. package/dist/ai/mockAi.js +57 -0
  17. package/dist/ai/mockAi.js.map +1 -1
  18. package/dist/ai/openAiAi.d.ts +33 -0
  19. package/dist/ai/openAiAi.d.ts.map +1 -0
  20. package/dist/ai/openAiAi.js +388 -0
  21. package/dist/ai/openAiAi.js.map +1 -0
  22. package/dist/ai/reviewHelpers.d.ts +66 -0
  23. package/dist/ai/reviewHelpers.d.ts.map +1 -0
  24. package/dist/ai/reviewHelpers.js +559 -0
  25. package/dist/ai/reviewHelpers.js.map +1 -0
  26. package/dist/ai/types.d.ts +247 -0
  27. package/dist/ai/types.d.ts.map +1 -1
  28. package/dist/ai/types.js.map +1 -1
  29. package/dist/cli/commands/ask.d.ts +27 -0
  30. package/dist/cli/commands/ask.d.ts.map +1 -0
  31. package/dist/cli/commands/ask.js +230 -0
  32. package/dist/cli/commands/ask.js.map +1 -0
  33. package/dist/cli/commands/commit.d.ts +16 -0
  34. package/dist/cli/commands/commit.d.ts.map +1 -0
  35. package/dist/cli/commands/commit.js +163 -0
  36. package/dist/cli/commands/commit.js.map +1 -0
  37. package/dist/cli/commands/config.d.ts +4 -0
  38. package/dist/cli/commands/config.d.ts.map +1 -0
  39. package/dist/cli/commands/config.js +666 -0
  40. package/dist/cli/commands/config.js.map +1 -0
  41. package/dist/cli/commands/implement.d.ts.map +1 -1
  42. package/dist/cli/commands/implement.js +149 -28
  43. package/dist/cli/commands/implement.js.map +1 -1
  44. package/dist/cli/commands/init.d.ts +4 -0
  45. package/dist/cli/commands/init.d.ts.map +1 -1
  46. package/dist/cli/commands/init.js +7 -54
  47. package/dist/cli/commands/init.js.map +1 -1
  48. package/dist/cli/commands/port.d.ts +32 -0
  49. package/dist/cli/commands/port.d.ts.map +1 -0
  50. package/dist/cli/commands/port.js +554 -0
  51. package/dist/cli/commands/port.js.map +1 -0
  52. package/dist/cli/commands/pr/close.d.ts +15 -0
  53. package/dist/cli/commands/pr/close.d.ts.map +1 -0
  54. package/dist/cli/commands/pr/close.js +71 -0
  55. package/dist/cli/commands/pr/close.js.map +1 -0
  56. package/dist/cli/commands/pr/create.d.ts +17 -0
  57. package/dist/cli/commands/pr/create.d.ts.map +1 -1
  58. package/dist/cli/commands/pr/create.js +209 -5
  59. package/dist/cli/commands/pr/create.js.map +1 -1
  60. package/dist/cli/commands/pr/fixComments.d.ts.map +1 -1
  61. package/dist/cli/commands/pr/fixComments.js +77 -5
  62. package/dist/cli/commands/pr/fixComments.js.map +1 -1
  63. package/dist/cli/commands/pr/index.d.ts.map +1 -1
  64. package/dist/cli/commands/pr/index.js +4 -0
  65. package/dist/cli/commands/pr/index.js.map +1 -1
  66. package/dist/cli/commands/pr/list.d.ts.map +1 -1
  67. package/dist/cli/commands/pr/list.js +26 -3
  68. package/dist/cli/commands/pr/list.js.map +1 -1
  69. package/dist/cli/commands/pr/merge.d.ts +23 -0
  70. package/dist/cli/commands/pr/merge.d.ts.map +1 -0
  71. package/dist/cli/commands/pr/merge.js +191 -0
  72. package/dist/cli/commands/pr/merge.js.map +1 -0
  73. package/dist/cli/commands/pr/review.d.ts.map +1 -1
  74. package/dist/cli/commands/pr/review.js +123 -5
  75. package/dist/cli/commands/pr/review.js.map +1 -1
  76. package/dist/cli/commands/push.d.ts +16 -0
  77. package/dist/cli/commands/push.d.ts.map +1 -0
  78. package/dist/cli/commands/push.js +166 -0
  79. package/dist/cli/commands/push.js.map +1 -0
  80. package/dist/cli/commands/sync.d.ts +24 -0
  81. package/dist/cli/commands/sync.d.ts.map +1 -0
  82. package/dist/cli/commands/sync.js +414 -0
  83. package/dist/cli/commands/sync.js.map +1 -0
  84. package/dist/cli/index.d.ts.map +1 -1
  85. package/dist/cli/index.js +34 -6
  86. package/dist/cli/index.js.map +1 -1
  87. package/dist/config/config.d.ts +20 -3
  88. package/dist/config/config.d.ts.map +1 -1
  89. package/dist/config/config.js +103 -24
  90. package/dist/config/config.js.map +1 -1
  91. package/dist/config/schema.d.ts.map +1 -1
  92. package/dist/config/schema.js +70 -9
  93. package/dist/config/schema.js.map +1 -1
  94. package/dist/core/context.d.ts +13 -0
  95. package/dist/core/context.d.ts.map +1 -0
  96. package/dist/core/context.js +2 -0
  97. package/dist/core/context.js.map +1 -0
  98. package/dist/core/gitx.d.ts +47 -0
  99. package/dist/core/gitx.d.ts.map +1 -1
  100. package/dist/core/gitx.js +204 -9
  101. package/dist/core/gitx.js.map +1 -1
  102. package/dist/index.d.ts +1 -5
  103. package/dist/index.d.ts.map +1 -1
  104. package/dist/index.js +4 -1
  105. package/dist/index.js.map +1 -1
  106. package/dist/providers/azure.d.ts +26 -0
  107. package/dist/providers/azure.d.ts.map +1 -0
  108. package/dist/providers/azure.js +256 -0
  109. package/dist/providers/azure.js.map +1 -0
  110. package/dist/providers/base.d.ts +104 -0
  111. package/dist/providers/base.d.ts.map +1 -0
  112. package/dist/providers/base.js +5 -0
  113. package/dist/providers/base.js.map +1 -0
  114. package/dist/providers/factory.d.ts +8 -0
  115. package/dist/providers/factory.d.ts.map +1 -0
  116. package/dist/providers/factory.js +25 -0
  117. package/dist/providers/factory.js.map +1 -0
  118. package/dist/providers/github.d.ts +19 -0
  119. package/dist/providers/github.d.ts.map +1 -0
  120. package/dist/providers/github.js +291 -0
  121. package/dist/providers/github.js.map +1 -0
  122. package/dist/providers/gitlab.d.ts +19 -0
  123. package/dist/providers/gitlab.d.ts.map +1 -0
  124. package/dist/providers/gitlab.js +186 -0
  125. package/dist/providers/gitlab.js.map +1 -0
  126. package/dist/types/config.d.ts +53 -9
  127. package/dist/types/config.d.ts.map +1 -1
  128. package/dist/types/config.js.map +1 -1
  129. package/dist/utils/azureAuth.d.ts +51 -0
  130. package/dist/utils/azureAuth.d.ts.map +1 -0
  131. package/dist/utils/azureAuth.js +172 -0
  132. package/dist/utils/azureAuth.js.map +1 -0
  133. package/dist/utils/git.d.ts +22 -0
  134. package/dist/utils/git.d.ts.map +1 -1
  135. package/dist/utils/git.js +63 -7
  136. package/dist/utils/git.js.map +1 -1
  137. package/dist/utils/gitOps.d.ts +118 -0
  138. package/dist/utils/gitOps.d.ts.map +1 -0
  139. package/dist/utils/gitOps.js +380 -0
  140. package/dist/utils/gitOps.js.map +1 -0
  141. package/dist/utils/lockFile.d.ts +13 -0
  142. package/dist/utils/lockFile.d.ts.map +1 -0
  143. package/dist/utils/lockFile.js +54 -0
  144. package/dist/utils/lockFile.js.map +1 -0
  145. package/dist/utils/retry.d.ts +10 -0
  146. package/dist/utils/retry.d.ts.map +1 -0
  147. package/dist/utils/retry.js +31 -0
  148. package/dist/utils/retry.js.map +1 -0
  149. package/dist/workflows/implement.d.ts +41 -0
  150. package/dist/workflows/implement.d.ts.map +1 -0
  151. package/dist/workflows/implement.js +219 -0
  152. package/dist/workflows/implement.js.map +1 -0
  153. package/dist/workflows/pr.d.ts +41 -0
  154. package/dist/workflows/pr.d.ts.map +1 -0
  155. package/dist/workflows/pr.js +285 -0
  156. package/dist/workflows/pr.js.map +1 -0
  157. package/dist/workflows/prAddress.d.ts +55 -0
  158. package/dist/workflows/prAddress.d.ts.map +1 -0
  159. package/dist/workflows/prAddress.js +349 -0
  160. package/dist/workflows/prAddress.js.map +1 -0
  161. package/package.json +1 -1
package/dist/core/gitx.js CHANGED
@@ -1,7 +1,12 @@
1
1
  import { loadConfig } from "../config/config.js";
2
2
  import { MockAi } from "../ai/mockAi.js";
3
- import { resolveRepoSlugFromCwd } from "../utils/git.js";
3
+ import { ClaudeAi } from "../ai/claudeAi.js";
4
+ import { ClaudeCliAi } from "../ai/claudeCliAi.js";
5
+ import { OpenAiAi } from "../ai/openAiAi.js";
6
+ import { detectProviderFromRemote, getGitRemoteOriginUrl, inferRepoSlugFromRemote, isInsideGitRepo, } from "../utils/git.js";
4
7
  import { GitxError } from "../utils/errors.js";
8
+ import { logger } from "../logger/logger.js";
9
+ import { getTokenViaGcm } from "../utils/azureAuth.js";
5
10
  export class Gitx {
6
11
  config;
7
12
  ai;
@@ -12,22 +17,212 @@ export class Gitx {
12
17
  this.ai = args.ai;
13
18
  this.cwd = args.cwd;
14
19
  }
20
+ // ─── AI resolution ─────────────────────────────────────────────────────────
21
+ /**
22
+ * Resolve the active Anthropic API key.
23
+ * Priority: ANTHROPIC_API_KEY env var → aiProviders.claude.apiKey → legacy ai.apiKey
24
+ */
25
+ static resolveAiKey(config) {
26
+ return (process.env["ANTHROPIC_API_KEY"] ??
27
+ config.aiProviders?.claude?.apiKey ??
28
+ config.ai?.apiKey);
29
+ }
30
+ /**
31
+ * Resolve the OpenAI API key.
32
+ * Priority: OPENAI_API_KEY env var → aiProviders.openai.apiKey
33
+ */
34
+ static resolveOpenAiKey(config) {
35
+ return process.env["OPENAI_API_KEY"] ?? config.aiProviders?.openai?.apiKey;
36
+ }
37
+ /**
38
+ * Check whether any real AI (API key or local CLI) is available.
39
+ */
40
+ static async isAiAvailable(config) {
41
+ if (Gitx.resolveAiKey(config))
42
+ return true;
43
+ if (Gitx.resolveOpenAiKey(config))
44
+ return true;
45
+ if (config.aiProviders?.["claude-cli"] !== undefined)
46
+ return true;
47
+ if (config.defaultAiProvider)
48
+ return true;
49
+ // Also check for auto-detected local claude CLI (same as buildAi step 4)
50
+ if (await ClaudeCliAi.isAvailable())
51
+ return true;
52
+ return false;
53
+ }
54
+ /**
55
+ * Build the best available AiClient for the given config.
56
+ *
57
+ * Selection cascade:
58
+ * 1. ANTHROPIC_API_KEY env var → ClaudeAi (remote API)
59
+ * 2. defaultAiProvider in config → use that specific provider
60
+ * 3. Any configured aiProviders entry → first one found with a key
61
+ * 4. Auto-detect local `claude` CLI → ClaudeCliAi (free, no key)
62
+ * 5. MockAi fallback → placeholders, warns user
63
+ */
64
+ static async buildAi(config) {
65
+ // 1. Env vars always win (Anthropic takes precedence over OpenAI)
66
+ const envClaudeKey = process.env["ANTHROPIC_API_KEY"];
67
+ if (envClaudeKey) {
68
+ return new ClaudeAi(envClaudeKey, config.aiProviders?.claude?.model);
69
+ }
70
+ const envOpenAiKey = process.env["OPENAI_API_KEY"];
71
+ if (envOpenAiKey) {
72
+ return new OpenAiAi(envOpenAiKey, config.aiProviders?.openai?.model);
73
+ }
74
+ // 2. Use the configured default provider
75
+ const defaultProv = config.defaultAiProvider;
76
+ if (defaultProv) {
77
+ const result = await Gitx._buildForProvider(defaultProv, config);
78
+ if (result)
79
+ return result;
80
+ // If it failed (e.g. claude-cli not installed), fall through with a warning
81
+ logger.warn(`⚠️ Default AI provider "${defaultProv}" is not available. Falling back…`);
82
+ }
83
+ // 3. Scan all configured aiProviders for one that works
84
+ const allEntries = Object.entries(config.aiProviders ?? {});
85
+ for (const [kind, entry] of allEntries) {
86
+ if (kind === defaultProv)
87
+ continue; // already tried above
88
+ const result = await Gitx._buildForProvider(kind, config, entry);
89
+ if (result)
90
+ return result;
91
+ }
92
+ // 4. Auto-detect locally installed claude CLI (no key needed)
93
+ if (await ClaudeCliAi.isAvailable()) {
94
+ logger.info("🔍 Auto-detected local Claude CLI — using it for AI features.");
95
+ return new ClaudeCliAi();
96
+ }
97
+ // 5. MockAi fallback
98
+ return new MockAi();
99
+ }
100
+ /** Build an AiClient for a specific provider kind. Returns null if not possible. */
101
+ static async _buildForProvider(kind, config, entry) {
102
+ const provEntry = entry ?? config.aiProviders?.[kind];
103
+ switch (kind) {
104
+ case "claude": {
105
+ const key = provEntry?.apiKey;
106
+ if (key)
107
+ return new ClaudeAi(key, provEntry?.model);
108
+ return null;
109
+ }
110
+ case "openai": {
111
+ const key = process.env["OPENAI_API_KEY"] ?? provEntry?.apiKey;
112
+ if (key)
113
+ return new OpenAiAi(key, provEntry?.model);
114
+ return null;
115
+ }
116
+ case "claude-cli": {
117
+ if (await ClaudeCliAi.isAvailable())
118
+ return new ClaudeCliAi();
119
+ return null;
120
+ }
121
+ default:
122
+ return null;
123
+ }
124
+ }
125
+ // ─── Factory ────────────────────────────────────────────────────────────────
126
+ /**
127
+ * Create a Gitx instance from the current working directory.
128
+ * Never throws due to missing config — returns graceful defaults.
129
+ */
15
130
  static async fromCwd(cwd = process.cwd()) {
16
- const config = await loadConfig(cwd);
17
- const ai = new MockAi();
131
+ let config;
132
+ try {
133
+ config = await loadConfig(cwd);
134
+ }
135
+ catch {
136
+ config = { providers: {} };
137
+ }
138
+ const ai = await Gitx.buildAi(config);
18
139
  return new Gitx({ config, ai, cwd });
19
140
  }
141
+ // ─── Plugin system ──────────────────────────────────────────────────────────
20
142
  async use(plugin) {
21
143
  this.plugins.push(plugin);
22
144
  await plugin.setup(this);
23
145
  }
146
+ // ─── Repo context ───────────────────────────────────────────────────────────
147
+ /**
148
+ * Resolve provider, repoSlug, and token for the current repo.
149
+ * Shows actionable guidance instead of crashing when token is missing.
150
+ */
151
+ async getRepoContext() {
152
+ if (!(await isInsideGitRepo(this.cwd))) {
153
+ throw new GitxError("Not inside a git repository. cd into your project folder first.", { exitCode: 2 });
154
+ }
155
+ const originUrl = await getGitRemoteOriginUrl(this.cwd);
156
+ if (!originUrl) {
157
+ throw new GitxError("No remote.origin.url found. Add an origin remote:\n" +
158
+ " git remote add origin <url>", { exitCode: 2 });
159
+ }
160
+ const provider = detectProviderFromRemote(originUrl);
161
+ if (!provider) {
162
+ throw new GitxError(`Could not detect a supported provider from: ${originUrl}\n` +
163
+ " Supported: github.com, gitlab.com, dev.azure.com", { exitCode: 2 });
164
+ }
165
+ const repoSlug = inferRepoSlugFromRemote(originUrl);
166
+ if (!repoSlug) {
167
+ throw new GitxError(`Could not parse repo slug from remote: ${originUrl}`, { exitCode: 2 });
168
+ }
169
+ const providerConfig = this.config.providers[provider];
170
+ const authMethod = providerConfig?.authMethod ?? "pat";
171
+ // ── GCM path (Azure DevOps only) ─────────────────────────────────────────
172
+ if (authMethod === "gcm") {
173
+ if (provider !== "azure") {
174
+ throw new GitxError(`GCM authentication is only supported for Azure DevOps, not "${provider}".`, { exitCode: 2 });
175
+ }
176
+ // Org is the first segment of the Azure slug: "org/project/repo"
177
+ const org = repoSlug.split("/")[0];
178
+ if (!org) {
179
+ throw new GitxError(`Cannot determine Azure DevOps org from repo slug: ${repoSlug}`, { exitCode: 2 });
180
+ }
181
+ let token;
182
+ try {
183
+ token = await getTokenViaGcm(org);
184
+ }
185
+ catch (err) {
186
+ throw new GitxError(`GCM authentication failed: ${err instanceof Error ? err.message : String(err)}`, { exitCode: 1 });
187
+ }
188
+ return { provider, repoSlug, token, tokenType: "bearer" };
189
+ }
190
+ // ── PAT path (default) ───────────────────────────────────────────────────
191
+ const token = providerConfig?.token;
192
+ if (!token) {
193
+ throw new GitxError(`This repo uses ${provider} but no ${provider} token is configured.\n` +
194
+ ` Run: gitx config\n` +
195
+ ` Or: gitx config set ${provider}`, { exitCode: 2 });
196
+ }
197
+ return { provider, repoSlug, token, tokenType: "pat" };
198
+ }
24
199
  async getRepoSlug() {
25
- if (this.config.repo && this.config.repo.trim().length > 0)
26
- return this.config.repo;
27
- const inferred = await resolveRepoSlugFromCwd(this.cwd);
28
- if (inferred)
29
- return inferred;
30
- throw new GitxError("Repo not configured and could not be inferred from git remote. Set `repo` in gitx config or run `gitx init` inside a repo with `origin`.", { exitCode: 2 });
200
+ return (await this.getRepoContext()).repoSlug;
201
+ }
202
+ async getProvider() {
203
+ return (await this.getRepoContext()).provider;
204
+ }
205
+ async getToken() {
206
+ return (await this.getRepoContext()).token;
207
+ }
208
+ /**
209
+ * Detect provider from the current repo without requiring a configured token.
210
+ * Useful for showing the user what they need to configure.
211
+ */
212
+ async detectProvider() {
213
+ try {
214
+ const originUrl = await getGitRemoteOriginUrl(this.cwd);
215
+ if (!originUrl)
216
+ return undefined;
217
+ const provider = detectProviderFromRemote(originUrl);
218
+ const repoSlug = inferRepoSlugFromRemote(originUrl);
219
+ if (!provider || !repoSlug)
220
+ return undefined;
221
+ return { provider, repoSlug };
222
+ }
223
+ catch {
224
+ return undefined;
225
+ }
31
226
  }
32
227
  }
33
228
  //# sourceMappingURL=gitx.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"gitx.js","sourceRoot":"","sources":["../../src/core/gitx.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AACjD,OAAO,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AAGzC,OAAO,EAAE,sBAAsB,EAAE,MAAM,iBAAiB,CAAC;AACzD,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAE/C,MAAM,OAAO,IAAI;IACC,MAAM,CAAa;IACnB,EAAE,CAAW;IACb,GAAG,CAAS;IACX,OAAO,GAAiB,EAAE,CAAC;IAE5C,YAAoB,IAAuD;QACzE,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC;QAC1B,IAAI,CAAC,EAAE,GAAG,IAAI,CAAC,EAAE,CAAC;QAClB,IAAI,CAAC,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC;IACtB,CAAC;IAED,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,GAAG,OAAO,CAAC,GAAG,EAAE;QACtC,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,GAAG,CAAC,CAAC;QACrC,MAAM,EAAE,GAAG,IAAI,MAAM,EAAE,CAAC;QACxB,OAAO,IAAI,IAAI,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,GAAG,EAAE,CAAC,CAAC;IACvC,CAAC;IAED,KAAK,CAAC,GAAG,CAAC,MAAkB;QAC1B,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAC1B,MAAM,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAC3B,CAAC;IAED,KAAK,CAAC,WAAW;QACf,IAAI,IAAI,CAAC,MAAM,CAAC,IAAI,IAAI,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC;YAAE,OAAO,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC;QACpF,MAAM,QAAQ,GAAG,MAAM,sBAAsB,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACxD,IAAI,QAAQ;YAAE,OAAO,QAAQ,CAAC;QAC9B,MAAM,IAAI,SAAS,CACjB,0IAA0I,EAC1I,EAAE,QAAQ,EAAE,CAAC,EAAE,CAChB,CAAC;IACJ,CAAC;CACF","sourcesContent":["import type { GitxConfig } from \"../types/config.js\";\nimport { loadConfig } from \"../config/config.js\";\nimport { MockAi } from \"../ai/mockAi.js\";\nimport type { AiClient } from \"../ai/types.js\";\nimport type { GitxPlugin } from \"./plugin.js\";\nimport { resolveRepoSlugFromCwd } from \"../utils/git.js\";\nimport { GitxError } from \"../utils/errors.js\";\n\nexport class Gitx {\n public readonly config: GitxConfig;\n public readonly ai: AiClient;\n public readonly cwd: string;\n private readonly plugins: GitxPlugin[] = [];\n\n private constructor(args: { config: GitxConfig; ai: AiClient; cwd: string }) {\n this.config = args.config;\n this.ai = args.ai;\n this.cwd = args.cwd;\n }\n\n static async fromCwd(cwd = process.cwd()): Promise<Gitx> {\n const config = await loadConfig(cwd);\n const ai = new MockAi();\n return new Gitx({ config, ai, cwd });\n }\n\n async use(plugin: GitxPlugin): Promise<void> {\n this.plugins.push(plugin);\n await plugin.setup(this);\n }\n\n async getRepoSlug(): Promise<string> {\n if (this.config.repo && this.config.repo.trim().length > 0) return this.config.repo;\n const inferred = await resolveRepoSlugFromCwd(this.cwd);\n if (inferred) return inferred;\n throw new GitxError(\n \"Repo not configured and could not be inferred from git remote. Set `repo` in gitx config or run `gitx init` inside a repo with `origin`.\",\n { exitCode: 2 },\n );\n }\n}\n"]}
1
+ {"version":3,"file":"gitx.js","sourceRoot":"","sources":["../../src/core/gitx.ts"],"names":[],"mappings":"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","sourcesContent":["import type { GitxConfig, AiProviderKind } from \"../types/config.js\";\nimport { loadConfig } from \"../config/config.js\";\nimport { MockAi } from \"../ai/mockAi.js\";\nimport { ClaudeAi } from \"../ai/claudeAi.js\";\nimport { ClaudeCliAi } from \"../ai/claudeCliAi.js\";\nimport { OpenAiAi } from \"../ai/openAiAi.js\";\nimport type { AiClient } from \"../ai/types.js\";\nimport type { GitxPlugin } from \"./plugin.js\";\nimport {\n detectProviderFromRemote,\n getGitRemoteOriginUrl,\n inferRepoSlugFromRemote,\n isInsideGitRepo,\n} from \"../utils/git.js\";\nimport { GitxError } from \"../utils/errors.js\";\nimport { logger } from \"../logger/logger.js\";\nimport type { RepoContext } from \"./context.js\";\nimport { getTokenViaGcm } from \"../utils/azureAuth.js\";\n\nexport class Gitx {\n public readonly config: GitxConfig;\n public readonly ai: AiClient;\n public readonly cwd: string;\n private readonly plugins: GitxPlugin[] = [];\n\n private constructor(args: { config: GitxConfig; ai: AiClient; cwd: string }) {\n this.config = args.config;\n this.ai = args.ai;\n this.cwd = args.cwd;\n }\n\n // ─── AI resolution ─────────────────────────────────────────────────────────\n\n /**\n * Resolve the active Anthropic API key.\n * Priority: ANTHROPIC_API_KEY env var → aiProviders.claude.apiKey → legacy ai.apiKey\n */\n static resolveAiKey(config: GitxConfig): string | undefined {\n return (\n process.env[\"ANTHROPIC_API_KEY\"] ??\n config.aiProviders?.claude?.apiKey ??\n config.ai?.apiKey\n );\n }\n\n /**\n * Resolve the OpenAI API key.\n * Priority: OPENAI_API_KEY env var → aiProviders.openai.apiKey\n */\n static resolveOpenAiKey(config: GitxConfig): string | undefined {\n return process.env[\"OPENAI_API_KEY\"] ?? config.aiProviders?.openai?.apiKey;\n }\n\n /**\n * Check whether any real AI (API key or local CLI) is available.\n */\n static async isAiAvailable(config: GitxConfig): Promise<boolean> {\n if (Gitx.resolveAiKey(config)) return true;\n if (Gitx.resolveOpenAiKey(config)) return true;\n if (config.aiProviders?.[\"claude-cli\"] !== undefined) return true;\n if (config.defaultAiProvider) return true;\n // Also check for auto-detected local claude CLI (same as buildAi step 4)\n if (await ClaudeCliAi.isAvailable()) return true;\n return false;\n }\n\n /**\n * Build the best available AiClient for the given config.\n *\n * Selection cascade:\n * 1. ANTHROPIC_API_KEY env var → ClaudeAi (remote API)\n * 2. defaultAiProvider in config → use that specific provider\n * 3. Any configured aiProviders entry → first one found with a key\n * 4. Auto-detect local `claude` CLI → ClaudeCliAi (free, no key)\n * 5. MockAi fallback → placeholders, warns user\n */\n static async buildAi(config: GitxConfig): Promise<AiClient> {\n // 1. Env vars always win (Anthropic takes precedence over OpenAI)\n const envClaudeKey = process.env[\"ANTHROPIC_API_KEY\"];\n if (envClaudeKey) {\n return new ClaudeAi(envClaudeKey, config.aiProviders?.claude?.model);\n }\n const envOpenAiKey = process.env[\"OPENAI_API_KEY\"];\n if (envOpenAiKey) {\n return new OpenAiAi(envOpenAiKey, config.aiProviders?.openai?.model);\n }\n\n // 2. Use the configured default provider\n const defaultProv = config.defaultAiProvider;\n if (defaultProv) {\n const result = await Gitx._buildForProvider(defaultProv, config);\n if (result) return result;\n // If it failed (e.g. claude-cli not installed), fall through with a warning\n logger.warn(`⚠️ Default AI provider \"${defaultProv}\" is not available. Falling back…`);\n }\n\n // 3. Scan all configured aiProviders for one that works\n const allEntries = Object.entries(config.aiProviders ?? {}) as Array<\n [AiProviderKind, { apiKey?: string; model?: string }]\n >;\n for (const [kind, entry] of allEntries) {\n if (kind === defaultProv) continue; // already tried above\n const result = await Gitx._buildForProvider(kind, config, entry);\n if (result) return result;\n }\n\n // 4. Auto-detect locally installed claude CLI (no key needed)\n if (await ClaudeCliAi.isAvailable()) {\n logger.info(\"🔍 Auto-detected local Claude CLI — using it for AI features.\");\n return new ClaudeCliAi();\n }\n\n // 5. MockAi fallback\n return new MockAi();\n }\n\n /** Build an AiClient for a specific provider kind. Returns null if not possible. */\n private static async _buildForProvider(\n kind: AiProviderKind,\n config: GitxConfig,\n entry?: { apiKey?: string; model?: string }\n ): Promise<AiClient | null> {\n const provEntry = entry ?? config.aiProviders?.[kind];\n\n switch (kind) {\n case \"claude\": {\n const key = provEntry?.apiKey;\n if (key) return new ClaudeAi(key, provEntry?.model);\n return null;\n }\n case \"openai\": {\n const key = process.env[\"OPENAI_API_KEY\"] ?? provEntry?.apiKey;\n if (key) return new OpenAiAi(key, provEntry?.model);\n return null;\n }\n case \"claude-cli\": {\n if (await ClaudeCliAi.isAvailable()) return new ClaudeCliAi();\n return null;\n }\n default:\n return null;\n }\n }\n\n // ─── Factory ────────────────────────────────────────────────────────────────\n\n /**\n * Create a Gitx instance from the current working directory.\n * Never throws due to missing config — returns graceful defaults.\n */\n static async fromCwd(cwd = process.cwd()): Promise<Gitx> {\n let config: GitxConfig;\n try {\n config = await loadConfig(cwd);\n } catch {\n config = { providers: {} };\n }\n\n const ai = await Gitx.buildAi(config);\n return new Gitx({ config, ai, cwd });\n }\n\n // ─── Plugin system ──────────────────────────────────────────────────────────\n\n async use(plugin: GitxPlugin): Promise<void> {\n this.plugins.push(plugin);\n await plugin.setup(this);\n }\n\n // ─── Repo context ───────────────────────────────────────────────────────────\n\n /**\n * Resolve provider, repoSlug, and token for the current repo.\n * Shows actionable guidance instead of crashing when token is missing.\n */\n async getRepoContext(): Promise<RepoContext> {\n if (!(await isInsideGitRepo(this.cwd))) {\n throw new GitxError(\n \"Not inside a git repository. cd into your project folder first.\",\n { exitCode: 2 }\n );\n }\n\n const originUrl = await getGitRemoteOriginUrl(this.cwd);\n if (!originUrl) {\n throw new GitxError(\n \"No remote.origin.url found. Add an origin remote:\\n\" +\n \" git remote add origin <url>\",\n { exitCode: 2 }\n );\n }\n\n const provider = detectProviderFromRemote(originUrl);\n if (!provider) {\n throw new GitxError(\n `Could not detect a supported provider from: ${originUrl}\\n` +\n \" Supported: github.com, gitlab.com, dev.azure.com\",\n { exitCode: 2 }\n );\n }\n\n const repoSlug = inferRepoSlugFromRemote(originUrl);\n if (!repoSlug) {\n throw new GitxError(\n `Could not parse repo slug from remote: ${originUrl}`,\n { exitCode: 2 }\n );\n }\n\n const providerConfig = this.config.providers[provider];\n const authMethod = providerConfig?.authMethod ?? \"pat\";\n\n // ── GCM path (Azure DevOps only) ─────────────────────────────────────────\n if (authMethod === \"gcm\") {\n if (provider !== \"azure\") {\n throw new GitxError(\n `GCM authentication is only supported for Azure DevOps, not \"${provider}\".`,\n { exitCode: 2 }\n );\n }\n // Org is the first segment of the Azure slug: \"org/project/repo\"\n const org = repoSlug.split(\"/\")[0];\n if (!org) {\n throw new GitxError(\n `Cannot determine Azure DevOps org from repo slug: ${repoSlug}`,\n { exitCode: 2 }\n );\n }\n let token: string;\n try {\n token = await getTokenViaGcm(org);\n } catch (err: unknown) {\n throw new GitxError(\n `GCM authentication failed: ${err instanceof Error ? err.message : String(err)}`,\n { exitCode: 1 }\n );\n }\n return { provider, repoSlug, token, tokenType: \"bearer\" };\n }\n\n // ── PAT path (default) ───────────────────────────────────────────────────\n const token = providerConfig?.token;\n if (!token) {\n throw new GitxError(\n `This repo uses ${provider} but no ${provider} token is configured.\\n` +\n ` Run: gitx config\\n` +\n ` Or: gitx config set ${provider}`,\n { exitCode: 2 }\n );\n }\n\n return { provider, repoSlug, token, tokenType: \"pat\" };\n }\n\n async getRepoSlug(): Promise<string> {\n return (await this.getRepoContext()).repoSlug;\n }\n\n async getProvider(): Promise<RepoContext[\"provider\"]> {\n return (await this.getRepoContext()).provider;\n }\n\n async getToken(): Promise<string> {\n return (await this.getRepoContext()).token;\n }\n\n /**\n * Detect provider from the current repo without requiring a configured token.\n * Useful for showing the user what they need to configure.\n */\n async detectProvider(): Promise<{ provider: string; repoSlug: string } | undefined> {\n try {\n const originUrl = await getGitRemoteOriginUrl(this.cwd);\n if (!originUrl) return undefined;\n const provider = detectProviderFromRemote(originUrl);\n const repoSlug = inferRepoSlugFromRemote(originUrl);\n if (!provider || !repoSlug) return undefined;\n return { provider, repoSlug };\n } catch {\n return undefined;\n }\n }\n}\n"]}
package/dist/index.d.ts CHANGED
@@ -1,6 +1,2 @@
1
- export { Gitx } from "./core/gitx.js";
2
- export type { GitxPlugin } from "./core/plugin.js";
3
- export type { GitxConfig } from "./types/config.js";
4
- export type { AutonomyMode } from "./types/modes.js";
5
- export type { ProviderKind } from "./types/provider.js";
1
+ export {};
6
2
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AACtC,YAAY,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AACnD,YAAY,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AACpD,YAAY,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AACrD,YAAY,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
package/dist/index.js CHANGED
@@ -1,2 +1,5 @@
1
- export { Gitx } from "./core/gitx.js";
1
+ export {};
2
+ // gitx is a CLI tool only — there is no public SDK surface.
3
+ // All functionality is available through the `gitx` command.
4
+ // Run `gitx --help` for usage.
2
5
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC","sourcesContent":["export { Gitx } from \"./core/gitx.js\";\nexport type { GitxPlugin } from \"./core/plugin.js\";\nexport type { GitxConfig } from \"./types/config.js\";\nexport type { AutonomyMode } from \"./types/modes.js\";\nexport type { ProviderKind } from \"./types/provider.js\";\n\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAAA,4DAA4D;AAC5D,6DAA6D;AAC7D,+BAA+B","sourcesContent":["// gitx is a CLI tool only there is no public SDK surface.\n// All functionality is available through the `gitx` command.\n// Run `gitx --help` for usage.\n"]}
@@ -0,0 +1,26 @@
1
+ import type { CreatePrOptions, GitProvider, MergePrOptions, PullRequest, PullRequestComment, SubmitReviewOptions } from "./base.js";
2
+ export declare class AzureProvider implements GitProvider {
3
+ private readonly http;
4
+ private readonly org;
5
+ private readonly project;
6
+ private readonly repoName;
7
+ /**
8
+ * @param token PAT string or OAuth Bearer token (from GCM).
9
+ * @param repoSlug "org/project/repo" slug.
10
+ * @param tokenType "pat" (default) → Basic auth; "bearer" → Bearer auth.
11
+ */
12
+ constructor(token: string, repoSlug: string, tokenType?: "pat" | "bearer");
13
+ private apiParams;
14
+ listPRs(_repoSlug: string): Promise<PullRequest[]>;
15
+ getPR(_repoSlug: string, prNumber: number): Promise<PullRequest>;
16
+ createPR(_repoSlug: string, opts: CreatePrOptions): Promise<PullRequest>;
17
+ getPRComments(_repoSlug: string, prNumber: number): Promise<PullRequestComment[]>;
18
+ addPRComment(_repoSlug: string, prNumber: number, body: string): Promise<void>;
19
+ getPRDiff(_repoSlug: string, prNumber: number): Promise<string>;
20
+ mergePR(_repoSlug: string, prNumber: number, opts: MergePrOptions): Promise<void>;
21
+ closePR(_repoSlug: string, prNumber: number): Promise<void>;
22
+ submitPRReview(_repoSlug: string, prNumber: number, opts: SubmitReviewOptions): Promise<void>;
23
+ replyToComment(_repoSlug: string, prNumber: number, commentId: number, body: string): Promise<void>;
24
+ getDefaultBranch(_repoSlug: string): Promise<string>;
25
+ }
26
+ //# sourceMappingURL=azure.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"azure.d.ts","sourceRoot":"","sources":["../../src/providers/azure.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACV,eAAe,EACf,WAAW,EACX,cAAc,EACd,WAAW,EACX,kBAAkB,EAClB,mBAAmB,EACpB,MAAM,WAAW,CAAC;AA8CnB,qBAAa,aAAc,YAAW,WAAW;IAC/C,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAgB;IACrC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAS;IAC7B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAElC;;;;OAIG;gBACS,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,GAAE,KAAK,GAAG,QAAgB;IAwBhF,OAAO,CAAC,SAAS;IAIX,OAAO,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;IAclD,KAAK,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC;IAYhE,QAAQ,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,eAAe,GAAG,OAAO,CAAC,WAAW,CAAC;IAmBxE,aAAa,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,EAAE,CAAC;IA2BjF,YAAY,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAgB9E,SAAS,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAsB/D,OAAO,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC;IAyCjF,OAAO,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAY3D,cAAc,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC;IAqC7F,cAAc,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAkBnG,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;CAW3D"}
@@ -0,0 +1,256 @@
1
+ import axios, { isAxiosError } from "axios";
2
+ import { GitxError } from "../utils/errors.js";
3
+ // ─── Azure Provider ───────────────────────────────────────────────────────────
4
+ export class AzureProvider {
5
+ http;
6
+ org;
7
+ project;
8
+ repoName;
9
+ /**
10
+ * @param token PAT string or OAuth Bearer token (from GCM).
11
+ * @param repoSlug "org/project/repo" slug.
12
+ * @param tokenType "pat" (default) → Basic auth; "bearer" → Bearer auth.
13
+ */
14
+ constructor(token, repoSlug, tokenType = "pat") {
15
+ // Expect "org/project/repo"
16
+ const parts = repoSlug.split("/");
17
+ this.org = parts[0] ?? "unknown";
18
+ this.project = parts[1] ?? "unknown";
19
+ this.repoName = parts[2] ?? parts[1] ?? "unknown";
20
+ // PAT auth: Basic base64(:<pat>)
21
+ // GCM OAuth auth: Bearer <oauth_token>
22
+ const authHeader = tokenType === "bearer"
23
+ ? `Bearer ${token}`
24
+ : `Basic ${Buffer.from(`:${token}`).toString("base64")}`;
25
+ this.http = axios.create({
26
+ baseURL: `https://dev.azure.com/${this.org}/${this.project}/_apis`,
27
+ headers: {
28
+ Authorization: authHeader,
29
+ "Content-Type": "application/json",
30
+ },
31
+ timeout: 20_000,
32
+ });
33
+ }
34
+ apiParams(extra) {
35
+ return { "api-version": "7.1-preview.1", ...extra };
36
+ }
37
+ async listPRs(_repoSlug) {
38
+ try {
39
+ const { data } = await this.http.get(`/git/repositories/${this.repoName}/pullrequests`, {
40
+ params: this.apiParams({ "searchCriteria.status": "active", $top: "50" }),
41
+ });
42
+ return (data.value ?? []).map(mapAzPr);
43
+ }
44
+ catch (err) {
45
+ throw wrapAzError(err, "list PRs");
46
+ }
47
+ }
48
+ async getPR(_repoSlug, prNumber) {
49
+ try {
50
+ const { data } = await this.http.get(`/git/repositories/${this.repoName}/pullrequests/${prNumber}`, { params: this.apiParams() });
51
+ return mapAzPr(data);
52
+ }
53
+ catch (err) {
54
+ throw wrapAzError(err, `get PR #${prNumber}`);
55
+ }
56
+ }
57
+ async createPR(_repoSlug, opts) {
58
+ try {
59
+ const { data } = await this.http.post(`/git/repositories/${this.repoName}/pullrequests`, {
60
+ title: opts.title,
61
+ description: opts.body,
62
+ sourceRefName: `refs/heads/${opts.head}`,
63
+ targetRefName: `refs/heads/${opts.base}`,
64
+ isDraft: opts.draft ?? false,
65
+ }, { params: this.apiParams() });
66
+ return mapAzPr(data);
67
+ }
68
+ catch (err) {
69
+ throw wrapAzError(err, "create PR");
70
+ }
71
+ }
72
+ async getPRComments(_repoSlug, prNumber) {
73
+ try {
74
+ const { data } = await this.http.get(`/git/repositories/${this.repoName}/pullrequests/${prNumber}/threads`, { params: this.apiParams() });
75
+ const result = [];
76
+ for (const thread of data.value ?? []) {
77
+ for (const c of thread.comments ?? []) {
78
+ if (c.commentType !== "system") {
79
+ result.push({
80
+ id: c.id,
81
+ body: c.content,
82
+ author: c.author?.displayName ?? "unknown",
83
+ path: thread.threadContext?.filePath,
84
+ line: thread.threadContext?.rightFileStart?.line,
85
+ createdAt: c.publishedDate,
86
+ });
87
+ }
88
+ }
89
+ }
90
+ return result;
91
+ }
92
+ catch (err) {
93
+ throw wrapAzError(err, `get PR #${prNumber} threads`);
94
+ }
95
+ }
96
+ async addPRComment(_repoSlug, prNumber, body) {
97
+ try {
98
+ await this.http.post(`/git/repositories/${this.repoName}/pullrequests/${prNumber}/threads`, {
99
+ comments: [{ parentCommentId: 0, content: body, commentType: 1 }],
100
+ status: 1,
101
+ }, { params: this.apiParams() });
102
+ }
103
+ catch (err) {
104
+ throw wrapAzError(err, `comment on PR #${prNumber}`);
105
+ }
106
+ }
107
+ async getPRDiff(_repoSlug, prNumber) {
108
+ try {
109
+ // Get latest iteration first
110
+ const iterRes = await this.http.get(`/git/repositories/${this.repoName}/pullRequests/${prNumber}/iterations`, { params: this.apiParams() });
111
+ const iterations = iterRes.data.value ?? [];
112
+ if (iterations.length === 0)
113
+ return "";
114
+ const latestId = iterations[iterations.length - 1].id;
115
+ const { data } = await this.http.get(`/git/repositories/${this.repoName}/pullRequests/${prNumber}/iterations/${latestId}/changes`, { params: this.apiParams() });
116
+ const paths = (data.changeEntries ?? []).map((e) => e.item?.path).filter(Boolean);
117
+ return paths.length > 0 ? `Changed files:\n${paths.join("\n")}` : "";
118
+ }
119
+ catch {
120
+ return "";
121
+ }
122
+ }
123
+ async mergePR(_repoSlug, prNumber, opts) {
124
+ // Azure requires the latest source commit SHA to complete a PR
125
+ let lastMergeSourceCommit;
126
+ try {
127
+ const { data } = await this.http.get(`/git/repositories/${this.repoName}/pullrequests/${prNumber}`, { params: this.apiParams() });
128
+ // Azure puts the head commit on the PR object
129
+ const pr = data;
130
+ lastMergeSourceCommit = pr.lastMergeSourceCommit;
131
+ }
132
+ catch (err) {
133
+ throw wrapAzError(err, `get PR #${prNumber} for merge`);
134
+ }
135
+ // Map our method to Azure's mergeStrategy
136
+ const strategyMap = {
137
+ squash: "squash",
138
+ merge: "noFastForward",
139
+ rebase: "rebase",
140
+ };
141
+ try {
142
+ await this.http.patch(`/git/repositories/${this.repoName}/pullrequests/${prNumber}`, {
143
+ status: "completed",
144
+ lastMergeSourceCommit,
145
+ completionOptions: {
146
+ mergeStrategy: strategyMap[opts.method],
147
+ deleteSourceBranch: opts.deleteSourceBranch ?? false,
148
+ squashMerge: opts.method === "squash",
149
+ },
150
+ }, { params: this.apiParams() });
151
+ }
152
+ catch (err) {
153
+ throw wrapAzError(err, `merge PR #${prNumber}`);
154
+ }
155
+ }
156
+ async closePR(_repoSlug, prNumber) {
157
+ try {
158
+ await this.http.patch(`/git/repositories/${this.repoName}/pullrequests/${prNumber}`, { status: "abandoned" }, { params: this.apiParams() });
159
+ }
160
+ catch (err) {
161
+ throw wrapAzError(err, `abandon PR #${prNumber}`);
162
+ }
163
+ }
164
+ async submitPRReview(_repoSlug, prNumber, opts) {
165
+ const verdictPrefix = opts.event === "approve" ? "✅ APPROVED" :
166
+ opts.event === "request_changes" ? "🔴 CHANGES REQUESTED" : "💬 REVIEW";
167
+ try {
168
+ // Post the summary as a PR thread
169
+ await this.http.post(`/git/repositories/${this.repoName}/pullrequests/${prNumber}/threads`, {
170
+ comments: [{ parentCommentId: 0, content: `${verdictPrefix}\n\n${opts.body}`, commentType: 1 }],
171
+ status: 1,
172
+ }, { params: this.apiParams() });
173
+ // Post inline comments as file-level threads
174
+ for (const c of opts.comments ?? []) {
175
+ await this.http.post(`/git/repositories/${this.repoName}/pullrequests/${prNumber}/threads`, {
176
+ comments: [{ parentCommentId: 0, content: c.body, commentType: 1 }],
177
+ threadContext: {
178
+ filePath: `/${c.path}`,
179
+ rightFileStart: { line: c.line, offset: 1 },
180
+ rightFileEnd: { line: c.line, offset: 120 },
181
+ },
182
+ status: 1,
183
+ }, { params: this.apiParams() }).catch(() => { });
184
+ }
185
+ }
186
+ catch (err) {
187
+ throw wrapAzError(err, `submit review on PR #${prNumber}`);
188
+ }
189
+ }
190
+ async replyToComment(_repoSlug, prNumber, commentId, body) {
191
+ try {
192
+ // Azure DevOps: add a comment to an existing thread
193
+ // We don't have the threadId stored separately, so post a new general comment
194
+ const replyBody = `*(in reply to comment #${commentId})*\n\n${body}`;
195
+ await this.http.post(`/git/repositories/${this.repoName}/pullRequests/${prNumber}/threads`, {
196
+ comments: [{ parentCommentId: 0, content: replyBody, commentType: 1 }],
197
+ status: 4, // Fixed
198
+ }, { params: this.apiParams() });
199
+ }
200
+ catch (err) {
201
+ throw wrapAzError(err, `reply to comment #${commentId}`);
202
+ }
203
+ }
204
+ async getDefaultBranch(_repoSlug) {
205
+ try {
206
+ const { data } = await this.http.get(`/git/repositories/${this.repoName}`, { params: this.apiParams() });
207
+ return (data.defaultBranch ?? "refs/heads/main").replace("refs/heads/", "");
208
+ }
209
+ catch (err) {
210
+ throw wrapAzError(err, "get default branch");
211
+ }
212
+ }
213
+ }
214
+ // ─── Mappers ──────────────────────────────────────────────────────────────────
215
+ function mapAzPr(d) {
216
+ let state;
217
+ if (d.status === "completed")
218
+ state = "merged";
219
+ else if (d.status === "active")
220
+ state = "open";
221
+ else
222
+ state = "closed";
223
+ return {
224
+ id: d.pullRequestId,
225
+ number: d.pullRequestId,
226
+ title: d.title,
227
+ body: d.description ?? "",
228
+ state,
229
+ head: (d.sourceRefName ?? "").replace("refs/heads/", ""),
230
+ base: (d.targetRefName ?? "").replace("refs/heads/", ""),
231
+ url: d.url ?? "",
232
+ author: d.createdBy?.displayName ?? "unknown",
233
+ createdAt: d.creationDate ?? "",
234
+ updatedAt: d.lastMergeSourceCommit?.author?.date ?? d.creationDate ?? "",
235
+ };
236
+ }
237
+ function wrapAzError(err, action) {
238
+ if (isAxiosError(err)) {
239
+ const status = err.response?.status;
240
+ const msg = err.response?.data?.message ?? err.message;
241
+ if (status === 401 || status === 203) {
242
+ return new GitxError(`Azure DevOps authentication failed while trying to ${action}.\n` +
243
+ ` If using PAT: run \`gitx config set azure\` to update your token.\n` +
244
+ ` If using GCM: run \`git pull\` in your repo to trigger a fresh login.`, { exitCode: 1, cause: err });
245
+ }
246
+ if (status === 404) {
247
+ return new GitxError(`Azure DevOps resource not found while trying to ${action}. Verify org/project/repo slug and token scopes.`, { exitCode: 1, cause: err });
248
+ }
249
+ return new GitxError(`Azure DevOps API error (${status ?? "network"}) while trying to ${action}: ${msg}`, { exitCode: 1, cause: err });
250
+ }
251
+ return new GitxError(`Unexpected error while trying to ${action}: ${String(err)}`, {
252
+ exitCode: 1,
253
+ cause: err,
254
+ });
255
+ }
256
+ //# sourceMappingURL=azure.js.map