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 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.
@@ -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. Re-check the intake: original request, input shape, authority, proof, blind spots, existing plan facts, and likely misfire.
65
- 4. Work only on the active board task.
66
- 5. Assign Scout, Judge, Worker, or PM according to the task.
67
- 6. Write a compact task receipt.
68
- 7. Update the board.
69
- 8. If Judge selected a safe Worker task with `allowed_files`, `verify`, and `stop_if`, activate it and continue unless blocked.
70
- 9. Treat a slice audit as a checkpoint, not completion, unless it explicitly proves the full original outcome is complete.
71
- 10. 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`.
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
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "goalbuddy",
3
- "version": "0.2.20",
3
+ "version": "0.2.21",
4
4
  "description": "Turn open-ended Codex goals into a GoalBuddy Scout/Judge/Worker board with receipts, verification, and optional extensions.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "goalbuddy",
3
- "version": "0.2.20",
3
+ "version": "0.2.21",
4
4
  "description": "Turn broad Codex work into verified GoalBuddy boards with Scout, Judge, Worker, receipts, and optional extensions.",
5
5
  "author": {
6
6
  "name": "tolibear",
@@ -15,6 +15,7 @@ From the repo root:
15
15
  ```bash
16
16
  npm run check
17
17
  npx goalbuddy doctor
18
+ npx goalbuddy check-update
18
19
  ```
19
20
 
20
21
  ## Native Codex Install
@@ -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. Re-check the intake: original request, input shape, authority, proof, blind spots, existing plan facts, and likely misfire.
65
- 4. Work only on the active board task.
66
- 5. Assign Scout, Judge, Worker, or PM according to the task.
67
- 6. Write a compact task receipt.
68
- 7. Update the board.
69
- 8. If Judge selected a safe Worker task with `allowed_files`, `verify`, and `stop_if`, activate it and continue unless blocked.
70
- 9. Treat a slice audit as a checkpoint, not completion, unless it explicitly proves the full original outcome is complete.
71
- 10. 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`.
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.