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.
- package/CONTRIBUTING.md +14 -5
- package/README.md +68 -55
- package/goalbuddy/SKILL.md +44 -14
- package/goalbuddy/agents/README.md +15 -8
- package/goalbuddy/extend/github-projects/README.md +105 -0
- package/goalbuddy/extend/github-projects/examples/goal-board-sync/state.yaml +63 -0
- package/goalbuddy/extend/github-projects/extension.yaml +43 -0
- package/goalbuddy/extend/github-projects/scripts/lib/github-projects.mjs +728 -0
- package/goalbuddy/extend/github-projects/scripts/lib/goal-state.mjs +362 -0
- package/goalbuddy/extend/github-projects/scripts/sync-github-project.mjs +193 -0
- package/goalbuddy/extend/github-projects/test/github-projects.test.mjs +267 -0
- package/goalbuddy/extend/local-goal-board/README.md +75 -0
- package/goalbuddy/extend/local-goal-board/assets/goalbuddy-mark.png +0 -0
- package/goalbuddy/extend/local-goal-board/examples/sample-goal/notes/T001-scout.md +3 -0
- package/goalbuddy/extend/local-goal-board/examples/sample-goal/state.yaml +124 -0
- package/goalbuddy/extend/local-goal-board/extension.yaml +37 -0
- package/goalbuddy/extend/local-goal-board/scripts/lib/goal-board.mjs +1225 -0
- package/goalbuddy/extend/local-goal-board/scripts/local-goal-board.mjs +258 -0
- package/goalbuddy/extend/local-goal-board/test/local-goal-board.test.mjs +146 -0
- package/goalbuddy/scripts/check-goal-state.mjs +24 -9
- package/goalbuddy/templates/state.yaml +18 -3
- package/internal/assets/goalbuddy-live-board.jpg +0 -0
- package/internal/cli/goal-maker.mjs +424 -31
- package/internal/cli/postinstall.mjs +3 -3
- package/package.json +7 -2
- package/plugins/goalbuddy/.claude-plugin/plugin.json +24 -0
- package/plugins/goalbuddy/.codex-plugin/plugin.json +5 -4
- package/plugins/goalbuddy/README.md +23 -13
- package/plugins/goalbuddy/agents/goal-judge.md +27 -0
- package/plugins/goalbuddy/agents/goal-scout.md +24 -0
- package/plugins/goalbuddy/agents/goal-worker.md +26 -0
- package/plugins/goalbuddy/commands/goal-prep.md +12 -0
- package/plugins/goalbuddy/skills/goalbuddy/SKILL.md +44 -14
- package/plugins/goalbuddy/skills/goalbuddy/agents/README.md +15 -8
- package/plugins/goalbuddy/skills/goalbuddy/extend/github-projects/README.md +105 -0
- package/plugins/goalbuddy/skills/goalbuddy/extend/github-projects/examples/goal-board-sync/state.yaml +63 -0
- package/plugins/goalbuddy/skills/goalbuddy/extend/github-projects/extension.yaml +43 -0
- package/plugins/goalbuddy/skills/goalbuddy/extend/github-projects/scripts/lib/github-projects.mjs +728 -0
- package/plugins/goalbuddy/skills/goalbuddy/extend/github-projects/scripts/lib/goal-state.mjs +362 -0
- package/plugins/goalbuddy/skills/goalbuddy/extend/github-projects/scripts/sync-github-project.mjs +193 -0
- package/plugins/goalbuddy/skills/goalbuddy/extend/github-projects/test/github-projects.test.mjs +267 -0
- package/plugins/goalbuddy/skills/goalbuddy/extend/local-goal-board/README.md +75 -0
- package/plugins/goalbuddy/skills/goalbuddy/extend/local-goal-board/assets/goalbuddy-mark.png +0 -0
- package/plugins/goalbuddy/skills/goalbuddy/extend/local-goal-board/examples/sample-goal/notes/T001-scout.md +3 -0
- package/plugins/goalbuddy/skills/goalbuddy/extend/local-goal-board/examples/sample-goal/state.yaml +124 -0
- package/plugins/goalbuddy/skills/goalbuddy/extend/local-goal-board/extension.yaml +37 -0
- package/plugins/goalbuddy/skills/goalbuddy/extend/local-goal-board/scripts/lib/goal-board.mjs +1225 -0
- package/plugins/goalbuddy/skills/goalbuddy/extend/local-goal-board/scripts/local-goal-board.mjs +258 -0
- package/plugins/goalbuddy/skills/goalbuddy/extend/local-goal-board/test/local-goal-board.test.mjs +146 -0
- package/plugins/goalbuddy/skills/goalbuddy/scripts/check-goal-state.mjs +24 -9
- package/plugins/goalbuddy/skills/goalbuddy/templates/state.yaml +18 -3
package/plugins/goalbuddy/skills/goalbuddy/extend/github-projects/test/github-projects.test.mjs
ADDED
|
@@ -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.
|
|
Binary file
|
package/plugins/goalbuddy/skills/goalbuddy/extend/local-goal-board/examples/sample-goal/state.yaml
ADDED
|
@@ -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
|