mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-03-20 23:13:07 +00:00
Feature: worktree view customization and stability fixes (#805)
* Changes from feature/worktree-view-customization * Feature: Git sync, set-tracking, and push divergence handling (#796) * Add quick-add feature with improved workflows (#802) * Changes from feature/quick-add * feat: Clarify system prompt and improve error handling across services. Address PR Feedback * feat: Improve PR description parsing and refactor event handling * feat: Add context options to pipeline orchestrator initialization * fix: Deduplicate React and handle CJS interop for use-sync-external-store Resolve "Cannot read properties of null (reading 'useState')" errors by deduplicating React/react-dom and ensuring use-sync-external-store is bundled together with React to prevent CJS packages from resolving to different React instances. * Changes from feature/worktree-view-customization * refactor: Remove unused worktree swap and highlight props * refactor: Consolidate feature completion logic and improve thinking level defaults * feat: Increase max turn limit to 10000 - Update DEFAULT_MAX_TURNS from 1000 to 10000 in settings-helpers.ts and agent-executor.ts - Update MAX_ALLOWED_TURNS from 2000 to 10000 in settings-helpers.ts - Update UI clamping logic from 2000 to 10000 in app-store.ts - Update fallback values from 1000 to 10000 in use-settings-sync.ts - Update default value from 1000 to 10000 in DEFAULT_GLOBAL_SETTINGS - Update documentation to reflect new range: 1-10000 Allows agents to perform up to 10000 turns for complex feature execution. Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com> * feat: Add model resolution, improve session handling, and enhance UI stability * refactor: Remove unused sync and tracking branch props from worktree components * feat: Add PR number update functionality to worktrees. Address pr feedback * feat: Optimize Gemini CLI startup and add tool result tracking * refactor: Improve error handling and simplify worktree task cleanup --------- Co-authored-by: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -44,7 +44,11 @@ export function createFeaturesRoutes(
|
||||
validatePathParams('projectPath'),
|
||||
createCreateHandler(featureLoader, events)
|
||||
);
|
||||
router.post('/update', validatePathParams('projectPath'), createUpdateHandler(featureLoader));
|
||||
router.post(
|
||||
'/update',
|
||||
validatePathParams('projectPath'),
|
||||
createUpdateHandler(featureLoader, events)
|
||||
);
|
||||
router.post(
|
||||
'/bulk-update',
|
||||
validatePathParams('projectPath'),
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
import type { Request, Response } from 'express';
|
||||
import { FeatureLoader } from '../../../services/feature-loader.js';
|
||||
import type { Feature, FeatureStatus } from '@automaker/types';
|
||||
import type { EventEmitter } from '../../../lib/events.js';
|
||||
import { getErrorMessage, logError } from '../common.js';
|
||||
import { createLogger } from '@automaker/utils';
|
||||
|
||||
@@ -13,7 +14,7 @@ const logger = createLogger('features/update');
|
||||
// Statuses that should trigger syncing to app_spec.txt
|
||||
const SYNC_TRIGGER_STATUSES: FeatureStatus[] = ['verified', 'completed'];
|
||||
|
||||
export function createUpdateHandler(featureLoader: FeatureLoader) {
|
||||
export function createUpdateHandler(featureLoader: FeatureLoader, events?: EventEmitter) {
|
||||
return async (req: Request, res: Response): Promise<void> => {
|
||||
try {
|
||||
const {
|
||||
@@ -54,8 +55,18 @@ export function createUpdateHandler(featureLoader: FeatureLoader) {
|
||||
preEnhancementDescription
|
||||
);
|
||||
|
||||
// Trigger sync to app_spec.txt when status changes to verified or completed
|
||||
// Emit completion event and sync to app_spec.txt when status transitions to verified/completed
|
||||
if (newStatus && SYNC_TRIGGER_STATUSES.includes(newStatus) && previousStatus !== newStatus) {
|
||||
events?.emit('feature:completed', {
|
||||
featureId,
|
||||
featureName: updated.title,
|
||||
projectPath,
|
||||
passes: true,
|
||||
message:
|
||||
newStatus === 'verified' ? 'Feature verified manually' : 'Feature completed manually',
|
||||
executionMode: 'manual',
|
||||
});
|
||||
|
||||
try {
|
||||
const synced = await featureLoader.syncFeatureToAppSpec(projectPath, updated);
|
||||
if (synced) {
|
||||
|
||||
@@ -69,6 +69,7 @@ import { createStageFilesHandler } from './routes/stage-files.js';
|
||||
import { createCheckChangesHandler } from './routes/check-changes.js';
|
||||
import { createSetTrackingHandler } from './routes/set-tracking.js';
|
||||
import { createSyncHandler } from './routes/sync.js';
|
||||
import { createUpdatePRNumberHandler } from './routes/update-pr-number.js';
|
||||
import type { SettingsService } from '../../services/settings-service.js';
|
||||
|
||||
export function createWorktreeRoutes(
|
||||
@@ -96,6 +97,12 @@ export function createWorktreeRoutes(
|
||||
router.post('/delete', validatePathParams('projectPath', 'worktreePath'), createDeleteHandler());
|
||||
router.post('/create-pr', createCreatePRHandler());
|
||||
router.post('/pr-info', createPRInfoHandler());
|
||||
router.post(
|
||||
'/update-pr-number',
|
||||
validatePathParams('worktreePath', 'projectPath?'),
|
||||
requireValidWorktree,
|
||||
createUpdatePRNumberHandler()
|
||||
);
|
||||
router.post(
|
||||
'/commit',
|
||||
validatePathParams('worktreePath'),
|
||||
|
||||
163
apps/server/src/routes/worktree/routes/update-pr-number.ts
Normal file
163
apps/server/src/routes/worktree/routes/update-pr-number.ts
Normal file
@@ -0,0 +1,163 @@
|
||||
/**
|
||||
* POST /update-pr-number endpoint - Update the tracked PR number for a worktree
|
||||
*
|
||||
* Allows users to manually change which PR number is tracked for a worktree branch.
|
||||
* Fetches updated PR info from GitHub when available, or updates metadata with the
|
||||
* provided number only if GitHub CLI is unavailable.
|
||||
*/
|
||||
|
||||
import type { Request, Response } from 'express';
|
||||
import { getErrorMessage, logError, execAsync, execEnv, isGhCliAvailable } from '../common.js';
|
||||
import { updateWorktreePRInfo } from '../../../lib/worktree-metadata.js';
|
||||
import { createLogger } from '@automaker/utils';
|
||||
import { validatePRState } from '@automaker/types';
|
||||
|
||||
const logger = createLogger('UpdatePRNumber');
|
||||
|
||||
export function createUpdatePRNumberHandler() {
|
||||
return async (req: Request, res: Response): Promise<void> => {
|
||||
try {
|
||||
const { worktreePath, projectPath, prNumber } = req.body as {
|
||||
worktreePath: string;
|
||||
projectPath?: string;
|
||||
prNumber: number;
|
||||
};
|
||||
|
||||
if (!worktreePath) {
|
||||
res.status(400).json({ success: false, error: 'worktreePath required' });
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
!prNumber ||
|
||||
typeof prNumber !== 'number' ||
|
||||
prNumber <= 0 ||
|
||||
!Number.isInteger(prNumber)
|
||||
) {
|
||||
res.status(400).json({ success: false, error: 'prNumber must be a positive integer' });
|
||||
return;
|
||||
}
|
||||
|
||||
const effectiveProjectPath = projectPath || worktreePath;
|
||||
|
||||
// Get current branch name
|
||||
const { stdout: branchOutput } = await execAsync('git rev-parse --abbrev-ref HEAD', {
|
||||
cwd: worktreePath,
|
||||
env: execEnv,
|
||||
});
|
||||
const branchName = branchOutput.trim();
|
||||
|
||||
if (!branchName || branchName === 'HEAD') {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
error: 'Cannot update PR number in detached HEAD state',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Try to fetch PR info from GitHub for the given PR number
|
||||
const ghCliAvailable = await isGhCliAvailable();
|
||||
|
||||
if (ghCliAvailable) {
|
||||
try {
|
||||
// Detect repository for gh CLI
|
||||
let repoFlag = '';
|
||||
try {
|
||||
const { stdout: remotes } = await execAsync('git remote -v', {
|
||||
cwd: worktreePath,
|
||||
env: execEnv,
|
||||
});
|
||||
const lines = remotes.split(/\r?\n/);
|
||||
let upstreamRepo: string | null = null;
|
||||
let originOwner: string | null = null;
|
||||
let originRepo: string | null = null;
|
||||
|
||||
for (const line of lines) {
|
||||
const match =
|
||||
line.match(/^(\w+)\s+.*[:/]([^/]+)\/([^/\s]+?)(?:\.git)?\s+\(fetch\)/) ||
|
||||
line.match(/^(\w+)\s+git@[^:]+:([^/]+)\/([^\s]+?)(?:\.git)?\s+\(fetch\)/) ||
|
||||
line.match(/^(\w+)\s+https?:\/\/[^/]+\/([^/]+)\/([^\s]+?)(?:\.git)?\s+\(fetch\)/);
|
||||
|
||||
if (match) {
|
||||
const [, remoteName, owner, repo] = match;
|
||||
if (remoteName === 'upstream') {
|
||||
upstreamRepo = `${owner}/${repo}`;
|
||||
} else if (remoteName === 'origin') {
|
||||
originOwner = owner;
|
||||
originRepo = repo;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const targetRepo =
|
||||
upstreamRepo || (originOwner && originRepo ? `${originOwner}/${originRepo}` : null);
|
||||
if (targetRepo) {
|
||||
repoFlag = ` --repo "${targetRepo}"`;
|
||||
}
|
||||
} catch {
|
||||
// Ignore remote parsing errors
|
||||
}
|
||||
|
||||
// Fetch PR info from GitHub using the PR number
|
||||
const viewCmd = `gh pr view ${prNumber}${repoFlag} --json number,title,url,state,createdAt`;
|
||||
const { stdout: prOutput } = await execAsync(viewCmd, {
|
||||
cwd: worktreePath,
|
||||
env: execEnv,
|
||||
});
|
||||
|
||||
const prData = JSON.parse(prOutput);
|
||||
|
||||
const prInfo = {
|
||||
number: prData.number,
|
||||
url: prData.url,
|
||||
title: prData.title,
|
||||
state: validatePRState(prData.state),
|
||||
createdAt: prData.createdAt || new Date().toISOString(),
|
||||
};
|
||||
|
||||
await updateWorktreePRInfo(effectiveProjectPath, branchName, prInfo);
|
||||
|
||||
logger.info(`Updated PR tracking to #${prNumber} for branch ${branchName}`);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
result: {
|
||||
branch: branchName,
|
||||
prInfo,
|
||||
},
|
||||
});
|
||||
return;
|
||||
} catch (error) {
|
||||
logger.warn(`Failed to fetch PR #${prNumber} from GitHub:`, error);
|
||||
// Fall through to simple update below
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback: update with just the number, preserving existing PR info structure
|
||||
// or creating minimal info if no GitHub data available
|
||||
const prInfo = {
|
||||
number: prNumber,
|
||||
url: `https://github.com/pulls/${prNumber}`,
|
||||
title: `PR #${prNumber}`,
|
||||
state: validatePRState('OPEN'),
|
||||
createdAt: new Date().toISOString(),
|
||||
};
|
||||
|
||||
await updateWorktreePRInfo(effectiveProjectPath, branchName, prInfo);
|
||||
|
||||
logger.info(`Updated PR tracking to #${prNumber} for branch ${branchName} (no GitHub data)`);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
result: {
|
||||
branch: branchName,
|
||||
prInfo,
|
||||
ghCliUnavailable: !ghCliAvailable,
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
logError(error, 'Update PR number failed');
|
||||
res.status(500).json({ success: false, error: getErrorMessage(error) });
|
||||
}
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user