Skip to content

Commit 8938c47

Browse files
author
catlog22
committed
feat: add experimental support for AST parsing and static graph indexing
- Introduced CLI options for using AST grep parsers and enabling static graph relationships during indexing. - Updated configuration management to load new settings for AST parsing and static graph types. - Enhanced AST grep processor to handle imports with aliases and improve relationship tracking. - Modified TreeSitter parsers to support synthetic module scopes for better static graph persistence. - Implemented global relationship updates in the incremental indexer for static graph expansion. - Added new ArtifactTag and FloatingFileBrowser components to the frontend for improved terminal dashboard functionality. - Created utility functions for detecting CCW artifacts in terminal output with associated tests.
1 parent 48a6a1f commit 8938c47

39 files changed

Lines changed: 2957 additions & 298 deletions

.claude/commands/ccw.md

Lines changed: 103 additions & 151 deletions
Large diffs are not rendered by default.

ccw/frontend/src/components/dashboard/widgets/WorkflowTaskWidget.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -704,7 +704,7 @@ function WorkflowTaskWidgetComponent({ className }: WorkflowTaskWidgetProps) {
704704
const isLastOdd = currentSession.tasks!.length % 2 === 1 && index === currentSession.tasks!.length - 1;
705705
return (
706706
<div
707-
key={`${currentSession.session_id}-${task.task_id}`}
707+
key={`${currentSession.session_id}-${task.task_id}-${index}`}
708708
className={cn(
709709
'flex items-center gap-2 p-2 rounded hover:bg-background/50 transition-colors',
710710
isLastOdd && 'col-span-2'

ccw/frontend/src/components/mcp/CcwToolsMcpCard.tsx

Lines changed: 104 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import {
2323
ChevronRight,
2424
Globe,
2525
Folder,
26+
AlertTriangle,
2627
} from 'lucide-react';
2728
import { Card } from '@/components/ui/Card';
2829
import { Button } from '@/components/ui/Button';
@@ -84,6 +85,12 @@ export interface CcwToolsMcpCardProps {
8485
onInstall: () => void;
8586
/** Installation target: Claude or Codex */
8687
target?: 'claude' | 'codex';
88+
/** Scopes where CCW MCP is currently installed */
89+
installedScopes?: ('global' | 'project')[];
90+
/** Callback to uninstall from a specific scope */
91+
onUninstallScope?: (scope: 'global' | 'project') => void;
92+
/** Callback to install to an additional scope */
93+
onInstallToScope?: (scope: 'global' | 'project') => void;
8794
}
8895

8996
// ========== Constants ==========
@@ -115,6 +122,9 @@ export function CcwToolsMcpCard({
115122
onUpdateConfig,
116123
onInstall,
117124
target = 'claude',
125+
installedScopes = [],
126+
onUninstallScope,
127+
onInstallToScope,
118128
}: CcwToolsMcpCardProps) {
119129
const { formatMessage } = useIntl();
120130
const queryClient = useQueryClient();
@@ -242,9 +252,26 @@ export function CcwToolsMcpCard({
242252
<span className="text-sm font-medium text-foreground">
243253
{formatMessage({ id: 'mcp.ccw.title' })}
244254
</span>
245-
<Badge variant={isInstalled ? 'default' : 'secondary'} className="text-xs">
246-
{isInstalled ? formatMessage({ id: 'mcp.ccw.status.installed' }) : formatMessage({ id: 'mcp.ccw.status.notInstalled' })}
247-
</Badge>
255+
{isInstalled && installedScopes.length > 0 ? (
256+
<>
257+
{installedScopes.map((s) => (
258+
<Badge key={s} variant="default" className="text-xs">
259+
{s === 'global' ? <Globe className="w-3 h-3 mr-1" /> : <Folder className="w-3 h-3 mr-1" />}
260+
{formatMessage({ id: `mcp.ccw.scope.${s}` })}
261+
</Badge>
262+
))}
263+
{installedScopes.length >= 2 && (
264+
<Badge variant="outline" className="text-xs text-orange-500 border-orange-300">
265+
<AlertTriangle className="w-3 h-3 mr-1" />
266+
{formatMessage({ id: 'mcp.conflict.badge' })}
267+
</Badge>
268+
)}
269+
</>
270+
) : (
271+
<Badge variant={isInstalled ? 'default' : 'secondary'} className="text-xs">
272+
{isInstalled ? formatMessage({ id: 'mcp.ccw.status.installed' }) : formatMessage({ id: 'mcp.ccw.status.notInstalled' })}
273+
</Badge>
274+
)}
248275
{isCodex && (
249276
<Badge variant="outline" className="text-xs text-blue-500">
250277
Codex
@@ -425,7 +452,7 @@ export function CcwToolsMcpCard({
425452

426453
{/* Install/Uninstall Button */}
427454
<div className="pt-3 border-t border-border space-y-3">
428-
{/* Scope Selection - Claude only (Codex is always global) */}
455+
{/* Scope Selection - Claude only, only when not installed */}
429456
{!isInstalled && !isCodex && (
430457
<div className="space-y-2">
431458
<p className="text-xs font-medium text-muted-foreground uppercase">
@@ -465,6 +492,20 @@ export function CcwToolsMcpCard({
465492
{formatMessage({ id: 'mcp.ccw.codexNote' })}
466493
</p>
467494
)}
495+
496+
{/* Dual-scope conflict warning */}
497+
{isInstalled && !isCodex && installedScopes.length >= 2 && (
498+
<div className="p-3 bg-orange-50 dark:bg-orange-950/30 border border-orange-200 dark:border-orange-800 rounded-lg space-y-1">
499+
<div className="flex items-center gap-2 text-orange-700 dark:text-orange-400">
500+
<AlertTriangle className="w-4 h-4" />
501+
<span className="text-sm font-medium">{formatMessage({ id: 'mcp.conflict.title' })}</span>
502+
</div>
503+
<p className="text-xs text-orange-600 dark:text-orange-400/80">
504+
{formatMessage({ id: 'mcp.conflict.description' }, { scope: formatMessage({ id: 'mcp.scope.global' }) })}
505+
</p>
506+
</div>
507+
)}
508+
468509
{!isInstalled ? (
469510
<Button
470511
onClick={handleInstallClick}
@@ -476,7 +517,8 @@ export function CcwToolsMcpCard({
476517
: formatMessage({ id: isCodex ? 'mcp.ccw.actions.installCodex' : 'mcp.ccw.actions.install' })
477518
}
478519
</Button>
479-
) : (
520+
) : isCodex ? (
521+
/* Codex: single uninstall button */
480522
<Button
481523
variant="destructive"
482524
onClick={handleUninstallClick}
@@ -488,6 +530,63 @@ export function CcwToolsMcpCard({
488530
: formatMessage({ id: 'mcp.ccw.actions.uninstall' })
489531
}
490532
</Button>
533+
) : (
534+
/* Claude: per-scope install/uninstall */
535+
<div className="space-y-2">
536+
{/* Install to missing scope */}
537+
{installedScopes.length === 1 && onInstallToScope && (
538+
<Button
539+
variant="outline"
540+
onClick={() => {
541+
const missingScope = installedScopes.includes('global') ? 'project' : 'global';
542+
onInstallToScope(missingScope);
543+
}}
544+
disabled={isPending}
545+
className="w-full"
546+
>
547+
{installedScopes.includes('global')
548+
? formatMessage({ id: 'mcp.ccw.scope.installToProject' })
549+
: formatMessage({ id: 'mcp.ccw.scope.installToGlobal' })
550+
}
551+
</Button>
552+
)}
553+
554+
{/* Per-scope uninstall buttons */}
555+
{onUninstallScope && installedScopes.map((s) => (
556+
<Button
557+
key={s}
558+
variant="destructive"
559+
size="sm"
560+
onClick={() => {
561+
if (confirm(formatMessage({ id: 'mcp.ccw.actions.uninstallScopeConfirm' }, { scope: formatMessage({ id: `mcp.ccw.scope.${s}` }) }))) {
562+
onUninstallScope(s);
563+
}
564+
}}
565+
disabled={isPending}
566+
className="w-full"
567+
>
568+
{s === 'global'
569+
? formatMessage({ id: 'mcp.ccw.scope.uninstallGlobal' })
570+
: formatMessage({ id: 'mcp.ccw.scope.uninstallProject' })
571+
}
572+
</Button>
573+
))}
574+
575+
{/* Fallback: full uninstall if no scope info */}
576+
{(!onUninstallScope || installedScopes.length === 0) && (
577+
<Button
578+
variant="destructive"
579+
onClick={handleUninstallClick}
580+
disabled={isPending}
581+
className="w-full"
582+
>
583+
{isPending
584+
? formatMessage({ id: 'mcp.ccw.actions.uninstalling' })
585+
: formatMessage({ id: 'mcp.ccw.actions.uninstall' })
586+
}
587+
</Button>
588+
)}
589+
</div>
491590
)}
492591
</div>
493592
</div>

0 commit comments

Comments
 (0)