Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 10 additions & 4 deletions src/tools/checkpoint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,11 +84,17 @@ ${dirty || "clean"}

if (commitResult === "no uncommitted changes") {
// Stage the checkpoint file too
run(`git add "${checkpointFile}"`);
const result = run(`${addCmd} && git commit -m "${commitMsg.replace(/"/g, '\\"')}" 2>&1`);
if (result.includes("commit failed") || result.includes("nothing to commit")) {
run(["add", checkpointFile]);
// Stage files based on mode
if (mode === "tracked") {
run(["add", "-u"]);
} else if (mode === "all") {
run(["add", "-A"]);
}
const result = run(["commit", "-m", commitMsg]);
if (result.includes("nothing to commit") || result.startsWith("[command failed")) {
// Rollback: unstage if commit failed
run("git reset HEAD 2>/dev/null");
run(["reset", "HEAD"]);
commitResult = `commit failed: ${result}`;
} else {
commitResult = result;
Expand Down
3 changes: 2 additions & 1 deletion src/tools/sequence-tasks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,8 @@ export function registerSequenceTasks(server: McpServer): void {
// For locality: infer directories from path-like tokens in task text
if (strategy === "locality") {
// Use git ls-files with a depth limit instead of find for performance
const gitFiles = run("git ls-files 2>/dev/null | head -1000");
const allGitFiles = run(["ls-files"]);
const gitFiles = allGitFiles.startsWith("[") ? "" : allGitFiles.split("\n").slice(0, 1000).join("\n");
const knownDirs = new Set<string>();
for (const f of gitFiles.split("\n").filter(Boolean)) {
const parts = f.split("/");
Expand Down
23 changes: 18 additions & 5 deletions src/tools/session-handoff.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,16 @@ import { join } from "path";
import { run, getBranch, getRecentCommits, getStatus } from "../lib/git.js";
import { readIfExists, findWorkspaceDocs } from "../lib/files.js";
import { STATE_DIR, now } from "../lib/state.js";
import { execFileSync } from "child_process";

/** Check if a CLI tool is available */
function hasCommand(cmd: string): boolean {
const result = run(`command -v ${cmd} 2>/dev/null`);
return !!result && !result.startsWith("[command failed");
try {
execFileSync("which", [cmd], { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
return true;
} catch {
return false;
}
}

export function registerSessionHandoff(server: McpServer): void {
Expand Down Expand Up @@ -44,9 +49,17 @@ export function registerSessionHandoff(server: McpServer): void {

// Only try gh if it exists
if (hasCommand("gh")) {
const openPRs = run("gh pr list --state open --json number,title,headRefName 2>/dev/null || echo '[]'");
if (openPRs && openPRs !== "[]") {
sections.push(`## Open PRs\n\`\`\`json\n${openPRs}\n\`\`\``);
try {
const openPRs = execFileSync("gh", ["pr", "list", "--state", "open", "--json", "number,title,headRefName"], {
encoding: "utf-8",
timeout: 10000,
stdio: ["pipe", "pipe", "pipe"],
}).trim();
if (openPRs && openPRs !== "[]") {
sections.push(`## Open PRs\n\`\`\`json\n${openPRs}\n\`\`\``);
}
} catch {
// gh pr list failed, skip
}
}

Expand Down
33 changes: 19 additions & 14 deletions src/tools/sharpen-followup.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// CATEGORY 4: sharpen_followup — Follow-up Specificity
import { z } from "zod";
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { run } from "../lib/git.js";
import { run, getDiffFiles, getStagedFiles } from "../lib/git.js";
import { now } from "../lib/state.js";

/** Parse git porcelain output into deduplicated file paths, handling renames (R/C) */
Expand All @@ -26,19 +26,24 @@ function parsePortelainFiles(output: string): string[] {

/** Get recently changed files, safe for first commit / shallow clones */
function getRecentChangedFiles(): string[] {
// Try HEAD~1..HEAD, fall back to just staged, then unstaged
const commands = [
"git diff --name-only HEAD~1 HEAD 2>/dev/null",
"git diff --name-only --cached 2>/dev/null",
"git diff --name-only 2>/dev/null",
];
const results = new Set<string>();
for (const cmd of commands) {
const out = run(cmd);
if (out) out.split("\n").filter(Boolean).forEach((f) => results.add(f));
if (results.size > 0) break; // first successful source is enough
// Try HEAD~1..HEAD first
const committed = run(["diff", "--name-only", "HEAD~1", "HEAD"]);
if (committed && !committed.startsWith("[")) {
const files = committed.split("\n").filter(Boolean);
if (files.length > 0) return files;
}
return [...results];
// Fall back to staged
const staged = getStagedFiles();
if (staged && !staged.startsWith("[")) {
const files = staged.split("\n").filter(Boolean);
if (files.length > 0) return files;
}
// Fall back to unstaged
const unstaged = run(["diff", "--name-only"]);
if (unstaged && !unstaged.startsWith("[")) {
return unstaged.split("\n").filter(Boolean);
}
return [];
}

export function registerSharpenFollowup(server: McpServer): void {
Expand Down Expand Up @@ -87,7 +92,7 @@ export function registerSharpenFollowup(server: McpServer): void {
// Gather context to resolve ambiguity
const contextFiles: string[] = [...(previous_files ?? [])];
const recentChanged = getRecentChangedFiles();
const porcelainOutput = run("git status --porcelain 2>/dev/null");
const porcelainOutput = run(["status", "--porcelain"]);
const untrackedOrModified = parsePortelainFiles(porcelainOutput);

const allKnownFiles = [...new Set([...contextFiles, ...recentChanged, ...untrackedOrModified])].filter(Boolean);
Expand Down
7 changes: 4 additions & 3 deletions src/tools/what-changed.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { z } from "zod";
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { run, getBranch, getDiffStat } from "../lib/git.js";
import { run, getBranch, getDiffFiles, getDiffStat } from "../lib/git.js";

export function registerWhatChanged(server: McpServer): void {
server.tool(
Expand All @@ -12,8 +12,9 @@ export function registerWhatChanged(server: McpServer): void {
async ({ since }) => {
const ref = since || "HEAD~5";
const diffStat = getDiffStat(ref);
const diffFiles = run(`git diff ${ref} --name-only 2>/dev/null || git diff HEAD~3 --name-only`);
const log = run(`git log ${ref}..HEAD --oneline 2>/dev/null || git log -5 --oneline`);
const diffFiles = getDiffFiles(ref);
const logResult = run(["log", `${ref}..HEAD`, "--oneline"]);
const log = logResult.startsWith("[") ? run(["log", "-5", "--oneline"]) : logResult;
const branch = getBranch();

const fileList = diffFiles.split("\n").filter(Boolean);
Expand Down
Loading