Skip to content
Open
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
fc34077
refactor: generalize coding agent to behavior + business; part 1
AshishKumar4 Nov 7, 2025
de53b8e
refactor: agent behaviors, objectives generalized, abstracted + agent…
AshishKumar4 Nov 7, 2025
5685c7d
feat: finish most refactor and get it to build
AshishKumar4 Nov 9, 2025
2aea3c7
feat: add ai-based project type detection and workflow support
AshishKumar4 Nov 10, 2025
e8d07af
fix: template initialization
AshishKumar4 Nov 10, 2025
40ab40e
fix: wire up onConnect to coding agent
AshishKumar4 Nov 10, 2025
740bf1c
feat: improve GitHub Actions workflow reliability
AshishKumar4 Nov 10, 2025
a25e72f
refactor: improve type safety in state migration logic
AshishKumar4 Nov 10, 2025
bb09d92
fix: add optional chaining to prevent runtime errors in blueprint ren…
AshishKumar4 Nov 10, 2025
5e4ebb2
feat: general agent
AshishKumar4 Nov 11, 2025
1faaca1
refactor: reorganize project builder architecture + sandbox templatel…
AshishKumar4 Nov 11, 2025
f5a3be6
feat: add project mode selector with agentic behavior support
AshishKumar4 Nov 11, 2025
feca8ca
fix: files format
AshishKumar4 Nov 11, 2025
bb23e88
fix: ensure workspace directory exists before writing files
AshishKumar4 Nov 11, 2025
234860f
feat: replace template manager with ai template selector
AshishKumar4 Nov 11, 2025
97bc622
refactor: integrate conversation history and sync for agentic builder
AshishKumar4 Nov 12, 2025
060cc9e
fix: template import and state init
AshishKumar4 Nov 12, 2025
5deea6a
fix: template cache clear before import + init meta in behavior const…
AshishKumar4 Nov 12, 2025
bbf1979
fix: ui and convo state management
AshishKumar4 Nov 12, 2025
d45f368
fix: convo id uniqueness and improve message deduplication
AshishKumar4 Nov 12, 2025
868ba34
fix: ui auto focus, preview hiding and blueprints
AshishKumar4 Nov 12, 2025
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
65 changes: 27 additions & 38 deletions .github/workflows/claude-reviews.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ on:
pull_request_review_comment:
types: [created]

concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true

jobs:
comprehensive-review:
name: PR Description & Code Review
Expand All @@ -30,6 +34,29 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Delete Previous Claude Comments
run: |
echo "🧹 Deleting previous Claude comments from github-actions bot..."

# Get all comments from github-actions bot containing 'Claude'
CLAUDE_COMMENTS=$(gh api repos/${{ github.repository }}/issues/${{ github.event.pull_request.number }}/comments \
--jq '[.[] | select(.user.login == "github-actions[bot]") | select(.body | contains("Claude")) | .id]')

if [ "$CLAUDE_COMMENTS" = "[]" ] || [ -z "$CLAUDE_COMMENTS" ]; then
echo "No previous Claude comments found"
else
echo "Found Claude comments to delete:"
echo "$CLAUDE_COMMENTS" | jq -r '.[]' | while read comment_id; do
echo "Deleting comment $comment_id"
gh api repos/${{ github.repository }}/issues/comments/$comment_id -X DELETE || echo "Failed to delete comment $comment_id"
done
echo "✅ Deleted previous Claude comments"
fi
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
continue-on-error: true

- name: Detect Critical Paths
id: critical_paths
run: |
Expand Down Expand Up @@ -147,41 +174,3 @@ jobs:
--max-turns ${{ steps.critical_paths.outputs.is_critical == 'true' && '90' || '65' }}
--model claude-sonnet-4-5-20250929

- name: Intelligent Comment Cleanup
uses: anthropics/claude-code-action@v1
if: always()
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
prompt: |
Clean up stale bot comments on PR #${{ github.event.pull_request.number }}.

**Task:**
1. Fetch all comments on this PR
2. Identify bot comments (users ending in [bot]) that are stale/outdated:
- Old reviews superseded by newer ones
- Old PR description suggestions
- Previously collapsed/outdated markers
- Progress/status comments from previous workflow runs
3. Keep only the most recent comment per category per bot
4. DELETE all stale comments (do not collapse)

**Get all comments:**
```bash
gh api repos/${{ github.repository }}/issues/${{ github.event.pull_request.number }}/comments --jq '.[] | {id, user: .user.login, body, created_at}'
```

**Delete a comment:**
```bash
gh api repos/${{ github.repository }}/issues/comments/COMMENT_ID -X DELETE
```

Be intelligent:
- Preserve the newest useful comment in each category
- Delete everything else that's redundant or stale
- If unsure, keep the comment (conservative approach)

claude_args: |
--allowed-tools "Bash(gh api repos/*/issues/*/comments:*),Bash(gh api repos/*/issues/comments/*:*)"
--max-turns 8
--model claude-haiku-4-5-20251001
6 changes: 3 additions & 3 deletions commitlint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,10 @@ export default {
'type-empty': [2, 'never'],
'subject-empty': [2, 'never'],
'subject-full-stop': [2, 'never', '.'],
'header-max-length': [2, 'always', 100],
'header-max-length': [2, 'always', 150],
'body-leading-blank': [1, 'always'],
'body-max-line-length': [2, 'always', 100],
'body-max-line-length': [2, 'always', 200],
'footer-leading-blank': [1, 'always'],
'footer-max-line-length': [2, 'always', 100],
'footer-max-line-length': [2, 'always', 200],
},
};
4 changes: 3 additions & 1 deletion src/api-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,13 +138,15 @@ export type {
// Agent/Generator Types
export type {
Blueprint as BlueprintType,
PhasicBlueprint,
CodeReviewOutputType,
FileConceptType,
FileOutputType as GeneratedFile,
} from 'worker/agents/schemas';

export type {
CodeGenState
AgentState,
PhasicState
} from 'worker/agents/core/state';

export type {
Expand Down
13 changes: 9 additions & 4 deletions src/routes/chat/chat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import { ViewModeSwitch } from './components/view-mode-switch';
import { DebugPanel, type DebugMessage } from './components/debug-panel';
import { DeploymentControls } from './components/deployment-controls';
import { useChat, type FileType } from './hooks/use-chat';
import { type ModelConfigsData, type BlueprintType, SUPPORTED_IMAGE_MIME_TYPES } from '@/api-types';
import { type ModelConfigsData, type BlueprintType, type PhasicBlueprint, SUPPORTED_IMAGE_MIME_TYPES } from '@/api-types';
import { Copy } from './components/copy';
import { useFileContentStream } from './hooks/use-file-content-stream';
import { logger } from '@/utils/logger';
Expand All @@ -42,6 +42,9 @@ import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigge
import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle } from '@/components/ui/alert-dialog';
import { sendWebSocketMessage } from './utils/websocket-helpers';

const isPhasicBlueprint = (blueprint?: BlueprintType | null): blueprint is PhasicBlueprint =>
!!blueprint && 'implementationRoadmap' in blueprint;

export default function Chat() {
const { chatId: urlChatId } = useParams();

Expand Down Expand Up @@ -492,11 +495,13 @@ export default function Chat() {
const completedPhases = phaseTimeline.filter(p => p.status === 'completed').length;

// Get predicted phase count from blueprint, fallback to current phase count
const predictedPhaseCount = blueprint?.implementationRoadmap?.length || 0;
const predictedPhaseCount = isPhasicBlueprint(blueprint)
? blueprint.implementationRoadmap.length
: 0;
const totalPhases = Math.max(predictedPhaseCount, phaseTimeline.length, 1);

return [completedPhases, totalPhases];
}, [phaseTimeline, blueprint?.implementationRoadmap]);
}, [phaseTimeline, blueprint]);

if (import.meta.env.DEV) {
logger.debug({
Expand Down Expand Up @@ -1262,4 +1267,4 @@ export default function Chat() {
)}
</div>
);
}
}
47 changes: 26 additions & 21 deletions src/routes/chat/components/blueprint.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import type { BlueprintType } from '@/api-types';
import type { BlueprintType, PhasicBlueprint } from '@/api-types';
import clsx from 'clsx';
import { Markdown } from './messages';

const isPhasicBlueprint = (blueprint: BlueprintType): blueprint is PhasicBlueprint =>
'views' in blueprint;

export function Blueprint({
blueprint,
className,
Expand All @@ -11,6 +14,8 @@ export function Blueprint({
}) {
if (!blueprint) return null;

const phasicBlueprint = isPhasicBlueprint(blueprint) ? blueprint : null;

return (
<div className={clsx('w-full flex flex-col', className)} {...props}>
<div className="bg-accent p-6 rounded-t-xl flex items-center bg-graph-paper">
Expand Down Expand Up @@ -84,13 +89,13 @@ export function Blueprint({
</div>

{/* Views */}
{Array.isArray(blueprint.views) && blueprint.views.length > 0 && (
{phasicBlueprint && phasicBlueprint.views.length > 0 && (
<div>
<h3 className="text-sm font-medium mb-3 text-text-50/70 uppercase tracking-wider">
Views
</h3>
<div className="space-y-3">
{blueprint.views.map((view, index) => (
{phasicBlueprint.views.map((view, index) => (
<div key={`view-${index}`} className="space-y-1">
<h4 className="text-xs font-medium text-text-50/70">
{view.name}
Expand All @@ -105,41 +110,41 @@ export function Blueprint({
)}

{/* User Flow */}
{blueprint.userFlow && (
{phasicBlueprint?.userFlow && (
<div>
<h3 className="text-sm font-medium mb-3 text-text-50/70 uppercase tracking-wider">
User Flow
</h3>
<div className="space-y-4">
{blueprint.userFlow?.uiLayout && (
{phasicBlueprint.userFlow.uiLayout && (
<div>
<h4 className="text-xs font-medium mb-2 text-text-50/70">
UI Layout
</h4>
<Markdown className="text-sm text-text-50">
{blueprint.userFlow.uiLayout}
{phasicBlueprint.userFlow.uiLayout}
</Markdown>
</div>
)}

{blueprint.userFlow?.uiDesign && (
{phasicBlueprint.userFlow.uiDesign && (
<div>
<h4 className="text-xs font-medium mb-2 text-text-50/70">
UI Design
</h4>
<Markdown className="text-sm text-text-50">
{blueprint.userFlow.uiDesign}
{phasicBlueprint.userFlow.uiDesign}
</Markdown>
</div>
)}

{blueprint.userFlow?.userJourney && (
{phasicBlueprint.userFlow.userJourney && (
<div>
<h4 className="text-xs font-medium mb-2 text-text-50/70">
User Journey
</h4>
<Markdown className="text-sm text-text-50">
{blueprint.userFlow?.userJourney}
{phasicBlueprint.userFlow.userJourney}
</Markdown>
</div>
)}
Expand All @@ -148,25 +153,25 @@ export function Blueprint({
)}

{/* Data Flow */}
{(blueprint.dataFlow || blueprint.architecture?.dataFlow) && (
{phasicBlueprint && (phasicBlueprint.dataFlow || phasicBlueprint.architecture?.dataFlow) && (
<div>
<h3 className="text-sm font-medium mb-2 text-text-50/70 uppercase tracking-wider">
Data Flow
</h3>
<Markdown className="text-sm text-text-50">
{blueprint.dataFlow || blueprint.architecture?.dataFlow}
{phasicBlueprint.dataFlow || phasicBlueprint.architecture?.dataFlow}
</Markdown>
</div>
)}

{/* Implementation Roadmap */}
{Array.isArray(blueprint.implementationRoadmap) && blueprint.implementationRoadmap.length > 0 && (
{phasicBlueprint && phasicBlueprint.implementationRoadmap.length > 0 && (
<div>
<h3 className="text-sm font-medium mb-2 text-text-50/70 uppercase tracking-wider">
Implementation Roadmap
</h3>
<div className="space-y-3">
{blueprint.implementationRoadmap.map((roadmapItem, index) => (
{phasicBlueprint.implementationRoadmap.map((roadmapItem, index) => (
<div key={`roadmap-${index}`} className="space-y-1">
<h4 className="text-xs font-medium text-text-50/70">
Phase {index + 1}: {roadmapItem.phase}
Expand All @@ -181,26 +186,26 @@ export function Blueprint({
)}

{/* Initial Phase */}
{blueprint.initialPhase && (
{phasicBlueprint?.initialPhase && (
<div>
<h3 className="text-sm font-medium mb-2 text-text-50/70 uppercase tracking-wider">
Initial Phase
</h3>
<div className="space-y-3">
<div>
<h4 className="text-xs font-medium mb-2 text-text-50/70">
{blueprint.initialPhase.name}
{phasicBlueprint.initialPhase.name}
</h4>
<Markdown className="text-sm text-text-50 mb-3">
{blueprint.initialPhase.description}
{phasicBlueprint.initialPhase.description}
</Markdown>
{Array.isArray(blueprint.initialPhase.files) && blueprint.initialPhase.files.length > 0 && (
{Array.isArray(phasicBlueprint.initialPhase.files) && phasicBlueprint.initialPhase.files.length > 0 && (
<div>
<h5 className="text-xs font-medium mb-2 text-text-50/60">
Files to be created:
</h5>
<div className="space-y-2">
{blueprint.initialPhase.files.map((file, fileIndex) => (
{phasicBlueprint.initialPhase.files.map((file, fileIndex) => (
<div key={`initial-phase-file-${fileIndex}`} className="border-l-2 border-text/10 pl-3">
<div className="font-mono text-xs text-text-50/80">{file.path}</div>
<div className="text-xs text-text-50/60">{file.purpose}</div>
Expand All @@ -215,14 +220,14 @@ export function Blueprint({
)}

{/* Pitfalls */}
{Array.isArray(blueprint.pitfalls) && blueprint.pitfalls.length > 0 && (
{phasicBlueprint && phasicBlueprint.pitfalls.length > 0 && (
<div>
<h3 className="text-sm font-medium mb-2 text-text-50/70 uppercase tracking-wider">
Pitfalls
</h3>
<div className="prose prose-sm prose-invert">
<ul className="">
{blueprint.pitfalls?.map((pitfall, index) => (
{phasicBlueprint.pitfalls.map((pitfall, index) => (
<li key={`pitfall-${index}`} className="">
{pitfall}
</li>
Expand Down
11 changes: 7 additions & 4 deletions src/routes/chat/utils/handle-websocket-message.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { WebSocket } from 'partysocket';
import type { WebSocketMessage, BlueprintType, ConversationMessage } from '@/api-types';
import type { WebSocketMessage, BlueprintType, ConversationMessage, AgentState, PhasicState } from '@/api-types';
import { deduplicateMessages, isAssistantMessageDuplicate } from './deduplicate-messages';
import { logger } from '@/utils/logger';
import { getFileType } from '@/utils/string';
Expand All @@ -23,6 +23,9 @@ import { sendWebSocketMessage } from './websocket-helpers';
import type { FileType, PhaseTimelineItem } from '../hooks/use-chat';
import { toast } from 'sonner';

const isPhasicState = (state: AgentState): state is PhasicState =>
state.behaviorType === 'phasic';

export interface HandleMessageDeps {
// State setters
setFiles: React.Dispatch<React.SetStateAction<FileType[]>>;
Expand Down Expand Up @@ -191,12 +194,12 @@ export function createWebSocketMessageHandler(deps: HandleMessageDeps) {
);
}

if (state.generatedPhases && state.generatedPhases.length > 0 && phaseTimeline.length === 0) {
if (isPhasicState(state) && state.generatedPhases.length > 0 && phaseTimeline.length === 0) {
logger.debug('📋 Restoring phase timeline:', state.generatedPhases);
// If not actively generating, mark incomplete phases as cancelled (they were interrupted)
const isActivelyGenerating = state.shouldBeGenerating === true;

const timeline = state.generatedPhases.map((phase: any, index: number) => {
const timeline = state.generatedPhases.map((phase, index: number) => {
// Determine phase status:
// - completed if explicitly marked complete
// - cancelled if incomplete and not actively generating (interrupted)
Expand All @@ -212,7 +215,7 @@ export function createWebSocketMessageHandler(deps: HandleMessageDeps) {
name: phase.name,
description: phase.description,
status: phaseStatus,
files: phase.files.map((filesConcept: any) => {
files: phase.files.map(filesConcept => {
const file = state.generatedFilesMap?.[filesConcept.path];
// File status:
// - completed if it exists in generated files
Expand Down
Loading