goalbuddy 0.2.20 → 0.2.21
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 +15 -0
- package/goalbuddy/SKILL.md +37 -0
- package/goalbuddy/scripts/check-update.mjs +102 -0
- package/goalbuddy/templates/goal.md +12 -8
- package/internal/assets/goalbuddy-og.png +0 -0
- package/internal/assets/goalbuddy-readme-hero.png +0 -0
- package/internal/cli/goal-maker.mjs +134 -2
- package/package.json +1 -1
- package/plugins/goalbuddy/.codex-plugin/plugin.json +1 -1
- package/plugins/goalbuddy/README.md +1 -0
- package/plugins/goalbuddy/skills/goalbuddy/SKILL.md +37 -0
- package/plugins/goalbuddy/skills/goalbuddy/scripts/check-update.mjs +102 -0
- package/plugins/goalbuddy/skills/goalbuddy/templates/goal.md +12 -8
package/README.md
CHANGED
|
@@ -124,6 +124,12 @@ Check the local install:
|
|
|
124
124
|
npx goalbuddy doctor
|
|
125
125
|
```
|
|
126
126
|
|
|
127
|
+
Check whether a newer GoalBuddy release is available:
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
npx goalbuddy check-update
|
|
131
|
+
```
|
|
132
|
+
|
|
127
133
|
Use a non-default Codex home:
|
|
128
134
|
|
|
129
135
|
```bash
|
|
@@ -146,6 +152,14 @@ Check board health at any time:
|
|
|
146
152
|
node ~/.codex/skills/goalbuddy/scripts/check-goal-state.mjs docs/goals/<slug>/state.yaml
|
|
147
153
|
```
|
|
148
154
|
|
|
155
|
+
Launch the optional local board viewer for a goal:
|
|
156
|
+
|
|
157
|
+
```bash
|
|
158
|
+
npx goalbuddy board docs/goals/<slug>
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
`goalbuddy board` installs the `local-goal-board` extension from the catalog if needed, writes `.goalbuddy-board/` into the goal directory, and serves a local live-updating board.
|
|
162
|
+
|
|
149
163
|
For a broad prompt like "Improve my project," the first active task should usually be Scout, not Worker:
|
|
150
164
|
|
|
151
165
|
```yaml
|
|
@@ -193,6 +207,7 @@ Current catalog examples include:
|
|
|
193
207
|
|
|
194
208
|
- `github-pr-workflow`: prepares receipt-aligned commit and PR handoff text.
|
|
195
209
|
- `github-projects`: mirrors GoalBuddy boards into GitHub Projects.
|
|
210
|
+
- `local-goal-board`: serves a local GoalBuddy-branded board that updates live from `state.yaml` and `notes/`.
|
|
196
211
|
- `ai-diff-risk-review`: summarizes risk in the current diff.
|
|
197
212
|
- `ci-failure-triage`: maps failing CI back to likely causes and next tasks.
|
|
198
213
|
- `docs-drift-audit`: checks whether docs still match implementation.
|
package/goalbuddy/SKILL.md
CHANGED
|
@@ -28,6 +28,7 @@ During a `$goal-prep` turn, do not perform the user's requested work, even if th
|
|
|
28
28
|
|
|
29
29
|
Allowed `$goal-prep` actions:
|
|
30
30
|
|
|
31
|
+
- run the bundled GoalBuddy update checker and mention a newer version if one is available;
|
|
31
32
|
- ask diagnostic intake questions and wait when required;
|
|
32
33
|
- create or repair only `docs/goals/<slug>/goal.md`, `docs/goals/<slug>/state.yaml`, and `docs/goals/<slug>/notes/`;
|
|
33
34
|
- optionally run the GoalBuddy board checker against that `state.yaml`;
|
|
@@ -37,6 +38,22 @@ Allowed `$goal-prep` actions:
|
|
|
37
38
|
|
|
38
39
|
If the prompt names another skill or tool, such as "use the taste skill", "refresh the taste skill", "look at this repo", "use browser", or "generate assets", record that requirement in the charter and seed tasks. Do not load that skill, browse that repo, or generate those assets during `$goal-prep`.
|
|
39
40
|
|
|
41
|
+
## Update Check
|
|
42
|
+
|
|
43
|
+
At the start of a `$goal-prep` turn, check whether GoalBuddy itself is stale. Run the bundled checker from the installed skill directory when available:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
node <skill-path>/scripts/check-update.mjs --json
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
If the checker reports `update_available: true`, tell the user once before continuing:
|
|
50
|
+
|
|
51
|
+
```text
|
|
52
|
+
GoalBuddy <latest_version> is available. After this turn, update with: npx goalbuddy
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Do not block intake or board creation on update checking. If the checker is missing, cannot find npm, or network access fails, continue silently unless the user asked about updates.
|
|
56
|
+
|
|
40
57
|
## Intake Compiler
|
|
41
58
|
|
|
42
59
|
Before creating, repairing, or running a board, privately translate the user's input into a Goal Intake. The input may be vague, specific, or detailed with an existing plan. Do not dump the intake to the user unless they ask for it.
|
|
@@ -131,6 +148,7 @@ When invoked directly, run intake first. For vague, strategic, improvement-orien
|
|
|
131
148
|
|
|
132
149
|
Do:
|
|
133
150
|
|
|
151
|
+
- check for a newer GoalBuddy version once at the start and mention it without blocking;
|
|
134
152
|
- clarify or infer the goal title and slug;
|
|
135
153
|
- run the Intake Compiler;
|
|
136
154
|
- ask diagnostic intake questions when clarity would materially improve the board;
|
|
@@ -409,6 +427,25 @@ Blocked tasks do not necessarily block the goal. The PM should keep doing safe l
|
|
|
409
427
|
|
|
410
428
|
Avoid setting `goal.status: blocked` for missing input, credentials, production access, destructive-operation permission, or policy decisions. Block the specific task instead, record the missing requirement, and continue with every safe local workaround or adjacent slice.
|
|
411
429
|
|
|
430
|
+
## Operator Escalation
|
|
431
|
+
|
|
432
|
+
When Scout, Judge, Worker, or PM discovers a problem, improvement opportunity, product suggestion, follow-up repair, or tool limitation that should not be fixed inside the current active task, do not let it disappear in chat.
|
|
433
|
+
|
|
434
|
+
The PM may create a board task to prepare a repo-native follow-up. If the user has already approved publishing and the repo/auth state supports it, the PM may create an issue or PR directly and record the link in the receipt. Otherwise, ask the operator one concise question before creating the external artifact:
|
|
435
|
+
|
|
436
|
+
```markdown
|
|
437
|
+
I found [problem or suggestion].
|
|
438
|
+
|
|
439
|
+
Should I:
|
|
440
|
+
1. Create an issue in this repo for it? (Recommended) - [why]
|
|
441
|
+
2. Prepare a PR for the fix/suggestion - [when this is better]
|
|
442
|
+
3. Keep it only in the GoalBuddy board for now - [tradeoff]
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
Use an issue for follow-up work, unclear scope, missing approval, or suggestions that need discussion. Use a PR when the fix is already implemented or safely implementable within the current approved scope. If neither is appropriate, propose a different path and record the decision in `state.yaml`.
|
|
446
|
+
|
|
447
|
+
External issues and PRs are supporting artifacts, not board truth. `state.yaml` remains authoritative, and every issue/PR creation or decision must be reflected in a PM, Worker, or Judge receipt.
|
|
448
|
+
|
|
412
449
|
## Continuation Rule
|
|
413
450
|
|
|
414
451
|
After a task completes, immediately write its receipt and select the next active task unless:
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
3
|
+
import { spawnSync } from "node:child_process";
|
|
4
|
+
import { dirname, join, resolve } from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
|
|
7
|
+
const packageName = "goalbuddy";
|
|
8
|
+
const scriptDir = dirname(fileURLToPath(import.meta.url));
|
|
9
|
+
const args = process.argv.slice(2);
|
|
10
|
+
|
|
11
|
+
const report = {
|
|
12
|
+
package: packageName,
|
|
13
|
+
current_version: findCurrentVersion(),
|
|
14
|
+
latest_version: null,
|
|
15
|
+
update_available: false,
|
|
16
|
+
check_status: "unknown",
|
|
17
|
+
update_command: "npx goalbuddy",
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
try {
|
|
21
|
+
report.latest_version = latestPublishedVersion();
|
|
22
|
+
report.update_available = compareVersions(report.current_version, report.latest_version) < 0;
|
|
23
|
+
report.check_status = "ok";
|
|
24
|
+
} catch (error) {
|
|
25
|
+
report.check_status = "unavailable";
|
|
26
|
+
report.error = error.message;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (args.includes("--json")) {
|
|
30
|
+
console.log(JSON.stringify(report, null, 2));
|
|
31
|
+
} else if (report.check_status !== "ok") {
|
|
32
|
+
console.log(`GoalBuddy update check unavailable: ${report.error}`);
|
|
33
|
+
} else if (report.update_available) {
|
|
34
|
+
console.log(`GoalBuddy ${report.latest_version} is available; installed version is ${report.current_version}.`);
|
|
35
|
+
console.log(`Update with: ${report.update_command}`);
|
|
36
|
+
} else {
|
|
37
|
+
console.log(`GoalBuddy is up to date (${report.current_version}).`);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function findCurrentVersion() {
|
|
41
|
+
const candidates = [
|
|
42
|
+
join(scriptDir, "..", ".goalbuddy-install.json"),
|
|
43
|
+
join(scriptDir, "..", "..", "..", ".codex-plugin", "plugin.json"),
|
|
44
|
+
join(scriptDir, "..", "..", "package.json"),
|
|
45
|
+
];
|
|
46
|
+
|
|
47
|
+
for (const path of candidates) {
|
|
48
|
+
const data = readJson(path);
|
|
49
|
+
const version = data?.package_version || data?.version;
|
|
50
|
+
if (version) return normalizeVersion(version);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return "0.0.0";
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function latestPublishedVersion() {
|
|
57
|
+
if (process.env.GOALBUDDY_TEST_NPM_LATEST_VERSION) {
|
|
58
|
+
return normalizeVersion(process.env.GOALBUDDY_TEST_NPM_LATEST_VERSION);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const result = spawnSync("npm", ["view", packageName, "version"], {
|
|
62
|
+
cwd: resolve(scriptDir, ".."),
|
|
63
|
+
encoding: "utf8",
|
|
64
|
+
timeout: 5000,
|
|
65
|
+
env: {
|
|
66
|
+
...process.env,
|
|
67
|
+
npm_config_update_notifier: "false",
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
if (result.error) throw result.error;
|
|
72
|
+
if (result.status !== 0) {
|
|
73
|
+
const output = `${result.stderr || ""}${result.stdout || ""}`.trim();
|
|
74
|
+
throw new Error(output || `npm view exited with status ${result.status}`);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return normalizeVersion(result.stdout);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function readJson(path) {
|
|
81
|
+
if (!existsSync(path)) return null;
|
|
82
|
+
try {
|
|
83
|
+
return JSON.parse(readFileSync(path, "utf8"));
|
|
84
|
+
} catch {
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function normalizeVersion(value) {
|
|
90
|
+
const match = String(value).trim().match(/^v?(\d+)\.(\d+)\.(\d+)(?:[-+].*)?$/);
|
|
91
|
+
if (!match) throw new Error(`Unsupported version: ${value}`);
|
|
92
|
+
return `${Number(match[1])}.${Number(match[2])}.${Number(match[3])}`;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function compareVersions(left, right) {
|
|
96
|
+
const leftParts = normalizeVersion(left).split(".").map(Number);
|
|
97
|
+
const rightParts = normalizeVersion(right).split(".").map(Number);
|
|
98
|
+
for (let index = 0; index < 3; index += 1) {
|
|
99
|
+
if (leftParts[index] !== rightParts[index]) return leftParts[index] - rightParts[index];
|
|
100
|
+
}
|
|
101
|
+
return 0;
|
|
102
|
+
}
|
|
@@ -61,11 +61,15 @@ On every `/goal` continuation:
|
|
|
61
61
|
|
|
62
62
|
1. Read this charter.
|
|
63
63
|
2. Read `state.yaml`.
|
|
64
|
-
3.
|
|
65
|
-
4.
|
|
66
|
-
5.
|
|
67
|
-
6.
|
|
68
|
-
7.
|
|
69
|
-
8.
|
|
70
|
-
9.
|
|
71
|
-
10.
|
|
64
|
+
3. Run the bundled GoalBuddy update checker when available and mention a newer version without blocking.
|
|
65
|
+
4. Re-check the intake: original request, input shape, authority, proof, blind spots, existing plan facts, and likely misfire.
|
|
66
|
+
5. Work only on the active board task.
|
|
67
|
+
6. Assign Scout, Judge, Worker, or PM according to the task.
|
|
68
|
+
7. Write a compact task receipt.
|
|
69
|
+
8. Update the board.
|
|
70
|
+
9. If Judge selected a safe Worker task with `allowed_files`, `verify`, and `stop_if`, activate it and continue unless blocked.
|
|
71
|
+
10. If a problem, suggestion, or follow-up should become a repo artifact, create an approved issue/PR or ask the operator whether to create one.
|
|
72
|
+
11. Treat a slice audit as a checkpoint, not completion, unless it explicitly proves the full original outcome is complete.
|
|
73
|
+
12. Finish only with a Judge/PM audit receipt that maps receipts and verification back to the original user outcome and records `full_outcome_complete: true`.
|
|
74
|
+
|
|
75
|
+
Issue and PR handoffs are supporting artifacts. `state.yaml` remains authoritative, and every external artifact decision must be recorded in a task receipt.
|
|
Binary file
|
|
Binary file
|
|
@@ -37,7 +37,10 @@ const optionsWithValues = new Set([
|
|
|
37
37
|
"--catalog",
|
|
38
38
|
"--catalog-url",
|
|
39
39
|
"--codex-home",
|
|
40
|
+
"--goal",
|
|
41
|
+
"--host",
|
|
40
42
|
"--kind",
|
|
43
|
+
"--port",
|
|
41
44
|
"--source",
|
|
42
45
|
]);
|
|
43
46
|
|
|
@@ -70,12 +73,19 @@ async function main() {
|
|
|
70
73
|
case "doctor":
|
|
71
74
|
doctor();
|
|
72
75
|
break;
|
|
76
|
+
case "check-update":
|
|
77
|
+
case "update-check":
|
|
78
|
+
checkUpdate();
|
|
79
|
+
break;
|
|
73
80
|
case "plugin":
|
|
74
81
|
plugin();
|
|
75
82
|
break;
|
|
76
83
|
case "extend":
|
|
77
84
|
await extend();
|
|
78
85
|
break;
|
|
86
|
+
case "board":
|
|
87
|
+
await board();
|
|
88
|
+
break;
|
|
79
89
|
case "help":
|
|
80
90
|
case "--help":
|
|
81
91
|
case "-h":
|
|
@@ -144,11 +154,13 @@ Usage:
|
|
|
144
154
|
${canonicalCliName} update [--codex-home <path>] [--json]
|
|
145
155
|
${canonicalCliName} agents [--codex-home <path>] [--force]
|
|
146
156
|
${canonicalCliName} doctor [--codex-home <path>] [--goal-ready]
|
|
157
|
+
${canonicalCliName} check-update [--json]
|
|
147
158
|
${canonicalCliName} extend [--catalog-url <url-or-path>] [--kind <kind>] [--json]
|
|
148
159
|
${canonicalCliName} extend <id> [--catalog-url <url-or-path>] [--json]
|
|
149
160
|
${canonicalCliName} extend install <id> [--catalog-url <url-or-path>] [--dry-run] [--force] [--json]
|
|
150
161
|
${canonicalCliName} extend install --all [--catalog-url <url-or-path>] [--dry-run] [--force] [--json]
|
|
151
162
|
${canonicalCliName} extend doctor [<id>] [--codex-home <path>] [--json]
|
|
163
|
+
${canonicalCliName} board <docs/goals/slug> [--catalog-url <url-or-path>] [--host <host>] [--port <port>] [--once] [--json]
|
|
152
164
|
|
|
153
165
|
Default:
|
|
154
166
|
${canonicalCliName} Installs and enables the native Codex plugin.
|
|
@@ -335,6 +347,46 @@ function doctor() {
|
|
|
335
347
|
process.exit(installOk && goalReadyOk ? 0 : 1);
|
|
336
348
|
}
|
|
337
349
|
|
|
350
|
+
function checkUpdate() {
|
|
351
|
+
const report = updateReport();
|
|
352
|
+
|
|
353
|
+
if (hasFlag("--json")) {
|
|
354
|
+
printJson(report);
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
if (report.check_status !== "ok") {
|
|
359
|
+
console.log(`GoalBuddy update check unavailable: ${report.error}`);
|
|
360
|
+
} else if (report.update_available) {
|
|
361
|
+
console.log(`GoalBuddy ${report.latest_version} is available; installed version is ${report.current_version}.`);
|
|
362
|
+
console.log(`Update with: ${report.update_command}`);
|
|
363
|
+
} else {
|
|
364
|
+
console.log(`GoalBuddy is up to date (${report.current_version}).`);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
function updateReport() {
|
|
369
|
+
const report = {
|
|
370
|
+
package: packageInfo.name,
|
|
371
|
+
current_version: normalizeVersion(packageInfo.version),
|
|
372
|
+
latest_version: null,
|
|
373
|
+
update_available: false,
|
|
374
|
+
check_status: "unknown",
|
|
375
|
+
update_command: `npx ${canonicalCliName}`,
|
|
376
|
+
};
|
|
377
|
+
|
|
378
|
+
try {
|
|
379
|
+
report.latest_version = latestPublishedVersion();
|
|
380
|
+
report.update_available = compareVersions(report.current_version, report.latest_version) < 0;
|
|
381
|
+
report.check_status = "ok";
|
|
382
|
+
} catch (error) {
|
|
383
|
+
report.check_status = "unavailable";
|
|
384
|
+
report.error = error.message;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
return report;
|
|
388
|
+
}
|
|
389
|
+
|
|
338
390
|
function plugin() {
|
|
339
391
|
const subcommand = positional(1) || "";
|
|
340
392
|
switch (subcommand) {
|
|
@@ -528,6 +580,56 @@ async function extend() {
|
|
|
528
580
|
}
|
|
529
581
|
}
|
|
530
582
|
|
|
583
|
+
async function board() {
|
|
584
|
+
const goal = optionValue("--goal") || positional(1);
|
|
585
|
+
if (!goal) {
|
|
586
|
+
console.error(`Missing goal directory. Usage: ${canonicalCliName} board docs/goals/<slug>`);
|
|
587
|
+
process.exit(2);
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
const script = await ensureLocalBoardExtension();
|
|
591
|
+
const scriptArgs = [script, "--goal", goal];
|
|
592
|
+
for (const option of ["--host", "--port"]) {
|
|
593
|
+
const value = optionValue(option);
|
|
594
|
+
if (value) scriptArgs.push(option, value);
|
|
595
|
+
}
|
|
596
|
+
if (hasFlag("--once")) scriptArgs.push("--once");
|
|
597
|
+
if (hasFlag("--json")) scriptArgs.push("--json");
|
|
598
|
+
|
|
599
|
+
const capture = hasFlag("--once") || hasFlag("--json");
|
|
600
|
+
const result = spawnSync(process.execPath, scriptArgs, {
|
|
601
|
+
cwd: packageRoot,
|
|
602
|
+
encoding: "utf8",
|
|
603
|
+
env: process.env,
|
|
604
|
+
stdio: capture ? ["ignore", "pipe", "pipe"] : "inherit",
|
|
605
|
+
});
|
|
606
|
+
|
|
607
|
+
if (capture) {
|
|
608
|
+
if (result.stdout) process.stdout.write(result.stdout);
|
|
609
|
+
if (result.stderr) process.stderr.write(result.stderr);
|
|
610
|
+
}
|
|
611
|
+
if (result.error) throw result.error;
|
|
612
|
+
process.exit(result.status ?? 1);
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
async function ensureLocalBoardExtension() {
|
|
616
|
+
const id = "local-goal-board";
|
|
617
|
+
const script = join(extensionTarget(id), "scripts", "local-goal-board.mjs");
|
|
618
|
+
if (existsSync(script)) return script;
|
|
619
|
+
|
|
620
|
+
const catalog = await loadCatalog();
|
|
621
|
+
const extension = catalog.extensions.find((candidate) => candidate.id === id);
|
|
622
|
+
if (!extension) {
|
|
623
|
+
throw new Error(`Extension ${id} is not available in ${catalog.url}.`);
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
await installCatalogExtension(catalog, extension);
|
|
627
|
+
if (!existsSync(script)) {
|
|
628
|
+
throw new Error(`Extension ${id} installed, but script is missing: ${script}`);
|
|
629
|
+
}
|
|
630
|
+
return script;
|
|
631
|
+
}
|
|
632
|
+
|
|
531
633
|
function extendUsage() {
|
|
532
634
|
console.log(`${canonicalProductName} Extend
|
|
533
635
|
|
|
@@ -1129,9 +1231,39 @@ function assertSkillInstalledForExtensionInstall() {
|
|
|
1129
1231
|
}
|
|
1130
1232
|
}
|
|
1131
1233
|
|
|
1234
|
+
function latestPublishedVersion() {
|
|
1235
|
+
if (process.env.GOALBUDDY_TEST_NPM_LATEST_VERSION) {
|
|
1236
|
+
return normalizeVersion(process.env.GOALBUDDY_TEST_NPM_LATEST_VERSION);
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1239
|
+
const result = spawnSync("npm", ["view", packageInfo.name, "version"], {
|
|
1240
|
+
cwd: packageRoot,
|
|
1241
|
+
encoding: "utf8",
|
|
1242
|
+
timeout: 5000,
|
|
1243
|
+
env: {
|
|
1244
|
+
...process.env,
|
|
1245
|
+
npm_config_update_notifier: "false",
|
|
1246
|
+
},
|
|
1247
|
+
});
|
|
1248
|
+
|
|
1249
|
+
if (result.error) throw result.error;
|
|
1250
|
+
if (result.status !== 0) {
|
|
1251
|
+
const output = `${result.stderr || ""}${result.stdout || ""}`.trim();
|
|
1252
|
+
throw new Error(output || `npm view exited with status ${result.status}`);
|
|
1253
|
+
}
|
|
1254
|
+
|
|
1255
|
+
return normalizeVersion(result.stdout);
|
|
1256
|
+
}
|
|
1257
|
+
|
|
1258
|
+
function normalizeVersion(value) {
|
|
1259
|
+
const match = String(value).trim().match(/^v?(\d+)\.(\d+)\.(\d+)(?:[-+].*)?$/);
|
|
1260
|
+
if (!match) throw new Error(`Unsupported version: ${value}`);
|
|
1261
|
+
return `${Number(match[1])}.${Number(match[2])}.${Number(match[3])}`;
|
|
1262
|
+
}
|
|
1263
|
+
|
|
1132
1264
|
function compareVersions(left, right) {
|
|
1133
|
-
const leftParts = left.split(".").map((part) => Number.parseInt(part, 10) || 0);
|
|
1134
|
-
const rightParts = right.split(".").map((part) => Number.parseInt(part, 10) || 0);
|
|
1265
|
+
const leftParts = normalizeVersion(left).split(".").map((part) => Number.parseInt(part, 10) || 0);
|
|
1266
|
+
const rightParts = normalizeVersion(right).split(".").map((part) => Number.parseInt(part, 10) || 0);
|
|
1135
1267
|
const length = Math.max(leftParts.length, rightParts.length);
|
|
1136
1268
|
for (let index = 0; index < length; index += 1) {
|
|
1137
1269
|
const diff = (leftParts[index] || 0) - (rightParts[index] || 0);
|
package/package.json
CHANGED
|
@@ -28,6 +28,7 @@ During a `$goal-prep` turn, do not perform the user's requested work, even if th
|
|
|
28
28
|
|
|
29
29
|
Allowed `$goal-prep` actions:
|
|
30
30
|
|
|
31
|
+
- run the bundled GoalBuddy update checker and mention a newer version if one is available;
|
|
31
32
|
- ask diagnostic intake questions and wait when required;
|
|
32
33
|
- create or repair only `docs/goals/<slug>/goal.md`, `docs/goals/<slug>/state.yaml`, and `docs/goals/<slug>/notes/`;
|
|
33
34
|
- optionally run the GoalBuddy board checker against that `state.yaml`;
|
|
@@ -37,6 +38,22 @@ Allowed `$goal-prep` actions:
|
|
|
37
38
|
|
|
38
39
|
If the prompt names another skill or tool, such as "use the taste skill", "refresh the taste skill", "look at this repo", "use browser", or "generate assets", record that requirement in the charter and seed tasks. Do not load that skill, browse that repo, or generate those assets during `$goal-prep`.
|
|
39
40
|
|
|
41
|
+
## Update Check
|
|
42
|
+
|
|
43
|
+
At the start of a `$goal-prep` turn, check whether GoalBuddy itself is stale. Run the bundled checker from the installed skill directory when available:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
node <skill-path>/scripts/check-update.mjs --json
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
If the checker reports `update_available: true`, tell the user once before continuing:
|
|
50
|
+
|
|
51
|
+
```text
|
|
52
|
+
GoalBuddy <latest_version> is available. After this turn, update with: npx goalbuddy
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Do not block intake or board creation on update checking. If the checker is missing, cannot find npm, or network access fails, continue silently unless the user asked about updates.
|
|
56
|
+
|
|
40
57
|
## Intake Compiler
|
|
41
58
|
|
|
42
59
|
Before creating, repairing, or running a board, privately translate the user's input into a Goal Intake. The input may be vague, specific, or detailed with an existing plan. Do not dump the intake to the user unless they ask for it.
|
|
@@ -131,6 +148,7 @@ When invoked directly, run intake first. For vague, strategic, improvement-orien
|
|
|
131
148
|
|
|
132
149
|
Do:
|
|
133
150
|
|
|
151
|
+
- check for a newer GoalBuddy version once at the start and mention it without blocking;
|
|
134
152
|
- clarify or infer the goal title and slug;
|
|
135
153
|
- run the Intake Compiler;
|
|
136
154
|
- ask diagnostic intake questions when clarity would materially improve the board;
|
|
@@ -409,6 +427,25 @@ Blocked tasks do not necessarily block the goal. The PM should keep doing safe l
|
|
|
409
427
|
|
|
410
428
|
Avoid setting `goal.status: blocked` for missing input, credentials, production access, destructive-operation permission, or policy decisions. Block the specific task instead, record the missing requirement, and continue with every safe local workaround or adjacent slice.
|
|
411
429
|
|
|
430
|
+
## Operator Escalation
|
|
431
|
+
|
|
432
|
+
When Scout, Judge, Worker, or PM discovers a problem, improvement opportunity, product suggestion, follow-up repair, or tool limitation that should not be fixed inside the current active task, do not let it disappear in chat.
|
|
433
|
+
|
|
434
|
+
The PM may create a board task to prepare a repo-native follow-up. If the user has already approved publishing and the repo/auth state supports it, the PM may create an issue or PR directly and record the link in the receipt. Otherwise, ask the operator one concise question before creating the external artifact:
|
|
435
|
+
|
|
436
|
+
```markdown
|
|
437
|
+
I found [problem or suggestion].
|
|
438
|
+
|
|
439
|
+
Should I:
|
|
440
|
+
1. Create an issue in this repo for it? (Recommended) - [why]
|
|
441
|
+
2. Prepare a PR for the fix/suggestion - [when this is better]
|
|
442
|
+
3. Keep it only in the GoalBuddy board for now - [tradeoff]
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
Use an issue for follow-up work, unclear scope, missing approval, or suggestions that need discussion. Use a PR when the fix is already implemented or safely implementable within the current approved scope. If neither is appropriate, propose a different path and record the decision in `state.yaml`.
|
|
446
|
+
|
|
447
|
+
External issues and PRs are supporting artifacts, not board truth. `state.yaml` remains authoritative, and every issue/PR creation or decision must be reflected in a PM, Worker, or Judge receipt.
|
|
448
|
+
|
|
412
449
|
## Continuation Rule
|
|
413
450
|
|
|
414
451
|
After a task completes, immediately write its receipt and select the next active task unless:
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
3
|
+
import { spawnSync } from "node:child_process";
|
|
4
|
+
import { dirname, join, resolve } from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
|
|
7
|
+
const packageName = "goalbuddy";
|
|
8
|
+
const scriptDir = dirname(fileURLToPath(import.meta.url));
|
|
9
|
+
const args = process.argv.slice(2);
|
|
10
|
+
|
|
11
|
+
const report = {
|
|
12
|
+
package: packageName,
|
|
13
|
+
current_version: findCurrentVersion(),
|
|
14
|
+
latest_version: null,
|
|
15
|
+
update_available: false,
|
|
16
|
+
check_status: "unknown",
|
|
17
|
+
update_command: "npx goalbuddy",
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
try {
|
|
21
|
+
report.latest_version = latestPublishedVersion();
|
|
22
|
+
report.update_available = compareVersions(report.current_version, report.latest_version) < 0;
|
|
23
|
+
report.check_status = "ok";
|
|
24
|
+
} catch (error) {
|
|
25
|
+
report.check_status = "unavailable";
|
|
26
|
+
report.error = error.message;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (args.includes("--json")) {
|
|
30
|
+
console.log(JSON.stringify(report, null, 2));
|
|
31
|
+
} else if (report.check_status !== "ok") {
|
|
32
|
+
console.log(`GoalBuddy update check unavailable: ${report.error}`);
|
|
33
|
+
} else if (report.update_available) {
|
|
34
|
+
console.log(`GoalBuddy ${report.latest_version} is available; installed version is ${report.current_version}.`);
|
|
35
|
+
console.log(`Update with: ${report.update_command}`);
|
|
36
|
+
} else {
|
|
37
|
+
console.log(`GoalBuddy is up to date (${report.current_version}).`);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function findCurrentVersion() {
|
|
41
|
+
const candidates = [
|
|
42
|
+
join(scriptDir, "..", ".goalbuddy-install.json"),
|
|
43
|
+
join(scriptDir, "..", "..", "..", ".codex-plugin", "plugin.json"),
|
|
44
|
+
join(scriptDir, "..", "..", "package.json"),
|
|
45
|
+
];
|
|
46
|
+
|
|
47
|
+
for (const path of candidates) {
|
|
48
|
+
const data = readJson(path);
|
|
49
|
+
const version = data?.package_version || data?.version;
|
|
50
|
+
if (version) return normalizeVersion(version);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return "0.0.0";
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function latestPublishedVersion() {
|
|
57
|
+
if (process.env.GOALBUDDY_TEST_NPM_LATEST_VERSION) {
|
|
58
|
+
return normalizeVersion(process.env.GOALBUDDY_TEST_NPM_LATEST_VERSION);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const result = spawnSync("npm", ["view", packageName, "version"], {
|
|
62
|
+
cwd: resolve(scriptDir, ".."),
|
|
63
|
+
encoding: "utf8",
|
|
64
|
+
timeout: 5000,
|
|
65
|
+
env: {
|
|
66
|
+
...process.env,
|
|
67
|
+
npm_config_update_notifier: "false",
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
if (result.error) throw result.error;
|
|
72
|
+
if (result.status !== 0) {
|
|
73
|
+
const output = `${result.stderr || ""}${result.stdout || ""}`.trim();
|
|
74
|
+
throw new Error(output || `npm view exited with status ${result.status}`);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return normalizeVersion(result.stdout);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function readJson(path) {
|
|
81
|
+
if (!existsSync(path)) return null;
|
|
82
|
+
try {
|
|
83
|
+
return JSON.parse(readFileSync(path, "utf8"));
|
|
84
|
+
} catch {
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function normalizeVersion(value) {
|
|
90
|
+
const match = String(value).trim().match(/^v?(\d+)\.(\d+)\.(\d+)(?:[-+].*)?$/);
|
|
91
|
+
if (!match) throw new Error(`Unsupported version: ${value}`);
|
|
92
|
+
return `${Number(match[1])}.${Number(match[2])}.${Number(match[3])}`;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function compareVersions(left, right) {
|
|
96
|
+
const leftParts = normalizeVersion(left).split(".").map(Number);
|
|
97
|
+
const rightParts = normalizeVersion(right).split(".").map(Number);
|
|
98
|
+
for (let index = 0; index < 3; index += 1) {
|
|
99
|
+
if (leftParts[index] !== rightParts[index]) return leftParts[index] - rightParts[index];
|
|
100
|
+
}
|
|
101
|
+
return 0;
|
|
102
|
+
}
|
|
@@ -61,11 +61,15 @@ On every `/goal` continuation:
|
|
|
61
61
|
|
|
62
62
|
1. Read this charter.
|
|
63
63
|
2. Read `state.yaml`.
|
|
64
|
-
3.
|
|
65
|
-
4.
|
|
66
|
-
5.
|
|
67
|
-
6.
|
|
68
|
-
7.
|
|
69
|
-
8.
|
|
70
|
-
9.
|
|
71
|
-
10.
|
|
64
|
+
3. Run the bundled GoalBuddy update checker when available and mention a newer version without blocking.
|
|
65
|
+
4. Re-check the intake: original request, input shape, authority, proof, blind spots, existing plan facts, and likely misfire.
|
|
66
|
+
5. Work only on the active board task.
|
|
67
|
+
6. Assign Scout, Judge, Worker, or PM according to the task.
|
|
68
|
+
7. Write a compact task receipt.
|
|
69
|
+
8. Update the board.
|
|
70
|
+
9. If Judge selected a safe Worker task with `allowed_files`, `verify`, and `stop_if`, activate it and continue unless blocked.
|
|
71
|
+
10. If a problem, suggestion, or follow-up should become a repo artifact, create an approved issue/PR or ask the operator whether to create one.
|
|
72
|
+
11. Treat a slice audit as a checkpoint, not completion, unless it explicitly proves the full original outcome is complete.
|
|
73
|
+
12. Finish only with a Judge/PM audit receipt that maps receipts and verification back to the original user outcome and records `full_outcome_complete: true`.
|
|
74
|
+
|
|
75
|
+
Issue and PR handoffs are supporting artifacts. `state.yaml` remains authoritative, and every external artifact decision must be recorded in a task receipt.
|