mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-03-06 16:31:12 +08:00
feat: add category and scope to specs for enhanced filtering and organization
- Introduced SpecCategory and SpecScope types to categorize specs by workflow stage and scope (global/project). - Updated Spec interface to include category and scope properties. - Enhanced SpecCard component to display category and scope badges. - Implemented category and scope filtering in SpecsSettingsPage. - Updated localization files to support new category and scope labels. - Modified spec loading commands to utilize category instead of keywords. - Adjusted spec index builder to handle category and scope during spec parsing. - Updated seed documents to include category information.
This commit is contained in:
@@ -5,6 +5,7 @@
|
||||
|
||||
import { useState, useEffect, useCallback, useMemo } from 'react';
|
||||
import { useIntl } from 'react-intl';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { toast } from 'sonner';
|
||||
import { cn } from '@/lib/utils';
|
||||
import {
|
||||
@@ -28,7 +29,7 @@ import {
|
||||
Plug,
|
||||
Download,
|
||||
CheckCircle2,
|
||||
ExternalLink,
|
||||
Settings,
|
||||
} from 'lucide-react';
|
||||
import { useInstallRecommendedHooks } from '@/hooks/useSystemSettings';
|
||||
|
||||
@@ -325,32 +326,34 @@ export function InjectionControlTab({ className }: InjectionControlTabProps) {
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<div className="flex items-center gap-4">
|
||||
<Button
|
||||
onClick={handleInstallAllHooks}
|
||||
disabled={allHooksInstalled || installingHookIds.length > 0}
|
||||
>
|
||||
{allHooksInstalled ? (
|
||||
<>
|
||||
<CheckCircle2 className="h-4 w-4 mr-2" />
|
||||
{formatMessage({ id: 'specs.allHooksInstalled', defaultMessage: 'All Hooks Installed' })}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Download className="h-4 w-4 mr-2" />
|
||||
{formatMessage({ id: 'specs.installAllHooks', defaultMessage: 'Install All Hooks' })}
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
<div className="text-sm text-muted-foreground">
|
||||
{installedCount} / {RECOMMENDED_HOOKS.length}{' '}
|
||||
{formatMessage({ id: 'specs.hooksInstalled', defaultMessage: 'installed' })}
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-4">
|
||||
<Button
|
||||
onClick={handleInstallAllHooks}
|
||||
disabled={allHooksInstalled || installingHookIds.length > 0}
|
||||
>
|
||||
{allHooksInstalled ? (
|
||||
<>
|
||||
<CheckCircle2 className="h-4 w-4 mr-2" />
|
||||
{formatMessage({ id: 'specs.allHooksInstalled', defaultMessage: 'All Hooks Installed' })}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Download className="h-4 w-4 mr-2" />
|
||||
{formatMessage({ id: 'specs.installAllHooks', defaultMessage: 'Install All Hooks' })}
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
<div className="text-sm text-muted-foreground">
|
||||
{installedCount} / {RECOMMENDED_HOOKS.length}{' '}
|
||||
{formatMessage({ id: 'specs.hooksInstalled', defaultMessage: 'installed' })}
|
||||
</div>
|
||||
</div>
|
||||
<Button variant="ghost" size="sm" asChild>
|
||||
<a href="/hooks" target="_blank" rel="noopener noreferrer">
|
||||
<ExternalLink className="h-4 w-4 mr-1" />
|
||||
<Link to="/hooks">
|
||||
<Settings className="h-4 w-4 mr-1" />
|
||||
{formatMessage({ id: 'specs.manageHooks', defaultMessage: 'Manage Hooks' })}
|
||||
</a>
|
||||
</Link>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -23,6 +23,10 @@ import {
|
||||
Trash2,
|
||||
FileText,
|
||||
Tag,
|
||||
Eye,
|
||||
Globe,
|
||||
Folder,
|
||||
Layers,
|
||||
} from 'lucide-react';
|
||||
|
||||
// ========== Types ==========
|
||||
@@ -32,6 +36,11 @@ import {
|
||||
*/
|
||||
export type SpecDimension = 'specs' | 'personal';
|
||||
|
||||
/**
|
||||
* Spec scope type
|
||||
*/
|
||||
export type SpecScope = 'global' | 'project';
|
||||
|
||||
/**
|
||||
* Spec read mode type
|
||||
*/
|
||||
@@ -42,6 +51,11 @@ export type SpecReadMode = 'required' | 'optional';
|
||||
*/
|
||||
export type SpecPriority = 'critical' | 'high' | 'medium' | 'low';
|
||||
|
||||
/**
|
||||
* Spec category type for workflow stage-based loading
|
||||
*/
|
||||
export type SpecCategory = 'general' | 'exploration' | 'planning' | 'execution';
|
||||
|
||||
/**
|
||||
* Spec data structure
|
||||
*/
|
||||
@@ -54,6 +68,10 @@ export interface Spec {
|
||||
file: string;
|
||||
/** Spec dimension/category */
|
||||
dimension: SpecDimension;
|
||||
/** Scope: global (from ~/.ccw/) or project (from .ccw/) */
|
||||
scope: SpecScope;
|
||||
/** Workflow stage category for system-level loading */
|
||||
category?: SpecCategory;
|
||||
/** Read mode: required (always inject) or optional (keyword match) */
|
||||
readMode: SpecReadMode;
|
||||
/** Priority level */
|
||||
@@ -72,6 +90,8 @@ export interface Spec {
|
||||
export interface SpecCardProps {
|
||||
/** Spec data */
|
||||
spec: Spec;
|
||||
/** Called when view content action is triggered */
|
||||
onView?: (spec: Spec) => void;
|
||||
/** Called when edit action is triggered */
|
||||
onEdit?: (spec: Spec) => void;
|
||||
/** Called when delete action is triggered */
|
||||
@@ -108,6 +128,17 @@ const priorityConfig: Record<
|
||||
low: { variant: 'secondary', labelKey: 'specs.priority.low' },
|
||||
};
|
||||
|
||||
// Category badge configuration for workflow stage
|
||||
const categoryConfig: Record<
|
||||
SpecCategory,
|
||||
{ variant: 'default' | 'secondary' | 'outline'; labelKey: string }
|
||||
> = {
|
||||
general: { variant: 'secondary', labelKey: 'specs.category.general' },
|
||||
exploration: { variant: 'outline', labelKey: 'specs.category.exploration' },
|
||||
planning: { variant: 'outline', labelKey: 'specs.category.planning' },
|
||||
execution: { variant: 'outline', labelKey: 'specs.category.execution' },
|
||||
};
|
||||
|
||||
// ========== Component ==========
|
||||
|
||||
/**
|
||||
@@ -115,6 +146,7 @@ const priorityConfig: Record<
|
||||
*/
|
||||
export function SpecCard({
|
||||
spec,
|
||||
onView,
|
||||
onEdit,
|
||||
onDelete,
|
||||
onToggle,
|
||||
@@ -181,6 +213,10 @@ export function SpecCard({
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
<DropdownMenuItem onClick={(e) => { e.stopPropagation(); onView?.(spec); }}>
|
||||
<Eye className="mr-2 h-4 w-4" />
|
||||
{formatMessage({ id: 'specs.actions.view', defaultMessage: 'View Content' })}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={(e) => handleAction(e, 'edit')}>
|
||||
<Edit className="mr-2 h-4 w-4" />
|
||||
{formatMessage({ id: 'specs.actions.edit' })}
|
||||
@@ -201,6 +237,29 @@ export function SpecCard({
|
||||
|
||||
{/* Badges */}
|
||||
<div className="mt-3 flex flex-wrap items-center gap-2">
|
||||
{/* Category badge - workflow stage */}
|
||||
{spec.category && (
|
||||
<Badge variant={categoryConfig[spec.category].variant} className="text-xs gap-1">
|
||||
<Layers className="h-3 w-3" />
|
||||
{formatMessage({ id: categoryConfig[spec.category].labelKey, defaultMessage: spec.category })}
|
||||
</Badge>
|
||||
)}
|
||||
{/* Scope badge - only show for personal specs */}
|
||||
{spec.dimension === 'personal' && (
|
||||
<Badge variant="outline" className="text-xs gap-1">
|
||||
{spec.scope === 'global' ? (
|
||||
<>
|
||||
<Globe className="h-3 w-3" />
|
||||
{formatMessage({ id: 'specs.scope.global', defaultMessage: 'Global' })}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Folder className="h-3 w-3" />
|
||||
{formatMessage({ id: 'specs.scope.project', defaultMessage: 'Project' })}
|
||||
</>
|
||||
)}
|
||||
</Badge>
|
||||
)}
|
||||
<Badge variant={readMode.variant} className="text-xs">
|
||||
{formatMessage({ id: readMode.labelKey })}
|
||||
</Badge>
|
||||
|
||||
@@ -11,8 +11,10 @@ export {
|
||||
export type {
|
||||
Spec,
|
||||
SpecDimension,
|
||||
SpecScope,
|
||||
SpecReadMode,
|
||||
SpecPriority,
|
||||
SpecCategory,
|
||||
SpecCardProps,
|
||||
} from './SpecCard';
|
||||
|
||||
|
||||
Reference in New Issue
Block a user