UNPKG

chittycan

Version:

Your completely autonomous network that grows with you - DNA ownership platform with encrypted vaults, PDX portability, and ChittyFoundation governance

282 lines 9.42 kB
/** * Workflow Auto-Generation * * Detects repeated command sequences and automatically suggests * creating workflows from them. * * Example: * You run: git add . → git commit → git push (3 times in a week) * ChittyCan suggests: Create "deploy" workflow? */ import { loadUsageStats } from "./usage-tracker.js"; import { addWorkflow } from "./custom-workflows.js"; import { readFileSync, writeFileSync, existsSync } from "fs"; import { join } from "path"; import { homedir } from "os"; const SUGGESTIONS_FILE = join(homedir(), ".chittycan", "workflow-suggestions.json"); const MIN_FREQUENCY = 3; // Must see sequence at least 3 times const MAX_SEQUENCE_LENGTH = 6; // Max commands in a sequence const MAX_TIME_BETWEEN_MS = 5 * 60 * 1000; // 5 minutes /** * Analyze usage and detect repeated command sequences */ export function detectRepeatedSequences() { const stats = loadUsageStats(); const commands = stats.commands.filter(c => c.successful); if (commands.length < 6) { return []; // Not enough data } const sequences = new Map(); // Sliding window to detect sequences for (let length = 2; length <= MAX_SEQUENCE_LENGTH; length++) { for (let i = 0; i <= commands.length - length; i++) { const sequence = commands.slice(i, i + length); // Check time between commands (must be close together) let valid = true; for (let j = 1; j < sequence.length; j++) { const timeDiff = new Date(sequence[j].timestamp).getTime() - new Date(sequence[j - 1].timestamp).getTime(); if (timeDiff > MAX_TIME_BETWEEN_MS) { valid = false; break; } } if (!valid) continue; // Create sequence key const key = sequence.map(c => `${c.cli}:${c.interpretedCommand}`).join(" → "); if (sequences.has(key)) { const existing = sequences.get(key); existing.frequency++; existing.lastSeen = sequence[sequence.length - 1].timestamp; } else { sequences.set(key, { commands: sequence.map(c => c.interpretedCommand), frequency: 1, lastSeen: sequence[sequence.length - 1].timestamp, avgTimeBetween: 0 }); } } } // Filter by minimum frequency return Array.from(sequences.values()) .filter(seq => seq.frequency >= MIN_FREQUENCY) .sort((a, b) => b.frequency - a.frequency); } /** * Generate workflow suggestions from repeated sequences */ export function generateWorkflowSuggestions() { const sequences = detectRepeatedSequences(); const suggestions = []; for (const seq of sequences) { const suggestion = sequenceToWorkflow(seq); if (suggestion) { suggestions.push(suggestion); } } return suggestions; } /** * Convert a command sequence to a workflow suggestion */ function sequenceToWorkflow(seq) { // Analyze the sequence to generate a good name const analysis = analyzeSequence(seq.commands); if (!analysis) return null; const steps = seq.commands.map(cmd => ({ type: "command", value: cmd, description: cmd })); return { name: analysis.name, trigger: analysis.trigger, description: analysis.description, steps, reason: `You've run this sequence ${seq.frequency} times`, frequency: seq.frequency }; } /** * Analyze sequence and suggest names/triggers */ function analyzeSequence(commands) { const cmdStr = commands.join(" "); // Pattern 1: Git deployment flow if (cmdStr.includes("git add") && cmdStr.includes("git commit") && cmdStr.includes("git push")) { return { name: "Deploy Changes", trigger: "deploy", description: "Add, commit, and push changes to git" }; } // Pattern 2: Docker rebuild if (cmdStr.includes("docker build") && cmdStr.includes("docker run")) { return { name: "Rebuild Docker", trigger: "rebuild docker", description: "Build and run Docker container" }; } // Pattern 3: Test and deploy if (cmdStr.includes("test") && cmdStr.includes("deploy")) { return { name: "Test and Deploy", trigger: "test deploy", description: "Run tests then deploy if passing" }; } // Pattern 4: Build and publish if (cmdStr.includes("build") && cmdStr.includes("publish")) { return { name: "Build and Publish", trigger: "publish", description: "Build and publish package" }; } // Pattern 5: Database migration if (cmdStr.includes("migrate") || cmdStr.includes("db")) { return { name: "Run Migrations", trigger: "migrate", description: "Run database migrations" }; } // Pattern 6: PR workflow if (cmdStr.includes("gh pr")) { return { name: "PR Workflow", trigger: "pr flow", description: "GitHub pull request operations" }; } // Pattern 7: Kubernetes deployment if (cmdStr.includes("kubectl")) { return { name: "K8s Deploy", trigger: "k8s deploy", description: "Kubernetes deployment workflow" }; } // Generic pattern const firstCmd = commands[0].split(" ")[0]; return { name: `${firstCmd} Workflow`, trigger: `${firstCmd} flow`, description: `Automated ${firstCmd} sequence` }; } /** * Save workflow suggestions to file */ export function saveSuggestions(suggestions) { writeFileSync(SUGGESTIONS_FILE, JSON.stringify(suggestions, null, 2)); } /** * Load saved workflow suggestions */ export function loadSuggestions() { if (!existsSync(SUGGESTIONS_FILE)) { return []; } try { const data = readFileSync(SUGGESTIONS_FILE, "utf-8"); return JSON.parse(data); } catch { return []; } } /** * Accept a workflow suggestion and create the workflow */ export function acceptSuggestion(suggestion) { const workflow = { name: suggestion.name, trigger: suggestion.trigger, description: suggestion.description, steps: suggestion.steps, createdAt: new Date().toISOString(), usageCount: 0 }; addWorkflow(workflow); // Remove from suggestions const suggestions = loadSuggestions(); const filtered = suggestions.filter(s => s.trigger !== suggestion.trigger); saveSuggestions(filtered); } /** * Dismiss a workflow suggestion */ export function dismissSuggestion(trigger) { const suggestions = loadSuggestions(); const filtered = suggestions.filter(s => s.trigger !== trigger); saveSuggestions(filtered); } /** * Check for new suggestions and notify user */ export function checkForNewSuggestions() { const newSuggestions = generateWorkflowSuggestions(); const existingSuggestions = loadSuggestions(); // Filter out already suggested const existingTriggers = new Set(existingSuggestions.map(s => s.trigger)); const fresh = newSuggestions.filter(s => !existingTriggers.has(s.trigger)); if (fresh.length > 0) { // Merge and save const merged = [...existingSuggestions, ...fresh]; saveSuggestions(merged); } return fresh; } /** * Smart learning: Track command sequences in real-time */ export class SequenceTracker { currentSequence = []; lastCommandTime = 0; trackCommand(command) { const now = Date.now(); const timeSinceLastCmd = now - this.lastCommandTime; // Reset sequence if too much time passed if (timeSinceLastCmd > MAX_TIME_BETWEEN_MS) { this.currentSequence = []; } this.currentSequence.push(command); this.lastCommandTime = now; // Check if we should suggest a workflow if (this.currentSequence.length >= 3) { this.checkForSuggestion(); } } checkForSuggestion() { // Check if this sequence has been repeated const seq = this.currentSequence.map(c => c.interpretedCommand).join(" → "); const stats = loadUsageStats(); let count = 0; for (let i = 0; i <= stats.commands.length - this.currentSequence.length; i++) { const slice = stats.commands.slice(i, i + this.currentSequence.length); const sliceStr = slice.map(c => c.interpretedCommand).join(" → "); if (sliceStr === seq) { count++; } } if (count >= MIN_FREQUENCY) { // Generate suggestion const suggestions = generateWorkflowSuggestions(); if (suggestions.length > 0) { console.log(`\n💡 ChittyCan noticed you repeat this sequence often.`); console.log(` Would you like to create a workflow? Run: can chitty suggestions\n`); } } } reset() { this.currentSequence = []; this.lastCommandTime = 0; } } //# sourceMappingURL=workflow-generator.js.map