goalbuddy 0.3.6 → 0.3.7
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/README.md +17 -8
- package/RELEASE-0.3.5.md +4 -4
- package/RELEASE-0.3.7.md +127 -0
- package/goalbuddy/SKILL.md +19 -10
- package/goalbuddy/scripts/check-goal-state.mjs +53 -0
- package/goalbuddy/scripts/render-task-prompt.mjs +22 -1
- package/{plugins/goalbuddy/skills/goalbuddy/extend → goalbuddy/surfaces}/local-goal-board/README.md +7 -9
- package/{plugins/goalbuddy/skills/goalbuddy/extend → goalbuddy/surfaces}/local-goal-board/examples/sample-goal/state.yaml +5 -5
- package/{plugins/goalbuddy/skills/goalbuddy/extend → goalbuddy/surfaces}/local-goal-board/examples/subgoal-parent/state.yaml +3 -3
- package/goalbuddy/{extend → surfaces}/local-goal-board/examples/subgoal-parent/subgoals/T004-board-view/state.yaml +3 -3
- package/goalbuddy/{extend → surfaces}/local-goal-board/scripts/lib/goal-board.mjs +2 -2
- package/goalbuddy/{extend → surfaces}/local-goal-board/scripts/local-goal-board.mjs +4 -4
- package/goalbuddy/{extend → surfaces}/local-goal-board/test/local-goal-board.test.mjs +8 -8
- package/goalbuddy/templates/goal.md +9 -0
- package/goalbuddy/templates/state.yaml +7 -6
- package/internal/assets/goalbuddy-v0.3.7-release.png +0 -0
- package/internal/cli/goal-maker.mjs +9 -711
- package/package.json +4 -4
- package/plugins/goalbuddy/.claude-plugin/plugin.json +3 -4
- package/plugins/goalbuddy/.codex-plugin/plugin.json +5 -6
- package/plugins/goalbuddy/README.md +4 -3
- package/plugins/goalbuddy/skills/goalbuddy/SKILL.md +19 -10
- package/plugins/goalbuddy/skills/goalbuddy/scripts/check-goal-state.mjs +53 -0
- package/plugins/goalbuddy/skills/goalbuddy/scripts/render-task-prompt.mjs +22 -1
- package/{goalbuddy/extend → plugins/goalbuddy/skills/goalbuddy/surfaces}/local-goal-board/README.md +7 -9
- package/{goalbuddy/extend → plugins/goalbuddy/skills/goalbuddy/surfaces}/local-goal-board/examples/sample-goal/state.yaml +5 -5
- package/{goalbuddy/extend → plugins/goalbuddy/skills/goalbuddy/surfaces}/local-goal-board/examples/subgoal-parent/state.yaml +3 -3
- package/plugins/goalbuddy/skills/goalbuddy/{extend → surfaces}/local-goal-board/examples/subgoal-parent/subgoals/T004-board-view/state.yaml +3 -3
- package/plugins/goalbuddy/skills/goalbuddy/{extend → surfaces}/local-goal-board/scripts/lib/goal-board.mjs +2 -2
- package/plugins/goalbuddy/skills/goalbuddy/{extend → surfaces}/local-goal-board/scripts/local-goal-board.mjs +4 -4
- package/plugins/goalbuddy/skills/goalbuddy/{extend → surfaces}/local-goal-board/test/local-goal-board.test.mjs +8 -8
- package/plugins/goalbuddy/skills/goalbuddy/templates/goal.md +9 -0
- package/plugins/goalbuddy/skills/goalbuddy/templates/state.yaml +7 -6
- package/examples/extend-catalog-workflow/goal.md +0 -53
- package/examples/extend-catalog-workflow/notes/T001-extension-model-map.md +0 -47
- package/examples/extend-catalog-workflow/notes/T002-architecture-decision.md +0 -48
- package/examples/extend-catalog-workflow/notes/T003-implementation-summary.md +0 -43
- package/examples/extend-catalog-workflow/notes/T004-root-extend-folder.md +0 -24
- package/examples/extend-catalog-workflow/notes/T005-layout-cleanup.md +0 -46
- package/examples/extend-catalog-workflow/notes/T006-catalog-location.md +0 -50
- package/examples/extend-catalog-workflow/notes/T999-completion-audit.md +0 -36
- package/examples/extend-catalog-workflow/state.yaml +0 -327
- package/examples/github-pr-workflow-extension/pr-handoff.md +0 -46
- package/goalbuddy/extend/github-projects/README.md +0 -105
- package/goalbuddy/extend/github-projects/examples/goal-board-sync/state.yaml +0 -63
- package/goalbuddy/extend/github-projects/extension.yaml +0 -43
- package/goalbuddy/extend/github-projects/scripts/lib/github-projects.mjs +0 -728
- package/goalbuddy/extend/github-projects/scripts/lib/goal-state.mjs +0 -362
- package/goalbuddy/extend/github-projects/scripts/sync-github-project.mjs +0 -193
- package/goalbuddy/extend/github-projects/test/github-projects.test.mjs +0 -267
- package/goalbuddy/extend/local-goal-board/extension.yaml +0 -39
- package/internal/assets/extend-release.png +0 -0
- package/internal/assets/extend-release.svg +0 -83
- package/plugins/goalbuddy/skills/goalbuddy/extend/github-projects/README.md +0 -105
- package/plugins/goalbuddy/skills/goalbuddy/extend/github-projects/examples/goal-board-sync/state.yaml +0 -63
- package/plugins/goalbuddy/skills/goalbuddy/extend/github-projects/extension.yaml +0 -43
- package/plugins/goalbuddy/skills/goalbuddy/extend/github-projects/scripts/lib/github-projects.mjs +0 -728
- package/plugins/goalbuddy/skills/goalbuddy/extend/github-projects/scripts/lib/goal-state.mjs +0 -362
- package/plugins/goalbuddy/skills/goalbuddy/extend/github-projects/scripts/sync-github-project.mjs +0 -193
- package/plugins/goalbuddy/skills/goalbuddy/extend/github-projects/test/github-projects.test.mjs +0 -267
- package/plugins/goalbuddy/skills/goalbuddy/extend/local-goal-board/extension.yaml +0 -39
- /package/goalbuddy/{extend → surfaces}/local-goal-board/assets/goalbuddy-mark.png +0 -0
- /package/goalbuddy/{extend → surfaces}/local-goal-board/examples/sample-goal/notes/T001-scout.md +0 -0
- /package/goalbuddy/{extend → surfaces}/local-goal-board/examples/subgoal-parent/goal.md +0 -0
- /package/goalbuddy/{extend → surfaces}/local-goal-board/examples/subgoal-parent/notes/.gitkeep +0 -0
- /package/goalbuddy/{extend → surfaces}/local-goal-board/examples/subgoal-parent/subgoals/T004-board-view/goal.md +0 -0
- /package/goalbuddy/{extend → surfaces}/local-goal-board/examples/subgoal-parent/subgoals/T004-board-view/notes/.gitkeep +0 -0
- /package/plugins/goalbuddy/skills/goalbuddy/{extend → surfaces}/local-goal-board/assets/goalbuddy-mark.png +0 -0
- /package/plugins/goalbuddy/skills/goalbuddy/{extend → surfaces}/local-goal-board/examples/sample-goal/notes/T001-scout.md +0 -0
- /package/plugins/goalbuddy/skills/goalbuddy/{extend → surfaces}/local-goal-board/examples/subgoal-parent/goal.md +0 -0
- /package/plugins/goalbuddy/skills/goalbuddy/{extend → surfaces}/local-goal-board/examples/subgoal-parent/notes/.gitkeep +0 -0
- /package/plugins/goalbuddy/skills/goalbuddy/{extend → surfaces}/local-goal-board/examples/subgoal-parent/subgoals/T004-board-view/goal.md +0 -0
- /package/plugins/goalbuddy/skills/goalbuddy/{extend → surfaces}/local-goal-board/examples/subgoal-parent/subgoals/T004-board-view/notes/.gitkeep +0 -0
|
@@ -1,267 +0,0 @@
|
|
|
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
|
-
});
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
id: local-goal-board
|
|
2
|
-
name: Local Goal Board
|
|
3
|
-
kind: visualization
|
|
4
|
-
version: 0.2.0
|
|
5
|
-
source_of_truth: local
|
|
6
|
-
description: Generate and serve a GoalBuddy-branded local Kanban board that updates live from a goal directory's state.yaml, notes, and linked depth-1 sub-goals.
|
|
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 through the shared goalbuddy.localhost:41737 local board hub with live SSE updates from state.yaml, notes, and linked depth-1 sub-goals. Multiple active boards are available from the board header switcher. 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
|
-
- A parent task links a depth-1 child board under subgoals/.
|
|
13
|
-
activation: user_requested
|
|
14
|
-
outputs:
|
|
15
|
-
- Local GoalBuddy board web app
|
|
16
|
-
- Live state.yaml and notes viewer
|
|
17
|
-
requires_approval: false
|
|
18
|
-
safe_by_default: true
|
|
19
|
-
reads:
|
|
20
|
-
- docs/goals/<slug>/state.yaml
|
|
21
|
-
- docs/goals/<slug>/notes
|
|
22
|
-
- docs/goals/<slug>/subgoals
|
|
23
|
-
writes:
|
|
24
|
-
- docs/goals/<slug>/.goalbuddy-board
|
|
25
|
-
side_effects:
|
|
26
|
-
- Starts or reuses the shared local-only HTTP board hub when not run with --once.
|
|
27
|
-
agent_instructions:
|
|
28
|
-
- Use the bundled local-goal-board script; do not build a static-only mockup.
|
|
29
|
-
- Keep state.yaml authoritative; the app is only a viewer.
|
|
30
|
-
- Require live no-manual-reload updates through the local server event stream.
|
|
31
|
-
- Keep the generated UI simple, clean, minimal, and GoalBuddy branded.
|
|
32
|
-
auth:
|
|
33
|
-
env: []
|
|
34
|
-
supports:
|
|
35
|
-
dry_run: true
|
|
36
|
-
live_local_board: true
|
|
37
|
-
live_updates: true
|
|
38
|
-
credentials_required: false
|
|
39
|
-
generated_goal_artifact: true
|
|
Binary file
|
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
<svg xmlns="http://www.w3.org/2000/svg" width="1600" height="900" viewBox="0 0 1600 900" role="img" aria-labelledby="title desc">
|
|
2
|
-
<title id="title">GoalBuddy Extend release graphic</title>
|
|
3
|
-
<desc id="desc">A white-background black-and-red diagram showing a vague Codex goal becoming a GoalBuddy board, then extending to optional add-ons through a GitHub-hosted catalog.</desc>
|
|
4
|
-
<defs>
|
|
5
|
-
<style>
|
|
6
|
-
.bg { fill: #ffffff; }
|
|
7
|
-
.ink { fill: none; stroke: #141414; stroke-width: 8; stroke-linecap: round; stroke-linejoin: round; }
|
|
8
|
-
.thin { fill: none; stroke: #141414; stroke-width: 5; stroke-linecap: round; stroke-linejoin: round; }
|
|
9
|
-
.red { fill: none; stroke: #e1261c; stroke-width: 8; stroke-linecap: round; stroke-linejoin: round; }
|
|
10
|
-
.redfill { fill: #e1261c; }
|
|
11
|
-
.inkfill { fill: #141414; }
|
|
12
|
-
.muted { fill: #5b5f68; }
|
|
13
|
-
text { font-family: Arial, Helvetica, sans-serif; letter-spacing: 0; }
|
|
14
|
-
.label { font-size: 40px; font-weight: 700; }
|
|
15
|
-
.small { font-size: 26px; font-weight: 500; }
|
|
16
|
-
.mono { font-family: Menlo, Consolas, monospace; font-size: 30px; font-weight: 700; }
|
|
17
|
-
.title { font-size: 86px; font-weight: 800; }
|
|
18
|
-
</style>
|
|
19
|
-
</defs>
|
|
20
|
-
|
|
21
|
-
<rect class="bg" width="1600" height="900"/>
|
|
22
|
-
|
|
23
|
-
<text x="110" y="130" class="title inkfill">goalbuddy EXTEND</text>
|
|
24
|
-
<path class="red" d="M960 102 C1105 70 1255 76 1385 112"/>
|
|
25
|
-
<path class="red" d="M1372 91 L1394 116 L1362 130"/>
|
|
26
|
-
<text x="115" y="190" class="small muted">Open-ended Codex work becomes a local board. Optional integrations stay outside the core.</text>
|
|
27
|
-
|
|
28
|
-
<g transform="translate(105 280)">
|
|
29
|
-
<rect x="0" y="0" width="315" height="190" rx="24" class="thin"/>
|
|
30
|
-
<text x="34" y="58" class="label inkfill">vague goal</text>
|
|
31
|
-
<text x="34" y="105" class="small muted">“improve this”</text>
|
|
32
|
-
<path class="thin" d="M36 142 H212"/>
|
|
33
|
-
<path class="thin" d="M36 165 H152"/>
|
|
34
|
-
</g>
|
|
35
|
-
|
|
36
|
-
<path class="ink" d="M445 375 H565"/>
|
|
37
|
-
<path class="ink" d="M542 348 L572 375 L542 402"/>
|
|
38
|
-
|
|
39
|
-
<g transform="translate(600 250)">
|
|
40
|
-
<rect x="0" y="0" width="430" height="250" rx="28" class="ink"/>
|
|
41
|
-
<text x="38" y="62" class="label inkfill">local board</text>
|
|
42
|
-
<g transform="translate(42 98)">
|
|
43
|
-
<circle cx="20" cy="20" r="13" class="redfill"/>
|
|
44
|
-
<text x="52" y="30" class="small inkfill">Scout maps repo</text>
|
|
45
|
-
</g>
|
|
46
|
-
<g transform="translate(42 145)">
|
|
47
|
-
<circle cx="20" cy="20" r="13" class="redfill"/>
|
|
48
|
-
<text x="52" y="30" class="small inkfill">Judge picks scope</text>
|
|
49
|
-
</g>
|
|
50
|
-
<g transform="translate(42 192)">
|
|
51
|
-
<circle cx="20" cy="20" r="13" class="redfill"/>
|
|
52
|
-
<text x="52" y="30" class="small inkfill">Worker patches</text>
|
|
53
|
-
</g>
|
|
54
|
-
</g>
|
|
55
|
-
|
|
56
|
-
<path class="ink" d="M1060 375 H1130"/>
|
|
57
|
-
<path class="ink" d="M1107 348 L1137 375 L1107 402"/>
|
|
58
|
-
|
|
59
|
-
<g transform="translate(1160 245)">
|
|
60
|
-
<rect x="0" y="0" width="330" height="260" rx="28" class="red"/>
|
|
61
|
-
<text x="38" y="64" class="label inkfill">extend</text>
|
|
62
|
-
<text x="38" y="108" class="mono redfill">catalog.json</text>
|
|
63
|
-
<path class="thin" d="M42 144 H255"/>
|
|
64
|
-
<text x="42" y="190" class="small inkfill">publish</text>
|
|
65
|
-
<text x="42" y="228" class="small inkfill">report</text>
|
|
66
|
-
<text x="165" y="190" class="small inkfill">intake</text>
|
|
67
|
-
<text x="165" y="228" class="small inkfill">roles</text>
|
|
68
|
-
</g>
|
|
69
|
-
|
|
70
|
-
<g transform="translate(175 640)">
|
|
71
|
-
<path class="thin" d="M0 0 H1250"/>
|
|
72
|
-
<text x="0" y="62" class="small inkfill">core stays stable</text>
|
|
73
|
-
<text x="355" y="62" class="small inkfill">extensions move through GitHub</text>
|
|
74
|
-
<text x="870" y="62" class="small inkfill">state.yaml remains truth</text>
|
|
75
|
-
<circle cx="305" cy="51" r="7" class="redfill"/>
|
|
76
|
-
<circle cx="820" cy="51" r="7" class="redfill"/>
|
|
77
|
-
</g>
|
|
78
|
-
|
|
79
|
-
<g transform="translate(1110 735)">
|
|
80
|
-
<rect x="0" y="0" width="300" height="72" rx="36" class="red"/>
|
|
81
|
-
<text x="38" y="47" class="mono redfill">npx goalbuddy</text>
|
|
82
|
-
</g>
|
|
83
|
-
</svg>
|
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
# GitHub Projects
|
|
2
|
-
|
|
3
|
-
Mirror a GoalBuddy `state.yaml` board into GitHub Projects without making GitHub the source of truth.
|
|
4
|
-
|
|
5
|
-
This extension ports the GitHub Projects work from PR #1 into the catalog-based extension system. It keeps the package core dependency-free and optional, while giving teams a practical way to publish a GoalBuddy board into a familiar project surface.
|
|
6
|
-
|
|
7
|
-
## Use When
|
|
8
|
-
|
|
9
|
-
- A long-running GoalBuddy board needs stakeholder visibility in GitHub Projects.
|
|
10
|
-
- A team wants one-way sync from `state.yaml` into ProjectV2 draft issues.
|
|
11
|
-
- The PM needs a dry-run plan before using GitHub credentials.
|
|
12
|
-
- Existing GoalBuddy receipts, verification commands, allowed files, owners, and dependencies should be visible in a board layout.
|
|
13
|
-
|
|
14
|
-
## What It Creates
|
|
15
|
-
|
|
16
|
-
The live sync ensures a GitHub Project has:
|
|
17
|
-
|
|
18
|
-
- Draft issues keyed by `Task ID`, so reruns update existing cards instead of duplicating them.
|
|
19
|
-
- Status mapping: `queued -> Todo`, `active -> In Progress`, `blocked -> Blocked`, `done -> Done`.
|
|
20
|
-
- Single-select fields for `Status`, `Priority`, `Work Type`, and `Agent Lane`.
|
|
21
|
-
- Text fields for `Task ID`, `Owner`, `Goal Role`, `Agent Responsible`, `Credential Gate`, `Parent ID`, `Depends On`, `Receipt Summary`, `Verify`, `Allowed Files`, and `Goal Updated`.
|
|
22
|
-
- A `Goal Board` board-layout view for PM flow.
|
|
23
|
-
|
|
24
|
-
The extension must use the bundled sync script for live writes. Do not use Computer Use, browser automation, or the GitHub web UI to create or repair the board view. Do not replace the script with `gh project` commands; `gh project` does not expose the complete ProjectV2 view creation path this extension needs. The script uses GitHub GraphQL for projects, fields, items, and draft issues, plus the GitHub REST Project views endpoint for the `Goal Board` view.
|
|
25
|
-
|
|
26
|
-
The sync only creates or reuses `Goal Board`. It does not create a default Table view, an `Agent Workboard`, or extra role-specific views. GitHub may still show views that already existed on the Project.
|
|
27
|
-
|
|
28
|
-
The extension does not promise custom board grouping or sort order. GitHub's public Project views REST API currently accepts `name`, `layout`, `filter`, and `visible_fields` when creating a view, and GraphQL exposes grouping/sort fields for reading but not a public mutation for saving them. Because that display state cannot be written reliably through the public API, the sync only creates supported fields, cards, and the single `Goal Board` view.
|
|
29
|
-
|
|
30
|
-
## Inputs
|
|
31
|
-
|
|
32
|
-
- `docs/goals/<slug>/state.yaml`
|
|
33
|
-
- Optional `GITHUB_PROJECT_ID`
|
|
34
|
-
- Optional `GITHUB_PROJECT_OWNER` and `GITHUB_PROJECT_NUMBER`
|
|
35
|
-
- `GITHUB_TOKEN` or `GH_TOKEN` for live sync
|
|
36
|
-
|
|
37
|
-
## Dry Run
|
|
38
|
-
|
|
39
|
-
Dry-run mode does not call GitHub:
|
|
40
|
-
|
|
41
|
-
```bash
|
|
42
|
-
node extend/github-projects/scripts/sync-github-project.mjs \
|
|
43
|
-
--state docs/goals/<slug>/state.yaml \
|
|
44
|
-
--dry-run
|
|
45
|
-
```
|
|
46
|
-
|
|
47
|
-
For structured output:
|
|
48
|
-
|
|
49
|
-
```bash
|
|
50
|
-
node extend/github-projects/scripts/sync-github-project.mjs \
|
|
51
|
-
--state docs/goals/<slug>/state.yaml \
|
|
52
|
-
--dry-run \
|
|
53
|
-
--json
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
## Live Sync
|
|
57
|
-
|
|
58
|
-
Always run the bundled script for live sync. It creates or reuses the `Goal Board` view as part of the same run that syncs fields and draft issues.
|
|
59
|
-
|
|
60
|
-
Use a ProjectV2 node ID:
|
|
61
|
-
|
|
62
|
-
```bash
|
|
63
|
-
GITHUB_TOKEN=... node extend/github-projects/scripts/sync-github-project.mjs \
|
|
64
|
-
--state docs/goals/<slug>/state.yaml \
|
|
65
|
-
--project-id <project-node-id>
|
|
66
|
-
```
|
|
67
|
-
|
|
68
|
-
Or use an owner and project number:
|
|
69
|
-
|
|
70
|
-
```bash
|
|
71
|
-
GITHUB_TOKEN=... node extend/github-projects/scripts/sync-github-project.mjs \
|
|
72
|
-
--state docs/goals/<slug>/state.yaml \
|
|
73
|
-
--owner <user-or-org> \
|
|
74
|
-
--project-number <number>
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
Environment alternatives:
|
|
78
|
-
|
|
79
|
-
- `GITHUB_PROJECT_ID`
|
|
80
|
-
- `GITHUB_PROJECT_OWNER`
|
|
81
|
-
- `GITHUB_PROJECT_NUMBER`
|
|
82
|
-
- `GITHUB_TOKEN` or `GH_TOKEN`
|
|
83
|
-
|
|
84
|
-
## Verification
|
|
85
|
-
|
|
86
|
-
```bash
|
|
87
|
-
node --test extend/github-projects/test/*.test.mjs
|
|
88
|
-
node extend/github-projects/scripts/sync-github-project.mjs \
|
|
89
|
-
--state extend/github-projects/examples/goal-board-sync/state.yaml \
|
|
90
|
-
--dry-run
|
|
91
|
-
node extend/github-projects/scripts/sync-github-project.mjs \
|
|
92
|
-
--state extend/github-projects/examples/goal-board-sync/state.yaml \
|
|
93
|
-
--dry-run \
|
|
94
|
-
--json
|
|
95
|
-
```
|
|
96
|
-
|
|
97
|
-
## Boundaries
|
|
98
|
-
|
|
99
|
-
- `state.yaml` remains authoritative.
|
|
100
|
-
- The sync is one-way from GoalBuddy to GitHub Projects.
|
|
101
|
-
- Missing GitHub credentials block only live sync, not local dry-run validation.
|
|
102
|
-
- Live sync creates or updates GitHub Project draft issues, fields, and the `Goal Board` view through the bundled script.
|
|
103
|
-
- Agents must not fall back to Computer Use, browser automation, or the GitHub web UI for Project setup.
|
|
104
|
-
- Agents must not claim ProjectV2 board views are UI-only; GitHub's REST Project views API supports creating board-layout views.
|
|
105
|
-
- Native GitHub issue hierarchy and dependencies are represented as fields because ProjectV2 draft issues do not provide full issue relationship semantics.
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
version: 2
|
|
2
|
-
|
|
3
|
-
goal:
|
|
4
|
-
title: "Goal board sync MVP"
|
|
5
|
-
slug: "goal-board-sync-mvp"
|
|
6
|
-
kind: specific
|
|
7
|
-
tranche: "Mirror a GoalBuddy board into an external Kanban surface."
|
|
8
|
-
status: active
|
|
9
|
-
|
|
10
|
-
rules:
|
|
11
|
-
pm_owns_state: true
|
|
12
|
-
one_active_task: true
|
|
13
|
-
max_write_workers: 1
|
|
14
|
-
no_implementation_without_worker_or_pm_task: true
|
|
15
|
-
no_completion_without_judge_or_pm_audit: true
|
|
16
|
-
|
|
17
|
-
active_task: T002
|
|
18
|
-
|
|
19
|
-
tasks:
|
|
20
|
-
- id: T001
|
|
21
|
-
type: scout
|
|
22
|
-
assignee: Scout
|
|
23
|
-
status: done
|
|
24
|
-
priority: P3
|
|
25
|
-
objective: "Map external board API requirements."
|
|
26
|
-
inputs:
|
|
27
|
-
- "Board API docs"
|
|
28
|
-
constraints:
|
|
29
|
-
- "Read-only."
|
|
30
|
-
expected_output:
|
|
31
|
-
- "Required scopes"
|
|
32
|
-
- "Field mapping"
|
|
33
|
-
receipt:
|
|
34
|
-
result: done
|
|
35
|
-
summary: "The board sync can read GoalBuddy state.yaml and mirror tasks into an external board."
|
|
36
|
-
- id: T002
|
|
37
|
-
type: worker
|
|
38
|
-
assignee: Worker
|
|
39
|
-
status: active
|
|
40
|
-
priority: P1
|
|
41
|
-
objective: "Implement one-way external board sync."
|
|
42
|
-
parent: T001
|
|
43
|
-
depends_on:
|
|
44
|
-
- T001
|
|
45
|
-
allowed_files:
|
|
46
|
-
- "extend/github-projects/scripts/**"
|
|
47
|
-
- "README.md"
|
|
48
|
-
verify:
|
|
49
|
-
- "node --test extend/github-projects/test/*.test.mjs"
|
|
50
|
-
- "node extend/github-projects/scripts/sync-github-project.mjs --state extend/github-projects/examples/goal-board-sync/state.yaml --dry-run"
|
|
51
|
-
stop_if:
|
|
52
|
-
- "Board API credentials are unavailable."
|
|
53
|
-
receipt: null
|
|
54
|
-
- id: T003
|
|
55
|
-
type: judge
|
|
56
|
-
assignee: Judge
|
|
57
|
-
status: queued
|
|
58
|
-
priority: P2
|
|
59
|
-
objective: "Audit whether the board sync MVP is ready to document."
|
|
60
|
-
parent: T002
|
|
61
|
-
depends_on:
|
|
62
|
-
- T002
|
|
63
|
-
receipt: null
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
id: github-projects
|
|
2
|
-
name: GitHub Projects
|
|
3
|
-
kind: integration
|
|
4
|
-
version: 0.1.1
|
|
5
|
-
source_of_truth: local
|
|
6
|
-
description: Mirror GoalBuddy state.yaml tasks into GitHub Projects with dry-run planning, ProjectV2 fields, draft issue upserts, and one Goal Board view.
|
|
7
|
-
local_use_prompt: Run the bundled GitHub Projects sync script for docs/goals/<slug>/state.yaml. Use --dry-run first unless the user has approved live GitHub writes, then run the same script with GITHUB_TOKEN or GH_TOKEN and --project-id or --owner plus --project-number. Do not use Computer Use, browser automation, the GitHub web UI, or gh project as a fallback.
|
|
8
|
-
use_when:
|
|
9
|
-
- A long-running GoalBuddy board needs stakeholder visibility in GitHub Projects.
|
|
10
|
-
- The team wants one-way sync from state.yaml into ProjectV2 draft issues.
|
|
11
|
-
- The PM needs a safe dry-run plan before using GitHub credentials.
|
|
12
|
-
- GoalBuddy receipts, verification commands, allowed files, owners, role/agent fields, credential gates, and dependencies should appear in a GitHub board.
|
|
13
|
-
activation: publish_handoff
|
|
14
|
-
outputs:
|
|
15
|
-
- GitHub Projects dry-run plan
|
|
16
|
-
- GitHub ProjectV2 draft issue sync
|
|
17
|
-
- Goal Board ProjectV2 view
|
|
18
|
-
requires_approval: true
|
|
19
|
-
safe_by_default: false
|
|
20
|
-
reads:
|
|
21
|
-
- docs/goals/<slug>/state.yaml
|
|
22
|
-
- GITHUB_PROJECT_ID
|
|
23
|
-
- GITHUB_PROJECT_OWNER
|
|
24
|
-
- GITHUB_PROJECT_NUMBER
|
|
25
|
-
writes:
|
|
26
|
-
- GitHub ProjectV2 draft issues
|
|
27
|
-
- GitHub ProjectV2 fields
|
|
28
|
-
- GitHub ProjectV2 Goal Board view
|
|
29
|
-
side_effects:
|
|
30
|
-
- Live sync creates or updates GitHub Project draft issues, fields, and views.
|
|
31
|
-
agent_instructions:
|
|
32
|
-
- Use the bundled sync script for all live GitHub Projects writes.
|
|
33
|
-
- Do not use Computer Use, browser automation, or the GitHub web UI to create or repair Project views.
|
|
34
|
-
- Do not replace the bundled script with gh project commands; the script uses GraphQL plus the REST Project views endpoint.
|
|
35
|
-
- Do not claim ProjectV2 board views are UI-only; the GitHub REST Project views API supports creating board-layout views.
|
|
36
|
-
auth:
|
|
37
|
-
env:
|
|
38
|
-
- GITHUB_TOKEN
|
|
39
|
-
supports:
|
|
40
|
-
dry_run: true
|
|
41
|
-
live_github_projects: true
|
|
42
|
-
credentials_required: true
|
|
43
|
-
one_way_sync: true
|