goalbuddy 0.3.7 → 0.3.8
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/CHANGELOG.md +61 -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/README.md +83 -0
- package/goalbuddy/SKILL.md +2 -0
- package/goalbuddy/scripts/render-task-prompt.mjs +17 -3
- package/goalbuddy/surfaces/local-goal-board/scripts/lib/goal-board.mjs +15 -11
- package/goalbuddy/surfaces/local-goal-board/scripts/local-goal-board.mjs +23 -2
- package/goalbuddy/surfaces/local-goal-board/test/local-goal-board.test.mjs +55 -4
- package/internal/cli/goal-maker.mjs +168 -6
- package/package.json +5 -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 +2 -0
- package/plugins/goalbuddy/skills/goalbuddy/scripts/render-task-prompt.mjs +17 -3
- package/plugins/goalbuddy/skills/goalbuddy/surfaces/local-goal-board/scripts/local-goal-board.mjs +23 -2
- package/plugins/goalbuddy/skills/goalbuddy/surfaces/local-goal-board/test/local-goal-board.test.mjs +27 -0
- 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
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.3.8 — Board Hub Guardrails (2026-05-29)
|
|
4
|
+
|
|
5
|
+
- **Clarified multi-board hub recovery.** Unregistered board URLs now explain that a `/slug/` 404 does not mean the `41737` process is stale; agents should verify `/api/boards` and register the new goal on the same hub before stopping any process. Release checks now include the local board surface tests.
|
|
6
|
+
- **Prefer the largest safe useful slice.** GoalBuddy now teaches Judge to pick whole useful slices, Worker to complete the assigned slice, and PM to reorient boards when tasks are safe-looking but outcome-light. `goalbuddy prompt` and the state checker emit non-fatal micro-slicing warnings without breaking old boards.
|
|
7
|
+
- **Hardened Codex plugin-only installs.** Codex install/update now use the native plugin path, refresh the bundled Scout/Judge/Worker agents, and leave stale personal `~/.codex/skills/goalbuddy` / `goal-maker` folders out of the expected clean state.
|
|
8
|
+
- **Fixed Codex doctor for plugin-only installs.** `goalbuddy doctor --target codex --goal-ready` now validates the plugin cache, bundled `$goal-prep` skill, enabled plugin config, and GoalBuddy agents instead of failing only because standalone personal skill folders are absent. The report also distinguishes native OpenAI-gated Codex `/goal` from GoalBuddy `$goal-prep` and local boards.
|
|
9
|
+
- **Made mutating command help safe.** `goalbuddy plugin install --help` and `goalbuddy update --help` print help without installing, updating, or touching global Codex/Claude files.
|
|
10
|
+
|
|
11
|
+
## 0.3.5 — Subgoals, Parallel Agents, and Dark Mode (2026-05-12)
|
|
12
|
+
|
|
13
|
+
- **Subgoals for bounded branching work.** Parent tasks can link to depth-1 child `state.yaml` boards under `subgoals/`, the checker validates child shape and containment, and the local board renders the child board inside the parent task detail.
|
|
14
|
+
- **Parallel-agent-ready boards.** `goalbuddy parallel-plan` reports safe read-only Scout/Judge handoffs and Worker handoffs only when write scopes are known and disjoint. It does not mutate state or spawn agents.
|
|
15
|
+
- **Dark mode and a sharper live board.** The local board now has readable dark mode, global viewer settings, compact mode, completed-task collapse, a site-aligned header, GitHub stars, and active-card motion with reduced-motion handling.
|
|
16
|
+
- **Multi-board local hub navigation.** Multiple local boards share one readable `goalbuddy.localhost` hub with an in-header board selector, and parent boards stream updates when linked child subgoal state changes.
|
|
17
|
+
- **More durable execution plumbing.** Scout/Judge/Worker contracts are stricter, `goalbuddy prompt` emits compact task prompts, Worker write-scope checks fail closed for ambiguous overlap, and source/plugin tests cover the new branching and parallel-safety surfaces.
|
|
18
|
+
|
|
19
|
+
## 0.3.2 — Harden Codex plugin cache updates (2026-05-11)
|
|
20
|
+
|
|
21
|
+
- **Fixed Codex plugin updates when stale preserved-extension folders exist.** The updater now ignores non-version cache directories like `.goalbuddy-preserved-extend-*` while selecting the active plugin skill, so a leftover temporary folder cannot make `npx goalbuddy update` fail with `Unsupported version`.
|
|
22
|
+
- **Stopped leaving empty preserved-extension folders during plugin reinstalls.** The updater only creates the temporary preservation directory when there is a custom extension to copy.
|
|
23
|
+
|
|
24
|
+
## 0.3.1 — Fix duplicate /goal-prep slash entry (2026-05-11)
|
|
25
|
+
|
|
26
|
+
- **Fixed duplicate `/goal-prep` in the Claude Code slash menu.** Previous installs shipped both a `name: goal-prep` skill and a `commands/goal-prep.md` slash command, so Claude Code listed `/goal-prep` twice with different descriptions. The skill is now the single canonical surface for `/goal-prep`. Existing installs with `~/.claude/commands/goal-prep.md` are migrated automatically: `npx goalbuddy` (and `install` / `update`) removes the legacy file. `goalbuddy doctor --target claude` reports `legacy_command_present` and fails until the legacy file is gone.
|
|
27
|
+
|
|
28
|
+
## 0.3.0 — Claude Code and Codex targets
|
|
29
|
+
|
|
30
|
+
GoalBuddy now installs into both **Codex** and **Claude Code** with a single `npx goalbuddy` run. The shared skill payload and `/goal` workflow are unchanged — this release adds a Claude Code target alongside the existing Codex one and reframes the project as "a /goal operating system for Codex and Claude Code."
|
|
31
|
+
|
|
32
|
+
### Highlights
|
|
33
|
+
|
|
34
|
+
- **One command installs both targets.** `npx goalbuddy` installs and enables the native Codex plugin in `~/.codex/`, then installs the GoalBuddy skill, three Scout/Judge/Worker subagents, and the `/goal-prep` slash command into `~/.claude/`.
|
|
35
|
+
- **Target-specific installs remain available.** Use `npx goalbuddy --target codex` or `npx goalbuddy --target claude` when you only want one side.
|
|
36
|
+
- **Claude Code plugin scaffold** at `plugins/goalbuddy/.claude-plugin/plugin.json` with markdown subagents (`agents/goal-scout.md`, `agents/goal-judge.md`, `agents/goal-worker.md`) and a `/goal-prep` command (`commands/goal-prep.md`).
|
|
37
|
+
- **`$goal-prep` (Codex) and `/goal-prep` (Claude Code)** are documented as sibling entry points throughout the skill, README, site, and CLI.
|
|
38
|
+
- **Reframed README, site, plugin docs, package.json, and SKILL.md** to position the workflow as "a /goal operating system for Codex and Claude Code."
|
|
39
|
+
- **CLI is target-aware.** New flags: `--target codex|claude`, `--claude-home <path>`. Existing `--codex-home` and `CODEX_HOME` continue to work unchanged.
|
|
40
|
+
- **Update supports both targets.** `goalbuddy update` refreshes the Codex plugin and Claude Code skill/agents/command together unless `--target` narrows it.
|
|
41
|
+
- **Doctor checks both targets.** Default is Codex; `goalbuddy doctor --target claude` runs the Claude Code skill/agent/command check.
|
|
42
|
+
|
|
43
|
+
### Compatibility
|
|
44
|
+
|
|
45
|
+
- `npx goalbuddy` with no flag now prepares Codex and Claude Code together. Existing Codex-only automation can keep using `--target codex` or `--codex-home`.
|
|
46
|
+
- `npx goal-maker` continues to work as a temporary alias and prints the new command.
|
|
47
|
+
- The shared `goalbuddy/SKILL.md` payload is unchanged in shape; the framing is now bilingual.
|
|
48
|
+
|
|
49
|
+
### Tests
|
|
50
|
+
|
|
51
|
+
- All 46 tests pass.
|
|
52
|
+
- Help-text and version-arithmetic tests updated for the bilingual usage and the 0.3.0 bump.
|
|
53
|
+
|
|
54
|
+
### Adding Or Updating Both
|
|
55
|
+
|
|
56
|
+
Install or refresh both supported agent environments:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
npx goalbuddy
|
|
60
|
+
npx goalbuddy update
|
|
61
|
+
```
|
package/CONTRIBUTING.md
CHANGED
|
@@ -39,11 +39,11 @@ Before opening a PR, verify the npm package contents:
|
|
|
39
39
|
npm pack --dry-run
|
|
40
40
|
```
|
|
41
41
|
|
|
42
|
-
The package should include `README.md`, `internal/assets/`, `package.json`, `internal/cli/`, the canonical `goalbuddy/` skill directory, and `plugins/goalbuddy/` (with both `.codex-plugin/` and `.claude-plugin/` manifests). The temporary `$goal-maker` compatibility skill is generated by the installer; do not add a second tracked skill payload.
|
|
42
|
+
The package should include `README.md`, `CHANGELOG.md`, `docs/releases/`, `internal/assets/`, `package.json`, `internal/cli/`, the canonical `goalbuddy/` skill directory, and `plugins/goalbuddy/` (with both `.codex-plugin/` and `.claude-plugin/` manifests). The temporary `$goal-maker` compatibility skill is generated by the installer; do not add a second tracked skill payload.
|
|
43
43
|
|
|
44
44
|
## Releases
|
|
45
45
|
|
|
46
|
-
GoalBuddy publishes from GitHub Actions with npm trusted publishing. See [
|
|
46
|
+
GoalBuddy publishes from GitHub Actions with npm trusted publishing. See [docs/releases](docs/releases/README.md) before creating a release.
|
|
47
47
|
|
|
48
48
|
## Contribution Guidelines
|
|
49
49
|
|
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
<p align="center">
|
|
4
4
|
<a href="https://goalbuddy.dev">
|
|
5
|
-
<img src="internal/assets/goalbuddy-
|
|
5
|
+
<img src="internal/assets/goalbuddy-readme-hero.png" alt="GoalBuddy local board and agent workflow." width="100%">
|
|
6
6
|
</a>
|
|
7
7
|
</p>
|
|
8
8
|
|
|
@@ -63,6 +63,14 @@ To verify a Codex install:
|
|
|
63
63
|
npx goalbuddy doctor --target codex --goal-ready
|
|
64
64
|
```
|
|
65
65
|
|
|
66
|
+
To remove GoalBuddy-owned Codex runtime surfaces:
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
npx goalbuddy reset --target codex
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Native `codex plugin remove goalbuddy@goalbuddy` only removes the native plugin surface. GoalBuddy also owns the `goal_*.toml` agent files it installed, its Codex plugin cache, its marketplace entry, and old personal skill folders from earlier installs. Use `goalbuddy reset --target codex` when you want those GoalBuddy-owned files removed too.
|
|
73
|
+
|
|
66
74
|
## What It Creates
|
|
67
75
|
|
|
68
76
|
```text
|
|
@@ -144,7 +152,7 @@ Multiple local boards reuse one readable `goalbuddy.localhost` hub with an in-he
|
|
|
144
152
|
|
|
145
153
|
Custom external integrations should be built as ordinary repo work with a concrete implementation plan, not installed from a GoalBuddy catalog.
|
|
146
154
|
|
|
147
|
-
See [GoalBuddy 0.3.
|
|
155
|
+
See [GoalBuddy 0.3.8: Board Hub Guardrails](docs/releases/0.3.8.md) for the latest release notes.
|
|
148
156
|
|
|
149
157
|
<p align="center">
|
|
150
158
|
<img src="internal/assets/goalbuddy-live-board.jpg" alt="GoalBuddy local live board open next to Codex while Scout, Judge, and Worker tasks populate." width="100%">
|
|
@@ -164,7 +172,7 @@ GoalBuddy is MIT licensed and published on npm.
|
|
|
164
172
|
|
|
165
173
|
The implementation lives in this repo, but the happy path is intentionally tiny: install it, run Goal Prep, then let `/goal` work from the generated files.
|
|
166
174
|
|
|
167
|
-
For release process details, see [
|
|
175
|
+
For release process details, see [docs/releases](docs/releases/README.md).
|
|
168
176
|
|
|
169
177
|
## Star History
|
|
170
178
|
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# GoalBuddy 0.3.7: Goalmaxxed
|
|
2
2
|
|
|
3
|
+

|
|
4
|
+
|
|
3
5
|
Release date: 2026-05-19
|
|
4
6
|
|
|
5
7
|
Goalmaxxed is the release where GoalBuddy stops trying to become a workflow catalog and commits to one sharper job:
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# GoalBuddy 0.3.8: Board Hub Guardrails
|
|
2
|
+
|
|
3
|
+
Release date: 2026-05-29
|
|
4
|
+
|
|
5
|
+
This patch release fixes a confusing local-board failure mode.
|
|
6
|
+
|
|
7
|
+
GoalBuddy already supports multiple local boards on one shared hub:
|
|
8
|
+
|
|
9
|
+
```text
|
|
10
|
+
http://goalbuddy.localhost:41737/<slug>/
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
The problem was the unregistered-path error. If an agent opened a new board URL before registering that goal with the hub, the server returned a bare 404. That made it too easy to infer that the process on `41737` was stale, even when it was a healthy multi-board hub for another goal.
|
|
14
|
+
|
|
15
|
+
## What Changed
|
|
16
|
+
|
|
17
|
+
- Unregistered board paths now return an explicit diagnostic explaining that a `/slug/` 404 does not mean the hub is stale.
|
|
18
|
+
- The diagnostic points agents to `http://127.0.0.1:41737/api/boards` and tells them to rerun `npx goalbuddy board <goal-dir>` to register the goal on the same port.
|
|
19
|
+
- `$goal-prep` / `/goal-prep` now says to stop a process on `41737` only when `/api/boards` proves the listener is not a current GoalBuddy multi-board hub.
|
|
20
|
+
- `npm run check` now includes the local board surface tests and syntax checks.
|
|
21
|
+
|
|
22
|
+
## Release Boundaries
|
|
23
|
+
|
|
24
|
+
This release does not change the board state model. `state.yaml` remains the source of truth, the local board remains a viewer over repo files, and multiple boards still share the same local hub.
|
|
25
|
+
|
|
26
|
+
## Package Notes
|
|
27
|
+
|
|
28
|
+
This release updates:
|
|
29
|
+
|
|
30
|
+
- npm package version: `0.3.8`
|
|
31
|
+
- Codex plugin version: `0.3.8`
|
|
32
|
+
- Claude Code plugin version: `0.3.8`
|
|
33
|
+
- release checks to cover `goalbuddy/surfaces/local-goal-board/`
|
|
34
|
+
|
|
35
|
+
Before publishing, verify:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
npm run check
|
|
39
|
+
npm run publish:check
|
|
40
|
+
```
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# Release Process
|
|
2
|
+
|
|
3
|
+
Historical release notes live next to this process doc:
|
|
4
|
+
|
|
5
|
+
- [0.3.8: Board Hub Guardrails](0.3.8.md)
|
|
6
|
+
- [0.3.7: Goalmaxxed](0.3.7.md)
|
|
7
|
+
- [0.3.5: Subgoals, Parallel Agents, and Dark Mode](0.3.5.md)
|
|
8
|
+
|
|
9
|
+
GoalBuddy publishes the `goalbuddy` npm package from GitHub Actions using npm trusted publishing. This avoids long-lived npm write tokens and lets npm generate provenance for future releases.
|
|
10
|
+
|
|
11
|
+
## One-Time npm Setup
|
|
12
|
+
|
|
13
|
+
Configure this on npmjs.com for the `goalbuddy` package:
|
|
14
|
+
|
|
15
|
+
- Publisher: GitHub Actions
|
|
16
|
+
- GitHub owner/user: `tolibear`
|
|
17
|
+
- Repository: `goalbuddy`
|
|
18
|
+
- Workflow filename: `npm-publish.yml`
|
|
19
|
+
- Package: `goalbuddy`
|
|
20
|
+
|
|
21
|
+
The workflow path in this repo is:
|
|
22
|
+
|
|
23
|
+
```text
|
|
24
|
+
.github/workflows/npm-publish.yml
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Or configure the same trust relationship from the npm CLI:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
npx --yes npm@11.13.0 trust github goalbuddy \
|
|
31
|
+
--repo tolibear/goalbuddy \
|
|
32
|
+
--file npm-publish.yml \
|
|
33
|
+
--yes
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
This command requires npm owner authentication and may print an `EOTP` browser/OTP URL. Complete that npm authentication step, then rerun the same command if needed. The `npx --yes npm@11.13.0 trust ...` form is intentional; using `npx -p npm@latest npm trust ...` can resolve to an older global npm binary that does not expose the `trust` command.
|
|
37
|
+
|
|
38
|
+
After the trusted publisher works, use npm package settings to require 2FA and disallow tokens for publishing. Keep `goal-maker` published during the migration window.
|
|
39
|
+
|
|
40
|
+
Starting in `0.3.0`, the installer is target-aware: `npx goalbuddy` installs into both `~/.codex/` and `~/.claude/`, and `goalbuddy update` refreshes both by default. Use `--target codex` or `--target claude` to narrow a command. Both targets share the same `goalbuddy/` skill payload and are exercised by the test suite under `internal/test/`.
|
|
41
|
+
|
|
42
|
+
## Release Flow
|
|
43
|
+
|
|
44
|
+
1. Update `package.json` version.
|
|
45
|
+
2. Run local checks:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
npm run check
|
|
49
|
+
npm run pack:dry-run
|
|
50
|
+
node internal/cli/check-publish-version.mjs
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
3. Commit and push the version change.
|
|
54
|
+
4. Create and publish a GitHub release whose tag matches the package version, for example `v0.2.11`.
|
|
55
|
+
5. Confirm the GitHub Actions workflow `Publish npm package` completed.
|
|
56
|
+
6. Verify npm:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
npm view goalbuddy name version dist-tags repository bin --json
|
|
60
|
+
npx goalbuddy --help
|
|
61
|
+
npx goalbuddy doctor --target codex
|
|
62
|
+
npx goalbuddy doctor --target claude
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Provenance Expectations
|
|
66
|
+
|
|
67
|
+
npm trusted publishing requires a GitHub-hosted runner, Node `22.14.0` or newer, npm `11.5.1` or newer, and `id-token: write` workflow permission. The release workflow uses Node 24 and grants the OIDC permission required by npm.
|
|
68
|
+
|
|
69
|
+
When publishing through trusted publishing from this public repo to the public `goalbuddy` package, npm should generate provenance automatically. The workflow intentionally runs `npm publish` without `NODE_AUTH_TOKEN`; npm exchanges the GitHub OIDC identity for a short-lived publish credential.
|
|
70
|
+
|
|
71
|
+
## Compatibility Package
|
|
72
|
+
|
|
73
|
+
Do not unpublish `goal-maker`. During the 60-90 day compatibility window, `npx goal-maker` should continue to work and point users to:
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
npx goalbuddy
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
After the compatibility window:
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
npm deprecate goal-maker "Renamed to goalbuddy. Use: npx goalbuddy"
|
|
83
|
+
```
|
package/goalbuddy/SKILL.md
CHANGED
|
@@ -95,6 +95,8 @@ Recommended options:
|
|
|
95
95
|
|
|
96
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.
|
|
97
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 `npx goalbuddy board docs/goals/<slug>` with the 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.
|
|
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
|
|
|
100
102
|
Ask before board creation when the request is vague, strategic, improvement-oriented, or open-ended and the user has not explicitly said to use defaults. Ask one guided question at a time with 2-3 options and a recommended default, then wait. Continue the diagnostic intake until the user's answers are sufficient to choose the board shape. Do not create or repair `docs/goals/<slug>/` until the diagnostic intake is complete or the user explicitly accepts defaults.
|
|
@@ -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
|
|
|
@@ -142,8 +142,8 @@ export function buildColumns(tasks) {
|
|
|
142
142
|
byColumn.get(task.column).push(task);
|
|
143
143
|
}
|
|
144
144
|
|
|
145
|
-
for (const columnTasks of byColumn.
|
|
146
|
-
columnTasks.sort((left, right) =>
|
|
145
|
+
for (const [columnId, columnTasks] of byColumn.entries()) {
|
|
146
|
+
columnTasks.sort((left, right) => compareColumnTasks(columnId, left, right));
|
|
147
147
|
}
|
|
148
148
|
|
|
149
149
|
return [
|
|
@@ -289,7 +289,7 @@ function titleForTask(task) {
|
|
|
289
289
|
function compactTaskTitle(value) {
|
|
290
290
|
const text = cleanText(value).replace(/\.$/, "");
|
|
291
291
|
const routeMatch = text.match(/^Implement\b.*?\s(\/[A-Za-z0-9_./:-]+)\s+(route|queue slice|slice)\b/i);
|
|
292
|
-
if (routeMatch) return
|
|
292
|
+
if (routeMatch) return `Implement ${routeMatch[1]} ${routeMatch[2]}`;
|
|
293
293
|
|
|
294
294
|
const firstClause = text
|
|
295
295
|
.split(/(?<=[.!?])\s+|\s+(?:Use only|Add|Match|Render|Clearly label|Do not)\b/i)[0]
|
|
@@ -300,14 +300,7 @@ function compactTaskTitle(value) {
|
|
|
300
300
|
.replace(/[.;:,]\s*$/, "")
|
|
301
301
|
.trim();
|
|
302
302
|
|
|
303
|
-
return
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
function truncateTitle(value, maxLength = 82) {
|
|
307
|
-
const text = cleanText(value).replace(/\.$/, "");
|
|
308
|
-
if (text.length <= maxLength) return text;
|
|
309
|
-
const shortened = text.slice(0, maxLength + 1).replace(/\s+\S*$/, "").trim();
|
|
310
|
-
return `${shortened || text.slice(0, maxLength).trim()}...`;
|
|
303
|
+
return firstClause || text;
|
|
311
304
|
}
|
|
312
305
|
|
|
313
306
|
function columnForStatus(status) {
|
|
@@ -322,6 +315,12 @@ function taskSortKey(task) {
|
|
|
322
315
|
return `${rank}:${task.id}`;
|
|
323
316
|
}
|
|
324
317
|
|
|
318
|
+
function compareColumnTasks(columnId, left, right) {
|
|
319
|
+
const order = taskSortKey(left).localeCompare(taskSortKey(right));
|
|
320
|
+
if (columnId === "completed") return -order;
|
|
321
|
+
return order;
|
|
322
|
+
}
|
|
323
|
+
|
|
325
324
|
function normalizeStringList(value) {
|
|
326
325
|
if (!value) return [];
|
|
327
326
|
if (Array.isArray(value)) return value.map(cleanText).filter(Boolean);
|
|
@@ -1503,8 +1502,13 @@ h1 {
|
|
|
1503
1502
|
.task-title {
|
|
1504
1503
|
margin: 0;
|
|
1505
1504
|
color: #2f3437;
|
|
1505
|
+
display: -webkit-box;
|
|
1506
1506
|
font-size: 15px;
|
|
1507
1507
|
line-height: 1.35;
|
|
1508
|
+
overflow: hidden;
|
|
1509
|
+
overflow-wrap: anywhere;
|
|
1510
|
+
-webkit-box-orient: vertical;
|
|
1511
|
+
-webkit-line-clamp: 5;
|
|
1508
1512
|
}
|
|
1509
1513
|
|
|
1510
1514
|
.card-footer {
|
|
@@ -228,8 +228,7 @@ export async function startBoardServer(options = {}) {
|
|
|
228
228
|
|
|
229
229
|
const route = routeBoardRequest(url.pathname, boards, initialBoard);
|
|
230
230
|
if (!route.board) {
|
|
231
|
-
response.
|
|
232
|
-
response.end("Not found");
|
|
231
|
+
sendUnregisteredBoardPath(response, url.pathname, boards, baseUrl);
|
|
233
232
|
return;
|
|
234
233
|
}
|
|
235
234
|
if (route.pathname === "/api/board") {
|
|
@@ -400,6 +399,28 @@ function routeBoardRequest(pathname, boards, initialBoard) {
|
|
|
400
399
|
return matches[0] || { board: null, pathname };
|
|
401
400
|
}
|
|
402
401
|
|
|
402
|
+
function sendUnregisteredBoardPath(response, pathname, boards, baseUrl) {
|
|
403
|
+
response.writeHead(404, {
|
|
404
|
+
"Content-Type": "text/plain; charset=utf-8",
|
|
405
|
+
"Cache-Control": "no-store",
|
|
406
|
+
});
|
|
407
|
+
const registeredBoards = [...boards.values()].map((board) => {
|
|
408
|
+
const summary = boardSummary(board, baseUrl);
|
|
409
|
+
return `- ${summary.title}: ${summary.url}`;
|
|
410
|
+
});
|
|
411
|
+
response.end([
|
|
412
|
+
`GoalBuddy board path is not registered in this local hub: ${pathname}`,
|
|
413
|
+
"",
|
|
414
|
+
"This server is the GoalBuddy multi-board hub. Do not stop it just because a /<slug>/ board URL returned 404.",
|
|
415
|
+
"Start or rerun `npx goalbuddy board <goal-dir>` to register that goal on this same port, then open the printed /<slug>/ URL.",
|
|
416
|
+
"",
|
|
417
|
+
"Registered boards:",
|
|
418
|
+
registeredBoards.length ? registeredBoards.join("\n") : "- none",
|
|
419
|
+
"",
|
|
420
|
+
`Hub API: ${baseUrl}/api/boards`,
|
|
421
|
+
].join("\n"));
|
|
422
|
+
}
|
|
423
|
+
|
|
403
424
|
function stripBoardPathPrefix(pathname, boardPath) {
|
|
404
425
|
const prefix = boardPath.endsWith("/") ? boardPath.slice(0, -1) : boardPath;
|
|
405
426
|
if (pathname === prefix) return "/";
|
|
@@ -4,7 +4,7 @@ import { cpSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } f
|
|
|
4
4
|
import { spawnSync } from "node:child_process";
|
|
5
5
|
import { tmpdir } from "node:os";
|
|
6
6
|
import { join, resolve } from "node:path";
|
|
7
|
-
import { createBoardPayload, writeBoardApp } from "../scripts/lib/goal-board.mjs";
|
|
7
|
+
import { buildColumns, createBoardPayload, writeBoardApp } from "../scripts/lib/goal-board.mjs";
|
|
8
8
|
import { parseArgs, startBoardServer } from "../scripts/local-goal-board.mjs";
|
|
9
9
|
|
|
10
10
|
test("normalizes a dense goal into local board columns", () => {
|
|
@@ -23,6 +23,18 @@ test("normalizes a dense goal into local board columns", () => {
|
|
|
23
23
|
assert.equal(scout.receipt.summary, "T001 completed during the progressive board motion demo.");
|
|
24
24
|
});
|
|
25
25
|
|
|
26
|
+
test("orders completed cards newest first while preserving queued order", () => {
|
|
27
|
+
const columns = buildColumns([
|
|
28
|
+
{ id: "T001", column: "completed", status: "done" },
|
|
29
|
+
{ id: "T002", column: "todo", status: "queued" },
|
|
30
|
+
{ id: "T003", column: "completed", status: "done" },
|
|
31
|
+
{ id: "T004", column: "todo", status: "queued" },
|
|
32
|
+
]);
|
|
33
|
+
|
|
34
|
+
assert.deepEqual(columns.find((column) => column.id === "todo").tasks.map((task) => task.id), ["T002", "T004"]);
|
|
35
|
+
assert.deepEqual(columns.find((column) => column.id === "completed").tasks.map((task) => task.id), ["T003", "T001"]);
|
|
36
|
+
});
|
|
37
|
+
|
|
26
38
|
test("loads depth-1 subgoal boards into parent task payloads", () => {
|
|
27
39
|
const payload = createBoardPayload(resolve("goalbuddy/surfaces/local-goal-board/examples/subgoal-parent"));
|
|
28
40
|
const parentTask = payload.tasks.find((task) => task.id === "T004");
|
|
@@ -37,10 +49,10 @@ test("loads depth-1 subgoal boards into parent task payloads", () => {
|
|
|
37
49
|
assert.equal(parentTask.subgoal.board.tasks.find((task) => task.id === "T002").subgoal, null);
|
|
38
50
|
});
|
|
39
51
|
|
|
40
|
-
test("uses
|
|
41
|
-
const root = mkdtempSync(join(tmpdir(), "goalbuddy-
|
|
52
|
+
test("uses readable card titles while preserving full objectives", () => {
|
|
53
|
+
const root = mkdtempSync(join(tmpdir(), "goalbuddy-readable-titles-"));
|
|
42
54
|
try {
|
|
43
|
-
const goalDir = join(root, "
|
|
55
|
+
const goalDir = join(root, "readable-titles");
|
|
44
56
|
mkdirSync(join(goalDir, "notes"), { recursive: true });
|
|
45
57
|
writeFileSync(join(goalDir, "state.yaml"), `version: 2
|
|
46
58
|
goal:
|
|
@@ -70,6 +82,13 @@ tasks:
|
|
|
70
82
|
status: queued
|
|
71
83
|
objective: "This objective can stay much more detailed because it belongs in the modal, not on the card face."
|
|
72
84
|
receipt: null
|
|
85
|
+
- id: T004
|
|
86
|
+
title: "Run installed-Cursor runtime proof for a named model request through the local BYOK bridge"
|
|
87
|
+
type: worker
|
|
88
|
+
assignee: Worker
|
|
89
|
+
status: queued
|
|
90
|
+
objective: "Run installed-Cursor runtime proof for a named model request through the local BYOK bridge."
|
|
91
|
+
receipt: null
|
|
73
92
|
`);
|
|
74
93
|
|
|
75
94
|
const payload = createBoardPayload(goalDir);
|
|
@@ -77,6 +96,10 @@ tasks:
|
|
|
77
96
|
assert.equal(payload.tasks.find((task) => task.id === "T001").objective.includes("admin_seed_metrics.enrichment_qa"), true);
|
|
78
97
|
assert.equal(payload.tasks.find((task) => task.id === "T002").title, "Implement /contacts/con_aaron_keller route");
|
|
79
98
|
assert.equal(payload.tasks.find((task) => task.id === "T003").title, "Human-friendly release title");
|
|
99
|
+
assert.equal(
|
|
100
|
+
payload.tasks.find((task) => task.id === "T004").title,
|
|
101
|
+
"Run installed-Cursor runtime proof for a named model request through the local BYOK bridge",
|
|
102
|
+
);
|
|
80
103
|
} finally {
|
|
81
104
|
rmSync(root, { recursive: true, force: true });
|
|
82
105
|
}
|
|
@@ -249,6 +272,7 @@ test("writes a minimal GoalBuddy web app into the goal directory", () => {
|
|
|
249
272
|
assert.match(css, /:root\[data-theme="dark"\]/);
|
|
250
273
|
assert.match(css, /:root\[data-density="compact"\] \.task-card/);
|
|
251
274
|
assert.match(css, /:root\[data-completed-visibility="collapse"\]/);
|
|
275
|
+
assert.match(css, /-webkit-line-clamp: 5/);
|
|
252
276
|
assert.match(css, /\.subgoal-board/);
|
|
253
277
|
assert.match(css, /\.board-error/);
|
|
254
278
|
assert.match(js, /new EventSource\("\.\/events"\)/);
|
|
@@ -558,6 +582,33 @@ test("serves multiple local boards from one shared hub URL", async () => {
|
|
|
558
582
|
}
|
|
559
583
|
});
|
|
560
584
|
|
|
585
|
+
test("unregistered board paths explain hub reuse instead of stale-port cleanup", async () => {
|
|
586
|
+
const root = mkdtempSync(join(tmpdir(), "goalbuddy-local-board-unregistered-"));
|
|
587
|
+
const goalDir = join(root, "first-goal");
|
|
588
|
+
try {
|
|
589
|
+
mkdirSync(join(goalDir, "notes"), { recursive: true });
|
|
590
|
+
writeFileSync(join(goalDir, "state.yaml"), stateYaml("active", { title: "First Goal", slug: "first-goal" }));
|
|
591
|
+
|
|
592
|
+
const server = await startBoardServer({ goalDir, host: "127.0.0.1", port: 0 });
|
|
593
|
+
try {
|
|
594
|
+
const baseUrl = new URL(server.url).origin;
|
|
595
|
+
const missingResponse = await fetch(`${baseUrl}/rinova-client-revision-redesign/`);
|
|
596
|
+
assert.equal(missingResponse.status, 404);
|
|
597
|
+
const message = await missingResponse.text();
|
|
598
|
+
assert.match(message, /board path is not registered/i);
|
|
599
|
+
assert.match(message, /multi-board hub/i);
|
|
600
|
+
assert.match(message, /Do not stop it just because a \/<slug>\/ board URL returned 404/);
|
|
601
|
+
assert.match(message, /npx goalbuddy board <goal-dir>/);
|
|
602
|
+
assert.match(message, /First Goal/);
|
|
603
|
+
assert.match(message, /\/api\/boards/);
|
|
604
|
+
} finally {
|
|
605
|
+
await server.close();
|
|
606
|
+
}
|
|
607
|
+
} finally {
|
|
608
|
+
rmSync(root, { recursive: true, force: true });
|
|
609
|
+
}
|
|
610
|
+
});
|
|
611
|
+
|
|
561
612
|
async function readUntil(reader, pattern) {
|
|
562
613
|
const decoder = new TextDecoder();
|
|
563
614
|
let text = "";
|