From 0b5f07a7d2808134849b79a30545cec1e11a5327 Mon Sep 17 00:00:00 2001 From: centra Date: Sat, 4 Apr 2026 16:08:27 +0900 Subject: [PATCH] Fix structured strategy schema --- README.md | 13 +++++++------ src/prompts.ts | 1 + src/schema-catalog.ts | 6 ++++-- src/types.ts | 2 +- test/orchestrator.test.ts | 21 +++++++++++++++++++++ 5 files changed, 34 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 066f36b..fd8ccf0 100644 --- a/README.md +++ b/README.md @@ -12,13 +12,14 @@ The CLI persists state in SQLite plus run artifacts under `.agent-r/`, so a long ```bash npm install -npm run build +npm run dev -- run "Build a plugin system" --repo /path/to/repo -agent-r run "Build a plugin system" --repo /path/to/repo -agent-r resume --repo /path/to/repo -agent-r status --repo /path/to/repo -agent-r inspect --repo /path/to/repo -agent-r logs --repo /path/to/repo --agent strategy +npm run build +./dist/index.js run "Build a plugin system" --repo /path/to/repo +./dist/index.js resume --repo /path/to/repo +./dist/index.js status --repo /path/to/repo +./dist/index.js inspect --repo /path/to/repo +./dist/index.js logs --repo /path/to/repo --agent strategy ``` ## Config diff --git a/src/prompts.ts b/src/prompts.ts index 12fb572..e9ee383 100644 --- a/src/prompts.ts +++ b/src/prompts.ts @@ -126,6 +126,7 @@ Decision policy: - Use \`decision: "continue"\` when there is meaningful work left. Provide the complete next backlog. - Use \`decision: "done"\` only when no substantial work remains. - Use \`decision: "blocked"\` only for external blockers or unresolved contradictions. +- Always include \`blockedReason\`. Use \`null\` unless \`decision\` is \`"blocked"\`. - Keep the backlog concise. Prefer a few substantive tasks over many trivial tasks. `.trim(); } diff --git a/src/schema-catalog.ts b/src/schema-catalog.ts index f82b43b..ef29a8e 100644 --- a/src/schema-catalog.ts +++ b/src/schema-catalog.ts @@ -23,7 +23,7 @@ export const taskDraftSchema = { export const strategyPlanSchema = { type: "object", additionalProperties: false, - required: ["decision", "summary", "rationale", "goalProgress", "risks", "tasks"], + required: ["decision", "summary", "rationale", "goalProgress", "risks", "tasks", "blockedReason"], properties: { decision: { type: "string", @@ -40,7 +40,9 @@ export const strategyPlanSchema = { type: "array", items: taskDraftSchema, }, - blockedReason: { type: "string" }, + blockedReason: { + type: ["string", "null"], + }, }, } as const; diff --git a/src/types.ts b/src/types.ts index 0622862..98cb65a 100644 --- a/src/types.ts +++ b/src/types.ts @@ -142,7 +142,7 @@ export interface StrategyPlanOutput { goalProgress: string; risks: string[]; tasks: TaskDraft[]; - blockedReason?: string; + blockedReason: string | null; } export interface StrategySelfCheckOutput { diff --git a/test/orchestrator.test.ts b/test/orchestrator.test.ts index 48c7663..b23f51b 100644 --- a/test/orchestrator.test.ts +++ b/test/orchestrator.test.ts @@ -5,6 +5,7 @@ import { describe, expect, test } from "vitest"; import { ArtifactManager } from "../src/artifacts.js"; import { loadConfig } from "../src/config.js"; import { AgentROrchestrator } from "../src/orchestrator.js"; +import { strategyPlanSchema } from "../src/schema-catalog.js"; import { RunStore } from "../src/store.js"; import type { AgentInvocation, AgentInvocationResult, RawAgentRunner } from "../src/types.js"; @@ -63,6 +64,7 @@ describe("AgentROrchestrator", () => { allowedPaths: ["src", "package.json"], }, ], + blockedReason: null, }), JSON.stringify({ taskId: "", @@ -81,6 +83,7 @@ describe("AgentROrchestrator", () => { goalProgress: "The vertical slice exists.", risks: [], tasks: [], + blockedReason: null, }), JSON.stringify({ readyForIndependentCheck: true, @@ -135,4 +138,22 @@ describe("AgentROrchestrator", () => { expect(runner.prompts[0]).toContain("You may inspect the repository directly"); expect(runner.prompts[0]).toContain("Do not edit files"); }); + + test("strategy plan schema requires blockedReason and accepts null", () => { + const valid = JSON.parse( + JSON.stringify({ + decision: "continue", + summary: "Keep going.", + rationale: "Work remains.", + goalProgress: "Partial.", + risks: [], + tasks: [], + blockedReason: null, + }), + ) as Record; + + expect(strategyPlanSchema.required).toContain("blockedReason"); + expect(strategyPlanSchema.properties.blockedReason.type).toEqual(["string", "null"]); + expect(valid.blockedReason).toBeNull(); + }); });