Skip to content
Merged
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
6 changes: 3 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@ jobs:
quality:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1

- uses: actions/setup-node@v4
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: 'lts/*'
node-version: '22'
cache: npm

- run: npm ci
Expand Down
20 changes: 10 additions & 10 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@ jobs:
outputs:
release_id: ${{ steps.create.outputs.id }}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1

- name: Create draft release
id: create
uses: softprops/action-gh-release@v2
uses: softprops/action-gh-release@3bb12739c298aeb8a4eeaf626c5b8d85266b0e65 # v2
with:
draft: true
generate_release_notes: true
Expand All @@ -27,18 +27,18 @@ jobs:
needs: create-release
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1

- uses: actions/setup-node@v4
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: 'lts/*'
node-version: '22'
cache: npm

- run: npm ci
- run: npm run build -- --publish never

- name: Upload artifacts
uses: softprops/action-gh-release@v2
uses: softprops/action-gh-release@3bb12739c298aeb8a4eeaf626c5b8d85266b0e65 # v2
with:
tag_name: ${{ github.ref_name }}
files: |
Expand All @@ -50,11 +50,11 @@ jobs:
needs: create-release
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1

- uses: actions/setup-node@v4
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: 'lts/*'
node-version: '22'
cache: npm

- name: Import code signing certificate
Expand Down Expand Up @@ -98,7 +98,7 @@ jobs:
run: npm run build -- --universal --publish never

- name: Upload artifacts
uses: softprops/action-gh-release@v2
uses: softprops/action-gh-release@3bb12739c298aeb8a4eeaf626c5b8d85266b0e65 # v2
with:
tag_name: ${{ github.ref_name }}
files: release/*.dmg
Expand Down
21 changes: 21 additions & 0 deletions .husky/commit-msg
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#!/bin/sh
commit_msg=$(cat "$1")

# Allow merge commits, revert commits, and fixup/squash commits
if echo "$commit_msg" | grep -qE "^(Merge|Revert|fixup!|squash!)"; then
exit 0
fi

# Enforce conventional commit format: type(scope): message
if ! echo "$commit_msg" | grep -qE "^(feat|fix|docs|style|refactor|perf|test|build|ci|chore|revert)(\(.+\))?: .{1,}"; then
echo ""
echo "Invalid commit message format."
echo "Expected: type(scope): description"
echo "Examples:"
echo " feat(coordinator): add MCP status indicator"
echo " fix(terminal): restore PTY preservation on reload"
echo " chore: update dependencies"
echo ""
echo "Valid types: feat fix docs style refactor perf test build ci chore revert"
exit 1
fi
15 changes: 15 additions & 0 deletions .husky/pre-commit
Original file line number Diff line number Diff line change
@@ -1,3 +1,18 @@
#!/bin/sh
npx lint-staged
npm run check

# Verify package-lock.json is committed and in sync with package.json
if git diff --cached --name-only | grep -q "package\.json$"; then
if ! git diff --cached --name-only | grep -q "package-lock\.json$"; then
echo "Error: package.json changed without updating package-lock.json"
echo "Run 'npm install' to update the lockfile"
exit 1
fi
fi

# Ensure package-lock.json is not gitignored (supply chain: lockfile must be tracked)
if git check-ignore -q package-lock.json 2>/dev/null; then
echo "Error: package-lock.json must not be gitignored — it provides integrity hashes"
exit 1
fi
9 changes: 9 additions & 0 deletions .husky/pre-push
Original file line number Diff line number Diff line change
@@ -1,2 +1,11 @@
#!/bin/sh
npm run check

echo "Running tests before push..."
npm test
if [ $? -ne 0 ]; then
echo ""
echo "Tests failed. Fix failing tests before pushing."
echo "Run 'npm test' to see details."
exit 1
fi
38 changes: 1 addition & 37 deletions electron/ipc/agents.test.ts
Original file line number Diff line number Diff line change
@@ -1,41 +1,5 @@
import { describe, expect, it } from 'vitest';
import { getMcpConfigArgs, getSkipPermissionsArgs } from './agents.js';

describe('getMcpConfigArgs', () => {
it('returns flag + path for claude', () => {
expect(getMcpConfigArgs('claude', '/tmp/config.json')).toEqual([
'--mcp-config',
'/tmp/config.json',
]);
});

it('returns flag + path for codex', () => {
expect(getMcpConfigArgs('codex', '/tmp/config.json')).toEqual(['--config', '/tmp/config.json']);
});

it('returns empty for gemini', () => {
expect(getMcpConfigArgs('gemini', '/tmp/config.json')).toEqual([]);
});

it('returns empty for opencode', () => {
expect(getMcpConfigArgs('opencode', '/tmp/config.json')).toEqual([]);
});

it('returns empty for copilot', () => {
expect(getMcpConfigArgs('copilot', '/tmp/config.json')).toEqual([]);
});

it('handles path-qualified claude command', () => {
expect(getMcpConfigArgs('/usr/local/bin/claude', '/tmp/config.json')).toEqual([
'--mcp-config',
'/tmp/config.json',
]);
});

it('handles unknown agent', () => {
expect(getMcpConfigArgs('unknown-agent', '/tmp/config.json')).toEqual([]);
});
});
import { getSkipPermissionsArgs } from './agents.js';

describe('getSkipPermissionsArgs', () => {
it('returns a copy of default skip-permission args', () => {
Expand Down
10 changes: 0 additions & 10 deletions electron/ipc/agents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ interface AgentDef {
description: string;
available?: boolean;
prompt_ready_delay_ms?: number;
mcp_config_flag?: string; // CLI flag to pass MCP config file path; omit if agent doesn't support it
}

const DEFAULT_AGENTS: AgentDef[] = [
Expand All @@ -26,7 +25,6 @@ const DEFAULT_AGENTS: AgentDef[] = [
resume_args: ['--continue'],
skip_permissions_args: ['--dangerously-skip-permissions'],
description: "Anthropic's Claude Code CLI agent",
mcp_config_flag: '--mcp-config',
},
{
id: 'codex',
Expand All @@ -36,7 +34,6 @@ const DEFAULT_AGENTS: AgentDef[] = [
resume_args: ['resume', '--last'],
skip_permissions_args: ['--dangerously-bypass-approvals-and-sandbox'],
description: "OpenAI's Codex CLI agent",
mcp_config_flag: '--config',
},
{
id: 'gemini',
Expand Down Expand Up @@ -91,13 +88,6 @@ export function getSkipPermissionsArgs(command: string): string[] {
return agent ? [...agent.skip_permissions_args] : [];
}

export function getMcpConfigArgs(command: string, configPath: string): string[] {
const base = path.basename(command);
const agent = DEFAULT_AGENTS.find((a) => a.command === base || a.command === command);
if (!agent?.mcp_config_flag) return [];
return [agent.mcp_config_flag, configPath];
}

export async function listAgents(): Promise<AgentDef[]> {
const now = Date.now();
if (cachedAgents && now - cacheTime < AGENT_CACHE_TTL) {
Expand Down
15 changes: 11 additions & 4 deletions electron/ipc/git.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1424,10 +1424,16 @@ export async function getWorktreeStatus(
if (!fs.existsSync(worktreePath)) {
return { has_committed_changes: false, has_uncommitted_changes: false, current_branch: null };
}
const { stdout: statusOut } = await exec('git', ['status', '--porcelain'], {
cwd: worktreePath,
maxBuffer: MAX_BUFFER,
});
let statusOut: string;
try {
({ stdout: statusOut } = await exec('git', ['status', '--porcelain'], {
cwd: worktreePath,
maxBuffer: MAX_BUFFER,
}));
} catch {
// Worktree removed between existsSync and exec (race condition)
return { has_committed_changes: false, has_uncommitted_changes: false, current_branch: null };
}
const hasUncommittedChanges = statusOut.trim().length > 0;

const currentBranch = await getCurrentBranchName(worktreePath).catch(() => null);
Expand Down Expand Up @@ -1883,6 +1889,7 @@ export async function getBranchCommits(
baseBranch?: string,
recentFallback?: number,
): Promise<CommitInfo[]> {
if (!fs.existsSync(worktreePath)) return [];
const mergeBase = await detectMergeBase(worktreePath, 'HEAD', baseBranch);
try {
const { stdout } = await exec(
Expand Down
Loading
Loading