mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-03-05 16:13:08 +08:00
- Renamed `team-lifecycle-v5` to `team-lifecycle` across various documentation files for consistency. - Updated references in code examples and usage sections to reflect the new skill name. - Added a new command file for the `monitor` functionality in the `team-iterdev` skill, detailing the coordinator's monitoring events and task management. - Introduced new components for dynamic pipeline visualization and session coordinates display in the frontend. - Implemented utility functions for pipeline stage detection and status derivation based on message history. - Enhanced the team role panel to map members to their respective pipeline roles with status indicators. - Updated Chinese documentation to reflect the changes in skill names and descriptions.
120 lines
3.8 KiB
TypeScript
120 lines
3.8 KiB
TypeScript
import type { TeamMessage, DynamicStage, DynamicStageStatus, PhaseInfo } from '@/types/team';
|
|
|
|
const LEGACY_STAGES = ['plan', 'impl', 'test', 'review'] as const;
|
|
|
|
/**
|
|
* Capitalize first letter, lowercase rest.
|
|
* "SCAN" -> "Scan", "review" -> "Review"
|
|
*/
|
|
export function formatStageLabel(stageId: string): string {
|
|
if (!stageId) return '';
|
|
return stageId.charAt(0).toUpperCase() + stageId.slice(1).toLowerCase();
|
|
}
|
|
|
|
/**
|
|
* Derive the status of a pipeline stage from the message history.
|
|
*
|
|
* Matches messages whose `from` field equals or starts with `roleOrStage`
|
|
* (case-insensitive). The status is determined by the LAST matching message's type.
|
|
*/
|
|
export function deriveStageStatus(
|
|
roleOrStage: string,
|
|
messages: TeamMessage[],
|
|
): DynamicStageStatus {
|
|
const needle = roleOrStage.toLowerCase();
|
|
const matching = messages.filter((m) => {
|
|
const from = m.from.toLowerCase();
|
|
return from === needle || from.startsWith(needle);
|
|
});
|
|
|
|
if (matching.length === 0) return 'pending';
|
|
|
|
const last = matching[matching.length - 1];
|
|
const completionTypes: string[] = ['shutdown', 'impl_complete', 'review_result', 'test_result'];
|
|
|
|
if (completionTypes.includes(last.type)) return 'completed';
|
|
if (last.type === 'error') return 'blocked';
|
|
return 'in_progress';
|
|
}
|
|
|
|
/**
|
|
* Three-tier pipeline stage detection.
|
|
*
|
|
* Tier 1 - explicit `pipeline_stages` from meta
|
|
* Tier 2 - inferred from message senders (excluding "coordinator")
|
|
* Tier 3 - legacy 4-stage fallback
|
|
*/
|
|
export function derivePipelineStages(
|
|
meta: {
|
|
pipeline_stages?: string[];
|
|
role_state?: Record<string, Record<string, unknown>>;
|
|
roles?: string[];
|
|
},
|
|
messages: TeamMessage[],
|
|
): DynamicStage[] {
|
|
// Tier 1: explicit pipeline_stages
|
|
if (meta.pipeline_stages && meta.pipeline_stages.length > 0) {
|
|
return meta.pipeline_stages.map((stage) => {
|
|
const id = stage.toUpperCase();
|
|
const lowerStage = stage.toLowerCase();
|
|
const role = meta.roles?.find((r) => r.toLowerCase().startsWith(lowerStage));
|
|
return {
|
|
id,
|
|
label: formatStageLabel(stage),
|
|
role,
|
|
status: deriveStageStatus(stage, messages),
|
|
};
|
|
});
|
|
}
|
|
|
|
// Tier 2: extract from message senders
|
|
if (messages.length > 0) {
|
|
const seen = new Map<string, string>(); // lowercase sender -> original sender
|
|
for (const msg of messages) {
|
|
const sender = msg.from;
|
|
if (sender.toLowerCase() === 'coordinator') continue;
|
|
const key = sender.toLowerCase();
|
|
if (!seen.has(key)) {
|
|
seen.set(key, sender);
|
|
}
|
|
}
|
|
|
|
if (seen.size > 0) {
|
|
return Array.from(seen.entries()).map(([, sender]) => ({
|
|
id: sender.toUpperCase(),
|
|
label: formatStageLabel(sender),
|
|
role: sender,
|
|
status: deriveStageStatus(sender, messages),
|
|
}));
|
|
}
|
|
}
|
|
|
|
// Tier 3: legacy fallback
|
|
return LEGACY_STAGES.map((stage) => ({
|
|
id: stage.toUpperCase(),
|
|
label: formatStageLabel(stage),
|
|
role: undefined,
|
|
status: deriveStageStatus(stage, messages),
|
|
}));
|
|
}
|
|
|
|
/**
|
|
* Detect multi-phase execution from coordinator role state.
|
|
* Returns PhaseInfo when the coordinator reports `current_phase`, otherwise null.
|
|
*/
|
|
export function detectMultiPhase(
|
|
roleState?: Record<string, Record<string, unknown>>,
|
|
): PhaseInfo | null {
|
|
if (!roleState || Object.keys(roleState).length === 0) return null;
|
|
|
|
const coordinator = roleState['coordinator'];
|
|
if (!coordinator || typeof coordinator.current_phase !== 'number') return null;
|
|
|
|
return {
|
|
currentPhase: coordinator.current_phase as number,
|
|
totalPhases: typeof coordinator.total_phases === 'number' ? coordinator.total_phases : null,
|
|
currentStep: typeof coordinator.current_step === 'string' ? coordinator.current_step : null,
|
|
gapIteration: typeof coordinator.gap_iteration === 'number' ? coordinator.gap_iteration : 0,
|
|
};
|
|
}
|