@g-abhishek/gitx 0.1.4 β†’ 0.1.5

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.
@@ -0,0 +1,92 @@
1
+ import inquirer from "inquirer";
2
+ import { logger } from "../../../logger/logger.js";
3
+ import { Gitx } from "../../../core/gitx.js";
4
+ import { runFixCommentsWorkflow } from "../../../workflows/pr.js";
5
+ import { pushBranch, getCurrentBranch, isWorkingTreeDirty } from "../../../utils/gitOps.js";
6
+ export function registerPrResolveCommand(pr) {
7
+ pr.command("resolve")
8
+ .description("πŸ”§ AI-resolve review comments: applies code fixes, commits, and pushes")
9
+ .argument("<id>", "Pull request number")
10
+ .option("--dry-run", "Preview fixes without applying or committing", false)
11
+ .option("--no-commit", "Apply fixes to working tree only β€” skip commit and push", false)
12
+ .option("--no-push", "Apply & commit locally but skip push", false)
13
+ .action(async (id, options) => {
14
+ const prNumber = parseInt(id, 10);
15
+ if (isNaN(prNumber) || prNumber <= 0) {
16
+ logger.error(`Invalid PR number: ${id}`);
17
+ process.exit(1);
18
+ }
19
+ // Commander sets options.commit = false when --no-commit is passed
20
+ const noCommit = options.commit === false;
21
+ const gitx = await Gitx.fromCwd();
22
+ const ctx = await gitx.getRepoContext();
23
+ logger.info(`πŸ”§ Resolving review comments on PR #${prNumber} (${ctx.repoSlug})…\n`);
24
+ // ── AI availability check ─────────────────────────────────────────────
25
+ if (!await Gitx.isAiAvailable(gitx.config)) {
26
+ logger.warn("⚠️ No AI provider configured β€” cannot generate fixes.\n" +
27
+ " Run `gitx config` to set up an AI provider (Claude, OpenAI, or claude-cli).");
28
+ return;
29
+ }
30
+ // ── Confirmation prompt ───────────────────────────────────────────────
31
+ if (!options.dryRun) {
32
+ const modeDesc = noCommit
33
+ ? "apply fixes to your working tree only (no commit or push)"
34
+ : options.push === false
35
+ ? "apply fixes and commit locally (no push)"
36
+ : "apply fixes, commit, and push";
37
+ const { proceed } = await inquirer.prompt([
38
+ {
39
+ type: "confirm",
40
+ name: "proceed",
41
+ message: `This will ${modeDesc}. Continue?`,
42
+ default: false,
43
+ },
44
+ ]);
45
+ if (!proceed) {
46
+ logger.warn("Cancelled.");
47
+ return;
48
+ }
49
+ }
50
+ // ── Guard: warn about uncommitted changes ─────────────────────────────
51
+ if (!options.dryRun) {
52
+ const dirty = await isWorkingTreeDirty(gitx.cwd);
53
+ if (dirty) {
54
+ logger.warn("⚠️ You have uncommitted changes. They may conflict with applied fixes.");
55
+ const { cont } = await inquirer.prompt([
56
+ { type: "confirm", name: "cont", message: "Continue anyway?", default: false },
57
+ ]);
58
+ if (!cont) {
59
+ logger.warn("Cancelled.");
60
+ return;
61
+ }
62
+ }
63
+ }
64
+ const result = await runFixCommentsWorkflow(gitx, prNumber, options.dryRun, noCommit);
65
+ logger.info(`\nπŸ“‹ PR: ${result.pr.title}`);
66
+ if (result.appliedFixes.length === 0 && result.skippedFixes.length === 0) {
67
+ logger.info("No actionable review comments found.");
68
+ return;
69
+ }
70
+ if (result.appliedFixes.length > 0) {
71
+ logger.success(`\nβœ… Applied ${result.appliedFixes.length} fix(es):`);
72
+ result.appliedFixes.forEach((f) => logger.info(` β€’ ${f.path} β€” ${f.rationale}`));
73
+ }
74
+ if (result.skippedFixes.length > 0) {
75
+ logger.warn(`\n⚠️ Skipped ${result.skippedFixes.length} fix(es):`);
76
+ result.skippedFixes.forEach((f) => logger.warn(` β€’ ${f.path}: ${f.reason}`));
77
+ }
78
+ // ── Push if applicable ────────────────────────────────────────────────
79
+ if (!options.dryRun && !noCommit && options.push !== false && result.appliedFixes.length > 0) {
80
+ const branch = await getCurrentBranch(gitx.cwd);
81
+ logger.info(`\nπŸš€ Pushing ${branch}…`);
82
+ try {
83
+ await pushBranch(branch, gitx.cwd);
84
+ logger.success("Branch pushed.");
85
+ }
86
+ catch (err) {
87
+ logger.warn(`Push failed: ${String(err.message ?? err)}`);
88
+ }
89
+ }
90
+ });
91
+ }
92
+ //# sourceMappingURL=resolve.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolve.js","sourceRoot":"","sources":["../../../../src/cli/commands/pr/resolve.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Command } from \"commander\";\nimport inquirer from \"inquirer\";\nimport { logger } from \"../../../logger/logger.js\";\nimport { Gitx } from \"../../../core/gitx.js\";\nimport { runFixCommentsWorkflow } from \"../../../workflows/pr.js\";\nimport { pushBranch, getCurrentBranch, isWorkingTreeDirty } from \"../../../utils/gitOps.js\";\n\nexport function registerPrResolveCommand(pr: Command): void {\n pr.command(\"resolve\")\n .description(\"πŸ”§ AI-resolve review comments: applies code fixes, commits, and pushes\")\n .argument(\"<id>\", \"Pull request number\")\n .option(\"--dry-run\", \"Preview fixes without applying or committing\", false)\n .option(\"--no-commit\", \"Apply fixes to working tree only β€” skip commit and push\", false)\n .option(\"--no-push\", \"Apply & commit locally but skip push\", false)\n .action(async (id: string, options: { dryRun: boolean; commit: boolean; push: boolean }) => {\n const prNumber = parseInt(id, 10);\n if (isNaN(prNumber) || prNumber <= 0) {\n logger.error(`Invalid PR number: ${id}`);\n process.exit(1);\n }\n\n // Commander sets options.commit = false when --no-commit is passed\n const noCommit = options.commit === false;\n\n const gitx = await Gitx.fromCwd();\n const ctx = await gitx.getRepoContext();\n logger.info(`πŸ”§ Resolving review comments on PR #${prNumber} (${ctx.repoSlug})…\\n`);\n\n // ── AI availability check ─────────────────────────────────────────────\n if (!await Gitx.isAiAvailable(gitx.config)) {\n logger.warn(\n \"⚠️ No AI provider configured β€” cannot generate fixes.\\n\" +\n \" Run `gitx config` to set up an AI provider (Claude, OpenAI, or claude-cli).\"\n );\n return;\n }\n\n // ── Confirmation prompt ───────────────────────────────────────────────\n if (!options.dryRun) {\n const modeDesc = noCommit\n ? \"apply fixes to your working tree only (no commit or push)\"\n : options.push === false\n ? \"apply fixes and commit locally (no push)\"\n : \"apply fixes, commit, and push\";\n\n const { proceed } = await inquirer.prompt<{ proceed: boolean }>([\n {\n type: \"confirm\",\n name: \"proceed\",\n message: `This will ${modeDesc}. Continue?`,\n default: false,\n },\n ]);\n if (!proceed) {\n logger.warn(\"Cancelled.\");\n return;\n }\n }\n\n // ── Guard: warn about uncommitted changes ─────────────────────────────\n if (!options.dryRun) {\n const dirty = await isWorkingTreeDirty(gitx.cwd);\n if (dirty) {\n logger.warn(\"⚠️ You have uncommitted changes. They may conflict with applied fixes.\");\n const { cont } = await inquirer.prompt<{ cont: boolean }>([\n { type: \"confirm\", name: \"cont\", message: \"Continue anyway?\", default: false },\n ]);\n if (!cont) { logger.warn(\"Cancelled.\"); return; }\n }\n }\n\n const result = await runFixCommentsWorkflow(gitx, prNumber, options.dryRun, noCommit);\n\n logger.info(`\\nπŸ“‹ PR: ${result.pr.title}`);\n\n if (result.appliedFixes.length === 0 && result.skippedFixes.length === 0) {\n logger.info(\"No actionable review comments found.\");\n return;\n }\n\n if (result.appliedFixes.length > 0) {\n logger.success(`\\nβœ… Applied ${result.appliedFixes.length} fix(es):`);\n result.appliedFixes.forEach((f) => logger.info(` β€’ ${f.path} β€” ${f.rationale}`));\n }\n\n if (result.skippedFixes.length > 0) {\n logger.warn(`\\n⚠️ Skipped ${result.skippedFixes.length} fix(es):`);\n result.skippedFixes.forEach((f) => logger.warn(` β€’ ${f.path}: ${f.reason}`));\n }\n\n // ── Push if applicable ────────────────────────────────────────────────\n if (!options.dryRun && !noCommit && options.push !== false && result.appliedFixes.length > 0) {\n const branch = await getCurrentBranch(gitx.cwd);\n logger.info(`\\nπŸš€ Pushing ${branch}…`);\n try {\n await pushBranch(branch, gitx.cwd);\n logger.success(\"Branch pushed.\");\n } catch (err) {\n logger.warn(`Push failed: ${String((err as Error).message ?? err)}`);\n }\n }\n });\n}\n"]}
@@ -123,7 +123,7 @@ export function registerPrReviewCommand(pr) {
123
123
  logger.warn("⚠️ Review could not be posted to the PR (see error above).\n" +
124
124
  " The full review is shown above β€” you can copy it manually.");
125
125
  }
126
- // To address the review comments, run: gitx pr fix-comments <PR_number>
126
+ // To resolve the review comments, run: gitx pr resolve <PR_number>
127
127
  });
128
128
  }
129
129
  //# sourceMappingURL=review.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"review.js","sourceRoot":"","sources":["../../../../src/cli/commands/pr/review.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Command } from \"commander\";\nimport { logger } from \"../../../logger/logger.js\";\nimport { Gitx } from \"../../../core/gitx.js\";\nimport { runReviewWorkflow } from \"../../../workflows/pr.js\";\n\nexport function registerPrReviewCommand(pr: Command): void {\n pr.command(\"review\")\n .description(\"🧐 Senior-dev AI review: inline comments, checklist, verdict\")\n .argument(\"<id>\", \"Pull request number\")\n .option(\"--no-comment\", \"Show review locally only β€” do not post to PR\")\n .option(\"--no-inline\", \"Post overall review but skip inline file comments\")\n .action(async (id: string, options: { comment: boolean; inline: boolean }) => {\n const prNumber = parseInt(id, 10);\n if (isNaN(prNumber) || prNumber <= 0) {\n logger.error(`Invalid PR number: ${id}`);\n process.exit(1);\n }\n\n const gitx = await Gitx.fromCwd();\n\n if (!await Gitx.isAiAvailable(gitx.config)) {\n logger.warn(\n \"⚠️ No AI provider configured β€” review will not be meaningful.\\n\" +\n \" Run: gitx config set anthropic (or openai / claude-cli)\"\n );\n return;\n }\n\n const postComment = options.comment !== false;\n\n logger.info(`\\n🧐 Senior-dev AI review β€” PR #${prNumber}\\n`);\n\n let result: Awaited<ReturnType<typeof runReviewWorkflow>>;\n try {\n result = await runReviewWorkflow(gitx, prNumber, postComment);\n } catch (err) {\n logger.error(`Review failed: ${String((err as Error).message ?? err)}`);\n process.exitCode = 1;\n return;\n }\n\n const { pr: pullReq, review, comments } = result;\n\n // ── PR header ─────────────────────────────────────────────────────────\n logger.info(`\\nπŸ“‹ ${pullReq.title}`);\n logger.info(` ${pullReq.head} β†’ ${pullReq.base} Β· by ${pullReq.author} Β· ${pullReq.state}`);\n logger.info(` ${pullReq.url}\\n`);\n\n if (!review) {\n logger.info(`πŸ€– Review:\\n${result.aiSummary}`);\n return;\n }\n\n // ── Verdict ───────────────────────────────────────────────────────────\n const verdictIcon =\n review.verdict === \"approve\"\n ? \"βœ… APPROVE\"\n : review.verdict === \"request_changes\"\n ? \"πŸ”΄ REQUEST CHANGES\"\n : \"πŸ’¬ COMMENT\";\n\n logger.info(`πŸ€– Verdict: ${verdictIcon}\\n`);\n\n // ── Summary ───────────────────────────────────────────────────────────\n logger.info(`πŸ“ Summary:\\n ${review.summary.replace(/\\n/g, \"\\n \")}\\n`);\n\n // ── Checklist ─────────────────────────────────────────────────────────\n if (review.checklist.length > 0) {\n logger.info(\"βœ… Review Checklist:\");\n for (const item of review.checklist) {\n const icon = item.status === \"pass\" ? \"βœ…\" : item.status === \"warn\" ? \"⚠️ \" : \"❌\";\n logger.info(` ${icon} ${item.area.padEnd(20)} ${item.note}`);\n }\n logger.info(\"\");\n }\n\n // ── Issues ────────────────────────────────────────────────────────────\n const criticals = review.issues.filter((i) => i.severity === \"critical\");\n const warnings = review.issues.filter((i) => i.severity === \"warning\");\n const suggestions = review.issues.filter((i) => i.severity === \"suggestion\");\n\n if (review.issues.length > 0) {\n logger.info(`πŸ”Ž Issues (${review.issues.length}):`);\n for (const issue of [...criticals, ...warnings, ...suggestions]) {\n const icon = issue.severity === \"critical\" ? \"πŸ”΄\" : issue.severity === \"warning\" ? \"🟑\" : \"πŸ’‘\";\n const loc = issue.file ? ` [${issue.file}${issue.line ? `:${issue.line}` : \"\"}]` : \"\";\n logger.info(` ${icon} ${issue.description}${loc}`);\n }\n logger.info(\"\");\n }\n\n // ── Inline comments ───────────────────────────────────────────────────\n if (review.inlineComments.length > 0) {\n logger.info(`πŸ’¬ Inline Comments (${review.inlineComments.length}):`);\n for (const c of review.inlineComments) {\n const icon = c.severity === \"critical\" ? \"πŸ”΄\" : c.severity === \"warning\" ? \"🟑\" : \"πŸ’‘\";\n logger.info(`\\n ${icon} ${c.path}:${c.line}`);\n logger.info(` ${c.body.replace(/\\n/g, \"\\n \")}`);\n if (c.suggestion) {\n logger.info(` πŸ“Œ Suggestion: ${c.suggestion.split(\"\\n\")[0]}…`);\n }\n }\n logger.info(\"\");\n }\n\n // ── Positives ─────────────────────────────────────────────────────────\n if (review.positives.length > 0) {\n logger.info(\"πŸ‘ Positives:\");\n for (const p of review.positives) logger.info(` βœ” ${p}`);\n logger.info(\"\");\n }\n\n // ── Testing notes ─────────────────────────────────────────────────────\n if (review.testingNotes) {\n logger.info(`πŸ§ͺ How to test:\\n ${review.testingNotes.replace(/\\n/g, \"\\n \")}\\n`);\n }\n\n // ── Existing PR comments ──────────────────────────────────────────────\n if (comments.length > 0) {\n logger.info(`πŸ’¬ Existing comments on PR (${comments.length}):`);\n for (const c of comments.slice(0, 4)) {\n const loc = c.path ? ` @ ${c.path}${c.line ? `:${c.line}` : \"\"}` : \"\";\n logger.info(\n ` [${c.author}${loc}]: ${c.body.slice(0, 120)}${c.body.length > 120 ? \"…\" : \"\"}`\n );\n }\n if (comments.length > 4) logger.info(` … and ${comments.length - 4} more.`);\n logger.info(\"\");\n }\n\n // ── Status ────────────────────────────────────────────────────────────\n if (!postComment) {\n logger.info(\"ℹ️ Review shown locally only (--no-comment).\");\n } else if (result.reviewPosted) {\n const inlineCount = review.inlineComments.length;\n if (inlineCount > 0) {\n logger.success(\n `βœ… Review posted to PR. ${inlineCount} inline comment(s) β€” check the PR to see delivery method.`\n );\n logger.info(\n ` (If lines were outside the diff, comments were posted as plain PR comments instead.)`\n );\n } else {\n logger.success(\"βœ… Review posted to PR.\");\n }\n } else {\n logger.warn(\n \"⚠️ Review could not be posted to the PR (see error above).\\n\" +\n \" The full review is shown above β€” you can copy it manually.\"\n );\n }\n\n // To address the review comments, run: gitx pr fix-comments <PR_number>\n });\n}\n\n"]}
1
+ {"version":3,"file":"review.js","sourceRoot":"","sources":["../../../../src/cli/commands/pr/review.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Command } from \"commander\";\nimport { logger } from \"../../../logger/logger.js\";\nimport { Gitx } from \"../../../core/gitx.js\";\nimport { runReviewWorkflow } from \"../../../workflows/pr.js\";\n\nexport function registerPrReviewCommand(pr: Command): void {\n pr.command(\"review\")\n .description(\"🧐 Senior-dev AI review: inline comments, checklist, verdict\")\n .argument(\"<id>\", \"Pull request number\")\n .option(\"--no-comment\", \"Show review locally only β€” do not post to PR\")\n .option(\"--no-inline\", \"Post overall review but skip inline file comments\")\n .action(async (id: string, options: { comment: boolean; inline: boolean }) => {\n const prNumber = parseInt(id, 10);\n if (isNaN(prNumber) || prNumber <= 0) {\n logger.error(`Invalid PR number: ${id}`);\n process.exit(1);\n }\n\n const gitx = await Gitx.fromCwd();\n\n if (!await Gitx.isAiAvailable(gitx.config)) {\n logger.warn(\n \"⚠️ No AI provider configured β€” review will not be meaningful.\\n\" +\n \" Run: gitx config set anthropic (or openai / claude-cli)\"\n );\n return;\n }\n\n const postComment = options.comment !== false;\n\n logger.info(`\\n🧐 Senior-dev AI review β€” PR #${prNumber}\\n`);\n\n let result: Awaited<ReturnType<typeof runReviewWorkflow>>;\n try {\n result = await runReviewWorkflow(gitx, prNumber, postComment);\n } catch (err) {\n logger.error(`Review failed: ${String((err as Error).message ?? err)}`);\n process.exitCode = 1;\n return;\n }\n\n const { pr: pullReq, review, comments } = result;\n\n // ── PR header ─────────────────────────────────────────────────────────\n logger.info(`\\nπŸ“‹ ${pullReq.title}`);\n logger.info(` ${pullReq.head} β†’ ${pullReq.base} Β· by ${pullReq.author} Β· ${pullReq.state}`);\n logger.info(` ${pullReq.url}\\n`);\n\n if (!review) {\n logger.info(`πŸ€– Review:\\n${result.aiSummary}`);\n return;\n }\n\n // ── Verdict ───────────────────────────────────────────────────────────\n const verdictIcon =\n review.verdict === \"approve\"\n ? \"βœ… APPROVE\"\n : review.verdict === \"request_changes\"\n ? \"πŸ”΄ REQUEST CHANGES\"\n : \"πŸ’¬ COMMENT\";\n\n logger.info(`πŸ€– Verdict: ${verdictIcon}\\n`);\n\n // ── Summary ───────────────────────────────────────────────────────────\n logger.info(`πŸ“ Summary:\\n ${review.summary.replace(/\\n/g, \"\\n \")}\\n`);\n\n // ── Checklist ─────────────────────────────────────────────────────────\n if (review.checklist.length > 0) {\n logger.info(\"βœ… Review Checklist:\");\n for (const item of review.checklist) {\n const icon = item.status === \"pass\" ? \"βœ…\" : item.status === \"warn\" ? \"⚠️ \" : \"❌\";\n logger.info(` ${icon} ${item.area.padEnd(20)} ${item.note}`);\n }\n logger.info(\"\");\n }\n\n // ── Issues ────────────────────────────────────────────────────────────\n const criticals = review.issues.filter((i) => i.severity === \"critical\");\n const warnings = review.issues.filter((i) => i.severity === \"warning\");\n const suggestions = review.issues.filter((i) => i.severity === \"suggestion\");\n\n if (review.issues.length > 0) {\n logger.info(`πŸ”Ž Issues (${review.issues.length}):`);\n for (const issue of [...criticals, ...warnings, ...suggestions]) {\n const icon = issue.severity === \"critical\" ? \"πŸ”΄\" : issue.severity === \"warning\" ? \"🟑\" : \"πŸ’‘\";\n const loc = issue.file ? ` [${issue.file}${issue.line ? `:${issue.line}` : \"\"}]` : \"\";\n logger.info(` ${icon} ${issue.description}${loc}`);\n }\n logger.info(\"\");\n }\n\n // ── Inline comments ───────────────────────────────────────────────────\n if (review.inlineComments.length > 0) {\n logger.info(`πŸ’¬ Inline Comments (${review.inlineComments.length}):`);\n for (const c of review.inlineComments) {\n const icon = c.severity === \"critical\" ? \"πŸ”΄\" : c.severity === \"warning\" ? \"🟑\" : \"πŸ’‘\";\n logger.info(`\\n ${icon} ${c.path}:${c.line}`);\n logger.info(` ${c.body.replace(/\\n/g, \"\\n \")}`);\n if (c.suggestion) {\n logger.info(` πŸ“Œ Suggestion: ${c.suggestion.split(\"\\n\")[0]}…`);\n }\n }\n logger.info(\"\");\n }\n\n // ── Positives ─────────────────────────────────────────────────────────\n if (review.positives.length > 0) {\n logger.info(\"πŸ‘ Positives:\");\n for (const p of review.positives) logger.info(` βœ” ${p}`);\n logger.info(\"\");\n }\n\n // ── Testing notes ─────────────────────────────────────────────────────\n if (review.testingNotes) {\n logger.info(`πŸ§ͺ How to test:\\n ${review.testingNotes.replace(/\\n/g, \"\\n \")}\\n`);\n }\n\n // ── Existing PR comments ──────────────────────────────────────────────\n if (comments.length > 0) {\n logger.info(`πŸ’¬ Existing comments on PR (${comments.length}):`);\n for (const c of comments.slice(0, 4)) {\n const loc = c.path ? ` @ ${c.path}${c.line ? `:${c.line}` : \"\"}` : \"\";\n logger.info(\n ` [${c.author}${loc}]: ${c.body.slice(0, 120)}${c.body.length > 120 ? \"…\" : \"\"}`\n );\n }\n if (comments.length > 4) logger.info(` … and ${comments.length - 4} more.`);\n logger.info(\"\");\n }\n\n // ── Status ────────────────────────────────────────────────────────────\n if (!postComment) {\n logger.info(\"ℹ️ Review shown locally only (--no-comment).\");\n } else if (result.reviewPosted) {\n const inlineCount = review.inlineComments.length;\n if (inlineCount > 0) {\n logger.success(\n `βœ… Review posted to PR. ${inlineCount} inline comment(s) β€” check the PR to see delivery method.`\n );\n logger.info(\n ` (If lines were outside the diff, comments were posted as plain PR comments instead.)`\n );\n } else {\n logger.success(\"βœ… Review posted to PR.\");\n }\n } else {\n logger.warn(\n \"⚠️ Review could not be posted to the PR (see error above).\\n\" +\n \" The full review is shown above β€” you can copy it manually.\"\n );\n }\n\n // To resolve the review comments, run: gitx pr resolve <PR_number>\n });\n}\n\n"]}
@@ -62,10 +62,17 @@ export declare function getWorkingDiffStat(cwd?: string): Promise<string>;
62
62
  * Auto-detect the most likely base branch for the current feature branch.
63
63
  *
64
64
  * Strategy:
65
- * 1. Check if the current branch has a configured upstream tracking branch.
66
- * 2. Otherwise, try common default branch names (main, master, develop, dev).
67
- * 3. For each candidate, count commits on HEAD that are NOT in that branch.
68
- * The candidate with the fewest such commits is the likely origin.
65
+ * 1. Upstream tracking branch set via `git push -u` β€” strongest signal.
66
+ * Ignored if it points to the branch's own remote ref (origin/current).
67
+ * 2. Scan ALL remote tracking branches and count how many commits HEAD has
68
+ * that each candidate doesn't (`git rev-list --count <ref>..HEAD`).
69
+ * The branch with the FEWEST such commits is the closest ancestor β€”
70
+ * i.e. the branch this one was forked from.
71
+ *
72
+ * Example: main β†’ feature1 (3 commits) β†’ feature2 (2 commits)
73
+ * - origin/main..HEAD = 5 (feature1's 3 + feature2's 2)
74
+ * - origin/feature1..HEAD = 2 (just feature2's commits)
75
+ * β†’ feature1 wins βœ“
69
76
  *
70
77
  * Falls back to "main" if nothing can be determined.
71
78
  */
@@ -1 +1 @@
1
- {"version":3,"file":"gitOps.d.ts","sourceRoot":"","sources":["../../src/utils/gitOps.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"gitOps.d.ts","sourceRoot":"","sources":["../../src/utils/gitOps.ts"],"names":[],"mappings":"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"}
@@ -205,20 +205,24 @@ export async function getWorkingDiffStat(cwd = process.cwd()) {
205
205
  * Auto-detect the most likely base branch for the current feature branch.
206
206
  *
207
207
  * Strategy:
208
- * 1. Check if the current branch has a configured upstream tracking branch.
209
- * 2. Otherwise, try common default branch names (main, master, develop, dev).
210
- * 3. For each candidate, count commits on HEAD that are NOT in that branch.
211
- * The candidate with the fewest such commits is the likely origin.
208
+ * 1. Upstream tracking branch set via `git push -u` β€” strongest signal.
209
+ * Ignored if it points to the branch's own remote ref (origin/current).
210
+ * 2. Scan ALL remote tracking branches and count how many commits HEAD has
211
+ * that each candidate doesn't (`git rev-list --count <ref>..HEAD`).
212
+ * The branch with the FEWEST such commits is the closest ancestor β€”
213
+ * i.e. the branch this one was forked from.
214
+ *
215
+ * Example: main β†’ feature1 (3 commits) β†’ feature2 (2 commits)
216
+ * - origin/main..HEAD = 5 (feature1's 3 + feature2's 2)
217
+ * - origin/feature1..HEAD = 2 (just feature2's commits)
218
+ * β†’ feature1 wins βœ“
212
219
  *
213
220
  * Falls back to "main" if nothing can be determined.
214
221
  */
215
222
  export async function detectBaseBranch(cwd = process.cwd()) {
216
- // Get current branch name upfront β€” used for all checks below
217
223
  const current = await git(["rev-parse", "--abbrev-ref", "HEAD"], cwd).catch(() => "");
218
- // 1. Try upstream tracking branch β€” only useful if it points to a DIFFERENT
219
- // branch (e.g. origin/main), not the branch's own remote tracking ref.
220
- // e.g. "origin/gitx/test" β†’ "gitx/test" == current β†’ skip
221
- // "origin/main" β†’ "main" != current β†’ use it
224
+ // 1. Explicit upstream tracking branch β€” only trust it if it differs from
225
+ // the current branch (guards against "origin/feature2" β†’ "feature2").
222
226
  try {
223
227
  const upstream = await git(["rev-parse", "--abbrev-ref", "@{upstream}"], cwd);
224
228
  const branch = upstream.replace(/^[^/]+\//, "").trim();
@@ -226,39 +230,51 @@ export async function detectBaseBranch(cwd = process.cwd()) {
226
230
  return branch;
227
231
  }
228
232
  catch {
229
- // No upstream configured β€” fall through
233
+ // No upstream set β€” fall through
230
234
  }
231
- // 2. Check remote HEAD (origin's default branch)
235
+ // 2. Scan all remote tracking branches (origin/*), compute how many commits
236
+ // HEAD has that each remote ref doesn't. Closest ancestor wins.
232
237
  try {
233
- const remoteHead = await git(["rev-parse", "--abbrev-ref", "origin/HEAD"], cwd);
234
- const branch = remoteHead.replace(/^origin\//, "").trim();
235
- if (branch && branch !== current)
236
- return branch;
238
+ const remoteOut = await git(["branch", "-r", "--format=%(refname:short)"], cwd);
239
+ const remoteRefs = remoteOut
240
+ .split("\n")
241
+ .map((b) => b.trim())
242
+ .filter((b) => b && !b.endsWith("/HEAD")); // skip "origin/HEAD" pointer
243
+ if (remoteRefs.length === 0)
244
+ return "main";
245
+ // Count commits ahead of every remote ref in parallel for speed
246
+ const results = await Promise.all(remoteRefs.map(async (ref) => {
247
+ const name = ref.replace(/^[^/]+\//, ""); // "origin/feature1" β†’ "feature1"
248
+ if (name === current)
249
+ return null; // skip own remote tracking ref
250
+ try {
251
+ const out = await git(["rev-list", "--count", `${ref}..HEAD`], cwd);
252
+ return { branch: name, ahead: parseInt(out.trim(), 10) || 0 };
253
+ }
254
+ catch {
255
+ return null;
256
+ }
257
+ }));
258
+ const counts = results.filter((r) => r !== null);
259
+ if (counts.length === 0)
260
+ return "main";
261
+ // Sort by fewest commits ahead β€” that's the closest ancestor.
262
+ // Tiebreak: prefer default branch names so "main" beats an equally-close
263
+ // unrelated branch that happens to have 0 commits.
264
+ const defaults = new Set(["main", "master", "develop", "dev", "staging"]);
265
+ counts.sort((a, b) => {
266
+ if (a.ahead !== b.ahead)
267
+ return a.ahead - b.ahead;
268
+ // Same distance β€” prefer default branches
269
+ const aDefault = defaults.has(a.branch) ? 0 : 1;
270
+ const bDefault = defaults.has(b.branch) ? 0 : 1;
271
+ return aDefault - bDefault;
272
+ });
273
+ return counts[0].branch;
237
274
  }
238
275
  catch {
239
- // Not available β€” fall through
240
- }
241
- // 3. Count commits ahead of each common default branch name
242
- const candidates = ["main", "master", "develop", "dev", "staging"];
243
- const counts = [];
244
- for (const candidate of candidates) {
245
- if (candidate === current)
246
- continue;
247
- try {
248
- // Count commits on HEAD not in candidate
249
- const out = await git(["rev-list", "--count", `${candidate}..HEAD`], cwd);
250
- counts.push({ branch: candidate, ahead: parseInt(out.trim(), 10) || 0 });
251
- }
252
- catch {
253
- // Branch doesn't exist locally β€” skip
254
- }
255
- }
256
- if (counts.length > 0) {
257
- // Pick the branch with the fewest commits ahead (closest ancestor)
258
- counts.sort((a, b) => a.ahead - b.ahead);
259
- return counts[0].branch;
276
+ return "main";
260
277
  }
261
- return "main";
262
278
  }
263
279
  /**
264
280
  * Get the one-line commit log for commits on HEAD that are not in baseBranch.
@@ -1 +1 @@
1
- {"version":3,"file":"gitOps.js","sourceRoot":"","sources":["../../src/utils/gitOps.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Extended git operations for the implement workflow.\n * All functions execute native git commands via child_process.\n */\n\nimport { execFile, exec } from \"node:child_process\";\nimport { promisify } from \"node:util\";\nimport { writeFile, mkdir } from \"node:fs/promises\";\nimport { dirname, resolve, join } from \"node:path\";\nimport { GitxError } from \"./errors.js\";\n\nconst execFileAsync = promisify(execFile);\nconst execAsync = promisify(exec);\n\n// ─── Internal helper ──────────────────────────────────────────────────────────\n\nasync function git(args: string[], cwd: string): Promise<string> {\n try {\n const { stdout } = await execFileAsync(\"git\", args, { cwd });\n return String(stdout ?? \"\").trim();\n } catch (err: unknown) {\n const stderr =\n (err as { stderr?: string }).stderr ??\n (err as Error).message ??\n String(err);\n throw new GitxError(`git ${args[0]} failed: ${stderr.trim()}`, {\n exitCode: 1,\n cause: err,\n });\n }\n}\n\n// ─── Branch operations ────────────────────────────────────────────────────────\n\nexport async function getCurrentBranch(cwd = process.cwd()): Promise<string> {\n return git([\"rev-parse\", \"--abbrev-ref\", \"HEAD\"], cwd);\n}\n\n/**\n * Resolve the default branch (main/master/develop) by inspecting the remote.\n * Falls back to \"main\" if nothing can be determined.\n */\nexport async function getDefaultBranchFromGit(\n cwd = process.cwd(),\n configuredDefault?: string\n): Promise<string> {\n if (configuredDefault) return configuredDefault;\n\n try {\n // Try to read from remote HEAD reference\n const out = await git(\n [\"rev-parse\", \"--abbrev-ref\", \"origin/HEAD\"],\n cwd\n );\n // \"origin/main\" β†’ \"main\"\n return out.replace(/^origin\\//, \"\") || \"main\";\n } catch {\n // Fall back to checking common branch names\n try {\n const branches = await git(\n [\"branch\", \"-r\", \"--format=%(refname:short)\"],\n cwd\n );\n const candidates = [\"origin/main\", \"origin/master\", \"origin/develop\"];\n for (const candidate of candidates) {\n if (branches.split(\"\\n\").some((b) => b.trim() === candidate)) {\n return candidate.replace(\"origin/\", \"\");\n }\n }\n } catch {\n /* ignore */\n }\n return \"main\";\n }\n}\n\n/**\n * Create and checkout a new branch.\n * If the branch already exists, just check it out.\n */\nexport async function createAndCheckoutBranch(\n branchName: string,\n cwd = process.cwd()\n): Promise<void> {\n try {\n await git([\"checkout\", \"-b\", branchName], cwd);\n } catch {\n // Branch might already exist – try checking it out\n await git([\"checkout\", branchName], cwd);\n }\n}\n\n/** Sanitise a free-form task string into a valid branch name */\nexport function slugifyBranchName(task: string, prefix = \"gitx\"): string {\n const slug = task\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, \"-\")\n .replace(/^-+|-+$/g, \"\")\n .slice(0, 50);\n const ts = Date.now().toString(36);\n return `${prefix}/${slug}-${ts}`;\n}\n\n// ─── File operations ──────────────────────────────────────────────────────────\n\n/**\n * Write content to a file inside the repo, creating parent directories as needed.\n * Paths should be relative to `cwd`.\n */\nexport async function writeRepoFile(\n relativePath: string,\n content: string,\n cwd = process.cwd()\n): Promise<void> {\n const abs = resolve(join(cwd, relativePath));\n await mkdir(dirname(abs), { recursive: true });\n await writeFile(abs, content, \"utf-8\");\n}\n\n// ─── Diff application ─────────────────────────────────────────────────────────\n\n/**\n * Apply a unified diff string using `git apply`.\n * Returns `true` if applied cleanly, `false` if it failed (caller decides how\n * to handle partial failures).\n */\nexport async function applyUnifiedDiff(\n unifiedDiff: string,\n cwd = process.cwd()\n): Promise<{ ok: boolean; error?: string }> {\n try {\n // Use --3way to handle minor conflicts gracefully\n const { stdout, stderr } = await execAsync(\n `echo ${JSON.stringify(unifiedDiff)} | git apply --3way --whitespace=fix -`,\n { cwd }\n );\n return { ok: true, error: stderr || stdout || undefined };\n } catch (err: unknown) {\n const stderr =\n (err as { stderr?: string }).stderr ??\n (err as Error).message ??\n String(err);\n return { ok: false, error: stderr.trim() };\n }\n}\n\n// ─── Staging & committing ─────────────────────────────────────────────────────\n\nexport async function stageAll(cwd = process.cwd()): Promise<void> {\n await git([\"add\", \"-A\"], cwd);\n}\n\nexport async function hasStagedChanges(cwd = process.cwd()): Promise<boolean> {\n try {\n const out = await git([\"diff\", \"--cached\", \"--name-only\"], cwd);\n return out.trim().length > 0;\n } catch {\n return false;\n }\n}\n\nexport async function commitChanges(\n message: string,\n cwd = process.cwd()\n): Promise<string> {\n await git([\"commit\", \"-m\", message], cwd);\n return git([\"rev-parse\", \"HEAD\"], cwd);\n}\n\n// ─── Push ─────────────────────────────────────────────────────────────────────\n\nexport async function pushBranch(\n branchName: string,\n cwd = process.cwd()\n): Promise<void> {\n await git([\"push\", \"--set-upstream\", \"origin\", branchName], cwd);\n}\n\n// ─── Repo inspection ──────────────────────────────────────────────────────────\n\n/**\n * List all tracked files in the repo (respects .gitignore).\n * Returns paths relative to `cwd`.\n */\nexport async function listTrackedFiles(cwd = process.cwd()): Promise<string[]> {\n try {\n const out = await git([\"ls-files\"], cwd);\n return out\n .split(\"\\n\")\n .map((l) => l.trim())\n .filter(Boolean);\n } catch {\n return [];\n }\n}\n\n/**\n * Get the unified diff of only staged changes (`git diff --cached`).\n * Use this when the caller wants to commit only what's already in the index.\n */\nexport async function getStagedDiff(cwd = process.cwd()): Promise<string> {\n try {\n return await git([\"diff\", \"--cached\"], cwd);\n } catch {\n return \"\";\n }\n}\n\n/**\n * Get a compact stat summary of only staged changes (`git diff --cached --stat`).\n */\nexport async function getStagedDiffStat(cwd = process.cwd()): Promise<string> {\n try {\n return await git([\"diff\", \"--cached\", \"--stat\"], cwd);\n } catch {\n return \"\";\n }\n}\n\n/**\n * Get the unified diff of all uncommitted changes (staged + unstaged).\n */\nexport async function getWorkingDiff(cwd = process.cwd()): Promise<string> {\n try {\n const staged = await git([\"diff\", \"--cached\"], cwd);\n const unstaged = await git([\"diff\"], cwd);\n return [staged, unstaged].filter(Boolean).join(\"\\n\");\n } catch {\n return \"\";\n }\n}\n\n/**\n * Get a compact summary of staged changes (--stat format):\n * lists every changed file with insertion/deletion counts.\n * Always small regardless of diff size β€” used to give the AI the\n * complete picture of what changed even when the full diff is truncated.\n */\nexport async function getWorkingDiffStat(cwd = process.cwd()): Promise<string> {\n try {\n const staged = await git([\"diff\", \"--cached\", \"--stat\"], cwd);\n const unstaged = await git([\"diff\", \"--stat\"], cwd);\n return [staged, unstaged].filter(Boolean).join(\"\\n\");\n } catch {\n return \"\";\n }\n}\n\n/**\n * Auto-detect the most likely base branch for the current feature branch.\n *\n * Strategy:\n * 1. Check if the current branch has a configured upstream tracking branch.\n * 2. Otherwise, try common default branch names (main, master, develop, dev).\n * 3. For each candidate, count commits on HEAD that are NOT in that branch.\n * The candidate with the fewest such commits is the likely origin.\n *\n * Falls back to \"main\" if nothing can be determined.\n */\nexport async function detectBaseBranch(cwd = process.cwd()): Promise<string> {\n // Get current branch name upfront β€” used for all checks below\n const current = await git([\"rev-parse\", \"--abbrev-ref\", \"HEAD\"], cwd).catch(() => \"\");\n\n // 1. Try upstream tracking branch β€” only useful if it points to a DIFFERENT\n // branch (e.g. origin/main), not the branch's own remote tracking ref.\n // e.g. \"origin/gitx/test\" β†’ \"gitx/test\" == current β†’ skip\n // \"origin/main\" β†’ \"main\" != current β†’ use it\n try {\n const upstream = await git([\"rev-parse\", \"--abbrev-ref\", \"@{upstream}\"], cwd);\n const branch = upstream.replace(/^[^/]+\\//, \"\").trim();\n if (branch && branch !== current) return branch;\n } catch {\n // No upstream configured β€” fall through\n }\n\n // 2. Check remote HEAD (origin's default branch)\n try {\n const remoteHead = await git([\"rev-parse\", \"--abbrev-ref\", \"origin/HEAD\"], cwd);\n const branch = remoteHead.replace(/^origin\\//, \"\").trim();\n if (branch && branch !== current) return branch;\n } catch {\n // Not available β€” fall through\n }\n\n // 3. Count commits ahead of each common default branch name\n const candidates = [\"main\", \"master\", \"develop\", \"dev\", \"staging\"];\n const counts: Array<{ branch: string; ahead: number }> = [];\n for (const candidate of candidates) {\n if (candidate === current) continue;\n try {\n // Count commits on HEAD not in candidate\n const out = await git([\"rev-list\", \"--count\", `${candidate}..HEAD`], cwd);\n counts.push({ branch: candidate, ahead: parseInt(out.trim(), 10) || 0 });\n } catch {\n // Branch doesn't exist locally β€” skip\n }\n }\n\n if (counts.length > 0) {\n // Pick the branch with the fewest commits ahead (closest ancestor)\n counts.sort((a, b) => a.ahead - b.ahead);\n return counts[0]!.branch;\n }\n\n return \"main\";\n}\n\n/**\n * Get the one-line commit log for commits on HEAD that are not in baseBranch.\n * Used to give AI context about what this branch adds.\n */\nexport async function getBranchCommits(\n cwd = process.cwd(),\n baseBranch = \"main\"\n): Promise<string[]> {\n try {\n const out = await git(\n [\"log\", \"--oneline\", \"--no-decorate\", `${baseBranch}..HEAD`],\n cwd\n );\n return out.split(\"\\n\").map((l) => l.trim()).filter(Boolean);\n } catch {\n return [];\n }\n}\n\n/**\n * Get the unified diff of all changes between baseBranch and HEAD.\n * This is what the PR reviewer would see β€” all additions across all commits.\n */\nexport async function getBranchDiff(\n cwd = process.cwd(),\n baseBranch = \"main\"\n): Promise<string> {\n try {\n // Three-dot diff: all changes introduced by this branch vs. the merge base\n return await git([\"diff\", `${baseBranch}...HEAD`], cwd);\n } catch {\n return \"\";\n }\n}\n\n/**\n * Get the --stat summary of all changes between baseBranch and HEAD.\n * Lists every file changed with insertion/deletion counts.\n * Used alongside a truncated diff so the AI sees the full file list even\n * when the detailed patch is cut off.\n */\nexport async function getBranchStat(\n cwd = process.cwd(),\n baseBranch = \"main\"\n): Promise<string> {\n try {\n return await git([\"diff\", \"--stat\", `${baseBranch}...HEAD`], cwd);\n } catch {\n return \"\";\n }\n}\n\n/**\n * Read file content as a string. Returns empty string if file doesn't exist.\n */\nexport async function readRepoFile(\n relativePath: string,\n cwd = process.cwd()\n): Promise<string | undefined> {\n try {\n const abs = resolve(join(cwd, relativePath));\n const { readFile } = await import(\"node:fs/promises\");\n return await readFile(abs, \"utf-8\");\n } catch {\n return undefined;\n }\n}\n\n/**\n * Check whether a branch exists on the remote (origin).\n * Uses git ls-remote which does not require a full fetch.\n */\nexport async function branchExistsOnRemote(\n branchName: string,\n cwd = process.cwd()\n): Promise<boolean> {\n try {\n const out = await git(\n [\"ls-remote\", \"--heads\", \"origin\", `refs/heads/${branchName}`],\n cwd\n );\n return out.trim().length > 0;\n } catch {\n return false;\n }\n}\n\n/**\n * Returns true if the working tree has uncommitted changes (staged or unstaged).\n */\nexport async function isWorkingTreeDirty(cwd = process.cwd()): Promise<boolean> {\n try {\n const out = await git([\"status\", \"--porcelain\"], cwd);\n return out.trim().length > 0;\n } catch {\n return false;\n }\n}\n\n// ─── Context helpers for `gitx ask` ──────────────────────────────────────────\n\n/**\n * Returns `git status --short` output for use in AI context.\n * Returns an empty string when the working tree is clean or git fails.\n */\nexport async function getGitStatus(cwd = process.cwd()): Promise<string> {\n try {\n return await git([\"status\", \"--short\"], cwd);\n } catch {\n return \"\";\n }\n}\n\n/**\n * Returns the last `count` commits as one-line summaries (hash + subject).\n * Defaults to the last 10 commits.\n */\nexport async function getRecentCommits(cwd = process.cwd(), count = 10): Promise<string[]> {\n try {\n const out = await git([\"log\", \"--oneline\", \"--no-decorate\", `-${count}`], cwd);\n return out.split(\"\\n\").map((l) => l.trim()).filter(Boolean);\n } catch {\n return [];\n }\n}\n\n/**\n * Returns the list of git stashes as `stash@{0}: ...` strings.\n * Returns an empty array when there are no stashes or git fails.\n */\nexport async function getStashList(cwd = process.cwd()): Promise<string[]> {\n try {\n const out = await git([\"stash\", \"list\"], cwd);\n return out.split(\"\\n\").map((l) => l.trim()).filter(Boolean);\n } catch {\n return [];\n }\n}\n"]}
1
+ {"version":3,"file":"gitOps.js","sourceRoot":"","sources":["../../src/utils/gitOps.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Extended git operations for the implement workflow.\n * All functions execute native git commands via child_process.\n */\n\nimport { execFile, exec } from \"node:child_process\";\nimport { promisify } from \"node:util\";\nimport { writeFile, mkdir } from \"node:fs/promises\";\nimport { dirname, resolve, join } from \"node:path\";\nimport { GitxError } from \"./errors.js\";\n\nconst execFileAsync = promisify(execFile);\nconst execAsync = promisify(exec);\n\n// ─── Internal helper ──────────────────────────────────────────────────────────\n\nasync function git(args: string[], cwd: string): Promise<string> {\n try {\n const { stdout } = await execFileAsync(\"git\", args, { cwd });\n return String(stdout ?? \"\").trim();\n } catch (err: unknown) {\n const stderr =\n (err as { stderr?: string }).stderr ??\n (err as Error).message ??\n String(err);\n throw new GitxError(`git ${args[0]} failed: ${stderr.trim()}`, {\n exitCode: 1,\n cause: err,\n });\n }\n}\n\n// ─── Branch operations ────────────────────────────────────────────────────────\n\nexport async function getCurrentBranch(cwd = process.cwd()): Promise<string> {\n return git([\"rev-parse\", \"--abbrev-ref\", \"HEAD\"], cwd);\n}\n\n/**\n * Resolve the default branch (main/master/develop) by inspecting the remote.\n * Falls back to \"main\" if nothing can be determined.\n */\nexport async function getDefaultBranchFromGit(\n cwd = process.cwd(),\n configuredDefault?: string\n): Promise<string> {\n if (configuredDefault) return configuredDefault;\n\n try {\n // Try to read from remote HEAD reference\n const out = await git(\n [\"rev-parse\", \"--abbrev-ref\", \"origin/HEAD\"],\n cwd\n );\n // \"origin/main\" β†’ \"main\"\n return out.replace(/^origin\\//, \"\") || \"main\";\n } catch {\n // Fall back to checking common branch names\n try {\n const branches = await git(\n [\"branch\", \"-r\", \"--format=%(refname:short)\"],\n cwd\n );\n const candidates = [\"origin/main\", \"origin/master\", \"origin/develop\"];\n for (const candidate of candidates) {\n if (branches.split(\"\\n\").some((b) => b.trim() === candidate)) {\n return candidate.replace(\"origin/\", \"\");\n }\n }\n } catch {\n /* ignore */\n }\n return \"main\";\n }\n}\n\n/**\n * Create and checkout a new branch.\n * If the branch already exists, just check it out.\n */\nexport async function createAndCheckoutBranch(\n branchName: string,\n cwd = process.cwd()\n): Promise<void> {\n try {\n await git([\"checkout\", \"-b\", branchName], cwd);\n } catch {\n // Branch might already exist – try checking it out\n await git([\"checkout\", branchName], cwd);\n }\n}\n\n/** Sanitise a free-form task string into a valid branch name */\nexport function slugifyBranchName(task: string, prefix = \"gitx\"): string {\n const slug = task\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, \"-\")\n .replace(/^-+|-+$/g, \"\")\n .slice(0, 50);\n const ts = Date.now().toString(36);\n return `${prefix}/${slug}-${ts}`;\n}\n\n// ─── File operations ──────────────────────────────────────────────────────────\n\n/**\n * Write content to a file inside the repo, creating parent directories as needed.\n * Paths should be relative to `cwd`.\n */\nexport async function writeRepoFile(\n relativePath: string,\n content: string,\n cwd = process.cwd()\n): Promise<void> {\n const abs = resolve(join(cwd, relativePath));\n await mkdir(dirname(abs), { recursive: true });\n await writeFile(abs, content, \"utf-8\");\n}\n\n// ─── Diff application ─────────────────────────────────────────────────────────\n\n/**\n * Apply a unified diff string using `git apply`.\n * Returns `true` if applied cleanly, `false` if it failed (caller decides how\n * to handle partial failures).\n */\nexport async function applyUnifiedDiff(\n unifiedDiff: string,\n cwd = process.cwd()\n): Promise<{ ok: boolean; error?: string }> {\n try {\n // Use --3way to handle minor conflicts gracefully\n const { stdout, stderr } = await execAsync(\n `echo ${JSON.stringify(unifiedDiff)} | git apply --3way --whitespace=fix -`,\n { cwd }\n );\n return { ok: true, error: stderr || stdout || undefined };\n } catch (err: unknown) {\n const stderr =\n (err as { stderr?: string }).stderr ??\n (err as Error).message ??\n String(err);\n return { ok: false, error: stderr.trim() };\n }\n}\n\n// ─── Staging & committing ─────────────────────────────────────────────────────\n\nexport async function stageAll(cwd = process.cwd()): Promise<void> {\n await git([\"add\", \"-A\"], cwd);\n}\n\nexport async function hasStagedChanges(cwd = process.cwd()): Promise<boolean> {\n try {\n const out = await git([\"diff\", \"--cached\", \"--name-only\"], cwd);\n return out.trim().length > 0;\n } catch {\n return false;\n }\n}\n\nexport async function commitChanges(\n message: string,\n cwd = process.cwd()\n): Promise<string> {\n await git([\"commit\", \"-m\", message], cwd);\n return git([\"rev-parse\", \"HEAD\"], cwd);\n}\n\n// ─── Push ─────────────────────────────────────────────────────────────────────\n\nexport async function pushBranch(\n branchName: string,\n cwd = process.cwd()\n): Promise<void> {\n await git([\"push\", \"--set-upstream\", \"origin\", branchName], cwd);\n}\n\n// ─── Repo inspection ──────────────────────────────────────────────────────────\n\n/**\n * List all tracked files in the repo (respects .gitignore).\n * Returns paths relative to `cwd`.\n */\nexport async function listTrackedFiles(cwd = process.cwd()): Promise<string[]> {\n try {\n const out = await git([\"ls-files\"], cwd);\n return out\n .split(\"\\n\")\n .map((l) => l.trim())\n .filter(Boolean);\n } catch {\n return [];\n }\n}\n\n/**\n * Get the unified diff of only staged changes (`git diff --cached`).\n * Use this when the caller wants to commit only what's already in the index.\n */\nexport async function getStagedDiff(cwd = process.cwd()): Promise<string> {\n try {\n return await git([\"diff\", \"--cached\"], cwd);\n } catch {\n return \"\";\n }\n}\n\n/**\n * Get a compact stat summary of only staged changes (`git diff --cached --stat`).\n */\nexport async function getStagedDiffStat(cwd = process.cwd()): Promise<string> {\n try {\n return await git([\"diff\", \"--cached\", \"--stat\"], cwd);\n } catch {\n return \"\";\n }\n}\n\n/**\n * Get the unified diff of all uncommitted changes (staged + unstaged).\n */\nexport async function getWorkingDiff(cwd = process.cwd()): Promise<string> {\n try {\n const staged = await git([\"diff\", \"--cached\"], cwd);\n const unstaged = await git([\"diff\"], cwd);\n return [staged, unstaged].filter(Boolean).join(\"\\n\");\n } catch {\n return \"\";\n }\n}\n\n/**\n * Get a compact summary of staged changes (--stat format):\n * lists every changed file with insertion/deletion counts.\n * Always small regardless of diff size β€” used to give the AI the\n * complete picture of what changed even when the full diff is truncated.\n */\nexport async function getWorkingDiffStat(cwd = process.cwd()): Promise<string> {\n try {\n const staged = await git([\"diff\", \"--cached\", \"--stat\"], cwd);\n const unstaged = await git([\"diff\", \"--stat\"], cwd);\n return [staged, unstaged].filter(Boolean).join(\"\\n\");\n } catch {\n return \"\";\n }\n}\n\n/**\n * Auto-detect the most likely base branch for the current feature branch.\n *\n * Strategy:\n * 1. Upstream tracking branch set via `git push -u` β€” strongest signal.\n * Ignored if it points to the branch's own remote ref (origin/current).\n * 2. Scan ALL remote tracking branches and count how many commits HEAD has\n * that each candidate doesn't (`git rev-list --count <ref>..HEAD`).\n * The branch with the FEWEST such commits is the closest ancestor β€”\n * i.e. the branch this one was forked from.\n *\n * Example: main β†’ feature1 (3 commits) β†’ feature2 (2 commits)\n * - origin/main..HEAD = 5 (feature1's 3 + feature2's 2)\n * - origin/feature1..HEAD = 2 (just feature2's commits)\n * β†’ feature1 wins βœ“\n *\n * Falls back to \"main\" if nothing can be determined.\n */\nexport async function detectBaseBranch(cwd = process.cwd()): Promise<string> {\n const current = await git([\"rev-parse\", \"--abbrev-ref\", \"HEAD\"], cwd).catch(() => \"\");\n\n // 1. Explicit upstream tracking branch β€” only trust it if it differs from\n // the current branch (guards against \"origin/feature2\" β†’ \"feature2\").\n try {\n const upstream = await git([\"rev-parse\", \"--abbrev-ref\", \"@{upstream}\"], cwd);\n const branch = upstream.replace(/^[^/]+\\//, \"\").trim();\n if (branch && branch !== current) return branch;\n } catch {\n // No upstream set β€” fall through\n }\n\n // 2. Scan all remote tracking branches (origin/*), compute how many commits\n // HEAD has that each remote ref doesn't. Closest ancestor wins.\n try {\n const remoteOut = await git(\n [\"branch\", \"-r\", \"--format=%(refname:short)\"],\n cwd\n );\n\n const remoteRefs = remoteOut\n .split(\"\\n\")\n .map((b) => b.trim())\n .filter((b) => b && !b.endsWith(\"/HEAD\")); // skip \"origin/HEAD\" pointer\n\n if (remoteRefs.length === 0) return \"main\";\n\n // Count commits ahead of every remote ref in parallel for speed\n const results = await Promise.all(\n remoteRefs.map(async (ref) => {\n const name = ref.replace(/^[^/]+\\//, \"\"); // \"origin/feature1\" β†’ \"feature1\"\n if (name === current) return null; // skip own remote tracking ref\n try {\n const out = await git([\"rev-list\", \"--count\", `${ref}..HEAD`], cwd);\n return { branch: name, ahead: parseInt(out.trim(), 10) || 0 };\n } catch {\n return null;\n }\n })\n );\n\n const counts = results.filter((r): r is { branch: string; ahead: number } => r !== null);\n if (counts.length === 0) return \"main\";\n\n // Sort by fewest commits ahead β€” that's the closest ancestor.\n // Tiebreak: prefer default branch names so \"main\" beats an equally-close\n // unrelated branch that happens to have 0 commits.\n const defaults = new Set([\"main\", \"master\", \"develop\", \"dev\", \"staging\"]);\n counts.sort((a, b) => {\n if (a.ahead !== b.ahead) return a.ahead - b.ahead;\n // Same distance β€” prefer default branches\n const aDefault = defaults.has(a.branch) ? 0 : 1;\n const bDefault = defaults.has(b.branch) ? 0 : 1;\n return aDefault - bDefault;\n });\n\n return counts[0]!.branch;\n } catch {\n return \"main\";\n }\n}\n\n/**\n * Get the one-line commit log for commits on HEAD that are not in baseBranch.\n * Used to give AI context about what this branch adds.\n */\nexport async function getBranchCommits(\n cwd = process.cwd(),\n baseBranch = \"main\"\n): Promise<string[]> {\n try {\n const out = await git(\n [\"log\", \"--oneline\", \"--no-decorate\", `${baseBranch}..HEAD`],\n cwd\n );\n return out.split(\"\\n\").map((l) => l.trim()).filter(Boolean);\n } catch {\n return [];\n }\n}\n\n/**\n * Get the unified diff of all changes between baseBranch and HEAD.\n * This is what the PR reviewer would see β€” all additions across all commits.\n */\nexport async function getBranchDiff(\n cwd = process.cwd(),\n baseBranch = \"main\"\n): Promise<string> {\n try {\n // Three-dot diff: all changes introduced by this branch vs. the merge base\n return await git([\"diff\", `${baseBranch}...HEAD`], cwd);\n } catch {\n return \"\";\n }\n}\n\n/**\n * Get the --stat summary of all changes between baseBranch and HEAD.\n * Lists every file changed with insertion/deletion counts.\n * Used alongside a truncated diff so the AI sees the full file list even\n * when the detailed patch is cut off.\n */\nexport async function getBranchStat(\n cwd = process.cwd(),\n baseBranch = \"main\"\n): Promise<string> {\n try {\n return await git([\"diff\", \"--stat\", `${baseBranch}...HEAD`], cwd);\n } catch {\n return \"\";\n }\n}\n\n/**\n * Read file content as a string. Returns empty string if file doesn't exist.\n */\nexport async function readRepoFile(\n relativePath: string,\n cwd = process.cwd()\n): Promise<string | undefined> {\n try {\n const abs = resolve(join(cwd, relativePath));\n const { readFile } = await import(\"node:fs/promises\");\n return await readFile(abs, \"utf-8\");\n } catch {\n return undefined;\n }\n}\n\n/**\n * Check whether a branch exists on the remote (origin).\n * Uses git ls-remote which does not require a full fetch.\n */\nexport async function branchExistsOnRemote(\n branchName: string,\n cwd = process.cwd()\n): Promise<boolean> {\n try {\n const out = await git(\n [\"ls-remote\", \"--heads\", \"origin\", `refs/heads/${branchName}`],\n cwd\n );\n return out.trim().length > 0;\n } catch {\n return false;\n }\n}\n\n/**\n * Returns true if the working tree has uncommitted changes (staged or unstaged).\n */\nexport async function isWorkingTreeDirty(cwd = process.cwd()): Promise<boolean> {\n try {\n const out = await git([\"status\", \"--porcelain\"], cwd);\n return out.trim().length > 0;\n } catch {\n return false;\n }\n}\n\n// ─── Context helpers for `gitx ask` ──────────────────────────────────────────\n\n/**\n * Returns `git status --short` output for use in AI context.\n * Returns an empty string when the working tree is clean or git fails.\n */\nexport async function getGitStatus(cwd = process.cwd()): Promise<string> {\n try {\n return await git([\"status\", \"--short\"], cwd);\n } catch {\n return \"\";\n }\n}\n\n/**\n * Returns the last `count` commits as one-line summaries (hash + subject).\n * Defaults to the last 10 commits.\n */\nexport async function getRecentCommits(cwd = process.cwd(), count = 10): Promise<string[]> {\n try {\n const out = await git([\"log\", \"--oneline\", \"--no-decorate\", `-${count}`], cwd);\n return out.split(\"\\n\").map((l) => l.trim()).filter(Boolean);\n } catch {\n return [];\n }\n}\n\n/**\n * Returns the list of git stashes as `stash@{0}: ...` strings.\n * Returns an empty array when there are no stashes or git fails.\n */\nexport async function getStashList(cwd = process.cwd()): Promise<string[]> {\n try {\n const out = await git([\"stash\", \"list\"], cwd);\n return out.split(\"\\n\").map((l) => l.trim()).filter(Boolean);\n } catch {\n return [];\n }\n}\n"]}
@@ -37,5 +37,5 @@ export interface FixCommentsResult {
37
37
  * Fetch PR review comments, ask AI to suggest fixes, apply them, and
38
38
  * commit + push the changes.
39
39
  */
40
- export declare function runFixCommentsWorkflow(gitx: Gitx, prNumber: number, dryRun?: boolean): Promise<FixCommentsResult>;
40
+ export declare function runFixCommentsWorkflow(gitx: Gitx, prNumber: number, dryRun?: boolean, noCommit?: boolean): Promise<FixCommentsResult>;
41
41
  //# sourceMappingURL=pr.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"pr.d.ts","sourceRoot":"","sources":["../../src/workflows/pr.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,KAAK,EAAE,WAAW,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AA4B5E,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,WAAW,CAAC;IAChB,QAAQ,EAAE,kBAAkB,EAAE,CAAC;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,OAAO,gBAAgB,EAAE,wBAAwB,CAAC;IAC3D,gEAAgE;IAChE,YAAY,EAAE,OAAO,CAAC;IACtB,uFAAuF;IACvF,cAAc,EAAE,QAAQ,GAAG,gBAAgB,GAAG,MAAM,CAAC;CACtD;AA6DD;;;;GAIG;AACH,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,IAAI,EACV,QAAQ,EAAE,MAAM,EAChB,WAAW,UAAO,GACjB,OAAO,CAAC,YAAY,CAAC,CAiJvB;AAID,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,WAAW,CAAC;IAChB,QAAQ,EAAE,kBAAkB,EAAE,CAAC;IAC/B,YAAY,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACzD,YAAY,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACvD;AAED;;;GAGG;AACH,wBAAsB,sBAAsB,CAC1C,IAAI,EAAE,IAAI,EACV,QAAQ,EAAE,MAAM,EAChB,MAAM,UAAQ,GACb,OAAO,CAAC,iBAAiB,CAAC,CA0F5B"}
1
+ {"version":3,"file":"pr.d.ts","sourceRoot":"","sources":["../../src/workflows/pr.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,KAAK,EAAE,WAAW,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AA4B5E,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,WAAW,CAAC;IAChB,QAAQ,EAAE,kBAAkB,EAAE,CAAC;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,OAAO,gBAAgB,EAAE,wBAAwB,CAAC;IAC3D,gEAAgE;IAChE,YAAY,EAAE,OAAO,CAAC;IACtB,uFAAuF;IACvF,cAAc,EAAE,QAAQ,GAAG,gBAAgB,GAAG,MAAM,CAAC;CACtD;AA6DD;;;;GAIG;AACH,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,IAAI,EACV,QAAQ,EAAE,MAAM,EAChB,WAAW,UAAO,GACjB,OAAO,CAAC,YAAY,CAAC,CAoJvB;AAID,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,WAAW,CAAC;IAChB,QAAQ,EAAE,kBAAkB,EAAE,CAAC;IAC/B,YAAY,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACzD,YAAY,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACvD;AAED;;;GAGG;AACH,wBAAsB,sBAAsB,CAC1C,IAAI,EAAE,IAAI,EACV,QAAQ,EAAE,MAAM,EAChB,MAAM,UAAQ,EACd,QAAQ,UAAQ,GACf,OAAO,CAAC,iBAAiB,CAAC,CA8F5B"}
@@ -96,8 +96,10 @@ export async function runReviewWorkflow(gitx, prNumber, postComment = true) {
96
96
  const ctxSpinner = ora("Building codebase context from changed files…").start();
97
97
  const allTracked = await listTrackedFiles(cwd);
98
98
  const allChangedPaths = parseChangedPathsFromDiff(diff);
99
- // Filter out lockfiles / generated files β€” they waste tokens and add no review value
100
- const changedPaths = allChangedPaths.filter(isReviewableFile).slice(0, 8);
99
+ // Filter out lockfiles / generated files β€” they waste tokens and add no review value.
100
+ // No file count cap β€” per-file budgets in buildSeniorReviewPrompt already control
101
+ // total size. Dropping files arbitrarily is worse than sending all of them.
102
+ const changedPaths = allChangedPaths.filter(isReviewableFile);
101
103
  const skippedCount = allChangedPaths.length - changedPaths.length;
102
104
  // Read full content of changed files
103
105
  const changedFiles = {};
@@ -107,7 +109,8 @@ export async function runReviewWorkflow(gitx, prNumber, postComment = true) {
107
109
  changedFiles[p] = content;
108
110
  }
109
111
  // Read supporting context files (imported by the changed files)
110
- const ctxPaths = await findContextFiles(changedPaths, allTracked, cwd, 5);
112
+ // 10 context files gives the AI a solid picture of the codebase structure
113
+ const ctxPaths = await findContextFiles(changedPaths, allTracked, cwd, 10);
111
114
  const contextFiles = {};
112
115
  for (const p of ctxPaths) {
113
116
  const content = await readRepoFile(p, cwd);
@@ -208,7 +211,7 @@ export async function runReviewWorkflow(gitx, prNumber, postComment = true) {
208
211
  * Fetch PR review comments, ask AI to suggest fixes, apply them, and
209
212
  * commit + push the changes.
210
213
  */
211
- export async function runFixCommentsWorkflow(gitx, prNumber, dryRun = false) {
214
+ export async function runFixCommentsWorkflow(gitx, prNumber, dryRun = false, noCommit = false) {
212
215
  const { applyUnifiedDiff, stageAll, hasStagedChanges, commitChanges } = await import("../utils/gitOps.js");
213
216
  const cwd = gitx.cwd;
214
217
  const ctx = await gitx.getRepoContext();
@@ -269,7 +272,7 @@ export async function runFixCommentsWorkflow(gitx, prNumber, dryRun = false) {
269
272
  });
270
273
  }
271
274
  }
272
- if (!dryRun && appliedFixes.length > 0) {
275
+ if (!dryRun && !noCommit && appliedFixes.length > 0) {
273
276
  await stageAll(cwd);
274
277
  if (await hasStagedChanges(cwd)) {
275
278
  const msg = `fix: address PR #${prNumber} review comments\n\n${appliedFixes.map((f) => `- ${f.path}: ${f.rationale}`).join("\n")}`;
@@ -280,6 +283,9 @@ export async function runFixCommentsWorkflow(gitx, prNumber, dryRun = false) {
280
283
  await provider.addPRComment(ctx.repoSlug, prNumber, `## πŸ€– Auto-fixes applied (gitx)\n\n${fixSummary}\n\nCommit: \`${sha.slice(0, 8)}\``);
281
284
  }
282
285
  }
286
+ if (!dryRun && noCommit && appliedFixes.length > 0) {
287
+ logger.info("\nπŸ’‘ Fixes applied to working tree. Review the changes and commit when ready.");
288
+ }
283
289
  return { pr, comments, appliedFixes, skippedFixes };
284
290
  }
285
291
  //# sourceMappingURL=pr.js.map