diff options
Diffstat (limited to 'pi/agent/extensions/taskwarrior-plan-mode/utils.ts')
| -rw-r--r-- | pi/agent/extensions/taskwarrior-plan-mode/utils.ts | 252 |
1 files changed, 0 insertions, 252 deletions
diff --git a/pi/agent/extensions/taskwarrior-plan-mode/utils.ts b/pi/agent/extensions/taskwarrior-plan-mode/utils.ts deleted file mode 100644 index cfaba15..0000000 --- a/pi/agent/extensions/taskwarrior-plan-mode/utils.ts +++ /dev/null @@ -1,252 +0,0 @@ -export interface PlanItem { - step: number; - text: string; - uuid?: string; -} - -export interface TaskwarriorAnnotation { - entry?: string; - description: string; -} - -export interface TaskwarriorTask { - id?: number; - uuid: string; - description: string; - status?: string; - priority?: string; - start?: string; - project?: string; - urgency?: number; - depends?: string[]; - annotations?: TaskwarriorAnnotation[]; -} - -const ANSI_PATTERN = - // biome-ignore lint/suspicious/noControlCharactersInRegex: strips terminal escape sequences from command output - /\u001B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])/g; - -const DESTRUCTIVE_PATTERNS = [ - /\brm\b/i, - /\brmdir\b/i, - /\bmv\b/i, - /\bcp\b/i, - /\bmkdir\b/i, - /\btouch\b/i, - /\bchmod\b/i, - /\bchown\b/i, - /\bchgrp\b/i, - /\bln\b/i, - /\btee\b/i, - /\btruncate\b/i, - /\bdd\b/i, - /\bshred\b/i, - /(^|[^<])>(?!>)/, - />>/, - /\bnpm\s+(install|uninstall|update|ci|link|publish)/i, - /\byarn\s+(add|remove|install|publish)/i, - /\bpnpm\s+(add|remove|install|publish)/i, - /\bpip\s+(install|uninstall)/i, - /\bapt(-get)?\s+(install|remove|purge|update|upgrade)/i, - /\bbrew\s+(install|uninstall|upgrade)/i, - /\bgit\s+(add|commit|push|pull|merge|rebase|reset|checkout|branch\s+-[dD]|stash|cherry-pick|revert|tag|init|clone)/i, - /\bsudo\b/i, - /\bsu\b/i, - /\bkill\b/i, - /\bpkill\b/i, - /\bkillall\b/i, - /\breboot\b/i, - /\bshutdown\b/i, - /\bsystemctl\s+(start|stop|restart|enable|disable)/i, - /\bservice\s+\S+\s+(start|stop|restart)/i, - /\b(vim?|nano|emacs|code|subl)\b/i, -]; - -const SAFE_PATTERNS = [ - /^\s*cat\b/, - /^\s*head\b/, - /^\s*tail\b/, - /^\s*less\b/, - /^\s*more\b/, - /^\s*grep\b/, - /^\s*find\b/, - /^\s*ls\b/, - /^\s*pwd\b/, - /^\s*echo\b/, - /^\s*printf\b/, - /^\s*wc\b/, - /^\s*sort\b/, - /^\s*uniq\b/, - /^\s*diff\b/, - /^\s*file\b/, - /^\s*stat\b/, - /^\s*du\b/, - /^\s*df\b/, - /^\s*tree\b/, - /^\s*which\b/, - /^\s*whereis\b/, - /^\s*type\b/, - /^\s*env\b/, - /^\s*printenv\b/, - /^\s*uname\b/, - /^\s*whoami\b/, - /^\s*id\b/, - /^\s*date\b/, - /^\s*cal\b/, - /^\s*uptime\b/, - /^\s*ps\b/, - /^\s*top\b/, - /^\s*htop\b/, - /^\s*free\b/, - /^\s*git\s+(status|log|diff|show|branch|remote|config\s+--get)/i, - /^\s*git\s+ls-/i, - /^\s*npm\s+(list|ls|view|info|search|outdated|audit)/i, - /^\s*yarn\s+(list|info|why|audit)/i, - /^\s*node\s+--version/i, - /^\s*python\s+--version/i, - /^\s*curl\s/i, - /^\s*wget\s+-O\s*-/i, - /^\s*jq\b/, - /^\s*sed\s+-n/i, - /^\s*awk\b/, - /^\s*rg\b/, - /^\s*fd\b/, - /^\s*bat\b/, - /^\s*exa\b/, -]; - -const MUTATING_TASK_PATTERNS = [ - /\badd\b/i, - /\bannotate\b/i, - /\bappend\b/i, - /\bdelete\b/i, - /\bdenotate\b/i, - /\bdone\b/i, - /\bduplicate\b/i, - /\bedit\b/i, - /\bimport\b/i, - /\blog\b/i, - /\bmodify\b/i, - /\bprepend\b/i, - /\bpurge\b/i, - /\bstart\b/i, - /\bstop\b/i, - /\bsynchronize\b/i, - /\bundo\b/i, -]; - -export function stripAnsi(text: string): string { - return text.replace(ANSI_PATTERN, ""); -} - -export function containsRawTaskCommand(command: string): boolean { - return /(^|[;&|]\s*)task\b/.test(command); -} - -export function isSafeAskCommand(command: string): boolean { - const trimmed = command.trim(); - if (!trimmed.startsWith("ask ")) return false; - if (containsRawTaskCommand(trimmed)) return false; - if (/[;&]/.test(trimmed) || /(^|[^|])\|([^|]|$)/.test(trimmed)) return false; - return !MUTATING_TASK_PATTERNS.some((pattern) => pattern.test(trimmed)); -} - -export function isSafePlanCommand(command: string): boolean { - if (containsRawTaskCommand(command)) return false; - if (isSafeAskCommand(command)) return true; - - const isDestructive = DESTRUCTIVE_PATTERNS.some((pattern) => pattern.test(command)); - const isSafe = SAFE_PATTERNS.some((pattern) => pattern.test(command)); - return !isDestructive && isSafe; -} - -export function cleanPlanStep(text: string): string { - return text - .replace(/\*{1,2}([^*]+)\*{1,2}/g, "$1") - .replace(/`([^`]+)`/g, "$1") - .replace(/\[[^\]]+\]\([^)]+\)/g, "$1") - .replace(/\s+/g, " ") - .trim() - .replace(/[.;:]+$/, ""); -} - -export function normalizeTaskText(text: string): string { - return cleanPlanStep(text).toLowerCase(); -} - -export function extractPlanItems(message: string): PlanItem[] { - const items: PlanItem[] = []; - const headerMatch = message.match(/\*{0,2}Plan:\*{0,2}\s*\n/i); - if (!headerMatch) return items; - - const planSection = message.slice(message.indexOf(headerMatch[0]) + headerMatch[0].length); - const numberedPattern = /^\s*(\d+)[.)]\s+(.+)$/gm; - - for (const match of planSection.matchAll(numberedPattern)) { - const cleaned = cleanPlanStep(match[2] ?? ""); - if (cleaned.length < 4) continue; - if (cleaned.startsWith("-") || cleaned.startsWith("/")) continue; - items.push({ - step: items.length + 1, - text: cleaned.slice(0, 240), - }); - } - - return dedupePlanItems(items); -} - -export function dedupePlanItems(items: PlanItem[]): PlanItem[] { - const seen = new Set<string>(); - const deduped: PlanItem[] = []; - - for (const item of items) { - const key = normalizeTaskText(item.text); - if (!key || seen.has(key)) continue; - seen.add(key); - deduped.push({ - step: deduped.length + 1, - text: item.text, - uuid: item.uuid, - }); - } - - return deduped; -} - -export function parseUuidList(text: string): string[] { - return stripAnsi(text) - .match(/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/gi) - ?.map((value) => value.toLowerCase()) ?? []; -} - -export function parseCreatedTaskId(text: string): number | undefined { - const match = stripAnsi(text).match(/Created task (\d+)/i); - return match ? Number(match[1]) : undefined; -} - -export function formatTaskLine(task: TaskwarriorTask): string { - const bits = [ - task.priority ? `[${task.priority}]` : undefined, - task.start ? "started" : "ready", - task.description, - ]; - return bits.filter(Boolean).join(" "); -} - -export function formatTaskDetails(task: TaskwarriorTask): string { - const annotations = (task.annotations ?? []) - .map((annotation) => `- ${annotation.description}`) - .join("\n"); - - const lines = [ - `UUID: ${task.uuid}`, - `Description: ${task.description}`, - task.priority ? `Priority: ${task.priority}` : undefined, - task.status ? `Status: ${task.status}` : undefined, - task.start ? "Active: yes" : "Active: no", - annotations ? `Annotations:\n${annotations}` : undefined, - ]; - - return lines.filter(Boolean).join("\n"); -} - |
