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.
Files changed (36) hide show
  1. package/.claude-plugin/marketplace.json +16 -0
  2. package/CHANGELOG.md +70 -0
  3. package/CONTRIBUTING.md +2 -2
  4. package/README.md +11 -3
  5. package/{RELEASE-0.3.7.md → docs/releases/0.3.7.md} +2 -0
  6. package/docs/releases/0.3.8.md +40 -0
  7. package/docs/releases/0.3.9.md +46 -0
  8. package/docs/releases/README.md +84 -0
  9. package/goalbuddy/SKILL.md +26 -8
  10. package/goalbuddy/scripts/check-goal-state.mjs +22 -4
  11. package/goalbuddy/scripts/check-update.mjs +18 -1
  12. package/goalbuddy/scripts/render-task-prompt.mjs +17 -3
  13. package/goalbuddy/surfaces/local-goal-board/scripts/lib/goal-board.mjs +16 -15
  14. package/goalbuddy/surfaces/local-goal-board/scripts/local-goal-board.mjs +25 -3
  15. package/goalbuddy/surfaces/local-goal-board/test/local-goal-board.test.mjs +189 -4
  16. package/goalbuddy/templates/goal.md +12 -0
  17. package/goalbuddy/templates/state.yaml +2 -1
  18. package/internal/cli/goal-maker.mjs +186 -7
  19. package/package.json +6 -6
  20. package/plugins/goalbuddy/.claude-plugin/plugin.json +1 -1
  21. package/plugins/goalbuddy/.codex-plugin/plugin.json +1 -1
  22. package/plugins/goalbuddy/README.md +1 -1
  23. package/plugins/goalbuddy/skills/goalbuddy/SKILL.md +26 -8
  24. package/plugins/goalbuddy/skills/goalbuddy/scripts/check-goal-state.mjs +22 -4
  25. package/plugins/goalbuddy/skills/goalbuddy/scripts/check-update.mjs +18 -1
  26. package/plugins/goalbuddy/skills/goalbuddy/scripts/render-task-prompt.mjs +17 -3
  27. package/plugins/goalbuddy/skills/goalbuddy/surfaces/local-goal-board/scripts/lib/goal-board.mjs +1 -4
  28. package/plugins/goalbuddy/skills/goalbuddy/surfaces/local-goal-board/scripts/local-goal-board.mjs +25 -3
  29. package/plugins/goalbuddy/skills/goalbuddy/surfaces/local-goal-board/test/local-goal-board.test.mjs +27 -0
  30. package/plugins/goalbuddy/skills/goalbuddy/templates/goal.md +12 -0
  31. package/plugins/goalbuddy/skills/goalbuddy/templates/state.yaml +2 -1
  32. package/examples/improve-goal-maker/goal.md +0 -51
  33. package/examples/improve-goal-maker/notes/T001-repo-map.md +0 -59
  34. package/examples/improve-goal-maker/notes/T002-risk-map.md +0 -37
  35. package/examples/improve-goal-maker/state.yaml +0 -224
  36. /package/{RELEASE-0.3.5.md → docs/releases/0.3.5.md} +0 -0
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "goalbuddy",
3
- "version": "0.3.7",
3
+ "version": "0.3.9",
4
4
  "description": "Turn broad Codex and Claude Code work into pressured /goal runs with oracles, local boards, receipts, and verification.",
5
5
  "author": {
6
6
  "name": "tolibear",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "goalbuddy",
3
- "version": "0.3.7",
3
+ "version": "0.3.9",
4
4
  "description": "Turn broad Codex and Claude Code work into pressured /goal runs with oracles, local boards, receipts, and verification.",
5
5
  "author": {
6
6
  "name": "tolibear",
@@ -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.7 is the Goalmaxxed release: goal oracles, largest safe useful slices, built-in local boards, receipt pressure, final-proof gates, and a smaller core without the extension catalog.
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 with: npx goalbuddy
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 `npx goalbuddy board 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.
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 `npx goalbuddy agents`. |
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 `npx goalbuddy install`. |
531
- | `unknown` | Agent availability could not be checked. | `/goal` can proceed through PM fallback. To check before `/goal`, run `npx goalbuddy doctor`. |
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 `goalbuddy prompt 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.
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 `npx goalbuddy agents`/`npx goalbuddy install`. After one `wait_agent` timeout with no visible allowed-file changes, stop waiting, record the timeout, and recover deterministically instead of waiting forever.
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 `goalbuddy parallel-plan 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.
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: npx goalbuddy agents`;
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: npx goalbuddy install`;
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: npx goalbuddy doctor`;
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: "npx goalbuddy",
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: [{ cmd: "<command>", status: "pass | fail | not_run" }],
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
- summary: "<=120 words",
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
 
@@ -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: "The active task", tasks: byColumn.get("in-progress") },
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
  ];
@@ -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.writeHead(404);
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, 80);
445
+ const schedule = debounce(onChange, STATE_CHANGE_SETTLE_MS);
424
446
  let watchedDirs = new Set();
425
447
 
426
448
  const rebuild = () => {
@@ -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: "npx goalbuddy board docs/goals/<goal-slug>"
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
- ```