mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-02-05 01:50:27 +08:00
feat: Enhance multi-CLI session handling and UI updates
- Added loading of plan.json in scanMultiCliDir to improve task extraction. - Implemented normalization of tasks from plan.json format to support new UI. - Updated CSS for multi-CLI plan summary and task item badges for better visibility. - Refactored hook-manager to use Node.js for cross-platform compatibility in command execution. - Improved i18n support for new CLI tool configuration in the hook wizard. - Enhanced lite-tasks view to utilize normalized tasks and provide better fallback mechanisms. - Updated memory-update-queue to return string messages for better integration with hooks.
This commit is contained in:
@@ -238,10 +238,11 @@ async function scanMultiCliDir(dir: string): Promise<MultiCliSession[]> {
|
||||
.map(async (entry) => {
|
||||
const sessionPath = join(dir, entry.name);
|
||||
|
||||
const [createdAt, syntheses, sessionState] = await Promise.all([
|
||||
const [createdAt, syntheses, sessionState, planJson] = await Promise.all([
|
||||
getCreatedTime(sessionPath),
|
||||
loadRoundSyntheses(sessionPath),
|
||||
loadSessionState(sessionPath),
|
||||
loadPlanJson(sessionPath),
|
||||
]);
|
||||
|
||||
// Extract data from syntheses
|
||||
@@ -258,13 +259,20 @@ async function scanMultiCliDir(dir: string): Promise<MultiCliSession[]> {
|
||||
const status = sessionState?.status ||
|
||||
(latestSynthesis?.convergence?.recommendation === 'converged' ? 'converged' : 'analyzing');
|
||||
|
||||
// Use plan.json if available, otherwise extract from synthesis
|
||||
const plan = planJson || latestSynthesis;
|
||||
// Use tasks from plan.json if available, otherwise extract from synthesis
|
||||
const tasks = (planJson as any)?.tasks?.length > 0
|
||||
? normalizePlanJsonTasks((planJson as any).tasks)
|
||||
: extractTasksFromSyntheses(syntheses);
|
||||
|
||||
const session: MultiCliSession = {
|
||||
id: entry.name,
|
||||
type: 'multi-cli-plan',
|
||||
path: sessionPath,
|
||||
createdAt,
|
||||
plan: latestSynthesis,
|
||||
tasks: extractTasksFromSyntheses(syntheses),
|
||||
plan,
|
||||
tasks,
|
||||
progress,
|
||||
// Extended multi-cli specific fields
|
||||
roundCount,
|
||||
@@ -548,6 +556,53 @@ function normalizeSolutionTask(task: SolutionTask, solution: Solution): Normaliz
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize tasks from plan.json format to NormalizedTask[]
|
||||
* plan.json tasks have: id, name, description, depends_on, status, files, key_point, acceptance_criteria
|
||||
* @param tasks - Tasks array from plan.json
|
||||
* @returns Normalized tasks
|
||||
*/
|
||||
function normalizePlanJsonTasks(tasks: unknown[]): NormalizedTask[] {
|
||||
if (!Array.isArray(tasks)) return [];
|
||||
|
||||
return tasks.map((task: any): NormalizedTask | null => {
|
||||
if (!task || !task.id) return null;
|
||||
|
||||
return {
|
||||
id: task.id,
|
||||
title: task.name || task.title || 'Untitled Task',
|
||||
status: task.status || 'pending',
|
||||
meta: {
|
||||
type: 'implementation',
|
||||
agent: null,
|
||||
scope: task.scope || null,
|
||||
module: null
|
||||
},
|
||||
context: {
|
||||
requirements: task.description ? [task.description] : (task.key_point ? [task.key_point] : []),
|
||||
focus_paths: task.files?.map((f: any) => typeof f === 'string' ? f : f.file) || [],
|
||||
acceptance: task.acceptance_criteria || [],
|
||||
depends_on: task.depends_on || []
|
||||
},
|
||||
flow_control: {
|
||||
implementation_approach: task.files?.map((f: any, i: number) => {
|
||||
const filePath = typeof f === 'string' ? f : f.file;
|
||||
const action = typeof f === 'string' ? 'modify' : f.action;
|
||||
const line = typeof f === 'string' ? null : f.line;
|
||||
return {
|
||||
step: `Step ${i + 1}`,
|
||||
action: `${action} ${filePath}${line ? ` at line ${line}` : ''}`
|
||||
};
|
||||
}) || []
|
||||
},
|
||||
_raw: {
|
||||
task,
|
||||
estimated_complexity: task.estimated_complexity
|
||||
}
|
||||
};
|
||||
}).filter((task): task is NormalizedTask => task !== null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load plan.json or fix-plan.json from session directory
|
||||
* @param sessionPath - Session directory path
|
||||
|
||||
@@ -1281,7 +1281,7 @@
|
||||
.multi-cli-status.pending,
|
||||
.multi-cli-status.exploring,
|
||||
.multi-cli-status.initialized {
|
||||
) background: hsl(var(--muted));
|
||||
background: hsl(var(--muted));
|
||||
color: hsl(var(--muted-foreground));
|
||||
}
|
||||
|
||||
@@ -3621,3 +3621,328 @@
|
||||
}
|
||||
}
|
||||
|
||||
/* ===================================
|
||||
Multi-CLI Plan Summary Section
|
||||
=================================== */
|
||||
|
||||
/* Plan Summary Section - card-like styling */
|
||||
.plan-summary-section {
|
||||
background: hsl(var(--card));
|
||||
border: 1px solid hsl(var(--border));
|
||||
border-radius: 0.5rem;
|
||||
padding: 1rem 1.25rem;
|
||||
margin-bottom: 1.25rem;
|
||||
}
|
||||
|
||||
.plan-summary-section:hover {
|
||||
border-color: hsl(var(--purple, 280 60% 50%) / 0.3);
|
||||
}
|
||||
|
||||
/* Plan text styles */
|
||||
.plan-summary-text,
|
||||
.plan-solution-text,
|
||||
.plan-approach-text {
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.6;
|
||||
color: hsl(var(--foreground));
|
||||
margin: 0 0 0.75rem 0;
|
||||
}
|
||||
|
||||
.plan-summary-text:last-child,
|
||||
.plan-solution-text:last-child,
|
||||
.plan-approach-text:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.plan-summary-text strong,
|
||||
.plan-solution-text strong,
|
||||
.plan-approach-text strong {
|
||||
color: hsl(var(--muted-foreground));
|
||||
font-weight: 600;
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
|
||||
/* Plan meta badges container */
|
||||
.plan-meta-badges {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.5rem;
|
||||
margin-top: 0.75rem;
|
||||
padding-top: 0.75rem;
|
||||
border-top: 1px solid hsl(var(--border) / 0.5);
|
||||
}
|
||||
|
||||
/* Feasibility badge */
|
||||
.feasibility-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 0.25rem 0.625rem;
|
||||
background: hsl(var(--primary) / 0.1);
|
||||
color: hsl(var(--primary));
|
||||
border-radius: 0.25rem;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* Effort badge variants */
|
||||
.effort-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 0.25rem 0.625rem;
|
||||
border-radius: 0.25rem;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.effort-badge.low {
|
||||
background: hsl(var(--success-light, 142 70% 95%));
|
||||
color: hsl(var(--success, 142 70% 45%));
|
||||
}
|
||||
|
||||
.effort-badge.medium {
|
||||
background: hsl(var(--warning-light, 45 90% 95%));
|
||||
color: hsl(var(--warning, 45 90% 40%));
|
||||
}
|
||||
|
||||
.effort-badge.high {
|
||||
background: hsl(var(--destructive) / 0.1);
|
||||
color: hsl(var(--destructive));
|
||||
}
|
||||
|
||||
/* Complexity badge */
|
||||
.complexity-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 0.25rem 0.625rem;
|
||||
background: hsl(var(--muted));
|
||||
color: hsl(var(--foreground));
|
||||
border-radius: 0.25rem;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* Time badge */
|
||||
.time-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 0.25rem 0.625rem;
|
||||
background: hsl(var(--info-light, 220 80% 95%));
|
||||
color: hsl(var(--info, 220 80% 55%));
|
||||
border-radius: 0.25rem;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* ===================================
|
||||
Multi-CLI Task Item Additional Badges
|
||||
=================================== */
|
||||
|
||||
/* Files meta badge */
|
||||
.meta-badge.files {
|
||||
background: hsl(var(--purple, 280 60% 50%) / 0.1);
|
||||
color: hsl(var(--purple, 280 60% 50%));
|
||||
}
|
||||
|
||||
/* Depends meta badge */
|
||||
.meta-badge.depends {
|
||||
background: hsl(var(--info-light, 220 80% 95%));
|
||||
color: hsl(var(--info, 220 80% 55%));
|
||||
}
|
||||
|
||||
/* Multi-CLI Task Item Full - enhanced padding */
|
||||
.detail-task-item-full.multi-cli-task-item {
|
||||
background: hsl(var(--card));
|
||||
border: 1px solid hsl(var(--border));
|
||||
border-radius: 0.5rem;
|
||||
padding: 0.875rem 1rem;
|
||||
transition: all 0.2s ease;
|
||||
border-left: 3px solid hsl(var(--primary) / 0.5);
|
||||
}
|
||||
|
||||
.detail-task-item-full.multi-cli-task-item:hover {
|
||||
border-color: hsl(var(--primary) / 0.4);
|
||||
border-left-color: hsl(var(--primary));
|
||||
box-shadow: 0 2px 8px hsl(var(--primary) / 0.1);
|
||||
background: hsl(var(--hover));
|
||||
}
|
||||
|
||||
/* Task ID badge enhancement */
|
||||
.task-id-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: 2.5rem;
|
||||
padding: 0.25rem 0.5rem;
|
||||
background: hsl(var(--purple, 280 60% 50%));
|
||||
color: white;
|
||||
border-radius: 0.25rem;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* Tasks list container */
|
||||
.tasks-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.625rem;
|
||||
}
|
||||
|
||||
/* Plan section styling (for Plan tab) */
|
||||
.plan-section {
|
||||
background: hsl(var(--muted) / 0.3);
|
||||
border: 1px solid hsl(var(--border));
|
||||
border-radius: 0.5rem;
|
||||
padding: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.plan-section:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.plan-section-title {
|
||||
font-size: 0.9rem;
|
||||
font-weight: 600;
|
||||
color: hsl(var(--foreground));
|
||||
margin-bottom: 0.75rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.plan-tab-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
.tasks-tab-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
/* ===================================
|
||||
Plan Summary Meta Badges
|
||||
=================================== */
|
||||
|
||||
/* Base meta badge style (plan summary) */
|
||||
.plan-meta-badges .meta-badge {
|
||||
display: inline-block;
|
||||
padding: 0.25rem 0.625rem;
|
||||
border-radius: 0.375rem;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 500;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* Feasibility badge */
|
||||
.meta-badge.feasibility {
|
||||
background: hsl(var(--success) / 0.15);
|
||||
color: hsl(var(--success));
|
||||
border: 1px solid hsl(var(--success) / 0.3);
|
||||
}
|
||||
|
||||
/* Effort badges */
|
||||
.meta-badge.effort {
|
||||
background: hsl(var(--muted));
|
||||
color: hsl(var(--foreground));
|
||||
}
|
||||
|
||||
.meta-badge.effort.low {
|
||||
background: hsl(142 70% 50% / 0.15);
|
||||
color: hsl(142 70% 35%);
|
||||
}
|
||||
|
||||
.meta-badge.effort.medium {
|
||||
background: hsl(30 90% 50% / 0.15);
|
||||
color: hsl(30 90% 40%);
|
||||
}
|
||||
|
||||
.meta-badge.effort.high {
|
||||
background: hsl(0 70% 50% / 0.15);
|
||||
color: hsl(0 70% 45%);
|
||||
}
|
||||
|
||||
/* Risk badges */
|
||||
.meta-badge.risk {
|
||||
background: hsl(var(--muted));
|
||||
color: hsl(var(--foreground));
|
||||
}
|
||||
|
||||
.meta-badge.risk.low {
|
||||
background: hsl(142 70% 50% / 0.15);
|
||||
color: hsl(142 70% 35%);
|
||||
}
|
||||
|
||||
.meta-badge.risk.medium {
|
||||
background: hsl(30 90% 50% / 0.15);
|
||||
color: hsl(30 90% 40%);
|
||||
}
|
||||
|
||||
.meta-badge.risk.high {
|
||||
background: hsl(0 70% 50% / 0.15);
|
||||
color: hsl(0 70% 45%);
|
||||
}
|
||||
|
||||
/* Severity badges */
|
||||
.meta-badge.severity {
|
||||
background: hsl(var(--muted));
|
||||
color: hsl(var(--foreground));
|
||||
}
|
||||
|
||||
.meta-badge.severity.low {
|
||||
background: hsl(142 70% 50% / 0.15);
|
||||
color: hsl(142 70% 35%);
|
||||
}
|
||||
|
||||
.meta-badge.severity.medium {
|
||||
background: hsl(30 90% 50% / 0.15);
|
||||
color: hsl(30 90% 40%);
|
||||
}
|
||||
|
||||
.meta-badge.severity.high,
|
||||
.meta-badge.severity.critical {
|
||||
background: hsl(0 70% 50% / 0.15);
|
||||
color: hsl(0 70% 45%);
|
||||
}
|
||||
|
||||
/* Complexity badge */
|
||||
.meta-badge.complexity {
|
||||
background: hsl(var(--muted));
|
||||
color: hsl(var(--muted-foreground));
|
||||
}
|
||||
|
||||
/* Time badge */
|
||||
.meta-badge.time {
|
||||
background: hsl(220 80% 50% / 0.15);
|
||||
color: hsl(220 80% 45%);
|
||||
}
|
||||
|
||||
/* Task item action badge */
|
||||
.meta-badge.action {
|
||||
background: hsl(var(--primary) / 0.15);
|
||||
color: hsl(var(--primary));
|
||||
}
|
||||
|
||||
/* Task item scope badge */
|
||||
.meta-badge.scope {
|
||||
background: hsl(var(--muted));
|
||||
color: hsl(var(--muted-foreground));
|
||||
font-family: var(--font-mono);
|
||||
font-size: 0.7rem;
|
||||
}
|
||||
|
||||
/* Task item impl steps badge */
|
||||
.meta-badge.impl {
|
||||
background: hsl(280 60% 50% / 0.1);
|
||||
color: hsl(280 60% 50%);
|
||||
}
|
||||
|
||||
/* Task item acceptance criteria badge */
|
||||
.meta-badge.accept {
|
||||
background: hsl(var(--success) / 0.1);
|
||||
color: hsl(var(--success));
|
||||
}
|
||||
|
||||
|
||||
@@ -52,12 +52,13 @@ const HOOK_TEMPLATES = {
|
||||
'memory-update-queue': {
|
||||
event: 'Stop',
|
||||
matcher: '',
|
||||
command: 'bash',
|
||||
args: ['-c', 'ccw tool exec memory_queue "{\\"action\\":\\"add\\",\\"path\\":\\"$CLAUDE_PROJECT_DIR\\"}"'],
|
||||
command: 'node',
|
||||
args: ['-e', "require('child_process').spawnSync(process.platform==='win32'?'cmd':'ccw',process.platform==='win32'?['/c','ccw','tool','exec','memory_queue',JSON.stringify({action:'add',path:process.env.CLAUDE_PROJECT_DIR,tool:'gemini'})]:['tool','exec','memory_queue',JSON.stringify({action:'add',path:process.env.CLAUDE_PROJECT_DIR,tool:'gemini'})],{stdio:'inherit'})"],
|
||||
description: 'Queue CLAUDE.md update when session ends (batched by threshold/timeout)',
|
||||
category: 'memory',
|
||||
configurable: true,
|
||||
config: {
|
||||
tool: { type: 'select', default: 'gemini', options: ['gemini', 'qwen', 'codex', 'opencode'], label: 'CLI Tool' },
|
||||
threshold: { type: 'number', default: 5, min: 1, max: 20, label: 'Threshold (paths)', step: 1 },
|
||||
timeout: { type: 'number', default: 300, min: 60, max: 1800, label: 'Timeout (seconds)', step: 60 }
|
||||
}
|
||||
@@ -66,8 +67,8 @@ const HOOK_TEMPLATES = {
|
||||
'skill-context-keyword': {
|
||||
event: 'UserPromptSubmit',
|
||||
matcher: '',
|
||||
command: 'bash',
|
||||
args: ['-c', 'ccw tool exec skill_context_loader --stdin'],
|
||||
command: 'node',
|
||||
args: ['-e', "const p=JSON.parse(process.env.HOOK_INPUT||'{}');require('child_process').spawnSync('ccw',['tool','exec','skill_context_loader',JSON.stringify({prompt:p.user_prompt||''})],{stdio:'inherit'})"],
|
||||
description: 'Load SKILL context based on keyword matching in user prompt',
|
||||
category: 'skill',
|
||||
configurable: true,
|
||||
@@ -79,8 +80,8 @@ const HOOK_TEMPLATES = {
|
||||
'skill-context-auto': {
|
||||
event: 'UserPromptSubmit',
|
||||
matcher: '',
|
||||
command: 'bash',
|
||||
args: ['-c', 'ccw tool exec skill_context_loader --stdin --mode auto'],
|
||||
command: 'node',
|
||||
args: ['-e', "const p=JSON.parse(process.env.HOOK_INPUT||'{}');require('child_process').spawnSync('ccw',['tool','exec','skill_context_loader',JSON.stringify({mode:'auto',prompt:p.user_prompt||''})],{stdio:'inherit'})"],
|
||||
description: 'Auto-detect and load SKILL based on skill name in prompt',
|
||||
category: 'skill',
|
||||
configurable: false
|
||||
@@ -195,6 +196,7 @@ const WIZARD_TEMPLATES = {
|
||||
}
|
||||
],
|
||||
configFields: [
|
||||
{ key: 'tool', type: 'select', label: 'CLI Tool', default: 'gemini', options: ['gemini', 'qwen', 'codex', 'opencode'], description: 'CLI tool for CLAUDE.md generation' },
|
||||
{ key: 'threshold', type: 'number', label: 'Threshold (paths)', default: 5, min: 1, max: 20, step: 1, description: 'Number of paths to trigger batch update' },
|
||||
{ key: 'timeout', type: 'number', label: 'Timeout (seconds)', default: 300, min: 60, max: 1800, step: 60, description: 'Auto-flush queue after this time' }
|
||||
]
|
||||
@@ -748,6 +750,7 @@ function renderWizardModalContent() {
|
||||
// Helper to get translated field labels
|
||||
const getFieldLabel = (fieldKey) => {
|
||||
const labels = {
|
||||
'tool': t('hook.wizard.cliTool') || 'CLI Tool',
|
||||
'threshold': t('hook.wizard.thresholdPaths') || 'Threshold (paths)',
|
||||
'timeout': t('hook.wizard.timeoutSeconds') || 'Timeout (seconds)'
|
||||
};
|
||||
@@ -756,6 +759,7 @@ function renderWizardModalContent() {
|
||||
|
||||
const getFieldDesc = (fieldKey) => {
|
||||
const descs = {
|
||||
'tool': t('hook.wizard.cliToolDesc') || 'CLI tool for CLAUDE.md generation',
|
||||
'threshold': t('hook.wizard.thresholdPathsDesc') || 'Number of paths to trigger batch update',
|
||||
'timeout': t('hook.wizard.timeoutSecondsDesc') || 'Auto-flush queue after this time'
|
||||
};
|
||||
@@ -1121,20 +1125,19 @@ function generateWizardCommand() {
|
||||
keywords: c.keywords.split(',').map(k => k.trim()).filter(k => k)
|
||||
}));
|
||||
|
||||
const params = JSON.stringify({ configs: configJson, prompt: '$CLAUDE_PROMPT' });
|
||||
return `ccw tool exec skill_context_loader '${params}'`;
|
||||
// Use node + spawnSync for cross-platform JSON handling
|
||||
const paramsObj = { configs: configJson, prompt: '${p.user_prompt}' };
|
||||
return `node -e "const p=JSON.parse(process.env.HOOK_INPUT||'{}');require('child_process').spawnSync('ccw',['tool','exec','skill_context_loader',JSON.stringify(${JSON.stringify(paramsObj).replace('${p.user_prompt}', "'+p.user_prompt+'")})],{stdio:'inherit'})"`;
|
||||
} else {
|
||||
// auto mode
|
||||
const params = JSON.stringify({ mode: 'auto', prompt: '$CLAUDE_PROMPT' });
|
||||
return `ccw tool exec skill_context_loader '${params}'`;
|
||||
// auto mode - use node + spawnSync
|
||||
return `node -e "const p=JSON.parse(process.env.HOOK_INPUT||'{}');require('child_process').spawnSync('ccw',['tool','exec','skill_context_loader',JSON.stringify({mode:'auto',prompt:p.user_prompt||''})],{stdio:'inherit'})"`;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle memory-update wizard (default)
|
||||
// Now uses memory_queue for batched updates with configurable threshold/timeout
|
||||
// The command adds to queue, configuration is applied separately via submitHookWizard
|
||||
const params = `"{\\"action\\":\\"add\\",\\"path\\":\\"$CLAUDE_PROJECT_DIR\\"}"`;
|
||||
return `ccw tool exec memory_queue ${params}`;
|
||||
// Use node + spawnSync for cross-platform JSON handling
|
||||
const selectedTool = wizardConfig.tool || 'gemini';
|
||||
return `node -e "require('child_process').spawnSync(process.platform==='win32'?'cmd':'ccw',process.platform==='win32'?['/c','ccw','tool','exec','memory_queue',JSON.stringify({action:'add',path:process.env.CLAUDE_PROJECT_DIR,tool:'${selectedTool}'})]:['tool','exec','memory_queue',JSON.stringify({action:'add',path:process.env.CLAUDE_PROJECT_DIR,tool:'${selectedTool}'})],{stdio:'inherit'})"`;
|
||||
}
|
||||
|
||||
async function submitHookWizard() {
|
||||
@@ -1217,13 +1220,18 @@ async function submitHookWizard() {
|
||||
const baseTemplate = HOOK_TEMPLATES[selectedOption.templateId];
|
||||
if (!baseTemplate) return;
|
||||
|
||||
const command = generateWizardCommand();
|
||||
|
||||
const hookData = {
|
||||
command: 'bash',
|
||||
args: ['-c', command]
|
||||
// Build hook data with configured values
|
||||
let hookData = {
|
||||
command: baseTemplate.command,
|
||||
args: [...baseTemplate.args]
|
||||
};
|
||||
|
||||
// For memory-update wizard, use configured tool in args (cross-platform)
|
||||
if (wizard.id === 'memory-update') {
|
||||
const selectedTool = wizardConfig.tool || 'gemini';
|
||||
hookData.args = ['-e', `require('child_process').spawnSync(process.platform==='win32'?'cmd':'ccw',process.platform==='win32'?['/c','ccw','tool','exec','memory_queue',JSON.stringify({action:'add',path:process.env.CLAUDE_PROJECT_DIR,tool:'${selectedTool}'})]:['tool','exec','memory_queue',JSON.stringify({action:'add',path:process.env.CLAUDE_PROJECT_DIR,tool:'${selectedTool}'})],{stdio:'inherit'})`];
|
||||
}
|
||||
|
||||
if (baseTemplate.matcher) {
|
||||
hookData.matcher = baseTemplate.matcher;
|
||||
}
|
||||
@@ -1232,6 +1240,7 @@ async function submitHookWizard() {
|
||||
|
||||
// For memory-update wizard, also configure queue settings
|
||||
if (wizard.id === 'memory-update') {
|
||||
const selectedTool = wizardConfig.tool || 'gemini';
|
||||
const threshold = wizardConfig.threshold || 5;
|
||||
const timeout = wizardConfig.timeout || 300;
|
||||
try {
|
||||
@@ -1242,7 +1251,7 @@ async function submitHookWizard() {
|
||||
body: JSON.stringify({ tool: 'memory_queue', params: configParams })
|
||||
});
|
||||
if (response.ok) {
|
||||
showRefreshToast(`Queue configured: threshold=${threshold}, timeout=${timeout}s`, 'success');
|
||||
showRefreshToast(`Queue configured: tool=${selectedTool}, threshold=${threshold}, timeout=${timeout}s`, 'success');
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('Failed to configure memory queue:', e);
|
||||
|
||||
@@ -1107,6 +1107,8 @@ const i18n = {
|
||||
'hook.wizard.memoryUpdateDesc': 'Queue-based CLAUDE.md updates with configurable threshold and timeout',
|
||||
'hook.wizard.queueBasedUpdate': 'Queue-Based Update',
|
||||
'hook.wizard.queueBasedUpdateDesc': 'Batch updates when threshold reached or timeout expires',
|
||||
'hook.wizard.cliTool': 'CLI Tool',
|
||||
'hook.wizard.cliToolDesc': 'CLI tool for CLAUDE.md generation',
|
||||
'hook.wizard.thresholdPaths': 'Threshold (paths)',
|
||||
'hook.wizard.thresholdPathsDesc': 'Number of paths to trigger batch update',
|
||||
'hook.wizard.timeoutSeconds': 'Timeout (seconds)',
|
||||
@@ -3347,6 +3349,8 @@ const i18n = {
|
||||
'hook.wizard.memoryUpdateDesc': '基于队列的 CLAUDE.md 更新,支持阈值和超时配置',
|
||||
'hook.wizard.queueBasedUpdate': '队列批量更新',
|
||||
'hook.wizard.queueBasedUpdateDesc': '达到路径数量阈值或超时时批量更新',
|
||||
'hook.wizard.cliTool': 'CLI 工具',
|
||||
'hook.wizard.cliToolDesc': '用于生成 CLAUDE.md 的 CLI 工具',
|
||||
'hook.wizard.thresholdPaths': '阈值(路径数)',
|
||||
'hook.wizard.thresholdPathsDesc': '触发批量更新的路径数量',
|
||||
'hook.wizard.timeoutSeconds': '超时(秒)',
|
||||
|
||||
@@ -362,7 +362,8 @@ function showMultiCliDetailPage(sessionKey) {
|
||||
const container = document.getElementById('mainContent');
|
||||
const metadata = session.metadata || {};
|
||||
const plan = session.plan || {};
|
||||
const tasks = plan.tasks || [];
|
||||
// Use session.tasks (normalized from backend) with fallback to plan.tasks
|
||||
const tasks = session.tasks?.length > 0 ? session.tasks : (plan.tasks || []);
|
||||
const roundCount = metadata.roundId || session.roundCount || 1;
|
||||
const status = session.status || 'analyzing';
|
||||
|
||||
@@ -401,10 +402,6 @@ function showMultiCliDetailPage(sessionKey) {
|
||||
<span class="tab-text">${t('tab.tasks') || 'Tasks'}</span>
|
||||
<span class="tab-count">${tasks.length}</span>
|
||||
</button>
|
||||
<button class="detail-tab" data-tab="plan" onclick="switchMultiCliDetailTab('plan')">
|
||||
<span class="tab-icon"><i data-lucide="ruler" class="w-4 h-4"></i></span>
|
||||
<span class="tab-text">${t('tab.plan') || 'Plan'}</span>
|
||||
</button>
|
||||
<button class="detail-tab" data-tab="discussion" onclick="switchMultiCliDetailTab('discussion')">
|
||||
<span class="tab-icon"><i data-lucide="messages-square" class="w-4 h-4"></i></span>
|
||||
<span class="tab-text">${t('multiCli.tab.discussion') || 'Discussion'}</span>
|
||||
@@ -440,7 +437,8 @@ function showMultiCliDetailPage(sessionKey) {
|
||||
*/
|
||||
function renderMultiCliToolbar(session) {
|
||||
const plan = session.plan;
|
||||
const tasks = plan?.tasks || [];
|
||||
// Use session.tasks (normalized from backend) with fallback to plan.tasks
|
||||
const tasks = session.tasks?.length > 0 ? session.tasks : (plan?.tasks || []);
|
||||
const taskCount = tasks.length;
|
||||
|
||||
let toolbarHtml = `
|
||||
@@ -473,8 +471,8 @@ function renderMultiCliToolbar(session) {
|
||||
toolbarHtml += `
|
||||
<div class="toolbar-task-list">
|
||||
${tasks.map((task, idx) => {
|
||||
const taskTitle = task.title || task.summary || `Task ${idx + 1}`;
|
||||
const taskScope = task.scope || '';
|
||||
const taskTitle = task.title || task.name || task.summary || `Task ${idx + 1}`;
|
||||
const taskScope = task.meta?.scope || task.scope || '';
|
||||
const taskIdValue = task.id || `T${idx + 1}`;
|
||||
|
||||
return `
|
||||
@@ -650,9 +648,6 @@ function switchMultiCliDetailTab(tabName) {
|
||||
case 'tasks':
|
||||
contentArea.innerHTML = renderMultiCliTasksTab(session);
|
||||
break;
|
||||
case 'plan':
|
||||
contentArea.innerHTML = renderMultiCliPlanTab(session);
|
||||
break;
|
||||
case 'discussion':
|
||||
contentArea.innerHTML = renderMultiCliDiscussionSection(session);
|
||||
break;
|
||||
@@ -680,32 +675,91 @@ function switchMultiCliDetailTab(tabName) {
|
||||
// ============================================
|
||||
|
||||
/**
|
||||
* Render Tasks tab - displays tasks from plan.json (same style as lite-plan)
|
||||
* Render Tasks tab - displays plan summary + tasks (same style as lite-plan)
|
||||
* Uses session.tasks (normalized tasks) with fallback to session.plan.tasks
|
||||
*/
|
||||
function renderMultiCliTasksTab(session) {
|
||||
const plan = session.plan || {};
|
||||
const tasks = plan.tasks || [];
|
||||
// Use session.tasks (normalized from backend) with fallback to plan.tasks
|
||||
const tasks = session.tasks?.length > 0 ? session.tasks : (plan.tasks || []);
|
||||
|
||||
// Populate drawer tasks for click-to-open functionality
|
||||
currentDrawerTasks = tasks;
|
||||
|
||||
let sections = [];
|
||||
|
||||
// Extract plan info from multiple sources (plan.json, synthesis, or session)
|
||||
// plan.json: task_description, solution.name, execution_flow
|
||||
// synthesis: solutions[].summary, solutions[].implementation_plan.approach
|
||||
const taskDescription = plan.task_description || session.topicTitle || '';
|
||||
const solutionName = plan.solution?.name || (plan.solutions?.[0]?.name) || '';
|
||||
const solutionSummary = plan.solutions?.[0]?.summary || '';
|
||||
const approach = plan.solutions?.[0]?.implementation_plan?.approach || plan.execution_flow || '';
|
||||
const feasibility = plan.solution?.feasibility || plan.solutions?.[0]?.feasibility;
|
||||
const effort = plan.solution?.effort || plan.solutions?.[0]?.effort || '';
|
||||
const risk = plan.solution?.risk || plan.solutions?.[0]?.risk || '';
|
||||
|
||||
// Plan Summary Section (if any info available)
|
||||
const hasInfo = taskDescription || solutionName || solutionSummary || approach || plan.summary;
|
||||
if (hasInfo) {
|
||||
let planInfo = [];
|
||||
|
||||
// Task description (main objective)
|
||||
if (taskDescription) {
|
||||
planInfo.push(`<p class="plan-summary-text"><strong>${t('plan.objective') || 'Objective'}:</strong> ${escapeHtml(taskDescription)}</p>`);
|
||||
}
|
||||
// Solution name and summary
|
||||
if (solutionName) {
|
||||
planInfo.push(`<p class="plan-solution-text"><strong>${t('plan.solution') || 'Solution'}:</strong> ${escapeHtml(solutionName)}</p>`);
|
||||
}
|
||||
if (solutionSummary) {
|
||||
planInfo.push(`<p class="plan-summary-text">${escapeHtml(solutionSummary)}</p>`);
|
||||
}
|
||||
// Legacy summary field
|
||||
if (plan.summary && !taskDescription && !solutionSummary) {
|
||||
planInfo.push(`<p class="plan-summary-text">${escapeHtml(plan.summary)}</p>`);
|
||||
}
|
||||
// Approach/execution flow
|
||||
if (approach) {
|
||||
planInfo.push(`<p class="plan-approach-text"><strong>${t('plan.approach') || 'Approach'}:</strong> ${escapeHtml(approach)}</p>`);
|
||||
}
|
||||
|
||||
// Metadata badges - concise format
|
||||
let metaBadges = [];
|
||||
if (feasibility) metaBadges.push(`<span class="meta-badge feasibility">${Math.round(feasibility * 100)}%</span>`);
|
||||
if (effort) metaBadges.push(`<span class="meta-badge effort ${escapeHtml(effort)}">${escapeHtml(effort)}</span>`);
|
||||
if (risk) metaBadges.push(`<span class="meta-badge risk ${escapeHtml(risk)}">${escapeHtml(risk)} risk</span>`);
|
||||
// Legacy badges
|
||||
if (plan.severity) metaBadges.push(`<span class="meta-badge severity ${escapeHtml(plan.severity)}">${escapeHtml(plan.severity)}</span>`);
|
||||
if (plan.complexity) metaBadges.push(`<span class="meta-badge complexity">${escapeHtml(plan.complexity)}</span>`);
|
||||
if (plan.estimated_time) metaBadges.push(`<span class="meta-badge time">${escapeHtml(plan.estimated_time)}</span>`);
|
||||
|
||||
sections.push(`
|
||||
<div class="plan-summary-section">
|
||||
${planInfo.join('')}
|
||||
${metaBadges.length ? `<div class="plan-meta-badges">${metaBadges.join(' ')}</div>` : ''}
|
||||
</div>
|
||||
`);
|
||||
}
|
||||
|
||||
// Tasks Section
|
||||
if (tasks.length === 0) {
|
||||
return `
|
||||
sections.push(`
|
||||
<div class="tab-empty-state">
|
||||
<div class="empty-icon"><i data-lucide="clipboard-list" class="w-12 h-12"></i></div>
|
||||
<div class="empty-title">${t('empty.noTasks') || 'No Tasks'}</div>
|
||||
<div class="empty-text">${t('empty.noTasksText') || 'No tasks available for this session.'}</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
return `
|
||||
<div class="tasks-tab-content">
|
||||
`);
|
||||
} else {
|
||||
sections.push(`
|
||||
<div class="tasks-list" id="multiCliTasksListContent">
|
||||
${tasks.map((task, idx) => renderMultiCliTaskItem(session.id, task, idx)).join('')}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
`);
|
||||
}
|
||||
|
||||
return `<div class="tasks-tab-content">${sections.join('')}</div>`;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1375,12 +1429,15 @@ function renderMultiCliTaskItem(sessionId, task, idx) {
|
||||
const taskJsonId = `multi-cli-task-${sessionId}-${taskId}`.replace(/[^a-zA-Z0-9-]/g, '-');
|
||||
taskJsonStore[taskJsonId] = task;
|
||||
|
||||
// Get preview info
|
||||
const action = task.action || '';
|
||||
const scope = task.scope || task.file || '';
|
||||
const modCount = task.modification_points?.length || 0;
|
||||
const implCount = task.implementation?.length || 0;
|
||||
const acceptCount = task.acceptance?.length || 0;
|
||||
// Get preview info - handle both normalized and raw formats
|
||||
// Normalized: meta.type, meta.scope, context.focus_paths, context.acceptance, flow_control.implementation_approach
|
||||
// Raw: action, scope, file, modification_points, implementation, acceptance
|
||||
const taskType = task.meta?.type || task.action || '';
|
||||
const scope = task.meta?.scope || task.scope || task.file || '';
|
||||
const filesCount = task.context?.focus_paths?.length || task.files?.length || task.modification_points?.length || 0;
|
||||
const implCount = task.flow_control?.implementation_approach?.length || task.implementation?.length || 0;
|
||||
const acceptCount = task.context?.acceptance?.length || task.acceptance?.length || task.acceptance_criteria?.length || 0;
|
||||
const dependsCount = task.context?.depends_on?.length || task.depends_on?.length || 0;
|
||||
|
||||
// Escape for data attributes
|
||||
const safeSessionId = escapeHtml(sessionId);
|
||||
@@ -1390,15 +1447,16 @@ function renderMultiCliTaskItem(sessionId, task, idx) {
|
||||
<div class="detail-task-item-full multi-cli-task-item" data-session-id="${safeSessionId}" data-task-id="${safeTaskId}" style="cursor: pointer;" title="Click to view details">
|
||||
<div class="task-item-header-lite">
|
||||
<span class="task-id-badge">${escapeHtml(taskId)}</span>
|
||||
<span class="task-title">${escapeHtml(task.title || task.summary || 'Untitled')}</span>
|
||||
<span class="task-title">${escapeHtml(task.title || task.name || task.summary || 'Untitled')}</span>
|
||||
<button class="btn-view-json" data-task-json-id="${taskJsonId}" data-task-display-id="${safeTaskId}">{ } JSON</button>
|
||||
</div>
|
||||
<div class="task-item-meta-lite">
|
||||
${action ? `<span class="meta-badge action">${escapeHtml(action)}</span>` : ''}
|
||||
${taskType ? `<span class="meta-badge action">${escapeHtml(taskType)}</span>` : ''}
|
||||
${scope ? `<span class="meta-badge scope">${escapeHtml(scope)}</span>` : ''}
|
||||
${modCount > 0 ? `<span class="meta-badge mods">${modCount} mods</span>` : ''}
|
||||
${filesCount > 0 ? `<span class="meta-badge files">${filesCount} files</span>` : ''}
|
||||
${implCount > 0 ? `<span class="meta-badge impl">${implCount} steps</span>` : ''}
|
||||
${acceptCount > 0 ? `<span class="meta-badge accept">${acceptCount} acceptance</span>` : ''}
|
||||
${acceptCount > 0 ? `<span class="meta-badge accept">${acceptCount} criteria</span>` : ''}
|
||||
${dependsCount > 0 ? `<span class="meta-badge depends">${dependsCount} deps</span>` : ''}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@@ -1442,16 +1500,18 @@ function initMultiCliTaskClickHandlers() {
|
||||
*/
|
||||
function openTaskDrawerForMultiCli(sessionId, taskId) {
|
||||
const session = liteTaskDataStore[currentSessionDetailKey];
|
||||
if (!session || !session.plan) return;
|
||||
if (!session) return;
|
||||
|
||||
const task = session.plan.tasks?.find(t => (t.id || `T${session.plan.tasks.indexOf(t) + 1}`) === taskId);
|
||||
// Use session.tasks (normalized from backend) with fallback to plan.tasks
|
||||
const tasks = session.tasks?.length > 0 ? session.tasks : (session.plan?.tasks || []);
|
||||
const task = tasks.find(t => (t.id || `T${tasks.indexOf(t) + 1}`) === taskId);
|
||||
if (!task) return;
|
||||
|
||||
// Set current drawer tasks
|
||||
currentDrawerTasks = session.plan.tasks || [];
|
||||
currentDrawerTasks = tasks;
|
||||
window._currentDrawerSession = session;
|
||||
|
||||
document.getElementById('drawerTaskTitle').textContent = task.title || task.summary || taskId;
|
||||
document.getElementById('drawerTaskTitle').textContent = task.title || task.name || task.summary || taskId;
|
||||
document.getElementById('drawerContent').innerHTML = renderMultiCliTaskDrawerContent(task, session);
|
||||
document.getElementById('taskDetailDrawer').classList.add('open');
|
||||
document.getElementById('drawerOverlay').classList.add('active');
|
||||
@@ -1523,12 +1583,21 @@ function switchMultiCliDrawerTab(tabName) {
|
||||
|
||||
/**
|
||||
* Render multi-cli task overview section
|
||||
* Handles both normalized format (meta, context, flow_control) and raw format
|
||||
*/
|
||||
function renderMultiCliTaskOverview(task) {
|
||||
let sections = [];
|
||||
|
||||
// Description Card
|
||||
if (task.description) {
|
||||
// Extract from both normalized and raw formats
|
||||
const description = task.description || (task.context?.requirements?.length > 0 ? task.context.requirements.join('\n') : '');
|
||||
const scope = task.meta?.scope || task.scope || task.file || '';
|
||||
const acceptance = task.context?.acceptance || task.acceptance || task.acceptance_criteria || [];
|
||||
const dependsOn = task.context?.depends_on || task.depends_on || [];
|
||||
const focusPaths = task.context?.focus_paths || task.files?.map(f => typeof f === 'string' ? f : f.file) || [];
|
||||
const keyPoint = task._raw?.task?.key_point || task.key_point || '';
|
||||
|
||||
// Description/Key Point Card
|
||||
if (description || keyPoint) {
|
||||
sections.push(`
|
||||
<div class="lite-card">
|
||||
<div class="lite-card-header">
|
||||
@@ -1536,14 +1605,15 @@ function renderMultiCliTaskOverview(task) {
|
||||
<h4 class="lite-card-title">Description</h4>
|
||||
</div>
|
||||
<div class="lite-card-body">
|
||||
<p class="lite-description">${escapeHtml(task.description)}</p>
|
||||
${keyPoint ? `<p class="lite-key-point"><strong>Key Point:</strong> ${escapeHtml(keyPoint)}</p>` : ''}
|
||||
${description ? `<p class="lite-description">${escapeHtml(description)}</p>` : ''}
|
||||
</div>
|
||||
</div>
|
||||
`);
|
||||
}
|
||||
|
||||
// Scope Card
|
||||
if (task.scope || task.file) {
|
||||
if (scope) {
|
||||
sections.push(`
|
||||
<div class="lite-card">
|
||||
<div class="lite-card-header">
|
||||
@@ -1552,15 +1622,49 @@ function renderMultiCliTaskOverview(task) {
|
||||
</div>
|
||||
<div class="lite-card-body">
|
||||
<div class="lite-scope-box">
|
||||
<code>${escapeHtml(task.scope || task.file)}</code>
|
||||
<code>${escapeHtml(scope)}</code>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`);
|
||||
}
|
||||
|
||||
// Dependencies Card
|
||||
if (dependsOn.length > 0) {
|
||||
sections.push(`
|
||||
<div class="lite-card">
|
||||
<div class="lite-card-header">
|
||||
<span class="lite-card-icon">🔗</span>
|
||||
<h4 class="lite-card-title">Dependencies</h4>
|
||||
</div>
|
||||
<div class="lite-card-body">
|
||||
<div class="lite-deps-list">
|
||||
${dependsOn.map(dep => `<span class="dep-badge">${escapeHtml(dep)}</span>`).join('')}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`);
|
||||
}
|
||||
|
||||
// Focus Paths / Files Card
|
||||
if (focusPaths.length > 0) {
|
||||
sections.push(`
|
||||
<div class="lite-card">
|
||||
<div class="lite-card-header">
|
||||
<span class="lite-card-icon">📁</span>
|
||||
<h4 class="lite-card-title">Target Files</h4>
|
||||
</div>
|
||||
<div class="lite-card-body">
|
||||
<ul class="lite-file-list">
|
||||
${focusPaths.map(f => `<li><code>${escapeHtml(f)}</code></li>`).join('')}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
`);
|
||||
}
|
||||
|
||||
// Acceptance Criteria Card
|
||||
if (task.acceptance?.length) {
|
||||
if (acceptance.length > 0) {
|
||||
sections.push(`
|
||||
<div class="lite-card">
|
||||
<div class="lite-card-header">
|
||||
@@ -1569,7 +1673,7 @@ function renderMultiCliTaskOverview(task) {
|
||||
</div>
|
||||
<div class="lite-card-body">
|
||||
<ul class="lite-acceptance-list">
|
||||
${task.acceptance.map(ac => `<li>${escapeHtml(ac)}</li>`).join('')}
|
||||
${acceptance.map(ac => `<li>${escapeHtml(ac)}</li>`).join('')}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1599,12 +1703,35 @@ function renderMultiCliTaskOverview(task) {
|
||||
|
||||
/**
|
||||
* Render multi-cli task implementation section
|
||||
* Handles both normalized format (flow_control.implementation_approach) and raw format
|
||||
*/
|
||||
function renderMultiCliTaskImplementation(task) {
|
||||
let sections = [];
|
||||
|
||||
// Modification Points
|
||||
if (task.modification_points?.length) {
|
||||
// Get implementation steps from normalized or raw format
|
||||
const implApproach = task.flow_control?.implementation_approach || [];
|
||||
const rawImpl = task.implementation || [];
|
||||
const modPoints = task.modification_points || [];
|
||||
|
||||
// Modification Points / Flow Control Implementation Approach
|
||||
if (implApproach.length > 0) {
|
||||
sections.push(`
|
||||
<div class="drawer-section">
|
||||
<h4 class="drawer-section-title">
|
||||
<i data-lucide="list-ordered" class="w-4 h-4"></i>
|
||||
Implementation Steps
|
||||
</h4>
|
||||
<ol class="impl-steps-detail-list">
|
||||
${implApproach.map((step, idx) => `
|
||||
<li class="impl-step-item">
|
||||
<span class="step-num">${step.step || (idx + 1)}</span>
|
||||
<span class="step-text">${escapeHtml(step.action || step)}</span>
|
||||
</li>
|
||||
`).join('')}
|
||||
</ol>
|
||||
</div>
|
||||
`);
|
||||
} else if (modPoints.length > 0) {
|
||||
sections.push(`
|
||||
<div class="drawer-section">
|
||||
<h4 class="drawer-section-title">
|
||||
@@ -1612,7 +1739,7 @@ function renderMultiCliTaskImplementation(task) {
|
||||
Modification Points
|
||||
</h4>
|
||||
<ul class="mod-points-detail-list">
|
||||
${task.modification_points.map(mp => `
|
||||
${modPoints.map(mp => `
|
||||
<li class="mod-point-item">
|
||||
<code class="mod-file">${escapeHtml(mp.file || '')}</code>
|
||||
${mp.target ? `<span class="mod-target">→ ${escapeHtml(mp.target)}</span>` : ''}
|
||||
@@ -1625,8 +1752,8 @@ function renderMultiCliTaskImplementation(task) {
|
||||
`);
|
||||
}
|
||||
|
||||
// Implementation Steps
|
||||
if (task.implementation?.length) {
|
||||
// Raw Implementation Steps (if not already rendered via implApproach)
|
||||
if (rawImpl.length > 0 && implApproach.length === 0) {
|
||||
sections.push(`
|
||||
<div class="drawer-section">
|
||||
<h4 class="drawer-section-title">
|
||||
@@ -1634,7 +1761,7 @@ function renderMultiCliTaskImplementation(task) {
|
||||
Implementation Steps
|
||||
</h4>
|
||||
<ol class="impl-steps-detail-list">
|
||||
${task.implementation.map((step, idx) => `
|
||||
${rawImpl.map((step, idx) => `
|
||||
<li class="impl-step-item">
|
||||
<span class="step-num">${idx + 1}</span>
|
||||
<span class="step-text">${escapeHtml(step)}</span>
|
||||
@@ -1665,10 +1792,26 @@ function renderMultiCliTaskImplementation(task) {
|
||||
|
||||
/**
|
||||
* Render multi-cli task files section
|
||||
* Handles both normalized format (context.focus_paths) and raw format
|
||||
*/
|
||||
function renderMultiCliTaskFiles(task) {
|
||||
const files = [];
|
||||
|
||||
// Collect from normalized format (context.focus_paths)
|
||||
if (task.context?.focus_paths) {
|
||||
task.context.focus_paths.forEach(f => {
|
||||
if (f && !files.includes(f)) files.push(f);
|
||||
});
|
||||
}
|
||||
|
||||
// Collect from raw files array (plan.json format)
|
||||
if (task.files) {
|
||||
task.files.forEach(f => {
|
||||
const filePath = typeof f === 'string' ? f : f.file;
|
||||
if (filePath && !files.includes(filePath)) files.push(filePath);
|
||||
});
|
||||
}
|
||||
|
||||
// Collect from modification_points
|
||||
if (task.modification_points) {
|
||||
task.modification_points.forEach(mp => {
|
||||
@@ -1676,7 +1819,7 @@ function renderMultiCliTaskFiles(task) {
|
||||
});
|
||||
}
|
||||
|
||||
// Collect from scope/file
|
||||
// Collect from scope/file (legacy)
|
||||
if (task.scope && !files.includes(task.scope)) files.push(task.scope);
|
||||
if (task.file && !files.includes(task.file)) files.push(task.file);
|
||||
|
||||
|
||||
@@ -391,11 +391,7 @@ async function execute(params) {
|
||||
if (timeoutCheck.flushed) {
|
||||
// Queue was flushed due to timeout, add to fresh queue
|
||||
const result = addToQueue(path, { tool, strategy });
|
||||
return {
|
||||
...result,
|
||||
timeoutFlushed: true,
|
||||
flushResult: timeoutCheck.result
|
||||
};
|
||||
return `[MemoryQueue] Timeout flush (${timeoutCheck.result.processed} items) → ${result.message}`;
|
||||
}
|
||||
|
||||
const addResult = addToQueue(path, { tool, strategy });
|
||||
@@ -403,14 +399,12 @@ async function execute(params) {
|
||||
// Auto-flush if threshold reached
|
||||
if (addResult.willFlush) {
|
||||
const flushResult = await flushQueue();
|
||||
return {
|
||||
...addResult,
|
||||
flushed: true,
|
||||
flushResult
|
||||
};
|
||||
// Return string for hook-friendly output
|
||||
return `[MemoryQueue] ${addResult.message} → Flushed ${flushResult.processed} items`;
|
||||
}
|
||||
|
||||
return addResult;
|
||||
// Return string for hook-friendly output
|
||||
return `[MemoryQueue] ${addResult.message}`;
|
||||
|
||||
case 'status':
|
||||
// Check timeout first
|
||||
|
||||
Reference in New Issue
Block a user