diff --git a/.gitignore b/.gitignore index d277a4e1..753a64b2 100644 --- a/.gitignore +++ b/.gitignore @@ -29,7 +29,7 @@ Thumbs.db .opencode/ # Generated prompt files (from scripts/generate-prompts.ts) -lib/prompts/*.generated.ts +lib/prompts/**/*.generated.ts # Tests tests/results/ diff --git a/index.ts b/index.ts index d066fac8..577a0b06 100644 --- a/index.ts +++ b/index.ts @@ -112,6 +112,17 @@ const plugin: Plugin = (async (ctx) => { logger.info( `Added ${toolsToAdd.map((t) => `'${t}'`).join(" and ")} to experimental.primary_tools via config mutation`, ) + + // Set compress permission to ask (only if not already configured) + if (config.tools.compress.enabled) { + const permission = opencodeConfig.permission ?? {} + if (!("compress" in permission)) { + opencodeConfig.permission = { + ...permission, + compress: "ask", + } as typeof permission + } + } } }, } diff --git a/lib/prompts/compress-tool-spec.ts b/lib/prompts/compress-tool-spec.ts deleted file mode 100644 index 0a08b08f..00000000 --- a/lib/prompts/compress-tool-spec.ts +++ /dev/null @@ -1,57 +0,0 @@ -export const COMPRESS_TOOL_SPEC = `Collapses a contiguous range of conversation into a single summary. - -## When to Use This Tool - -Use \`compress\` when you want to condense an entire sequence of work into a brief summary: - -- **Phase Completion:** You completed a phase (research, tool calls, implementation) and want to collapse the entire sequence into a summary. -- **Exploration Done:** You explored multiple files or ran multiple commands and only need a summary of what you learned. -- **Failed Attempts:** You tried several unsuccessful approaches and want to condense them into a brief note. -- **Verbose Output:** A section of conversation has grown large but can be summarized without losing critical details. - -## When NOT to Use This Tool - -- **If you need specific details:** If you'll need exact code, file contents, or error messages from the range, keep them. -- **For individual tool outputs:** Use \`prune\` or \`distill\` for single tool outputs. Compress targets conversation ranges. -- **If it's recent content:** You may still need recent work for the current phase. - -## How It Works - -1. \`startString\` — A unique text string that marks the start of the range to compress -2. \`endString\` — A unique text string that marks the end of the range to compress -3. \`topic\` — A short label (3-5 words) describing the compressed content -4. \`summary\` — The replacement text that will be inserted - -Everything between startString and endString (inclusive) is removed and replaced with your summary. - -**Important:** The compress will FAIL if \`startString\` or \`endString\` is not found in the conversation. The compress will also FAIL if either string is found multiple times. Provide a larger string with more surrounding context to uniquely identify the intended match. - -## Best Practices -- **Choose unique strings:** Pick text that appears only once in the conversation. -- **Write concise topics:** Examples: "Auth System Exploration", "Token Logic Refactor" -- **Write comprehensive summaries:** Include key information like file names, function signatures, and important findings. -- **Timing:** Best used after finishing a work phase, not during active exploration. - -## Format - -- \`input\`: Array with four elements: [startString, endString, topic, summary] - -## Example - - -Conversation: [Asked about auth] -> [Read 5 files] -> [Analyzed patterns] -> [Found "JWT tokens with 24h expiry"] - -[Uses compress with: - input: [ - "Asked about authentication", - "JWT tokens with 24h expiry", - "Auth System Exploration", - "Auth: JWT 24h expiry, bcrypt passwords, refresh rotation. Files: auth.ts, tokens.ts, middleware/auth.ts" - ] -] - - - -Assistant: [Just finished reading auth.ts] -I've read the auth file and now need to make edits based on it. I'm keeping this in context rather than compressing. -` diff --git a/lib/prompts/compress.md b/lib/prompts/compress.md new file mode 100644 index 00000000..6a083297 --- /dev/null +++ b/lib/prompts/compress.md @@ -0,0 +1,35 @@ +Use this tool to collapse a contiguous range of conversation into a preserved summary. + +THE PHILOSOPHY OF COMPRESS +`compress` transforms verbose conversation sequences into dense, high-fidelity summaries. This is not cleanup - it is crystallization. Your summary becomes the authoritative record of what transpired. + +Think of compression as phase transitions: raw exploration becomes refined understanding. The original context served its purpose; your summary now carries that understanding forward. + +THE SUMMARY +Your summary must be EXHAUSTIVE. Capture file paths, function signatures, decisions made, constraints discovered, key findings... EVERYTHING that maintains context integrity. This is not a brief note - it is an authoritative record so faithful that the original conversation adds no value. + +Yet be LEAN. Strip away the noise: failed attempts that led nowhere, verbose tool outputs, back-and-forth exploration. What remains should be pure signal - golden nuggets of detail that preserve full understanding with zero ambiguity. + +THE WAYS OF COMPRESS +`compress` when a chapter closes - when a phase of work is truly complete and the raw conversation has served its purpose: + +Research concluded and findings are clear +Implementation finished and verified +Exploration exhausted and patterns understood + +Do NOT compress when: +You may need exact code, error messages, or file contents from the range +Work in that area is still active or may resume +You're mid-sprint on related functionality + +Before compressing, ask: _"Is this chapter closed?"_ Compression is irreversible. The summary replaces everything in the range. + +BOUNDARY MATCHING +You specify boundaries by matching unique text strings in the conversation. CRITICAL: In code-centric conversations, strings repeat often. Provide sufficiently unique text to match exactly once. If a match fails (not found or found multiple times), the tool will error - extend your boundary string with more surrounding context in order to make SURE the tool does NOT error. + +THE FORMAT OF COMPRESS +`topic`: Short label (3-5 words) for display - e.g., "Auth System Exploration" +`content`: Object containing: +`startString`: Unique text string marking the beginning of the range +`endString`: Unique text string marking the end of the range +`summary`: Complete technical summary replacing all content in the range diff --git a/lib/prompts/distill-tool-spec.ts b/lib/prompts/distill-tool-spec.ts deleted file mode 100644 index 9fccc048..00000000 --- a/lib/prompts/distill-tool-spec.ts +++ /dev/null @@ -1,47 +0,0 @@ -export const DISTILL_TOOL_SPEC = `Distills key findings from tool outputs into preserved knowledge, then removes the raw outputs from context. - -## IMPORTANT: The Prunable List -A \`\` list is provided to you showing available tool outputs you can distill from when there are tools available for pruning. Each line has the format \`ID: tool, parameter\` (e.g., \`20: read, /path/to/file.ts\`). You MUST only use numeric IDs that appear in this list to select which tools to distill. - -## When to Use This Tool - -Use \`distill\` when you have individual tool outputs with valuable information you want to **preserve in distilled form** before removing the raw content: - -- **Large Outputs:** The raw output is too large but contains valuable technical details worth keeping. -- **Knowledge Preservation:** You have context that contains valuable information (signatures, logic, constraints) but also a lot of unnecessary detail. - -## When NOT to Use This Tool - -- **If you need precise syntax:** If you'll edit a file or grep for exact strings, keep the raw output. -- **If uncertain:** Prefer keeping over re-fetching. - - -## Best Practices -- **Strategic Batching:** Wait until you have several items or a few large outputs to distill, rather than doing tiny, frequent distillations. Aim for high-impact distillations that significantly reduce context size. -- **Think ahead:** Before distilling, ask: "Will I need the raw output for upcoming work?" If you researched a file you'll later edit, do NOT distill it. - -## Format - -- \`ids\`: Array of numeric IDs as strings from the \`\` list -- \`distillation\`: Array of strings, one per ID (positional: distillation[0] is for ids[0], etc.) - -Each distillation string should capture the essential information you need to preserve - function signatures, logic, constraints, values, etc. Be as detailed as needed. - -## Example - - -Assistant: [Reads auth service and user types] -I'll preserve the key details before distilling. -[Uses distill with: - ids: ["10", "11"], - distillation: [ - "auth.ts: validateToken(token: string) -> User|null checks cache first (5min TTL) then OIDC. hashPassword uses bcrypt 12 rounds. Tokens must be 128+ chars.", - "user.ts: interface User { id: string; email: string; permissions: ('read'|'write'|'admin')[]; status: 'active'|'suspended' }" - ] -] - - - -Assistant: [Reads 'auth.ts' to understand the login flow] -I've understood the auth flow. I'll need to modify this file to add the new validation, so I'm keeping this read in context rather than distilling. -` diff --git a/lib/prompts/distill.md b/lib/prompts/distill.md new file mode 100644 index 00000000..39a78cc9 --- /dev/null +++ b/lib/prompts/distill.md @@ -0,0 +1,28 @@ +Use this tool to distill relevant findings from a selection of raw tool outputs into preserved knowledge, in order to denoise key bits and parts of context. + +THE PRUNABLE TOOLS LIST +A will show in context when outputs are available for distillation (you don't need to look for it). Each entry follows the format `ID: tool, parameter (~token usage)` (e.g., `20: read, /path/to/file.ts (~1500 tokens)`). You MUST select outputs by their numeric ID. THESE ARE YOUR ONLY VALID TARGETS. + +THE PHILOSOPHY OF DISTILLATION +`distill` is your favored instrument for transforming raw tool outputs into preserved knowledge. This is not mere summarization; it is high-fidelity extraction that makes the original output obsolete. + +Your distillation must be COMPLETE. Capture function signatures, type definitions, business logic, constraints, configuration values... EVERYTHING essential. Think of it as creating a high signal technical substitute so faithful that re-fetching the original would yield no additional value. Be thorough; be comprehensive; leave no ambiguity, ensure that your distillation stands alone, and is designed for easy retrieval and comprehension. + +AIM FOR IMPACT. Distillation is most powerful when applied to outputs that contain signal buried in noise. A single line requires no distillation; a hundred lines of API documentation do. Make sure the distillation is meaningful. + +THE WAYS OF DISTILL +`distill` when you have extracted the essence from tool outputs and the raw form has served its purpose. +Here are some examples: +EXPLORATION: You've read extensively and grasp the architecture. The original file contents are no longer needed; your understanding, synthesized, is sufficient. +PRESERVATION: Valuable technical details (signatures, logic, constraints) coexist with noise. Preserve the former; discard the latter. + +Not everything should be distilled. Prefer keeping raw outputs when: +PRECISION MATTERS: You will edit the file, grep for exact strings, or need line-accurate references. Distillation sacrifices precision for essence. +UNCERTAINTY REMAINS: If you might need to re-examine the original, defer. Distillation is irreversible; be certain before you commit. + +Before distilling, ask yourself: _"Will I need the raw output for upcoming work?"_ If you plan to edit a file you just read, keep it intact. Distillation is for completed exploration, not active work. + +THE FORMAT OF DISTILL +`targets`: Array of objects, each containing: +`id`: Numeric ID (as string) from the `` list +`distillation`: Complete technical substitute for that tool output diff --git a/lib/prompts/index.ts b/lib/prompts/index.ts index c80115f4..7520a417 100644 --- a/lib/prompts/index.ts +++ b/lib/prompts/index.ts @@ -1,11 +1,9 @@ -// Tool specs -import { PRUNE_TOOL_SPEC } from "./prune-tool-spec" -import { DISTILL_TOOL_SPEC } from "./distill-tool-spec" -import { COMPRESS_TOOL_SPEC } from "./compress-tool-spec" - // Generated prompts (from .md files via scripts/generate-prompts.ts) -import { SYSTEM as SYSTEM_PROMPT } from "./system.generated" -import { NUDGE } from "./nudge.generated" +import { SYSTEM as SYSTEM_PROMPT } from "./_codegen/system.generated" +import { NUDGE } from "./_codegen/nudge.generated" +import { PRUNE as PRUNE_TOOL_SPEC } from "./_codegen/prune.generated" +import { DISTILL as DISTILL_TOOL_SPEC } from "./_codegen/distill.generated" +import { COMPRESS as COMPRESS_TOOL_SPEC } from "./_codegen/compress.generated" export interface ToolFlags { distill: boolean diff --git a/lib/prompts/nudge.md b/lib/prompts/nudge.md index 078f166e..4e4d8e4a 100644 --- a/lib/prompts/nudge.md +++ b/lib/prompts/nudge.md @@ -7,6 +7,6 @@ You should prioritize context management, but do not interrupt a critical atomic IMMEDIATE ACTION REQUIRED KNOWLEDGE PRESERVATION: If holding valuable raw data you POTENTIALLY will need in your task, use the `distill` tool. Produce a high-fidelity distillation to preserve insights - be thorough -NOISE REMOVAL: If you read files or ran commands that yielded no value, use the `prune` tool to remove them. If newer tools supersedes older ones, prune the old PHASE COMPLETION: If a phase is complete, use the `compress` tool to condense the entire sequence into a detailed summary +NOISE REMOVAL: If you read files or ran commands that yielded no value, use the `prune` tool to remove them. If newer tools supersedes older ones, prune the old diff --git a/lib/prompts/prune-tool-spec.ts b/lib/prompts/prune-tool-spec.ts deleted file mode 100644 index c2ea3cbb..00000000 --- a/lib/prompts/prune-tool-spec.ts +++ /dev/null @@ -1,39 +0,0 @@ -export const PRUNE_TOOL_SPEC = `Prunes tool outputs from context to manage conversation size and reduce noise. - -## IMPORTANT: The Prunable List -A \`\` list is provided to you showing available tool outputs you can prune when there are tools available for pruning. Each line has the format \`ID: tool, parameter\` (e.g., \`20: read, /path/to/file.ts\`). You MUST only use numeric IDs that appear in this list to select which tools to prune. - -## When to Use This Tool - -Use \`prune\` for removing individual tool outputs that are no longer needed: - -- **Noise:** Irrelevant, unhelpful, or superseded outputs that provide no value. -- **Wrong Files:** You read or accessed something that turned out to be irrelevant. -- **Outdated Info:** Outputs that have been superseded by newer information. - -## When NOT to Use This Tool - -- **If the output contains useful information:** Keep it in context rather than pruning. -- **If you'll need the output later:** Don't prune files you plan to edit or context you'll need for implementation. - -## Best Practices -- **Strategic Batching:** Don't prune single small tool outputs (like short bash commands) unless they are pure noise. Wait until you have several items to perform high-impact prunes. -- **Think ahead:** Before pruning, ask: "Will I need this output for upcoming work?" If yes, keep it. - -## Format - -- \`ids\`: Array of numeric IDs as strings from the \`\` list - -## Example - - -Assistant: [Reads 'wrong_file.ts'] -This file isn't relevant to the auth system. I'll remove it to clear the context. -[Uses prune with ids: ["5"]] - - - -Assistant: [Reads config.ts, then reads updated config.ts after changes] -The first read is now outdated. I'll prune it and keep the updated version. -[Uses prune with ids: ["20"]] -` diff --git a/lib/prompts/prune.md b/lib/prompts/prune.md new file mode 100644 index 00000000..be18b009 --- /dev/null +++ b/lib/prompts/prune.md @@ -0,0 +1,18 @@ +Use this tool to remove tool outputs from context entirely. No preservation - pure deletion. + +THE PRUNABLE TOOLS LIST +A `` section surfaces in context showing outputs eligible for removal. Each line reads `ID: tool, parameter (~token usage)` (e.g., `20: read, /path/to/file.ts (~1500 tokens)`). Reference outputs by their numeric ID - these are your ONLY valid targets for pruning. + +THE WAYS OF PRUNE +`prune` is surgical excision - eliminating noise (irrelevant or unhelpful outputs), superseded information (older outputs replaced by newer data), or wrong targets (you accessed something that turned out to be irrelevant). Use it to keep your context lean and focused. + +BATCH WISELY! Pruning is most effective when consolidated. Don't prune a single tiny output - accumulate several candidates before acting. + +Do NOT prune when: +NEEDED LATER: You plan to edit the file or reference this context for implementation. +UNCERTAINTY: If you might need to re-examine the original, keep it. + +Before pruning, ask: _"Is this noise, or will it serve me?"_ If the latter, keep it. Pruning that forces re-fetching is a net loss. + +THE FORMAT OF PRUNE +`ids`: Array of numeric IDs (as strings) from the `` list diff --git a/lib/prompts/system.md b/lib/prompts/system.md index a0574236..4ea2b6b1 100644 --- a/lib/prompts/system.md +++ b/lib/prompts/system.md @@ -1,85 +1,42 @@ +You operate a context-constrained environment and MUST PROACTIVELY MANAGE IT TO AVOID CONTEXT ROT. Efficient context management is CRITICAL to maintaining performance and ensuring successful task completion. -ENVIRONMENT -You are operating in a context-constrained environment and must proactively manage your context window. The environment calls the `context_info` tool to provide an up-to-date list after each turn. Use this information when deciding what to prune. +AVAILABLE TOOLS FOR CONTEXT MANAGEMENT +`distill`: condense key findings from tool calls into high-fidelity distillation to preserve gained insights. Use to extract valuable knowledge to the user's request. BE THOROUGH, your distillation MUST be high-signal, low noise and complete +`compress`: squash contiguous portion of the conversation and replace it with a low level technical summary. Use to filter noise from the conversation and retain purified understanding. Compress conversation phases ORGANICALLY as they get completed, think meso, not micro nor macro. Do not be cheap with that low level technical summary and BE MINDFUL of specifics that must be crystallized to retain UNAMBIGUOUS full picture. +`prune`: remove individual tool calls that are noise, irrelevant, or superseded. No preservation of content. DO NOT let irrelevant tool calls accumulate. DO NOT PRUNE TOOL OUTPUTS THAT YOU MAY NEED LATER -IMPORTANT: The `context_info` tool is only available to the environment - you do not have access to it and must not attempt to call it. +THE DISTILL TOOL +`distill` is the favored way to target specific tools and crystalize their value into high-signal low-noise knowledge nuggets. Your distillation must be comprehensive, capturing technical details (symbols, signatures, logic, constraints) such that the raw output is no longer needed. THINK complete technical substitute. `distill` is typically best used when you are certain the raw information is not needed anymore, but the knowledge it contains is valuable to retain so you maintain context authenticity and understanding. Be conservative in your approach to distilling, but do NOT hesitate to distill when appropriate. + -AVAILABLE TOOLS -`prune`: Remove individual tool outputs that are noise, irrelevant, or superseded. No preservation of content. -`distill`: Distill key findings from individual tool outputs into preserved knowledge. Use when you need to preserve valuable technical details. -`compress`: Collapse a contiguous range of conversation (completed phases) into a single summary. +THE COMPRESS TOOL +`compress` is a sledgehammer and should be used accordingly. It's purpose is to reduce whole part of the conversation to its essence and technical details in order to leave room for newer context. Your summary MUST be technical and specific enough to preserve FULL understanding of WHAT TRANSPIRED, such that NO AMBIGUITY remains about what was done, found, or decided. Your compress summary must be thorough and precise. `compress` will replace everything in the range you match, user and assistant messages, tool inputs and outputs. It is preferred to not compress preemptively, but rather wait for natural breakpoints in the conversation. Those breakpoints are to be infered from user messages. You WILL NOT compress based on thinking that you are done with the task, wait for conversation queues that the user has moved on from current phase. -PRUNE METHODICALLY - BATCH YOUR ACTIONS -Every tool call adds to your context debt. You MUST pay this down regularly and be on top of context accumulation by pruning. Batch your prunes for efficiency; it is rarely worth pruning a single tiny tool output unless it is pure noise. Evaluate what SHOULD be pruned before jumping the gun. +This tool will typically be used at the end of a phase of work, when conversation starts to accumulate noise that would better served summarized, or when you've done significant exploration and can FULLY synthesize your findings and understanding into a technical summary. -You MUST NOT prune when: +Make sure to match enough of the context with start and end strings so you're not faced with an error calling the tool. Be VERY CAREFUL AND CONSERVATIVE when using `compress`. + -- The tool output will be needed for upcoming implementation work -- The output contains files or context you'll need to reference when making edits +THE PRUNE TOOL +`prune` is your last resort for context management. It is a blunt instrument that removes tool outputs entirely, without ANY preservation. It is best used to eliminate noise, irrelevant information, or superseded outputs that no longer add value to the conversation. You MUST NOT prune tool outputs that you may need later. Prune is a targeted nuke, not a general cleanup tool. -Pruning that forces you to re-call the same tool later is a net loss. Only prune when you're confident the information won't be needed again. - - -WHEN TO PRUNE -- **Noise Removal:** Outputs that are irrelevant, unhelpful, or superseded by newer info. -- **Wrong Files:** You read or accessed something that turned out to be irrelevant to the current work. -- **Outdated Info:** Outputs that have been superseded by newer information. - -You WILL evaluate pruning when ANY of these are true: - -You accessed something that turned out to be irrelevant -Information has been superseded by newer outputs -You are about to start a new phase of work +Contemplate only pruning when you are certain that the tool output is irrelevant to the current task or has been superseded by more recent information. If in doubt, defer for when you are definitive. Evaluate WHAT SHOULD be pruned before jumping the gun. - -WHEN TO DISTILL -**Large Outputs:** The raw output is too large but contains valuable technical details worth keeping. -**Knowledge Preservation:** Valuable context you want to preserve but need to reduce size. Your distillation must be comprehensive, capturing technical details (signatures, logic, constraints) such that the raw output is no longer needed. THINK: high signal, complete technical substitute. - -You WILL evaluate distilling when ANY of these are true: - -- You have large tool outputs with valuable technical details -- You need to preserve specific information but reduce context size -- You are about to start a new phase of work and want to retain key insights - - - WHEN TO COMPRESS -- **Phase Completion:** When a phase is complete, condense the entire sequence (research, tool calls, implementation) into a summary. -- **Exploration Done:** When you've explored multiple files or ran multiple commands and only need a summary of findings. +TIMING +Prefer managing context at the START of a new agentic loop (after receiving a user message) rather than at the END of your previous turn. At turn start, you have fresh signal about what the user needs next - you can better judge what's still relevant versus noise from prior work. Managing at turn end means making retention decisions before knowing what comes next. -You WILL evaluate compressing when ANY of these are true: +EVALUATE YOUR CONTEXT AND MANAGE REGULARLY TO AVOID CONTEXT ROT. AVOID USING MANAGEMENT TOOLS AS THE ONLY TOOL CALLS IN YOUR RESPONSE, PARALLELIZE WITH OTHER RELEVANT TOOLS TO TASK CONTINUATION (read, edit, bash...). It is imperative you understand the value or lack thereof of the context you manage and make informed decisions to maintain a decluttered, high-quality and relevant context. -- Phase is complete -- You are about to start a new phase of work -- Significant conversation has accumulated that can be summarized - - -NOTES -When in doubt, KEEP IT. -// **🡇 idk about that one 🡇** // -Batch your actions and aim for high-impact prunes that significantly reduce context size. -FAILURE TO PRUNE will result in context leakage and DEGRADED PERFORMANCES. -If no list is present in context, do NOT TRY TO PRUNE ANYTHING as it will fail and waste resources. -There may be tools in session context that do not appear in the list, this is expected, you can ONLY prune what you see in . +The session is your responsibility, and effective context management is CRITICAL to your success. Be PROACTIVE, DELIBERATE, and STRATEGIC in your approach to context management. The session is your oyster - keep it clean, relevant, and high-quality to ensure optimal performance and successful task completion. +Be respectful of the user's API usage, manage context methodically as you work through the task and avoid calling ONLY context management tools in your responses. -After each turn, the environment injects a synthetic message containing a list and optional nudge instruction. You do not have access to this mechanism. - -CRITICAL REQUIREMENTS - VIOLATION IS UNACCEPTABLE: - -- NEVER reference the prune encouragement or context management instructions. Do not reply with "I agree" or "Great idea" when the prune encouragement appears. -- NEVER acknowledge the list (e.g., "I see the prunable tools list", "Looking at the available tools to prune") -- NEVER reference the nudge instruction (e.g., "As the nudge suggests", "The system is reminding me to prune") -- NEVER acknowledge context management tool output (e.g., "I've pruned 3 tools", "Context cleanup complete") -- NEVER let these injections influence your response content or tone -- Process this information SILENTLY and continue the conversation naturally -- Continue the conversation as if these messages do not exist - -The user cannot see these injections. Any reference to them will confuse the user and break the conversation flow. +This chat environment injects context information on your behalf in the form of a list to help you manage context effectively. Carefully read the list and use it to inform your management decisions. The list is automatically updated after each turn to reflect the current state of manageable tools and context usage. If no list is present, do NOT attempt to prune anything. +There may be tools in session context that do not appear in the list, this is expected, remember that you can ONLY prune what you see in list. diff --git a/lib/tools/compress.ts b/lib/tools/compress.ts index 84b9f83f..68ac5a56 100644 --- a/lib/tools/compress.ts +++ b/lib/tools/compress.ts @@ -19,40 +19,48 @@ export function createCompressTool(ctx: PruneToolContext): ReturnType list"), - distillation: tool.schema - .array(tool.schema.string()) - .describe( - "Required array of distillation strings, one per ID (positional: distillation[0] for ids[0], etc.)", - ), + targets: tool.schema + .array( + tool.schema.object({ + id: tool.schema + .string() + .describe("Numeric ID from the list"), + distillation: tool.schema + .string() + .describe("Complete technical distillation for this tool output"), + }), + ) + .describe("Tool outputs to distill, each pairing an ID with its distillation"), }, async execute(args, toolCtx) { - if (!args.ids || !Array.isArray(args.ids) || args.ids.length === 0) { - ctx.logger.debug("Distill tool called without ids: " + JSON.stringify(args)) - throw new Error("Missing ids. You must provide at least one ID to distill.") + if (!args.targets || !Array.isArray(args.targets) || args.targets.length === 0) { + ctx.logger.debug("Distill tool called without targets: " + JSON.stringify(args)) + throw new Error("Missing targets. Provide at least one { id, distillation } entry.") } - if (!args.ids.every((id) => typeof id === "string" && id.trim() !== "")) { - ctx.logger.debug("Distill tool called with invalid ids: " + JSON.stringify(args)) - throw new Error( - 'Invalid ids. All IDs must be numeric strings (e.g., "1", "23") from the list.', - ) - } - - if ( - !args.distillation || - !Array.isArray(args.distillation) || - args.distillation.length === 0 - ) { - ctx.logger.debug( - "Distill tool called without distillation: " + JSON.stringify(args), - ) - throw new Error( - 'Missing distillation. You must provide an array of strings (e.g., ["summary 1", "summary 2"]).', - ) - } - - if (!args.distillation.every((d) => typeof d === "string")) { - ctx.logger.debug( - "Distill tool called with non-string distillation: " + JSON.stringify(args), - ) - throw new Error("Invalid distillation. All distillation entries must be strings.") + for (const target of args.targets) { + if (!target.id || typeof target.id !== "string" || target.id.trim() === "") { + ctx.logger.debug("Distill target missing id: " + JSON.stringify(target)) + throw new Error( + "Each target must have an id (numeric string from ).", + ) + } + if (!target.distillation || typeof target.distillation !== "string") { + ctx.logger.debug( + "Distill target missing distillation: " + JSON.stringify(target), + ) + throw new Error("Each target must have a distillation string.") + } } - // ctx.logger.info("Distillation data received:") - // ctx.logger.info(JSON.stringify(args.distillation, null, 2)) + const ids = args.targets.map((t) => t.id) + const distillations = args.targets.map((t) => t.distillation) return executePruneOperation( ctx, toolCtx, - args.ids, + ids, "extraction" as PruneReason, "Distill", - args.distillation, + distillations, ) }, }) diff --git a/package-lock.json b/package-lock.json index 317b7cd1..d9abd5ae 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,7 +7,7 @@ "": { "name": "@tarquinen/opencode-dcp", "version": "1.3.3-beta.0", - "license": "MIT", + "license": "AGPL-3.0-or-later", "dependencies": { "@anthropic-ai/tokenizer": "^0.0.4", "@opencode-ai/sdk": "^1.1.48", @@ -16,7 +16,7 @@ "zod": "^4.3.6" }, "devDependencies": { - "@opencode-ai/plugin": "^1.1.48", + "@opencode-ai/plugin": "^1.1.49", "@types/node": "^25.1.0", "prettier": "^3.8.1", "tsx": "^4.21.0", @@ -494,13 +494,13 @@ } }, "node_modules/@opencode-ai/plugin": { - "version": "1.1.48", - "resolved": "https://registry.npmjs.org/@opencode-ai/plugin/-/plugin-1.1.48.tgz", - "integrity": "sha512-KkaSMevXmz7tOwYDMJeWiXE5N8LmRP18qWI5Xhv3+c+FdGPL+l1hQrjSgyv3k7Co7qpCyW3kAUESBB7BzIOl2w==", + "version": "1.1.49", + "resolved": "https://registry.npmjs.org/@opencode-ai/plugin/-/plugin-1.1.49.tgz", + "integrity": "sha512-+FEE730fLJtoHCta5MXixOIzI9Cjos700QDNnAx6mA8YjFzO+kABnyqLQrCgZ9wUPJgiKH9bnHxT7AdRjWsNPw==", "dev": true, "license": "MIT", "dependencies": { - "@opencode-ai/sdk": "1.1.48", + "@opencode-ai/sdk": "1.1.49", "zod": "4.1.8" } }, @@ -515,9 +515,9 @@ } }, "node_modules/@opencode-ai/sdk": { - "version": "1.1.48", - "resolved": "https://registry.npmjs.org/@opencode-ai/sdk/-/sdk-1.1.48.tgz", - "integrity": "sha512-j5/79X45fUPWVD2Ffm/qvwLclDCdPeV+TYMDrm9to0p4pmzhmeKevCsyiRdLg0o0HE3AFRUnOo2rdO9NetN79A==", + "version": "1.1.49", + "resolved": "https://registry.npmjs.org/@opencode-ai/sdk/-/sdk-1.1.49.tgz", + "integrity": "sha512-F5ZkgiqOiV+z3U4zeBLvrmNZv5MwNFMTWM+HWhChD+/UEswIebQKk9UMz9lPX4fswexIJdFPwFI/TBdNyZfKMg==", "license": "MIT" }, "node_modules/@types/node": { diff --git a/package.json b/package.json index 8a489139..235d3f0c 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,7 @@ "zod": "^4.3.6" }, "devDependencies": { - "@opencode-ai/plugin": "^1.1.48", + "@opencode-ai/plugin": "^1.1.49", "@types/node": "^25.1.0", "prettier": "^3.8.1", "tsx": "^4.21.0", diff --git a/scripts/generate-prompts.ts b/scripts/generate-prompts.ts index 815a54db..e83ffe75 100644 --- a/scripts/generate-prompts.ts +++ b/scripts/generate-prompts.ts @@ -9,12 +9,23 @@ * .ts files with exported string constants that bundle correctly. */ -import { readFileSync, writeFileSync, readdirSync } from "node:fs" +import { readFileSync, writeFileSync, readdirSync, mkdirSync, unlinkSync } from "node:fs" import { dirname, join, basename } from "node:path" import { fileURLToPath } from "node:url" const __dirname = dirname(fileURLToPath(import.meta.url)) const PROMPTS_DIR = join(__dirname, "..", "lib", "prompts") +const CODEGEN_DIR = join(PROMPTS_DIR, "_codegen") + +// Ensure _codegen directory exists +mkdirSync(CODEGEN_DIR, { recursive: true }) + +// MIGRATION - Clean up old generated files from the prompts directory root (they're now in _codegen/) +const oldGeneratedFiles = readdirSync(PROMPTS_DIR).filter((f) => f.endsWith(".generated.ts")) +for (const file of oldGeneratedFiles) { + unlinkSync(join(PROMPTS_DIR, file)) + console.log(`Cleaned up old: ${file}`) +} // Find all .md files in the prompts directory const mdFiles = readdirSync(PROMPTS_DIR).filter((f) => f.endsWith(".md")) @@ -23,7 +34,7 @@ for (const mdFile of mdFiles) { const mdPath = join(PROMPTS_DIR, mdFile) const baseName = basename(mdFile, ".md") const constName = baseName.toUpperCase().replace(/-/g, "_") - const tsPath = join(PROMPTS_DIR, `${baseName}.generated.ts`) + const tsPath = join(CODEGEN_DIR, `${baseName}.generated.ts`) const content = readFileSync(mdPath, "utf-8")