goalbuddy 0.2.10

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.
Files changed (68) hide show
  1. package/CONTRIBUTING.md +45 -0
  2. package/LICENSE +21 -0
  3. package/README.md +215 -0
  4. package/examples/extend-catalog-workflow/goal.md +53 -0
  5. package/examples/extend-catalog-workflow/notes/T001-extension-model-map.md +47 -0
  6. package/examples/extend-catalog-workflow/notes/T002-architecture-decision.md +48 -0
  7. package/examples/extend-catalog-workflow/notes/T003-implementation-summary.md +43 -0
  8. package/examples/extend-catalog-workflow/notes/T004-root-extend-folder.md +24 -0
  9. package/examples/extend-catalog-workflow/notes/T005-layout-cleanup.md +46 -0
  10. package/examples/extend-catalog-workflow/notes/T006-catalog-location.md +50 -0
  11. package/examples/extend-catalog-workflow/notes/T999-completion-audit.md +36 -0
  12. package/examples/extend-catalog-workflow/state.yaml +327 -0
  13. package/examples/github-pr-workflow-extension/pr-handoff.md +46 -0
  14. package/examples/improve-goal-maker/goal.md +51 -0
  15. package/examples/improve-goal-maker/notes/T001-repo-map.md +59 -0
  16. package/examples/improve-goal-maker/notes/T002-risk-map.md +37 -0
  17. package/examples/improve-goal-maker/state.yaml +224 -0
  18. package/goal-maker/SKILL.md +18 -0
  19. package/goal-maker/agents/README.md +23 -0
  20. package/goal-maker/agents/config-snippet.toml +5 -0
  21. package/goal-maker/agents/goal_judge.toml +29 -0
  22. package/goal-maker/agents/goal_scout.toml +26 -0
  23. package/goal-maker/agents/goal_worker.toml +28 -0
  24. package/goal-maker/agents/openai.yaml +6 -0
  25. package/goal-maker/scripts/check-goal-state.mjs +370 -0
  26. package/goal-maker/scripts/install-agents.mjs +28 -0
  27. package/goal-maker/templates/agents.md +48 -0
  28. package/goal-maker/templates/goal-prompt.txt +1 -0
  29. package/goal-maker/templates/goal.md +71 -0
  30. package/goal-maker/templates/note.md +22 -0
  31. package/goal-maker/templates/state.yaml +125 -0
  32. package/goalbuddy/SKILL.md +484 -0
  33. package/goalbuddy/agents/README.md +23 -0
  34. package/goalbuddy/agents/config-snippet.toml +5 -0
  35. package/goalbuddy/agents/goal_judge.toml +29 -0
  36. package/goalbuddy/agents/goal_scout.toml +26 -0
  37. package/goalbuddy/agents/goal_worker.toml +28 -0
  38. package/goalbuddy/agents/openai.yaml +6 -0
  39. package/goalbuddy/scripts/check-goal-state.mjs +370 -0
  40. package/goalbuddy/scripts/install-agents.mjs +28 -0
  41. package/goalbuddy/templates/agents.md +48 -0
  42. package/goalbuddy/templates/goal-prompt.txt +1 -0
  43. package/goalbuddy/templates/goal.md +71 -0
  44. package/goalbuddy/templates/note.md +22 -0
  45. package/goalbuddy/templates/state.yaml +125 -0
  46. package/internal/assets/extend-release.png +0 -0
  47. package/internal/assets/extend-release.svg +83 -0
  48. package/internal/assets/goal-maker-flow.png +0 -0
  49. package/internal/cli/check-publish-version.mjs +86 -0
  50. package/internal/cli/goal-maker.mjs +1061 -0
  51. package/package.json +65 -0
  52. package/plugins/goalbuddy/.codex-plugin/plugin.json +48 -0
  53. package/plugins/goalbuddy/README.md +29 -0
  54. package/plugins/goalbuddy/assets/goalbuddy-icon.svg +8 -0
  55. package/plugins/goalbuddy/skills/goalbuddy/SKILL.md +484 -0
  56. package/plugins/goalbuddy/skills/goalbuddy/agents/README.md +23 -0
  57. package/plugins/goalbuddy/skills/goalbuddy/agents/config-snippet.toml +5 -0
  58. package/plugins/goalbuddy/skills/goalbuddy/agents/goal_judge.toml +29 -0
  59. package/plugins/goalbuddy/skills/goalbuddy/agents/goal_scout.toml +26 -0
  60. package/plugins/goalbuddy/skills/goalbuddy/agents/goal_worker.toml +28 -0
  61. package/plugins/goalbuddy/skills/goalbuddy/agents/openai.yaml +6 -0
  62. package/plugins/goalbuddy/skills/goalbuddy/scripts/check-goal-state.mjs +370 -0
  63. package/plugins/goalbuddy/skills/goalbuddy/scripts/install-agents.mjs +28 -0
  64. package/plugins/goalbuddy/skills/goalbuddy/templates/agents.md +48 -0
  65. package/plugins/goalbuddy/skills/goalbuddy/templates/goal-prompt.txt +1 -0
  66. package/plugins/goalbuddy/skills/goalbuddy/templates/goal.md +71 -0
  67. package/plugins/goalbuddy/skills/goalbuddy/templates/note.md +22 -0
  68. package/plugins/goalbuddy/skills/goalbuddy/templates/state.yaml +125 -0
@@ -0,0 +1,370 @@
1
+ #!/usr/bin/env node
2
+ import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
3
+ import { basename, dirname, join } from "node:path";
4
+
5
+ const statePath = process.argv[2];
6
+
7
+ if (!statePath) {
8
+ console.error("Usage: node scripts/check-goal-state.mjs docs/goals/<slug>/state.yaml");
9
+ process.exit(2);
10
+ }
11
+
12
+ if (!existsSync(statePath)) {
13
+ console.error(JSON.stringify({ ok: false, errors: [`state file not found: ${statePath}`], warnings: [] }, null, 2));
14
+ process.exit(1);
15
+ }
16
+
17
+ const root = dirname(statePath);
18
+ const text = readFileSync(statePath, "utf8");
19
+ const errors = [];
20
+ const warnings = [];
21
+
22
+ function clean(value) {
23
+ if (value === undefined || value === null) return null;
24
+ const cleaned = value.replace(/#.*/, "").trim().replace(/^[\'\"]|[\'\"]$/g, "");
25
+ if (cleaned === "" || cleaned === "null") return null;
26
+ if (cleaned === "true") return true;
27
+ if (cleaned === "false") return false;
28
+ if (/^\d+$/.test(cleaned)) return Number(cleaned);
29
+ return cleaned;
30
+ }
31
+
32
+ function topScalar(key) {
33
+ const match = text.match(new RegExp(`^${key}:\\s*(.*?)\\s*$`, "m"));
34
+ return match ? clean(match[1]) : null;
35
+ }
36
+
37
+ function nestedScalar(section, key) {
38
+ const lines = text.split(/\r?\n/);
39
+ let inSection = false;
40
+ for (const line of lines) {
41
+ if (new RegExp(`^${section}:\\s*$`).test(line)) {
42
+ inSection = true;
43
+ continue;
44
+ }
45
+ if (inSection && /^\S/.test(line)) break;
46
+ if (inSection) {
47
+ const match = line.match(new RegExp(`^\\s{2}${key}:\\s*(.*?)\\s*$`));
48
+ if (match) return clean(match[1]);
49
+ }
50
+ }
51
+ return null;
52
+ }
53
+
54
+ function sectionText(section) {
55
+ const lines = text.split(/\r?\n/);
56
+ const start = lines.findIndex((line) => new RegExp(`^${section}:\\s*$`).test(line));
57
+ if (start === -1) return "";
58
+ const collected = [];
59
+ for (let i = start + 1; i < lines.length; i += 1) {
60
+ if (/^\S/.test(lines[i])) break;
61
+ collected.push(lines[i]);
62
+ }
63
+ return collected.join("\n");
64
+ }
65
+
66
+ function parseTasks() {
67
+ const body = sectionText("tasks");
68
+ if (!body) return [];
69
+ const lines = body.split(/\r?\n/);
70
+ const tasks = [];
71
+ let current = null;
72
+ let currentLines = [];
73
+
74
+ function finish() {
75
+ if (!current) return;
76
+ current.raw = currentLines.join("\n");
77
+ tasks.push(current);
78
+ }
79
+
80
+ for (const line of lines) {
81
+ const idMatch = line.match(/^\s{2}-\s+id:\s*(.+?)\s*$/);
82
+ if (idMatch) {
83
+ finish();
84
+ current = { id: clean(idMatch[1]) };
85
+ currentLines = [line];
86
+ continue;
87
+ }
88
+ if (current) currentLines.push(line);
89
+ }
90
+ finish();
91
+ return tasks.map((task) => ({
92
+ ...task,
93
+ type: taskScalar(task, "type"),
94
+ assignee: taskScalar(task, "assignee"),
95
+ status: taskScalar(task, "status"),
96
+ objective: taskScalar(task, "objective"),
97
+ allowedFiles: taskList(task, "allowed_files"),
98
+ verify: taskList(task, "verify"),
99
+ stopIf: taskList(task, "stop_if"),
100
+ receipt: taskReceipt(task),
101
+ }));
102
+ }
103
+
104
+ function taskScalar(task, key) {
105
+ const match = task.raw.match(new RegExp(`^\\s{4}${key}:\\s*(.*?)\\s*$`, "m"));
106
+ return match ? clean(match[1]) : null;
107
+ }
108
+
109
+ function taskList(task, key) {
110
+ const lines = task.raw.split(/\r?\n/);
111
+ const start = lines.findIndex((line) => new RegExp(`^\\s{4}${key}:\\s*$`).test(line));
112
+ if (start === -1) return [];
113
+ const values = [];
114
+ for (let i = start + 1; i < lines.length; i += 1) {
115
+ if (/^\s{4}\S/.test(lines[i])) break;
116
+ const item = lines[i].match(/^\s{6}-\s*(.+?)\s*$/);
117
+ if (item) values.push(clean(item[1]));
118
+ }
119
+ return values.filter((value) => value !== null);
120
+ }
121
+
122
+ function taskReceipt(task) {
123
+ const lines = task.raw.split(/\r?\n/);
124
+ const start = lines.findIndex((line) => /^\s{4}receipt:\s*/.test(line));
125
+ if (start === -1) return { present: false, value: null, raw: "" };
126
+
127
+ const inline = clean(lines[start].replace(/^\s{4}receipt:\s*/, ""));
128
+ if (inline === null && !/^(\s{6}|\s{8})/.test(lines[start + 1] || "")) {
129
+ return { present: true, value: null, raw: "" };
130
+ }
131
+
132
+ const receiptLines = [];
133
+ for (let i = start + 1; i < lines.length; i += 1) {
134
+ if (/^\s{4}\S/.test(lines[i])) break;
135
+ receiptLines.push(lines[i]);
136
+ }
137
+ const raw = receiptLines.join("\n");
138
+ return {
139
+ present: true,
140
+ value: inline || "object",
141
+ raw,
142
+ has: (key) => new RegExp(`^\\s{6}${key}:`, "m").test(raw),
143
+ list: (key) => receiptList(raw, key),
144
+ commandStatuses: () => receiptCommandStatuses(raw),
145
+ scalar: (key) => {
146
+ const match = raw.match(new RegExp(`^\\s{6}${key}:\\s*(.*?)\\s*$`, "m"));
147
+ return match ? clean(match[1]) : null;
148
+ },
149
+ };
150
+ }
151
+
152
+ function receiptList(raw, key) {
153
+ const lines = raw.split(/\r?\n/);
154
+ const start = lines.findIndex((line) => new RegExp(`^\\s{6}${key}:\\s*$`).test(line));
155
+ if (start === -1) return [];
156
+ const values = [];
157
+ for (let i = start + 1; i < lines.length; i += 1) {
158
+ if (/^\s{6}\S/.test(lines[i])) break;
159
+ const item = lines[i].match(/^\s{8}-\s*(.+?)\s*$/);
160
+ if (item) values.push(clean(item[1]));
161
+ }
162
+ return values.filter((value) => value !== null);
163
+ }
164
+
165
+ function receiptCommandStatuses(raw) {
166
+ return [...raw.matchAll(/^\s{10}status:\s*(.*?)\s*$/gm)]
167
+ .map((match) => clean(match[1]))
168
+ .filter((value) => value !== null);
169
+ }
170
+
171
+ function rootEntryErrors() {
172
+ const allowed = new Set(["goal.md", "state.yaml", "notes"]);
173
+ const unexpected = [];
174
+ for (const entry of readdirSync(root).filter((item) => item !== ".DS_Store")) {
175
+ const path = join(root, entry);
176
+ const stats = statSync(path);
177
+ if (!allowed.has(entry)) {
178
+ unexpected.push(entry);
179
+ } else if (entry === "notes" && !stats.isDirectory()) {
180
+ unexpected.push("notes (must be a directory)");
181
+ } else if (entry !== "notes" && !stats.isFile()) {
182
+ unexpected.push(`${entry} (must be a file)`);
183
+ }
184
+ }
185
+ return unexpected;
186
+ }
187
+
188
+ const version = topScalar("version");
189
+ const goalStatus = nestedScalar("goal", "status");
190
+ const activeTask = topScalar("active_task");
191
+ const installedAgents = ["scout", "worker", "judge"].map((agent) => ({
192
+ agent,
193
+ status: nestedScalar("agents", agent),
194
+ }));
195
+ const continuousUntilFullOutcome = nestedScalar("rules", "continuous_until_full_outcome") === true;
196
+ const missingInputOrCredentialsDoNotStopGoal =
197
+ nestedScalar("rules", "missing_input_or_credentials_do_not_stop_goal") === true;
198
+ const legacySignals = [
199
+ /^gate:\s*$/m,
200
+ /^artifact_policy:\s*$/m,
201
+ /^active_unit:/m,
202
+ /^evidence\.jsonl/m,
203
+ ].some((pattern) => pattern.test(text)) || ["units", "artifacts", "evidence.jsonl"].some((entry) => existsSync(join(root, entry)));
204
+
205
+ if (version !== 2) {
206
+ if (legacySignals) {
207
+ errors.push("legacy v1 goal state detected; Goal Maker v2 requires version: 2 with a task board. Create a new v2 goal or migrate manually.");
208
+ } else {
209
+ errors.push("state.yaml must declare version: 2");
210
+ }
211
+ }
212
+
213
+ if (!["active", "blocked", "done"].includes(goalStatus)) {
214
+ errors.push(`goal.status must be active, blocked, or done; got ${goalStatus || "<missing>"}`);
215
+ }
216
+
217
+ for (const { agent, status } of installedAgents) {
218
+ if (status !== "installed") {
219
+ errors.push(`agents.${agent} must be installed; got ${status || "<missing>"}`);
220
+ }
221
+ }
222
+
223
+ if (!existsSync(join(root, "goal.md"))) errors.push("missing goal.md");
224
+ if (!existsSync(join(root, "notes")) || !statSync(join(root, "notes")).isDirectory()) {
225
+ errors.push("missing notes/ directory");
226
+ }
227
+
228
+ const unexpected = rootEntryErrors();
229
+ if (unexpected.length > 0) {
230
+ errors.push(`unexpected root entries; v2 goal roots may contain only goal.md, state.yaml, and notes/: ${unexpected.join(", ")}`);
231
+ }
232
+
233
+ const tasks = parseTasks();
234
+ const ids = new Set();
235
+ for (const task of tasks) {
236
+ if (!task.id || !/^T\d{3}$/.test(task.id)) errors.push(`task id must use T### format; got ${task.id || "<missing>"}`);
237
+ if (ids.has(task.id)) errors.push(`duplicate task id: ${task.id}`);
238
+ ids.add(task.id);
239
+ if (!["scout", "judge", "worker", "pm"].includes(task.type)) {
240
+ errors.push(`task ${task.id} type must be scout, judge, worker, or pm`);
241
+ }
242
+ if (!["Scout", "Judge", "Worker", "PM"].includes(task.assignee)) {
243
+ errors.push(`task ${task.id} assignee must be Scout, Judge, Worker, or PM`);
244
+ }
245
+ const expectedAssignee = {
246
+ scout: "Scout",
247
+ judge: "Judge",
248
+ worker: "Worker",
249
+ pm: "PM",
250
+ }[task.type];
251
+ if (expectedAssignee && task.assignee !== expectedAssignee) {
252
+ errors.push(`task ${task.id} assignee must be ${expectedAssignee} for type ${task.type}`);
253
+ }
254
+ if (!["queued", "active", "blocked", "done"].includes(task.status)) {
255
+ errors.push(`task ${task.id} status must be queued, active, blocked, or done`);
256
+ }
257
+ if (!task.objective) errors.push(`task ${task.id} missing objective`);
258
+ }
259
+
260
+ if (tasks.length === 0) errors.push("tasks must contain at least one task");
261
+
262
+ const activeTasks = tasks.filter((task) => task.status === "active");
263
+ if (goalStatus === "done") {
264
+ if (activeTasks.length !== 0) errors.push("done goals must not have an active task");
265
+ if (activeTask !== null) errors.push("done goals must set active_task: null");
266
+ const unfinishedWorkers = tasks
267
+ .filter((task) => task.type === "worker" && ["queued", "active"].includes(task.status))
268
+ .map((task) => task.id);
269
+ if (unfinishedWorkers.length > 0) {
270
+ errors.push(`done goals must not leave queued or active Worker tasks: ${unfinishedWorkers.join(", ")}`);
271
+ }
272
+ } else if (goalStatus === "blocked") {
273
+ if (activeTasks.length > 1) errors.push("blocked goals may have at most one active task");
274
+ if (continuousUntilFullOutcome && missingInputOrCredentialsDoNotStopGoal) {
275
+ errors.push("continuous goals must keep goal.status active; missing input or credentials should block specific tasks, not the whole goal");
276
+ }
277
+ } else if (activeTasks.length !== 1) {
278
+ errors.push(`exactly one active task is required while goal.status is active; found ${activeTasks.length}`);
279
+ }
280
+
281
+ if (activeTasks.length === 1 && activeTask !== activeTasks[0].id) {
282
+ errors.push(`active_task must point to active task ${activeTasks[0].id}; got ${activeTask || "null"}`);
283
+ }
284
+ if (activeTask && !ids.has(activeTask)) errors.push(`active_task points to unknown task: ${activeTask}`);
285
+
286
+ for (const task of tasks) {
287
+ const hasReceipt = task.receipt.present && task.receipt.value !== null;
288
+ const receiptResult = hasReceipt ? task.receipt.scalar("result") : null;
289
+ if (task.status === "done" && !hasReceipt) {
290
+ errors.push(`done task ${task.id} missing receipt`);
291
+ }
292
+ if (task.status === "done" && hasReceipt && receiptResult !== "done") {
293
+ errors.push(`done task ${task.id} receipt must include result: done`);
294
+ }
295
+ if (task.status === "blocked" && !hasReceipt) {
296
+ errors.push(`blocked task ${task.id} missing receipt`);
297
+ }
298
+ if (task.type === "worker" && task.status === "active") {
299
+ if (task.allowedFiles.length === 0) errors.push(`active Worker task ${task.id} must include allowed_files`);
300
+ if (task.verify.length === 0) errors.push(`active Worker task ${task.id} must include verify`);
301
+ if (task.stopIf.length === 0) errors.push(`active Worker task ${task.id} must include stop_if`);
302
+ }
303
+ if (task.type === "worker" && task.status === "done" && hasReceipt) {
304
+ for (const key of ["changed_files", "commands", "summary"]) {
305
+ if (!task.receipt.has(key)) errors.push(`Worker receipt for ${task.id} missing ${key}`);
306
+ }
307
+ const changedFiles = task.receipt.list("changed_files");
308
+ for (const changedFile of changedFiles) {
309
+ if (!task.allowedFiles.includes(changedFile)) {
310
+ errors.push(`Worker receipt for ${task.id} changed file outside allowed_files: ${changedFile}`);
311
+ }
312
+ }
313
+ const commandStatuses = task.receipt.commandStatuses();
314
+ if (task.receipt.has("commands") && commandStatuses.length === 0) {
315
+ errors.push(`Worker receipt for ${task.id} commands must include status fields`);
316
+ }
317
+ for (const status of commandStatuses) {
318
+ if (status !== "pass") {
319
+ errors.push(`Worker receipt for ${task.id} has non-passing command status: ${status}`);
320
+ }
321
+ }
322
+ }
323
+ if (task.type === "scout" && task.status === "done" && hasReceipt) {
324
+ if (!task.receipt.has("summary")) errors.push(`Scout receipt for ${task.id} missing summary`);
325
+ if (!task.receipt.has("evidence") && !task.receipt.has("note")) {
326
+ errors.push(`Scout receipt for ${task.id} must include evidence or note`);
327
+ }
328
+ }
329
+ if (task.type === "judge" && task.status === "done" && hasReceipt && !task.receipt.has("decision")) {
330
+ errors.push(`Judge receipt for ${task.id} missing decision`);
331
+ }
332
+ }
333
+
334
+ if (goalStatus === "done") {
335
+ const finalAudit = tasks.some((task) => {
336
+ if (!["judge", "pm"].includes(task.type) || task.status !== "done") return false;
337
+ if (!task.receipt.present || task.receipt.value === null) return false;
338
+ const decision = task.receipt.scalar("decision");
339
+ return decision === "complete" || decision === "done";
340
+ });
341
+ if (!finalAudit) {
342
+ errors.push("completion requires a final done Judge or PM audit receipt with decision: complete");
343
+ }
344
+ if (continuousUntilFullOutcome) {
345
+ const finalFullOutcomeAudit = tasks.some((task) => {
346
+ if (!["judge", "pm"].includes(task.type) || task.status !== "done") return false;
347
+ if (!task.receipt.present || task.receipt.value === null) return false;
348
+ const decision = task.receipt.scalar("decision");
349
+ const fullOutcomeComplete = task.receipt.scalar("full_outcome_complete");
350
+ return (decision === "complete" || decision === "done") && fullOutcomeComplete === true;
351
+ });
352
+ if (!finalFullOutcomeAudit) {
353
+ errors.push("continuous goals require a final done Judge or PM audit receipt with full_outcome_complete: true before goal.status: done");
354
+ }
355
+ }
356
+ }
357
+
358
+ const result = {
359
+ ok: errors.length === 0,
360
+ version,
361
+ state_path: statePath,
362
+ goal_status: goalStatus,
363
+ active_task: activeTask,
364
+ task_count: tasks.length,
365
+ errors,
366
+ warnings,
367
+ };
368
+
369
+ console.log(JSON.stringify(result, null, 2));
370
+ process.exit(result.ok ? 0 : 1);
@@ -0,0 +1,28 @@
1
+ #!/usr/bin/env node
2
+ import { copyFileSync, existsSync, mkdirSync, readdirSync } from "node:fs";
3
+ import { dirname, join, resolve } from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+
6
+ const __dirname = dirname(fileURLToPath(import.meta.url));
7
+ const sourceDir = resolve(__dirname, "../agents");
8
+ const destDir = resolve(process.argv[2] || ".codex/agents");
9
+ const force = process.argv.includes("--force");
10
+
11
+ if (!existsSync(sourceDir)) {
12
+ console.error(`agent definitions not found: ${sourceDir}`);
13
+ process.exit(1);
14
+ }
15
+
16
+ mkdirSync(destDir, { recursive: true });
17
+
18
+ const files = readdirSync(sourceDir).filter((f) => f.endsWith(".toml") && !f.includes("config-snippet"));
19
+ for (const file of files) {
20
+ const src = join(sourceDir, file);
21
+ const dest = join(destDir, file);
22
+ if (existsSync(dest) && !force) {
23
+ console.log(`skip existing ${dest} (use --force to overwrite)`);
24
+ continue;
25
+ }
26
+ copyFileSync(src, dest);
27
+ console.log(`installed ${dest}`);
28
+ }
@@ -0,0 +1,48 @@
1
+ # Goal Maker Agents
2
+
3
+ Use three generic agents. The main `/goal` thread remains PM and owns the board.
4
+
5
+ | Agent | model_reasoning_effort | sandbox_mode | Purpose |
6
+ |---|---:|---|---|
7
+ | goal_scout | medium | read-only | Evidence mapping and candidate tasks |
8
+ | goal_worker | low | workspace-write | One bounded implementation/recovery task |
9
+ | goal_judge | high | read-only | Strategic review, escalation, completion skepticism |
10
+
11
+ ## PM Thinking Policy
12
+
13
+ The main `/goal` thread is the PM. It owns board truth, chooses active tasks, and decides when receipts are sufficient.
14
+
15
+ | Goal mode | PM thinking |
16
+ |---|---:|
17
+ | specific, bounded | medium |
18
+ | open-ended | high |
19
+ | recovery | high |
20
+ | audit | high |
21
+ | high-risk or multi-day final audit | xhigh optional |
22
+
23
+ Do not use `xhigh` by default. Use it only when a wrong board, scope, or completion decision would be materially more expensive than latency and cost.
24
+
25
+ Tasks may include optional `reasoning_hint: default | low | medium | high | xhigh`. Treat it as PM guidance, not permission to widen scope.
26
+
27
+ Recommended project config:
28
+
29
+ ```toml
30
+ [agents]
31
+ max_threads = 4
32
+ max_depth = 1
33
+ job_max_runtime_seconds = 1800
34
+ ```
35
+
36
+ Install:
37
+
38
+ ```bash
39
+ mkdir -p .codex/agents
40
+ cp .codex/skills/goal-maker/agents/goal_*.toml .codex/agents/
41
+ ```
42
+
43
+ Rules:
44
+
45
+ - Only the PM loop chooses active tasks, marks tasks done, or completes the goal.
46
+ - Keep at most one write-capable Worker active unless disjoint write scopes are explicit in `state.yaml`.
47
+ - Scout and Judge are read-only.
48
+ - Judge is high thinking.
@@ -0,0 +1 @@
1
+ /goal Follow docs/goals/<slug>/goal.md.
@@ -0,0 +1,71 @@
1
+ # <Goal Title>
2
+
3
+ ## Objective
4
+
5
+ <User-editable objective. Keep this bounded to the current tranche, not an infinite mission.>
6
+
7
+ ## Original Request
8
+
9
+ <Shortest faithful copy of what the user asked for. Preserve user-provided plan details here or summarize them under Intake Summary.>
10
+
11
+ ## Intake Summary
12
+
13
+ - Input shape: `vague | specific | existing_plan | recovery | audit`
14
+ - Audience: <beneficiary or unknown>
15
+ - Authority: `requested | approved | inferred | needs_approval | blocked`
16
+ - Proof type: `test | demo | artifact | metric | review | source_backed_answer | decision`
17
+ - Completion proof: <observable signal that closes the full original outcome>
18
+ - Likely misfire: <how Goal Maker could succeed at the wrong thing>
19
+ - Blind spots considered: <risks, unstated choices, or success dimensions surfaced during diagnostic intake>
20
+ - Existing plan facts: <user-provided steps/files/constraints/sequencing to preserve and validate, or none>
21
+
22
+ ## Goal Kind
23
+
24
+ `specific | open_ended | existing_plan | recovery | audit`
25
+
26
+ ## Current Tranche
27
+
28
+ <What is enough for the full owner outcome, and what is the current safe slice? For execution goals, the default is continuous: discover enough evidence, choose a safe implementation slice, implement it, verify it, audit it, then immediately advance to the next safe slice until the full original outcome is complete. Plan-only or one-slice-only stopping is valid only when explicitly requested.>
29
+
30
+ ## Non-Negotiable Constraints
31
+
32
+ - <Constraint, safety rule, compatibility rule, or owner preference.>
33
+
34
+ ## Stop Rule
35
+
36
+ Stop only when a final audit proves the full original outcome is complete.
37
+
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
+
40
+ Do not stop after a single verified Worker slice when the broader owner outcome still has safe local follow-up slices. After each slice audit, advance the board to the next highest-leverage safe Worker task and continue.
41
+
42
+ 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
+
44
+ ## Canonical Board
45
+
46
+ Machine truth lives at:
47
+
48
+ `docs/goals/<slug>/state.yaml`
49
+
50
+ If this charter and `state.yaml` disagree, `state.yaml` wins for task status, active task, receipts, verification freshness, and completion truth.
51
+
52
+ ## Run Command
53
+
54
+ ```text
55
+ /goal Follow docs/goals/<slug>/goal.md.
56
+ ```
57
+
58
+ ## PM Loop
59
+
60
+ On every `/goal` continuation:
61
+
62
+ 1. Read this charter.
63
+ 2. Read `state.yaml`.
64
+ 3. Re-check the intake: original request, input shape, authority, proof, blind spots, existing plan facts, and likely misfire.
65
+ 4. Work only on the active board task.
66
+ 5. Assign Scout, Judge, Worker, or PM according to the task.
67
+ 6. Write a compact task receipt.
68
+ 7. Update the board.
69
+ 8. If Judge selected a safe Worker task with `allowed_files`, `verify`, and `stop_if`, activate it and continue unless blocked.
70
+ 9. Treat a slice audit as a checkpoint, not completion, unless it explicitly proves the full original outcome is complete.
71
+ 10. 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`.
@@ -0,0 +1,22 @@
1
+ # <Task ID>: <Note Title>
2
+
3
+ Task: `<T###>`
4
+ Kind: `scout | judge | pm`
5
+ Status: `current | superseded | blocked`
6
+
7
+ ## Summary
8
+
9
+ <One paragraph the board receipt can point to.>
10
+
11
+ ## Details
12
+
13
+ - <Evidence, decision, or context too large for an inline receipt.>
14
+
15
+ ## Board Receipt Snippet
16
+
17
+ ```yaml
18
+ receipt:
19
+ result: done
20
+ note: notes/<task-id>-<slug>.md
21
+ summary: "<compact result>"
22
+ ```
@@ -0,0 +1,125 @@
1
+ # Goal Maker v2 state.yaml
2
+ # Board truth lives here. goal.md is the editable charter; notes/ holds long receipts only.
3
+
4
+ version: 2
5
+
6
+ goal:
7
+ title: "<Goal title>"
8
+ slug: "<goal-slug>"
9
+ kind: open_ended # specific | open_ended | existing_plan | recovery | audit
10
+ tranche: "<continuous execution: complete successive safe verified slices until the full original outcome is complete>"
11
+ status: active # active | blocked | done
12
+ intake:
13
+ original_request: "<shortest faithful user request>"
14
+ interpreted_outcome: "<one sentence>"
15
+ input_shape: vague # vague | specific | existing_plan | recovery | audit
16
+ audience: unknown
17
+ authority: requested # requested | approved | inferred | needs_approval | blocked
18
+ proof_type: artifact # test | demo | artifact | metric | review | source_backed_answer | decision
19
+ completion_proof: "<observable signal that proves the full original outcome is complete>"
20
+ likely_misfire: "<how Goal Maker could succeed at the wrong thing>"
21
+ blind_spots_considered: []
22
+ existing_plan_facts: []
23
+
24
+ rules:
25
+ pm_owns_state: true
26
+ one_active_task: true
27
+ max_write_workers: 1
28
+ no_implementation_without_worker_or_pm_task: true
29
+ no_completion_without_judge_or_pm_audit: true
30
+ planning_is_not_completion: true
31
+ queued_required_worker_blocks_completion: true
32
+ continuous_until_full_outcome: true
33
+ missing_input_or_credentials_do_not_stop_goal: true
34
+ preserve_and_validate_existing_plan: true
35
+ intake_misfire_must_be_audited: true
36
+
37
+ agents:
38
+ scout: installed
39
+ worker: installed
40
+ judge: installed
41
+
42
+ active_task: T001
43
+
44
+ tasks:
45
+ - id: T001
46
+ type: scout
47
+ assignee: Scout
48
+ status: active
49
+ reasoning_hint: default # default | low | medium | high | xhigh
50
+ objective: "Map repo purpose, architecture, verification commands, health signals, and improvement candidates."
51
+ inputs:
52
+ - README.md
53
+ - package.json
54
+ - docs
55
+ - tests
56
+ constraints:
57
+ - "Read-only."
58
+ - "Do not edit implementation files."
59
+ - "Prefer concrete file-path evidence over generic advice."
60
+ expected_output:
61
+ - "Repo map"
62
+ - "Verification commands"
63
+ - "Ranked improvement candidates"
64
+ - "Candidate next tasks"
65
+ receipt: null
66
+ - id: T002
67
+ type: judge
68
+ assignee: Judge
69
+ status: queued
70
+ reasoning_hint: default
71
+ objective: "Review Scout findings and choose the first safe implementation task."
72
+ inputs:
73
+ - "T001 receipt"
74
+ constraints:
75
+ - "Do not implement."
76
+ - "Pick small reviewable work."
77
+ expected_output:
78
+ - "Decision"
79
+ - "Exact Worker objective"
80
+ - "allowed_files"
81
+ - "verify"
82
+ - "stop_if"
83
+ - "Blocked or deferred tasks"
84
+ receipt: null
85
+ - id: T003
86
+ type: worker
87
+ assignee: Worker
88
+ status: queued
89
+ reasoning_hint: default
90
+ objective: "Execute the first safe implementation task selected by Judge."
91
+ allowed_files: []
92
+ verify: []
93
+ stop_if:
94
+ - "Need files outside allowed_files."
95
+ - "Behavior is ambiguous."
96
+ - "Verification fails twice."
97
+ receipt: null
98
+ - id: T999
99
+ type: judge
100
+ assignee: Judge
101
+ status: queued
102
+ reasoning_hint: default
103
+ objective: "Audit whether the implemented slice satisfies the original user outcome for this tranche."
104
+ inputs:
105
+ - "All done task receipts"
106
+ - "Last verification"
107
+ - "Current dirty diff"
108
+ constraints:
109
+ - "Do not implement."
110
+ - "Reject completion if required Worker work is still queued or active."
111
+ - "Reject completion if the broader original outcome still has safe local follow-up slices."
112
+ - "Reject stopping only because a slice needs owner input, credentials, production access, destructive operations, or policy decisions."
113
+ expected_output:
114
+ - "complete | not_complete"
115
+ - "full_outcome_complete: true | false"
116
+ - "missing evidence"
117
+ - "next task if not complete"
118
+ receipt: null
119
+
120
+ checks:
121
+ dirty_fingerprint: unknown
122
+ last_verification:
123
+ result: unknown
124
+ task: null
125
+ commands: []