goalbuddy 0.2.21 → 0.3.0

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 (51) hide show
  1. package/CONTRIBUTING.md +14 -5
  2. package/README.md +68 -55
  3. package/goalbuddy/SKILL.md +44 -14
  4. package/goalbuddy/agents/README.md +15 -8
  5. package/goalbuddy/extend/github-projects/README.md +105 -0
  6. package/goalbuddy/extend/github-projects/examples/goal-board-sync/state.yaml +63 -0
  7. package/goalbuddy/extend/github-projects/extension.yaml +43 -0
  8. package/goalbuddy/extend/github-projects/scripts/lib/github-projects.mjs +728 -0
  9. package/goalbuddy/extend/github-projects/scripts/lib/goal-state.mjs +362 -0
  10. package/goalbuddy/extend/github-projects/scripts/sync-github-project.mjs +193 -0
  11. package/goalbuddy/extend/github-projects/test/github-projects.test.mjs +267 -0
  12. package/goalbuddy/extend/local-goal-board/README.md +75 -0
  13. package/goalbuddy/extend/local-goal-board/assets/goalbuddy-mark.png +0 -0
  14. package/goalbuddy/extend/local-goal-board/examples/sample-goal/notes/T001-scout.md +3 -0
  15. package/goalbuddy/extend/local-goal-board/examples/sample-goal/state.yaml +124 -0
  16. package/goalbuddy/extend/local-goal-board/extension.yaml +37 -0
  17. package/goalbuddy/extend/local-goal-board/scripts/lib/goal-board.mjs +1225 -0
  18. package/goalbuddy/extend/local-goal-board/scripts/local-goal-board.mjs +258 -0
  19. package/goalbuddy/extend/local-goal-board/test/local-goal-board.test.mjs +146 -0
  20. package/goalbuddy/scripts/check-goal-state.mjs +24 -9
  21. package/goalbuddy/templates/state.yaml +18 -3
  22. package/internal/assets/goalbuddy-live-board.jpg +0 -0
  23. package/internal/cli/goal-maker.mjs +424 -31
  24. package/internal/cli/postinstall.mjs +3 -3
  25. package/package.json +7 -2
  26. package/plugins/goalbuddy/.claude-plugin/plugin.json +24 -0
  27. package/plugins/goalbuddy/.codex-plugin/plugin.json +5 -4
  28. package/plugins/goalbuddy/README.md +23 -13
  29. package/plugins/goalbuddy/agents/goal-judge.md +27 -0
  30. package/plugins/goalbuddy/agents/goal-scout.md +24 -0
  31. package/plugins/goalbuddy/agents/goal-worker.md +26 -0
  32. package/plugins/goalbuddy/commands/goal-prep.md +12 -0
  33. package/plugins/goalbuddy/skills/goalbuddy/SKILL.md +44 -14
  34. package/plugins/goalbuddy/skills/goalbuddy/agents/README.md +15 -8
  35. package/plugins/goalbuddy/skills/goalbuddy/extend/github-projects/README.md +105 -0
  36. package/plugins/goalbuddy/skills/goalbuddy/extend/github-projects/examples/goal-board-sync/state.yaml +63 -0
  37. package/plugins/goalbuddy/skills/goalbuddy/extend/github-projects/extension.yaml +43 -0
  38. package/plugins/goalbuddy/skills/goalbuddy/extend/github-projects/scripts/lib/github-projects.mjs +728 -0
  39. package/plugins/goalbuddy/skills/goalbuddy/extend/github-projects/scripts/lib/goal-state.mjs +362 -0
  40. package/plugins/goalbuddy/skills/goalbuddy/extend/github-projects/scripts/sync-github-project.mjs +193 -0
  41. package/plugins/goalbuddy/skills/goalbuddy/extend/github-projects/test/github-projects.test.mjs +267 -0
  42. package/plugins/goalbuddy/skills/goalbuddy/extend/local-goal-board/README.md +75 -0
  43. package/plugins/goalbuddy/skills/goalbuddy/extend/local-goal-board/assets/goalbuddy-mark.png +0 -0
  44. package/plugins/goalbuddy/skills/goalbuddy/extend/local-goal-board/examples/sample-goal/notes/T001-scout.md +3 -0
  45. package/plugins/goalbuddy/skills/goalbuddy/extend/local-goal-board/examples/sample-goal/state.yaml +124 -0
  46. package/plugins/goalbuddy/skills/goalbuddy/extend/local-goal-board/extension.yaml +37 -0
  47. package/plugins/goalbuddy/skills/goalbuddy/extend/local-goal-board/scripts/lib/goal-board.mjs +1225 -0
  48. package/plugins/goalbuddy/skills/goalbuddy/extend/local-goal-board/scripts/local-goal-board.mjs +258 -0
  49. package/plugins/goalbuddy/skills/goalbuddy/extend/local-goal-board/test/local-goal-board.test.mjs +146 -0
  50. package/plugins/goalbuddy/skills/goalbuddy/scripts/check-goal-state.mjs +24 -9
  51. package/plugins/goalbuddy/skills/goalbuddy/templates/state.yaml +18 -3
@@ -0,0 +1,267 @@
1
+ import { describe, it } from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import { readFile } from "node:fs/promises";
4
+ import { resolve } from "node:path";
5
+ import { GoalStateError, normalizeGoalBoard, parseGoalStateText } from "../scripts/lib/goal-state.mjs";
6
+ import {
7
+ GITHUB_PROJECT_FIELDS,
8
+ GITHUB_PROJECT_VIEWS,
9
+ agentLaneForTask,
10
+ buildDraftIssueBody,
11
+ buildFieldUpdates,
12
+ ensureGoalProjectViews,
13
+ planGitHubProjectSync,
14
+ priorityForTask,
15
+ projectStatusForTask,
16
+ workTypeForTask,
17
+ } from "../scripts/lib/github-projects.mjs";
18
+
19
+ describe("goal state parsing", () => {
20
+ it("normalizes a GoalBuddy v2 board", async () => {
21
+ const text = await readFile(resolve("extend/github-projects/examples/goal-board-sync/state.yaml"), "utf8");
22
+ const board = normalizeGoalBoard(parseGoalStateText(text));
23
+
24
+ assert.equal(board.title, "Goal board sync MVP");
25
+ assert.equal(board.activeTask, "T002");
26
+ assert.equal(board.tasks.length, 3);
27
+ assert.equal(board.tasks[0].title, "Map external board API requirements");
28
+ assert.equal(board.tasks[1].priority, "P1");
29
+ assert.equal(board.tasks[0].receiptSummary, "The board sync can read GoalBuddy state.yaml and mirror tasks into an external board.");
30
+ assert.equal(board.tasks[0].goalRole, "Scout");
31
+ assert.equal(board.tasks[1].agentResponsible, "Worker");
32
+ assert.equal(board.tasks[1].credentialGate, "Credentials");
33
+ assert.equal(board.tasks[1].parentId, "T001");
34
+ assert.deepEqual(board.tasks[1].dependsOn, ["T001"]);
35
+ assert.deepEqual(board.tasks[1].verify, [
36
+ "node --test extend/github-projects/test/*.test.mjs",
37
+ "node extend/github-projects/scripts/sync-github-project.mjs --state extend/github-projects/examples/goal-board-sync/state.yaml --dry-run",
38
+ ]);
39
+ });
40
+
41
+ it("rejects malformed task status", () => {
42
+ const parsed = parseGoalStateText(`
43
+ version: 2
44
+ goal:
45
+ title: "Bad board"
46
+ slug: "bad-board"
47
+ tasks:
48
+ - id: T001
49
+ status: moving
50
+ objective: "Bad status"
51
+ `);
52
+
53
+ assert.throws(() => normalizeGoalBoard(parsed), GoalStateError);
54
+ });
55
+ });
56
+
57
+ describe("GitHub Projects mapping", () => {
58
+ it("plans GitHub draft issue creates and updates by task id", () => {
59
+ const tasks = [
60
+ {
61
+ id: "T001",
62
+ title: "T001: Existing",
63
+ objective: "Existing",
64
+ status: "done",
65
+ type: "scout",
66
+ assignee: "Scout",
67
+ receiptSummary: "Done",
68
+ verify: [],
69
+ allowedFiles: [],
70
+ updatedLabel: "receipt:done",
71
+ },
72
+ {
73
+ id: "T002",
74
+ title: "T002: New",
75
+ objective: "New",
76
+ status: "queued",
77
+ type: "worker",
78
+ assignee: "Worker",
79
+ receiptSummary: "",
80
+ verify: [],
81
+ allowedFiles: [],
82
+ updatedLabel: "receipt:none",
83
+ },
84
+ ];
85
+
86
+ const operations = planGitHubProjectSync(tasks, [
87
+ {
88
+ id: "PVTI_existing",
89
+ taskId: { text: "T001" },
90
+ content: { __typename: "DraftIssue", id: "DI_existing" },
91
+ },
92
+ ]);
93
+
94
+ assert.equal(operations[0].type, "update");
95
+ assert.equal(operations[0].itemId, "PVTI_existing");
96
+ assert.equal(operations[0].draftIssueId, "DI_existing");
97
+ assert.equal(operations[1].type, "create");
98
+ });
99
+
100
+ it("builds GitHub field updates for text and single-select fields", () => {
101
+ const fields = {
102
+ taskId: { id: "F_task" },
103
+ status: { id: "F_status", name: GITHUB_PROJECT_FIELDS.status, options: [{ id: "O_progress", name: "In Progress" }] },
104
+ priority: { id: "F_priority", name: GITHUB_PROJECT_FIELDS.priority, options: [{ id: "O_p1", name: "P1" }] },
105
+ workType: { id: "F_type", name: GITHUB_PROJECT_FIELDS.workType, options: [{ id: "O_execution", name: "Execution" }] },
106
+ agentLane: { id: "F_lane", name: GITHUB_PROJECT_FIELDS.agentLane, options: [{ id: "O_worker", name: "Worker" }] },
107
+ owner: { id: "F_owner" },
108
+ goalRole: { id: "F_goal_role" },
109
+ agentResponsible: { id: "F_agent" },
110
+ credentialGate: { id: "F_gate" },
111
+ parentId: { id: "F_parent" },
112
+ dependsOn: { id: "F_depends" },
113
+ receiptSummary: { id: "F_receipt" },
114
+ verify: { id: "F_verify" },
115
+ allowedFiles: { id: "F_allowed" },
116
+ updated: { id: "F_updated" },
117
+ };
118
+ const task = {
119
+ id: "T002",
120
+ status: "active",
121
+ type: "worker",
122
+ assignee: "Worker",
123
+ goalRole: "Worker",
124
+ agentResponsible: "Worker",
125
+ credentialGate: "None",
126
+ receiptSummary: "",
127
+ verify: ["npm test"],
128
+ allowedFiles: ["scripts/**"],
129
+ parentId: "T001",
130
+ dependsOn: ["T001"],
131
+ updatedLabel: "receipt:none",
132
+ };
133
+
134
+ const updates = buildFieldUpdates(task, fields);
135
+ assert.equal(updates.length, 15);
136
+ assert.deepEqual(updates.find((update) => update.fieldId === "F_status").value, {
137
+ singleSelectOptionId: "O_progress",
138
+ });
139
+ assert.deepEqual(updates.find((update) => update.fieldId === "F_task").value, {
140
+ text: "T002",
141
+ });
142
+ assert.deepEqual(updates.find((update) => update.fieldId === "F_lane").value, {
143
+ singleSelectOptionId: "O_worker",
144
+ });
145
+ assert.deepEqual(updates.find((update) => update.fieldId === "F_goal_role").value, { text: "Worker" });
146
+ assert.deepEqual(updates.find((update) => update.fieldId === "F_agent").value, { text: "Worker" });
147
+ assert.deepEqual(updates.find((update) => update.fieldId === "F_gate").value, { text: "None" });
148
+ assert.deepEqual(updates.find((update) => update.fieldId === "F_parent").value, { text: "T001" });
149
+ assert.deepEqual(updates.find((update) => update.fieldId === "F_depends").value, { text: "T001" });
150
+ });
151
+
152
+ it("builds a draft issue body that points back to the state file", () => {
153
+ const body = buildDraftIssueBody(
154
+ {
155
+ id: "T002",
156
+ objective: "Run sync.",
157
+ status: "active",
158
+ type: "worker",
159
+ assignee: "Worker",
160
+ receiptSummary: "",
161
+ verify: ["npm test"],
162
+ allowedFiles: ["scripts/**"],
163
+ parentId: "T001",
164
+ dependsOn: ["T001"],
165
+ },
166
+ {
167
+ sourcePath: "extend/github-projects/examples/goal-board-sync/state.yaml",
168
+ }
169
+ );
170
+
171
+ assert.match(body, /YAML remains the source of truth/);
172
+ assert.match(body, /Task ID: T002/);
173
+ assert.match(body, /Parent: T001/);
174
+ assert.match(body, /Depends on:/);
175
+ assert.match(body, /extend\/github-projects\/examples\/goal-board-sync\/state\.yaml/);
176
+ });
177
+
178
+ it("creates only the Goal Board GitHub Project view with visible fields when missing", async () => {
179
+ const restCalls = [];
180
+ const views = await ensureGoalProjectViews({
181
+ client: {
182
+ rest: async (path, options) => {
183
+ restCalls.push({ path, options });
184
+ return {
185
+ html_url: "https://github.com/users/example/projects/1/views/2",
186
+ };
187
+ },
188
+ },
189
+ project: {
190
+ number: 1,
191
+ owner: { __typename: "User", login: "example" },
192
+ views: { nodes: [] },
193
+ },
194
+ fields: {
195
+ taskId: { databaseId: 1 },
196
+ status: { databaseId: 2 },
197
+ priority: { databaseId: 3 },
198
+ workType: { databaseId: 4 },
199
+ owner: { databaseId: 5 },
200
+ agentLane: { databaseId: 13 },
201
+ goalRole: { databaseId: 10 },
202
+ agentResponsible: { databaseId: 11 },
203
+ credentialGate: { databaseId: 12 },
204
+ receiptSummary: { databaseId: 6 },
205
+ verify: { databaseId: 7 },
206
+ allowedFiles: { databaseId: 8 },
207
+ updated: { databaseId: 9 },
208
+ },
209
+ });
210
+
211
+ assert.equal(views.board.html_url, "https://github.com/users/example/projects/1/views/2");
212
+ assert.equal(views.agentWorkboard, undefined);
213
+ assert.equal(restCalls.length, 1);
214
+ assert.equal(restCalls[0].path, "users/example/projectsV2/1/views");
215
+ assert.equal(restCalls[0].options.body.layout, "board");
216
+ assert.equal(restCalls[0].options.body.name, GITHUB_PROJECT_VIEWS.board.name);
217
+ assert.deepEqual(restCalls[0].options.body.visible_fields, [3, 2, 4, 5, 10, 11, 13, 12]);
218
+ assert.equal(restCalls[0].options.body.group_by, undefined);
219
+ });
220
+
221
+ it("reuses an existing Goal Board view without creating extra views", async () => {
222
+ const views = await ensureGoalProjectViews({
223
+ client: {
224
+ rest: async () => {
225
+ throw new Error("REST should not be called for existing views.");
226
+ },
227
+ },
228
+ project: {
229
+ number: 1,
230
+ owner: { __typename: "User", login: "example" },
231
+ views: {
232
+ nodes: [
233
+ { id: "PVTV_board", name: "Goal Board", layout: "BOARD_LAYOUT" },
234
+ { id: "PVTV_agent", name: "Agent Workboard", layout: "BOARD_LAYOUT" },
235
+ ],
236
+ },
237
+ },
238
+ fields: {},
239
+ });
240
+
241
+ assert.equal(views.board.id, "PVTV_board");
242
+ assert.equal(views.agentWorkboard, undefined);
243
+ });
244
+
245
+ it("maps GoalBuddy task statuses to native GitHub board statuses", () => {
246
+ assert.equal(projectStatusForTask("queued"), "Todo");
247
+ assert.equal(projectStatusForTask("active"), "In Progress");
248
+ assert.equal(projectStatusForTask("blocked"), "Blocked");
249
+ assert.equal(projectStatusForTask("done"), "Done");
250
+ });
251
+
252
+ it("maps GoalBuddy task types and priorities to lean PM fields", () => {
253
+ assert.equal(workTypeForTask("scout"), "Discovery");
254
+ assert.equal(workTypeForTask("judge"), "Decision");
255
+ assert.equal(workTypeForTask("worker"), "Execution");
256
+ assert.equal(priorityForTask({ status: "blocked", type: "worker" }), "P0");
257
+ assert.equal(priorityForTask({ status: "active", type: "worker" }), "P1");
258
+ assert.equal(priorityForTask({ status: "queued", type: "scout" }), "P2");
259
+ assert.equal(priorityForTask({ status: "done", type: "worker" }), "P3");
260
+ assert.equal(priorityForTask({ status: "queued", type: "scout", priority: "P0" }), "P0");
261
+ assert.equal(agentLaneForTask({ assignee: "PM" }), "PM");
262
+ assert.equal(agentLaneForTask({ agentResponsible: "Scout" }), "Scout");
263
+ assert.equal(agentLaneForTask({ goalRole: "Judge" }), "Judge");
264
+ assert.equal(agentLaneForTask({ agentResponsible: "Worker" }), "Worker");
265
+ assert.equal(agentLaneForTask({ agentResponsible: "User" }), "User");
266
+ });
267
+ });
@@ -0,0 +1,75 @@
1
+ # Local Goal Board
2
+
3
+ Generate a small local GoalBuddy board for a goal directory and watch it update live while agents work.
4
+
5
+ The extension keeps `state.yaml` authoritative. It writes static web app files into the goal directory and serves them from a local-only Node server. The browser subscribes to Server-Sent Events, so cards update as `state.yaml` or `notes/` changes without a manual reload.
6
+
7
+ ## Use When
8
+
9
+ - A human wants a local board view during a GoalBuddy run.
10
+ - The team wants GitHub-Projects-like visibility without GitHub credentials.
11
+ - A goal should expose in-progress, completed, and blocked cards from local files.
12
+
13
+ ## Generate And Serve
14
+
15
+ ```bash
16
+ node extend/local-goal-board/scripts/local-goal-board.mjs \
17
+ --goal docs/goals/<slug>
18
+ ```
19
+
20
+ The generated app includes the bundled `assets/goalbuddy-mark.png`, so the board keeps the GoalBuddy mark after the extension is installed or copied elsewhere.
21
+
22
+ The command writes:
23
+
24
+ ```text
25
+ docs/goals/<slug>/.goalbuddy-board/
26
+ index.html
27
+ styles.css
28
+ app.js
29
+ ```
30
+
31
+ Then it starts a server on `127.0.0.1` and prints the local URL.
32
+
33
+ ## Check Without A Long-Running Server
34
+
35
+ ```bash
36
+ node extend/local-goal-board/scripts/local-goal-board.mjs \
37
+ --goal docs/goals/<slug> \
38
+ --once \
39
+ --json
40
+ ```
41
+
42
+ ## Live Updates
43
+
44
+ The server watches:
45
+
46
+ - `docs/goals/<slug>/state.yaml`
47
+ - `docs/goals/<slug>/notes/`
48
+
49
+ When either changes, the server re-reads the goal board and pushes a fresh board payload to connected browsers over `/events`.
50
+
51
+ ## Board Mapping
52
+
53
+ - `queued` tasks appear under **Todo**.
54
+ - `active` tasks appear under **In Progress**.
55
+ - `blocked` tasks appear under **Blocked**.
56
+ - `done` tasks appear under **Completed**, the right-most column.
57
+
58
+ Clicking a card opens a detail modal with the task objective, status, assignee, inputs, constraints, expected output, verify commands, allowed files, stop conditions, and receipt details. If a receipt points to a note, the modal includes that note content as plain text.
59
+
60
+ ## Verification
61
+
62
+ ```bash
63
+ node --test extend/local-goal-board/test/*.test.mjs
64
+ node extend/local-goal-board/scripts/local-goal-board.mjs \
65
+ --goal extend/local-goal-board/examples/sample-goal \
66
+ --once \
67
+ --json
68
+ ```
69
+
70
+ ## Boundaries
71
+
72
+ - `state.yaml` remains the source of truth.
73
+ - The server binds to `127.0.0.1` by default.
74
+ - The generated UI renders file content as text, not raw HTML.
75
+ - No package dependencies are required.
@@ -0,0 +1,3 @@
1
+ # Scout Note
2
+
3
+ The local board should render this note as plain text in the task detail modal.
@@ -0,0 +1,124 @@
1
+ version: 2
2
+
3
+ goal:
4
+ title: "Local Kanban Board Extension"
5
+ slug: "local-kanban-board-extension"
6
+ kind: specific
7
+ tranche: "Demonstrate local GoalBuddy board rendering."
8
+ status: active
9
+
10
+ active_task: null
11
+
12
+ tasks:
13
+ - id: T001
14
+ type: scout
15
+ assignee: Scout
16
+ status: done
17
+ objective: "Map the goal state inputs."
18
+ receipt:
19
+ result: done
20
+ summary: "T001 completed during the progressive board motion demo."
21
+ - id: T002
22
+ type: worker
23
+ assignee: Worker
24
+ status: done
25
+ objective: "Build the local board viewer."
26
+ receipt:
27
+ result: done
28
+ summary: "T002 completed during the progressive board motion demo."
29
+ - id: T003
30
+ type: worker
31
+ assignee: Worker
32
+ status: blocked
33
+ objective: "Catalog and document the local board extension."
34
+ receipt:
35
+ result: blocked
36
+ summary: "T003 is blocked during the progressive board motion demo."
37
+ - id: T004
38
+ type: judge
39
+ assignee: Judge
40
+ status: done
41
+ objective: "Audit the local board viewer."
42
+ receipt:
43
+ result: done
44
+ summary: "T004 completed during the progressive board motion demo."
45
+ - id: T005
46
+ type: scout
47
+ assignee: Scout
48
+ status: blocked
49
+ objective: "Review live update edge cases."
50
+ receipt:
51
+ result: blocked
52
+ summary: "T005 is blocked during the progressive board motion demo."
53
+ - id: T006
54
+ type: worker
55
+ assignee: Worker
56
+ status: done
57
+ objective: "Tune board spacing for dense card sets."
58
+ receipt:
59
+ result: done
60
+ summary: "T006 completed during the progressive board motion demo."
61
+ - id: T007
62
+ type: worker
63
+ assignee: Worker
64
+ status: blocked
65
+ objective: "Add motion timing polish."
66
+ receipt:
67
+ result: blocked
68
+ summary: "T007 is blocked during the progressive board motion demo."
69
+ - id: T008
70
+ type: judge
71
+ assignee: Judge
72
+ status: done
73
+ objective: "Confirm the board remains readable with many cards."
74
+ receipt:
75
+ result: done
76
+ summary: "T008 completed during the progressive board motion demo."
77
+ - id: T009
78
+ type: scout
79
+ assignee: Scout
80
+ status: done
81
+ objective: "List the extension launch paths."
82
+ receipt:
83
+ result: done
84
+ summary: "T009 completed during the progressive board motion demo."
85
+ - id: T010
86
+ type: worker
87
+ assignee: Worker
88
+ status: done
89
+ objective: "Exercise a queued card moving into active work."
90
+ receipt:
91
+ result: done
92
+ summary: "T010 completed during the progressive board motion demo."
93
+ - id: T011
94
+ type: worker
95
+ assignee: Worker
96
+ status: blocked
97
+ objective: "Demonstrate blocked card handling."
98
+ receipt:
99
+ result: blocked
100
+ summary: "T011 is blocked during the progressive board motion demo."
101
+ - id: T012
102
+ type: judge
103
+ assignee: Judge
104
+ status: done
105
+ objective: "Audit the animation behavior after repeated updates."
106
+ receipt:
107
+ result: done
108
+ summary: "T012 completed during the progressive board motion demo."
109
+ - id: T013
110
+ type: scout
111
+ assignee: Scout
112
+ status: blocked
113
+ objective: "Capture visual proof notes."
114
+ receipt:
115
+ result: blocked
116
+ summary: "T013 is blocked during the progressive board motion demo."
117
+ - id: T014
118
+ type: worker
119
+ assignee: Worker
120
+ status: done
121
+ objective: "Prepare final extension packaging check."
122
+ receipt:
123
+ result: done
124
+ summary: "T014 completed during the progressive board motion demo."
@@ -0,0 +1,37 @@
1
+ id: local-goal-board
2
+ name: Local Goal Board
3
+ kind: visualization
4
+ version: 0.1.0
5
+ source_of_truth: local
6
+ description: Generate and serve a minimal GoalBuddy-branded local Kanban board that updates live from a goal directory's state.yaml and notes.
7
+ local_use_prompt: Run the bundled local board script for docs/goals/<slug>. It writes a tiny web app into docs/goals/<slug>/.goalbuddy-board and serves it on 127.0.0.1 with live SSE updates from state.yaml and notes. Use --once --json for generation checks and no long-running server.
8
+ use_when:
9
+ - A human wants to watch a GoalBuddy run in a local board UI.
10
+ - The goal needs GitHub-Projects-like visibility without GitHub credentials.
11
+ - state.yaml and notes should remain the source of truth while the browser updates live.
12
+ activation: user_requested
13
+ outputs:
14
+ - Local GoalBuddy board web app
15
+ - Live state.yaml and notes viewer
16
+ requires_approval: false
17
+ safe_by_default: true
18
+ reads:
19
+ - docs/goals/<slug>/state.yaml
20
+ - docs/goals/<slug>/notes
21
+ writes:
22
+ - docs/goals/<slug>/.goalbuddy-board
23
+ side_effects:
24
+ - Starts a local-only HTTP server when not run with --once.
25
+ agent_instructions:
26
+ - Use the bundled local-goal-board script; do not build a static-only mockup.
27
+ - Keep state.yaml authoritative; the app is only a viewer.
28
+ - Require live no-manual-reload updates through the local server event stream.
29
+ - Keep the generated UI simple, clean, minimal, and GoalBuddy branded.
30
+ auth:
31
+ env: []
32
+ supports:
33
+ dry_run: true
34
+ live_local_board: true
35
+ live_updates: true
36
+ credentials_required: false
37
+ generated_goal_artifact: true