mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-02-15 02:42:45 +08:00
Add integration verification and validation phases, role templates, and static graph tests
- Implement Phase 4: Integration Verification to ensure skill package consistency. - Implement Phase 5: Validation to verify quality and deliver the final skill package. - Create role-template.md for generating per-role execution detail files. - Create skill-router-template.md for generating SKILL.md with role-based routing. - Add tests for static graph relationship writing during index build in test_static_graph_integration.py.
This commit is contained in:
@@ -10,47 +10,20 @@ import { Flowchart } from './Flowchart';
|
||||
import { Badge } from '../ui/Badge';
|
||||
import { Button } from '../ui/Button';
|
||||
import { Tabs, TabsList, TabsTrigger, TabsContent } from '../ui/Tabs';
|
||||
import type { LiteTask, FlowControl } from '@/lib/api';
|
||||
import type { NormalizedTask } from '@/lib/api';
|
||||
import { buildFlowControl } from '@/lib/api';
|
||||
import type { TaskData } from '@/types/store';
|
||||
|
||||
// ========== Types ==========
|
||||
|
||||
export interface TaskDrawerProps {
|
||||
task: LiteTask | TaskData | null;
|
||||
task: NormalizedTask | TaskData | null;
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
type TabValue = 'overview' | 'flowchart' | 'files';
|
||||
|
||||
// ========== Helper: Unified Task Access ==========
|
||||
|
||||
/**
|
||||
* Normalize task data to common interface
|
||||
*/
|
||||
function getTaskId(task: LiteTask | TaskData): string {
|
||||
if ('task_id' in task && task.task_id) return task.task_id;
|
||||
if ('id' in task) return task.id;
|
||||
return 'N/A';
|
||||
}
|
||||
|
||||
function getTaskTitle(task: LiteTask | TaskData): string {
|
||||
return task.title || 'Untitled Task';
|
||||
}
|
||||
|
||||
function getTaskDescription(task: LiteTask | TaskData): string | undefined {
|
||||
return task.description;
|
||||
}
|
||||
|
||||
function getTaskStatus(task: LiteTask | TaskData): string {
|
||||
return task.status;
|
||||
}
|
||||
|
||||
function getFlowControl(task: LiteTask | TaskData): FlowControl | undefined {
|
||||
if ('flow_control' in task) return task.flow_control;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Status configuration
|
||||
const taskStatusConfig: Record<string, { label: string; variant: 'default' | 'secondary' | 'destructive' | 'outline' | 'success' | 'warning' | 'info' | null; icon: React.ComponentType<{ className?: string }> }> = {
|
||||
pending: {
|
||||
@@ -113,17 +86,28 @@ export function TaskDrawer({ task, isOpen, onClose }: TaskDrawerProps) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const taskId = getTaskId(task);
|
||||
const taskTitle = getTaskTitle(task);
|
||||
const taskDescription = getTaskDescription(task);
|
||||
const taskStatus = getTaskStatus(task);
|
||||
const flowControl = getFlowControl(task);
|
||||
// Use NormalizedTask fields (works for both old nested and new flat formats)
|
||||
const nt = task as NormalizedTask;
|
||||
const taskId = nt.task_id || 'N/A';
|
||||
const taskTitle = nt.title || 'Untitled Task';
|
||||
const taskDescription = nt.description;
|
||||
const taskStatus = nt.status;
|
||||
const flowControl = buildFlowControl(nt);
|
||||
|
||||
// Normalized flat fields
|
||||
const acceptanceCriteria = nt.convergence?.criteria || [];
|
||||
const focusPaths = nt.focus_paths || [];
|
||||
const dependsOn = nt.depends_on || [];
|
||||
const preAnalysis = nt.pre_analysis || flowControl?.pre_analysis || [];
|
||||
const implSteps = nt.implementation || flowControl?.implementation_approach || [];
|
||||
const taskFiles = nt.files || flowControl?.target_files || [];
|
||||
const taskScope = nt.scope;
|
||||
|
||||
const statusConfig = taskStatusConfig[taskStatus] || taskStatusConfig.pending;
|
||||
const StatusIcon = statusConfig.icon;
|
||||
|
||||
const hasFlowchart = !!flowControl?.implementation_approach && flowControl.implementation_approach.length > 0;
|
||||
const hasFiles = !!flowControl?.target_files && flowControl.target_files.length > 0;
|
||||
const hasFlowchart = implSteps.length > 0;
|
||||
const hasFiles = taskFiles.length > 0;
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -205,27 +189,27 @@ export function TaskDrawer({ task, isOpen, onClose }: TaskDrawerProps) {
|
||||
)}
|
||||
|
||||
{/* Scope Section */}
|
||||
{(task as LiteTask).meta?.scope && (
|
||||
{taskScope && (
|
||||
<div className="p-4 bg-card rounded-lg border border-border">
|
||||
<h3 className="text-sm font-semibold text-foreground mb-2 flex items-center gap-2">
|
||||
<span>📁</span>
|
||||
Scope
|
||||
</h3>
|
||||
<div className="pl-3 border-l-2 border-primary">
|
||||
<code className="text-sm text-foreground">{(task as LiteTask).meta?.scope}</code>
|
||||
<code className="text-sm text-foreground">{taskScope}</code>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Acceptance Criteria Section */}
|
||||
{(task as LiteTask).context?.acceptance && (task as LiteTask).context!.acceptance!.length > 0 && (
|
||||
{/* Acceptance / Convergence Criteria Section */}
|
||||
{acceptanceCriteria.length > 0 && (
|
||||
<div className="p-4 bg-card rounded-lg border border-border">
|
||||
<h3 className="text-sm font-semibold text-foreground mb-3 flex items-center gap-2">
|
||||
<span>✅</span>
|
||||
{formatMessage({ id: 'liteTasks.acceptanceCriteria' })}
|
||||
</h3>
|
||||
<div className="space-y-2">
|
||||
{(task as LiteTask).context!.acceptance!.map((criterion, i) => (
|
||||
{acceptanceCriteria.map((criterion, i) => (
|
||||
<div key={i} className="flex items-start gap-2">
|
||||
<span className="text-muted-foreground mt-0.5">○</span>
|
||||
<span className="text-sm text-foreground">{criterion}</span>
|
||||
@@ -236,14 +220,14 @@ export function TaskDrawer({ task, isOpen, onClose }: TaskDrawerProps) {
|
||||
)}
|
||||
|
||||
{/* Focus Paths / Reference Section */}
|
||||
{(task as LiteTask).context?.focus_paths && (task as LiteTask).context!.focus_paths!.length > 0 && (
|
||||
{focusPaths.length > 0 && (
|
||||
<div className="p-4 bg-card rounded-lg border border-border">
|
||||
<h3 className="text-sm font-semibold text-foreground mb-3 flex items-center gap-2">
|
||||
<span>📚</span>
|
||||
{formatMessage({ id: 'liteTasks.focusPaths' })}
|
||||
</h3>
|
||||
<div className="space-y-1">
|
||||
{(task as LiteTask).context!.focus_paths!.map((path, i) => (
|
||||
{focusPaths.map((path, i) => (
|
||||
<code key={i} className="block text-xs bg-muted px-3 py-1.5 rounded text-foreground font-mono">
|
||||
{path}
|
||||
</code>
|
||||
@@ -253,14 +237,14 @@ export function TaskDrawer({ task, isOpen, onClose }: TaskDrawerProps) {
|
||||
)}
|
||||
|
||||
{/* Dependencies Section */}
|
||||
{(task as LiteTask).context?.depends_on && (task as LiteTask).context!.depends_on!.length > 0 && (
|
||||
{dependsOn.length > 0 && (
|
||||
<div className="p-4 bg-card rounded-lg border border-border">
|
||||
<h3 className="text-sm font-semibold text-foreground mb-3 flex items-center gap-2">
|
||||
<span>🔗</span>
|
||||
{formatMessage({ id: 'liteTasks.dependsOn' })}
|
||||
</h3>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{(task as LiteTask).context!.depends_on!.map((dep, i) => (
|
||||
{dependsOn.map((dep, i) => (
|
||||
<Badge key={i} variant="secondary">{dep}</Badge>
|
||||
))}
|
||||
</div>
|
||||
@@ -268,14 +252,14 @@ export function TaskDrawer({ task, isOpen, onClose }: TaskDrawerProps) {
|
||||
)}
|
||||
|
||||
{/* Pre-analysis Steps */}
|
||||
{flowControl?.pre_analysis && flowControl.pre_analysis.length > 0 && (
|
||||
{preAnalysis.length > 0 && (
|
||||
<div className="p-4 bg-card rounded-lg border border-border">
|
||||
<h3 className="text-sm font-semibold text-foreground mb-3 flex items-center gap-2">
|
||||
<span>🔍</span>
|
||||
{formatMessage({ id: 'sessionDetail.taskDrawer.overview.preAnalysis' })}
|
||||
</h3>
|
||||
<div className="space-y-3">
|
||||
{flowControl.pre_analysis.map((step, index) => (
|
||||
{preAnalysis.map((step, index) => (
|
||||
<div key={index} className="flex items-start gap-3">
|
||||
<span className="flex-shrink-0 flex items-center justify-center w-6 h-6 rounded-full bg-primary text-primary-foreground text-xs font-medium">
|
||||
{index + 1}
|
||||
|
||||
@@ -312,7 +312,8 @@ function transformBackendSession(
|
||||
has_review: backendData.hasReview,
|
||||
review,
|
||||
summaries: (backendSession as unknown as { summaries?: SessionMetadata['summaries'] }).summaries,
|
||||
tasks: (backendSession as unknown as { tasks?: TaskData[] }).tasks,
|
||||
tasks: ((backendSession as unknown as { tasks?: TaskData[] }).tasks || [])
|
||||
.map(t => normalizeTask(t as unknown as Record<string, unknown>)),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1986,6 +1987,139 @@ export interface LiteTask {
|
||||
updated_at?: string;
|
||||
}
|
||||
|
||||
// ========== Normalized Task (Unified Flat Format) ==========
|
||||
|
||||
/**
|
||||
* Normalized task type that unifies both old 6-field nested format
|
||||
* and new unified flat format into a single interface.
|
||||
*
|
||||
* Old format paths → New flat paths:
|
||||
* - context.acceptance[] → convergence.criteria[]
|
||||
* - context.focus_paths[] → focus_paths[]
|
||||
* - context.depends_on[] → depends_on[]
|
||||
* - context.requirements[] → description
|
||||
* - flow_control.pre_analysis[] → pre_analysis[]
|
||||
* - flow_control.implementation_approach[] → implementation[]
|
||||
* - flow_control.target_files[] → files[]
|
||||
*/
|
||||
export interface NormalizedTask extends TaskData {
|
||||
// Promoted from context
|
||||
focus_paths?: string[];
|
||||
convergence?: {
|
||||
criteria?: string[];
|
||||
verification?: string;
|
||||
definition_of_done?: string;
|
||||
};
|
||||
|
||||
// Promoted from flow_control
|
||||
pre_analysis?: PreAnalysisStep[];
|
||||
implementation?: (ImplementationStep | string)[];
|
||||
files?: Array<{ path: string; name?: string }>;
|
||||
|
||||
// Promoted from meta
|
||||
type?: string;
|
||||
scope?: string;
|
||||
action?: string;
|
||||
|
||||
// Original nested objects (preserved for long-term compat)
|
||||
flow_control?: FlowControl;
|
||||
context?: {
|
||||
focus_paths?: string[];
|
||||
acceptance?: string[];
|
||||
depends_on?: string[];
|
||||
requirements?: string[];
|
||||
};
|
||||
meta?: {
|
||||
type?: string;
|
||||
scope?: string;
|
||||
[key: string]: unknown;
|
||||
};
|
||||
|
||||
// Raw data reference for JSON viewer / debugging
|
||||
_raw?: unknown;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize a raw task object (old 6-field or new unified flat) into NormalizedTask.
|
||||
* Reads new flat fields first, falls back to old nested paths.
|
||||
* Long-term compatible: handles both formats permanently.
|
||||
*/
|
||||
export function normalizeTask(raw: Record<string, unknown>): NormalizedTask {
|
||||
if (!raw || typeof raw !== 'object') {
|
||||
return { task_id: 'N/A', status: 'pending', _raw: raw } as NormalizedTask;
|
||||
}
|
||||
|
||||
// Type-safe access helpers
|
||||
const rawContext = raw.context as LiteTask['context'] | undefined;
|
||||
const rawFlowControl = raw.flow_control as FlowControl | undefined;
|
||||
const rawMeta = raw.meta as LiteTask['meta'] | undefined;
|
||||
const rawConvergence = raw.convergence as NormalizedTask['convergence'] | undefined;
|
||||
|
||||
// Description: new flat field first, then join old context.requirements
|
||||
const rawRequirements = rawContext?.requirements;
|
||||
const description = (raw.description as string | undefined)
|
||||
|| (Array.isArray(rawRequirements) && rawRequirements.length > 0
|
||||
? rawRequirements.join('; ')
|
||||
: undefined);
|
||||
|
||||
return {
|
||||
// Identity
|
||||
task_id: (raw.task_id as string) || (raw.id as string) || 'N/A',
|
||||
title: raw.title as string | undefined,
|
||||
description,
|
||||
status: (raw.status as NormalizedTask['status']) || 'pending',
|
||||
priority: raw.priority as NormalizedTask['priority'],
|
||||
created_at: raw.created_at as string | undefined,
|
||||
updated_at: raw.updated_at as string | undefined,
|
||||
has_summary: raw.has_summary as boolean | undefined,
|
||||
estimated_complexity: raw.estimated_complexity as string | undefined,
|
||||
|
||||
// Promoted from context (new first, old fallback)
|
||||
depends_on: (raw.depends_on as string[]) || rawContext?.depends_on || [],
|
||||
focus_paths: (raw.focus_paths as string[]) || rawContext?.focus_paths || [],
|
||||
convergence: rawConvergence || (rawContext?.acceptance?.length
|
||||
? { criteria: rawContext.acceptance }
|
||||
: undefined),
|
||||
|
||||
// Promoted from flow_control (new first, old fallback)
|
||||
pre_analysis: (raw.pre_analysis as PreAnalysisStep[]) || rawFlowControl?.pre_analysis,
|
||||
implementation: (raw.implementation as (ImplementationStep | string)[]) || rawFlowControl?.implementation_approach,
|
||||
files: (raw.files as Array<{ path: string; name?: string }>) || rawFlowControl?.target_files,
|
||||
|
||||
// Promoted from meta (new first, old fallback)
|
||||
type: (raw.type as string) || rawMeta?.type,
|
||||
scope: (raw.scope as string) || rawMeta?.scope,
|
||||
action: (raw.action as string) || (rawMeta as Record<string, unknown> | undefined)?.action as string | undefined,
|
||||
|
||||
// Preserve original nested objects for backward compat
|
||||
flow_control: rawFlowControl,
|
||||
context: rawContext,
|
||||
meta: rawMeta,
|
||||
|
||||
// Raw reference
|
||||
_raw: raw,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a FlowControl object from NormalizedTask for backward-compatible components (e.g. Flowchart).
|
||||
*/
|
||||
export function buildFlowControl(task: NormalizedTask): FlowControl | undefined {
|
||||
const preAnalysis = task.pre_analysis;
|
||||
const implementation = task.implementation;
|
||||
const files = task.files;
|
||||
|
||||
if (!preAnalysis?.length && !implementation?.length && !files?.length) {
|
||||
return task.flow_control; // Fall back to original if no flat fields
|
||||
}
|
||||
|
||||
return {
|
||||
pre_analysis: preAnalysis || task.flow_control?.pre_analysis,
|
||||
implementation_approach: implementation || task.flow_control?.implementation_approach,
|
||||
target_files: files || task.flow_control?.target_files,
|
||||
};
|
||||
}
|
||||
|
||||
export interface LiteTaskSession {
|
||||
id: string;
|
||||
session_id?: string;
|
||||
|
||||
Reference in New Issue
Block a user