Fix structured strategy schema

This commit is contained in:
centra 2026-04-04 16:08:27 +09:00
parent a909a99310
commit 0b5f07a7d2
Signed by: centra
GPG key ID: 0C09689D20B25ACA
5 changed files with 34 additions and 9 deletions

View file

@ -12,13 +12,14 @@ The CLI persists state in SQLite plus run artifacts under `.agent-r/`, so a long
```bash ```bash
npm install 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 npm run build
agent-r resume <run-id> --repo /path/to/repo ./dist/index.js run "Build a plugin system" --repo /path/to/repo
agent-r status <run-id> --repo /path/to/repo ./dist/index.js resume <run-id> --repo /path/to/repo
agent-r inspect <run-id> --repo /path/to/repo ./dist/index.js status <run-id> --repo /path/to/repo
agent-r logs <run-id> --repo /path/to/repo --agent strategy ./dist/index.js inspect <run-id> --repo /path/to/repo
./dist/index.js logs <run-id> --repo /path/to/repo --agent strategy
``` ```
## Config ## Config

View file

@ -126,6 +126,7 @@ Decision policy:
- Use \`decision: "continue"\` when there is meaningful work left. Provide the complete next backlog. - 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: "done"\` only when no substantial work remains.
- Use \`decision: "blocked"\` only for external blockers or unresolved contradictions. - 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. - Keep the backlog concise. Prefer a few substantive tasks over many trivial tasks.
`.trim(); `.trim();
} }

View file

@ -23,7 +23,7 @@ export const taskDraftSchema = {
export const strategyPlanSchema = { export const strategyPlanSchema = {
type: "object", type: "object",
additionalProperties: false, additionalProperties: false,
required: ["decision", "summary", "rationale", "goalProgress", "risks", "tasks"], required: ["decision", "summary", "rationale", "goalProgress", "risks", "tasks", "blockedReason"],
properties: { properties: {
decision: { decision: {
type: "string", type: "string",
@ -40,7 +40,9 @@ export const strategyPlanSchema = {
type: "array", type: "array",
items: taskDraftSchema, items: taskDraftSchema,
}, },
blockedReason: { type: "string" }, blockedReason: {
type: ["string", "null"],
},
}, },
} as const; } as const;

View file

@ -142,7 +142,7 @@ export interface StrategyPlanOutput {
goalProgress: string; goalProgress: string;
risks: string[]; risks: string[];
tasks: TaskDraft[]; tasks: TaskDraft[];
blockedReason?: string; blockedReason: string | null;
} }
export interface StrategySelfCheckOutput { export interface StrategySelfCheckOutput {

View file

@ -5,6 +5,7 @@ import { describe, expect, test } from "vitest";
import { ArtifactManager } from "../src/artifacts.js"; import { ArtifactManager } from "../src/artifacts.js";
import { loadConfig } from "../src/config.js"; import { loadConfig } from "../src/config.js";
import { AgentROrchestrator } from "../src/orchestrator.js"; import { AgentROrchestrator } from "../src/orchestrator.js";
import { strategyPlanSchema } from "../src/schema-catalog.js";
import { RunStore } from "../src/store.js"; import { RunStore } from "../src/store.js";
import type { AgentInvocation, AgentInvocationResult, RawAgentRunner } from "../src/types.js"; import type { AgentInvocation, AgentInvocationResult, RawAgentRunner } from "../src/types.js";
@ -63,6 +64,7 @@ describe("AgentROrchestrator", () => {
allowedPaths: ["src", "package.json"], allowedPaths: ["src", "package.json"],
}, },
], ],
blockedReason: null,
}), }),
JSON.stringify({ JSON.stringify({
taskId: "", taskId: "",
@ -81,6 +83,7 @@ describe("AgentROrchestrator", () => {
goalProgress: "The vertical slice exists.", goalProgress: "The vertical slice exists.",
risks: [], risks: [],
tasks: [], tasks: [],
blockedReason: null,
}), }),
JSON.stringify({ JSON.stringify({
readyForIndependentCheck: true, readyForIndependentCheck: true,
@ -135,4 +138,22 @@ describe("AgentROrchestrator", () => {
expect(runner.prompts[0]).toContain("You may inspect the repository directly"); expect(runner.prompts[0]).toContain("You may inspect the repository directly");
expect(runner.prompts[0]).toContain("Do not edit files"); 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<string, unknown>;
expect(strategyPlanSchema.required).toContain("blockedReason");
expect(strategyPlanSchema.properties.blockedReason.type).toEqual(["string", "null"]);
expect(valid.blockedReason).toBeNull();
});
}); });