mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-03-21 19:08:17 +08:00
feat: add workflow-tune skill for multi-step workflow pipeline optimization
New skill that executes workflow step chains sequentially, inspects artifacts, analyzes quality via ccw cli resume chain (Gemini), and generates optimization reports. Supports 4 input formats: pipe-separated commands, comma-separated skills, JSON file, and natural language with semantic decomposition. Key features: - Orchestrator + 5-phase progressive loading architecture - Intent-to-tool mapping with ambiguity resolution for NL input - Command document generation with pre-execution confirmation loop - Per-step analysis + cross-step synthesis via resume chain - Auto-fix with user confirmation safety gate Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
454
.claude/skills/workflow-tune/phases/01-setup.md
Normal file
454
.claude/skills/workflow-tune/phases/01-setup.md
Normal file
@@ -0,0 +1,454 @@
|
||||
# Phase 1: Setup
|
||||
|
||||
Initialize workspace, parse workflow definition, semantic decomposition for natural language, generate command document, user confirmation, create state and process log.
|
||||
|
||||
## Objective
|
||||
|
||||
- Parse workflow steps from user input (Format 1-3: direct parse, Format 4: semantic decomposition)
|
||||
- Generate Command Document (formatted execution plan)
|
||||
- User confirmation: Execute / Edit steps / Cancel
|
||||
- Validate step commands/skill paths
|
||||
- Create isolated workspace directory
|
||||
- Initialize workflow-state.json and process-log.md
|
||||
|
||||
## Execution
|
||||
|
||||
### Step 1.1: Parse Input
|
||||
|
||||
```javascript
|
||||
const args = $ARGUMENTS.trim();
|
||||
|
||||
// Detect input format
|
||||
let steps = [];
|
||||
let workflowName = 'unnamed-workflow';
|
||||
let workflowContext = '';
|
||||
|
||||
// Format 1: JSON file (--file path)
|
||||
const fileMatch = args.match(/--file\s+"?([^\s"]+)"?/);
|
||||
if (fileMatch) {
|
||||
const wfDef = JSON.parse(Read(fileMatch[1]));
|
||||
workflowName = wfDef.name || 'unnamed-workflow';
|
||||
workflowContext = wfDef.description || '';
|
||||
steps = wfDef.steps;
|
||||
}
|
||||
|
||||
// Format 2: Pipe-separated commands ("cmd1 | cmd2 | cmd3")
|
||||
else if (args.includes('|')) {
|
||||
const rawSteps = args.split(/(?:--context|--depth|-y|--yes|--auto-fix)\s+("[^"]*"|\S+)/)[0];
|
||||
steps = rawSteps.split('|').map((cmd, i) => ({
|
||||
name: `step-${i + 1}`,
|
||||
type: cmd.trim().startsWith('/') ? 'skill'
|
||||
: cmd.trim().startsWith('ccw cli') ? 'ccw-cli'
|
||||
: 'command',
|
||||
command: cmd.trim(),
|
||||
expected_artifacts: [],
|
||||
success_criteria: ''
|
||||
}));
|
||||
}
|
||||
|
||||
// Format 3: Comma-separated skill names (matches pattern: word,word or word-word,word-word)
|
||||
else if (/^[\w-]+(,[\w-]+)+/.test(args.split(/\s/)[0])) {
|
||||
const skillPart = args.match(/^([^\s]+)/);
|
||||
const skillNames = skillPart ? skillPart[1].split(',') : [];
|
||||
steps = skillNames.map((name, i) => {
|
||||
const skillPath = name.startsWith('.claude/') ? name : `.claude/skills/${name}`;
|
||||
return {
|
||||
name: name.replace('.claude/skills/', ''),
|
||||
type: 'skill',
|
||||
command: `/${name.replace('.claude/skills/', '')}`,
|
||||
skill_path: skillPath,
|
||||
expected_artifacts: [],
|
||||
success_criteria: ''
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
// Format 4: Natural language → semantic decomposition
|
||||
else {
|
||||
inputFormat = 'natural-language';
|
||||
naturalLanguageInput = args.replace(/--\w+\s+"[^"]*"/g, '').replace(/--\w+\s+\S+/g, '').replace(/-y|--yes/g, '').trim();
|
||||
// Steps will be populated in Step 1.1b
|
||||
steps = [];
|
||||
}
|
||||
|
||||
// Parse --context
|
||||
const contextMatch = args.match(/--context\s+"([^"]+)"/);
|
||||
workflowContext = contextMatch ? contextMatch[1] : workflowContext;
|
||||
|
||||
// Parse --depth
|
||||
const depthMatch = args.match(/--depth\s+(quick|standard|deep)/);
|
||||
if (depthMatch) {
|
||||
workflowPreferences.analysisDepth = depthMatch[1];
|
||||
}
|
||||
|
||||
// If no context provided, ask user
|
||||
if (!workflowContext) {
|
||||
const response = AskUserQuestion({
|
||||
questions: [{
|
||||
question: "请描述这个 workflow 的目标和预期效果:",
|
||||
header: "Workflow Context",
|
||||
multiSelect: false,
|
||||
options: [
|
||||
{ label: "General quality check", description: "通用质量检查,评估步骤间衔接" },
|
||||
{ label: "Custom description", description: "自定义描述 workflow 目标" }
|
||||
]
|
||||
}]
|
||||
});
|
||||
workflowContext = response["Workflow Context"];
|
||||
}
|
||||
```
|
||||
|
||||
### Step 1.1b: Semantic Decomposition (Format 4 only)
|
||||
|
||||
> Skip this step if `inputFormat !== 'natural-language'`.
|
||||
|
||||
Decompose natural language input into a structured step chain by identifying intent verbs and mapping them to available tools/skills.
|
||||
|
||||
```javascript
|
||||
if (inputFormat === 'natural-language') {
|
||||
// Intent-to-tool mapping (regex patterns → tool config)
|
||||
const intentMap = [
|
||||
{ pattern: /分析|analyze|审查|inspect|scan/i, name: 'analyze', tool: 'gemini', mode: 'analysis', rule: 'analysis-analyze-code-patterns' },
|
||||
{ pattern: /评审|review|code.?review/i, name: 'review', tool: 'gemini', mode: 'analysis', rule: 'analysis-review-code-quality' },
|
||||
{ pattern: /诊断|debug|排查|diagnose/i, name: 'diagnose', tool: 'gemini', mode: 'analysis', rule: 'analysis-diagnose-bug-root-cause' },
|
||||
{ pattern: /安全|security|漏洞|vulnerability/i, name: 'security-audit', tool: 'gemini', mode: 'analysis', rule: 'analysis-assess-security-risks' },
|
||||
{ pattern: /性能|performance|perf/i, name: 'perf-analysis', tool: 'gemini', mode: 'analysis', rule: 'analysis-analyze-performance' },
|
||||
{ pattern: /架构|architecture/i, name: 'arch-review', tool: 'gemini', mode: 'analysis', rule: 'analysis-review-architecture' },
|
||||
{ pattern: /修复|fix|repair|解决/i, name: 'fix', tool: 'claude', mode: 'write', rule: 'development-debug-runtime-issues' },
|
||||
{ pattern: /实现|implement|开发|create|新增/i, name: 'implement', tool: 'claude', mode: 'write', rule: 'development-implement-feature' },
|
||||
{ pattern: /重构|refactor/i, name: 'refactor', tool: 'claude', mode: 'write', rule: 'development-refactor-codebase' },
|
||||
{ pattern: /测试|test|generate.?test/i, name: 'test', tool: 'claude', mode: 'write', rule: 'development-generate-tests' },
|
||||
{ pattern: /规划|plan|设计|design/i, name: 'plan', tool: 'gemini', mode: 'analysis', rule: 'planning-plan-architecture-design' },
|
||||
{ pattern: /拆解|breakdown|分解/i, name: 'breakdown', tool: 'gemini', mode: 'analysis', rule: 'planning-breakdown-task-steps' },
|
||||
];
|
||||
|
||||
// Segment input by Chinese/English delimiters: 、,,;然后/接着/最后/之后 etc.
|
||||
const segments = naturalLanguageInput
|
||||
.split(/[,,;;、]|(?:然后|接着|之后|最后|再|并|and then|then|finally|next)\s*/i)
|
||||
.map(s => s.trim())
|
||||
.filter(Boolean);
|
||||
|
||||
// Match each segment to an intent (with ambiguity resolution)
|
||||
steps = segments.map((segment, i) => {
|
||||
const allMatches = intentMap.filter(m => m.pattern.test(segment));
|
||||
let matched = allMatches[0] || null;
|
||||
|
||||
// ★ Ambiguity resolution: if multiple intents match, ask user
|
||||
if (allMatches.length > 1) {
|
||||
const disambig = AskUserQuestion({
|
||||
questions: [{
|
||||
question: `"${segment}" 匹配到多个意图,请选择最符合的:`,
|
||||
header: `Disambiguate Step ${i + 1}`,
|
||||
multiSelect: false,
|
||||
options: allMatches.map(m => ({
|
||||
label: m.name,
|
||||
description: `Tool: ${m.tool}, Mode: ${m.mode}, Rule: ${m.rule}`
|
||||
}))
|
||||
}]
|
||||
});
|
||||
const chosen = disambig[`Disambiguate Step ${i + 1}`];
|
||||
matched = allMatches.find(m => m.name === chosen) || allMatches[0];
|
||||
}
|
||||
|
||||
if (matched) {
|
||||
// Extract target scope from segment (e.g., "分析 src 目录" → scope = "src")
|
||||
const scopeMatch = segment.match(/(?:目录|文件|模块|directory|file|module)?\s*[::]?\s*(\S+)/);
|
||||
const scope = scopeMatch ? scopeMatch[1].replace(/[的地得]$/, '') : '**/*';
|
||||
|
||||
return {
|
||||
name: `${matched.name}`,
|
||||
type: 'ccw-cli',
|
||||
command: `ccw cli -p "${segment}" --tool ${matched.tool} --mode ${matched.mode} --rule ${matched.rule}`,
|
||||
tool: matched.tool,
|
||||
mode: matched.mode,
|
||||
rule: matched.rule,
|
||||
original_text: segment,
|
||||
expected_artifacts: [],
|
||||
success_criteria: ''
|
||||
};
|
||||
} else {
|
||||
// Unmatched segment → generic analysis step
|
||||
return {
|
||||
name: `step-${i + 1}`,
|
||||
type: 'ccw-cli',
|
||||
command: `ccw cli -p "${segment}" --tool gemini --mode analysis`,
|
||||
tool: 'gemini',
|
||||
mode: 'analysis',
|
||||
rule: 'universal-rigorous-style',
|
||||
original_text: segment,
|
||||
expected_artifacts: [],
|
||||
success_criteria: ''
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
// Deduplicate: if same intent name appears twice, suffix with index
|
||||
const nameCount = {};
|
||||
steps.forEach(s => {
|
||||
nameCount[s.name] = (nameCount[s.name] || 0) + 1;
|
||||
if (nameCount[s.name] > 1) {
|
||||
s.name = `${s.name}-${nameCount[s.name]}`;
|
||||
}
|
||||
});
|
||||
|
||||
// Set workflow context from the full natural language input
|
||||
if (!workflowContext) {
|
||||
workflowContext = naturalLanguageInput;
|
||||
}
|
||||
workflowName = 'nl-workflow'; // natural language derived
|
||||
}
|
||||
```
|
||||
|
||||
### Step 1.1c: Generate Command Document
|
||||
|
||||
Generate a formatted execution plan for user review. This runs for ALL input formats, not just Format 4.
|
||||
|
||||
```javascript
|
||||
function generateCommandDoc(steps, workflowName, workflowContext, analysisDepth) {
|
||||
const stepTable = steps.map((s, i) => {
|
||||
const tool = s.tool || (s.type === 'skill' ? '-' : 'claude');
|
||||
const mode = s.mode || (s.type === 'skill' ? '-' : 'write');
|
||||
const cmdPreview = s.command.length > 60 ? s.command.substring(0, 57) + '...' : s.command;
|
||||
return `| ${i + 1} | ${s.name} | ${s.type} | \`${cmdPreview}\` | ${tool} | ${mode} |`;
|
||||
}).join('\n');
|
||||
|
||||
const flowDiagram = steps.map((s, i) => {
|
||||
const arrow = i < steps.length - 1 ? '\n ↓' : '';
|
||||
const feedsInto = i < steps.length - 1 ? `Feeds into: Step ${i + 2} (${steps[i + 1].name})` : 'Final step';
|
||||
const originalText = s.original_text ? `\n Source: "${s.original_text}"` : '';
|
||||
return `Step ${i + 1}: ${s.name}
|
||||
Command: ${s.command}
|
||||
Type: ${s.type} | Tool: ${s.tool || '-'} | Mode: ${s.mode || '-'}${originalText}
|
||||
${feedsInto}${arrow}`;
|
||||
}).join('\n');
|
||||
|
||||
const totalCli = steps.filter(s => s.type === 'ccw-cli').length;
|
||||
const totalSkill = steps.filter(s => s.type === 'skill').length;
|
||||
const totalCmd = steps.filter(s => s.type === 'command').length;
|
||||
|
||||
return `# Workflow Tune — Execution Plan
|
||||
|
||||
**Workflow**: ${workflowName}
|
||||
**Goal**: ${workflowContext}
|
||||
**Steps**: ${steps.length}
|
||||
**Analysis Depth**: ${analysisDepth}
|
||||
|
||||
## Step Chain
|
||||
|
||||
| # | Name | Type | Command | Tool | Mode |
|
||||
|---|------|------|---------|------|------|
|
||||
${stepTable}
|
||||
|
||||
## Execution Flow
|
||||
|
||||
\`\`\`
|
||||
${flowDiagram}
|
||||
\`\`\`
|
||||
|
||||
## Estimated Scope
|
||||
|
||||
- CLI execute calls: ${totalCli}
|
||||
- Skill invocations: ${totalSkill}
|
||||
- Shell commands: ${totalCmd}
|
||||
- Analysis calls (gemini --resume chain): ${steps.length} (per-step) + 1 (synthesis)
|
||||
- Process documentation: process-log.md (accumulated)
|
||||
- Final output: final-report.md with optimization recommendations
|
||||
`;
|
||||
}
|
||||
|
||||
const commandDoc = generateCommandDoc(steps, workflowName, workflowContext, workflowPreferences.analysisDepth);
|
||||
|
||||
// Output command document to user (direct text output)
|
||||
// The orchestrator displays this as formatted text before confirmation
|
||||
```
|
||||
|
||||
### Step 1.1d: Pre-Execution Confirmation
|
||||
|
||||
```javascript
|
||||
// ★ Skip confirmation if -y/--yes auto mode
|
||||
if (!workflowPreferences.autoYes) {
|
||||
// Display commandDoc to user as formatted text output
|
||||
// Then ask for confirmation
|
||||
|
||||
const confirmation = AskUserQuestion({
|
||||
questions: [{
|
||||
question: "确认执行以上 Workflow 调优计划?",
|
||||
header: "Confirm Execution",
|
||||
multiSelect: false,
|
||||
options: [
|
||||
{ label: "Execute (确认执行)", description: "按计划开始执行所有步骤" },
|
||||
{ label: "Edit steps (修改步骤)", description: "调整步骤顺序、增删步骤、更换工具" },
|
||||
{ label: "Cancel (取消)", description: "取消本次调优" }
|
||||
]
|
||||
}]
|
||||
});
|
||||
|
||||
const choice = confirmation["Confirm Execution"];
|
||||
|
||||
if (choice.startsWith("Cancel")) {
|
||||
// Abort: no workspace created, no state written
|
||||
// Output: "Workflow tune cancelled."
|
||||
return;
|
||||
}
|
||||
|
||||
if (choice.startsWith("Edit")) {
|
||||
// Enter edit loop: ask user what to change, apply, re-display, re-confirm
|
||||
let editing = true;
|
||||
while (editing) {
|
||||
const editResponse = AskUserQuestion({
|
||||
questions: [{
|
||||
question: "请描述要修改的内容:\n" +
|
||||
" - 删除步骤: '删除步骤2' 或 'remove step 2'\n" +
|
||||
" - 添加步骤: '在步骤1后加入安全扫描' 或 'add security scan after step 1'\n" +
|
||||
" - 修改工具: '步骤3改用codex' 或 'step 3 use codex'\n" +
|
||||
" - 调换顺序: '步骤2和步骤3互换' 或 'swap step 2 and 3'\n" +
|
||||
" - 修改命令: '步骤1命令改为 ccw cli -p \"...\" --tool gemini'",
|
||||
header: "Edit Steps"
|
||||
}]
|
||||
});
|
||||
|
||||
const editText = editResponse["Edit Steps"];
|
||||
|
||||
// Apply edits to steps[] based on user instruction
|
||||
// The orchestrator interprets the edit instruction and modifies steps:
|
||||
//
|
||||
// Delete: filter out the specified step, re-index
|
||||
// Add: insert new step at specified position
|
||||
// Modify tool: update the step's tool/mode/command
|
||||
// Swap: exchange positions of two steps
|
||||
// Modify command: replace command string
|
||||
//
|
||||
// After applying edits, re-generate command doc and re-display
|
||||
|
||||
const updatedCommandDoc = generateCommandDoc(steps, workflowName, workflowContext, workflowPreferences.analysisDepth);
|
||||
// Display updatedCommandDoc to user
|
||||
|
||||
const reconfirm = AskUserQuestion({
|
||||
questions: [{
|
||||
question: "修改后的计划如上,是否确认?",
|
||||
header: "Confirm Execution",
|
||||
multiSelect: false,
|
||||
options: [
|
||||
{ label: "Execute (确认执行)", description: "按修改后的计划执行" },
|
||||
{ label: "Edit more (继续修改)", description: "还需要调整" },
|
||||
{ label: "Cancel (取消)", description: "取消本次调优" }
|
||||
]
|
||||
}]
|
||||
});
|
||||
|
||||
const reChoice = reconfirm["Confirm Execution"];
|
||||
if (reChoice.startsWith("Execute")) {
|
||||
editing = false;
|
||||
} else if (reChoice.startsWith("Cancel")) {
|
||||
return; // Abort
|
||||
}
|
||||
// else: continue editing loop
|
||||
}
|
||||
}
|
||||
|
||||
// choice === "Execute" → proceed to workspace creation
|
||||
}
|
||||
|
||||
// Save command doc for reference
|
||||
// Will be written to workspace after Step 1.3
|
||||
```
|
||||
|
||||
### Step 1.2: Validate Steps
|
||||
|
||||
```javascript
|
||||
for (const step of steps) {
|
||||
if (step.type === 'skill' && step.skill_path) {
|
||||
const skillFiles = Glob(`${step.skill_path}/SKILL.md`);
|
||||
if (skillFiles.length === 0) {
|
||||
step.validation = 'warning';
|
||||
step.validation_msg = `Skill not found: ${step.skill_path}`;
|
||||
} else {
|
||||
step.validation = 'ok';
|
||||
}
|
||||
} else {
|
||||
// Command-type steps: basic validation (non-empty)
|
||||
step.validation = step.command && step.command.trim() ? 'ok' : 'invalid';
|
||||
}
|
||||
}
|
||||
|
||||
const invalidSteps = steps.filter(s => s.validation === 'invalid');
|
||||
if (invalidSteps.length > 0) {
|
||||
throw new Error(`Invalid steps: ${invalidSteps.map(s => s.name).join(', ')}`);
|
||||
}
|
||||
```
|
||||
|
||||
### Step 1.3: Create Workspace
|
||||
|
||||
```javascript
|
||||
const ts = Date.now();
|
||||
const workDir = `.workflow/.scratchpad/workflow-tune-${ts}`;
|
||||
|
||||
Bash(`mkdir -p "${workDir}/steps"`);
|
||||
|
||||
// Create per-step directories
|
||||
for (let i = 0; i < steps.length; i++) {
|
||||
Bash(`mkdir -p "${workDir}/steps/step-${i + 1}/artifacts"`);
|
||||
}
|
||||
```
|
||||
|
||||
### Step 1.3b: Save Command Document
|
||||
|
||||
```javascript
|
||||
// Save confirmed command doc to workspace for reference
|
||||
Write(`${workDir}/command-doc.md`, commandDoc);
|
||||
```
|
||||
|
||||
### Step 1.4: Initialize State
|
||||
|
||||
```javascript
|
||||
const initialState = {
|
||||
status: 'running',
|
||||
started_at: new Date().toISOString(),
|
||||
updated_at: new Date().toISOString(),
|
||||
workflow_name: workflowName,
|
||||
workflow_context: workflowContext,
|
||||
analysis_depth: workflowPreferences.analysisDepth,
|
||||
auto_fix: workflowPreferences.autoFix,
|
||||
steps: steps.map((s, i) => ({
|
||||
...s,
|
||||
index: i,
|
||||
status: 'pending',
|
||||
execution: null,
|
||||
analysis: null
|
||||
})),
|
||||
analysis_session_id: null, // ccw cli resume chain
|
||||
process_log_entries: [],
|
||||
synthesis: null,
|
||||
errors: [],
|
||||
error_count: 0,
|
||||
max_errors: 3,
|
||||
work_dir: workDir
|
||||
};
|
||||
|
||||
Write(`${workDir}/workflow-state.json`, JSON.stringify(initialState, null, 2));
|
||||
```
|
||||
|
||||
### Step 1.5: Initialize Process Log
|
||||
|
||||
```javascript
|
||||
const processLog = `# Workflow Tune Process Log
|
||||
|
||||
**Workflow**: ${workflowName}
|
||||
**Context**: ${workflowContext}
|
||||
**Steps**: ${steps.length}
|
||||
**Analysis Depth**: ${workflowPreferences.analysisDepth}
|
||||
**Started**: ${new Date().toISOString()}
|
||||
|
||||
---
|
||||
|
||||
`;
|
||||
|
||||
Write(`${workDir}/process-log.md`, processLog);
|
||||
```
|
||||
|
||||
## Output
|
||||
|
||||
- **Variables**: `workDir`, `steps[]`, `workflowContext`, `commandDoc`, initialized state
|
||||
- **Files**: `workflow-state.json`, `process-log.md`, `command-doc.md`, per-step directories
|
||||
- **User Confirmation**: Execution plan confirmed (or cancelled → abort)
|
||||
- **TaskUpdate**: Mark Phase 1 completed, start Step Loop
|
||||
197
.claude/skills/workflow-tune/phases/02-step-execute.md
Normal file
197
.claude/skills/workflow-tune/phases/02-step-execute.md
Normal file
@@ -0,0 +1,197 @@
|
||||
# Phase 2: Execute Step
|
||||
|
||||
> **COMPACT SENTINEL [Phase 2: Execute Step]**
|
||||
> This phase contains 4 execution steps (Step 2.1 -- 2.4).
|
||||
> If you can read this sentinel but cannot find the full Step protocol below, context has been compressed.
|
||||
> Recovery: `Read("phases/02-step-execute.md")`
|
||||
|
||||
Execute a single workflow step and collect its output artifacts.
|
||||
|
||||
## Objective
|
||||
|
||||
- Determine step execution method (skill invoke / ccw cli / shell command)
|
||||
- Execute step with appropriate tool
|
||||
- Collect output artifacts into step directory
|
||||
- Write artifacts manifest
|
||||
|
||||
## Execution
|
||||
|
||||
### Step 2.1: Prepare Step Directory
|
||||
|
||||
```javascript
|
||||
const stepIdx = currentStepIndex; // from orchestrator loop
|
||||
const step = state.steps[stepIdx];
|
||||
const stepDir = `${state.work_dir}/steps/step-${stepIdx + 1}`;
|
||||
const artifactsDir = `${stepDir}/artifacts`;
|
||||
|
||||
// Capture pre-execution state (git status, file timestamps)
|
||||
const preGitStatus = Bash('git status --porcelain 2>/dev/null || echo "not a git repo"').stdout;
|
||||
|
||||
// ★ Warn if dirty git working directory (first step only)
|
||||
if (stepIdx === 0 && preGitStatus.trim() && preGitStatus.trim() !== 'not a git repo') {
|
||||
const dirtyLines = preGitStatus.trim().split('\n').length;
|
||||
// Log warning — artifact collection via git diff may be unreliable
|
||||
// This is informational; does not block execution
|
||||
console.warn(`⚠ Dirty git working directory detected (${dirtyLines} changed files). Artifact collection via git diff may include pre-existing changes.`);
|
||||
}
|
||||
|
||||
const preExecSnapshot = {
|
||||
timestamp: new Date().toISOString(),
|
||||
git_status: preGitStatus,
|
||||
working_files: Glob('**/*.{ts,js,md,json}').slice(0, 50) // sample
|
||||
};
|
||||
Write(`${stepDir}/pre-exec-snapshot.json`, JSON.stringify(preExecSnapshot, null, 2));
|
||||
```
|
||||
|
||||
### Step 2.2: Execute by Step Type
|
||||
|
||||
```javascript
|
||||
let executionResult = { success: false, method: '', output: '', duration: 0 };
|
||||
const startTime = Date.now();
|
||||
|
||||
switch (step.type) {
|
||||
case 'skill': {
|
||||
// Skill invocation — use Skill tool
|
||||
// Extract skill name and arguments from command
|
||||
const skillCmd = step.command.replace(/^\//, '');
|
||||
const [skillName, ...skillArgs] = skillCmd.split(/\s+/);
|
||||
|
||||
// Execute skill (this runs synchronously within current context)
|
||||
// Note: Skill execution produces artifacts in the working directory
|
||||
// We capture changes by comparing pre/post state
|
||||
Skill({
|
||||
name: skillName,
|
||||
arguments: skillArgs.join(' ')
|
||||
});
|
||||
|
||||
executionResult.method = 'skill';
|
||||
executionResult.success = true;
|
||||
break;
|
||||
}
|
||||
|
||||
case 'ccw-cli': {
|
||||
// Direct ccw cli command
|
||||
const cliCommand = step.command;
|
||||
|
||||
Bash({
|
||||
command: cliCommand,
|
||||
run_in_background: true,
|
||||
timeout: 600000 // 10 minutes
|
||||
});
|
||||
|
||||
// STOP — wait for hook callback
|
||||
// After callback:
|
||||
executionResult.method = 'ccw-cli';
|
||||
executionResult.success = true;
|
||||
break;
|
||||
}
|
||||
|
||||
case 'command': {
|
||||
// Generic shell command
|
||||
const result = Bash({
|
||||
command: step.command,
|
||||
timeout: 300000 // 5 minutes
|
||||
});
|
||||
|
||||
executionResult.method = 'command';
|
||||
executionResult.output = result.stdout || '';
|
||||
executionResult.success = result.exitCode === 0;
|
||||
|
||||
// Save command output
|
||||
if (executionResult.output) {
|
||||
Write(`${artifactsDir}/command-output.txt`, executionResult.output);
|
||||
}
|
||||
if (result.stderr) {
|
||||
Write(`${artifactsDir}/command-stderr.txt`, result.stderr);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
executionResult.duration = Date.now() - startTime;
|
||||
```
|
||||
|
||||
### Step 2.3: Collect Artifacts
|
||||
|
||||
```javascript
|
||||
// Capture post-execution state
|
||||
const postExecSnapshot = {
|
||||
timestamp: new Date().toISOString(),
|
||||
git_status: Bash('git status --porcelain 2>/dev/null || echo "not a git repo"').stdout,
|
||||
working_files: Glob('**/*.{ts,js,md,json}').slice(0, 50)
|
||||
};
|
||||
|
||||
// Detect changed/new files by comparing snapshots
|
||||
const preFiles = new Set(preExecSnapshot.working_files);
|
||||
const newOrChanged = postExecSnapshot.working_files.filter(f => !preFiles.has(f));
|
||||
|
||||
// Also check git diff for modified files
|
||||
const gitDiff = Bash('git diff --name-only 2>/dev/null || true').stdout.trim().split('\n').filter(Boolean);
|
||||
|
||||
// Collect all artifacts (new files + git-changed files + declared expected_artifacts)
|
||||
const declaredArtifacts = (step.expected_artifacts || []).filter(f => {
|
||||
// Verify declared artifacts actually exist
|
||||
const exists = Glob(f);
|
||||
return exists.length > 0;
|
||||
}).flatMap(f => Glob(f));
|
||||
|
||||
const allArtifacts = [...new Set([...newOrChanged, ...gitDiff, ...declaredArtifacts])];
|
||||
|
||||
// Copy detected artifacts to step artifacts dir (or record references)
|
||||
const artifactManifest = {
|
||||
step: step.name,
|
||||
step_index: stepIdx,
|
||||
execution_method: executionResult.method,
|
||||
success: executionResult.success,
|
||||
duration_ms: executionResult.duration,
|
||||
artifacts: allArtifacts.map(f => ({
|
||||
path: f,
|
||||
type: f.endsWith('.md') ? 'markdown' : f.endsWith('.json') ? 'json' : 'other',
|
||||
size: 'unknown' // Can be filled by stat if needed
|
||||
})),
|
||||
// For skill type: also check .workflow/.scratchpad for generated files
|
||||
scratchpad_files: step.type === 'skill'
|
||||
? Glob('.workflow/.scratchpad/**/*').filter(f => {
|
||||
// Only include files created after step started
|
||||
return true; // Heuristic: include recent scratchpad files
|
||||
}).slice(0, 20)
|
||||
: [],
|
||||
collected_at: new Date().toISOString()
|
||||
};
|
||||
|
||||
Write(`${stepDir}/artifacts-manifest.json`, JSON.stringify(artifactManifest, null, 2));
|
||||
```
|
||||
|
||||
### Step 2.4: Update State
|
||||
|
||||
```javascript
|
||||
state.steps[stepIdx].status = 'executed';
|
||||
state.steps[stepIdx].execution = {
|
||||
method: executionResult.method,
|
||||
success: executionResult.success,
|
||||
duration_ms: executionResult.duration,
|
||||
artifacts_dir: artifactsDir,
|
||||
manifest_path: `${stepDir}/artifacts-manifest.json`,
|
||||
artifact_count: artifactManifest.artifacts.length,
|
||||
started_at: preExecSnapshot.timestamp,
|
||||
completed_at: new Date().toISOString()
|
||||
};
|
||||
|
||||
state.updated_at = new Date().toISOString();
|
||||
Write(`${state.work_dir}/workflow-state.json`, JSON.stringify(state, null, 2));
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
| Error | Recovery |
|
||||
|-------|----------|
|
||||
| Skill not found | Record failure, set success=false, continue to Phase 3 |
|
||||
| CLI timeout (10min) | Retry once with shorter timeout, then record failure |
|
||||
| Command exit non-zero | Record stderr, set success=false, continue to Phase 3 |
|
||||
| No artifacts detected | Continue to Phase 3 — analysis evaluates step definition quality |
|
||||
|
||||
## Output
|
||||
|
||||
- **Files**: `pre-exec-snapshot.json`, `artifacts-manifest.json`, `artifacts/` (if command type)
|
||||
- **State**: `steps[stepIdx].execution` updated
|
||||
- **Next**: Phase 3 (Analyze Step)
|
||||
318
.claude/skills/workflow-tune/phases/03-step-analyze.md
Normal file
318
.claude/skills/workflow-tune/phases/03-step-analyze.md
Normal file
@@ -0,0 +1,318 @@
|
||||
# Phase 3: Analyze Step
|
||||
|
||||
> **COMPACT SENTINEL [Phase 3: Analyze Step]**
|
||||
> This phase contains 5 execution steps (Step 3.1 -- 3.5).
|
||||
> If you can read this sentinel but cannot find the full Step protocol below, context has been compressed.
|
||||
> Recovery: `Read("phases/03-step-analyze.md")`
|
||||
|
||||
Analyze a completed step's artifacts and quality using `ccw cli --tool gemini --mode analysis`. Uses `--resume` to maintain context across step analyses, building a continuous analysis chain.
|
||||
|
||||
## Objective
|
||||
|
||||
- Inspect step artifacts (file list, content, quality signals)
|
||||
- Build analysis prompt with step context + prior process log
|
||||
- Execute via ccw cli Gemini with resume chain
|
||||
- Parse analysis results → write step-{N}-analysis.md
|
||||
- Append findings to process-log.md
|
||||
- Return updated session ID for resume chain
|
||||
|
||||
## Execution
|
||||
|
||||
### Step 3.1: Inspect Artifacts
|
||||
|
||||
```javascript
|
||||
const stepIdx = currentStepIndex;
|
||||
const step = state.steps[stepIdx];
|
||||
const stepDir = `${state.work_dir}/steps/step-${stepIdx + 1}`;
|
||||
|
||||
// Read artifacts manifest
|
||||
const manifest = JSON.parse(Read(`${stepDir}/artifacts-manifest.json`));
|
||||
|
||||
// Build artifact summary based on analysis depth
|
||||
let artifactSummary = '';
|
||||
|
||||
if (state.analysis_depth === 'quick') {
|
||||
// Quick: just file list and sizes
|
||||
artifactSummary = `Artifacts (${manifest.artifacts.length} files):\n` +
|
||||
manifest.artifacts.map(a => `- ${a.path} (${a.type})`).join('\n');
|
||||
} else {
|
||||
// Standard/Deep: include file content summaries
|
||||
artifactSummary = manifest.artifacts.map(a => {
|
||||
const maxLines = state.analysis_depth === 'deep' ? 300 : 150;
|
||||
try {
|
||||
const content = Read(a.path, { limit: maxLines });
|
||||
return `--- ${a.path} (${a.type}) ---\n${content}`;
|
||||
} catch {
|
||||
return `--- ${a.path} --- [unreadable]`;
|
||||
}
|
||||
}).join('\n\n');
|
||||
|
||||
// Deep: also include scratchpad files
|
||||
if (state.analysis_depth === 'deep' && manifest.scratchpad_files?.length > 0) {
|
||||
artifactSummary += '\n\n--- Scratchpad Files ---\n' +
|
||||
manifest.scratchpad_files.slice(0, 5).map(f => {
|
||||
const content = Read(f, { limit: 100 });
|
||||
return `--- ${f} ---\n${content}`;
|
||||
}).join('\n\n');
|
||||
}
|
||||
}
|
||||
|
||||
// Execution result summary
|
||||
const execSummary = `Execution: ${step.execution.method} | ` +
|
||||
`Success: ${step.execution.success} | ` +
|
||||
`Duration: ${step.execution.duration_ms}ms | ` +
|
||||
`Artifacts: ${manifest.artifacts.length} files`;
|
||||
```
|
||||
|
||||
### Step 3.2: Build Prior Context
|
||||
|
||||
```javascript
|
||||
// Build accumulated process log context for this analysis
|
||||
const priorProcessLog = Read(`${state.work_dir}/process-log.md`);
|
||||
|
||||
// Build step chain context (what came before, what comes after)
|
||||
const stepChainContext = state.steps.map((s, i) => {
|
||||
const status = i < stepIdx ? 'completed' : i === stepIdx ? 'CURRENT' : 'pending';
|
||||
const score = s.analysis?.quality_score || '-';
|
||||
return `${i + 1}. [${status}] ${s.name} (${s.type}) — Quality: ${score}`;
|
||||
}).join('\n');
|
||||
|
||||
// Previous step handoff context (if not first step)
|
||||
let handoffContext = '';
|
||||
if (stepIdx > 0) {
|
||||
const prevStep = state.steps[stepIdx - 1];
|
||||
const prevAnalysis = prevStep.analysis;
|
||||
if (prevAnalysis) {
|
||||
handoffContext = `PREVIOUS STEP OUTPUT SUMMARY:
|
||||
Step "${prevStep.name}" produced ${prevStep.execution?.artifact_count || 0} artifacts.
|
||||
Quality: ${prevAnalysis.quality_score}/100
|
||||
Key outputs: ${prevAnalysis.key_outputs?.join(', ') || 'unknown'}
|
||||
Handoff notes: ${prevAnalysis.handoff_notes || 'none'}`;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Step 3.3: Construct Analysis Prompt
|
||||
|
||||
```javascript
|
||||
// Ref: templates/step-analysis-prompt.md
|
||||
|
||||
const depthInstructions = {
|
||||
quick: 'Provide brief assessment (3-5 bullet points). Focus on: execution success, output completeness, obvious issues.',
|
||||
standard: 'Provide detailed assessment. Cover: execution quality, output completeness, artifact quality, step-to-step handoff readiness, potential issues.',
|
||||
deep: 'Provide exhaustive assessment. Cover: execution quality, output completeness and correctness, artifact quality and structure, step-to-step handoff integrity, error handling, performance signals, architecture implications, edge cases.'
|
||||
};
|
||||
|
||||
const analysisPrompt = `PURPOSE: Analyze the output of workflow step "${step.name}" (step ${stepIdx + 1}/${state.steps.length}) to assess quality, identify issues, and evaluate handoff readiness for the next step.
|
||||
|
||||
WORKFLOW CONTEXT:
|
||||
Name: ${state.workflow_name}
|
||||
Goal: ${state.workflow_context}
|
||||
Step Chain:
|
||||
${stepChainContext}
|
||||
|
||||
CURRENT STEP:
|
||||
Name: ${step.name}
|
||||
Type: ${step.type}
|
||||
Command: ${step.command}
|
||||
${step.success_criteria ? `Success Criteria: ${step.success_criteria}` : ''}
|
||||
|
||||
EXECUTION RESULT:
|
||||
${execSummary}
|
||||
|
||||
${handoffContext}
|
||||
|
||||
STEP ARTIFACTS:
|
||||
${artifactSummary}
|
||||
|
||||
ANALYSIS DEPTH: ${state.analysis_depth}
|
||||
${depthInstructions[state.analysis_depth]}
|
||||
|
||||
TASK:
|
||||
1. Assess step execution quality (did it succeed? complete output?)
|
||||
2. Evaluate artifact quality (content correctness, completeness, format)
|
||||
3. Check handoff readiness (can the next step consume this output?)
|
||||
4. Identify issues, risks, or optimization opportunities
|
||||
5. Rate overall step quality 0-100
|
||||
|
||||
EXPECTED OUTPUT (strict JSON, no markdown):
|
||||
{
|
||||
"quality_score": <0-100>,
|
||||
"execution_assessment": {
|
||||
"success": <true|false>,
|
||||
"completeness": "<complete|partial|failed>",
|
||||
"notes": "<brief assessment>"
|
||||
},
|
||||
"artifact_assessment": {
|
||||
"count": <number>,
|
||||
"quality": "<high|medium|low>",
|
||||
"key_outputs": ["<main output 1>", "<main output 2>"],
|
||||
"missing_outputs": ["<expected but missing>"]
|
||||
},
|
||||
"handoff_assessment": {
|
||||
"ready": <true|false>,
|
||||
"next_step_compatible": <true|false|null>,
|
||||
"handoff_notes": "<what next step should know>"
|
||||
},
|
||||
"issues": [
|
||||
{ "severity": "high|medium|low", "description": "<issue>", "suggestion": "<fix>" }
|
||||
],
|
||||
"optimization_opportunities": [
|
||||
{ "area": "<area>", "description": "<opportunity>", "impact": "high|medium|low" }
|
||||
],
|
||||
"step_summary": "<1-2 sentence summary for process log>"
|
||||
}
|
||||
|
||||
CONSTRAINTS: Be specific, reference artifact content where possible, output ONLY JSON`;
|
||||
```
|
||||
|
||||
### Step 3.4: Execute via ccw cli Gemini with Resume
|
||||
|
||||
```javascript
|
||||
function escapeForShell(str) {
|
||||
return str.replace(/"/g, '\\"').replace(/\$/g, '\\$').replace(/`/g, '\\`');
|
||||
}
|
||||
|
||||
// Build CLI command with optional resume
|
||||
let cliCommand = `ccw cli -p "${escapeForShell(analysisPrompt)}" --tool gemini --mode analysis`;
|
||||
|
||||
// Resume from previous step's analysis session (maintains context chain)
|
||||
if (state.analysis_session_id) {
|
||||
cliCommand += ` --resume ${state.analysis_session_id}`;
|
||||
}
|
||||
|
||||
Bash({
|
||||
command: cliCommand,
|
||||
run_in_background: true,
|
||||
timeout: 300000 // 5 minutes
|
||||
});
|
||||
|
||||
// STOP — wait for hook callback
|
||||
```
|
||||
|
||||
### Step 3.5: Parse Results and Update Process Log
|
||||
|
||||
After CLI completes:
|
||||
|
||||
```javascript
|
||||
const rawOutput = /* CLI output from callback */;
|
||||
|
||||
// Extract session ID from CLI output for resume chain
|
||||
const sessionIdMatch = rawOutput.match(/\[CCW_EXEC_ID=([^\]]+)\]/);
|
||||
if (sessionIdMatch) {
|
||||
state.analysis_session_id = sessionIdMatch[1];
|
||||
}
|
||||
|
||||
// Parse JSON
|
||||
const jsonMatch = rawOutput.match(/\{[\s\S]*\}/);
|
||||
let analysis;
|
||||
|
||||
if (jsonMatch) {
|
||||
try {
|
||||
analysis = JSON.parse(jsonMatch[0]);
|
||||
} catch (e) {
|
||||
// Fallback: extract score heuristically
|
||||
const scoreMatch = rawOutput.match(/"quality_score"\s*:\s*(\d+)/);
|
||||
analysis = {
|
||||
quality_score: scoreMatch ? parseInt(scoreMatch[1]) : 50,
|
||||
execution_assessment: { success: step.execution.success, completeness: 'unknown', notes: 'Parse failed' },
|
||||
artifact_assessment: { count: manifest.artifacts.length, quality: 'unknown', key_outputs: [], missing_outputs: [] },
|
||||
handoff_assessment: { ready: true, next_step_compatible: null, handoff_notes: '' },
|
||||
issues: [{ severity: 'low', description: 'Analysis output parsing failed', suggestion: 'Review raw output' }],
|
||||
optimization_opportunities: [],
|
||||
step_summary: 'Analysis parsing failed — raw output saved for manual review'
|
||||
};
|
||||
}
|
||||
} else {
|
||||
analysis = {
|
||||
quality_score: 50,
|
||||
step_summary: 'No structured analysis output received'
|
||||
};
|
||||
}
|
||||
|
||||
// Write step analysis file
|
||||
const stepAnalysisReport = `# Step ${stepIdx + 1} Analysis: ${step.name}
|
||||
|
||||
**Quality Score**: ${analysis.quality_score}/100
|
||||
**Date**: ${new Date().toISOString()}
|
||||
|
||||
## Execution
|
||||
- Success: ${analysis.execution_assessment?.success}
|
||||
- Completeness: ${analysis.execution_assessment?.completeness}
|
||||
- Notes: ${analysis.execution_assessment?.notes}
|
||||
|
||||
## Artifacts
|
||||
- Count: ${analysis.artifact_assessment?.count}
|
||||
- Quality: ${analysis.artifact_assessment?.quality}
|
||||
- Key Outputs: ${analysis.artifact_assessment?.key_outputs?.join(', ') || 'N/A'}
|
||||
- Missing: ${analysis.artifact_assessment?.missing_outputs?.join(', ') || 'None'}
|
||||
|
||||
## Handoff Readiness
|
||||
- Ready: ${analysis.handoff_assessment?.ready}
|
||||
- Next Step Compatible: ${analysis.handoff_assessment?.next_step_compatible}
|
||||
- Notes: ${analysis.handoff_assessment?.handoff_notes}
|
||||
|
||||
## Issues
|
||||
${(analysis.issues || []).map(i => `- [${i.severity}] ${i.description} → ${i.suggestion}`).join('\n') || 'None'}
|
||||
|
||||
## Optimization Opportunities
|
||||
${(analysis.optimization_opportunities || []).map(o => `- [${o.impact}] ${o.area}: ${o.description}`).join('\n') || 'None'}
|
||||
`;
|
||||
|
||||
Write(`${stepDir}/step-${stepIdx + 1}-analysis.md`, stepAnalysisReport);
|
||||
|
||||
// Append to process log
|
||||
const processLogEntry = `
|
||||
## Step ${stepIdx + 1}: ${step.name} — Score: ${analysis.quality_score}/100
|
||||
|
||||
**Command**: \`${step.command}\`
|
||||
**Result**: ${analysis.execution_assessment?.completeness || 'unknown'} | ${analysis.artifact_assessment?.count || 0} artifacts
|
||||
**Summary**: ${analysis.step_summary || 'No summary'}
|
||||
**Issues**: ${(analysis.issues || []).filter(i => i.severity === 'high').map(i => i.description).join('; ') || 'None critical'}
|
||||
**Handoff**: ${analysis.handoff_assessment?.handoff_notes || 'Ready'}
|
||||
|
||||
---
|
||||
`;
|
||||
|
||||
// Append to process-log.md
|
||||
const currentLog = Read(`${state.work_dir}/process-log.md`);
|
||||
Write(`${state.work_dir}/process-log.md`, currentLog + processLogEntry);
|
||||
|
||||
// Update state
|
||||
state.steps[stepIdx].analysis = {
|
||||
quality_score: analysis.quality_score,
|
||||
key_outputs: analysis.artifact_assessment?.key_outputs || [],
|
||||
handoff_notes: analysis.handoff_assessment?.handoff_notes || '',
|
||||
issue_count: (analysis.issues || []).length,
|
||||
high_issues: (analysis.issues || []).filter(i => i.severity === 'high').length,
|
||||
optimization_count: (analysis.optimization_opportunities || []).length,
|
||||
analysis_file: `${stepDir}/step-${stepIdx + 1}-analysis.md`
|
||||
};
|
||||
state.steps[stepIdx].status = 'analyzed';
|
||||
|
||||
state.process_log_entries.push({
|
||||
step_index: stepIdx,
|
||||
step_name: step.name,
|
||||
quality_score: analysis.quality_score,
|
||||
summary: analysis.step_summary,
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
|
||||
state.updated_at = new Date().toISOString();
|
||||
Write(`${state.work_dir}/workflow-state.json`, JSON.stringify(state, null, 2));
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
| Error | Recovery |
|
||||
|-------|----------|
|
||||
| CLI timeout | Retry once without --resume (fresh session) |
|
||||
| Resume session not found | Start fresh analysis session, continue |
|
||||
| JSON parse fails | Extract score heuristically, save raw output |
|
||||
| No output | Default score 50, minimal process log entry |
|
||||
|
||||
## Output
|
||||
|
||||
- **Files**: `step-{N}-analysis.md`, updated `process-log.md`
|
||||
- **State**: `steps[stepIdx].analysis` updated, `analysis_session_id` updated
|
||||
- **Next**: Phase 2 for next step, or Phase 4 (Synthesize) if all steps done
|
||||
257
.claude/skills/workflow-tune/phases/04-synthesize.md
Normal file
257
.claude/skills/workflow-tune/phases/04-synthesize.md
Normal file
@@ -0,0 +1,257 @@
|
||||
# Phase 4: Synthesize
|
||||
|
||||
> **COMPACT SENTINEL [Phase 4: Synthesize]**
|
||||
> This phase contains 4 execution steps (Step 4.1 -- 4.4).
|
||||
> If you can read this sentinel but cannot find the full Step protocol below, context has been compressed.
|
||||
> Recovery: `Read("phases/04-synthesize.md")`
|
||||
|
||||
Synthesize all step analyses into cross-step insights. Evaluates the workflow as a whole: step ordering, handoff quality, redundancy, bottlenecks, and overall coherence.
|
||||
|
||||
## Objective
|
||||
|
||||
- Read complete process-log.md and all step analyses
|
||||
- Build synthesis prompt with full workflow context
|
||||
- Execute via ccw cli Gemini with resume chain
|
||||
- Generate cross-step optimization insights
|
||||
- Write synthesis.md
|
||||
|
||||
## Execution
|
||||
|
||||
### Step 4.1: Gather All Analyses
|
||||
|
||||
```javascript
|
||||
// Read process log
|
||||
const processLog = Read(`${state.work_dir}/process-log.md`);
|
||||
|
||||
// Read all step analysis files
|
||||
const stepAnalyses = state.steps.map((step, i) => {
|
||||
const analysisFile = `${state.work_dir}/steps/step-${i + 1}/step-${i + 1}-analysis.md`;
|
||||
try {
|
||||
return { step: step.name, index: i, content: Read(analysisFile) };
|
||||
} catch {
|
||||
return { step: step.name, index: i, content: '[Analysis not available]' };
|
||||
}
|
||||
});
|
||||
|
||||
// Build score summary
|
||||
const scoreSummary = state.steps.map((s, i) =>
|
||||
`Step ${i + 1} (${s.name}): ${s.analysis?.quality_score || '-'}/100 | Issues: ${s.analysis?.issue_count || 0} (${s.analysis?.high_issues || 0} high)`
|
||||
).join('\n');
|
||||
|
||||
// Compute aggregate stats
|
||||
const scores = state.steps.map(s => s.analysis?.quality_score).filter(Boolean);
|
||||
const avgScore = scores.length > 0 ? Math.round(scores.reduce((a, b) => a + b, 0) / scores.length) : 0;
|
||||
const minScore = scores.length > 0 ? Math.min(...scores) : 0;
|
||||
const totalIssues = state.steps.reduce((sum, s) => sum + (s.analysis?.issue_count || 0), 0);
|
||||
const totalHighIssues = state.steps.reduce((sum, s) => sum + (s.analysis?.high_issues || 0), 0);
|
||||
```
|
||||
|
||||
### Step 4.2: Construct Synthesis Prompt
|
||||
|
||||
```javascript
|
||||
// Ref: templates/synthesis-prompt.md
|
||||
|
||||
const synthesisPrompt = `PURPOSE: Synthesize all step analyses into a holistic workflow optimization assessment. Evaluate cross-step concerns: ordering, handoff quality, redundancy, bottlenecks, and overall workflow coherence.
|
||||
|
||||
WORKFLOW OVERVIEW:
|
||||
Name: ${state.workflow_name}
|
||||
Goal: ${state.workflow_context}
|
||||
Steps: ${state.steps.length}
|
||||
Average Quality: ${avgScore}/100
|
||||
Weakest Step: ${minScore}/100
|
||||
Total Issues: ${totalIssues} (${totalHighIssues} high severity)
|
||||
|
||||
SCORE SUMMARY:
|
||||
${scoreSummary}
|
||||
|
||||
COMPLETE PROCESS LOG:
|
||||
${processLog}
|
||||
|
||||
DETAILED STEP ANALYSES:
|
||||
${stepAnalyses.map(a => `### ${a.step} (Step ${a.index + 1})\n${a.content}`).join('\n\n---\n\n')}
|
||||
|
||||
TASK:
|
||||
1. **Workflow Coherence**: Do steps form a logical sequence? Any missing steps?
|
||||
2. **Handoff Quality**: Are step outputs well-consumed by subsequent steps? Data format mismatches?
|
||||
3. **Redundancy Detection**: Do any steps duplicate work? Overlapping concerns?
|
||||
4. **Bottleneck Identification**: Which steps are bottlenecks (lowest quality, longest duration)?
|
||||
5. **Step Ordering**: Would reordering steps improve outcomes?
|
||||
6. **Missing Steps**: Are there gaps in the pipeline that need additional steps?
|
||||
7. **Per-Step Optimization**: Top 3 improvements per underperforming step
|
||||
8. **Workflow-Level Optimization**: Structural changes to the overall pipeline
|
||||
|
||||
EXPECTED OUTPUT (strict JSON, no markdown):
|
||||
{
|
||||
"workflow_score": <0-100>,
|
||||
"coherence": {
|
||||
"score": <0-100>,
|
||||
"assessment": "<logical flow evaluation>",
|
||||
"gaps": ["<missing step or transition>"]
|
||||
},
|
||||
"handoff_quality": {
|
||||
"score": <0-100>,
|
||||
"issues": [
|
||||
{ "from_step": "<step name>", "to_step": "<step name>", "issue": "<description>", "fix": "<suggestion>" }
|
||||
]
|
||||
},
|
||||
"redundancy": {
|
||||
"found": <true|false>,
|
||||
"items": [
|
||||
{ "steps": ["<step1>", "<step2>"], "description": "<what overlaps>", "recommendation": "<merge or remove>" }
|
||||
]
|
||||
},
|
||||
"bottlenecks": [
|
||||
{ "step": "<step name>", "reason": "<why it's a bottleneck>", "impact": "high|medium|low", "fix": "<suggestion>" }
|
||||
],
|
||||
"ordering_suggestions": [
|
||||
{ "current": "<current order description>", "proposed": "<new order>", "rationale": "<why>" }
|
||||
],
|
||||
"per_step_improvements": [
|
||||
{ "step": "<step name>", "improvements": [
|
||||
{ "priority": "high|medium|low", "description": "<what to change>", "rationale": "<why>" }
|
||||
]}
|
||||
],
|
||||
"workflow_improvements": [
|
||||
{ "priority": "high|medium|low", "category": "structure|handoff|performance|quality", "description": "<change>", "rationale": "<why>", "affected_steps": ["<step names>"] }
|
||||
],
|
||||
"summary": "<2-3 sentence executive summary of workflow health and top recommendations>"
|
||||
}
|
||||
|
||||
CONSTRAINTS: Be specific, reference step names and artifact details, output ONLY JSON`;
|
||||
```
|
||||
|
||||
### Step 4.3: Execute via ccw cli Gemini with Resume
|
||||
|
||||
```javascript
|
||||
function escapeForShell(str) {
|
||||
return str.replace(/"/g, '\\"').replace(/\$/g, '\\$').replace(/`/g, '\\`');
|
||||
}
|
||||
|
||||
let cliCommand = `ccw cli -p "${escapeForShell(synthesisPrompt)}" --tool gemini --mode analysis`;
|
||||
|
||||
// Resume from the last step's analysis session
|
||||
if (state.analysis_session_id) {
|
||||
cliCommand += ` --resume ${state.analysis_session_id}`;
|
||||
}
|
||||
|
||||
Bash({
|
||||
command: cliCommand,
|
||||
run_in_background: true,
|
||||
timeout: 300000
|
||||
});
|
||||
|
||||
// STOP — wait for hook callback
|
||||
```
|
||||
|
||||
### Step 4.4: Parse Results and Write Synthesis
|
||||
|
||||
After CLI completes:
|
||||
|
||||
```javascript
|
||||
const rawOutput = /* CLI output from callback */;
|
||||
const jsonMatch = rawOutput.match(/\{[\s\S]*\}/);
|
||||
let synthesis;
|
||||
|
||||
if (jsonMatch) {
|
||||
try {
|
||||
synthesis = JSON.parse(jsonMatch[0]);
|
||||
} catch {
|
||||
synthesis = {
|
||||
workflow_score: avgScore,
|
||||
summary: 'Synthesis parsing failed — individual step analyses available',
|
||||
workflow_improvements: [],
|
||||
per_step_improvements: [],
|
||||
bottlenecks: [],
|
||||
handoff_quality: { score: 0, issues: [] },
|
||||
coherence: { score: 0, assessment: 'Parse error' },
|
||||
redundancy: { found: false, items: [] },
|
||||
ordering_suggestions: []
|
||||
};
|
||||
}
|
||||
} else {
|
||||
synthesis = {
|
||||
workflow_score: avgScore,
|
||||
summary: 'No synthesis output received',
|
||||
workflow_improvements: [],
|
||||
per_step_improvements: []
|
||||
};
|
||||
}
|
||||
|
||||
// Write synthesis report
|
||||
const synthesisReport = `# Workflow Synthesis
|
||||
|
||||
**Workflow Score**: ${synthesis.workflow_score}/100
|
||||
**Date**: ${new Date().toISOString()}
|
||||
|
||||
## Executive Summary
|
||||
${synthesis.summary}
|
||||
|
||||
## Coherence (${synthesis.coherence?.score || '-'}/100)
|
||||
${synthesis.coherence?.assessment || 'N/A'}
|
||||
${(synthesis.coherence?.gaps || []).length > 0 ? '\n### Gaps\n' + synthesis.coherence.gaps.map(g => `- ${g}`).join('\n') : ''}
|
||||
|
||||
## Handoff Quality (${synthesis.handoff_quality?.score || '-'}/100)
|
||||
${(synthesis.handoff_quality?.issues || []).map(i =>
|
||||
`- **${i.from_step} → ${i.to_step}**: ${i.issue}\n Fix: ${i.fix}`
|
||||
).join('\n') || 'No handoff issues'}
|
||||
|
||||
## Redundancy
|
||||
${synthesis.redundancy?.found ? (synthesis.redundancy.items || []).map(r =>
|
||||
`- Steps ${r.steps.join(', ')}: ${r.description} → ${r.recommendation}`
|
||||
).join('\n') : 'No redundancy detected'}
|
||||
|
||||
## Bottlenecks
|
||||
${(synthesis.bottlenecks || []).map(b =>
|
||||
`- **${b.step}** [${b.impact}]: ${b.reason}\n Fix: ${b.fix}`
|
||||
).join('\n') || 'No bottlenecks'}
|
||||
|
||||
## Ordering Suggestions
|
||||
${(synthesis.ordering_suggestions || []).map(o =>
|
||||
`- Current: ${o.current}\n Proposed: ${o.proposed}\n Rationale: ${o.rationale}`
|
||||
).join('\n') || 'Current ordering is optimal'}
|
||||
|
||||
## Per-Step Improvements
|
||||
${(synthesis.per_step_improvements || []).map(s =>
|
||||
`### ${s.step}\n` + (s.improvements || []).map(i =>
|
||||
`- [${i.priority}] ${i.description} — ${i.rationale}`
|
||||
).join('\n')
|
||||
).join('\n\n') || 'No per-step improvements'}
|
||||
|
||||
## Workflow-Level Improvements
|
||||
${(synthesis.workflow_improvements || []).map((w, i) =>
|
||||
`### ${i + 1}. [${w.priority}] ${w.description}\n- Category: ${w.category}\n- Rationale: ${w.rationale}\n- Affected: ${(w.affected_steps || []).join(', ')}`
|
||||
).join('\n\n') || 'No workflow-level improvements'}
|
||||
`;
|
||||
|
||||
Write(`${state.work_dir}/synthesis.md`, synthesisReport);
|
||||
|
||||
// Update state
|
||||
state.synthesis = {
|
||||
workflow_score: synthesis.workflow_score,
|
||||
summary: synthesis.summary,
|
||||
improvement_count: (synthesis.workflow_improvements || []).length +
|
||||
(synthesis.per_step_improvements || []).reduce((sum, s) => sum + (s.improvements || []).length, 0),
|
||||
high_priority_count: (synthesis.workflow_improvements || []).filter(w => w.priority === 'high').length,
|
||||
bottleneck_count: (synthesis.bottlenecks || []).length,
|
||||
handoff_issue_count: (synthesis.handoff_quality?.issues || []).length,
|
||||
synthesis_file: `${state.work_dir}/synthesis.md`,
|
||||
raw_data: synthesis
|
||||
};
|
||||
|
||||
state.updated_at = new Date().toISOString();
|
||||
Write(`${state.work_dir}/workflow-state.json`, JSON.stringify(state, null, 2));
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
| Error | Recovery |
|
||||
|-------|----------|
|
||||
| CLI timeout | Generate synthesis from individual step analyses only (no cross-step) |
|
||||
| Resume fails | Start fresh analysis session |
|
||||
| JSON parse fails | Use step-level data to construct minimal synthesis |
|
||||
|
||||
## Output
|
||||
|
||||
- **Files**: `synthesis.md`
|
||||
- **State**: `state.synthesis` updated
|
||||
- **Next**: Phase 5 (Optimization Report)
|
||||
244
.claude/skills/workflow-tune/phases/05-optimize-report.md
Normal file
244
.claude/skills/workflow-tune/phases/05-optimize-report.md
Normal file
@@ -0,0 +1,244 @@
|
||||
# Phase 5: Optimization Report
|
||||
|
||||
> **COMPACT SENTINEL [Phase 5: Report]**
|
||||
> This phase contains 4 execution steps (Step 5.1 -- 5.4).
|
||||
> If you can read this sentinel but cannot find the full Step protocol below, context has been compressed.
|
||||
> Recovery: `Read("phases/05-optimize-report.md")`
|
||||
|
||||
Generate the final optimization report and optionally apply high-priority fixes.
|
||||
|
||||
## Objective
|
||||
|
||||
- Read complete state, process log, synthesis
|
||||
- Generate structured final report
|
||||
- Optionally apply auto-fix (if enabled)
|
||||
- Write final-report.md
|
||||
- Display summary to user
|
||||
|
||||
## Execution
|
||||
|
||||
### Step 5.1: Read Complete State
|
||||
|
||||
```javascript
|
||||
const state = JSON.parse(Read(`${state.work_dir}/workflow-state.json`));
|
||||
const processLog = Read(`${state.work_dir}/process-log.md`);
|
||||
const synthesis = state.synthesis;
|
||||
state.status = 'completed';
|
||||
state.updated_at = new Date().toISOString();
|
||||
```
|
||||
|
||||
### Step 5.2: Generate Report
|
||||
|
||||
```javascript
|
||||
// Compute stats
|
||||
const scores = state.steps.map(s => s.analysis?.quality_score).filter(Boolean);
|
||||
const avgScore = scores.length > 0 ? Math.round(scores.reduce((a, b) => a + b, 0) / scores.length) : 0;
|
||||
const minStep = state.steps.reduce((min, s) =>
|
||||
(s.analysis?.quality_score || 100) < (min.analysis?.quality_score || 100) ? s : min
|
||||
, state.steps[0]);
|
||||
|
||||
const totalIssues = state.steps.reduce((sum, s) => sum + (s.analysis?.issue_count || 0), 0);
|
||||
const totalHighIssues = state.steps.reduce((sum, s) => sum + (s.analysis?.high_issues || 0), 0);
|
||||
|
||||
// Step quality table
|
||||
const stepTable = state.steps.map((s, i) =>
|
||||
`| ${i + 1} | ${s.name} | ${s.type} | ${s.execution?.success ? 'OK' : 'FAIL'} | ${s.analysis?.quality_score || '-'} | ${s.analysis?.issue_count || 0} | ${s.analysis?.high_issues || 0} |`
|
||||
).join('\n');
|
||||
|
||||
// Collect all improvements (workflow-level + per-step)
|
||||
const allImprovements = [];
|
||||
if (synthesis?.raw_data?.workflow_improvements) {
|
||||
synthesis.raw_data.workflow_improvements.forEach(w => {
|
||||
allImprovements.push({
|
||||
scope: 'workflow',
|
||||
priority: w.priority,
|
||||
description: w.description,
|
||||
rationale: w.rationale,
|
||||
category: w.category,
|
||||
affected: w.affected_steps || []
|
||||
});
|
||||
});
|
||||
}
|
||||
if (synthesis?.raw_data?.per_step_improvements) {
|
||||
synthesis.raw_data.per_step_improvements.forEach(s => {
|
||||
(s.improvements || []).forEach(imp => {
|
||||
allImprovements.push({
|
||||
scope: s.step,
|
||||
priority: imp.priority,
|
||||
description: imp.description,
|
||||
rationale: imp.rationale,
|
||||
category: 'step',
|
||||
affected: [s.step]
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Sort by priority
|
||||
const priorityOrder = { high: 0, medium: 1, low: 2 };
|
||||
allImprovements.sort((a, b) => (priorityOrder[a.priority] || 2) - (priorityOrder[b.priority] || 2));
|
||||
|
||||
const report = `# Workflow Tune — Final Report
|
||||
|
||||
## Summary
|
||||
|
||||
| Field | Value |
|
||||
|-------|-------|
|
||||
| **Workflow** | ${state.workflow_name} |
|
||||
| **Goal** | ${state.workflow_context} |
|
||||
| **Steps** | ${state.steps.length} |
|
||||
| **Workflow Score** | ${synthesis?.workflow_score || avgScore}/100 |
|
||||
| **Average Step Quality** | ${avgScore}/100 |
|
||||
| **Weakest Step** | ${minStep.name} (${minStep.analysis?.quality_score || '-'}/100) |
|
||||
| **Total Issues** | ${totalIssues} (${totalHighIssues} high severity) |
|
||||
| **Analysis Depth** | ${state.analysis_depth} |
|
||||
| **Started** | ${state.started_at} |
|
||||
| **Completed** | ${state.updated_at} |
|
||||
|
||||
## Step Quality Matrix
|
||||
|
||||
| # | Step | Type | Exec | Quality | Issues | High |
|
||||
|---|------|------|------|---------|--------|------|
|
||||
${stepTable}
|
||||
|
||||
## Workflow Flow Assessment
|
||||
|
||||
### Coherence: ${synthesis?.raw_data?.coherence?.score || '-'}/100
|
||||
${synthesis?.raw_data?.coherence?.assessment || 'Not evaluated'}
|
||||
|
||||
### Handoff Quality: ${synthesis?.raw_data?.handoff_quality?.score || '-'}/100
|
||||
${(synthesis?.raw_data?.handoff_quality?.issues || []).map(i =>
|
||||
`- **${i.from_step} → ${i.to_step}**: ${i.issue}`
|
||||
).join('\n') || 'No handoff issues'}
|
||||
|
||||
### Bottlenecks
|
||||
${(synthesis?.raw_data?.bottlenecks || []).map(b =>
|
||||
`- **${b.step}** [${b.impact}]: ${b.reason}`
|
||||
).join('\n') || 'No bottlenecks identified'}
|
||||
|
||||
## Optimization Recommendations
|
||||
|
||||
### Priority: HIGH
|
||||
${allImprovements.filter(i => i.priority === 'high').map((i, idx) =>
|
||||
`${idx + 1}. **[${i.scope}]** ${i.description}\n - Rationale: ${i.rationale}\n - Affected: ${i.affected.join(', ')}`
|
||||
).join('\n') || 'None'}
|
||||
|
||||
### Priority: MEDIUM
|
||||
${allImprovements.filter(i => i.priority === 'medium').map((i, idx) =>
|
||||
`${idx + 1}. **[${i.scope}]** ${i.description}\n - Rationale: ${i.rationale}`
|
||||
).join('\n') || 'None'}
|
||||
|
||||
### Priority: LOW
|
||||
${allImprovements.filter(i => i.priority === 'low').map((i, idx) =>
|
||||
`${idx + 1}. **[${i.scope}]** ${i.description}`
|
||||
).join('\n') || 'None'}
|
||||
|
||||
## Process Documentation
|
||||
|
||||
Full process log: \`${state.work_dir}/process-log.md\`
|
||||
Synthesis: \`${state.work_dir}/synthesis.md\`
|
||||
|
||||
### Per-Step Analysis Files
|
||||
|
||||
| Step | Analysis File |
|
||||
|------|---------------|
|
||||
${state.steps.map((s, i) =>
|
||||
`| ${s.name} | \`${state.work_dir}/steps/step-${i + 1}/step-${i + 1}-analysis.md\` |`
|
||||
).join('\n')}
|
||||
|
||||
## Artifact Locations
|
||||
|
||||
| Path | Description |
|
||||
|------|-------------|
|
||||
| \`${state.work_dir}/workflow-state.json\` | Complete state |
|
||||
| \`${state.work_dir}/process-log.md\` | Accumulated process log |
|
||||
| \`${state.work_dir}/synthesis.md\` | Cross-step synthesis |
|
||||
| \`${state.work_dir}/final-report.md\` | This report |
|
||||
`;
|
||||
|
||||
Write(`${state.work_dir}/final-report.md`, report);
|
||||
```
|
||||
|
||||
### Step 5.3: Optional Auto-Fix
|
||||
|
||||
```javascript
|
||||
if (state.auto_fix && allImprovements.filter(i => i.priority === 'high').length > 0) {
|
||||
const highPriorityFixes = allImprovements.filter(i => i.priority === 'high');
|
||||
|
||||
// ★ Safety: confirm with user before applying auto-fixes
|
||||
const fixList = highPriorityFixes.map((f, i) =>
|
||||
`${i + 1}. [${f.scope}] ${f.description}\n Affected: ${f.affected.join(', ')}`
|
||||
).join('\n');
|
||||
|
||||
const autoFixConfirm = AskUserQuestion({
|
||||
questions: [{
|
||||
question: `以下 ${highPriorityFixes.length} 项高优先级优化将被自动应用:\n\n${fixList}\n\n确认应用?`,
|
||||
header: "Auto-Fix Confirmation",
|
||||
multiSelect: false,
|
||||
options: [
|
||||
{ label: "Apply (应用)", description: "自动应用以上高优先级修复" },
|
||||
{ label: "Skip (跳过)", description: "跳过自动修复,仅保留报告" }
|
||||
]
|
||||
}]
|
||||
});
|
||||
|
||||
if (autoFixConfirm["Auto-Fix Confirmation"].startsWith("Skip")) {
|
||||
// Skip auto-fix, just log it
|
||||
state.auto_fix_skipped = true;
|
||||
} else {
|
||||
|
||||
Agent({
|
||||
subagent_type: 'general-purpose',
|
||||
run_in_background: false,
|
||||
description: 'Apply high-priority workflow optimizations',
|
||||
prompt: `## Task: Apply High-Priority Workflow Optimizations
|
||||
|
||||
You are applying the top optimization suggestions from a workflow analysis.
|
||||
|
||||
## Improvements to Apply (HIGH priority only)
|
||||
${highPriorityFixes.map((f, i) =>
|
||||
`${i + 1}. [${f.scope}] ${f.description}\n Rationale: ${f.rationale}\n Affected: ${f.affected.join(', ')}`
|
||||
).join('\n')}
|
||||
|
||||
## Workflow Steps
|
||||
${state.steps.map((s, i) => `${i + 1}. ${s.name} (${s.type}): ${s.command}`).join('\n')}
|
||||
|
||||
## Rules
|
||||
1. Read each affected file BEFORE modifying
|
||||
2. Apply ONLY the high-priority suggestions
|
||||
3. Preserve existing code style
|
||||
4. Write a changes summary to: ${state.work_dir}/auto-fix-changes.md
|
||||
`
|
||||
});
|
||||
|
||||
} // end Apply branch
|
||||
}
|
||||
```
|
||||
|
||||
### Step 5.4: Display Summary
|
||||
|
||||
Output to user:
|
||||
|
||||
```
|
||||
Workflow Tune Complete!
|
||||
|
||||
Workflow: {name}
|
||||
Steps: {count}
|
||||
Workflow Score: {score}/100
|
||||
Average Step Quality: {avgScore}/100
|
||||
Weakest Step: {name} ({score}/100)
|
||||
|
||||
Step Scores: {step1}={score1} → {step2}={score2} → ... → {stepN}={scoreN}
|
||||
|
||||
Issues: {total} ({high} high priority)
|
||||
Improvements: {count} ({highCount} high priority)
|
||||
|
||||
Full report: {workDir}/final-report.md
|
||||
Process log: {workDir}/process-log.md
|
||||
```
|
||||
|
||||
## Output
|
||||
|
||||
- **Files**: `final-report.md`, optionally `auto-fix-changes.md`
|
||||
- **State**: `status = completed`
|
||||
- **Next**: Workflow complete. Return control to user.
|
||||
Reference in New Issue
Block a user