diff --git a/.orchestration/agent_trace.jsonl b/.orchestration/agent_trace.jsonl index 48c0a71402..297c6c1735 100644 --- a/.orchestration/agent_trace.jsonl +++ b/.orchestration/agent_trace.jsonl @@ -1,3 +1,3 @@ -{"id":"df1a0b04-1993-45f4-b3cb-c0b4fcb2dba2","timestamp":"2026-02-21T08:37:15.794Z","vcs":{"revision_id":"abc123"},"files":[{"relative_path":"src/api/weather.ts","conversations":[{"url":"session-1","contributor":{"entity_type":"AI","model_identifier":"test-model"},"ranges":[{"start_line":1,"end_line":2,"content_hash":"sha256:2e4ec5cf14cfeb55ef32cb663ffd2c4fe6ab22bb2d2fe77ee8e284fe62866cac"}],"related":[{"type":"specification","value":"INT-001"}]}]}]} -{"id":"7f9541c8-7fd4-4cef-ae44-c51f191b942a","timestamp":"2026-02-21T08:37:15.797Z","files":[{"relative_path":"src/api/forecast.ts","conversations":[{"contributor":{"entity_type":"AI","model_identifier":"unknown"},"ranges":[{"start_line":1,"end_line":1,"content_hash":"sha256:c47272be41ffa9d8a897b81b3f9f6d5e13fbb5d6dd4609a7b20c282950f11842"}],"related":[{"type":"specification","value":"INT-001"}]}]}]} -{"id":"da60914f-8e16-4c59-8c5d-2c673793287d","timestamp":"2026-02-21T08:37:15.803Z","vcs":{"revision_id":"rev-1"},"files":[{"relative_path":"src/api/demo.ts","conversations":[{"url":"log-1","contributor":{"entity_type":"AI","model_identifier":"model-1"},"ranges":[{"start_line":1,"end_line":1,"content_hash":"sha256:bd994c26b795571c7c1e0357cba348f1d0238b648b748b9ec7a4d0de264d212f"}],"related":[{"type":"specification","value":"INT-001"}]}]}]} +{"id":"02a7c124-4730-4e49-8783-08f861f640ba","timestamp":"2026-02-21T09:48:48.707Z","vcs":{"revision_id":"abc123"},"files":[{"relative_path":"src/api/weather.ts","conversations":[{"url":"session-1","contributor":{"entity_type":"AI","model_identifier":"test-model"},"ranges":[{"start_line":1,"end_line":2,"content_hash":"sha256:2e4ec5cf14cfeb55ef32cb663ffd2c4fe6ab22bb2d2fe77ee8e284fe62866cac"}],"related":[{"type":"specification","value":"INT-001"}]}]}]} +{"id":"e55e9867-663a-412e-b341-af61f85dbedd","timestamp":"2026-02-21T09:48:48.716Z","files":[{"relative_path":"src/api/forecast.ts","conversations":[{"contributor":{"entity_type":"AI","model_identifier":"unknown"},"ranges":[{"start_line":1,"end_line":1,"content_hash":"sha256:c47272be41ffa9d8a897b81b3f9f6d5e13fbb5d6dd4609a7b20c282950f11842"}],"related":[{"type":"specification","value":"INT-001"}]}]}]} +{"id":"3609a24b-418e-4e1a-a554-2e3cf9db1be9","timestamp":"2026-02-21T09:48:48.737Z","vcs":{"revision_id":"rev-1"},"files":[{"relative_path":"src/api/demo.ts","conversations":[{"url":"log-1","contributor":{"entity_type":"AI","model_identifier":"model-1"},"ranges":[{"start_line":1,"end_line":1,"content_hash":"sha256:bd994c26b795571c7c1e0357cba348f1d0238b648b748b9ec7a4d0de264d212f"}],"related":[{"type":"specification","value":"INT-001"}]}]}]} diff --git a/scripts/test_phase1.js b/scripts/test_phase1.js new file mode 100644 index 0000000000..0f4c0bb6bd --- /dev/null +++ b/scripts/test_phase1.js @@ -0,0 +1,47 @@ +const fs = require("fs") +const path = require("path") + +// Load active intents +const intentsPath = path.join(__dirname, "..", ".orchestration", "active_intents.yaml") +const yaml = require("js-yaml") +const intents = yaml.load(fs.readFileSync(intentsPath, "utf8")).active_intents + +// Simple PreHook simulation +function preHook(intentId, targetFile) { + const intent = intents.find((i) => i.id === intentId) + if (!intent) { + throw new Error("You must select a valid active Intent ID before writing code.") + } + + const allowed = intent.owned_scope.some((scope) => targetFile.startsWith(scope.replace("**", ""))) + if (!allowed) { + throw new Error(`Scope Violation: ${intentId} is not authorized to edit ${targetFile}`) + } + + console.log(`PreHook Passed: ${targetFile} is within scope for ${intentId}`) +} + +// Simulate AI writing a file +function writeFile(intentId, filePath, content) { + preHook(intentId, filePath) + fs.writeFileSync(filePath, content) + console.log(`File written successfully: ${filePath}`) +} + +// === TEST CASES === +try { + // Valid case + writeFile("INT-001", "src/api/weather.ts", "// Weather API code here") + + // Invalid scope + writeFile("INT-001", "src/db/db.ts", "// DB code here") +} catch (err) { + console.error("Error:", err.message) +} + +try { + // Invalid intent + writeFile("INT-999", "src/api/weather.ts", "// Should fail") +} catch (err) { + console.error("Error:", err.message) +} diff --git a/src/api/weather.ts b/src/api/weather.ts new file mode 100644 index 0000000000..a442555ef2 --- /dev/null +++ b/src/api/weather.ts @@ -0,0 +1 @@ +// Weather API code here diff --git a/src/core/prompts/system.ts b/src/core/prompts/system.ts index 0d6071644a..de103ea8c3 100644 --- a/src/core/prompts/system.ts +++ b/src/core/prompts/system.ts @@ -10,6 +10,7 @@ import { isEmpty } from "../../utils/object" import { McpHub } from "../../services/mcp/McpHub" import { CodeIndexManager } from "../../services/code-index/manager" import { SkillsManager } from "../../services/skills/SkillsManager" +import { INTENT_DRIVEN_PROMPT_SNIPPET } from "../../hooks/intent-prompt-snippet" import type { SystemPromptSettings } from "./types" import { @@ -84,6 +85,8 @@ async function generatePrompt( const basePrompt = `${roleDefinition} +${INTENT_DRIVEN_PROMPT_SNIPPET} + ${markdownFormattingSection()} ${getSharedToolUseSection()}${toolsCatalog} diff --git a/src/core/prompts/tools/native-tools/index.ts b/src/core/prompts/tools/native-tools/index.ts index 758914d2d6..624d1db084 100644 --- a/src/core/prompts/tools/native-tools/index.ts +++ b/src/core/prompts/tools/native-tools/index.ts @@ -1,4 +1,5 @@ import type OpenAI from "openai" +import { selectActiveIntentToolDefinition } from "../../../../hooks/select-active-intent-tool" import accessMcpResource from "./access_mcp_resource" import { apply_diff } from "./apply_diff" import applyPatch from "./apply_patch" @@ -47,6 +48,7 @@ export function getNativeTools(options: NativeToolsOptions = {}): OpenAI.Chat.Ch } return [ + selectActiveIntentToolDefinition, accessMcpResource, apply_diff, applyPatch, diff --git a/src/hooks/context-loader.ts b/src/hooks/context-loader.ts index 62063c4fc8..6c3591cf70 100644 --- a/src/hooks/context-loader.ts +++ b/src/hooks/context-loader.ts @@ -34,8 +34,9 @@ export async function loadIntentContext(cwd: string, intentId: string): Promise< /** * Build an XML block to inject as the tool result for select_active_intent. + * Optionally include related agent trace entries for consolidated context. */ -export function buildIntentContextXml(context: IntentContext): string { +export function buildIntentContextXml(context: IntentContext, relatedTracePaths: string[] = []): string { const constraintsXml = context.constraints.length > 0 ? context.constraints.map((c) => ` ${escapeXml(c)}`).join("\n") @@ -48,6 +49,10 @@ export function buildIntentContextXml(context: IntentContext): string { (context.acceptance_criteria?.length ?? 0) > 0 ? context.acceptance_criteria!.map((a) => ` ${escapeXml(a)}`).join("\n") : " None specified" + const traceXml = + relatedTracePaths.length > 0 + ? relatedTracePaths.map((p) => ` ${escapeXml(p)}`).join("\n") + : " None yet" return ` ${escapeXml(context.id)} @@ -62,9 +67,34 @@ ${scopeXml} ${criteriaXml} + +${traceXml} + ` } +/** + * Load intent from active_intents.yaml, gather related agent_trace entries for that intent, + * and return a consolidated XML context block (for Pre-Hook injection). + */ +export async function buildConsolidatedIntentContextXml(cwd: string, intentId: string): Promise { + const context = await loadIntentContext(cwd, intentId) + if (!context) return null + const traceLines = await readRecentTraceForIntent(cwd, intentId, 20) + const paths = new Set() + for (const line of traceLines) { + try { + const entry = JSON.parse(line) as { files?: Array<{ relative_path?: string }> } + for (const f of entry.files ?? []) { + if (f.relative_path) paths.add(f.relative_path) + } + } catch { + // skip malformed lines + } + } + return buildIntentContextXml(context, [...paths]) +} + function escapeXml(s: string): string { return s .replace(/&/g, "&") diff --git a/src/hooks/pre-hook.ts b/src/hooks/pre-hook.ts index be650829cf..e8dc7315c6 100644 --- a/src/hooks/pre-hook.ts +++ b/src/hooks/pre-hook.ts @@ -2,7 +2,7 @@ import path from "path" import type { HookResult } from "./types" import { DESTRUCTIVE_TOOLS } from "./types" -import { loadIntentContext, buildIntentContextXml } from "./context-loader" +import { loadIntentContext, buildConsolidatedIntentContextXml } from "./context-loader" import { pathInScope } from "./scope" /** Block paths that escape workspace (.. or absolute outside cwd). */ @@ -41,15 +41,14 @@ export class PreHook { if (!intentId) { return { blocked: true, error: "You must provide a valid intent_id when calling select_active_intent." } } - const context = await loadIntentContext(cwd, intentId) - if (!context) { + const xml = await buildConsolidatedIntentContextXml(cwd, intentId) + if (!xml) { return { blocked: true, error: `You must cite a valid active Intent ID. Intent "${intentId}" was not found in .orchestration/active_intents.yaml.`, } } setActiveIntentId(intentId) - const xml = buildIntentContextXml(context) return { blocked: false, injectResult: xml } }