goalbuddy 0.3.2 → 0.3.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +55 -5
- package/RELEASE-0.3.5.md +324 -0
- package/goalbuddy/SKILL.md +40 -13
- package/goalbuddy/agents/README.md +1 -1
- package/goalbuddy/agents/goal_judge.toml +33 -17
- package/goalbuddy/agents/goal_scout.toml +34 -14
- package/goalbuddy/agents/goal_worker.toml +36 -16
- package/goalbuddy/extend/local-goal-board/README.md +8 -4
- package/goalbuddy/extend/local-goal-board/examples/subgoal-parent/goal.md +3 -0
- package/goalbuddy/extend/local-goal-board/examples/subgoal-parent/notes/.gitkeep +1 -0
- package/goalbuddy/extend/local-goal-board/examples/subgoal-parent/state.yaml +60 -0
- package/goalbuddy/extend/local-goal-board/examples/subgoal-parent/subgoals/T004-board-view/goal.md +3 -0
- package/goalbuddy/extend/local-goal-board/examples/subgoal-parent/subgoals/T004-board-view/notes/.gitkeep +1 -0
- package/goalbuddy/extend/local-goal-board/examples/subgoal-parent/subgoals/T004-board-view/state.yaml +52 -0
- package/goalbuddy/extend/local-goal-board/extension.yaml +6 -4
- package/goalbuddy/extend/local-goal-board/scripts/lib/goal-board.mjs +1188 -31
- package/goalbuddy/extend/local-goal-board/scripts/local-goal-board.mjs +389 -54
- package/goalbuddy/extend/local-goal-board/test/local-goal-board.test.mjs +479 -5
- package/goalbuddy/scripts/check-goal-state.mjs +192 -6
- package/goalbuddy/scripts/parallel-plan.mjs +191 -0
- package/goalbuddy/scripts/render-task-prompt.mjs +305 -0
- package/goalbuddy/templates/agents.md +5 -4
- package/goalbuddy/templates/goal.md +18 -4
- package/goalbuddy/templates/state.yaml +14 -1
- package/internal/assets/goalbuddy-v0.3.5-release.png +0 -0
- package/internal/cli/goal-maker.mjs +172 -9
- package/package.json +3 -2
- package/plugins/goalbuddy/.claude-plugin/plugin.json +2 -2
- package/plugins/goalbuddy/.codex-plugin/plugin.json +4 -4
- package/plugins/goalbuddy/README.md +5 -3
- package/plugins/goalbuddy/agents/goal-judge.md +35 -16
- package/plugins/goalbuddy/agents/goal-scout.md +38 -13
- package/plugins/goalbuddy/agents/goal-worker.md +37 -14
- package/plugins/goalbuddy/skills/goalbuddy/SKILL.md +40 -13
- package/plugins/goalbuddy/skills/goalbuddy/agents/README.md +1 -1
- package/plugins/goalbuddy/skills/goalbuddy/agents/goal_judge.toml +33 -17
- package/plugins/goalbuddy/skills/goalbuddy/agents/goal_scout.toml +34 -14
- package/plugins/goalbuddy/skills/goalbuddy/agents/goal_worker.toml +36 -16
- package/plugins/goalbuddy/skills/goalbuddy/extend/local-goal-board/README.md +8 -4
- package/plugins/goalbuddy/skills/goalbuddy/extend/local-goal-board/examples/subgoal-parent/goal.md +3 -0
- package/plugins/goalbuddy/skills/goalbuddy/extend/local-goal-board/examples/subgoal-parent/notes/.gitkeep +1 -0
- package/plugins/goalbuddy/skills/goalbuddy/extend/local-goal-board/examples/subgoal-parent/state.yaml +60 -0
- package/plugins/goalbuddy/skills/goalbuddy/extend/local-goal-board/examples/subgoal-parent/subgoals/T004-board-view/goal.md +3 -0
- package/plugins/goalbuddy/skills/goalbuddy/extend/local-goal-board/examples/subgoal-parent/subgoals/T004-board-view/notes/.gitkeep +1 -0
- package/plugins/goalbuddy/skills/goalbuddy/extend/local-goal-board/examples/subgoal-parent/subgoals/T004-board-view/state.yaml +52 -0
- package/plugins/goalbuddy/skills/goalbuddy/extend/local-goal-board/extension.yaml +6 -4
- package/plugins/goalbuddy/skills/goalbuddy/extend/local-goal-board/scripts/lib/goal-board.mjs +1188 -31
- package/plugins/goalbuddy/skills/goalbuddy/extend/local-goal-board/scripts/local-goal-board.mjs +389 -54
- package/plugins/goalbuddy/skills/goalbuddy/extend/local-goal-board/test/local-goal-board.test.mjs +479 -5
- package/plugins/goalbuddy/skills/goalbuddy/scripts/check-goal-state.mjs +192 -6
- package/plugins/goalbuddy/skills/goalbuddy/scripts/parallel-plan.mjs +191 -0
- package/plugins/goalbuddy/skills/goalbuddy/scripts/render-task-prompt.mjs +305 -0
- package/plugins/goalbuddy/skills/goalbuddy/templates/agents.md +5 -4
- package/plugins/goalbuddy/skills/goalbuddy/templates/goal.md +18 -4
- package/plugins/goalbuddy/skills/goalbuddy/templates/state.yaml +14 -1
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
3
|
+
import { basename, dirname, resolve } from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { parseGoalStateText } from "../extend/local-goal-board/scripts/lib/goal-board.mjs";
|
|
6
|
+
|
|
7
|
+
const ROLE_DEFAULTS = {
|
|
8
|
+
scout: { agent: "goal_scout", reasoning: "low", sandbox: "read-only" },
|
|
9
|
+
judge: { agent: "goal_judge", reasoning: "high", sandbox: "read-only" },
|
|
10
|
+
worker: { agent: "goal_worker", reasoning: "medium", sandbox: "workspace-write" },
|
|
11
|
+
pm: { agent: "PM", reasoning: "medium", sandbox: "workspace-write" },
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
if (isDirectRun()) {
|
|
15
|
+
try {
|
|
16
|
+
const result = renderTaskPrompt(parseArgs(process.argv.slice(2)));
|
|
17
|
+
if (result.json) {
|
|
18
|
+
console.log(JSON.stringify(result.payload, null, 2));
|
|
19
|
+
} else {
|
|
20
|
+
console.log(formatPrompt(result.payload));
|
|
21
|
+
}
|
|
22
|
+
} catch (error) {
|
|
23
|
+
console.error(error.message);
|
|
24
|
+
process.exitCode = 1;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function renderTaskPrompt(options) {
|
|
29
|
+
const boardPath = resolveBoardPath(options);
|
|
30
|
+
const board = loadBoard(boardPath);
|
|
31
|
+
const task = selectTask(board, options.taskId);
|
|
32
|
+
const role = normalizeRole(task.type);
|
|
33
|
+
const defaults = ROLE_DEFAULTS[role] || ROLE_DEFAULTS.pm;
|
|
34
|
+
const reasoning = normalizeReasoning(task.reasoning_hint, defaults.reasoning);
|
|
35
|
+
const warnings = promptWarnings(board, task);
|
|
36
|
+
|
|
37
|
+
return {
|
|
38
|
+
json: options.json,
|
|
39
|
+
payload: {
|
|
40
|
+
metadata: {
|
|
41
|
+
recommended_agent: defaults.agent,
|
|
42
|
+
required_spawn_agent_type: defaults.agent === "PM" ? null : defaults.agent,
|
|
43
|
+
recommended_reasoning: reasoning,
|
|
44
|
+
sandbox: defaults.sandbox,
|
|
45
|
+
fork_context_allowed: role !== "worker",
|
|
46
|
+
board_path: board.path,
|
|
47
|
+
child_board_paths: childBoardPaths(board),
|
|
48
|
+
slice_policy: board.document.rules?.slice_policy || null,
|
|
49
|
+
warnings,
|
|
50
|
+
},
|
|
51
|
+
task: {
|
|
52
|
+
id: task.id,
|
|
53
|
+
type: role,
|
|
54
|
+
assignee: task.assignee || defaults.agent,
|
|
55
|
+
status: task.status,
|
|
56
|
+
objective: task.objective || "",
|
|
57
|
+
inputs: stringList(task.inputs),
|
|
58
|
+
constraints: stringList(task.constraints),
|
|
59
|
+
allowed_files: stringList(task.allowed_files),
|
|
60
|
+
verify: stringList(task.verify),
|
|
61
|
+
stop_if: stringList(task.stop_if),
|
|
62
|
+
reasoning_hint: task.reasoning_hint || null,
|
|
63
|
+
expected_output: stringList(task.expected_output),
|
|
64
|
+
},
|
|
65
|
+
receipt_schema: receiptSchema(role),
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function parseArgs(args) {
|
|
71
|
+
const options = { goalRoot: "", boardPath: "", taskId: "", json: false };
|
|
72
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
73
|
+
const arg = args[index];
|
|
74
|
+
if (arg === "--json") {
|
|
75
|
+
options.json = true;
|
|
76
|
+
} else if (arg === "--task") {
|
|
77
|
+
options.taskId = args[++index] || "";
|
|
78
|
+
} else if (arg.startsWith("--task=")) {
|
|
79
|
+
options.taskId = arg.slice("--task=".length);
|
|
80
|
+
} else if (arg === "--board") {
|
|
81
|
+
options.boardPath = args[++index] || "";
|
|
82
|
+
} else if (arg.startsWith("--board=")) {
|
|
83
|
+
options.boardPath = arg.slice("--board=".length);
|
|
84
|
+
} else if (arg === "--parallel-plan") {
|
|
85
|
+
options.parallelPlan = true;
|
|
86
|
+
} else if (arg.startsWith("-")) {
|
|
87
|
+
throw new Error(`Unknown argument: ${arg}`);
|
|
88
|
+
} else if (!options.goalRoot) {
|
|
89
|
+
options.goalRoot = arg;
|
|
90
|
+
} else {
|
|
91
|
+
throw new Error(`Unexpected argument: ${arg}`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
if (!options.goalRoot && !options.boardPath) {
|
|
95
|
+
throw new Error("Usage: goalbuddy prompt <goal-root> [--task T###] [--board path/to/state.yaml]");
|
|
96
|
+
}
|
|
97
|
+
return options;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function loadBoard(boardPath) {
|
|
101
|
+
if (!existsSync(boardPath)) throw new Error(`state file not found: ${boardPath}`);
|
|
102
|
+
const document = parseGoalStateText(readFileSync(boardPath, "utf8"));
|
|
103
|
+
if (!document || Number(document.version) !== 2) {
|
|
104
|
+
throw new Error(`unsupported GoalBuddy state version in ${boardPath}`);
|
|
105
|
+
}
|
|
106
|
+
if (!Array.isArray(document.tasks)) throw new Error(`state file has no tasks: ${boardPath}`);
|
|
107
|
+
return {
|
|
108
|
+
path: boardPath,
|
|
109
|
+
root: dirname(boardPath),
|
|
110
|
+
document,
|
|
111
|
+
tasks: document.tasks,
|
|
112
|
+
goal: document.goal || {},
|
|
113
|
+
activeTask: document.active_task || "",
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export function resolveBoardPath(options) {
|
|
118
|
+
const candidate = options.boardPath || options.goalRoot;
|
|
119
|
+
if (!candidate) throw new Error("Missing goal root or board path.");
|
|
120
|
+
const resolved = resolve(candidate);
|
|
121
|
+
if (basename(resolved) === "state.yaml") return resolved;
|
|
122
|
+
return resolve(resolved, "state.yaml");
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export function selectTask(board, taskId = "") {
|
|
126
|
+
const id = taskId || board.activeTask;
|
|
127
|
+
if (!id) throw new Error(`No task selected and active_task is empty in ${board.path}`);
|
|
128
|
+
const task = board.tasks.find((candidate) => candidate?.id === id);
|
|
129
|
+
if (!task) throw new Error(`Task ${id} not found in ${board.path}`);
|
|
130
|
+
return task;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export function childBoardPaths(board) {
|
|
134
|
+
return board.tasks
|
|
135
|
+
.map((task) => task?.subgoal?.path)
|
|
136
|
+
.filter(Boolean)
|
|
137
|
+
.map((childPath) => resolve(board.root, childPath));
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function promptWarnings(board, task) {
|
|
141
|
+
const warnings = [];
|
|
142
|
+
const role = normalizeRole(task.type);
|
|
143
|
+
if (task.id !== board.activeTask) warnings.push(`Task ${task.id} is not the active task on this board.`);
|
|
144
|
+
if (role === "worker") {
|
|
145
|
+
if (stringList(task.allowed_files).length === 0) warnings.push(`Worker task ${task.id} has no allowed_files.`);
|
|
146
|
+
if (stringList(task.verify).length === 0) warnings.push(`Worker task ${task.id} has no verify commands.`);
|
|
147
|
+
if (stringList(task.stop_if).length === 0) warnings.push(`Worker task ${task.id} has no stop_if conditions.`);
|
|
148
|
+
if (isFalse(board.goal.full_outcome_complete)) {
|
|
149
|
+
warnings.push(`full_outcome_complete is false and ${task.id} is an active Worker; do not stop after rendering or repairing the board. Execute the Worker unless a stop_if condition applies.`);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
for (const candidate of board.tasks) {
|
|
153
|
+
if (candidate?.subgoal && Number(candidate.subgoal.depth) !== 1) {
|
|
154
|
+
warnings.push(`Task ${candidate.id} has subgoal.depth ${candidate.subgoal.depth || "<missing>"}; only depth 1 is supported.`);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
warnings.push(...microSliceWarnings(board, task));
|
|
158
|
+
return warnings;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function microSliceWarnings(board, task) {
|
|
162
|
+
const warnings = [];
|
|
163
|
+
const doneTasks = board.tasks.filter((candidate) => candidate?.status === "done");
|
|
164
|
+
const recentWorkers = board.tasks
|
|
165
|
+
.filter((candidate) => normalizeRole(candidate?.type) === "worker")
|
|
166
|
+
.slice(-5);
|
|
167
|
+
const recentTinyWorkers = recentWorkers.filter((candidate) => isTinyTask(candidate));
|
|
168
|
+
const activeRole = normalizeRole(task.type);
|
|
169
|
+
const activeAllowedFiles = stringList(task.allowed_files);
|
|
170
|
+
const firstMilestoneComplete = isTrue(board.goal.first_milestone_complete);
|
|
171
|
+
const microWarning = "Board may be micro-slicing. Prefer the largest safe useful slice.";
|
|
172
|
+
|
|
173
|
+
if (recentTinyWorkers.length >= 3) warnings.push(microWarning);
|
|
174
|
+
if (doneTasks.length >= 10 && activeRole === "worker" && activeAllowedFiles.length > 0 && activeAllowedFiles.length <= 2) {
|
|
175
|
+
warnings.push(`${microWarning} Active Worker ${task.id} has only ${activeAllowedFiles.length} allowed_files after ${doneTasks.length} completed tasks.`);
|
|
176
|
+
}
|
|
177
|
+
if (firstMilestoneComplete && activeRole === "worker" && isTinyTask(task)) {
|
|
178
|
+
warnings.push(`${microWarning} The first milestone is complete, so the active Worker should move toward the next real milestone.`);
|
|
179
|
+
}
|
|
180
|
+
if (activeRole === "judge" && /pick small reviewable work|select one narrow next task/i.test(String(task.objective || "") + "\n" + stringList(task.constraints).join("\n"))) {
|
|
181
|
+
warnings.push(`${microWarning} Judge instructions still ask for small or narrow work.`);
|
|
182
|
+
}
|
|
183
|
+
return [...new Set(warnings)];
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function isTinyTask(task) {
|
|
187
|
+
const text = [
|
|
188
|
+
task?.objective,
|
|
189
|
+
stringList(task?.constraints).join(" "),
|
|
190
|
+
task?.receipt?.summary,
|
|
191
|
+
].join(" ").toLowerCase();
|
|
192
|
+
return /\b(tiny|narrow|single helper|one helper|projection helper|projection function|contract file|read-only proof|doc note|validator|validation wrapper|pure helper|caller-input)\b/.test(text);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function normalizeRole(value) {
|
|
196
|
+
const role = String(value || "pm").toLowerCase();
|
|
197
|
+
return ROLE_DEFAULTS[role] ? role : "pm";
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function normalizeReasoning(value, fallback) {
|
|
201
|
+
const hint = String(value || "").toLowerCase();
|
|
202
|
+
if (["low", "medium", "high", "xhigh"].includes(hint)) return hint;
|
|
203
|
+
return fallback;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function isFalse(value) {
|
|
207
|
+
return value === false || String(value).toLowerCase() === "false";
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function isTrue(value) {
|
|
211
|
+
return value === true || String(value).toLowerCase() === "true";
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function stringList(value) {
|
|
215
|
+
return Array.isArray(value) ? value.filter((item) => item !== null && item !== undefined).map(String) : [];
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function receiptSchema(role) {
|
|
219
|
+
if (role === "worker") {
|
|
220
|
+
return {
|
|
221
|
+
result: "done | blocked",
|
|
222
|
+
changed_files: [],
|
|
223
|
+
commands: [{ cmd: "<command>", status: "pass | fail | not_run" }],
|
|
224
|
+
summary: "<=120 words",
|
|
225
|
+
remaining_blockers: [],
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
if (role === "judge") {
|
|
229
|
+
return {
|
|
230
|
+
result: "done | blocked",
|
|
231
|
+
decision: "approved | rejected | approve_subgoal | reject_subgoal | not_complete | complete",
|
|
232
|
+
full_outcome_complete: false,
|
|
233
|
+
evidence: [],
|
|
234
|
+
blocked_tasks: [],
|
|
235
|
+
required_board_updates: [],
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
return {
|
|
239
|
+
result: "done | blocked",
|
|
240
|
+
summary: "<=120 words",
|
|
241
|
+
evidence: [],
|
|
242
|
+
facts: [],
|
|
243
|
+
contradictions: [],
|
|
244
|
+
ambiguity_requiring_judge: [],
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function formatPrompt(payload) {
|
|
249
|
+
const lines = [
|
|
250
|
+
"GoalBuddy task prompt",
|
|
251
|
+
"",
|
|
252
|
+
"Metadata:",
|
|
253
|
+
`- recommended_agent: ${payload.metadata.recommended_agent}`,
|
|
254
|
+
`- required_spawn_agent_type: ${payload.metadata.required_spawn_agent_type || "PM fallback"}`,
|
|
255
|
+
`- recommended_reasoning: ${payload.metadata.recommended_reasoning}`,
|
|
256
|
+
`- sandbox: ${payload.metadata.sandbox}`,
|
|
257
|
+
`- fork_context_allowed: ${payload.metadata.fork_context_allowed}`,
|
|
258
|
+
`- board_path: ${payload.metadata.board_path}`,
|
|
259
|
+
];
|
|
260
|
+
if (payload.metadata.child_board_paths.length) {
|
|
261
|
+
lines.push("- child_board_paths:");
|
|
262
|
+
for (const path of payload.metadata.child_board_paths) lines.push(` - ${path}`);
|
|
263
|
+
}
|
|
264
|
+
if (payload.metadata.slice_policy) {
|
|
265
|
+
lines.push(`- slice_policy: ${JSON.stringify(payload.metadata.slice_policy)}`);
|
|
266
|
+
}
|
|
267
|
+
if (payload.metadata.warnings.length) {
|
|
268
|
+
lines.push("- warnings:");
|
|
269
|
+
for (const warning of payload.metadata.warnings) lines.push(` - ${warning}`);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
lines.push(
|
|
273
|
+
"",
|
|
274
|
+
"Spawn contract:",
|
|
275
|
+
`- Codex spawn_agent agent_type: ${payload.metadata.required_spawn_agent_type || "do not spawn; run as PM"}`,
|
|
276
|
+
"- Do not substitute generic scout, worker, or judge agents for GoalBuddy agents.",
|
|
277
|
+
"- If the required GoalBuddy agent is unavailable, stop spawning and continue as PM fallback or install agents.",
|
|
278
|
+
"- After one wait_agent timeout with no visible allowed-file changes, stop waiting and recover deterministically.",
|
|
279
|
+
"",
|
|
280
|
+
"Task:",
|
|
281
|
+
`- id: ${payload.task.id}`,
|
|
282
|
+
`- type: ${payload.task.type}`,
|
|
283
|
+
`- assignee: ${payload.task.assignee}`,
|
|
284
|
+
`- status: ${payload.task.status}`,
|
|
285
|
+
`- objective: ${payload.task.objective}`,
|
|
286
|
+
);
|
|
287
|
+
addList(lines, "inputs", payload.task.inputs);
|
|
288
|
+
addList(lines, "constraints", payload.task.constraints);
|
|
289
|
+
addList(lines, "allowed_files", payload.task.allowed_files);
|
|
290
|
+
addList(lines, "verify", payload.task.verify);
|
|
291
|
+
addList(lines, "stop_if", payload.task.stop_if);
|
|
292
|
+
addList(lines, "expected_output", payload.task.expected_output);
|
|
293
|
+
lines.push("", "Expected receipt JSON shape:", JSON.stringify(payload.receipt_schema, null, 2));
|
|
294
|
+
return lines.join("\n");
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function addList(lines, label, values) {
|
|
298
|
+
if (!values.length) return;
|
|
299
|
+
lines.push(`- ${label}:`);
|
|
300
|
+
for (const value of values) lines.push(` - ${value}`);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function isDirectRun() {
|
|
304
|
+
return process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url);
|
|
305
|
+
}
|
|
@@ -4,8 +4,8 @@ Use three generic agents. The main `/goal` thread remains PM and owns the board.
|
|
|
4
4
|
|
|
5
5
|
| Agent | model_reasoning_effort | sandbox_mode | Purpose |
|
|
6
6
|
|---|---:|---|---|
|
|
7
|
-
| goal_scout |
|
|
8
|
-
| goal_worker |
|
|
7
|
+
| goal_scout | low | read-only | Targeted evidence mapping and candidate facts |
|
|
8
|
+
| goal_worker | medium | workspace-write | One coherent bounded implementation/recovery slice |
|
|
9
9
|
| goal_judge | high | read-only | Strategic review, escalation, completion skepticism |
|
|
10
10
|
|
|
11
11
|
## PM Thinking Policy
|
|
@@ -44,5 +44,6 @@ Rules:
|
|
|
44
44
|
|
|
45
45
|
- Only the PM loop chooses active tasks, marks tasks done, or completes the goal.
|
|
46
46
|
- Keep at most one write-capable Worker active unless disjoint write scopes are explicit in `state.yaml`.
|
|
47
|
-
-
|
|
48
|
-
- Judge
|
|
47
|
+
- Worker defaults to medium reasoning for implementation tasks and should complete the whole assigned slice.
|
|
48
|
+
- Scout and Judge are read-only and safe to parallelize when their board inputs are clear.
|
|
49
|
+
- Judge is high thinking and should choose the largest safe useful slice, not the narrowest helper.
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
|
|
26
26
|
## Current Tranche
|
|
27
27
|
|
|
28
|
-
<What is enough for the full owner outcome, and what is the current
|
|
28
|
+
<What is enough for the full owner outcome, and what is the current largest reversible local work package? For execution goals, the default is continuous: discover enough evidence, choose a coherent work package, implement it, verify it, review only at phase/risk/final boundaries, then immediately advance to the next work package until the full original outcome is complete. Plan-only or one-package-only stopping is valid only when explicitly requested.>
|
|
29
29
|
|
|
30
30
|
## Non-Negotiable Constraints
|
|
31
31
|
|
|
@@ -37,7 +37,21 @@ Stop only when a final audit proves the full original outcome is complete.
|
|
|
37
37
|
|
|
38
38
|
Do not stop after planning, discovery, or Judge selection if the user asked for working software or automation and a safe Worker task can be activated.
|
|
39
39
|
|
|
40
|
-
Do not stop after a single verified Worker
|
|
40
|
+
Do not stop after a single verified Worker package when the broader owner outcome still has safe local follow-up work. Advance the board to the next highest-leverage safe Worker package and continue unless a phase, risk, rejected-verification, ambiguity, or final-completion review is due.
|
|
41
|
+
|
|
42
|
+
Do not create one Worker/Judge pair per repeated file, table, route, or helper. Put repeated same-shape work into one Worker package and review the package as a whole.
|
|
43
|
+
|
|
44
|
+
## Slice Sizing
|
|
45
|
+
|
|
46
|
+
Safe means bounded, explicit, verified, and reversible. It does not mean tiny.
|
|
47
|
+
|
|
48
|
+
A good task is the largest safe useful slice.
|
|
49
|
+
|
|
50
|
+
Small is not the goal. Useful is the goal.
|
|
51
|
+
|
|
52
|
+
A Worker should finish the whole assigned slice. A Judge should judge the whole assigned slice. A PM should reorient the board when tasks are safe but not moving the outcome.
|
|
53
|
+
|
|
54
|
+
Tiny tasks are allowed when the failure is isolated, the risk is high, the scope is unknown, or the tiny task unlocks a larger slice. Tiny tasks are bad when they keep happening, do not change behavior, only add wrappers/contracts/proof files, or avoid the real milestone.
|
|
41
55
|
|
|
42
56
|
Do not stop because a slice needs owner input, credentials, production access, destructive operations, or policy decisions. Mark that exact slice blocked with a receipt, create the smallest safe follow-up or workaround task, and continue all local, non-destructive work that can still move the goal toward the full outcome.
|
|
43
57
|
|
|
@@ -67,9 +81,9 @@ On every `/goal` continuation:
|
|
|
67
81
|
6. Assign Scout, Judge, Worker, or PM according to the task.
|
|
68
82
|
7. Write a compact task receipt.
|
|
69
83
|
8. Update the board.
|
|
70
|
-
9. If
|
|
84
|
+
9. If safe local work remains, choose the next largest reversible Worker package and continue unless blocked.
|
|
71
85
|
10. If a problem, suggestion, or follow-up should become a repo artifact, create an approved issue/PR or ask the operator whether to create one.
|
|
72
|
-
11.
|
|
86
|
+
11. Review at phase, risk, rejected-verification, ambiguity, or final-completion boundaries; do not review every small Worker by habit.
|
|
73
87
|
12. Finish only with a Judge/PM audit receipt that maps receipts and verification back to the original user outcome and records `full_outcome_complete: true`.
|
|
74
88
|
|
|
75
89
|
Issue and PR handoffs are supporting artifacts. `state.yaml` remains authoritative, and every external artifact decision must be recorded in a task receipt.
|
|
@@ -33,6 +33,11 @@ rules:
|
|
|
33
33
|
missing_input_or_credentials_do_not_stop_goal: true
|
|
34
34
|
preserve_and_validate_existing_plan: true
|
|
35
35
|
intake_misfire_must_be_audited: true
|
|
36
|
+
slice_policy:
|
|
37
|
+
max_consecutive_tiny_tasks: 2
|
|
38
|
+
prefer_vertical_slices: true
|
|
39
|
+
judge_picks_largest_safe_slice: true
|
|
40
|
+
worker_completes_whole_slice: true
|
|
36
41
|
|
|
37
42
|
agents:
|
|
38
43
|
# installed | bundled_not_installed | missing | unknown
|
|
@@ -88,7 +93,7 @@ tasks:
|
|
|
88
93
|
- "T001 receipt"
|
|
89
94
|
constraints:
|
|
90
95
|
- "Do not implement."
|
|
91
|
-
- "Pick
|
|
96
|
+
- "Pick the largest safe useful slice with clear allowed_files, verify commands, and stop conditions."
|
|
92
97
|
expected_output:
|
|
93
98
|
- "Decision"
|
|
94
99
|
- "Exact Worker objective"
|
|
@@ -109,6 +114,14 @@ tasks:
|
|
|
109
114
|
- "Need files outside allowed_files."
|
|
110
115
|
- "Behavior is ambiguous."
|
|
111
116
|
- "Verification fails twice."
|
|
117
|
+
# Optional depth-1 child board promoted by PM when a task needs bounded branching work:
|
|
118
|
+
# subgoal:
|
|
119
|
+
# status: active # active | blocked | done
|
|
120
|
+
# path: subgoals/T003-child/state.yaml
|
|
121
|
+
# owner: Worker
|
|
122
|
+
# created_from: T003
|
|
123
|
+
# depth: 1
|
|
124
|
+
# rollup_receipt: null
|
|
112
125
|
receipt: null
|
|
113
126
|
- id: T999
|
|
114
127
|
type: judge
|