goalbuddy 0.3.5 → 0.3.7
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 +46 -12
- package/RELEASE-0.3.5.md +4 -4
- package/RELEASE-0.3.7.md +127 -0
- package/goalbuddy/SKILL.md +53 -23
- package/goalbuddy/agents/README.md +1 -1
- package/goalbuddy/agents/goal_judge.toml +8 -4
- package/goalbuddy/agents/goal_worker.toml +8 -5
- package/goalbuddy/scripts/check-goal-state.mjs +129 -0
- package/goalbuddy/scripts/render-task-prompt.mjs +83 -5
- package/{plugins/goalbuddy/skills/goalbuddy/extend → goalbuddy/surfaces}/local-goal-board/README.md +7 -9
- package/{plugins/goalbuddy/skills/goalbuddy/extend → goalbuddy/surfaces}/local-goal-board/examples/sample-goal/state.yaml +5 -5
- package/{plugins/goalbuddy/skills/goalbuddy/extend → goalbuddy/surfaces}/local-goal-board/examples/subgoal-parent/state.yaml +3 -3
- package/goalbuddy/{extend → surfaces}/local-goal-board/examples/subgoal-parent/subgoals/T004-board-view/state.yaml +3 -3
- package/goalbuddy/{extend → surfaces}/local-goal-board/scripts/lib/goal-board.mjs +250 -9
- package/goalbuddy/{extend → surfaces}/local-goal-board/scripts/local-goal-board.mjs +4 -4
- package/goalbuddy/{extend → surfaces}/local-goal-board/test/local-goal-board.test.mjs +67 -9
- package/goalbuddy/templates/agents.md +3 -2
- package/goalbuddy/templates/goal.md +27 -4
- package/goalbuddy/templates/state.yaml +13 -7
- package/internal/assets/goalbuddy-v0.3.7-release.png +0 -0
- package/internal/cli/goal-maker.mjs +112 -714
- package/package.json +4 -4
- package/plugins/goalbuddy/.claude-plugin/plugin.json +3 -4
- package/plugins/goalbuddy/.codex-plugin/plugin.json +5 -6
- package/plugins/goalbuddy/README.md +4 -3
- package/plugins/goalbuddy/agents/goal-judge.md +8 -4
- package/plugins/goalbuddy/agents/goal-worker.md +6 -4
- package/plugins/goalbuddy/skills/goalbuddy/SKILL.md +53 -23
- package/plugins/goalbuddy/skills/goalbuddy/agents/README.md +1 -1
- package/plugins/goalbuddy/skills/goalbuddy/agents/goal_judge.toml +8 -4
- package/plugins/goalbuddy/skills/goalbuddy/agents/goal_worker.toml +8 -5
- package/plugins/goalbuddy/skills/goalbuddy/scripts/check-goal-state.mjs +129 -0
- package/plugins/goalbuddy/skills/goalbuddy/scripts/render-task-prompt.mjs +83 -5
- package/{goalbuddy/extend → plugins/goalbuddy/skills/goalbuddy/surfaces}/local-goal-board/README.md +7 -9
- package/{goalbuddy/extend → plugins/goalbuddy/skills/goalbuddy/surfaces}/local-goal-board/examples/sample-goal/state.yaml +5 -5
- package/{goalbuddy/extend → plugins/goalbuddy/skills/goalbuddy/surfaces}/local-goal-board/examples/subgoal-parent/state.yaml +3 -3
- package/plugins/goalbuddy/skills/goalbuddy/{extend → surfaces}/local-goal-board/examples/subgoal-parent/subgoals/T004-board-view/state.yaml +3 -3
- package/plugins/goalbuddy/skills/goalbuddy/{extend → surfaces}/local-goal-board/scripts/lib/goal-board.mjs +250 -9
- package/plugins/goalbuddy/skills/goalbuddy/{extend → surfaces}/local-goal-board/scripts/local-goal-board.mjs +4 -4
- package/plugins/goalbuddy/skills/goalbuddy/{extend → surfaces}/local-goal-board/test/local-goal-board.test.mjs +67 -9
- package/plugins/goalbuddy/skills/goalbuddy/templates/agents.md +3 -2
- package/plugins/goalbuddy/skills/goalbuddy/templates/goal.md +27 -4
- package/plugins/goalbuddy/skills/goalbuddy/templates/state.yaml +13 -7
- package/examples/extend-catalog-workflow/goal.md +0 -53
- package/examples/extend-catalog-workflow/notes/T001-extension-model-map.md +0 -47
- package/examples/extend-catalog-workflow/notes/T002-architecture-decision.md +0 -48
- package/examples/extend-catalog-workflow/notes/T003-implementation-summary.md +0 -43
- package/examples/extend-catalog-workflow/notes/T004-root-extend-folder.md +0 -24
- package/examples/extend-catalog-workflow/notes/T005-layout-cleanup.md +0 -46
- package/examples/extend-catalog-workflow/notes/T006-catalog-location.md +0 -50
- package/examples/extend-catalog-workflow/notes/T999-completion-audit.md +0 -36
- package/examples/extend-catalog-workflow/state.yaml +0 -327
- package/examples/github-pr-workflow-extension/pr-handoff.md +0 -46
- package/goalbuddy/extend/github-projects/README.md +0 -105
- package/goalbuddy/extend/github-projects/examples/goal-board-sync/state.yaml +0 -63
- package/goalbuddy/extend/github-projects/extension.yaml +0 -43
- package/goalbuddy/extend/github-projects/scripts/lib/github-projects.mjs +0 -728
- package/goalbuddy/extend/github-projects/scripts/lib/goal-state.mjs +0 -362
- package/goalbuddy/extend/github-projects/scripts/sync-github-project.mjs +0 -193
- package/goalbuddy/extend/github-projects/test/github-projects.test.mjs +0 -267
- package/goalbuddy/extend/local-goal-board/extension.yaml +0 -39
- package/internal/assets/extend-release.png +0 -0
- package/internal/assets/extend-release.svg +0 -83
- package/plugins/goalbuddy/skills/goalbuddy/extend/github-projects/README.md +0 -105
- package/plugins/goalbuddy/skills/goalbuddy/extend/github-projects/examples/goal-board-sync/state.yaml +0 -63
- package/plugins/goalbuddy/skills/goalbuddy/extend/github-projects/extension.yaml +0 -43
- package/plugins/goalbuddy/skills/goalbuddy/extend/github-projects/scripts/lib/github-projects.mjs +0 -728
- package/plugins/goalbuddy/skills/goalbuddy/extend/github-projects/scripts/lib/goal-state.mjs +0 -362
- package/plugins/goalbuddy/skills/goalbuddy/extend/github-projects/scripts/sync-github-project.mjs +0 -193
- package/plugins/goalbuddy/skills/goalbuddy/extend/github-projects/test/github-projects.test.mjs +0 -267
- package/plugins/goalbuddy/skills/goalbuddy/extend/local-goal-board/extension.yaml +0 -39
- /package/goalbuddy/{extend → surfaces}/local-goal-board/assets/goalbuddy-mark.png +0 -0
- /package/goalbuddy/{extend → surfaces}/local-goal-board/examples/sample-goal/notes/T001-scout.md +0 -0
- /package/goalbuddy/{extend → surfaces}/local-goal-board/examples/subgoal-parent/goal.md +0 -0
- /package/goalbuddy/{extend → surfaces}/local-goal-board/examples/subgoal-parent/notes/.gitkeep +0 -0
- /package/goalbuddy/{extend → surfaces}/local-goal-board/examples/subgoal-parent/subgoals/T004-board-view/goal.md +0 -0
- /package/goalbuddy/{extend → surfaces}/local-goal-board/examples/subgoal-parent/subgoals/T004-board-view/notes/.gitkeep +0 -0
- /package/plugins/goalbuddy/skills/goalbuddy/{extend → surfaces}/local-goal-board/assets/goalbuddy-mark.png +0 -0
- /package/plugins/goalbuddy/skills/goalbuddy/{extend → surfaces}/local-goal-board/examples/sample-goal/notes/T001-scout.md +0 -0
- /package/plugins/goalbuddy/skills/goalbuddy/{extend → surfaces}/local-goal-board/examples/subgoal-parent/goal.md +0 -0
- /package/plugins/goalbuddy/skills/goalbuddy/{extend → surfaces}/local-goal-board/examples/subgoal-parent/notes/.gitkeep +0 -0
- /package/plugins/goalbuddy/skills/goalbuddy/{extend → surfaces}/local-goal-board/examples/subgoal-parent/subgoals/T004-board-view/goal.md +0 -0
- /package/plugins/goalbuddy/skills/goalbuddy/{extend → surfaces}/local-goal-board/examples/subgoal-parent/subgoals/T004-board-view/notes/.gitkeep +0 -0
|
@@ -53,6 +53,38 @@ function nestedScalar(section, key) {
|
|
|
53
53
|
return null;
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
+
function pathScalar(path, key) {
|
|
57
|
+
const lines = text.split(/\r?\n/);
|
|
58
|
+
let depth = 0;
|
|
59
|
+
for (const line of lines) {
|
|
60
|
+
if (!line.trim()) continue;
|
|
61
|
+
const indent = line.match(/^ */)[0].length;
|
|
62
|
+
if (indent < depth * 2) depth = Math.floor(indent / 2);
|
|
63
|
+
|
|
64
|
+
if (depth < path.length && indent === depth * 2 && new RegExp(`^\\s{${indent}}${path[depth]}:\\s*$`).test(line)) {
|
|
65
|
+
depth += 1;
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (depth === path.length && indent === depth * 2) {
|
|
70
|
+
const match = line.match(new RegExp(`^\\s{${indent}}${key}:\\s*(.*?)\\s*$`));
|
|
71
|
+
if (match) return clean(match[1]);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function isWeakProof(value) {
|
|
78
|
+
if (value === null || value === undefined) return true;
|
|
79
|
+
const normalized = String(value).trim().toLowerCase();
|
|
80
|
+
return normalized === ""
|
|
81
|
+
|| normalized === "unknown"
|
|
82
|
+
|| normalized === "tbd"
|
|
83
|
+
|| normalized === "todo"
|
|
84
|
+
|| normalized === "none"
|
|
85
|
+
|| /^<.*>$/.test(normalized);
|
|
86
|
+
}
|
|
87
|
+
|
|
56
88
|
function sectionText(section) {
|
|
57
89
|
const lines = text.split(/\r?\n/);
|
|
58
90
|
const start = lines.findIndex((line) => new RegExp(`^${section}:\\s*$`).test(line));
|
|
@@ -225,6 +257,11 @@ const allowedAgentStatuses = new Set(["installed", "bundled_not_installed", "mis
|
|
|
225
257
|
const continuousUntilFullOutcome = nestedScalar("rules", "continuous_until_full_outcome") === true;
|
|
226
258
|
const missingInputOrCredentialsDoNotStopGoal =
|
|
227
259
|
nestedScalar("rules", "missing_input_or_credentials_do_not_stop_goal") === true;
|
|
260
|
+
const goalPressureRequiresOracle = nestedScalar("rules", "goal_pressure_requires_oracle") !== false;
|
|
261
|
+
const noCompletionOnWeakProof = nestedScalar("rules", "no_completion_on_weak_proof") !== false;
|
|
262
|
+
const completionProof = pathScalar(["goal", "intake"], "completion_proof");
|
|
263
|
+
const oracleSignal = pathScalar(["goal", "oracle"], "signal");
|
|
264
|
+
const oracleFinalProof = pathScalar(["goal", "oracle"], "final_proof");
|
|
228
265
|
const legacySignals = [
|
|
229
266
|
/^gate:\s*$/m,
|
|
230
267
|
/^artifact_policy:\s*$/m,
|
|
@@ -244,6 +281,19 @@ if (!["active", "blocked", "done"].includes(goalStatus)) {
|
|
|
244
281
|
errors.push(`goal.status must be active, blocked, or done; got ${goalStatus || "<missing>"}`);
|
|
245
282
|
}
|
|
246
283
|
|
|
284
|
+
if (goalPressureRequiresOracle) {
|
|
285
|
+
if (isWeakProof(oracleSignal)) {
|
|
286
|
+
warnings.push("goal.oracle.signal is missing or placeholder-like; weak oracles make /goal finish too early.");
|
|
287
|
+
}
|
|
288
|
+
if (isWeakProof(oracleFinalProof)) {
|
|
289
|
+
warnings.push("goal.oracle.final_proof is missing or placeholder-like; final completion needs receipt-backed proof.");
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
if (isWeakProof(completionProof)) {
|
|
294
|
+
warnings.push("goal.intake.completion_proof is missing or placeholder-like; record the observable signal that proves the full original outcome.");
|
|
295
|
+
}
|
|
296
|
+
|
|
247
297
|
function agentStatusWarning(agent, status) {
|
|
248
298
|
const agentLabel = agent[0].toUpperCase() + agent.slice(1);
|
|
249
299
|
if (status === "bundled_not_installed") {
|
|
@@ -369,6 +419,9 @@ for (const task of tasks) {
|
|
|
369
419
|
errors.push(`Worker receipt for ${task.id} has non-passing command status: ${status}`);
|
|
370
420
|
}
|
|
371
421
|
}
|
|
422
|
+
if (task.receipt.scalar("needs_judge") === true) {
|
|
423
|
+
warnings.push(`Worker receipt for ${task.id} requests legacy needs_judge; GoalBuddy now lets the PM continue by default and reviews only at phase, risk, ambiguity, rejected-verification, or final-completion boundaries`);
|
|
424
|
+
}
|
|
372
425
|
}
|
|
373
426
|
if (task.type === "scout" && task.status === "done" && hasReceipt) {
|
|
374
427
|
if (!task.receipt.has("summary")) errors.push(`Scout receipt for ${task.id} missing summary`);
|
|
@@ -381,6 +434,8 @@ for (const task of tasks) {
|
|
|
381
434
|
}
|
|
382
435
|
}
|
|
383
436
|
|
|
437
|
+
warnings.push(...microSliceWarnings(tasks, activeTask, goalStatus));
|
|
438
|
+
|
|
384
439
|
function validateSubgoal(task) {
|
|
385
440
|
if (isChildCheck) {
|
|
386
441
|
errors.push(`child task ${task.id} must not contain a nested subgoal`);
|
|
@@ -430,6 +485,77 @@ function validateSubgoal(task) {
|
|
|
430
485
|
}
|
|
431
486
|
}
|
|
432
487
|
|
|
488
|
+
function microSliceWarnings(tasks, activeTaskId, goalStatus) {
|
|
489
|
+
const found = [];
|
|
490
|
+
const guidance = "Board may be micro-slicing. Prefer the largest safe useful slice.";
|
|
491
|
+
const doneTasks = tasks.filter((task) => task.status === "done");
|
|
492
|
+
const workerTasks = tasks.filter((task) => task.type === "worker");
|
|
493
|
+
const recentTinyWorkers = workerTasks.slice(-5).filter((task) => isTinyTask(task));
|
|
494
|
+
const firstMilestoneComplete = nestedScalar("goal", "first_milestone_complete") === true;
|
|
495
|
+
|
|
496
|
+
if (recentTinyWorkers.length >= 3) {
|
|
497
|
+
found.push(`${guidance} Three recent Worker tasks look tiny.`);
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
for (const task of tasks) {
|
|
501
|
+
if (task.type === "judge" && /pick small reviewable work|select one narrow next task/i.test(task.raw)) {
|
|
502
|
+
found.push(`${guidance} Judge instructions still ask for small or narrow work.`);
|
|
503
|
+
break;
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
if (goalStatus !== "active" || !activeTaskId) return [...new Set(found)];
|
|
508
|
+
const activeIndex = tasks.findIndex((task) => task.id === activeTaskId);
|
|
509
|
+
if (activeIndex === -1) return [...new Set(found)];
|
|
510
|
+
const active = tasks[activeIndex];
|
|
511
|
+
if (active.type === "worker") {
|
|
512
|
+
if (doneTasks.length >= 10 && active.allowedFiles.length > 0 && active.allowedFiles.length <= 2) {
|
|
513
|
+
found.push(`${guidance} Active Worker ${active.id} has only ${active.allowedFiles.length} allowed_files after ${doneTasks.length} completed tasks.`);
|
|
514
|
+
}
|
|
515
|
+
if (firstMilestoneComplete && isTinyTask(active)) {
|
|
516
|
+
found.push(`${guidance} The first milestone is complete, so the active Worker should move toward the next real milestone.`);
|
|
517
|
+
}
|
|
518
|
+
if (isMicroWorkerTask(active)) {
|
|
519
|
+
found.push(`${guidance} Active Worker ${active.id} looks like another helper-sized slice.`);
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
if (active.type !== "judge") return [...new Set(found)];
|
|
523
|
+
|
|
524
|
+
let pairs = 0;
|
|
525
|
+
for (let index = activeIndex; index > 0; index -= 2) {
|
|
526
|
+
const judge = tasks[index];
|
|
527
|
+
const worker = tasks[index - 1];
|
|
528
|
+
if (!isMicroJudgeForWorker(judge, worker)) break;
|
|
529
|
+
pairs += 1;
|
|
530
|
+
}
|
|
531
|
+
if (pairs >= 2) {
|
|
532
|
+
found.push(`${guidance} Micro Worker/Judge loop detected ending at ${active.id}.`);
|
|
533
|
+
}
|
|
534
|
+
return [...new Set(found)];
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
function isMicroJudgeForWorker(judge, worker) {
|
|
538
|
+
if (!judge || !worker) return false;
|
|
539
|
+
if (judge.type !== "judge" || worker.type !== "worker") return false;
|
|
540
|
+
if (!["active", "queued", "done"].includes(judge.status) || worker.status !== "done") return false;
|
|
541
|
+
const objective = String(judge.objective || "").toLowerCase();
|
|
542
|
+
return objective.includes(worker.id.toLowerCase()) && /audit|review|approve/.test(objective) && isMicroWorkerTask(worker);
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
function isMicroWorkerTask(task) {
|
|
546
|
+
if (!task || task.type !== "worker") return false;
|
|
547
|
+
const objective = String(task.objective || "").toLowerCase();
|
|
548
|
+
if (/collapsed|batch|package|tranche/.test(objective)) return false;
|
|
549
|
+
return /one narrow|single helper|one helper|per[- ]helper|per[- ]table|projection helper/.test(objective);
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
function isTinyTask(task) {
|
|
553
|
+
if (!task) return false;
|
|
554
|
+
const text = [task.objective, task.raw, task.receipt?.raw].join(" ").toLowerCase();
|
|
555
|
+
if (/collapsed|batch|package|tranche|vertical slice|milestone/.test(text)) return false;
|
|
556
|
+
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);
|
|
557
|
+
}
|
|
558
|
+
|
|
433
559
|
function matchesAllowedFile(file, allowedFiles) {
|
|
434
560
|
return allowedFiles.some((pattern) => globMatch(pattern, file));
|
|
435
561
|
}
|
|
@@ -456,6 +582,9 @@ function escapeRegExp(value) {
|
|
|
456
582
|
}
|
|
457
583
|
|
|
458
584
|
if (goalStatus === "done") {
|
|
585
|
+
if (noCompletionOnWeakProof && (isWeakProof(completionProof) || isWeakProof(oracleSignal) || isWeakProof(oracleFinalProof))) {
|
|
586
|
+
errors.push("done goals require concrete completion proof, goal.oracle.signal, and goal.oracle.final_proof; weak proof cannot close a goal");
|
|
587
|
+
}
|
|
459
588
|
const finalAudit = tasks.some((task) => {
|
|
460
589
|
if (!["judge", "pm"].includes(task.type) || task.status !== "done") return false;
|
|
461
590
|
if (!task.receipt.present || task.receipt.value === null) return false;
|
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
import { existsSync, readFileSync } from "node:fs";
|
|
3
3
|
import { basename, dirname, resolve } from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
|
-
import { parseGoalStateText } from "../
|
|
5
|
+
import { parseGoalStateText } from "../surfaces/local-goal-board/scripts/lib/goal-board.mjs";
|
|
6
6
|
|
|
7
7
|
const ROLE_DEFAULTS = {
|
|
8
8
|
scout: { agent: "goal_scout", reasoning: "low", sandbox: "read-only" },
|
|
9
9
|
judge: { agent: "goal_judge", reasoning: "high", sandbox: "read-only" },
|
|
10
|
-
worker: { agent: "goal_worker", reasoning: "
|
|
10
|
+
worker: { agent: "goal_worker", reasoning: "medium", sandbox: "workspace-write" },
|
|
11
11
|
pm: { agent: "PM", reasoning: "medium", sandbox: "workspace-write" },
|
|
12
12
|
};
|
|
13
13
|
|
|
@@ -39,11 +39,14 @@ export function renderTaskPrompt(options) {
|
|
|
39
39
|
payload: {
|
|
40
40
|
metadata: {
|
|
41
41
|
recommended_agent: defaults.agent,
|
|
42
|
+
required_spawn_agent_type: defaults.agent === "PM" ? null : defaults.agent,
|
|
42
43
|
recommended_reasoning: reasoning,
|
|
43
44
|
sandbox: defaults.sandbox,
|
|
44
45
|
fork_context_allowed: role !== "worker",
|
|
45
46
|
board_path: board.path,
|
|
46
47
|
child_board_paths: childBoardPaths(board),
|
|
48
|
+
goal_oracle: board.goal.oracle || null,
|
|
49
|
+
slice_policy: board.document.rules?.slice_policy || null,
|
|
47
50
|
warnings,
|
|
48
51
|
},
|
|
49
52
|
task: {
|
|
@@ -139,19 +142,63 @@ function promptWarnings(board, task) {
|
|
|
139
142
|
const warnings = [];
|
|
140
143
|
const role = normalizeRole(task.type);
|
|
141
144
|
if (task.id !== board.activeTask) warnings.push(`Task ${task.id} is not the active task on this board.`);
|
|
145
|
+
if (isWeakProof(board.goal.oracle?.signal)) {
|
|
146
|
+
warnings.push("goal.oracle.signal is missing or placeholder-like; keep the goal pressured by a concrete completion oracle.");
|
|
147
|
+
}
|
|
148
|
+
if (isWeakProof(board.goal.oracle?.final_proof)) {
|
|
149
|
+
warnings.push("goal.oracle.final_proof is missing or placeholder-like; do not mark the goal complete without receipt-backed proof.");
|
|
150
|
+
}
|
|
142
151
|
if (role === "worker") {
|
|
143
152
|
if (stringList(task.allowed_files).length === 0) warnings.push(`Worker task ${task.id} has no allowed_files.`);
|
|
144
153
|
if (stringList(task.verify).length === 0) warnings.push(`Worker task ${task.id} has no verify commands.`);
|
|
145
154
|
if (stringList(task.stop_if).length === 0) warnings.push(`Worker task ${task.id} has no stop_if conditions.`);
|
|
155
|
+
if (isFalse(board.goal.full_outcome_complete)) {
|
|
156
|
+
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.`);
|
|
157
|
+
}
|
|
146
158
|
}
|
|
147
159
|
for (const candidate of board.tasks) {
|
|
148
160
|
if (candidate?.subgoal && Number(candidate.subgoal.depth) !== 1) {
|
|
149
161
|
warnings.push(`Task ${candidate.id} has subgoal.depth ${candidate.subgoal.depth || "<missing>"}; only depth 1 is supported.`);
|
|
150
162
|
}
|
|
151
163
|
}
|
|
164
|
+
warnings.push(...microSliceWarnings(board, task));
|
|
152
165
|
return warnings;
|
|
153
166
|
}
|
|
154
167
|
|
|
168
|
+
function microSliceWarnings(board, task) {
|
|
169
|
+
const warnings = [];
|
|
170
|
+
const doneTasks = board.tasks.filter((candidate) => candidate?.status === "done");
|
|
171
|
+
const recentWorkers = board.tasks
|
|
172
|
+
.filter((candidate) => normalizeRole(candidate?.type) === "worker")
|
|
173
|
+
.slice(-5);
|
|
174
|
+
const recentTinyWorkers = recentWorkers.filter((candidate) => isTinyTask(candidate));
|
|
175
|
+
const activeRole = normalizeRole(task.type);
|
|
176
|
+
const activeAllowedFiles = stringList(task.allowed_files);
|
|
177
|
+
const firstMilestoneComplete = isTrue(board.goal.first_milestone_complete);
|
|
178
|
+
const microWarning = "Board may be micro-slicing. Prefer the largest safe useful slice.";
|
|
179
|
+
|
|
180
|
+
if (recentTinyWorkers.length >= 3) warnings.push(microWarning);
|
|
181
|
+
if (doneTasks.length >= 10 && activeRole === "worker" && activeAllowedFiles.length > 0 && activeAllowedFiles.length <= 2) {
|
|
182
|
+
warnings.push(`${microWarning} Active Worker ${task.id} has only ${activeAllowedFiles.length} allowed_files after ${doneTasks.length} completed tasks.`);
|
|
183
|
+
}
|
|
184
|
+
if (firstMilestoneComplete && activeRole === "worker" && isTinyTask(task)) {
|
|
185
|
+
warnings.push(`${microWarning} The first milestone is complete, so the active Worker should move toward the next real milestone.`);
|
|
186
|
+
}
|
|
187
|
+
if (activeRole === "judge" && /pick small reviewable work|select one narrow next task/i.test(String(task.objective || "") + "\n" + stringList(task.constraints).join("\n"))) {
|
|
188
|
+
warnings.push(`${microWarning} Judge instructions still ask for small or narrow work.`);
|
|
189
|
+
}
|
|
190
|
+
return [...new Set(warnings)];
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function isTinyTask(task) {
|
|
194
|
+
const text = [
|
|
195
|
+
task?.objective,
|
|
196
|
+
stringList(task?.constraints).join(" "),
|
|
197
|
+
task?.receipt?.summary,
|
|
198
|
+
].join(" ").toLowerCase();
|
|
199
|
+
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);
|
|
200
|
+
}
|
|
201
|
+
|
|
155
202
|
function normalizeRole(value) {
|
|
156
203
|
const role = String(value || "pm").toLowerCase();
|
|
157
204
|
return ROLE_DEFAULTS[role] ? role : "pm";
|
|
@@ -163,6 +210,25 @@ function normalizeReasoning(value, fallback) {
|
|
|
163
210
|
return fallback;
|
|
164
211
|
}
|
|
165
212
|
|
|
213
|
+
function isFalse(value) {
|
|
214
|
+
return value === false || String(value).toLowerCase() === "false";
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function isTrue(value) {
|
|
218
|
+
return value === true || String(value).toLowerCase() === "true";
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function isWeakProof(value) {
|
|
222
|
+
if (value === null || value === undefined) return true;
|
|
223
|
+
const normalized = String(value).trim().toLowerCase();
|
|
224
|
+
return normalized === ""
|
|
225
|
+
|| normalized === "unknown"
|
|
226
|
+
|| normalized === "tbd"
|
|
227
|
+
|| normalized === "todo"
|
|
228
|
+
|| normalized === "none"
|
|
229
|
+
|| /^<.*>$/.test(normalized);
|
|
230
|
+
}
|
|
231
|
+
|
|
166
232
|
function stringList(value) {
|
|
167
233
|
return Array.isArray(value) ? value.filter((item) => item !== null && item !== undefined).map(String) : [];
|
|
168
234
|
}
|
|
@@ -175,15 +241,14 @@ function receiptSchema(role) {
|
|
|
175
241
|
commands: [{ cmd: "<command>", status: "pass | fail | not_run" }],
|
|
176
242
|
summary: "<=120 words",
|
|
177
243
|
remaining_blockers: [],
|
|
178
|
-
needs_judge: false,
|
|
179
244
|
};
|
|
180
245
|
}
|
|
181
246
|
if (role === "judge") {
|
|
182
247
|
return {
|
|
183
248
|
result: "done | blocked",
|
|
184
|
-
decision: "
|
|
249
|
+
decision: "approved | rejected | approve_subgoal | reject_subgoal | not_complete | complete",
|
|
250
|
+
full_outcome_complete: false,
|
|
185
251
|
evidence: [],
|
|
186
|
-
next_allowed_task: null,
|
|
187
252
|
blocked_tasks: [],
|
|
188
253
|
required_board_updates: [],
|
|
189
254
|
};
|
|
@@ -204,6 +269,7 @@ function formatPrompt(payload) {
|
|
|
204
269
|
"",
|
|
205
270
|
"Metadata:",
|
|
206
271
|
`- recommended_agent: ${payload.metadata.recommended_agent}`,
|
|
272
|
+
`- required_spawn_agent_type: ${payload.metadata.required_spawn_agent_type || "PM fallback"}`,
|
|
207
273
|
`- recommended_reasoning: ${payload.metadata.recommended_reasoning}`,
|
|
208
274
|
`- sandbox: ${payload.metadata.sandbox}`,
|
|
209
275
|
`- fork_context_allowed: ${payload.metadata.fork_context_allowed}`,
|
|
@@ -213,12 +279,24 @@ function formatPrompt(payload) {
|
|
|
213
279
|
lines.push("- child_board_paths:");
|
|
214
280
|
for (const path of payload.metadata.child_board_paths) lines.push(` - ${path}`);
|
|
215
281
|
}
|
|
282
|
+
if (payload.metadata.goal_oracle) {
|
|
283
|
+
lines.push(`- goal_oracle: ${JSON.stringify(payload.metadata.goal_oracle)}`);
|
|
284
|
+
}
|
|
285
|
+
if (payload.metadata.slice_policy) {
|
|
286
|
+
lines.push(`- slice_policy: ${JSON.stringify(payload.metadata.slice_policy)}`);
|
|
287
|
+
}
|
|
216
288
|
if (payload.metadata.warnings.length) {
|
|
217
289
|
lines.push("- warnings:");
|
|
218
290
|
for (const warning of payload.metadata.warnings) lines.push(` - ${warning}`);
|
|
219
291
|
}
|
|
220
292
|
|
|
221
293
|
lines.push(
|
|
294
|
+
"",
|
|
295
|
+
"Spawn contract:",
|
|
296
|
+
`- Codex spawn_agent agent_type: ${payload.metadata.required_spawn_agent_type || "do not spawn; run as PM"}`,
|
|
297
|
+
"- Do not substitute generic scout, worker, or judge agents for GoalBuddy agents.",
|
|
298
|
+
"- If the required GoalBuddy agent is unavailable, stop spawning and continue as PM fallback or install agents.",
|
|
299
|
+
"- After one wait_agent timeout with no visible allowed-file changes, stop waiting and recover deterministically.",
|
|
222
300
|
"",
|
|
223
301
|
"Task:",
|
|
224
302
|
`- id: ${payload.task.id}`,
|
package/{plugins/goalbuddy/skills/goalbuddy/extend → goalbuddy/surfaces}/local-goal-board/README.md
RENAMED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Generate a small local GoalBuddy board for a goal directory and watch it update live while agents work.
|
|
4
4
|
|
|
5
|
-
The
|
|
5
|
+
The surface keeps `state.yaml` authoritative. It writes static web app files into the goal directory and serves them from a local-only Node server. The browser subscribes to Server-Sent Events, so cards update as `state.yaml`, `notes/`, or linked depth-1 sub-goal state changes without a manual reload.
|
|
6
6
|
|
|
7
7
|
## Use When
|
|
8
8
|
|
|
@@ -14,11 +14,10 @@ The extension keeps `state.yaml` authoritative. It writes static web app files i
|
|
|
14
14
|
## Generate And Serve
|
|
15
15
|
|
|
16
16
|
```bash
|
|
17
|
-
|
|
18
|
-
--goal docs/goals/<slug>
|
|
17
|
+
npx goalbuddy board docs/goals/<slug>
|
|
19
18
|
```
|
|
20
19
|
|
|
21
|
-
The generated app includes the bundled `assets/goalbuddy-mark.png`, so the board keeps the GoalBuddy mark
|
|
20
|
+
The generated app includes the bundled `assets/goalbuddy-mark.png`, so the board keeps the GoalBuddy mark anywhere the package is installed.
|
|
22
21
|
|
|
23
22
|
The command writes:
|
|
24
23
|
|
|
@@ -34,8 +33,7 @@ Then it starts or reuses the shared local board hub at `http://goalbuddy.localho
|
|
|
34
33
|
## Check Without A Long-Running Server
|
|
35
34
|
|
|
36
35
|
```bash
|
|
37
|
-
|
|
38
|
-
--goal docs/goals/<slug> \
|
|
36
|
+
npx goalbuddy board docs/goals/<slug> \
|
|
39
37
|
--once \
|
|
40
38
|
--json
|
|
41
39
|
```
|
|
@@ -63,9 +61,9 @@ Clicking a card opens a detail modal with the task objective, status, assignee,
|
|
|
63
61
|
## Verification
|
|
64
62
|
|
|
65
63
|
```bash
|
|
66
|
-
node --test
|
|
67
|
-
node
|
|
68
|
-
--goal
|
|
64
|
+
node --test goalbuddy/surfaces/local-goal-board/test/*.test.mjs
|
|
65
|
+
node goalbuddy/surfaces/local-goal-board/scripts/local-goal-board.mjs \
|
|
66
|
+
--goal goalbuddy/surfaces/local-goal-board/examples/sample-goal \
|
|
69
67
|
--once \
|
|
70
68
|
--json
|
|
71
69
|
```
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
version: 2
|
|
2
2
|
|
|
3
3
|
goal:
|
|
4
|
-
title: "Local
|
|
5
|
-
slug: "local-
|
|
4
|
+
title: "Local Goal Board Surface"
|
|
5
|
+
slug: "local-goal-board-surface"
|
|
6
6
|
kind: specific
|
|
7
7
|
tranche: "Demonstrate local GoalBuddy board rendering."
|
|
8
8
|
status: active
|
|
@@ -30,7 +30,7 @@ tasks:
|
|
|
30
30
|
type: worker
|
|
31
31
|
assignee: Worker
|
|
32
32
|
status: blocked
|
|
33
|
-
objective: "Catalog and document the local board
|
|
33
|
+
objective: "Catalog and document the local board surface."
|
|
34
34
|
receipt:
|
|
35
35
|
result: blocked
|
|
36
36
|
summary: "T003 is blocked during the progressive board motion demo."
|
|
@@ -78,7 +78,7 @@ tasks:
|
|
|
78
78
|
type: scout
|
|
79
79
|
assignee: Scout
|
|
80
80
|
status: done
|
|
81
|
-
objective: "List the
|
|
81
|
+
objective: "List the board launch paths."
|
|
82
82
|
receipt:
|
|
83
83
|
result: done
|
|
84
84
|
summary: "T009 completed during the progressive board motion demo."
|
|
@@ -118,7 +118,7 @@ tasks:
|
|
|
118
118
|
type: worker
|
|
119
119
|
assignee: Worker
|
|
120
120
|
status: done
|
|
121
|
-
objective: "Prepare final
|
|
121
|
+
objective: "Prepare final board packaging check."
|
|
122
122
|
receipt:
|
|
123
123
|
result: done
|
|
124
124
|
summary: "T014 completed during the progressive board motion demo."
|
|
@@ -31,10 +31,10 @@ tasks:
|
|
|
31
31
|
status: active
|
|
32
32
|
objective: "Build the sub-goal board view."
|
|
33
33
|
allowed_files:
|
|
34
|
-
- goalbuddy/
|
|
35
|
-
- goalbuddy/
|
|
34
|
+
- goalbuddy/surfaces/local-goal-board/scripts/lib/goal-board.mjs
|
|
35
|
+
- goalbuddy/surfaces/local-goal-board/test/local-goal-board.test.mjs
|
|
36
36
|
verify:
|
|
37
|
-
- node --test goalbuddy/
|
|
37
|
+
- node --test goalbuddy/surfaces/local-goal-board/test/local-goal-board.test.mjs
|
|
38
38
|
stop_if:
|
|
39
39
|
- "Need files outside allowed_files."
|
|
40
40
|
subgoal:
|
|
@@ -24,16 +24,16 @@ tasks:
|
|
|
24
24
|
result: done
|
|
25
25
|
summary: "Child board payload needs normal columns and task details."
|
|
26
26
|
evidence:
|
|
27
|
-
- goalbuddy/
|
|
27
|
+
- goalbuddy/surfaces/local-goal-board/scripts/lib/goal-board.mjs
|
|
28
28
|
- id: T002
|
|
29
29
|
type: worker
|
|
30
30
|
assignee: Worker
|
|
31
31
|
status: active
|
|
32
32
|
objective: "Render the read-only embedded child board."
|
|
33
33
|
allowed_files:
|
|
34
|
-
- goalbuddy/
|
|
34
|
+
- goalbuddy/surfaces/local-goal-board/scripts/lib/goal-board.mjs
|
|
35
35
|
verify:
|
|
36
|
-
- node --test goalbuddy/
|
|
36
|
+
- node --test goalbuddy/surfaces/local-goal-board/test/local-goal-board.test.mjs
|
|
37
37
|
stop_if:
|
|
38
38
|
- "Need files outside allowed_files."
|
|
39
39
|
receipt: null
|