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();
+ });
});