From 3763dcec9fa4e8fddb406fc0b73b3dfb6e288718 Mon Sep 17 00:00:00 2001 From: yosef-zewdu Date: Wed, 18 Feb 2026 20:26:53 +0300 Subject: [PATCH 01/23] feat: add pre hook - intent validation & scope inforcement --- src/hooks/pre/IntentValidationHook.ts | 6 ++++++ src/hooks/pre/ScopeEnforcementHook.ts | 6 ++++++ 2 files changed, 12 insertions(+) create mode 100644 src/hooks/pre/IntentValidationHook.ts create mode 100644 src/hooks/pre/ScopeEnforcementHook.ts diff --git a/src/hooks/pre/IntentValidationHook.ts b/src/hooks/pre/IntentValidationHook.ts new file mode 100644 index 00000000000..4f443bb8ba4 --- /dev/null +++ b/src/hooks/pre/IntentValidationHook.ts @@ -0,0 +1,6 @@ +/** + * Validates that an intent is active before destructive tool use. + */ +export class IntentValidationHook { + // Skeleton for validation logic +} diff --git a/src/hooks/pre/ScopeEnforcementHook.ts b/src/hooks/pre/ScopeEnforcementHook.ts new file mode 100644 index 00000000000..8258eabe61a --- /dev/null +++ b/src/hooks/pre/ScopeEnforcementHook.ts @@ -0,0 +1,6 @@ +/** + * Enforces file access scope based on active intent. + */ +export class ScopeEnforcementHook { + // Skeleton for scope logic +} From 2b7c7c0b13cce43b7a2cadd4dd5523991f144349 Mon Sep 17 00:00:00 2001 From: yosef-zewdu Date: Wed, 18 Feb 2026 20:28:43 +0300 Subject: [PATCH 02/23] feat: add post hook - trace logging --- src/hooks/post/TraceLoggingHook.ts | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 src/hooks/post/TraceLoggingHook.ts diff --git a/src/hooks/post/TraceLoggingHook.ts b/src/hooks/post/TraceLoggingHook.ts new file mode 100644 index 00000000000..99a2a7ed9ee --- /dev/null +++ b/src/hooks/post/TraceLoggingHook.ts @@ -0,0 +1,6 @@ +/** + * Records tool execution history for governance. + */ +export class TraceLoggingHook { + // Skeleton for logging logic +} From fe1c0027fba51880fbf1cefa4ec5dfeb2b61f29e Mon Sep 17 00:00:00 2001 From: yosef-zewdu Date: Wed, 18 Feb 2026 20:29:41 +0300 Subject: [PATCH 03/23] feat: add hook engine & hook context files --- src/hooks/HookContext.ts | 6 ++++++ src/hooks/HookEngine.ts | 6 ++++++ src/hooks/types.ts | 13 +++++++++++++ 3 files changed, 25 insertions(+) create mode 100644 src/hooks/HookContext.ts create mode 100644 src/hooks/HookEngine.ts create mode 100644 src/hooks/types.ts diff --git a/src/hooks/HookContext.ts b/src/hooks/HookContext.ts new file mode 100644 index 00000000000..a33565b6ad4 --- /dev/null +++ b/src/hooks/HookContext.ts @@ -0,0 +1,6 @@ +/** + * Shared context for hook execution. + */ +export class HookContext { + // Skeleton for context state +} diff --git a/src/hooks/HookEngine.ts b/src/hooks/HookEngine.ts new file mode 100644 index 00000000000..ef0efe77388 --- /dev/null +++ b/src/hooks/HookEngine.ts @@ -0,0 +1,6 @@ +/** + * Central orchestrator for pre and post tool hooks. + */ +export class HookEngine { + // Skeleton for engine logic +} diff --git a/src/hooks/types.ts b/src/hooks/types.ts new file mode 100644 index 00000000000..488526ed8ef --- /dev/null +++ b/src/hooks/types.ts @@ -0,0 +1,13 @@ +/** + * Core types for the modular hook system. + */ +export enum HookType { + PRE = "pre", + POST = "post", +} + +export interface ToolHook { + name: string + type: HookType + execute(...args: any[]): Promise +} From ef18a039b66e819e9e97dd1a7cb99f63a79dc6bb Mon Sep 17 00:00:00 2001 From: yosef-zewdu Date: Wed, 18 Feb 2026 21:05:44 +0300 Subject: [PATCH 04/23] feat: Add orchestration files for intent management --- .orchestration/active_intent.yaml | 1 + .orchestration/agent_trace.jsonl | 0 .orchestration/intent_map.yaml | 1 + 3 files changed, 2 insertions(+) create mode 100644 .orchestration/active_intent.yaml create mode 100644 .orchestration/agent_trace.jsonl create mode 100644 .orchestration/intent_map.yaml diff --git a/.orchestration/active_intent.yaml b/.orchestration/active_intent.yaml new file mode 100644 index 00000000000..957490d0db9 --- /dev/null +++ b/.orchestration/active_intent.yaml @@ -0,0 +1 @@ +active_intent_id: null diff --git a/.orchestration/agent_trace.jsonl b/.orchestration/agent_trace.jsonl new file mode 100644 index 00000000000..e69de29bb2d diff --git a/.orchestration/intent_map.yaml b/.orchestration/intent_map.yaml new file mode 100644 index 00000000000..7c7bc7c9e69 --- /dev/null +++ b/.orchestration/intent_map.yaml @@ -0,0 +1 @@ +intents: [] From c35a2bbacb8e8d738e38f53a73c2c5bc84076262 Mon Sep 17 00:00:00 2001 From: yosef-zewdu Date: Wed, 18 Feb 2026 23:15:33 +0300 Subject: [PATCH 05/23] feat: Add Intent Protocol section for pompt & update system prompt --- src/core/prompts/sections/index.ts | 1 + src/core/prompts/sections/intent-protocol.ts | 12 ++++++++++++ src/core/prompts/system.ts | 3 +++ 3 files changed, 16 insertions(+) create mode 100644 src/core/prompts/sections/intent-protocol.ts diff --git a/src/core/prompts/sections/index.ts b/src/core/prompts/sections/index.ts index 318cd47bc9d..64127bd6b4b 100644 --- a/src/core/prompts/sections/index.ts +++ b/src/core/prompts/sections/index.ts @@ -8,3 +8,4 @@ export { getCapabilitiesSection } from "./capabilities" export { getModesSection } from "./modes" export { markdownFormattingSection } from "./markdown-formatting" export { getSkillsSection } from "./skills" +export { getIntentProtocolSection } from "./intent-protocol" diff --git a/src/core/prompts/sections/intent-protocol.ts b/src/core/prompts/sections/intent-protocol.ts new file mode 100644 index 00000000000..6426255b28f --- /dev/null +++ b/src/core/prompts/sections/intent-protocol.ts @@ -0,0 +1,12 @@ +export function getIntentProtocolSection(): string { + return `## Intent-Driven Development Protocol + +You are an Intent-Driven Architect. To ensure architectural governance and traceability, you MUST follow this protocol: + +1. **Analyze First**: Before making any changes or executing destructive commands, analyze the user's request. +2. **Select Intent**: You MUST call \`select_active_intent\` to load the specific context, constraints, and scope for the relevant business intent. +3. **Wait for Context**: Do not guess architectural boundaries. The \`select_active_intent\` tool will provide you with the \`\` block containing the necessary rules. +4. **Enforce Constraints**: Once an intent is selected, you must adhere to its constraints and work within its owned scope. + +**CRITICAL**: You CANNOT write code or execute destructive commands immediately. Your first action MUST be to call \`select_active_intent\` if a relevant intent exists.` +} diff --git a/src/core/prompts/system.ts b/src/core/prompts/system.ts index 0d6071644a9..a5c49cc5ad6 100644 --- a/src/core/prompts/system.ts +++ b/src/core/prompts/system.ts @@ -23,6 +23,7 @@ import { addCustomInstructions, markdownFormattingSection, getSkillsSection, + getIntentProtocolSection, } from "./sections" // Helper function to get prompt component, filtering out empty objects @@ -86,6 +87,8 @@ async function generatePrompt( ${markdownFormattingSection()} +${getIntentProtocolSection()} + ${getSharedToolUseSection()}${toolsCatalog} ${getToolUseGuidelinesSection()} From a45dd5085e56a67963176eff238521f0850581e0 Mon Sep 17 00:00:00 2001 From: yosef-zewdu Date: Thu, 19 Feb 2026 00:02:21 +0300 Subject: [PATCH 06/23] updated active intent, & intent map --- .orchestration/active_intent.yaml | 3 ++- .orchestration/agent_trace.jsonl | 2 ++ .orchestration/intent_map.yaml | 17 ++++++++++++++++- 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/.orchestration/active_intent.yaml b/.orchestration/active_intent.yaml index 957490d0db9..6ddcc97b28d 100644 --- a/.orchestration/active_intent.yaml +++ b/.orchestration/active_intent.yaml @@ -1 +1,2 @@ -active_intent_id: null +active_intent_id: "GOV-INIT" +timestamp: "2026-02-18T23:30:00Z" diff --git a/.orchestration/agent_trace.jsonl b/.orchestration/agent_trace.jsonl index e69de29bb2d..b6c1e933b9d 100644 --- a/.orchestration/agent_trace.jsonl +++ b/.orchestration/agent_trace.jsonl @@ -0,0 +1,2 @@ +{"timestamp":"2026-02-18T23:30:00Z","intentId":"GOV-INIT","tool":"write_to_file","path":".orchestration/intent_map.yaml","success":true,"error":null} +{"timestamp":"2026-02-18T23:30:05Z","intentId":"GOV-INIT","tool":"write_to_file","path":".orchestration/active_intent.yaml","success":true,"error":null} diff --git a/.orchestration/intent_map.yaml b/.orchestration/intent_map.yaml index 7c7bc7c9e69..829efa7d372 100644 --- a/.orchestration/intent_map.yaml +++ b/.orchestration/intent_map.yaml @@ -1 +1,16 @@ -intents: [] +intents: + - id: "GOV-INIT" + name: "Governance Initialization" + description: "Initial setup of the governed IDE hooks and sidecars" + priority: 1 + owned_scope: ["src/hooks", ".orchestration"] + - id: "CORE-REF" + name: "Core Refactoring" + description: "Refactoring core components like presentAssistantMessage and Task" + priority: 2 + owned_scope: ["src/core"] + - id: "PROMPT-UP" + name: "Prompt Engineering" + description: "Updating system prompts and intent protocol sections" + priority: 2 + owned_scope: ["src/core/prompts"] From fe8e83b9d07a3c64dfac8a20386552e6765faf3b Mon Sep 17 00:00:00 2001 From: yosef-zewdu Date: Fri, 20 Feb 2026 10:45:44 +0300 Subject: [PATCH 07/23] feat: Add IntentValidationHook to enforce active intent selection for destructive tools --- src/hooks/pre/IntentValidationHook.ts | 45 +++++++++++++++++++++++++-- 1 file changed, 42 insertions(+), 3 deletions(-) diff --git a/src/hooks/pre/IntentValidationHook.ts b/src/hooks/pre/IntentValidationHook.ts index 4f443bb8ba4..c477ebf1fe3 100644 --- a/src/hooks/pre/IntentValidationHook.ts +++ b/src/hooks/pre/IntentValidationHook.ts @@ -1,6 +1,45 @@ +import { IToolHook, HookType, HookContext, HookResponse } from "../types" + /** - * Validates that an intent is active before destructive tool use. + * Validates that an active intent is selected before executing destructive tools. */ -export class IntentValidationHook { - // Skeleton for validation logic +export class IntentValidationHook implements IToolHook { + name = "IntentValidationHook" + type = HookType.PRE + + private destructiveTools = [ + "write_to_file", + "execute_command", + "apply_diff", + "edit", + "search_and_replace", + "search_replace", + "edit_file", + "apply_patch", + ] + + async execute(context: HookContext): Promise { + const { task, toolName, intentId, arguments: toolArgs } = context + + if (this.destructiveTools.includes(toolName)) { + let shouldBlock = !intentId + + // If it's a command, check if it's actually destructive + if (toolName === "execute_command") { + const command = toolArgs.command || "" + shouldBlock = shouldBlock && task.intentController.isDestructiveCommand(command) + } + + if (shouldBlock) { + // Only block if the project actually has intents defined (governance is active) + const hasIntents = task.intentController.getAllIntents().length > 0 + if (hasIntents) { + return { + allow: false, + reason: `Access Denied: This project is under architectural governance. You MUST select an active intent from the catalog using 'select_active_intent' BEFORE you can use destructive tools like '${toolName}'. Your very next action MUST be to call 'select_active_intent'.`, + } + } + } + } + } } From 4b3c23524db8a6363ff3fb084b29591ee58aee81 Mon Sep 17 00:00:00 2001 From: yosef-zewdu Date: Fri, 20 Feb 2026 10:46:21 +0300 Subject: [PATCH 08/23] feat: Implement ScopeEnforcementHook to restrict file modifications to intent's owned scope --- src/hooks/pre/ScopeEnforcementHook.ts | 30 ++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/src/hooks/pre/ScopeEnforcementHook.ts b/src/hooks/pre/ScopeEnforcementHook.ts index 8258eabe61a..6bb32eaaba1 100644 --- a/src/hooks/pre/ScopeEnforcementHook.ts +++ b/src/hooks/pre/ScopeEnforcementHook.ts @@ -1,6 +1,30 @@ +import { IToolHook, HookType, HookContext, HookResponse } from "../types" + /** - * Enforces file access scope based on active intent. + * Enforces that file modifications stay within the intent's owned scope. */ -export class ScopeEnforcementHook { - // Skeleton for scope logic +export class ScopeEnforcementHook implements IToolHook { + name = "ScopeEnforcementHook" + type = HookType.PRE + + async execute(context: HookContext): Promise { + const { task, toolName, intentId, arguments: toolArgs } = context + + if (!intentId) return + + if (toolName === "write_to_file" || toolName === "apply_diff") { + const filePath = (toolArgs.path || "") as string + const intent = task.intentController.getIntent(intentId) + + if (intent && filePath) { + const isScoped = intent.owned_scope.some((scope) => filePath.includes(scope)) + if (!isScoped) { + return { + allow: false, + reason: `Security Violation: File '${filePath}' is outside the owned scope of intent '${intentId}'. You MUST either select a different intent that covers this file or ask the user to adjust the scope in '.orchestration/intent_map.yaml'.`, + } + } + } + } + } } From af06c88f40279e0af38184ea4cc9b11e02a37220 Mon Sep 17 00:00:00 2001 From: yosef-zewdu Date: Fri, 20 Feb 2026 10:54:57 +0300 Subject: [PATCH 09/23] feat: Add TraceLoggingHook to log intent-driven changes --- src/hooks/post/TraceLoggingHook.ts | 33 +++++++++++++++++++++++++++--- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/src/hooks/post/TraceLoggingHook.ts b/src/hooks/post/TraceLoggingHook.ts index 99a2a7ed9ee..c1b911d8442 100644 --- a/src/hooks/post/TraceLoggingHook.ts +++ b/src/hooks/post/TraceLoggingHook.ts @@ -1,6 +1,33 @@ +import path from "path" +import fs from "fs/promises" +import { IToolHook, HookType, HookContext } from "../types" +import { fileExistsAtPath } from "../../utils/fs" + /** - * Records tool execution history for governance. + * Automates project governance by logging all intent-driven changes. */ -export class TraceLoggingHook { - // Skeleton for logging logic +export class TraceLoggingHook implements IToolHook { + name = "TraceLoggingHook" + type = HookType.POST + + async execute(context: HookContext): Promise { + const { task, toolName, intentId, arguments: toolArgs, timestamp, result, error } = context + + try { + const logPath = path.join(task.cwd, ".orchestration", "agent_trace.jsonl") + const entry = { + timestamp, + intentId: intentId || "NONE", + tool: toolName, + path: toolArgs.path || toolArgs.file_path || null, + success: !error && !result?.includes("Access Denied"), // Hack for blocked hooks returning text errors + error: error ? error.message : result?.includes("Access Denied") ? result : null, + } + + const logLine = JSON.stringify(entry) + "\n" + await fs.appendFile(logPath, logLine, "utf8") + } catch (err) { + console.error("Error writing to agent_trace.jsonl:", err) + } + } } From fe7975ac0cb8486e2beed64d7c7c91a844a5a666 Mon Sep 17 00:00:00 2001 From: yosef-zewdu Date: Fri, 20 Feb 2026 10:58:25 +0300 Subject: [PATCH 10/23] feat: Refactor hook types to include HookResponse and HookContext interfaces --- src/hooks/types.ts | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/src/hooks/types.ts b/src/hooks/types.ts index 488526ed8ef..e7827ed11f3 100644 --- a/src/hooks/types.ts +++ b/src/hooks/types.ts @@ -1,13 +1,27 @@ -/** - * Core types for the modular hook system. - */ +import { Task } from "../core/task/Task" + export enum HookType { PRE = "pre", POST = "post", } -export interface ToolHook { +export interface HookResponse { + allow: boolean + reason?: string +} + +export interface IToolHook { name: string type: HookType - execute(...args: any[]): Promise + execute(context: HookContext): Promise +} + +export interface HookContext { + task: Task + toolName: string + arguments: any + intentId?: string + result?: any + error?: any + timestamp: string } From 8c97f578a502cfc7f28f0eb739022e129bda1a9c Mon Sep 17 00:00:00 2001 From: yosef-zewdu Date: Fri, 20 Feb 2026 10:58:57 +0300 Subject: [PATCH 11/23] feat: Enhance HookEngine with pre and post hook registration and execution methods --- src/hooks/HookEngine.ts | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/src/hooks/HookEngine.ts b/src/hooks/HookEngine.ts index ef0efe77388..c63b53816ad 100644 --- a/src/hooks/HookEngine.ts +++ b/src/hooks/HookEngine.ts @@ -1,6 +1,33 @@ +import { IToolHook, HookType, HookContext, HookResponse } from "./types" + /** - * Central orchestrator for pre and post tool hooks. + * HookEngine manages the execution of pre and post tool hooks. */ export class HookEngine { - // Skeleton for engine logic + private preHooks: IToolHook[] = [] + private postHooks: IToolHook[] = [] + + registerHook(hook: IToolHook) { + if (hook.type === HookType.PRE) { + this.preHooks.push(hook) + } else { + this.postHooks.push(hook) + } + } + + async executePreHooks(context: HookContext): Promise { + for (const hook of this.preHooks) { + const response = await hook.execute(context) + if (response && !response.allow) { + return response + } + } + return { allow: true } + } + + async executePostHooks(context: HookContext): Promise { + for (const hook of this.postHooks) { + await hook.execute(context) + } + } } From c1991d4fb5bbb4f0d04c15e025c36c8c25f54c5e Mon Sep 17 00:00:00 2001 From: yosef-zewdu Date: Fri, 20 Feb 2026 10:59:33 +0300 Subject: [PATCH 12/23] feat: Introduce SelectActiveIntentTool for managing active intent selection --- src/core/tools/SelectActiveIntentTool.ts | 47 ++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 src/core/tools/SelectActiveIntentTool.ts diff --git a/src/core/tools/SelectActiveIntentTool.ts b/src/core/tools/SelectActiveIntentTool.ts new file mode 100644 index 00000000000..9e6134aab50 --- /dev/null +++ b/src/core/tools/SelectActiveIntentTool.ts @@ -0,0 +1,47 @@ +import { Task } from "../task/Task" +import { BaseTool, ToolCallbacks } from "./BaseTool" + +interface SelectActiveIntentParams { + intent_id: string +} + +export class SelectActiveIntentTool extends BaseTool<"select_active_intent"> { + readonly name = "select_active_intent" as const + + async execute(params: SelectActiveIntentParams, task: Task, callbacks: ToolCallbacks): Promise { + const { pushToolResult, handleError } = callbacks + const { intent_id } = params + + try { + if (!task.intentController) { + throw new Error("IntentController not initialized") + } + + const intent = task.intentController.getIntent(intent_id) + + if (!intent) { + pushToolResult(`Error: Intent ID '${intent_id}' not found in intent_map.yaml.`) + return + } + + task.intentController.setActiveIntentId(intent_id) + + const contextBlock = [ + ``, + `ID: ${intent.id}`, + `Description: ${intent.description}`, + `Constraints:`, + ...intent.constraints.map((c) => `- ${c}`), + `Scope:`, + ...intent.owned_scope.map((s) => `- ${s}`), + ``, + ].join("\n") + + pushToolResult(contextBlock) + } catch (error) { + await handleError("selecting active intent", error as Error) + } + } +} + +export const selectActiveIntentTool = new SelectActiveIntentTool() From 6cdd91f58865898412f32d9b3f318fc478b9920a Mon Sep 17 00:00:00 2001 From: yosef-zewdu Date: Fri, 20 Feb 2026 11:01:56 +0300 Subject: [PATCH 13/23] feat: Add select_active_intent tool for loading specific intent context --- .../native-tools/select_active_intent.ts | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 src/core/prompts/tools/native-tools/select_active_intent.ts diff --git a/src/core/prompts/tools/native-tools/select_active_intent.ts b/src/core/prompts/tools/native-tools/select_active_intent.ts new file mode 100644 index 00000000000..43ac7cee2d7 --- /dev/null +++ b/src/core/prompts/tools/native-tools/select_active_intent.ts @@ -0,0 +1,25 @@ +import type OpenAI from "openai" + +const SELECT_ACTIVE_INTENT_DESCRIPTION = `Select an active intent to load its specific context, constraints, and scope. You MUST call this tool before making any changes if an active intent is relevant to your task.` + +const INTENT_ID_PARAMETER_DESCRIPTION = `The ID of the active intent to load (e.g., 'INT-001').` + +export default { + type: "function", + function: { + name: "select_active_intent", + description: SELECT_ACTIVE_INTENT_DESCRIPTION, + strict: true, + parameters: { + type: "object", + properties: { + intent_id: { + type: "string", + description: INTENT_ID_PARAMETER_DESCRIPTION, + }, + }, + required: ["intent_id"], + additionalProperties: false, + }, + }, +} satisfies OpenAI.Chat.ChatCompletionTool From 4f4745564d073fd9889d7da072eb665d186237eb Mon Sep 17 00:00:00 2001 From: yosef-zewdu Date: Fri, 20 Feb 2026 11:02:51 +0300 Subject: [PATCH 14/23] feat: Implement IntentController for managing active intents and file synchronization --- src/core/intent/IntentController.ts | 125 ++++++++++++++++++++++++++++ 1 file changed, 125 insertions(+) create mode 100644 src/core/intent/IntentController.ts diff --git a/src/core/intent/IntentController.ts b/src/core/intent/IntentController.ts new file mode 100644 index 00000000000..3e8e9e2024b --- /dev/null +++ b/src/core/intent/IntentController.ts @@ -0,0 +1,125 @@ +import path from "path" +import fs from "fs/promises" +import * as vscode from "vscode" +import YAML from "yaml" +import { fileExistsAtPath } from "../../utils/fs" + +export interface Intent { + id: string + description: string + constraints: string[] + owned_scope: string[] +} + +export interface ActiveIntentsConfig { + intents: Intent[] +} + +/** + * Manages active intents and enforces architectural constraints. + */ +export class IntentController { + private cwd: string + private disposables: vscode.Disposable[] = [] + private intents: Intent[] = [] + private activeIntentId: string | undefined + + constructor(cwd: string) { + this.cwd = cwd + } + + async initialize(): Promise { + await this.loadIntents() + this.setupFileWatcher() + } + + private setupFileWatcher(): void { + const pattern = new vscode.RelativePattern(path.join(this.cwd, ".orchestration"), "intent_map.yaml") + const fileWatcher = vscode.workspace.createFileSystemWatcher(pattern) + + this.disposables.push( + fileWatcher.onDidChange(() => this.loadIntents()), + fileWatcher.onDidCreate(() => this.loadIntents()), + fileWatcher.onDidDelete(() => { + this.intents = [] + }), + ) + + this.disposables.push(fileWatcher) + } + + private async loadIntents(): Promise { + try { + const configPath = path.join(this.cwd, ".orchestration", "intent_map.yaml") + if (await fileExistsAtPath(configPath)) { + const content = await fs.readFile(configPath, "utf8") + const config = YAML.parse(content) as ActiveIntentsConfig + this.intents = config.intents || [] + } + + const activePath = path.join(this.cwd, ".orchestration", "active_intent.yaml") + if (await fileExistsAtPath(activePath)) { + const content = await fs.readFile(activePath, "utf8") + const activeConfig = YAML.parse(content) as any + this.activeIntentId = activeConfig?.active_intent_id || undefined + } + } catch (error) { + console.error("Error loading orchestration files:", error) + } + } + + getActiveIntentId(): string | undefined { + return this.activeIntentId + } + + async setActiveIntentId(intentId: string | undefined): Promise { + this.activeIntentId = intentId + try { + const activePath = path.join(this.cwd, ".orchestration", "active_intent.yaml") + const content = YAML.stringify({ + active_intent_id: intentId || null, + timestamp: new Date().toISOString(), + }) + await fs.writeFile(activePath, content, "utf8") + } catch (error) { + console.error("Error persisting active intent:", error) + } + } + + getIntent(intentId: string): Intent | undefined { + return this.intents.find((i) => i.id === intentId) + } + + getAllIntents(): Intent[] { + return this.intents + } + + isDestructiveCommand(command: string): boolean { + const safeCommands = [ + "ls", + "dir", + "pwd", + "git status", + "git log", + "git diff", + "cat", + "type", + "git branch", + "git show", + "find", + "grep", + ] + + const trimmedCommand = command.trim() + if (safeCommands.some((c) => trimmedCommand.startsWith(c))) { + return false + } + + return true + } + + dispose(): void { + this.disposables.forEach((d) => d.dispose()) + this.disposables = [] + } +} From a17ef6326912ac71cfa88003dd21f583296ba35e Mon Sep 17 00:00:00 2001 From: yosef-zewdu Date: Fri, 20 Feb 2026 11:04:55 +0300 Subject: [PATCH 15/23] feat: Integrate intent management into assistant message processing --- .../presentAssistantMessage.ts | 510 ++++++++++-------- src/core/prompts/sections/intent-protocol.ts | 22 +- src/core/prompts/system.ts | 8 +- src/core/prompts/tools/native-tools/index.ts | 2 + 4 files changed, 308 insertions(+), 234 deletions(-) diff --git a/src/core/assistant-message/presentAssistantMessage.ts b/src/core/assistant-message/presentAssistantMessage.ts index 7f5862be154..7287cfac4c1 100644 --- a/src/core/assistant-message/presentAssistantMessage.ts +++ b/src/core/assistant-message/presentAssistantMessage.ts @@ -34,12 +34,14 @@ import { updateTodoListTool } from "../tools/UpdateTodoListTool" import { runSlashCommandTool } from "../tools/RunSlashCommandTool" import { skillTool } from "../tools/SkillTool" import { generateImageTool } from "../tools/GenerateImageTool" +import { selectActiveIntentTool } from "../tools/SelectActiveIntentTool" import { applyDiffTool as applyDiffToolClass } from "../tools/ApplyDiffTool" import { isValidToolName, validateToolUse } from "../tools/validateToolUse" import { codebaseSearchTool } from "../tools/CodebaseSearchTool" import { formatResponse } from "../prompts/responses" import { sanitizeToolUseId } from "../../utils/tool-id" +import { HookContext } from "../../hooks/types" /** * Processes and presents assistant message content to the user interface. @@ -445,6 +447,8 @@ export async function presentAssistantMessage(cline: Task) { // Store approval feedback to merge into tool result (GitHub #10465) let approvalFeedback: { text: string; images?: string[] } | undefined + let toolResult: any + let toolError: any const pushToolResult = (content: ToolResponse) => { // Native tool calling: only allow ONE tool_result per tool call @@ -488,6 +492,7 @@ export async function presentAssistantMessage(cline: Task) { cline.userMessageContent.push(...imageBlocks) } + toolResult = resultContent hasToolResult = true } @@ -675,245 +680,296 @@ export async function presentAssistantMessage(cline: Task) { } } - switch (block.name) { - case "write_to_file": - await checkpointSaveAndMark(cline) - await writeToFileTool.handle(cline, block as ToolUse<"write_to_file">, { - askApproval, - handleError, - pushToolResult, - }) - break - case "update_todo_list": - await updateTodoListTool.handle(cline, block as ToolUse<"update_todo_list">, { - askApproval, - handleError, - pushToolResult, - }) - break - case "apply_diff": - await checkpointSaveAndMark(cline) - await applyDiffToolClass.handle(cline, block as ToolUse<"apply_diff">, { - askApproval, - handleError, - pushToolResult, - }) - break - case "edit": - case "search_and_replace": - await checkpointSaveAndMark(cline) - await editTool.handle(cline, block as ToolUse<"edit">, { - askApproval, - handleError, - pushToolResult, - }) - break - case "search_replace": - await checkpointSaveAndMark(cline) - await searchReplaceTool.handle(cline, block as ToolUse<"search_replace">, { - askApproval, - handleError, - pushToolResult, - }) - break - case "edit_file": - await checkpointSaveAndMark(cline) - await editFileTool.handle(cline, block as ToolUse<"edit_file">, { - askApproval, - handleError, - pushToolResult, - }) - break - case "apply_patch": - await checkpointSaveAndMark(cline) - await applyPatchTool.handle(cline, block as ToolUse<"apply_patch">, { - askApproval, - handleError, - pushToolResult, - }) - break - case "read_file": - // Type assertion is safe here because we're in the "read_file" case - await readFileTool.handle(cline, block as ToolUse<"read_file">, { - askApproval, - handleError, - pushToolResult, - }) - break - case "list_files": - await listFilesTool.handle(cline, block as ToolUse<"list_files">, { - askApproval, - handleError, - pushToolResult, - }) - break - case "codebase_search": - await codebaseSearchTool.handle(cline, block as ToolUse<"codebase_search">, { - askApproval, - handleError, - pushToolResult, - }) - break - case "search_files": - await searchFilesTool.handle(cline, block as ToolUse<"search_files">, { - askApproval, - handleError, - pushToolResult, - }) - break - case "execute_command": - await executeCommandTool.handle(cline, block as ToolUse<"execute_command">, { - askApproval, - handleError, - pushToolResult, - }) - break - case "read_command_output": - await readCommandOutputTool.handle(cline, block as ToolUse<"read_command_output">, { - askApproval, - handleError, - pushToolResult, - }) - break - case "use_mcp_tool": - await useMcpToolTool.handle(cline, block as ToolUse<"use_mcp_tool">, { - askApproval, - handleError, - pushToolResult, - }) - break - case "access_mcp_resource": - await accessMcpResourceTool.handle(cline, block as ToolUse<"access_mcp_resource">, { - askApproval, - handleError, - pushToolResult, - }) - break - case "ask_followup_question": - await askFollowupQuestionTool.handle(cline, block as ToolUse<"ask_followup_question">, { - askApproval, - handleError, - pushToolResult, - }) - break - case "switch_mode": - await switchModeTool.handle(cline, block as ToolUse<"switch_mode">, { - askApproval, - handleError, - pushToolResult, - }) - break - case "new_task": - await checkpointSaveAndMark(cline) - await newTaskTool.handle(cline, block as ToolUse<"new_task">, { - askApproval, - handleError, - pushToolResult, - toolCallId: block.id, - }) - break - case "attempt_completion": { - const completionCallbacks: AttemptCompletionCallbacks = { - askApproval, - handleError, - pushToolResult, - askFinishSubTaskApproval, - toolDescription, + // 4. Hook Engine Orchestration + const hookContext: HookContext = { + task: cline, + toolName: block.name, + arguments: block.nativeArgs || block.params || {}, + intentId: cline.intentController.getActiveIntentId(), + timestamp: new Date().toISOString(), + } + + try { + if (!block.partial) { + const preHookResponse = await cline.hookEngine.executePreHooks(hookContext) + if (!preHookResponse.allow) { + const errorMessage = preHookResponse.reason || "Access Denied by Hook Engine" + const errorContent = formatResponse.toolError(errorMessage) + await cline.say("error", errorMessage) + cline.pushToolResultToUserContent({ + type: "tool_result", + tool_use_id: sanitizeToolUseId(toolCallId), + content: typeof errorContent === "string" ? errorContent : "Access Denied", + is_error: true, + }) + toolResult = errorContent + hasToolResult = true + cline.didAlreadyUseTool = true // Interrupt the stream + break // Exit the while loop } - await attemptCompletionTool.handle( - cline, - block as ToolUse<"attempt_completion">, - completionCallbacks, - ) - break } - case "run_slash_command": - await runSlashCommandTool.handle(cline, block as ToolUse<"run_slash_command">, { - askApproval, - handleError, - pushToolResult, - }) - break - case "skill": - await skillTool.handle(cline, block as ToolUse<"skill">, { - askApproval, - handleError, - pushToolResult, - }) - break - case "generate_image": - await checkpointSaveAndMark(cline) - await generateImageTool.handle(cline, block as ToolUse<"generate_image">, { - askApproval, - handleError, - pushToolResult, - }) - break - default: { - // Handle unknown/invalid tool names OR custom tools - // This is critical for native tool calling where every tool_use MUST have a tool_result - - // CRITICAL: Don't process partial blocks for unknown tools - just let them stream in. - // If we try to show errors for partial blocks, we'd show the error on every streaming chunk, - // creating a loop that appears to freeze the extension. Only handle complete blocks. - if (block.partial) { + + switch (block.name) { + case "write_to_file": + await checkpointSaveAndMark(cline) + await writeToFileTool.handle(cline, block as ToolUse<"write_to_file">, { + askApproval, + handleError, + pushToolResult, + }) + break + case "update_todo_list": + await updateTodoListTool.handle(cline, block as ToolUse<"update_todo_list">, { + askApproval, + handleError, + pushToolResult, + }) + break + case "apply_diff": + await checkpointSaveAndMark(cline) + await applyDiffToolClass.handle(cline, block as ToolUse<"apply_diff">, { + askApproval, + handleError, + pushToolResult, + }) + break + case "edit": + case "search_and_replace": + await checkpointSaveAndMark(cline) + await editTool.handle(cline, block as ToolUse<"edit">, { + askApproval, + handleError, + pushToolResult, + }) + break + case "search_replace": + await checkpointSaveAndMark(cline) + await searchReplaceTool.handle(cline, block as ToolUse<"search_replace">, { + askApproval, + handleError, + pushToolResult, + }) + break + case "edit_file": + await checkpointSaveAndMark(cline) + await editFileTool.handle(cline, block as ToolUse<"edit_file">, { + askApproval, + handleError, + pushToolResult, + }) + break + case "apply_patch": + await checkpointSaveAndMark(cline) + await applyPatchTool.handle(cline, block as ToolUse<"apply_patch">, { + askApproval, + handleError, + pushToolResult, + }) + break + case "read_file": + // Type assertion is safe here because we're in the "read_file" case + await readFileTool.handle(cline, block as ToolUse<"read_file">, { + askApproval, + handleError, + pushToolResult, + }) + break + case "list_files": + await listFilesTool.handle(cline, block as ToolUse<"list_files">, { + askApproval, + handleError, + pushToolResult, + }) + break + case "codebase_search": + await codebaseSearchTool.handle(cline, block as ToolUse<"codebase_search">, { + askApproval, + handleError, + pushToolResult, + }) + break + case "search_files": + await searchFilesTool.handle(cline, block as ToolUse<"search_files">, { + askApproval, + handleError, + pushToolResult, + }) + break + case "execute_command": + await executeCommandTool.handle(cline, block as ToolUse<"execute_command">, { + askApproval, + handleError, + pushToolResult, + }) + break + case "read_command_output": + await readCommandOutputTool.handle(cline, block as ToolUse<"read_command_output">, { + askApproval, + handleError, + pushToolResult, + }) + break + case "use_mcp_tool": + await useMcpToolTool.handle(cline, block as ToolUse<"use_mcp_tool">, { + askApproval, + handleError, + pushToolResult, + }) + break + case "access_mcp_resource": + await accessMcpResourceTool.handle(cline, block as ToolUse<"access_mcp_resource">, { + askApproval, + handleError, + pushToolResult, + }) + break + case "ask_followup_question": + await askFollowupQuestionTool.handle(cline, block as ToolUse<"ask_followup_question">, { + askApproval, + handleError, + pushToolResult, + }) + break + case "switch_mode": + await switchModeTool.handle(cline, block as ToolUse<"switch_mode">, { + askApproval, + handleError, + pushToolResult, + }) + break + case "new_task": + await checkpointSaveAndMark(cline) + await newTaskTool.handle(cline, block as ToolUse<"new_task">, { + askApproval, + handleError, + pushToolResult, + toolCallId: block.id, + }) + break + case "attempt_completion": { + const completionCallbacks: AttemptCompletionCallbacks = { + askApproval, + handleError, + pushToolResult, + askFinishSubTaskApproval, + toolDescription, + } + await attemptCompletionTool.handle( + cline, + block as ToolUse<"attempt_completion">, + completionCallbacks, + ) break } + case "run_slash_command": + await runSlashCommandTool.handle(cline, block as ToolUse<"run_slash_command">, { + askApproval, + handleError, + pushToolResult, + }) + break + case "skill": + await skillTool.handle(cline, block as ToolUse<"skill">, { + askApproval, + handleError, + pushToolResult, + }) + break + case "select_active_intent": + await selectActiveIntentTool.handle(cline, block as ToolUse<"select_active_intent">, { + askApproval, + handleError, + pushToolResult, + }) + break + case "generate_image": + await checkpointSaveAndMark(cline) + await generateImageTool.handle(cline, block as ToolUse<"generate_image">, { + askApproval, + handleError, + pushToolResult, + }) + break + default: { + // Handle unknown/invalid tool names OR custom tools + // This is critical for native tool calling where every tool_use MUST have a tool_result + + // CRITICAL: Don't process partial blocks for unknown tools - just let them stream in. + // If we try to show errors for partial blocks, we'd show the error on every streaming chunk, + // creating a loop that appears to freeze the extension. Only handle complete blocks. + if (block.partial) { + break + } - const customTool = stateExperiments?.customTools ? customToolRegistry.get(block.name) : undefined - - if (customTool) { - try { - let customToolArgs - - if (customTool.parameters) { - try { - customToolArgs = customTool.parameters.parse(block.nativeArgs || block.params || {}) - } catch (parseParamsError) { - const message = `Custom tool "${block.name}" argument validation failed: ${parseParamsError.message}` - console.error(message) - cline.consecutiveMistakeCount++ - await cline.say("error", message) - pushToolResult(formatResponse.toolError(message)) - break + const customTool = stateExperiments?.customTools + ? customToolRegistry.get(block.name) + : undefined + + if (customTool) { + try { + let customToolArgs + + if (customTool.parameters) { + try { + customToolArgs = customTool.parameters.parse( + block.nativeArgs || block.params || {}, + ) + } catch (parseParamsError) { + const message = `Custom tool "${block.name}" argument validation failed: ${parseParamsError.message}` + console.error(message) + cline.consecutiveMistakeCount++ + await cline.say("error", message) + pushToolResult(formatResponse.toolError(message)) + break + } } + + const result = await customTool.execute(customToolArgs, { + mode: mode ?? defaultModeSlug, + task: cline, + }) + + console.log( + `${customTool.name}.execute(): ${JSON.stringify(customToolArgs)} -> ${JSON.stringify(result)}`, + ) + + pushToolResult(result) + cline.consecutiveMistakeCount = 0 + } catch (executionError: any) { + cline.consecutiveMistakeCount++ + // Record custom tool error with static name + cline.recordToolError("custom_tool", executionError.message) + await handleError(`executing custom tool "${block.name}"`, executionError) } - const result = await customTool.execute(customToolArgs, { - mode: mode ?? defaultModeSlug, - task: cline, - }) - - console.log( - `${customTool.name}.execute(): ${JSON.stringify(customToolArgs)} -> ${JSON.stringify(result)}`, - ) - - pushToolResult(result) - cline.consecutiveMistakeCount = 0 - } catch (executionError: any) { - cline.consecutiveMistakeCount++ - // Record custom tool error with static name - cline.recordToolError("custom_tool", executionError.message) - await handleError(`executing custom tool "${block.name}"`, executionError) + break } + // Not a custom tool - handle as unknown tool error + const errorMessage = `Unknown tool "${block.name}". This tool does not exist. Please use one of the available tools.` + cline.consecutiveMistakeCount++ + cline.recordToolError(block.name as ToolName, errorMessage) + await cline.say("error", t("tools:unknownToolError", { toolName: block.name })) + // Push tool_result directly WITHOUT setting didAlreadyUseTool + // This prevents the stream from being interrupted with "Response interrupted by tool use result" + cline.pushToolResultToUserContent({ + type: "tool_result", + tool_use_id: sanitizeToolUseId(toolCallId), + content: formatResponse.toolError(errorMessage), + is_error: true, + }) break } - - // Not a custom tool - handle as unknown tool error - const errorMessage = `Unknown tool "${block.name}". This tool does not exist. Please use one of the available tools.` - cline.consecutiveMistakeCount++ - cline.recordToolError(block.name as ToolName, errorMessage) - await cline.say("error", t("tools:unknownToolError", { toolName: block.name })) - // Push tool_result directly WITHOUT setting didAlreadyUseTool - // This prevents the stream from being interrupted with "Response interrupted by tool use result" - cline.pushToolResultToUserContent({ - type: "tool_result", - tool_use_id: sanitizeToolUseId(toolCallId), - content: formatResponse.toolError(errorMessage), - is_error: true, - }) - break + } + } catch (error) { + toolError = error + throw error + } finally { + // 5. Post-Hook Execution + if (!block.partial) { + hookContext.result = toolResult + hookContext.error = toolError + await cline.hookEngine.executePostHooks(hookContext) } } diff --git a/src/core/prompts/sections/intent-protocol.ts b/src/core/prompts/sections/intent-protocol.ts index 6426255b28f..6918f1fcd15 100644 --- a/src/core/prompts/sections/intent-protocol.ts +++ b/src/core/prompts/sections/intent-protocol.ts @@ -1,12 +1,24 @@ -export function getIntentProtocolSection(): string { +import { Intent } from "../../intent/IntentController" + +export function getIntentProtocolSection(intents: Intent[]): string { + const intentCatalog = + intents.length > 0 + ? intents.map((i) => `- **${i.id}**: ${i.description}`).join("\n") + : "No intents defined in the current project. If this is a new project, you should first initialize governance by creating the `.orchestration/` directory and an `intent_map.yaml` file." + return `## Intent-Driven Development Protocol You are an Intent-Driven Architect. To ensure architectural governance and traceability, you MUST follow this protocol: -1. **Analyze First**: Before making any changes or executing destructive commands, analyze the user's request. -2. **Select Intent**: You MUST call \`select_active_intent\` to load the specific context, constraints, and scope for the relevant business intent. -3. **Wait for Context**: Do not guess architectural boundaries. The \`select_active_intent\` tool will provide you with the \`\` block containing the necessary rules. -4. **Enforce Constraints**: Once an intent is selected, you must adhere to its constraints and work within its owned scope. +### Available Intent Catalog +${intentCatalog} + +### Protocol Rules +1. **Analyze First**: Before making any changes or executing destructive commands, analyze the user's request against the catalog. +2. **Select Intent**: You MUST call \`select_active_intent\` to load context for a specific ID from the catalog. +3. **Paths**: Always use root-relative paths (e.g., \`.orchestration/intent_map.yaml\`) when referencing governance files. +4. **Initialization**: If governance is not yet set up (empty catalog), your first task should be to help the user initialize it. +5. **Rejection Recovery**: If a tool call is rejected with "Access Denied" or "Security Violation", your **IMMEDIATE NEXT RESPONSE** must consist only of a call to \`select_active_intent\` (to fix the missing intent) or \`ask_followup_question\` (to discuss scope). Do NOT try a different tool or the same tool again without first resolving the intent status. **CRITICAL**: You CANNOT write code or execute destructive commands immediately. Your first action MUST be to call \`select_active_intent\` if a relevant intent exists.` } diff --git a/src/core/prompts/system.ts b/src/core/prompts/system.ts index a5c49cc5ad6..b74b38e7bdb 100644 --- a/src/core/prompts/system.ts +++ b/src/core/prompts/system.ts @@ -25,6 +25,7 @@ import { getSkillsSection, getIntentProtocolSection, } from "./sections" +import { Intent } from "../intent/IntentController" // Helper function to get prompt component, filtering out empty objects export function getPromptComponent( @@ -56,6 +57,7 @@ async function generatePrompt( todoList?: TodoItem[], modelId?: string, skillsManager?: SkillsManager, + intents?: Intent[], ): Promise { if (!context) { throw new Error("Extension context is required for generating system prompt") @@ -86,8 +88,8 @@ async function generatePrompt( const basePrompt = `${roleDefinition} ${markdownFormattingSection()} - -${getIntentProtocolSection()} + +${getIntentProtocolSection(intents || [])} ${getSharedToolUseSection()}${toolsCatalog} @@ -129,6 +131,7 @@ export const SYSTEM_PROMPT = async ( todoList?: TodoItem[], modelId?: string, skillsManager?: SkillsManager, + intents?: Intent[], ): Promise => { if (!context) { throw new Error("Extension context is required for generating system prompt") @@ -157,5 +160,6 @@ export const SYSTEM_PROMPT = async ( todoList, modelId, skillsManager, + intents, ) } diff --git a/src/core/prompts/tools/native-tools/index.ts b/src/core/prompts/tools/native-tools/index.ts index 758914d2d65..adbb294ecb2 100644 --- a/src/core/prompts/tools/native-tools/index.ts +++ b/src/core/prompts/tools/native-tools/index.ts @@ -20,6 +20,7 @@ import searchFiles from "./search_files" import switchMode from "./switch_mode" import updateTodoList from "./update_todo_list" import writeToFile from "./write_to_file" +import selectActiveIntent from "./select_active_intent" export { getMcpServerTools } from "./mcp_server" export { convertOpenAIToolToAnthropic, convertOpenAIToolsToAnthropic } from "./converters" @@ -68,6 +69,7 @@ export function getNativeTools(options: NativeToolsOptions = {}): OpenAI.Chat.Ch switchMode, updateTodoList, writeToFile, + selectActiveIntent, ] satisfies OpenAI.Chat.ChatCompletionTool[] } From 33a3bbb085c7077cc277d6545158676504dd9acd Mon Sep 17 00:00:00 2001 From: yosef-zewdu Date: Fri, 20 Feb 2026 11:08:28 +0300 Subject: [PATCH 16/23] feat: Add intent_id parameter and update tool definitions for select_active_intent --- packages/types/src/tool.ts | 1 + src/core/task/Task.ts | 20 +++++++++++++++++++- src/shared/tools.ts | 9 +++++++++ 3 files changed, 29 insertions(+), 1 deletion(-) diff --git a/packages/types/src/tool.ts b/packages/types/src/tool.ts index 4f90b63e9fc..fe595ecf695 100644 --- a/packages/types/src/tool.ts +++ b/packages/types/src/tool.ts @@ -45,6 +45,7 @@ export const toolNames = [ "run_slash_command", "skill", "generate_image", + "select_active_intent", "custom_tool", ] as const diff --git a/src/core/task/Task.ts b/src/core/task/Task.ts index 6ba57e98ac3..9f543b2a7c8 100644 --- a/src/core/task/Task.ts +++ b/src/core/task/Task.ts @@ -102,7 +102,12 @@ import { restoreTodoListForTask } from "../tools/UpdateTodoListTool" import { FileContextTracker } from "../context-tracking/FileContextTracker" import { RooIgnoreController } from "../ignore/RooIgnoreController" import { RooProtectedController } from "../protect/RooProtectedController" -import { type AssistantMessageContent, presentAssistantMessage } from "../assistant-message" +import { IntentController } from "../intent/IntentController" +import { HookEngine } from "../../hooks/HookEngine" +import { IntentValidationHook } from "../../hooks/pre/IntentValidationHook" +import { ScopeEnforcementHook } from "../../hooks/pre/ScopeEnforcementHook" +import { TraceLoggingHook } from "../../hooks/post/TraceLoggingHook" +import { AssistantMessageContent, presentAssistantMessage } from "../assistant-message" import { NativeToolCallParser } from "../assistant-message/NativeToolCallParser" import { manageContext, willManageContext } from "../context-management" import { ClineProvider } from "../webview/ClineProvider" @@ -298,6 +303,8 @@ export class Task extends EventEmitter implements TaskLike { toolRepetitionDetector: ToolRepetitionDetector rooIgnoreController?: RooIgnoreController rooProtectedController?: RooProtectedController + intentController: IntentController + hookEngine: HookEngine fileContextTracker: FileContextTracker terminalProcess?: RooTerminalProcess @@ -487,6 +494,16 @@ export class Task extends EventEmitter implements TaskLike { console.error("Failed to initialize RooIgnoreController:", error) }) + this.intentController = new IntentController(this.cwd) + this.intentController.initialize().catch((error) => { + console.error("Failed to initialize IntentController:", error) + }) + + this.hookEngine = new HookEngine() + this.hookEngine.registerHook(new IntentValidationHook()) + this.hookEngine.registerHook(new ScopeEnforcementHook()) + this.hookEngine.registerHook(new TraceLoggingHook()) + this.apiConfiguration = apiConfiguration this.api = buildApiHandler(this.apiConfiguration) this.autoApprovalHandler = new AutoApprovalHandler() @@ -3815,6 +3832,7 @@ export class Task extends EventEmitter implements TaskLike { undefined, // todoList this.api.getModel().id, provider.getSkillsManager(), + this.intentController.getAllIntents(), ) })() } diff --git a/src/shared/tools.ts b/src/shared/tools.ts index 491ba693611..6bad21b18e4 100644 --- a/src/shared/tools.ts +++ b/src/shared/tools.ts @@ -80,6 +80,7 @@ export const toolParamNames = [ // read_file legacy format parameter (backward compatibility) "files", "line_ranges", + "intent_id", ] as const export type ToolParamName = (typeof toolParamNames)[number] @@ -115,6 +116,7 @@ export type NativeToolArgs = { update_todo_list: { todos: string } use_mcp_tool: { server_name: string; tool_name: string; arguments?: Record } write_to_file: { path: string; content: string } + select_active_intent: { intent_id: string } // Add more tools as they are migrated to native protocol } @@ -257,6 +259,11 @@ export interface GenerateImageToolUse extends ToolUse<"generate_image"> { params: Partial, "prompt" | "path" | "image">> } +export interface SelectActiveIntentToolUse extends ToolUse<"select_active_intent"> { + name: "select_active_intent" + params: Partial, "intent_id">> +} + // Define tool group configuration export type ToolGroupConfig = { tools: readonly string[] @@ -288,6 +295,7 @@ export const TOOL_DISPLAY_NAMES: Record = { run_slash_command: "run slash command", skill: "load skill", generate_image: "generate images", + select_active_intent: "select active intent", custom_tool: "use custom tools", } as const @@ -321,6 +329,7 @@ export const ALWAYS_AVAILABLE_TOOLS: ToolName[] = [ "update_todo_list", "run_slash_command", "skill", + "select_active_intent", ] as const /** From 8d6d2f9c5f0adf95384987b35dc2ad6ce7e03cdc Mon Sep 17 00:00:00 2001 From: yosef-zewdu Date: Sat, 21 Feb 2026 22:27:09 +0300 Subject: [PATCH 17/23] refactor: Remove deprecated files & update active_intents.yaml and intent_map.md for improved intent management --- .orchestration/active_intent.yaml | 2 - .orchestration/active_intents.yaml | 35 ++++++++++++++++++ .orchestration/agent_trace.jsonl | 59 +++++++++++++++++++++++++++++- .orchestration/intent_map.md | 14 +++++++ .orchestration/intent_map.yaml | 16 -------- 5 files changed, 106 insertions(+), 20 deletions(-) delete mode 100644 .orchestration/active_intent.yaml create mode 100644 .orchestration/active_intents.yaml create mode 100644 .orchestration/intent_map.md delete mode 100644 .orchestration/intent_map.yaml diff --git a/.orchestration/active_intent.yaml b/.orchestration/active_intent.yaml deleted file mode 100644 index 6ddcc97b28d..00000000000 --- a/.orchestration/active_intent.yaml +++ /dev/null @@ -1,2 +0,0 @@ -active_intent_id: "GOV-INIT" -timestamp: "2026-02-18T23:30:00Z" diff --git a/.orchestration/active_intents.yaml b/.orchestration/active_intents.yaml new file mode 100644 index 00000000000..10df75b8e1a --- /dev/null +++ b/.orchestration/active_intents.yaml @@ -0,0 +1,35 @@ +active_intents: + - id: INT-001 + name: testing PRD + description: testing PRD + status: IN_PROGRESS + owned_scope: + - /tests + constraints: [] + acceptance_criteria: [] + - id: INT-002 + name: project dependencies + description: Updating project dependencies + status: CREATED + owned_scope: + - requirements.txt + - pyproject.toml + constraints: [] + acceptance_criteria: [] + - id: INT-003 + name: project documentation + description: Updating project documentation + status: IN_PROGRESS + owned_scope: + - README.md + constraints: [] + acceptance_criteria: [] + - id: INT-004 + name: project bot + description: Updating project bot + status: CREATED + owned_scope: + - bot.py + constraints: [] + acceptance_criteria: [] +last_updated: 2026-02-21T19:01:30.984Z diff --git a/.orchestration/agent_trace.jsonl b/.orchestration/agent_trace.jsonl index b6c1e933b9d..2945578f700 100644 --- a/.orchestration/agent_trace.jsonl +++ b/.orchestration/agent_trace.jsonl @@ -1,2 +1,57 @@ -{"timestamp":"2026-02-18T23:30:00Z","intentId":"GOV-INIT","tool":"write_to_file","path":".orchestration/intent_map.yaml","success":true,"error":null} -{"timestamp":"2026-02-18T23:30:05Z","intentId":"GOV-INIT","tool":"write_to_file","path":".orchestration/active_intent.yaml","success":true,"error":null} +{"timestamp":"2026-02-20T13:43:47.352Z","actor_id":"claude-3-5-sonnet","spec_id":"UNSPECIFIED","call_id":"aba8052c-6025-4e79-94b2-5c98030ebdf8","input_hash":"","endpoint":"list_files","response_hash":"","outcome":"SUCCESS","vcs":{"revision_id":"UNKNOWN"}} +{"timestamp":"2026-02-20T13:44:00.822Z","actor_id":"claude-3-5-sonnet","spec_id":"UNSPECIFIED","call_id":"b0ddc7d9-0062-4b8e-8dea-1010336577c7","input_hash":"","endpoint":"read_file","response_hash":"","outcome":"SUCCESS","vcs":{"revision_id":"UNKNOWN"}} +{"timestamp":"2026-02-20T13:44:09.386Z","actor_id":"claude-3-5-sonnet","spec_id":"UNSPECIFIED","call_id":"8b0b9b61-7e9d-4d5c-a3cb-18ec7e1af5b7","input_hash":"","endpoint":"read_file","response_hash":"","outcome":"SUCCESS","vcs":{"revision_id":"UNKNOWN"}} +{"timestamp":"2026-02-20T13:44:16.656Z","actor_id":"claude-3-5-sonnet","spec_id":"UNSPECIFIED","call_id":"9c9980ca-e399-4e43-b549-28d3009f85fb","input_hash":"","endpoint":"read_file","response_hash":"","outcome":"SUCCESS","vcs":{"revision_id":"UNKNOWN"}} +{"timestamp":"2026-02-20T13:44:51.171Z","actor_id":"claude-3-5-sonnet","spec_id":"UNSPECIFIED","call_id":"d032aba9-e0e1-402d-a25c-e5695f3dfe25","input_hash":"","endpoint":"write_to_file","files":[{"relative_path":"README.md","conversations":[{"url":"session_log_id","contributor":{"entity_type":"AI","model_identifier":"claude-3-5-sonnet"},"ranges":[{"content_hash":"sha256:615ddf86dc5fec0f495ab6f00e5a49220108ea9a8ea2662070dd8265a7f11582"}],"related":[]}]}],"response_hash":"","outcome":"FAILURE","vcs":{"revision_id":"UNKNOWN"}} +{"timestamp":"2026-02-20T13:45:01.508Z","actor_id":"claude-3-5-sonnet","spec_id":"UNSPECIFIED","call_id":"c4a2a262-b211-4b5c-ba1a-93d2c1c2b774","input_hash":"","endpoint":"ask_followup_question","response_hash":"","outcome":"SUCCESS","vcs":{"revision_id":"UNKNOWN"}} +{"timestamp":"2026-02-20T13:45:52.180Z","actor_id":"claude-3-5-sonnet","spec_id":"UNSPECIFIED","call_id":"18904b14-2fc3-4170-856a-f5f2fba7a49b","input_hash":"","endpoint":"write_to_file","files":[{"relative_path":"README.md","conversations":[{"url":"session_log_id","contributor":{"entity_type":"AI","model_identifier":"claude-3-5-sonnet"},"ranges":[{"content_hash":"sha256:615ddf86dc5fec0f495ab6f00e5a49220108ea9a8ea2662070dd8265a7f11582"}],"related":[]}]}],"response_hash":"","outcome":"FAILURE","vcs":{"revision_id":"UNKNOWN"}} +{"timestamp":"2026-02-20T15:08:41.798Z","actor_id":"claude-3-5-sonnet","spec_id":"UNSPECIFIED","call_id":"1dd5efe9-01a0-40ab-a429-74afbb8ccf51","input_hash":"800c2462366d2b6dc6ff495063855cb331ed762e28a523676969bbad975f6e69","endpoint":"apply_diff","files":[{"relative_path":"README.md","conversations":[{"url":"session_log_id","contributor":{"entity_type":"AI","model_identifier":"claude-3-5-sonnet"},"ranges":[{"content_hash":"sha256:de4a30fc212f55886586ea10f2233ce30ff02f0656067606ce2bc06217aad182"}],"related":[]}]}],"response_hash":"52762d5198d67e55c76ed01fd9e2ca35a230530feda8845695301c7e9157a582","outcome":"PERMISSION_DENIED","vcs":{"revision_id":"UNKNOWN"}} +{"timestamp":"2026-02-20T15:29:18.654Z","actor_id":"claude-3-5-sonnet","spec_id":"UNSPECIFIED","call_id":"6312bda6-1b16-4aff-8f8f-62e4b7eb8c63","input_hash":"47c65533448e29e36e0f2ea0024a43ea08f7e7b510fcdb8c4751faad13360830","endpoint":"apply_diff","files":[{"relative_path":"README.md","conversations":[{"url":"session_log_id","contributor":{"entity_type":"AI","model_identifier":"claude-3-5-sonnet"},"ranges":[{"content_hash":"sha256:bb317deb33ac41efb5586ca97ed7abaf4c1348f6cedbfbf7e8681b736c4b5213"}],"related":[]}]}],"response_hash":"52762d5198d67e55c76ed01fd9e2ca35a230530feda8845695301c7e9157a582","outcome":"PERMISSION_DENIED","vcs":{"revision_id":"UNKNOWN"}} +{"timestamp":"2026-02-20T15:35:46.000Z","actor_id":"claude-3-5-sonnet","spec_id":"UNSPECIFIED","call_id":"f71d929d-e73e-4010-9c7d-8af3b9eb9422","input_hash":"22f56cf6c0ff03bca028e4e4856d7d4ecb2b0aff808327807a5a9fc9efbe2a52","endpoint":"apply_diff","files":[{"relative_path":"README.md","conversations":[{"url":"session_log_id","contributor":{"entity_type":"AI","model_identifier":"claude-3-5-sonnet"},"ranges":[{"content_hash":"sha256:ce92f035b23da5029ff77de19b75f5f0bb0cbbe6aad94a44422a34b372662adb"}],"related":[]}]}],"response_hash":"52762d5198d67e55c76ed01fd9e2ca35a230530feda8845695301c7e9157a582","outcome":"PERMISSION_DENIED","vcs":{"revision_id":"UNKNOWN"}} +{"timestamp":"2026-02-20T15:45:52.711Z","actor_id":"claude-3-5-sonnet","spec_id":"UNSPECIFIED","call_id":"b5c52db4-0e49-462f-828e-c3c235bf295d","input_hash":"67f3d18fa30cada3bb9218dadafdce743804fdf11d11955a63484b6d6a253f11","endpoint":"apply_diff","files":[{"relative_path":"README.md","conversations":[{"url":"session_log_id","contributor":{"entity_type":"AI","model_identifier":"claude-3-5-sonnet"},"ranges":[{"content_hash":"sha256:8f6c73ffac755c22f28958156f8872b2ca7b32561ecb85228223d6ef2c53b581"}],"related":[]}]}],"response_hash":"52762d5198d67e55c76ed01fd9e2ca35a230530feda8845695301c7e9157a582","outcome":"PERMISSION_DENIED","vcs":{"revision_id":"UNKNOWN"}} +{"timestamp":"2026-02-20T17:51:04.426Z","actor_id":"claude-3-5-sonnet","spec_id":"UNSPECIFIED","call_id":"b4d941f7-164c-42b1-80b3-a5a0915a88a5","input_hash":"71fb7b40f9d833583fe456829622c9ea43054c0d9e290aebde623ca12cf2cca6","endpoint":"apply_diff","files":[{"relative_path":"README.md","conversations":[{"url":"session_log_id","contributor":{"entity_type":"AI","model_identifier":"claude-3-5-sonnet"},"ranges":[{"content_hash":"sha256:befcc1bfcba40d3ed9f775b06d759fa6553d81fa2b99854196dc798a06e50d81"}],"related":[]}]}],"response_hash":"52762d5198d67e55c76ed01fd9e2ca35a230530feda8845695301c7e9157a582","outcome":"PERMISSION_DENIED","vcs":{"revision_id":"UNKNOWN"}} +{"timestamp":"2026-02-20T18:00:19.505Z","actor_id":"claude-3-5-sonnet","spec_id":"UNSPECIFIED","call_id":"fd9443d0-12cb-49ed-a82f-94e2476f6375","input_hash":"7bc689fc0a2c45e05ef5e20ae63400cfa3cfdd663b24eb9812ebf3971fae59dd","endpoint":"apply_diff","files":[{"relative_path":"README.md","conversations":[{"url":"session_log_id","contributor":{"entity_type":"AI","model_identifier":"claude-3-5-sonnet"},"ranges":[{"content_hash":"sha256:4b8c0a57095c39a1314f9dc35b5a6bc1c5700fd709c9aceb987856dd7d8d9e97"}],"related":[]}]}],"response_hash":"52762d5198d67e55c76ed01fd9e2ca35a230530feda8845695301c7e9157a582","outcome":"PERMISSION_DENIED","vcs":{"revision_id":"UNKNOWN"}} +{"timestamp":"2026-02-20T18:00:42.038Z","actor_id":"claude-3-5-sonnet","spec_id":"INT-002","call_id":"2502e46c-7a4c-4262-97bb-b511da4d2471","input_hash":"7bc689fc0a2c45e05ef5e20ae63400cfa3cfdd663b24eb9812ebf3971fae59dd","endpoint":"apply_diff","files":[{"relative_path":"README.md","conversations":[{"url":"session_log_id","contributor":{"entity_type":"AI","model_identifier":"claude-3-5-sonnet"},"ranges":[{"content_hash":"sha256:4b8c0a57095c39a1314f9dc35b5a6bc1c5700fd709c9aceb987856dd7d8d9e97"}],"related":[{"type":"specification","value":"INT-002"}]}]}],"response_hash":"69e3f7dd881130537cf9377efbb51f55459c027b5197877b7e88d56423eea08a","outcome":"SUCCESS","vcs":{"revision_id":"UNKNOWN"}} +{"timestamp":"2026-02-20T18:00:59.567Z","actor_id":"claude-3-5-sonnet","spec_id":"INT-003","call_id":"225ff46d-9870-4565-95bd-2d464f5ddf37","input_hash":"7bc689fc0a2c45e05ef5e20ae63400cfa3cfdd663b24eb9812ebf3971fae59dd","endpoint":"apply_diff","files":[{"relative_path":"README.md","conversations":[{"url":"session_log_id","contributor":{"entity_type":"AI","model_identifier":"claude-3-5-sonnet"},"ranges":[{"content_hash":"sha256:4b8c0a57095c39a1314f9dc35b5a6bc1c5700fd709c9aceb987856dd7d8d9e97"}],"related":[{"type":"specification","value":"INT-003"}]}]}],"response_hash":"72fb3593fe71767c998123c05ba5db416c848b77235a1e242d9ac4737a724a42","outcome":"SUCCESS","vcs":{"revision_id":"UNKNOWN"}} +{"timestamp":"2026-02-20T18:01:16.095Z","actor_id":"claude-3-5-sonnet","spec_id":"INT-003","call_id":"e5a20c27-5959-46df-96b2-736bce8d384c","input_hash":"7bc689fc0a2c45e05ef5e20ae63400cfa3cfdd663b24eb9812ebf3971fae59dd","endpoint":"apply_diff","files":[{"relative_path":"README.md","conversations":[{"url":"session_log_id","contributor":{"entity_type":"AI","model_identifier":"claude-3-5-sonnet"},"ranges":[{"content_hash":"sha256:4b8c0a57095c39a1314f9dc35b5a6bc1c5700fd709c9aceb987856dd7d8d9e97"}],"related":[{"type":"specification","value":"INT-003"}]}]}],"response_hash":"72fb3593fe71767c998123c05ba5db416c848b77235a1e242d9ac4737a724a42","outcome":"SUCCESS","vcs":{"revision_id":"UNKNOWN"}} +{"timestamp":"2026-02-20T18:01:28.953Z","actor_id":"claude-3-5-sonnet","spec_id":"INT-003","call_id":"a8efd730-3f65-4da7-9a78-b81068800d94","input_hash":"7bc689fc0a2c45e05ef5e20ae63400cfa3cfdd663b24eb9812ebf3971fae59dd","endpoint":"apply_diff","files":[{"relative_path":"README.md","conversations":[{"url":"session_log_id","contributor":{"entity_type":"AI","model_identifier":"claude-3-5-sonnet"},"ranges":[{"content_hash":"sha256:4b8c0a57095c39a1314f9dc35b5a6bc1c5700fd709c9aceb987856dd7d8d9e97"}],"related":[{"type":"specification","value":"INT-003"}]}]}],"response_hash":"72fb3593fe71767c998123c05ba5db416c848b77235a1e242d9ac4737a724a42","outcome":"SUCCESS","vcs":{"revision_id":"UNKNOWN"}} +{"timestamp":"2026-02-20T19:19:44.180Z","actor_id":"claude-3-5-sonnet","spec_id":"UNSPECIFIED","call_id":"3e9a872b-d30b-4af2-ae00-a8c1709f1241","input_hash":"4be32b68dc0f5ecf594c03ce0465cd40fc28996542fac184055bcb0440945f24","endpoint":"apply_diff","files":[{"relative_path":"README.md","conversations":[{"url":"session_log_id","contributor":{"entity_type":"AI","model_identifier":"claude-3-5-sonnet"},"ranges":[{"content_hash":"sha256:026db4ac9ea2bf64648c0dcfff3cc01cc255bd0bb26eb48333c83fb7b924bb50"}],"related":[]}]}],"response_hash":"52762d5198d67e55c76ed01fd9e2ca35a230530feda8845695301c7e9157a582","outcome":"PERMISSION_DENIED","vcs":{"revision_id":"UNKNOWN"}} +{"timestamp":"2026-02-20T19:20:01.947Z","actor_id":"claude-3-5-sonnet","spec_id":"INT-002","call_id":"e0b303a1-ed11-4dc7-97c0-bf168c1e3f8f","input_hash":"4be32b68dc0f5ecf594c03ce0465cd40fc28996542fac184055bcb0440945f24","endpoint":"apply_diff","files":[{"relative_path":"README.md","conversations":[{"url":"session_log_id","contributor":{"entity_type":"AI","model_identifier":"claude-3-5-sonnet"},"ranges":[{"content_hash":"sha256:026db4ac9ea2bf64648c0dcfff3cc01cc255bd0bb26eb48333c83fb7b924bb50"}],"related":[{"type":"specification","value":"INT-002"}]}]}],"response_hash":"69e3f7dd881130537cf9377efbb51f55459c027b5197877b7e88d56423eea08a","outcome":"SUCCESS","vcs":{"revision_id":"UNKNOWN"}} +{"timestamp":"2026-02-20T19:20:26.628Z","actor_id":"claude-3-5-sonnet","spec_id":"INT-003","call_id":"64f602a5-8c65-42fd-b2ee-384924894808","input_hash":"4be32b68dc0f5ecf594c03ce0465cd40fc28996542fac184055bcb0440945f24","endpoint":"apply_diff","files":[{"relative_path":"README.md","conversations":[{"url":"session_log_id","contributor":{"entity_type":"AI","model_identifier":"claude-3-5-sonnet"},"ranges":[{"content_hash":"sha256:026db4ac9ea2bf64648c0dcfff3cc01cc255bd0bb26eb48333c83fb7b924bb50"}],"related":[{"type":"specification","value":"INT-003"}]}]}],"response_hash":"f49b7cd27a00d0322239a3f43fb275babbe3aaa6ac7abea2b02ed3662897f245","outcome":"SUCCESS","vcs":{"revision_id":"UNKNOWN"}} +{"timestamp":"2026-02-20T19:20:36.785Z","actor_id":"claude-3-5-sonnet","spec_id":"INT-003","call_id":"73afc3d9-0ce3-4149-b440-619e4e00c59b","input_hash":"adfdf13b2917e57a2d27c8372e4f370a04fdd8e69395b93b0a5a48340ba6203e","endpoint":"apply_diff","files":[{"relative_path":"README.md","conversations":[{"url":"session_log_id","contributor":{"entity_type":"AI","model_identifier":"claude-3-5-sonnet"},"ranges":[{"content_hash":"sha256:a6890daefddd5bb68cb02b6338427de28ec2cead492be55a195da59490be16a7"}],"related":[{"type":"specification","value":"INT-003"}]}]}],"response_hash":"087b6d2cd4e1ba5381e04659eed610501d36e597f8061dfdc7c6575ec3983fcb","outcome":"SUCCESS","vcs":{"revision_id":"UNKNOWN"}} +{"timestamp":"2026-02-20T19:20:44.210Z","actor_id":"claude-3-5-sonnet","spec_id":"INT-003","call_id":"ae149adc-e729-4469-a90e-dbcde932017d","input_hash":"adfdf13b2917e57a2d27c8372e4f370a04fdd8e69395b93b0a5a48340ba6203e","endpoint":"apply_diff","files":[{"relative_path":"README.md","conversations":[{"url":"session_log_id","contributor":{"entity_type":"AI","model_identifier":"claude-3-5-sonnet"},"ranges":[{"content_hash":"sha256:a6890daefddd5bb68cb02b6338427de28ec2cead492be55a195da59490be16a7"}],"related":[{"type":"specification","value":"INT-003"}]}]}],"response_hash":"087b6d2cd4e1ba5381e04659eed610501d36e597f8061dfdc7c6575ec3983fcb","outcome":"SUCCESS","vcs":{"revision_id":"UNKNOWN"}} +{"timestamp":"2026-02-20T19:21:37.248Z","actor_id":"claude-3-5-sonnet","spec_id":"INT-003","call_id":"4239b519-f2fb-491e-8d8e-ba7c927f2ec9","input_hash":"adfdf13b2917e57a2d27c8372e4f370a04fdd8e69395b93b0a5a48340ba6203e","endpoint":"apply_diff","files":[{"relative_path":"README.md","conversations":[{"url":"session_log_id","contributor":{"entity_type":"AI","model_identifier":"claude-3-5-sonnet"},"ranges":[{"content_hash":"sha256:a6890daefddd5bb68cb02b6338427de28ec2cead492be55a195da59490be16a7"}],"related":[{"type":"specification","value":"INT-003"}]}]}],"response_hash":"087b6d2cd4e1ba5381e04659eed610501d36e597f8061dfdc7c6575ec3983fcb","outcome":"SUCCESS","vcs":{"revision_id":"UNKNOWN"}} +{"timestamp":"2026-02-21T16:14:58.619Z","intentId":"NONE","tool":"read_file","path":"bot.py","success":true,"error":null} +{"timestamp":"2026-02-21T16:15:15.997Z","intentId":"NONE","tool":"apply_diff","path":"bot.py","success":true,"error":null} +{"timestamp":"2026-02-21T16:21:33.066Z","actor_id":"claude-3-5-sonnet","spec_id":"UNSPECIFIED","call_id":"bd0d6f1f-27dd-4a72-b639-51e86674a387","input_hash":"d5039bd7c0a838d41fe37790e44f2cf6d2cf56896331de2d6665d88f14b701cc","endpoint":"apply_diff","files":[{"relative_path":"bot.py","conversations":[{"url":"session_log_id","contributor":{"entity_type":"AI","model_identifier":"claude-3-5-sonnet"},"ranges":[{"content_hash":"sha256:55ee3a28846a19133b352021db84dcb2e20af290927268a3539de92e0b653c0c"}],"related":[]}]}],"response_hash":"52762d5198d67e55c76ed01fd9e2ca35a230530feda8845695301c7e9157a582","outcome":"PERMISSION_DENIED","vcs":{"revision_id":"UNKNOWN"}} +{"timestamp":"2026-02-21T16:21:39.927Z","actor_id":"claude-3-5-sonnet","spec_id":"INT-001","call_id":"8a077b0c-449b-4c0f-81ab-36567c239630","input_hash":"d5039bd7c0a838d41fe37790e44f2cf6d2cf56896331de2d6665d88f14b701cc","endpoint":"apply_diff","files":[{"relative_path":"bot.py","conversations":[{"url":"session_log_id","contributor":{"entity_type":"AI","model_identifier":"claude-3-5-sonnet"},"ranges":[{"content_hash":"sha256:55ee3a28846a19133b352021db84dcb2e20af290927268a3539de92e0b653c0c"}],"related":[{"type":"specification","value":"INT-001"}]}]}],"response_hash":"8485b90c8cdeaa687c3dfae42a490f17ab2e53084872eb22ad5d696d892aa783","outcome":"SUCCESS","vcs":{"revision_id":"UNKNOWN"}} +{"timestamp":"2026-02-21T16:24:09.570Z","actor_id":"claude-3-5-sonnet","spec_id":"INT-003","call_id":"3bce6be7-e4f7-4099-9efc-2cb0a6dc9c0c","input_hash":"2ada26f80d182a50ef53143be4770ce4eb2b06b659942995df3112c957af99a9","endpoint":"apply_diff","files":[{"relative_path":".orchestration/active_intents.yaml","conversations":[{"url":"session_log_id","contributor":{"entity_type":"AI","model_identifier":"claude-3-5-sonnet"},"ranges":[{"content_hash":"sha256:1e387ace4f03c611e9052a5d715fe61dbb03bd850773f15f8c1fc7a98c271aef"}],"related":[{"type":"specification","value":"INT-003"}]}]}],"response_hash":"2e156fb5294e33c8cf2da4ce8656b3261df0ac1c7f40502d407a4f1dc5b8f092","outcome":"SUCCESS","vcs":{"revision_id":"UNKNOWN"}} +{"timestamp":"2026-02-21T16:24:47.223Z","actor_id":"claude-3-5-sonnet","spec_id":"INT-003","call_id":"4ec40712-a12d-48cb-bc0b-7ec47a5e9dab","input_hash":"7d20676009cafc44f747c820ed526070d9748fc0054396cf90b9dc6f4953fc58","endpoint":"apply_diff","files":[{"relative_path":".orchestration/active_intents.yaml","conversations":[{"url":"session_log_id","contributor":{"entity_type":"AI","model_identifier":"claude-3-5-sonnet"},"ranges":[{"content_hash":"sha256:e12ca3ab0eb308e7bafc0134d5a3209b0899d6225954a4a6a267459b47c0877f"}],"related":[{"type":"specification","value":"INT-003"}]}]}],"response_hash":"2e156fb5294e33c8cf2da4ce8656b3261df0ac1c7f40502d407a4f1dc5b8f092","outcome":"SUCCESS","vcs":{"revision_id":"UNKNOWN"}} +{"timestamp":"2026-02-21T16:30:12.810Z","actor_id":"claude-3-5-sonnet","spec_id":"UNSPECIFIED","call_id":"a55133f7-b81c-44e5-8192-05d1141f4919","input_hash":"d5039bd7c0a838d41fe37790e44f2cf6d2cf56896331de2d6665d88f14b701cc","endpoint":"apply_diff","files":[{"relative_path":"bot.py","conversations":[{"url":"session_log_id","contributor":{"entity_type":"AI","model_identifier":"claude-3-5-sonnet"},"ranges":[{"content_hash":"sha256:55ee3a28846a19133b352021db84dcb2e20af290927268a3539de92e0b653c0c"}],"related":[]}]}],"response_hash":"52762d5198d67e55c76ed01fd9e2ca35a230530feda8845695301c7e9157a582","outcome":"PERMISSION_DENIED","vcs":{"revision_id":"UNKNOWN"}} +{"timestamp":"2026-02-21T16:30:21.590Z","actor_id":"claude-3-5-sonnet","spec_id":"INT-001","call_id":"ac4fc981-986e-4f07-9226-0905d0ee4813","input_hash":"d5039bd7c0a838d41fe37790e44f2cf6d2cf56896331de2d6665d88f14b701cc","endpoint":"apply_diff","files":[{"relative_path":"bot.py","conversations":[{"url":"session_log_id","contributor":{"entity_type":"AI","model_identifier":"claude-3-5-sonnet"},"ranges":[{"content_hash":"sha256:55ee3a28846a19133b352021db84dcb2e20af290927268a3539de92e0b653c0c"}],"related":[{"type":"specification","value":"INT-001"}]}]}],"response_hash":"8485b90c8cdeaa687c3dfae42a490f17ab2e53084872eb22ad5d696d892aa783","outcome":"SUCCESS","vcs":{"revision_id":"UNKNOWN"}} +{"timestamp":"2026-02-21T16:30:36.822Z","actor_id":"claude-3-5-sonnet","spec_id":"INT-004","call_id":"b0e93ea6-3893-40da-9759-e9480ea592ff","input_hash":"d5039bd7c0a838d41fe37790e44f2cf6d2cf56896331de2d6665d88f14b701cc","endpoint":"apply_diff","files":[{"relative_path":"bot.py","conversations":[{"url":"session_log_id","contributor":{"entity_type":"AI","model_identifier":"claude-3-5-sonnet"},"ranges":[{"content_hash":"sha256:55ee3a28846a19133b352021db84dcb2e20af290927268a3539de92e0b653c0c"}],"related":[{"type":"specification","value":"INT-004"}]}]}],"response_hash":"74ea0bff9980efcbf06b8aba4ab56d3422896e15ff7e9ecfa8b25abbe36521de","outcome":"SUCCESS","vcs":{"revision_id":"UNKNOWN"}} +{"timestamp":"2026-02-21T16:30:44.695Z","actor_id":"claude-3-5-sonnet","spec_id":"INT-004","call_id":"52dcc8e4-8140-4d4a-a17d-83a6b7a4bbb3","input_hash":"d5039bd7c0a838d41fe37790e44f2cf6d2cf56896331de2d6665d88f14b701cc","endpoint":"apply_diff","files":[{"relative_path":"bot.py","conversations":[{"url":"session_log_id","contributor":{"entity_type":"AI","model_identifier":"claude-3-5-sonnet"},"ranges":[{"content_hash":"sha256:55ee3a28846a19133b352021db84dcb2e20af290927268a3539de92e0b653c0c"}],"related":[{"type":"specification","value":"INT-004"}]}]}],"response_hash":"74ea0bff9980efcbf06b8aba4ab56d3422896e15ff7e9ecfa8b25abbe36521de","outcome":"SUCCESS","vcs":{"revision_id":"UNKNOWN"}} +{"timestamp":"2026-02-21T16:32:31.104Z","actor_id":"claude-3-5-sonnet","spec_id":"INT-004","call_id":"3e7ccc99-a6cd-4c7c-a85f-79da83d871ca","input_hash":"d5039bd7c0a838d41fe37790e44f2cf6d2cf56896331de2d6665d88f14b701cc","endpoint":"apply_diff","files":[{"relative_path":"bot.py","conversations":[{"url":"session_log_id","contributor":{"entity_type":"AI","model_identifier":"claude-3-5-sonnet"},"ranges":[{"content_hash":"sha256:55ee3a28846a19133b352021db84dcb2e20af290927268a3539de92e0b653c0c"}],"related":[{"type":"specification","value":"INT-004"}]}]}],"response_hash":"74ea0bff9980efcbf06b8aba4ab56d3422896e15ff7e9ecfa8b25abbe36521de","outcome":"SUCCESS","vcs":{"revision_id":"UNKNOWN"}} +{"timestamp":"2026-02-21T16:41:07.862Z","actor_id":"claude-3-5-sonnet","spec_id":"INT-004","call_id":"f30d57e2-6088-485c-aef1-ee360f571dfd","input_hash":"9d676cf338a82885552eb8947a1a2ed4f0fd5db78de973a8547b9f782c37d5a5","endpoint":"apply_diff","files":[{"relative_path":"bot.py","conversations":[{"url":"session_log_id","contributor":{"entity_type":"AI","model_identifier":"claude-3-5-sonnet"},"ranges":[{"content_hash":"sha256:ab2d88b04ea74f2d4f899ca0477b84a8de4e42ac52bffd7e2d0737fd1615ad4f"}],"related":[{"type":"specification","value":"INT-004"}]}]}],"response_hash":"34b86c0400b58616f460181ce7b076439adc99637601bedceef0552d9009fd4a","outcome":"SUCCESS","vcs":{"revision_id":"UNKNOWN"}} +{"timestamp":"2026-02-21T16:42:54.785Z","actor_id":"claude-3-5-sonnet","spec_id":"INT-004","call_id":"e690c84f-47df-48c5-b971-b9d09c972330","input_hash":"961e335fb0ef55ee4d549922885e40259008d695a50b0fb05562bd0a57954b55","endpoint":"apply_diff","files":[{"relative_path":"bot.py","conversations":[{"url":"session_log_id","contributor":{"entity_type":"AI","model_identifier":"claude-3-5-sonnet"},"ranges":[{"content_hash":"sha256:24fad635564267014f5b93b2245ea51e5fe90e27dfb46604449fe4fdfd6caf5e"}],"related":[{"type":"specification","value":"INT-004"}]}]}],"response_hash":"34b86c0400b58616f460181ce7b076439adc99637601bedceef0552d9009fd4a","outcome":"SUCCESS","vcs":{"revision_id":"UNKNOWN"}} +{"timestamp":"2026-02-21T16:45:39.360Z","actor_id":"claude-3-5-sonnet","spec_id":"INT-004","call_id":"b2d659fb-1c1e-4c73-bfeb-ae44fc9a7fed","input_hash":"227bea794dca0741922dedc8987bbbd11c5e7216d9f70d1bdd19daa96c1294e6","endpoint":"apply_diff","files":[{"relative_path":"bot.py","conversations":[{"url":"session_log_id","contributor":{"entity_type":"AI","model_identifier":"claude-3-5-sonnet"},"ranges":[{"content_hash":"sha256:a459aef9567c69c8b076d9de48944bdf867363e0cdb717526f97b4a5e63e20ef"}],"related":[{"type":"specification","value":"INT-004"}]}]}],"response_hash":"34b86c0400b58616f460181ce7b076439adc99637601bedceef0552d9009fd4a","outcome":"SUCCESS","vcs":{"revision_id":"UNKNOWN"}} +{"timestamp":"2026-02-21T16:48:03.735Z","actor_id":"claude-3-5-sonnet","spec_id":"UNSPECIFIED","call_id":"bf5ea2bb-d93d-4203-a757-83ffecb66e8c","input_hash":"5369e074243f0a442795f3cf9841e5a6e2e891269326d5fd42eb14790c2a92ff","endpoint":"apply_diff","files":[{"relative_path":"bot.py","conversations":[{"url":"session_log_id","contributor":{"entity_type":"AI","model_identifier":"claude-3-5-sonnet"},"ranges":[{"content_hash":"sha256:69d0ffe7744bc812778317c98c5f3d860692f4d701e1103e83f092e1884495c1"}],"related":[]}]}],"response_hash":"52762d5198d67e55c76ed01fd9e2ca35a230530feda8845695301c7e9157a582","outcome":"PERMISSION_DENIED","vcs":{"revision_id":"UNKNOWN"}} +{"timestamp":"2026-02-21T16:48:22.327Z","actor_id":"claude-3-5-sonnet","spec_id":"INT-001","call_id":"13c4a449-4601-40b2-b767-e46604dfd206","input_hash":"5369e074243f0a442795f3cf9841e5a6e2e891269326d5fd42eb14790c2a92ff","endpoint":"apply_diff","files":[{"relative_path":"bot.py","conversations":[{"url":"session_log_id","contributor":{"entity_type":"AI","model_identifier":"claude-3-5-sonnet"},"ranges":[{"content_hash":"sha256:69d0ffe7744bc812778317c98c5f3d860692f4d701e1103e83f092e1884495c1"}],"related":[{"type":"specification","value":"INT-001"}]}]}],"response_hash":"8485b90c8cdeaa687c3dfae42a490f17ab2e53084872eb22ad5d696d892aa783","outcome":"SUCCESS","vcs":{"revision_id":"UNKNOWN"}} +{"timestamp":"2026-02-21T16:58:40.892Z","actor_id":"claude-3-5-sonnet","spec_id":"UNSPECIFIED","call_id":"cb7ad52e-d4e4-41e5-b11d-f90f0966ca62","input_hash":"30fb5b02b653498f6fd0d52f7d685abdff3b4e794974704022e8e71eb849a070","endpoint":"apply_diff","files":[{"relative_path":"bot.py","conversations":[{"url":"session_log_id","contributor":{"entity_type":"AI","model_identifier":"claude-3-5-sonnet"},"ranges":[{"content_hash":"sha256:ec3059550d88c864d2f1a32771aec62381a2597d1c9ff473505a596a6b67dfef"}],"related":[]}]}],"response_hash":"52762d5198d67e55c76ed01fd9e2ca35a230530feda8845695301c7e9157a582","outcome":"PERMISSION_DENIED","vcs":{"revision_id":"UNKNOWN"}} +{"timestamp":"2026-02-21T16:59:18.912Z","actor_id":"claude-3-5-sonnet","spec_id":"INT-001","call_id":"31657c1a-b727-4456-8067-630dcf2b1792","input_hash":"30fb5b02b653498f6fd0d52f7d685abdff3b4e794974704022e8e71eb849a070","endpoint":"apply_diff","files":[{"relative_path":"bot.py","conversations":[{"url":"session_log_id","contributor":{"entity_type":"AI","model_identifier":"claude-3-5-sonnet"},"ranges":[{"content_hash":"sha256:ec3059550d88c864d2f1a32771aec62381a2597d1c9ff473505a596a6b67dfef"}],"related":[{"type":"specification","value":"INT-001"}]}]}],"response_hash":"8485b90c8cdeaa687c3dfae42a490f17ab2e53084872eb22ad5d696d892aa783","outcome":"SUCCESS","vcs":{"revision_id":"UNKNOWN"}} +{"timestamp":"2026-02-21T16:59:45.535Z","actor_id":"claude-3-5-sonnet","spec_id":"INT-004","call_id":"350536ad-0bbb-46d5-a4bb-ff069fad9352","input_hash":"30fb5b02b653498f6fd0d52f7d685abdff3b4e794974704022e8e71eb849a070","endpoint":"apply_diff","files":[{"relative_path":"bot.py","conversations":[{"url":"session_log_id","contributor":{"entity_type":"AI","model_identifier":"claude-3-5-sonnet"},"ranges":[{"content_hash":"sha256:ec3059550d88c864d2f1a32771aec62381a2597d1c9ff473505a596a6b67dfef"}],"related":[{"type":"specification","value":"INT-004"}]}]}],"response_hash":"4609106fbde72f9b6d61648fbba8283036f989385a678b7838f739cafa03c609","outcome":"SUCCESS","vcs":{"revision_id":"UNKNOWN"}} +{"timestamp":"2026-02-21T17:00:23.831Z","actor_id":"claude-3-5-sonnet","spec_id":"INT-004","call_id":"e51762dc-b8a2-4842-b1a6-eb37c35071e2","input_hash":"f97ee872ce36ee15a7d8e671840a45e137d12d003ade06570680855fdef0a13c","endpoint":"apply_diff","files":[{"relative_path":"bot.py","conversations":[{"url":"session_log_id","contributor":{"entity_type":"AI","model_identifier":"claude-3-5-sonnet"},"ranges":[{"content_hash":"sha256:d9953b07972e00e764fe70c298d6abd727ef256172e9b3cd6fcb7034a4df033c"}],"related":[{"type":"specification","value":"INT-004"}]}]}],"response_hash":"36589748650cf2c49682309cd87061feed3462a53bf09e4bfe557b80e0995dd3","outcome":"SUCCESS","vcs":{"revision_id":"UNKNOWN"}} +{"timestamp":"2026-02-21T17:01:00.180Z","actor_id":"claude-3-5-sonnet","spec_id":"INT-004","call_id":"6eb9e316-0707-4a65-a135-ce8e129754e3","input_hash":"db5e01beca140f08d4e7c13cd52e40b92c71ba753cee565a2ed5762a10252cab","endpoint":"apply_diff","files":[{"relative_path":"bot.py","conversations":[{"url":"session_log_id","contributor":{"entity_type":"AI","model_identifier":"claude-3-5-sonnet"},"ranges":[{"content_hash":"sha256:de32b585e75d87d2d021da6877eceb463b8d43cb272e7e9798eb477a67398320"}],"related":[{"type":"specification","value":"INT-004"}]}]}],"response_hash":"b3820f88265a8787bb91719a9a652e7ad1999b62e1917cddde1346faadce5668","outcome":"SUCCESS","vcs":{"revision_id":"UNKNOWN"}} +{"id":"2cccabea-31ee-4289-851b-72720f780d72","timestamp":"2026-02-21T17:27:43.641Z","vcs":{"revision_id":"UNKNOWN"}} +{"id":"94cc041b-bcca-4d9b-9838-5586f08a1f11","timestamp":"2026-02-21T17:28:20.217Z","vcs":{"revision_id":"UNKNOWN"},"files":[{"relative_path":"tests/test_bot.py","conversations":[{"url":"019c813d-8488-729a-b994-0698037ba856","contributor":{"entity_type":"AI","model_identifier":"claude-3-5-sonnet"},"ranges":[{"start_line":1,"end_line":1,"content_hash":"sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"}],"related":[{"type":"specification","value":"INT-001"}]}]}]} +{"id":"ce3a6ad3-9fdd-40ad-a023-342b41889649","timestamp":"2026-02-21T17:28:56.520Z","vcs":{"revision_id":"UNKNOWN"},"files":[{"relative_path":"tests/test_bot.py","conversations":[{"url":"019c813d-8488-729a-b994-0698037ba856","contributor":{"entity_type":"AI","model_identifier":"claude-3-5-sonnet"},"ranges":[{"start_line":1,"end_line":36,"content_hash":"sha256:4793b2eb1bdd3c3f31404891363b2f715447de66d8c0230eea34a1595ca1844a"}],"related":[{"type":"specification","value":"INT-001"}]}]}]} +{"id":"d0401272-1de1-4250-aa53-4271233763ff","timestamp":"2026-02-21T17:57:49.911Z","vcs":{"revision_id":"UNKNOWN"}} +{"id":"df8057db-54b0-41da-a8b0-00c747bc3063","timestamp":"2026-02-21T17:58:03.261Z","vcs":{"revision_id":"UNKNOWN"}} +{"id":"5bee1c84-b451-4e12-b8ee-99ba58058792","timestamp":"2026-02-21T18:02:21.660Z","vcs":{"revision_id":"UNKNOWN"}} +{"id":"5cd7882f-d2b2-43c8-9c6d-a336c6a4500e","timestamp":"2026-02-21T18:28:05.701Z","vcs":{"revision_id":"UNKNOWN"},"files":[{"relative_path":"tests/__init__.py","conversations":[{"url":"019c8174-31e7-7509-9240-223fb63b537a","contributor":{"entity_type":"AI","model_identifier":"arcee-ai/trinity-large-preview:free"},"ranges":[{"start_line":1,"end_line":1,"content_hash":"sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"}],"related":[{"type":"specification","value":"INT-001"}]}]}]} +{"id":"1e663a70-84d8-4bb8-a994-9f833edd7aa9","timestamp":"2026-02-21T18:28:47.524Z","vcs":{"revision_id":"UNKNOWN"},"files":[{"relative_path":"tests/test_bot.py","conversations":[{"url":"019c8174-31e7-7509-9240-223fb63b537a","contributor":{"entity_type":"AI","model_identifier":"arcee-ai/trinity-large-preview:free"},"ranges":[{"start_line":1,"end_line":53,"content_hash":"sha256:2378a0c561aaace510374b093e66621c83381fe872872110ba78d798a837c68b"}],"related":[{"type":"specification","value":"INT-001"}]}]}]} +{"id":"b45a79df-8ce7-4f3f-8658-fe90c7f8464e","timestamp":"2026-02-21T18:30:01.823Z","vcs":{"revision_id":"UNKNOWN"},"files":[{"relative_path":"tests/test_integration.py","conversations":[{"url":"019c8174-31e7-7509-9240-223fb63b537a","contributor":{"entity_type":"AI","model_identifier":"arcee-ai/trinity-large-preview:free"},"ranges":[{"start_line":1,"end_line":54,"content_hash":"sha256:e9503fe2948194b83d1e31741ffc5922b9d6f942283ef0ae69a51e7cda3a7cbc"}],"related":[{"type":"specification","value":"INT-001"}]}]}]} +{"id":"91f54836-7263-44f9-94c4-893007e66003","timestamp":"2026-02-21T18:32:18.433Z","vcs":{"revision_id":"UNKNOWN"},"files":[{"relative_path":"requirements.txt","conversations":[{"url":"019c8174-31e7-7509-9240-223fb63b537a","contributor":{"entity_type":"AI","model_identifier":"arcee-ai/trinity-large-preview:free"},"ranges":[{"start_line":1,"end_line":9,"content_hash":"sha256:e1c39b040435a1fc0823c31d66abb8d4ade1ec0c83975759703eea58c9c7be19"}],"related":[{"type":"specification","value":"INT-001"}]}]}]} +{"id":"3682518d-bb6e-4e40-b839-ce78f642f088","timestamp":"2026-02-21T19:04:32.744Z","classification":"FILE_WRITE","vcs":{"revision_id":"UNKNOWN"},"files":[{"relative_path":"bot.py","conversations":[{"url":"019c8193-d377-72f5-b93e-84b78b91248b","contributor":{"entity_type":"AI","model_identifier":"arcee-ai/trinity-large-preview:free"},"ranges":[{"start_line":1,"end_line":38,"content_hash":"sha256:7da9ca2565989636789cb41c248542f36b9abc364fc644922ccbc29686a6fa9d"}],"related":[{"type":"specification","value":"INT-003"}]}]}]} +{"id":"ee619aec-ea09-456c-8f73-f834062100d1","timestamp":"2026-02-21T19:05:42.953Z","classification":"FILE_WRITE","vcs":{"revision_id":"UNKNOWN"},"files":[{"relative_path":"bot.py","conversations":[{"url":"019c8193-d377-72f5-b93e-84b78b91248b","contributor":{"entity_type":"AI","model_identifier":"arcee-ai/trinity-large-preview:free"},"ranges":[{"start_line":1,"end_line":38,"content_hash":"sha256:ec27afb4240b3fd5e7f6683ec830b5248ad806279223b08ff49c33bbb0f9de2a"}],"related":[{"type":"specification","value":"INT-003"}]}]}]} +{"id":"1b057636-20ff-4ac3-a159-39392c64819f","timestamp":"2026-02-21T19:06:53.414Z","classification":"FILE_WRITE","vcs":{"revision_id":"UNKNOWN"},"files":[{"relative_path":"bot.py","conversations":[{"url":"019c8193-d377-72f5-b93e-84b78b91248b","contributor":{"entity_type":"AI","model_identifier":"arcee-ai/trinity-large-preview:free"},"ranges":[{"start_line":1,"end_line":38,"content_hash":"sha256:ec27afb4240b3fd5e7f6683ec830b5248ad806279223b08ff49c33bbb0f9de2a"}],"related":[{"type":"specification","value":"INT-003"}]}]}]} diff --git a/.orchestration/intent_map.md b/.orchestration/intent_map.md new file mode 100644 index 00000000000..be81983aff1 --- /dev/null +++ b/.orchestration/intent_map.md @@ -0,0 +1,14 @@ +intents: + +- id: "INT-001" + name: "testing skills" + description: "testing skills" + owned_scope: ["/tests/"] +- id: "INT-002" + name: "project dependencies" + description: "Updating project dependencies" + owned_scope: ["requirements.txt", "pyproject.toml"] +- id: "INT-003" + name: "project documentation" + description: "create or update project documentation" + owned_scope: ["README.md"] diff --git a/.orchestration/intent_map.yaml b/.orchestration/intent_map.yaml deleted file mode 100644 index 829efa7d372..00000000000 --- a/.orchestration/intent_map.yaml +++ /dev/null @@ -1,16 +0,0 @@ -intents: - - id: "GOV-INIT" - name: "Governance Initialization" - description: "Initial setup of the governed IDE hooks and sidecars" - priority: 1 - owned_scope: ["src/hooks", ".orchestration"] - - id: "CORE-REF" - name: "Core Refactoring" - description: "Refactoring core components like presentAssistantMessage and Task" - priority: 2 - owned_scope: ["src/core"] - - id: "PROMPT-UP" - name: "Prompt Engineering" - description: "Updating system prompts and intent protocol sections" - priority: 2 - owned_scope: ["src/core/prompts"] From 840685fc51133ca776fd2c741a2aa6a9fc116bf6 Mon Sep 17 00:00:00 2001 From: yosef-zewdu Date: Sat, 21 Feb 2026 22:29:02 +0300 Subject: [PATCH 18/23] feat: Enhance IntentController with active intent management, including loading, saving, and status updates --- src/core/intent/IntentController.ts | 153 ++++++++++++++---- .../native-tools/select_active_intent.ts | 4 +- 2 files changed, 122 insertions(+), 35 deletions(-) diff --git a/src/core/intent/IntentController.ts b/src/core/intent/IntentController.ts index 3e8e9e2024b..da863bdd02a 100644 --- a/src/core/intent/IntentController.ts +++ b/src/core/intent/IntentController.ts @@ -1,18 +1,25 @@ import path from "path" import fs from "fs/promises" +import crypto from "crypto" import * as vscode from "vscode" import YAML from "yaml" import { fileExistsAtPath } from "../../utils/fs" +import ignore, { Ignore } from "ignore" +import "../../utils/path" // For toPosix + export interface Intent { id: string + name: string + status: "CREATED" | "IN_PROGRESS" | "COMPLETED" | string description: string constraints: string[] owned_scope: string[] + acceptance_criteria?: string[] } export interface ActiveIntentsConfig { - intents: Intent[] + active_intents: Intent[] } /** @@ -21,77 +28,145 @@ export interface ActiveIntentsConfig { export class IntentController { private cwd: string private disposables: vscode.Disposable[] = [] - private intents: Intent[] = [] - private activeIntentId: string | undefined + private activeIntents: Intent[] = [] + private ignoreInstances: Map = new Map() + private activeIntentId?: string + private readHashes: Map = new Map() constructor(cwd: string) { this.cwd = cwd } async initialize(): Promise { - await this.loadIntents() + await this.loadActiveIntents() this.setupFileWatcher() } + getActiveIntentId(): string | undefined { + return this.activeIntentId + } + + setActiveIntentId(intentId: string | undefined): void { + this.activeIntentId = intentId + } + private setupFileWatcher(): void { - const pattern = new vscode.RelativePattern(path.join(this.cwd, ".orchestration"), "intent_map.yaml") + const pattern = new vscode.RelativePattern(path.join(this.cwd, ".orchestration"), "active_intents.yaml") const fileWatcher = vscode.workspace.createFileSystemWatcher(pattern) this.disposables.push( - fileWatcher.onDidChange(() => this.loadIntents()), - fileWatcher.onDidCreate(() => this.loadIntents()), + fileWatcher.onDidChange(() => this.loadActiveIntents()), + fileWatcher.onDidCreate(() => this.loadActiveIntents()), fileWatcher.onDidDelete(() => { - this.intents = [] + this.activeIntents = [] + this.ignoreInstances.clear() }), ) this.disposables.push(fileWatcher) } - private async loadIntents(): Promise { + private async loadActiveIntents(): Promise { try { - const configPath = path.join(this.cwd, ".orchestration", "intent_map.yaml") - if (await fileExistsAtPath(configPath)) { - const content = await fs.readFile(configPath, "utf8") - const config = YAML.parse(content) as ActiveIntentsConfig - this.intents = config.intents || [] - } - - const activePath = path.join(this.cwd, ".orchestration", "active_intent.yaml") + const activePath = path.join(this.cwd, ".orchestration", "active_intents.yaml") if (await fileExistsAtPath(activePath)) { const content = await fs.readFile(activePath, "utf8") - const activeConfig = YAML.parse(content) as any - this.activeIntentId = activeConfig?.active_intent_id || undefined + const config = YAML.parse(content) as any + this.activeIntents = (config.active_intents || config.intents || []).map((intent: any) => ({ + ...intent, + status: (intent.status || "CREATED").toUpperCase(), + constraints: Array.isArray(intent.constraints) ? intent.constraints : [], + owned_scope: Array.isArray(intent.owned_scope) ? intent.owned_scope : [], + acceptance_criteria: Array.isArray(intent.acceptance_criteria) ? intent.acceptance_criteria : [], + })) + + // Refresh ignore instances for scope enforcement + this.ignoreInstances.clear() + for (const intent of this.activeIntents) { + if (intent.owned_scope.length > 0) { + const ig = ignore().add(intent.owned_scope) + this.ignoreInstances.set(intent.id, ig) + } + } + } else { + // Legacy support or fallback to optional map + const mapPath = path.join(this.cwd, ".orchestration", "intent_map.yaml") + if (await fileExistsAtPath(mapPath)) { + const content = await fs.readFile(mapPath, "utf8") + const config = YAML.parse(content) as any + this.activeIntents = config.active_intents || config.intents || [] + } else { + this.activeIntents = [] + } + this.ignoreInstances.clear() } } catch (error) { - console.error("Error loading orchestration files:", error) + console.error("Error loading active_intents.yaml:", error) } } - getActiveIntentId(): string | undefined { - return this.activeIntentId + getAllActiveIntents(): Intent[] { + return this.activeIntents } - async setActiveIntentId(intentId: string | undefined): Promise { - this.activeIntentId = intentId + getIntent(intentId: string): Intent | undefined { + return this.activeIntents.find((i) => i.id === intentId) + } + + async updateIntentStatus(intentId: string, status: Intent["status"]): Promise { + const intent = this.getIntent(intentId) + if (intent) { + intent.status = status + await this.saveActiveIntents() + } + } + + async addActiveIntent(intent: Intent): Promise { + this.activeIntents.push(intent) + await this.saveActiveIntents() + } + + private async saveActiveIntents(): Promise { try { - const activePath = path.join(this.cwd, ".orchestration", "active_intent.yaml") + const activePath = path.join(this.cwd, ".orchestration", "active_intents.yaml") const content = YAML.stringify({ - active_intent_id: intentId || null, - timestamp: new Date().toISOString(), + active_intents: this.activeIntents, + last_updated: new Date().toISOString(), }) await fs.writeFile(activePath, content, "utf8") } catch (error) { - console.error("Error persisting active intent:", error) + console.error("Error persisting active intents:", error) } } - getIntent(intentId: string): Intent | undefined { - return this.intents.find((i) => i.id === intentId) - } + /** + * Validates if a file path is within the owned scope of ANY active intent. + * If no intents are active, this typically returns true (governance is opt-in per intent). + * However, if projects strictly require an intent, that is handled by the pre-hook. + */ + validateScope(filePath: string, intentId?: string): boolean { + // Normalize path to be relative to cwd for the ignore library + // This handles absolute paths, ./ prefixes, and platform-specific separators + const absolutePath = path.resolve(this.cwd, filePath) + const posixPath = path.relative(this.cwd, absolutePath).toPosix() + + if (intentId) { + const ig = this.ignoreInstances.get(intentId) + return ig ? ig.ignores(posixPath) : true + } + + // Check if it matches ANY active intent's scope + if (this.ignoreInstances.size === 0) { + return true + } + + for (const ig of this.ignoreInstances.values()) { + if (ig.ignores(posixPath)) { + return true + } + } - getAllIntents(): Intent[] { - return this.intents + return false } isDestructiveCommand(command: string): boolean { @@ -108,6 +183,8 @@ export class IntentController { "git show", "find", "grep", + "pnpm test", + "npm test", ] const trimmedCommand = command.trim() @@ -118,6 +195,16 @@ export class IntentController { return true } + recordReadHash(filePath: string, hash: string): void { + const absolutePath = path.resolve(this.cwd, filePath) + this.readHashes.set(absolutePath, hash) + } + + getReadHash(filePath: string): string | undefined { + const absolutePath = path.resolve(this.cwd, filePath) + return this.readHashes.get(absolutePath) + } + dispose(): void { this.disposables.forEach((d) => d.dispose()) this.disposables = [] diff --git a/src/core/prompts/tools/native-tools/select_active_intent.ts b/src/core/prompts/tools/native-tools/select_active_intent.ts index 43ac7cee2d7..f7eb7d46380 100644 --- a/src/core/prompts/tools/native-tools/select_active_intent.ts +++ b/src/core/prompts/tools/native-tools/select_active_intent.ts @@ -1,8 +1,8 @@ import type OpenAI from "openai" -const SELECT_ACTIVE_INTENT_DESCRIPTION = `Select an active intent to load its specific context, constraints, and scope. You MUST call this tool before making any changes if an active intent is relevant to your task.` +const SELECT_ACTIVE_INTENT_DESCRIPTION = `Activate a specific intent for the current session to load its architectural constraints, filters, and owned scope. You MUST call this tool at the start of your task (even if the intent is already marked "IN_PROGRESS" in the file) before using any destructive tools.` -const INTENT_ID_PARAMETER_DESCRIPTION = `The ID of the active intent to load (e.g., 'INT-001').` +const INTENT_ID_PARAMETER_DESCRIPTION = `The ID of the active intent to load (e.g., 'INT-XXX').` export default { type: "function", From 731241e3045f55a326fbebeb31d403317d536bff Mon Sep 17 00:00:00 2001 From: yosef-zewdu Date: Sat, 21 Feb 2026 22:30:04 +0300 Subject: [PATCH 19/23] refactor: Update intent protocol and hooks for improved active intent management and validation --- src/core/prompts/sections/intent-protocol.ts | 18 +++++++-------- src/core/tools/SelectActiveIntentTool.ts | 23 +++++++++++++++----- src/hooks/pre/IntentValidationHook.ts | 2 +- src/hooks/pre/ScopeEnforcementHook.ts | 8 +++---- 4 files changed, 31 insertions(+), 20 deletions(-) diff --git a/src/core/prompts/sections/intent-protocol.ts b/src/core/prompts/sections/intent-protocol.ts index 6918f1fcd15..9d162bacff4 100644 --- a/src/core/prompts/sections/intent-protocol.ts +++ b/src/core/prompts/sections/intent-protocol.ts @@ -3,22 +3,22 @@ import { Intent } from "../../intent/IntentController" export function getIntentProtocolSection(intents: Intent[]): string { const intentCatalog = intents.length > 0 - ? intents.map((i) => `- **${i.id}**: ${i.description}`).join("\n") - : "No intents defined in the current project. If this is a new project, you should first initialize governance by creating the `.orchestration/` directory and an `intent_map.yaml` file." + ? intents.map((i) => `- **${i.id}** (${i.name}): ${i.description}`).join("\n") + : "No active intents. If this is a governed project, check `.orchestration/intent_map.md` for a spatial map and initialize `.orchestration/active_intents.yaml` with your target intent." return `## Intent-Driven Development Protocol You are an Intent-Driven Architect. To ensure architectural governance and traceability, you MUST follow this protocol: -### Available Intent Catalog +### Active Intent Catalog ${intentCatalog} ### Protocol Rules -1. **Analyze First**: Before making any changes or executing destructive commands, analyze the user's request against the catalog. -2. **Select Intent**: You MUST call \`select_active_intent\` to load context for a specific ID from the catalog. -3. **Paths**: Always use root-relative paths (e.g., \`.orchestration/intent_map.yaml\`) when referencing governance files. -4. **Initialization**: If governance is not yet set up (empty catalog), your first task should be to help the user initialize it. -5. **Rejection Recovery**: If a tool call is rejected with "Access Denied" or "Security Violation", your **IMMEDIATE NEXT RESPONSE** must consist only of a call to \`select_active_intent\` (to fix the missing intent) or \`ask_followup_question\` (to discuss scope). Do NOT try a different tool or the same tool again without first resolving the intent status. +1. **Analyze First**: Before making any changes, analyze your task against the business intents defined in \`.orchestration/active_intents.yaml\` and the spatial map in \`.orchestration/intent_map.md\`. +2. **Select Intent**: You MUST call \`select_active_intent\` to activate an ID from the specification for the **CURRENT SESSION**. This is required even if the intent status in the YAML is already "IN_PROGRESS", as the system needs to explicitly load session-level filters and constraints. +3. **Paths**: Use root-relative paths for all governance files in \`.orchestration/\`. +4. **Initialization**: If governance is not set up, your first task is to help the user initialize the \`.orchestration/\` folder with an \`intent_map.md\` and \`active_intents.yaml\`. +5. **Rejection Recovery**: If a tool call is rejected with "Access Denied" or "Security Violation", your **IMMEDIATE NEXT RESPONSE** must be a call to \`select_active_intent\` or \`ask_followup_question\`. -**CRITICAL**: You CANNOT write code or execute destructive commands immediately. Your first action MUST be to call \`select_active_intent\` if a relevant intent exists.` +**CRITICAL**: You CANNOT write code or execute destructive commands without an active intent in 'IN_PROGRESS' status that has been explicitly activated in this session.` } diff --git a/src/core/tools/SelectActiveIntentTool.ts b/src/core/tools/SelectActiveIntentTool.ts index 9e6134aab50..8d4c6d73b22 100644 --- a/src/core/tools/SelectActiveIntentTool.ts +++ b/src/core/tools/SelectActiveIntentTool.ts @@ -17,23 +17,36 @@ export class SelectActiveIntentTool extends BaseTool<"select_active_intent"> { throw new Error("IntentController not initialized") } - const intent = task.intentController.getIntent(intent_id) + // In this advanced model, search for the intent in the active list first + let intent = task.intentController.getIntent(intent_id) if (!intent) { - pushToolResult(`Error: Intent ID '${intent_id}' not found in intent_map.yaml.`) + pushToolResult( + `Error: Intent ID '${intent_id}' not found in .orchestration/active_intents.yaml. You may need to ask the user to add it or use an existing one from .orchestration/intent_map.md.`, + ) return } + // Update status if it's not already in progress + if (intent.status !== "IN_PROGRESS") { + await task.intentController.updateIntentStatus(intent_id, "IN_PROGRESS") + } + + // Essential: Set the session-level active intent ID for the Hook Engine task.intentController.setActiveIntentId(intent_id) const contextBlock = [ ``, `ID: ${intent.id}`, + `Name: ${intent.name}`, + `Status: ${intent.status}`, `Description: ${intent.description}`, `Constraints:`, - ...intent.constraints.map((c) => `- ${c}`), - `Scope:`, - ...intent.owned_scope.map((s) => `- ${s}`), + ...(intent.constraints || []).map((c) => `- ${c}`), + `Owned Scope:`, + ...(intent.owned_scope || []).map((s) => `- ${s}`), + `Acceptance Criteria:`, + ...(intent.acceptance_criteria || []).map((ac) => `- ${ac}`), ``, ].join("\n") diff --git a/src/hooks/pre/IntentValidationHook.ts b/src/hooks/pre/IntentValidationHook.ts index c477ebf1fe3..71d44e44a10 100644 --- a/src/hooks/pre/IntentValidationHook.ts +++ b/src/hooks/pre/IntentValidationHook.ts @@ -32,7 +32,7 @@ export class IntentValidationHook implements IToolHook { if (shouldBlock) { // Only block if the project actually has intents defined (governance is active) - const hasIntents = task.intentController.getAllIntents().length > 0 + const hasIntents = task.intentController.getAllActiveIntents().length > 0 if (hasIntents) { return { allow: false, diff --git a/src/hooks/pre/ScopeEnforcementHook.ts b/src/hooks/pre/ScopeEnforcementHook.ts index 6bb32eaaba1..39ea7a0de18 100644 --- a/src/hooks/pre/ScopeEnforcementHook.ts +++ b/src/hooks/pre/ScopeEnforcementHook.ts @@ -14,14 +14,12 @@ export class ScopeEnforcementHook implements IToolHook { if (toolName === "write_to_file" || toolName === "apply_diff") { const filePath = (toolArgs.path || "") as string - const intent = task.intentController.getIntent(intentId) - - if (intent && filePath) { - const isScoped = intent.owned_scope.some((scope) => filePath.includes(scope)) + if (filePath) { + const isScoped = task.intentController.validateScope(filePath, intentId) if (!isScoped) { return { allow: false, - reason: `Security Violation: File '${filePath}' is outside the owned scope of intent '${intentId}'. You MUST either select a different intent that covers this file or ask the user to adjust the scope in '.orchestration/intent_map.yaml'.`, + reason: `Security Violation: File '${filePath}' is outside the owned scope of intent '${intentId}'. You MUST either select a different intent that covers this file or ask the user to adjust the scope in '.orchestration/active_intents.yaml'.`, } } } From 76bde5c9dd93bccfba6ea32cce56b12156e22c18 Mon Sep 17 00:00:00 2001 From: yosef-zewdu Date: Sat, 21 Feb 2026 22:30:52 +0300 Subject: [PATCH 20/23] feat: Enhance TraceLoggingHook to log mutating actions with detailed file changes and Git revision tracking --- src/hooks/post/TraceLoggingHook.ts | 84 +++++++++++++++++++++++++++--- 1 file changed, 77 insertions(+), 7 deletions(-) diff --git a/src/hooks/post/TraceLoggingHook.ts b/src/hooks/post/TraceLoggingHook.ts index c1b911d8442..b4a3855c0c3 100644 --- a/src/hooks/post/TraceLoggingHook.ts +++ b/src/hooks/post/TraceLoggingHook.ts @@ -1,10 +1,14 @@ import path from "path" import fs from "fs/promises" +import crypto from "crypto" +import { v4 as uuidv4 } from "uuid" +import { execa } from "execa" import { IToolHook, HookType, HookContext } from "../types" -import { fileExistsAtPath } from "../../utils/fs" + +import { getModelId } from "@roo-code/types" /** - * Automates project governance by logging all intent-driven changes. + * Automates project governance by logging all intent-driven changes with high fidelity. */ export class TraceLoggingHook implements IToolHook { name = "TraceLoggingHook" @@ -13,15 +17,81 @@ export class TraceLoggingHook implements IToolHook { async execute(context: HookContext): Promise { const { task, toolName, intentId, arguments: toolArgs, timestamp, result, error } = context + // Only log mutating actions for the advanced ledger + const isMutating = toolName === "write_to_file" || toolName === "apply_diff" || toolName === "execute_command" + if (!isMutating) { + return + } + try { const logPath = path.join(task.cwd, ".orchestration", "agent_trace.jsonl") + + // Get current Git revision + let revisionId = "UNKNOWN" + try { + const { stdout } = await execa("git", ["rev-parse", "HEAD"], { cwd: task.cwd }) + revisionId = stdout.trim() + } catch (gitErr) { + // Fallback if not a git repo + } + + // Generate content hash and ranges for file writes + const files: any[] = [] + if (toolName === "write_to_file" || toolName === "apply_diff") { + const filePath = (toolArgs.path || toolArgs.file_path) as string + const absolutePath = path.resolve(task.cwd, filePath) + + try { + const content = await fs.readFile(absolutePath, "utf8") + const hash = crypto.createHash("sha256").update(content).digest("hex") + const lineCount = content.split("\n").length + + // Heuristic for range: for write_to_file it's the whole file + // For apply_diff, we could parse the diff, but for now we'll use the whole file + // or specific lines if we can extract them. Let's use 1 to lineCount as a safe default. + let startLine = 1 + let endLine = lineCount + + // Try to extract range from diff if it's apply_diff + if (toolName === "apply_diff" && toolArgs.diff) { + const match = toolArgs.diff.match(/@@ -(\d+),?\d* \+(\d+),?\d* @@/) + if (match) { + startLine = parseInt(match[2], 10) + // Fallback endLine to lineCount or estimate from diff + } + } + + files.push({ + relative_path: filePath, + conversations: [ + { + url: task.taskId, + contributor: { + entity_type: "AI", + model_identifier: getModelId(task.apiConfiguration) || "claude-3-5-sonnet", + }, + ranges: [ + { + start_line: startLine, + end_line: endLine, + content_hash: `sha256:${hash}`, + }, + ], + related: intentId ? [{ type: "specification", value: intentId }] : [], + }, + ], + }) + } catch (fileErr) { + console.error(`Error reading file for trace logging: ${filePath}`, fileErr) + } + } + const entry = { + id: uuidv4(), timestamp, - intentId: intentId || "NONE", - tool: toolName, - path: toolArgs.path || toolArgs.file_path || null, - success: !error && !result?.includes("Access Denied"), // Hack for blocked hooks returning text errors - error: error ? error.message : result?.includes("Access Denied") ? result : null, + classification: toolName === "apply_diff" ? "AST_REFACTOR" : "FILE_WRITE", + vcs: { revision_id: revisionId }, + files: files.length > 0 ? files : undefined, } const logLine = JSON.stringify(entry) + "\n" From 47b8b78c1560a50f8e000d3fe1602683e14f7f69 Mon Sep 17 00:00:00 2001 From: yosef-zewdu Date: Sat, 21 Feb 2026 22:31:44 +0300 Subject: [PATCH 21/23] feat: Implement ConcurrencyPreHook and ConcurrencyPostHook for optimistic locking and file change tracking --- src/hooks/post/ConcurrencyPostHook.ts | 26 ++++++++++++++ src/hooks/post/VerificationLessonHook.ts | 41 +++++++++++++++++++++ src/hooks/pre/ConcurrencyPreHook.ts | 46 ++++++++++++++++++++++++ 3 files changed, 113 insertions(+) create mode 100644 src/hooks/post/ConcurrencyPostHook.ts create mode 100644 src/hooks/post/VerificationLessonHook.ts create mode 100644 src/hooks/pre/ConcurrencyPreHook.ts diff --git a/src/hooks/post/ConcurrencyPostHook.ts b/src/hooks/post/ConcurrencyPostHook.ts new file mode 100644 index 00000000000..8e7a0a179f3 --- /dev/null +++ b/src/hooks/post/ConcurrencyPostHook.ts @@ -0,0 +1,26 @@ +import crypto from "crypto" +import { IToolHook, HookType, HookContext } from "../types" + +/** + * Records the hash of files when they are read by the agent to enable optimistic locking. + */ +export class ConcurrencyPostHook implements IToolHook { + name = "ConcurrencyPostHook" + type = HookType.POST + + async execute(context: HookContext): Promise { + const { task, toolName, arguments: toolArgs, result, error } = context + + // If the tool failed, don't record anything + if (error) return + + if (toolName === "read_file") { + const filePath = toolArgs.path as string + const content = result as string + if (!filePath || typeof content !== "string") return + + const hash = crypto.createHash("sha256").update(content).digest("hex") + task.intentController.recordReadHash(filePath, hash) + } + } +} diff --git a/src/hooks/post/VerificationLessonHook.ts b/src/hooks/post/VerificationLessonHook.ts new file mode 100644 index 00000000000..fc54462591f --- /dev/null +++ b/src/hooks/post/VerificationLessonHook.ts @@ -0,0 +1,41 @@ +import { IToolHook, HookType, HookContext } from "../types" + +/** + * Automatically records "Lessons Learned" to AGENTS.md when a verification step fails. + */ +export class VerificationLessonHook implements IToolHook { + name = "VerificationLessonHook" + type = HookType.POST + + async execute(context: HookContext): Promise { + const { task, toolName, arguments: toolArgs, result, error } = context + + // We only care about failures + if (!error) return + + // Identify verification tools + // Usually execute_command for npm test, etc. + if (toolName === "execute_command") { + const command = (toolArgs.command || "").toLowerCase() + const verificationKeywords = ["test", "lint", "check", "verify", "tsc", "build"] + + const isVerification = verificationKeywords.some((kw) => command.includes(kw)) + + if (isVerification) { + const lesson = `Verification failed for command: '${toolArgs.command}'. Error: ${error.message || error}` + + // Use the tool's logic without a real tool call to avoid loops + const agentsPath = require("path").join(task.cwd, "AGENTS.md") + const timestamp = new Date().toISOString() + const lessonEntry = `\n\n### Lessons Learned (${timestamp})\n- ${lesson}\n` + + try { + await require("fs/promises").appendFile(agentsPath, lessonEntry, "utf8") + console.log(`[VerificationLessonHook] Auto-recorded lesson for failed command: ${command}`) + } catch (err) { + console.error(`[VerificationLessonHook] Failed to record lesson:`, err) + } + } + } + } +} diff --git a/src/hooks/pre/ConcurrencyPreHook.ts b/src/hooks/pre/ConcurrencyPreHook.ts new file mode 100644 index 00000000000..e807e5e7541 --- /dev/null +++ b/src/hooks/pre/ConcurrencyPreHook.ts @@ -0,0 +1,46 @@ +import path from "path" +import fs from "fs/promises" +import crypto from "crypto" +import { IToolHook, HookType, HookContext, HookResponse } from "../types" + +/** + * Enforces optimistic locking by blocking writes if the file has changed on disk + * since it was last read by this agent session. + */ +export class ConcurrencyPreHook implements IToolHook { + name = "ConcurrencyPreHook" + type = HookType.PRE + + async execute(context: HookContext): Promise { + const { task, toolName, arguments: toolArgs } = context + + if (toolName === "write_to_file" || toolName === "apply_diff") { + const filePath = (toolArgs.path || toolArgs.file_path) as string + if (!filePath) return + + const absolutePath = path.resolve(task.cwd, filePath) + const storedHash = task.intentController.getReadHash(absolutePath) + + // If we haven't read this file yet in this session, we don't have a "read hash" + // This might happen if the agent assumes the file content or it's a new file. + if (!storedHash) { + return + } + + try { + const currentContent = await fs.readFile(absolutePath, "utf8") + const currentHash = crypto.createHash("sha256").update(currentContent).digest("hex") + + if (currentHash !== storedHash) { + return { + allow: false, + reason: `Stale File Error: File '${filePath}' has been modified by another agent or user since you last read it. You MUST re-read the file using 'read_file' to synchronize your state before attempting to write.`, + } + } + } catch (error) { + // If file doesn't exist, it's not a collision (it might be a new file being created) + // or some other error happened - let the original tool handle it. + } + } + } +} From 25f2fe1ffc131fb9b76504761e54bbbac29b73bb Mon Sep 17 00:00:00 2001 From: yosef-zewdu Date: Sat, 21 Feb 2026 22:32:37 +0300 Subject: [PATCH 22/23] feat: Add RecordLessonTool for recording lessons learned and update tool definitions --- packages/types/src/tool.ts | 1 + src/core/tools/RecordLessonTool.ts | 34 ++++++++++++++++++++++++++++ src/core/tools/UpdateTodoListTool.ts | 5 +--- src/shared/tools.ts | 4 ++++ 4 files changed, 40 insertions(+), 4 deletions(-) create mode 100644 src/core/tools/RecordLessonTool.ts diff --git a/packages/types/src/tool.ts b/packages/types/src/tool.ts index fe595ecf695..95ebc0b6a55 100644 --- a/packages/types/src/tool.ts +++ b/packages/types/src/tool.ts @@ -46,6 +46,7 @@ export const toolNames = [ "skill", "generate_image", "select_active_intent", + "record_lesson_learned", "custom_tool", ] as const diff --git a/src/core/tools/RecordLessonTool.ts b/src/core/tools/RecordLessonTool.ts new file mode 100644 index 00000000000..d5b6feef27a --- /dev/null +++ b/src/core/tools/RecordLessonTool.ts @@ -0,0 +1,34 @@ +import fs from "fs/promises" +import path from "path" +import { Task } from "../task/Task" +import { BaseTool, ToolCallbacks } from "./BaseTool" + +interface RecordLessonParams { + lesson: string +} + +export class RecordLessonTool extends BaseTool<"record_lesson_learned"> { + readonly name = "record_lesson_learned" as const + + async execute(params: RecordLessonParams, task: Task, callbacks: ToolCallbacks): Promise { + const { pushToolResult, handleError } = callbacks + const { lesson } = params + + try { + const agentsPath = path.join(task.cwd, "AGENTS.md") + + // Format the lesson entry + const timestamp = new Date().toISOString() + const lessonEntry = `\n\n### Lessons Learned (${timestamp})\n- ${lesson}\n` + + // Append to AGENTS.md (create if not exists) + await fs.appendFile(agentsPath, lessonEntry, "utf8") + + pushToolResult(`Successfully recorded lesson to AGENTS.md`) + } catch (error) { + await handleError("recording lesson", error as Error) + } + } +} + +export const recordLessonTool = new RecordLessonTool() diff --git a/src/core/tools/UpdateTodoListTool.ts b/src/core/tools/UpdateTodoListTool.ts index 7414b713cf4..6b58c7eb71a 100644 --- a/src/core/tools/UpdateTodoListTool.ts +++ b/src/core/tools/UpdateTodoListTool.ts @@ -188,10 +188,7 @@ export function parseMarkdownChecklist(md: string): TodoItem[] { let status: TodoStatus = "pending" if (match[1] === "x" || match[1] === "X") status = "completed" else if (match[1] === "-" || match[1] === "~") status = "in_progress" - const id = crypto - .createHash("md5") - .update(match[2] + status) - .digest("hex") + const id = crypto.createHash("md5").update(match[2]).digest("hex") todos.push({ id, content: match[2], diff --git a/src/shared/tools.ts b/src/shared/tools.ts index 6bad21b18e4..35cd6e3996e 100644 --- a/src/shared/tools.ts +++ b/src/shared/tools.ts @@ -81,6 +81,7 @@ export const toolParamNames = [ "files", "line_ranges", "intent_id", + "lesson", ] as const export type ToolParamName = (typeof toolParamNames)[number] @@ -117,6 +118,7 @@ export type NativeToolArgs = { use_mcp_tool: { server_name: string; tool_name: string; arguments?: Record } write_to_file: { path: string; content: string } select_active_intent: { intent_id: string } + record_lesson_learned: { lesson: string } // Add more tools as they are migrated to native protocol } @@ -296,6 +298,7 @@ export const TOOL_DISPLAY_NAMES: Record = { skill: "load skill", generate_image: "generate images", select_active_intent: "select active intent", + record_lesson_learned: "record lesson learned", custom_tool: "use custom tools", } as const @@ -330,6 +333,7 @@ export const ALWAYS_AVAILABLE_TOOLS: ToolName[] = [ "run_slash_command", "skill", "select_active_intent", + "record_lesson_learned", ] as const /** From 4b05ef4ae3526bd9c873f4e76d64503e1c3159cf Mon Sep 17 00:00:00 2001 From: yosef-zewdu Date: Sat, 21 Feb 2026 22:33:45 +0300 Subject: [PATCH 23/23] feat: enhance NativeToolCallParser with additional cases for lesson recording and intent selection --- refactor_test.ts | 3 ++ .../assistant-message/NativeToolCallParser.ts | 52 +++++++++++++++++++ .../presentAssistantMessage.ts | 10 ++++ src/core/task/Task.ts | 31 +++++++---- tsconfig.json | 2 +- 5 files changed, 88 insertions(+), 10 deletions(-) create mode 100644 refactor_test.ts diff --git a/refactor_test.ts b/refactor_test.ts new file mode 100644 index 00000000000..2112d767ff1 --- /dev/null +++ b/refactor_test.ts @@ -0,0 +1,3 @@ +function test() { + console.log("Original content") +} diff --git a/src/core/assistant-message/NativeToolCallParser.ts b/src/core/assistant-message/NativeToolCallParser.ts index e0ea1383f17..fff25d51126 100644 --- a/src/core/assistant-message/NativeToolCallParser.ts +++ b/src/core/assistant-message/NativeToolCallParser.ts @@ -636,6 +636,42 @@ export class NativeToolCallParser { } break + case "select_active_intent": + if (partialArgs.intent_id !== undefined) { + nativeArgs = { + intent_id: partialArgs.intent_id, + } + } + break + + case "record_lesson_learned": + if (partialArgs.lesson !== undefined) { + nativeArgs = { + lesson: partialArgs.lesson, + } + } + break + + case "access_mcp_resource": + if (partialArgs.server_name !== undefined || partialArgs.uri !== undefined) { + nativeArgs = { + server_name: partialArgs.server_name, + uri: partialArgs.uri, + } + } + break + + case "read_command_output": + if (partialArgs.artifact_id !== undefined) { + nativeArgs = { + artifact_id: partialArgs.artifact_id, + search: partialArgs.search, + offset: this.coerceOptionalNumber(partialArgs.offset), + limit: this.coerceOptionalNumber(partialArgs.limit), + } + } + break + default: break } @@ -965,6 +1001,14 @@ export class NativeToolCallParser { } break + case "record_lesson_learned": + if (args.lesson !== undefined) { + nativeArgs = { + lesson: args.lesson, + } as NativeArgsFor + } + break + case "list_files": if (args.path !== undefined) { nativeArgs = { @@ -984,6 +1028,14 @@ export class NativeToolCallParser { } break + case "select_active_intent": + if (args.intent_id !== undefined) { + nativeArgs = { + intent_id: args.intent_id, + } as NativeArgsFor + } + break + default: if (customToolRegistry.has(resolvedName)) { nativeArgs = args as NativeArgsFor diff --git a/src/core/assistant-message/presentAssistantMessage.ts b/src/core/assistant-message/presentAssistantMessage.ts index 7287cfac4c1..74b02e96953 100644 --- a/src/core/assistant-message/presentAssistantMessage.ts +++ b/src/core/assistant-message/presentAssistantMessage.ts @@ -35,6 +35,7 @@ import { runSlashCommandTool } from "../tools/RunSlashCommandTool" import { skillTool } from "../tools/SkillTool" import { generateImageTool } from "../tools/GenerateImageTool" import { selectActiveIntentTool } from "../tools/SelectActiveIntentTool" +import { recordLessonTool } from "../tools/RecordLessonTool" import { applyDiffTool as applyDiffToolClass } from "../tools/ApplyDiffTool" import { isValidToolName, validateToolUse } from "../tools/validateToolUse" import { codebaseSearchTool } from "../tools/CodebaseSearchTool" @@ -681,6 +682,8 @@ export async function presentAssistantMessage(cline: Task) { } // 4. Hook Engine Orchestration + await cline.initializationPromise + const hookContext: HookContext = { task: cline, toolName: block.name, @@ -816,6 +819,13 @@ export async function presentAssistantMessage(cline: Task) { pushToolResult, }) break + case "record_lesson_learned": + await recordLessonTool.handle(cline, block as ToolUse<"record_lesson_learned">, { + askApproval, + handleError, + pushToolResult, + }) + break case "access_mcp_resource": await accessMcpResourceTool.handle(cline, block as ToolUse<"access_mcp_resource">, { askApproval, diff --git a/src/core/task/Task.ts b/src/core/task/Task.ts index 9f543b2a7c8..1bcaae2071e 100644 --- a/src/core/task/Task.ts +++ b/src/core/task/Task.ts @@ -107,6 +107,9 @@ import { HookEngine } from "../../hooks/HookEngine" import { IntentValidationHook } from "../../hooks/pre/IntentValidationHook" import { ScopeEnforcementHook } from "../../hooks/pre/ScopeEnforcementHook" import { TraceLoggingHook } from "../../hooks/post/TraceLoggingHook" +import { ConcurrencyPreHook } from "../../hooks/pre/ConcurrencyPreHook" +import { ConcurrencyPostHook } from "../../hooks/post/ConcurrencyPostHook" +import { VerificationLessonHook } from "../../hooks/post/VerificationLessonHook" import { AssistantMessageContent, presentAssistantMessage } from "../assistant-message" import { NativeToolCallParser } from "../assistant-message/NativeToolCallParser" import { manageContext, willManageContext } from "../context-management" @@ -225,6 +228,11 @@ export class Task extends EventEmitter implements TaskLike { * @see {@link waitForModeInitialization} - Public method to await this promise */ private taskModeReady: Promise + /** + * Promise that resolves when all core controllers (Ignore, Protected, Intent) + * have been fully initialized. Ensures governance hooks have required state. + */ + public readonly initializationPromise: Promise /** * The API configuration name (provider profile) associated with this task. @@ -490,19 +498,24 @@ export class Task extends EventEmitter implements TaskLike { this.rooProtectedController = new RooProtectedController(this.cwd) this.fileContextTracker = new FileContextTracker(provider, this.taskId) - this.rooIgnoreController.initialize().catch((error) => { - console.error("Failed to initialize RooIgnoreController:", error) - }) - - this.intentController = new IntentController(this.cwd) - this.intentController.initialize().catch((error) => { - console.error("Failed to initialize IntentController:", error) - }) + // Controllers must be initialized before hooks run to prevent race conditions + this.initializationPromise = Promise.all([ + this.rooIgnoreController.initialize().catch((error) => { + console.error("Failed to initialize RooIgnoreController:", error) + }), + (this.intentController = new IntentController(this.cwd)), + this.intentController.initialize().catch((error) => { + console.error("Failed to initialize IntentController:", error) + }), + ]).then(() => {}) this.hookEngine = new HookEngine() this.hookEngine.registerHook(new IntentValidationHook()) this.hookEngine.registerHook(new ScopeEnforcementHook()) + this.hookEngine.registerHook(new ConcurrencyPreHook()) this.hookEngine.registerHook(new TraceLoggingHook()) + this.hookEngine.registerHook(new ConcurrencyPostHook()) + this.hookEngine.registerHook(new VerificationLessonHook()) this.apiConfiguration = apiConfiguration this.api = buildApiHandler(this.apiConfiguration) @@ -3832,7 +3845,7 @@ export class Task extends EventEmitter implements TaskLike { undefined, // todoList this.api.getModel().id, provider.getSkillsManager(), - this.intentController.getAllIntents(), + this.intentController.getAllActiveIntents(), ) })() } diff --git a/tsconfig.json b/tsconfig.json index 44f734c197a..14ea9d8cf5a 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,5 +4,5 @@ "types": ["node"] }, "exclude": ["node_modules"], - "include": ["scripts/*.ts"] + "include": ["scripts/*.ts", "*.ts"] }