goalbuddy 0.3.7 → 0.3.9
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/.claude-plugin/marketplace.json +16 -0
- package/CHANGELOG.md +70 -0
- package/CONTRIBUTING.md +2 -2
- package/README.md +11 -3
- package/{RELEASE-0.3.7.md → docs/releases/0.3.7.md} +2 -0
- package/docs/releases/0.3.8.md +40 -0
- package/docs/releases/0.3.9.md +46 -0
- package/docs/releases/README.md +84 -0
- package/goalbuddy/SKILL.md +26 -8
- package/goalbuddy/scripts/check-goal-state.mjs +22 -4
- package/goalbuddy/scripts/check-update.mjs +18 -1
- package/goalbuddy/scripts/render-task-prompt.mjs +17 -3
- package/goalbuddy/surfaces/local-goal-board/scripts/lib/goal-board.mjs +16 -15
- package/goalbuddy/surfaces/local-goal-board/scripts/local-goal-board.mjs +25 -3
- package/goalbuddy/surfaces/local-goal-board/test/local-goal-board.test.mjs +189 -4
- package/goalbuddy/templates/goal.md +12 -0
- package/goalbuddy/templates/state.yaml +2 -1
- package/internal/cli/goal-maker.mjs +186 -7
- package/package.json +6 -6
- package/plugins/goalbuddy/.claude-plugin/plugin.json +1 -1
- package/plugins/goalbuddy/.codex-plugin/plugin.json +1 -1
- package/plugins/goalbuddy/README.md +1 -1
- package/plugins/goalbuddy/skills/goalbuddy/SKILL.md +26 -8
- package/plugins/goalbuddy/skills/goalbuddy/scripts/check-goal-state.mjs +22 -4
- package/plugins/goalbuddy/skills/goalbuddy/scripts/check-update.mjs +18 -1
- package/plugins/goalbuddy/skills/goalbuddy/scripts/render-task-prompt.mjs +17 -3
- package/plugins/goalbuddy/skills/goalbuddy/surfaces/local-goal-board/scripts/lib/goal-board.mjs +1 -4
- package/plugins/goalbuddy/skills/goalbuddy/surfaces/local-goal-board/scripts/local-goal-board.mjs +25 -3
- package/plugins/goalbuddy/skills/goalbuddy/surfaces/local-goal-board/test/local-goal-board.test.mjs +27 -0
- package/plugins/goalbuddy/skills/goalbuddy/templates/goal.md +12 -0
- package/plugins/goalbuddy/skills/goalbuddy/templates/state.yaml +2 -1
- package/examples/improve-goal-maker/goal.md +0 -51
- package/examples/improve-goal-maker/notes/T001-repo-map.md +0 -59
- package/examples/improve-goal-maker/notes/T002-risk-map.md +0 -37
- package/examples/improve-goal-maker/state.yaml +0 -224
- /package/{RELEASE-0.3.5.md → docs/releases/0.3.5.md} +0 -0
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
GoalBuddy packages the canonical `goal-prep` skill as a plugin so teams can install the reusable workflow in **Codex** and **Claude Code**, while keeping the npm CLI for local setup, doctor checks, and the built-in local board surface.
|
|
4
4
|
|
|
5
|
-
Version 0.3.
|
|
5
|
+
Version 0.3.9 keeps the Goalmaxxed core and hardens marketplace installs, local board rendering, and PM runtime guidance for approval waits and board-health checks.
|
|
6
6
|
|
|
7
7
|
## What It Contains
|
|
8
8
|
|
|
@@ -58,7 +58,7 @@ node <skill-path>/scripts/check-update.mjs --json
|
|
|
58
58
|
If the checker reports `update_available: true`, tell the user once before continuing:
|
|
59
59
|
|
|
60
60
|
```text
|
|
61
|
-
GoalBuddy <latest_version> is available. After this turn, update
|
|
61
|
+
GoalBuddy <latest_version> is available. After this turn, update through the channel that installed GoalBuddy: `/plugin update goalbuddy@goalbuddy`, `npx goalbuddy@latest`, `npm i -g goalbuddy`, `pnpm update -g goalbuddy`, `bun update -g goalbuddy`, or `mise upgrade npm:goalbuddy`.
|
|
62
62
|
```
|
|
63
63
|
|
|
64
64
|
Do not block intake or board creation on update checking. If the checker is missing, cannot find npm, or network access fails, continue silently unless the user asked about updates.
|
|
@@ -93,7 +93,9 @@ Recommended options:
|
|
|
93
93
|
1. Local live board (Recommended) - starts immediately, requires no credentials, and lets the user watch tasks populate inside Codex or Claude Code.
|
|
94
94
|
2. No visual board - best for quick or private goals where the file board is enough.
|
|
95
95
|
|
|
96
|
-
If the user chooses the local live board, create the goal directory, `notes/`, and an initial minimal `state.yaml` as soon as the slug is known, then run `
|
|
96
|
+
If the user chooses the local live board, create the goal directory, `notes/`, and an initial minimal `state.yaml` as soon as the slug is known, then run `node <skill-path>/surfaces/local-goal-board/scripts/local-goal-board.mjs --goal docs/goals/<slug>` and open the printed local URL in the AI coding agent's in-app browser (the Codex in-app Browser, the Claude Code preview, or the user's regular browser). The default local hub is `http://goalbuddy.localhost:41737/`, and board URLs normally look like `http://goalbuddy.localhost:41737/<slug>/`. In short: start the local board before filling the task list so the board pops up right away and cards populate live as `state.yaml` changes. Include the printed board URL in the final prep response as an actual clickable Markdown link, for example `[Open GoalBuddy board](http://goalbuddy.localhost:41737/<slug>/)`. Do not put the board URL only in a code block, quote, HTML comment, or prose that the UI cannot click.
|
|
97
|
+
|
|
98
|
+
If `http://goalbuddy.localhost:41737/<slug>/` returns 404, do not assume the existing process is stale and do not stop it. First check `http://127.0.0.1:41737/api/boards`. If that endpoint returns board JSON, the port is the shared multi-board hub; rerun `node <skill-path>/surfaces/local-goal-board/scripts/local-goal-board.mjs --goal <absolute-goal-path>` if needed so the new goal registers on the same port. Only stop a specific process on 41737 when `/api/boards` is missing, returns 404, or otherwise proves the listener is not a current GoalBuddy multi-board hub.
|
|
97
99
|
|
|
98
100
|
If the user wants an external board, GitHub sync, Slack digest, Linear handoff, or any other custom integration, do not install a GoalBuddy catalog item. Treat it as normal implementation work: create a concrete task that designs and verifies that integration inside the target repo or asks the operator for the required credentials and scope.
|
|
99
101
|
|
|
@@ -472,6 +474,22 @@ Blocked tasks do not necessarily block the goal. The PM should keep doing safe l
|
|
|
472
474
|
|
|
473
475
|
Avoid setting `goal.status: blocked` for missing input, credentials, production access, destructive-operation permission, or policy decisions. Block the specific task instead, record the missing requirement, and continue with every safe local workaround or adjacent slice.
|
|
474
476
|
|
|
477
|
+
Exception: if an exact human approval phrase is the only remaining blocker and no safe local work remains, ask once, preserve the exact phrase, and stop. Set `goal.status: blocked`, set `active_task: null`, mark every unfinished task `blocked`, and write a receipt with `result: blocked`, `waiting_for_user_approval: true`, and `required_reply: "<exact phrase>"`. Do not rephrase, retry, spawn follow-up work, or post another approval prompt until the user replies.
|
|
478
|
+
|
|
479
|
+
## Board Health Stewardship
|
|
480
|
+
|
|
481
|
+
The PM owns board health. Do not auto-spawn a separate always-on steward by default.
|
|
482
|
+
|
|
483
|
+
When the board looks stale, misleading, offline, Not Found, or inconsistent, run the bundled checker:
|
|
484
|
+
|
|
485
|
+
```bash
|
|
486
|
+
node <skill-path>/scripts/check-goal-state.mjs docs/goals/<slug>
|
|
487
|
+
```
|
|
488
|
+
|
|
489
|
+
If a local board server is running, compare `state.yaml` with `http://127.0.0.1:41737/<slug>/api/board` or `http://127.0.0.1:41737/api/boards`. Repair only GoalBuddy control files: `goal.md`, `state.yaml`, `notes/`, depth-1 `subgoals/`, and `.goalbuddy-board/`. Never edit product implementation files during board-health work unless there is an active Worker or PM task with explicit `allowed_files`.
|
|
490
|
+
|
|
491
|
+
Board-health work should verify these truths: `active_task` matches live task status, done and blocked tasks have receipts, human-blocked work is in the blocked column, future work stays queued, and the live board/API reflects `state.yaml`.
|
|
492
|
+
|
|
475
493
|
## Operator Escalation
|
|
476
494
|
|
|
477
495
|
When Scout, Judge, Worker, or PM discovers a problem, improvement opportunity, product suggestion, follow-up repair, or tool limitation that should not be fixed inside the current active task, do not let it disappear in chat.
|
|
@@ -526,9 +544,9 @@ Use these `state.yaml` values:
|
|
|
526
544
|
| State | Meaning | Next action |
|
|
527
545
|
|---|---|---|
|
|
528
546
|
| `installed` | Matching Scout/Worker/Judge agent configs were found in the expected user or project agent location. | Continue. |
|
|
529
|
-
| `bundled_not_installed` | The bundled `goal_*.toml` template exists with the skill, but no matching installed agent config was verified. | `/goal` can proceed through PM fallback. If dedicated agents are required before `/goal`, run
|
|
530
|
-
| `missing` | Neither an installed config nor the bundled template was verified. | `/goal` can proceed through PM fallback. If dedicated agents are required before `/goal`, run
|
|
531
|
-
| `unknown` | Agent availability could not be checked. | `/goal` can proceed through PM fallback. To check before `/goal`, run
|
|
547
|
+
| `bundled_not_installed` | The bundled `goal_*.toml` template exists with the skill, but no matching installed agent config was verified. | `/goal` can proceed through PM fallback. If dedicated agents are required before `/goal`, run the GoalBuddy CLI through the user's install channel with `agents`. |
|
|
548
|
+
| `missing` | Neither an installed config nor the bundled template was verified. | `/goal` can proceed through PM fallback. If dedicated agents are required before `/goal`, run the GoalBuddy CLI through the user's install channel with `install`. |
|
|
549
|
+
| `unknown` | Agent availability could not be checked. | `/goal` can proceed through PM fallback. To check before `/goal`, run the GoalBuddy CLI through the user's install channel with `doctor`. |
|
|
532
550
|
|
|
533
551
|
Non-`installed` states are warnings, not false failures, because the main `/goal` PM can perform Scout/Judge/Worker-shaped tasks directly when dedicated agents are unavailable.
|
|
534
552
|
|
|
@@ -568,11 +586,11 @@ Treat `reasoning_hint` as PM guidance. It does not override task scope, write pe
|
|
|
568
586
|
|
|
569
587
|
## Execution Quality Commands
|
|
570
588
|
|
|
571
|
-
Use `
|
|
589
|
+
Use `node <skill-path>/scripts/render-task-prompt.mjs docs/goals/<slug>` to render a compact prompt for the active task. The prompt includes only task-specific material, safe agent metadata, continuation warnings, and the expected receipt shape. It should not include broad chat history or dump the whole state file.
|
|
572
590
|
|
|
573
|
-
When dispatching Codex subagents from a GoalBuddy prompt, the `required_spawn_agent_type` is mandatory. Use that exact `spawn_agent` `agent_type` (`goal_scout`, `goal_worker`, or `goal_judge`). Do not substitute generic `scout`, `worker`, or `judge` agents; if the required GoalBuddy agent is unavailable, stop spawning and continue as PM fallback or run
|
|
591
|
+
When dispatching Codex subagents from a GoalBuddy prompt, the `required_spawn_agent_type` is mandatory. Use that exact `spawn_agent` `agent_type` (`goal_scout`, `goal_worker`, or `goal_judge`). Do not substitute generic `scout`, `worker`, or `judge` agents; if the required GoalBuddy agent is unavailable, stop spawning and continue as PM fallback or ask the operator to run the GoalBuddy CLI through their install channel with `agents` or `install`. After one `wait_agent` timeout with no visible allowed-file changes, stop waiting, record the timeout, and recover deterministically instead of waiting forever.
|
|
574
592
|
|
|
575
|
-
Use `
|
|
593
|
+
Use `node <skill-path>/scripts/parallel-plan.mjs docs/goals/<slug>` when the user explicitly asks for parallel agent work. It is read-only: it recommends safe Scout/Judge handoffs and Worker handoffs only when write scopes are known and disjoint. It does not mutate `state.yaml`, create sub-goals, apply receipts, or spawn agents.
|
|
576
594
|
|
|
577
595
|
## Completion
|
|
578
596
|
|
|
@@ -297,12 +297,12 @@ if (isWeakProof(completionProof)) {
|
|
|
297
297
|
function agentStatusWarning(agent, status) {
|
|
298
298
|
const agentLabel = agent[0].toUpperCase() + agent.slice(1);
|
|
299
299
|
if (status === "bundled_not_installed") {
|
|
300
|
-
return `agents.${agent} is bundled_not_installed; /goal can continue through PM fallback, but dedicated ${agentLabel} delegation is unavailable until installed. If dedicated agents are required before /goal, run
|
|
300
|
+
return `agents.${agent} is bundled_not_installed; /goal can continue through PM fallback, but dedicated ${agentLabel} delegation is unavailable until installed. If dedicated agents are required before /goal, run the GoalBuddy CLI through the user's install channel with: agents`;
|
|
301
301
|
}
|
|
302
302
|
if (status === "missing") {
|
|
303
|
-
return `agents.${agent} is missing; /goal can continue through PM fallback, but dedicated ${agentLabel} delegation is unavailable. If dedicated agents are required before /goal, run
|
|
303
|
+
return `agents.${agent} is missing; /goal can continue through PM fallback, but dedicated ${agentLabel} delegation is unavailable. If dedicated agents are required before /goal, run the GoalBuddy CLI through the user's install channel with: install`;
|
|
304
304
|
}
|
|
305
|
-
return `agents.${agent} is unknown; /goal can continue through PM fallback, but dedicated ${agentLabel} delegation was not verified. To check before /goal, run
|
|
305
|
+
return `agents.${agent} is unknown; /goal can continue through PM fallback, but dedicated ${agentLabel} delegation was not verified. To check before /goal, run the GoalBuddy CLI through the user's install channel with: doctor`;
|
|
306
306
|
}
|
|
307
307
|
|
|
308
308
|
for (const { agent, status } of agentStatuses) {
|
|
@@ -353,6 +353,7 @@ for (const task of tasks) {
|
|
|
353
353
|
if (tasks.length === 0) errors.push("tasks must contain at least one task");
|
|
354
354
|
|
|
355
355
|
const activeTasks = tasks.filter((task) => task.status === "active");
|
|
356
|
+
const terminalApprovalWait = isTerminalApprovalWait(tasks, activeTasks, activeTask);
|
|
356
357
|
if (goalStatus === "done") {
|
|
357
358
|
if (activeTasks.length !== 0) errors.push("done goals must not have an active task");
|
|
358
359
|
if (activeTask !== null) errors.push("done goals must set active_task: null");
|
|
@@ -364,7 +365,7 @@ if (goalStatus === "done") {
|
|
|
364
365
|
}
|
|
365
366
|
} else if (goalStatus === "blocked") {
|
|
366
367
|
if (activeTasks.length > 1) errors.push("blocked goals may have at most one active task");
|
|
367
|
-
if (continuousUntilFullOutcome && missingInputOrCredentialsDoNotStopGoal) {
|
|
368
|
+
if (continuousUntilFullOutcome && missingInputOrCredentialsDoNotStopGoal && !terminalApprovalWait) {
|
|
368
369
|
errors.push("continuous goals must keep goal.status active; missing input or credentials should block specific tasks, not the whole goal");
|
|
369
370
|
}
|
|
370
371
|
} else if (activeTasks.length !== 1) {
|
|
@@ -436,6 +437,23 @@ for (const task of tasks) {
|
|
|
436
437
|
|
|
437
438
|
warnings.push(...microSliceWarnings(tasks, activeTask, goalStatus));
|
|
438
439
|
|
|
440
|
+
function isTerminalApprovalWait(tasks, activeTasks, activeTask) {
|
|
441
|
+
if (goalStatus !== "blocked") return false;
|
|
442
|
+
if (activeTask !== null) return false;
|
|
443
|
+
if (activeTasks.length !== 0) return false;
|
|
444
|
+
|
|
445
|
+
const unfinishedTasks = tasks.filter((task) => task.status !== "done");
|
|
446
|
+
if (unfinishedTasks.length === 0) return false;
|
|
447
|
+
if (unfinishedTasks.some((task) => task.status !== "blocked")) return false;
|
|
448
|
+
|
|
449
|
+
return unfinishedTasks.some((task) => {
|
|
450
|
+
if (!task.receipt.present || task.receipt.value === null) return false;
|
|
451
|
+
return task.receipt.scalar("result") === "blocked"
|
|
452
|
+
&& task.receipt.scalar("waiting_for_user_approval") === true
|
|
453
|
+
&& Boolean(task.receipt.scalar("required_reply"));
|
|
454
|
+
});
|
|
455
|
+
}
|
|
456
|
+
|
|
439
457
|
function validateSubgoal(task) {
|
|
440
458
|
if (isChildCheck) {
|
|
441
459
|
errors.push(`child task ${task.id} must not contain a nested subgoal`);
|
|
@@ -14,7 +14,7 @@ const report = {
|
|
|
14
14
|
latest_version: null,
|
|
15
15
|
update_available: false,
|
|
16
16
|
check_status: "unknown",
|
|
17
|
-
update_command:
|
|
17
|
+
update_command: detectUpdateCommand(),
|
|
18
18
|
};
|
|
19
19
|
|
|
20
20
|
try {
|
|
@@ -77,6 +77,23 @@ function latestPublishedVersion() {
|
|
|
77
77
|
return normalizeVersion(result.stdout);
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
+
function detectUpdateCommand() {
|
|
81
|
+
if (process.env.GOALBUDDY_TEST_UPDATE_COMMAND) return process.env.GOALBUDDY_TEST_UPDATE_COMMAND;
|
|
82
|
+
if (process.env.CLAUDE_PLUGIN_ROOT || normalizedPath(scriptDir).includes("/.claude/")) return "/plugin update goalbuddy@goalbuddy";
|
|
83
|
+
|
|
84
|
+
const userAgent = process.env.npm_config_user_agent || "";
|
|
85
|
+
if (/^pnpm\//.test(userAgent)) return "pnpm update -g goalbuddy";
|
|
86
|
+
if (/^bun\//.test(userAgent)) return "bun update -g goalbuddy";
|
|
87
|
+
if (process.env.MISE_EXE || process.env.MISE_SHELL || process.env.MISE_PROJECT_ROOT) return "mise upgrade npm:goalbuddy";
|
|
88
|
+
if (/^npm\//.test(userAgent)) return "npx goalbuddy@latest";
|
|
89
|
+
|
|
90
|
+
return "use the install channel that installed GoalBuddy";
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function normalizedPath(path) {
|
|
94
|
+
return String(path).replace(/\\/g, "/");
|
|
95
|
+
}
|
|
96
|
+
|
|
80
97
|
function readJson(path) {
|
|
81
98
|
if (!existsSync(path)) return null;
|
|
82
99
|
try {
|
|
@@ -237,29 +237,43 @@ function receiptSchema(role) {
|
|
|
237
237
|
if (role === "worker") {
|
|
238
238
|
return {
|
|
239
239
|
result: "done | blocked",
|
|
240
|
+
task_id: "<T###>",
|
|
241
|
+
board_path: "<path to state.yaml>",
|
|
240
242
|
changed_files: [],
|
|
241
|
-
commands: [
|
|
242
|
-
summary: "<=120 words",
|
|
243
|
+
commands: [],
|
|
244
|
+
summary: "<=120 words>",
|
|
243
245
|
remaining_blockers: [],
|
|
246
|
+
verification_attempts: 1,
|
|
247
|
+
stopped_because: null,
|
|
244
248
|
};
|
|
245
249
|
}
|
|
246
250
|
if (role === "judge") {
|
|
247
251
|
return {
|
|
248
252
|
result: "done | blocked",
|
|
253
|
+
task_id: "<T###>",
|
|
254
|
+
board_path: "<path to state.yaml>",
|
|
249
255
|
decision: "approved | rejected | approve_subgoal | reject_subgoal | not_complete | complete",
|
|
250
256
|
full_outcome_complete: false,
|
|
257
|
+
rationale: "<=120 words>",
|
|
251
258
|
evidence: [],
|
|
259
|
+
subgoal_contract: null,
|
|
260
|
+
parallel_safety: null,
|
|
252
261
|
blocked_tasks: [],
|
|
262
|
+
missing_evidence: [],
|
|
253
263
|
required_board_updates: [],
|
|
254
264
|
};
|
|
255
265
|
}
|
|
256
266
|
return {
|
|
257
267
|
result: "done | blocked",
|
|
258
|
-
|
|
268
|
+
task_id: "<T###>",
|
|
269
|
+
board_path: "<path to state.yaml>",
|
|
270
|
+
summary: "<=120 words>",
|
|
259
271
|
evidence: [],
|
|
260
272
|
facts: [],
|
|
261
273
|
contradictions: [],
|
|
262
274
|
ambiguity_requiring_judge: [],
|
|
275
|
+
commands: [],
|
|
276
|
+
note_needed: false,
|
|
263
277
|
};
|
|
264
278
|
}
|
|
265
279
|
|
package/plugins/goalbuddy/skills/goalbuddy/surfaces/local-goal-board/scripts/lib/goal-board.mjs
CHANGED
|
@@ -88,9 +88,6 @@ export function normalizeGoalBoard(document, goalDir = "<memory>") {
|
|
|
88
88
|
|
|
89
89
|
const tasks = document.tasks.map((task, index) => normalizeTask(task, index));
|
|
90
90
|
const activeTasks = tasks.filter((task) => task.status === "active");
|
|
91
|
-
if (activeTasks.length > 1) {
|
|
92
|
-
throw new GoalBoardError("Goal state has more than one active task.");
|
|
93
|
-
}
|
|
94
91
|
|
|
95
92
|
return {
|
|
96
93
|
goalDir,
|
|
@@ -148,7 +145,7 @@ export function buildColumns(tasks) {
|
|
|
148
145
|
|
|
149
146
|
return [
|
|
150
147
|
{ id: "todo", title: "Todo", description: "Queued work ready to pull", tasks: byColumn.get("todo") },
|
|
151
|
-
{ id: "in-progress", title: "In Progress", description: "
|
|
148
|
+
{ id: "in-progress", title: "In Progress", description: "Active task work", tasks: byColumn.get("in-progress") },
|
|
152
149
|
{ id: "blocked", title: "Blocked", description: "Needs unblock or a smaller slice", tasks: byColumn.get("blocked") },
|
|
153
150
|
{ id: "completed", title: "Completed", description: "Receipted work", tasks: byColumn.get("completed") },
|
|
154
151
|
];
|
package/plugins/goalbuddy/skills/goalbuddy/surfaces/local-goal-board/scripts/local-goal-board.mjs
CHANGED
|
@@ -33,6 +33,7 @@ const SETTINGS_OPTIONS = {
|
|
|
33
33
|
const DEFAULT_BIND_HOST = "127.0.0.1";
|
|
34
34
|
const DEFAULT_PUBLIC_HOST = "goalbuddy.localhost";
|
|
35
35
|
const DEFAULT_PORT = 41737;
|
|
36
|
+
const STATE_CHANGE_SETTLE_MS = 300;
|
|
36
37
|
|
|
37
38
|
if (isDirectRun()) {
|
|
38
39
|
main().catch((error) => {
|
|
@@ -228,8 +229,7 @@ export async function startBoardServer(options = {}) {
|
|
|
228
229
|
|
|
229
230
|
const route = routeBoardRequest(url.pathname, boards, initialBoard);
|
|
230
231
|
if (!route.board) {
|
|
231
|
-
response.
|
|
232
|
-
response.end("Not found");
|
|
232
|
+
sendUnregisteredBoardPath(response, url.pathname, boards, baseUrl);
|
|
233
233
|
return;
|
|
234
234
|
}
|
|
235
235
|
if (route.pathname === "/api/board") {
|
|
@@ -400,6 +400,28 @@ function routeBoardRequest(pathname, boards, initialBoard) {
|
|
|
400
400
|
return matches[0] || { board: null, pathname };
|
|
401
401
|
}
|
|
402
402
|
|
|
403
|
+
function sendUnregisteredBoardPath(response, pathname, boards, baseUrl) {
|
|
404
|
+
response.writeHead(404, {
|
|
405
|
+
"Content-Type": "text/plain; charset=utf-8",
|
|
406
|
+
"Cache-Control": "no-store",
|
|
407
|
+
});
|
|
408
|
+
const registeredBoards = [...boards.values()].map((board) => {
|
|
409
|
+
const summary = boardSummary(board, baseUrl);
|
|
410
|
+
return `- ${summary.title}: ${summary.url}`;
|
|
411
|
+
});
|
|
412
|
+
response.end([
|
|
413
|
+
`GoalBuddy board path is not registered in this local hub: ${pathname}`,
|
|
414
|
+
"",
|
|
415
|
+
"This server is the GoalBuddy multi-board hub. Do not stop it just because a /<slug>/ board URL returned 404.",
|
|
416
|
+
"Start or rerun `npx goalbuddy board <goal-dir>` to register that goal on this same port, then open the printed /<slug>/ URL.",
|
|
417
|
+
"",
|
|
418
|
+
"Registered boards:",
|
|
419
|
+
registeredBoards.length ? registeredBoards.join("\n") : "- none",
|
|
420
|
+
"",
|
|
421
|
+
`Hub API: ${baseUrl}/api/boards`,
|
|
422
|
+
].join("\n"));
|
|
423
|
+
}
|
|
424
|
+
|
|
403
425
|
function stripBoardPathPrefix(pathname, boardPath) {
|
|
404
426
|
const prefix = boardPath.endsWith("/") ? boardPath.slice(0, -1) : boardPath;
|
|
405
427
|
if (pathname === prefix) return "/";
|
|
@@ -420,7 +442,7 @@ async function readJsonRequest(request) {
|
|
|
420
442
|
|
|
421
443
|
function watchGoal(goalDir, onChange) {
|
|
422
444
|
const watchers = [];
|
|
423
|
-
const schedule = debounce(onChange,
|
|
445
|
+
const schedule = debounce(onChange, STATE_CHANGE_SETTLE_MS);
|
|
424
446
|
let watchedDirs = new Set();
|
|
425
447
|
|
|
426
448
|
const rebuild = () => {
|
package/plugins/goalbuddy/skills/goalbuddy/surfaces/local-goal-board/test/local-goal-board.test.mjs
CHANGED
|
@@ -558,6 +558,33 @@ test("serves multiple local boards from one shared hub URL", async () => {
|
|
|
558
558
|
}
|
|
559
559
|
});
|
|
560
560
|
|
|
561
|
+
test("unregistered board paths explain hub reuse instead of stale-port cleanup", async () => {
|
|
562
|
+
const root = mkdtempSync(join(tmpdir(), "goalbuddy-local-board-unregistered-"));
|
|
563
|
+
const goalDir = join(root, "first-goal");
|
|
564
|
+
try {
|
|
565
|
+
mkdirSync(join(goalDir, "notes"), { recursive: true });
|
|
566
|
+
writeFileSync(join(goalDir, "state.yaml"), stateYaml("active", { title: "First Goal", slug: "first-goal" }));
|
|
567
|
+
|
|
568
|
+
const server = await startBoardServer({ goalDir, host: "127.0.0.1", port: 0 });
|
|
569
|
+
try {
|
|
570
|
+
const baseUrl = new URL(server.url).origin;
|
|
571
|
+
const missingResponse = await fetch(`${baseUrl}/rinova-client-revision-redesign/`);
|
|
572
|
+
assert.equal(missingResponse.status, 404);
|
|
573
|
+
const message = await missingResponse.text();
|
|
574
|
+
assert.match(message, /board path is not registered/i);
|
|
575
|
+
assert.match(message, /multi-board hub/i);
|
|
576
|
+
assert.match(message, /Do not stop it just because a \/<slug>\/ board URL returned 404/);
|
|
577
|
+
assert.match(message, /npx goalbuddy board <goal-dir>/);
|
|
578
|
+
assert.match(message, /First Goal/);
|
|
579
|
+
assert.match(message, /\/api\/boards/);
|
|
580
|
+
} finally {
|
|
581
|
+
await server.close();
|
|
582
|
+
}
|
|
583
|
+
} finally {
|
|
584
|
+
rmSync(root, { recursive: true, force: true });
|
|
585
|
+
}
|
|
586
|
+
});
|
|
587
|
+
|
|
561
588
|
async function readUntil(reader, pattern) {
|
|
562
589
|
const decoder = new TextDecoder();
|
|
563
590
|
let text = "";
|
|
@@ -64,6 +64,18 @@ Tiny tasks are allowed when the failure is isolated, the risk is high, the scope
|
|
|
64
64
|
|
|
65
65
|
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.
|
|
66
66
|
|
|
67
|
+
If an exact human approval phrase is the only remaining blocker and no safe local work remains, ask once and stop. Preserve the exact phrase in the blocked receipt as `required_reply`, set `waiting_for_user_approval: true`, set `goal.status: blocked`, and set `active_task: null`. Do not keep posting approval prompts until the user replies.
|
|
68
|
+
|
|
69
|
+
## Board Health
|
|
70
|
+
|
|
71
|
+
The PM owns board health. If the board looks stale, misleading, offline, or inconsistent, run the bundled checker:
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
node <skill-path>/scripts/check-goal-state.mjs docs/goals/<slug>
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
If the local board is running, compare `state.yaml` to the live board API. Repair only GoalBuddy control files unless an active Worker or PM task explicitly allows product-file edits.
|
|
78
|
+
|
|
67
79
|
## Canonical Board
|
|
68
80
|
|
|
69
81
|
Machine truth lives at:
|
|
@@ -35,6 +35,7 @@ rules:
|
|
|
35
35
|
queued_required_worker_blocks_completion: true
|
|
36
36
|
continuous_until_full_outcome: true
|
|
37
37
|
missing_input_or_credentials_do_not_stop_goal: true
|
|
38
|
+
exact_human_approval_can_terminal_wait: true
|
|
38
39
|
preserve_and_validate_existing_plan: true
|
|
39
40
|
intake_misfire_must_be_audited: true
|
|
40
41
|
goal_pressure_requires_oracle: true
|
|
@@ -58,7 +59,7 @@ visual_board:
|
|
|
58
59
|
local:
|
|
59
60
|
status: not_requested # not_requested | starting | live | generated | blocked
|
|
60
61
|
url: null
|
|
61
|
-
command: "
|
|
62
|
+
command: "node <skill-path>/surfaces/local-goal-board/scripts/local-goal-board.mjs --goal docs/goals/<goal-slug>"
|
|
62
63
|
|
|
63
64
|
active_task: T001
|
|
64
65
|
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
# Improve Goal Maker
|
|
2
|
-
|
|
3
|
-
## Objective
|
|
4
|
-
|
|
5
|
-
Improve the local Goal Maker skill by discovering the highest-leverage repo-backed changes, completing the first safe implementation tranche, and leaving a reviewable handoff for anything larger.
|
|
6
|
-
|
|
7
|
-
## Goal Kind
|
|
8
|
-
|
|
9
|
-
`open_ended`
|
|
10
|
-
|
|
11
|
-
## Current Tranche
|
|
12
|
-
|
|
13
|
-
Map the current Goal Maker implementation, identify concrete improvement candidates with verification paths, select one small and reversible tranche, implement it, verify it, and finish with an audit receipt.
|
|
14
|
-
|
|
15
|
-
## Non-Negotiable Constraints
|
|
16
|
-
|
|
17
|
-
- Keep Goal Maker v2 control-file semantics intact: `goal.md`, `state.yaml`, and `notes/` only at each goal root.
|
|
18
|
-
- Do not implement from assumptions; Scout evidence or Judge selection must precede Worker edits.
|
|
19
|
-
- Preserve compatibility with Codex agent roles and the existing checker unless a Judge task explicitly chooses otherwise.
|
|
20
|
-
- Keep changes small, reviewable, and backed by local verification commands.
|
|
21
|
-
- Do not use destructive git operations or require credentials.
|
|
22
|
-
|
|
23
|
-
## Stop Rule
|
|
24
|
-
|
|
25
|
-
Stop when the tranche audit passes, all safe local work is blocked, or continuing would require owner input, credentials, destructive operations, or strategy the board cannot decide.
|
|
26
|
-
|
|
27
|
-
## Canonical Board
|
|
28
|
-
|
|
29
|
-
Machine truth lives at:
|
|
30
|
-
|
|
31
|
-
`examples/improve-goal-maker/state.yaml`
|
|
32
|
-
|
|
33
|
-
If this charter and `state.yaml` disagree, `state.yaml` wins for task status, active task, receipts, verification freshness, and completion truth.
|
|
34
|
-
|
|
35
|
-
## Run Command
|
|
36
|
-
|
|
37
|
-
```text
|
|
38
|
-
/goal Follow examples/improve-goal-maker/goal.md
|
|
39
|
-
```
|
|
40
|
-
|
|
41
|
-
## PM Loop
|
|
42
|
-
|
|
43
|
-
On every `/goal` continuation:
|
|
44
|
-
|
|
45
|
-
1. Read this charter.
|
|
46
|
-
2. Read `state.yaml`.
|
|
47
|
-
3. Work only on the active board task.
|
|
48
|
-
4. Assign Scout, Judge, Worker, or PM according to the task.
|
|
49
|
-
5. Write a compact task receipt.
|
|
50
|
-
6. Update the board.
|
|
51
|
-
7. Select the next active task or finish with a Judge/PM audit receipt.
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
# T001: Goal Maker Repo Map
|
|
2
|
-
|
|
3
|
-
Task: `T001`
|
|
4
|
-
Kind: `scout`
|
|
5
|
-
Status: `current`
|
|
6
|
-
|
|
7
|
-
## Summary
|
|
8
|
-
|
|
9
|
-
Goal Maker is a small dependency-free npm package that installs a Codex skill, three agent role definitions, templates, and a v2 board checker. The core verification path is healthy: syntax checks and all current checker tests pass, a temporary install plus doctor run succeeds, and the package dry run includes the expected runtime files. The highest-leverage next investigation is not packaging correctness; it is workflow reliability around checker blind spots, agent-installation assumptions, and template/skill contract drift.
|
|
10
|
-
|
|
11
|
-
## Repo Map
|
|
12
|
-
|
|
13
|
-
- `README.md` describes the package purpose, install commands, board model, repository layout, and status caveats.
|
|
14
|
-
- `CONTRIBUTING.md` defines the local verification contract: `npm run check`, temporary install/doctor, and `npm pack --dry-run`.
|
|
15
|
-
- `package.json` exposes the `goal-maker` bin, keeps runtime dependency-free, packages the skill runtime plus docs/assets, and defines `check`, `test`, and `pack:dry-run`.
|
|
16
|
-
- `goal-maker/SKILL.md` is the installed skill contract: direct `$goal-maker` creates or repairs a v2 board, stops for user choice, and `/goal` owns the PM loop.
|
|
17
|
-
- `goal-maker/bin/goal-maker.mjs` installs the skill and bundled `goal_*.toml` agents into a Codex home and supports `install`, `update`, `agents`, `doctor`, and help.
|
|
18
|
-
- `goal-maker/scripts/check-goal-state.mjs` validates v2 board shape with a lightweight line-oriented parser.
|
|
19
|
-
- `goal-maker/scripts/install-agents.mjs` is a secondary agent copy helper.
|
|
20
|
-
- `goal-maker/templates/goal.md`, `state.yaml`, and `note.md` seed new goal control files.
|
|
21
|
-
- `goal-maker/agents/goal_scout.toml`, `goal_worker.toml`, and `goal_judge.toml` define the intended role constraints.
|
|
22
|
-
- `goal-maker/test/check-goal-state.test.mjs` covers key checker invariants.
|
|
23
|
-
|
|
24
|
-
## Verification Commands
|
|
25
|
-
|
|
26
|
-
- `npm run check` passed: Node syntax checks and 7 node:test cases passed.
|
|
27
|
-
- Temporary install plus doctor passed: `skill_installed: true`; installed agents were `goal_judge.toml`, `goal_scout.toml`, and `goal_worker.toml`.
|
|
28
|
-
- `npm pack --dry-run` passed and listed the expected package files, including `README.md`, `CONTRIBUTING.md`, `internal/assets/`, `package.json`, and `goal-maker/` runtime files.
|
|
29
|
-
- `node goal-maker/scripts/check-goal-state.mjs examples/improve-goal-maker/state.yaml` passed with `ok: true`.
|
|
30
|
-
|
|
31
|
-
## Health Signals
|
|
32
|
-
|
|
33
|
-
- Current tests cover a valid Scout board, active Worker requirements, legacy v1 rejection, one-active-task enforcement, done-task receipt requirements, unexpected root entries, and final audit completion.
|
|
34
|
-
- The checker intentionally avoids dependencies, but it parses YAML with regular expressions and line indentation. This is acceptable for the template subset but creates likely blind spots for richer valid YAML or misleading strings inside quoted values.
|
|
35
|
-
- The package has two agent-installation paths: the CLI in `goal-maker/bin/goal-maker.mjs` and the helper in `goal-maker/scripts/install-agents.mjs`. They are similar but not identical.
|
|
36
|
-
- The direct skill contract says `$goal-maker` must verify Scout, Worker, and Judge agents are installed or explain what is missing; current templates mark them `installed`, while actual verification is behavior implemented by the PM thread, not the CLI/checker.
|
|
37
|
-
|
|
38
|
-
## Ranked Improvement Candidates
|
|
39
|
-
|
|
40
|
-
1. Add checker tests and fixes for root-path robustness and YAML parsing edge cases, especially misleading legacy markers inside quoted values or comments.
|
|
41
|
-
2. Reconcile or document the two agent-installation paths so CLI behavior, helper behavior, and README guidance cannot drift.
|
|
42
|
-
3. Make agent installation status less assumption-driven in generated boards or add checker warnings when agent status values are placeholders.
|
|
43
|
-
4. Add tests for the CLI install/doctor/package path so the contributor verification contract is covered by `npm run check`, not just manual commands.
|
|
44
|
-
5. Tighten docs/templates around what `/goal` PM may do in Codex environments where actual subagent dispatch is unavailable and roles are simulated in-thread.
|
|
45
|
-
|
|
46
|
-
## Candidate Next Tasks
|
|
47
|
-
|
|
48
|
-
- Investigate checker blind spots with focused failing test candidates before choosing a fix.
|
|
49
|
-
- Compare `goal-maker/bin/goal-maker.mjs` and `goal-maker/scripts/install-agents.mjs` and decide whether to consolidate, test, or document the helper.
|
|
50
|
-
- Judge the first implementation tranche after the risk map, preferring a small test-backed checker or CLI reliability fix.
|
|
51
|
-
|
|
52
|
-
## Board Receipt Snippet
|
|
53
|
-
|
|
54
|
-
```yaml
|
|
55
|
-
receipt:
|
|
56
|
-
result: done
|
|
57
|
-
note: notes/T001-repo-map.md
|
|
58
|
-
summary: "Repo map completed; verification is green, install/package checks work, and the top improvement area is Goal Maker workflow reliability around checker blind spots, agent installation assumptions, and template/skill drift."
|
|
59
|
-
```
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
# T002: Workflow Risk Map
|
|
2
|
-
|
|
3
|
-
Task: `T002`
|
|
4
|
-
Kind: `scout`
|
|
5
|
-
Status: `current`
|
|
6
|
-
|
|
7
|
-
## Summary
|
|
8
|
-
|
|
9
|
-
The highest-confidence risks are small and testable. First, `goal-maker doctor` reports success when one of the required bundled agents is missing, which weakens the "verify Scout, Worker, and Judge agents" contract. Second, the board checker accepts mismatched task `type` and `assignee` values, even though Goal Maker semantics depend on those fields agreeing. A lower-priority drift risk is that `update` does not overwrite stale existing agent definitions unless `--force` is supplied, despite README wording that says install or update the skill and bundled agents.
|
|
10
|
-
|
|
11
|
-
## Evidence
|
|
12
|
-
|
|
13
|
-
- `goal-maker/bin/goal-maker.mjs` `doctor()` exits based only on whether `skills/goal-maker/SKILL.md` exists. In a temp Codex home with `goal_worker.toml` removed, `doctor` exited 0 and reported only `goal_judge.toml` and `goal_scout.toml`.
|
|
14
|
-
- `goal-maker/scripts/check-goal-state.mjs` independently validates `task.type` and `task.assignee`, but does not validate pairs. A temp board with `type: scout` and `assignee: Worker` passed with `ok: true`.
|
|
15
|
-
- `goal-maker/scripts/check-goal-state.mjs` does not require or validate the `agents:` section. A temp board with the section omitted passed with `ok: true`.
|
|
16
|
-
- `goal-maker/bin/goal-maker.mjs` `installAgents()` skips existing `goal_*.toml` files unless `--force` is present. In a temp Codex home with a stale `goal_scout.toml`, `goal-maker update` left the stale file unchanged.
|
|
17
|
-
- `goal-maker/scripts/install-agents.mjs` copies any `.toml` except names containing `config-snippet`, while the CLI copies only `goal_*.toml`. Current files make this harmless, but it is a drift point.
|
|
18
|
-
|
|
19
|
-
## Small Safe Implementation Candidates
|
|
20
|
-
|
|
21
|
-
1. Add CLI tests for `doctor` and make `doctor` fail when any required agent file is missing. Allowed files would likely be `goal-maker/bin/goal-maker.mjs` and a new focused CLI test file.
|
|
22
|
-
2. Add checker tests for task type/assignee mismatch and fix the checker to reject mismatches. Allowed files would be `goal-maker/scripts/check-goal-state.mjs` and `goal-maker/test/check-goal-state.test.mjs`.
|
|
23
|
-
3. Add checker tests for missing or invalid `agents:` statuses and reject or warn. This is slightly more policy-heavy because templates currently treat agent status as PM-populated board metadata.
|
|
24
|
-
4. Decide whether `update` should overwrite existing bundled agents or whether README/CLI wording should clarify that `--force` is required for agent refresh.
|
|
25
|
-
|
|
26
|
-
## Recommended First Tranche
|
|
27
|
-
|
|
28
|
-
Prefer the `doctor` missing-agent fix. It directly supports the `$goal-maker` requirement to verify Scout, Worker, and Judge availability, has tight behavior, and can be verified with a new CLI test plus existing checks. The checker mismatch fix is also small, but it is less directly connected to the user-visible setup flow.
|
|
29
|
-
|
|
30
|
-
## Board Receipt Snippet
|
|
31
|
-
|
|
32
|
-
```yaml
|
|
33
|
-
receipt:
|
|
34
|
-
result: done
|
|
35
|
-
note: notes/T002-risk-map.md
|
|
36
|
-
summary: "Risk map completed; doctor exits green with missing agents, checker accepts type/assignee mismatches, and update leaves stale agents unless forced."
|
|
37
|
-
```
|