@bongos/core 1.17.0 → 1.17.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (90) hide show
  1. package/.bongos-core.json +154 -83
  2. package/CLAUDE.md +11 -238
  3. package/bin/bongos.js +1 -0
  4. package/clients/bongos-client/README.md +4 -4
  5. package/clients/bongos-client/bongos-client.global.js +14 -1
  6. package/clients/bongos-client/index.cjs +14 -1
  7. package/clients/bongos-client/index.d.ts +19 -2
  8. package/clients/bongos-client/index.mjs +11 -1
  9. package/clients/bongos-client/package.json +1 -1
  10. package/docs/adr/0133-github-app-manifest-onboarding-signin.md +2 -0
  11. package/docs/adr/0134-private-first-npm-distribution.md +20 -6
  12. package/docs/adr/0135-module-upstream-submission-interim-queue.md +34 -0
  13. package/docs/adr/README.md +5 -4
  14. package/docs/api/openapi.json +315 -6
  15. package/docs/api-reference.md +10 -5
  16. package/docs/architecture.md +1 -1
  17. package/docs/file-map.md +2 -2
  18. package/docs/modules-contract.md +2 -0
  19. package/docs/recipes/instance-deploy.md +4 -4
  20. package/docs/recipes/instance-first-run.md +51 -0
  21. package/docs/recipes/packaging-the-core.md +5 -5
  22. package/docs/recipes/private-npm-distribution.md +22 -12
  23. package/docs/recipes/self-host.md +1 -1
  24. package/docs/recipes/upgrading-the-core.md +8 -8
  25. package/migrations/core_191_module_submissions.sql +45 -0
  26. package/modules/autonomy/CLAUDE.md +11 -45
  27. package/modules/builder-settings/CLAUDE.md +11 -54
  28. package/modules/dev-box/CLAUDE.md +11 -34
  29. package/modules/discord/CLAUDE.md +11 -46
  30. package/modules/economy/CLAUDE.md +11 -44
  31. package/modules/grading/CLAUDE.md +11 -38
  32. package/modules/hall-ui/CLAUDE.md +11 -57
  33. package/modules/hall-ui/public/goals-page.js +227 -22
  34. package/modules/hall-ui/public/goals.html +16 -0
  35. package/modules/hall-ui/public/new-project-wizard.js +5 -4
  36. package/modules/ideas/CLAUDE.md +11 -33
  37. package/modules/lifecycle/CLAUDE.md +11 -59
  38. package/modules/lifecycle/routes/goals.js +80 -0
  39. package/modules/memory/CLAUDE.md +11 -36
  40. package/modules/onboarding/CLAUDE.md +11 -51
  41. package/modules/security/CLAUDE.md +11 -40
  42. package/modules/sessions/CLAUDE.md +11 -35
  43. package/modules/status-ui/CLAUDE.md +11 -43
  44. package/package-lock.json +2 -2
  45. package/package.json +1 -1
  46. package/public-landing/create.html +58 -12
  47. package/public-landing/index.html +40 -21
  48. package/scripts/gds/CLAUDE.md +11 -140
  49. package/scripts/gds/claude-materialize.js +151 -18
  50. package/scripts/gds/claude-md-template.js +84 -0
  51. package/scripts/gds/cli-lib.js +112 -2
  52. package/scripts/gds/gen-api-client.js +10 -10
  53. package/scripts/gds/init.js +53 -13
  54. package/scripts/gds/migration-namespace.js +1 -1
  55. package/scripts/gds/module.js +118 -4
  56. package/scripts/gds/npmrc.js +93 -0
  57. package/scripts/gds/onboard.js +13 -24
  58. package/scripts/gds/package-core.js +56 -14
  59. package/scripts/gds/provision.js +102 -17
  60. package/scripts/gds/seed-adopt-tasks.js +1 -1
  61. package/scripts/gds/status.js +6 -1
  62. package/scripts/gds/task.js +166 -0
  63. package/scripts/gds/upgrade.js +58 -18
  64. package/src/bongos/CLAUDE.md +11 -60
  65. package/src/bongos/auth.js +11 -0
  66. package/src/bongos/module-submissions.js +56 -0
  67. package/src/bongos/route-rank-check.js +6 -0
  68. package/src/bongos/routes/CLAUDE.md +11 -39
  69. package/src/bongos/routes/auth.js +6 -1
  70. package/src/bongos/routes/modules.js +123 -21
  71. package/src/instance-config.js +1 -1
  72. package/src/module-api.js +13 -1
  73. package/tests/claude_materialize.mjs +105 -0
  74. package/tests/device_flow_start.mjs +73 -0
  75. package/tests/first_run_engine_check.mjs +79 -0
  76. package/tests/goal_membership_requests.mjs +33 -3
  77. package/tests/hook_user_agents.mjs +62 -0
  78. package/tests/init.mjs +59 -6
  79. package/tests/instance_mismatch_guard.mjs +101 -0
  80. package/tests/module_cli.mjs +130 -2
  81. package/tests/module_submissions.mjs +135 -0
  82. package/tests/npmrc.mjs +105 -0
  83. package/tests/onboard.mjs +49 -0
  84. package/tests/package_core.mjs +68 -22
  85. package/tests/platform_boot.mjs +5 -2
  86. package/tests/provision.mjs +107 -8
  87. package/tests/provisioning_github_app_routes.mjs +29 -2
  88. package/tests/served_paths_shipped.mjs +2 -2
  89. package/tests/task_cli.mjs +99 -0
  90. package/tests/upgrade.mjs +89 -25
@@ -38,7 +38,7 @@
38
38
  // carries a live credential verbatim, PRODUCE NOTHING — dist/ is never touched. The
39
39
  // in-memory pass makes the abort atomic (same discipline as the mirror export).
40
40
  // 5. Only when the whole set is clean: write the tree to
41
- // dist/cloudbongos-core-<version>/, the .tgz, and the pin MANIFEST.
41
+ // dist/bongos-core-<version>/, the .tgz, and the pin MANIFEST.
42
42
  //
43
43
  // THE VERSION STAMP is the platform version — src/module-api.js CORE_VERSION,
44
44
  // read via the SAME fail-soft accessor scripts/gds/module.js + bongos upgrade use
@@ -55,9 +55,9 @@
55
55
  // [--version <v>] [--droplet-ip <ip>] [--publish [--live]]
56
56
  // Defaults: --ref HEAD, --out-dir dist, --version = platform CORE_VERSION.
57
57
  // Outputs (nothing is committed — dist/ is gitignored):
58
- // dist/cloudbongos-core-<version>/ the redacted core tree (inspectable)
59
- // dist/cloudbongos-core-<version>.tgz the installable unit
60
- // dist/cloudbongos-core-<version>.manifest.json the PIN (tree hash + per-file hashes)
58
+ // dist/bongos-core-<version>/ the redacted core tree (inspectable)
59
+ // dist/bongos-core-<version>.tgz the installable unit
60
+ // dist/bongos-core-<version>.manifest.json the PIN (tree hash + per-file hashes)
61
61
  // --publish after building, `npm publish` the .tgz as a PRIVATE scoped
62
62
  // package (--access restricted). DRY-RUN unless --live is added
63
63
  // (task 2089 / ADR 0134). Private publish needs the cloudbongos
@@ -81,7 +81,7 @@ const { collectEnvSecrets } = require(path.join(REPO_ROOT, 'modules/security/sec
81
81
  const { redactBlob, parseLsTree } = require(path.join(REPO_ROOT, 'scripts/public-mirror-export.js'));
82
82
 
83
83
  const MANIFEST_SCHEMA = 1;
84
- const ARTIFACT_NAME = 'cloudbongos-core';
84
+ const ARTIFACT_NAME = 'bongos-core';
85
85
  // The tar internal root MUST be `package/` — that is the one hardcoded npm
86
86
  // convention for an installable tarball (`npm install ./x.tgz` / `npm pack`
87
87
  // output both use it; npm does not read the directory name to find package.json,
@@ -92,7 +92,7 @@ const TAR_ROOT = 'package/';
92
92
  // The self-describing manifest carried INSIDE the artifact (minus tarball.* — a
93
93
  // file cannot contain its own hash). The authoritative PIN is the side-car next
94
94
  // to the .tgz, which adds tarball.{sha256,bytes}.
95
- const INNER_MANIFEST = `${TAR_ROOT}.cloudbongos-core.json`;
95
+ const INNER_MANIFEST = `${TAR_ROOT}.bongos-core.json`;
96
96
 
97
97
  // ---------------------------------------------------------------------------
98
98
  // The platform version — read CORE_VERSION via the doorway, lazily + fail-soft,
@@ -136,6 +136,38 @@ function sha256hex(buf) {
136
136
  const DOC_RE = /\.(md|markdown)$/i;
137
137
  function isDocPath(p) { return DOC_RE.test(String(p || '')); }
138
138
 
139
+ // ---------------------------------------------------------------------------
140
+ // Agent-doc STUB (task 2121; surfaced by the 2026-07-08 demo task-creation audit).
141
+ // Claude Code AUTO-LOADS the nearest file named CLAUDE.md (or AGENTS.md) into the
142
+ // live session whenever an agent opens a sibling file. In a CONSUMER instance the
143
+ // core installs at node_modules/@bongos/core/, so the core repo's OWN CLAUDE.md tree
144
+ // (root + scripts/gds + src/bongos + every module) would silently inject the
145
+ // platform's project memory — wrong project title, hundreds of lines, dangling links
146
+ // — into the instance's context the moment an agent reads any core file.
147
+ // The PUBLIC MIRROR deliberately publishes CLAUDE.md as the portable methodology
148
+ // (ADR 0065 §4), so this cannot live in the shared publish-manifest — it is a
149
+ // package-core-only transform. We keep the files PRESENT (nothing that probes for
150
+ // them by path breaks) but replace their body with a tiny, brand-free stub, turning
151
+ // the auto-load into a harmless "this doesn't govern you" redirect.
152
+ const AGENT_DOC_RE = /(^|\/)(CLAUDE|AGENTS)\.md$/i;
153
+ function isAgentDoc(p) { return AGENT_DOC_RE.test(String(p || '')); }
154
+ const CORE_AGENT_DOC_STUB = `# @bongos/core — installed platform dependency (not your project's CLAUDE.md)
155
+
156
+ This file is a placeholder shipped inside the **@bongos/core** package. The core's own
157
+ internal agent notes are intentionally omitted from the published artifact so they cannot
158
+ load into your instance's context.
159
+
160
+ **It does not govern this repository.** Your own root \`CLAUDE.md\`, your \`.claude/\`
161
+ directory, and the live Bongos database are the source of truth for how work happens here
162
+ and who may do what.
163
+
164
+ You are seeing this only because an agent opened a file under \`node_modules/@bongos/core/\`.
165
+ Nothing in here needs to be read to use the platform — run \`bongos help\` for commands.
166
+
167
+ _To work ON the platform core itself, use the standalone @bongos/core source repository,
168
+ not this vendored copy._
169
+ `;
170
+
139
171
  // Fail-loud gate for a FUNCTIONAL file: only an exact live-credential value (an env-secret
140
172
  // VALUE present verbatim, >= 8 chars) is a real leak. The sample is withheld (it's a secret).
141
173
  function functionalSecretLeaks(view, envSecrets) {
@@ -154,6 +186,12 @@ function functionalSecretLeaks(view, envSecrets) {
154
186
  // `advisories` = brand-identity substrings + public IPs that ship as-is (non-fatal, for
155
187
  // human awareness) — REUSES findLeaks so the detector is defined once.
156
188
  function processCoreFile({ pathRel, raw, rules, sensitiveLiterals, envSecrets }) {
189
+ // An auto-loaded agent doc (CLAUDE.md/AGENTS.md) → ship a brand-free stub, never the
190
+ // core's own project memory (task 2121). The stub is brand-free by construction, so it
191
+ // needs no redaction and can never leak — it short-circuits ahead of the doc path.
192
+ if (isAgentDoc(pathRel)) {
193
+ return { buf: Buffer.from(CORE_AGENT_DOC_STUB, 'utf8'), leaks: [], advisories: [], kind: 'stub' };
194
+ }
157
195
  if (isDocPath(pathRel)) {
158
196
  const r = redactBlob({ buf: raw, rules, sensitiveLiterals, envSecrets });
159
197
  const leaks = r.leaks.map((l) => ({ label: `doc:${l.label}`, sample: l.sample }));
@@ -174,7 +212,7 @@ function processCoreFile({ pathRel, raw, rules, sensitiveLiterals, envSecrets })
174
212
  //
175
213
  // The source repo's package.json/package-lock.json (isPublishable() includes
176
214
  // both) are GAME-heavy — colyseus/phaser/sharp/discord.js plus the repo's own
177
- // name/version — and are not what a downstream `npm install @cloudbongos/core`
215
+ // name/version — and are not what a downstream `npm install @bongos/core`
178
216
  // should see. These two files are SYNTHESIZED here (never copied verbatim from
179
217
  // git) and staged in their place. Everything else still ships through the
180
218
  // normal isPublishable()/processCoreFile path.
@@ -195,7 +233,7 @@ function buildPackageJson({ version, sourceDeps, sourceOverrides }) {
195
233
  dependencies[name] = range;
196
234
  }
197
235
  const pkg = {
198
- name: '@cloudbongos/core',
236
+ name: '@bongos/core',
199
237
  version,
200
238
  description: 'Cloud Bongos — the AI-first build platform core (GDS + platform surfaces + module system), installed as a versioned dependency (ADR 0108).',
201
239
  license: 'AGPL-3.0-or-later',
@@ -215,7 +253,7 @@ function buildPackageJson({ version, sourceDeps, sourceOverrides }) {
215
253
  // default. `access:"restricted"` makes a scoped package private on publish, so a
216
254
  // paid npm org can ship it where only members can install — no accidental public
217
255
  // leak if someone forgets the CLI flag. Going public at launch is one step:
218
- // `npm access public @cloudbongos/core` (or flip this to "public"). The name must
256
+ // `npm access public @bongos/core` (or flip this to "public"). The name must
219
257
  // stay SCOPED to be private at all — unscoped npm names are always public.
220
258
  publishConfig: { access: 'restricted' },
221
259
  dependencies,
@@ -481,7 +519,7 @@ function publishTarball({ tgzPath, live }) {
481
519
  if (!live) {
482
520
  console.error(' Dry-run: nothing is uploaded. To publish for real: re-run with --publish --live.');
483
521
  console.error(' That is PERMANENT and requires `npm login` + the cloudbongos npm org on a paid plan');
484
- console.error(' (private scoped publish needs one). Going public later: `npm access public @cloudbongos/core`.');
522
+ console.error(' (private scoped publish needs one). Going public later: `npm access public @bongos/core`.');
485
523
  }
486
524
  const r = spawnSync(NPM_BIN, args, { cwd: REPO_ROOT, stdio: ['inherit', process.stderr, 'inherit'] });
487
525
  if (r.error || r.status !== 0) {
@@ -489,7 +527,7 @@ function publishTarball({ tgzPath, live }) {
489
527
  if (live) console.error(' Common causes: not logged in (`npm login`), the org isn’t on a paid plan (private scoped publish requires one), or 2FA.');
490
528
  process.exit(r.status || 1);
491
529
  }
492
- console.error(`[package-core] npm publish ${live ? 'succeeded — @cloudbongos/core is live (private).' : 'dry-run OK — the tarball is publishable.'}`);
530
+ console.error(`[package-core] npm publish ${live ? 'succeeded — @bongos/core is live (private).' : 'dry-run OK — the tarball is publishable.'}`);
493
531
  }
494
532
 
495
533
  function main() {
@@ -520,7 +558,7 @@ function main() {
520
558
 
521
559
  // 2b. Synthesize the core's own package.json + pruned package-lock.json from the
522
560
  // SOURCE files at this ref (never the live working tree, so --ref stays
523
- // reproducible) — express+pg only, name @cloudbongos/core, version = the
561
+ // reproducible) — express+pg only, name @bongos/core, version = the
524
562
  // platform version being packaged.
525
563
  const pkgJsonBlob = entries.find((e) => e.path === 'package.json');
526
564
  const pkgLockBlob = entries.find((e) => e.path === 'package-lock.json');
@@ -557,6 +595,7 @@ function main() {
557
595
  const advisories = []; // { path, label, sample } — non-fatal (brand/IP that ships verbatim)
558
596
  let docCount = 0;
559
597
  let funcCount = 0;
598
+ let stubCount = 0; // agent docs (CLAUDE.md/AGENTS.md) replaced with the brand-free stub (task 2121)
560
599
  for (const e of publishable) {
561
600
  let raw;
562
601
  try { raw = gitBuf(['cat-file', 'blob', e.sha]); }
@@ -565,7 +604,7 @@ function main() {
565
604
  processCoreFile({ pathRel: e.path, raw, rules, sensitiveLiterals, envSecrets });
566
605
  for (const l of fileLeaks) leaks.push({ path: e.path, label: l.label, sample: l.sample });
567
606
  for (const a of fileAdvisories) advisories.push({ path: e.path, label: a.label, sample: a.sample });
568
- if (kind === 'doc') docCount++; else funcCount++;
607
+ if (kind === 'doc') docCount++; else if (kind === 'stub') stubCount++; else funcCount++;
569
608
  filesMeta.push({ path: e.path, mode: e.mode, sha256: sha256hex(buf) });
570
609
  staged.push({ name: TAR_ROOT + e.path, mode: parseInt(normalizeMode(e.mode), 8), buf });
571
610
  }
@@ -593,7 +632,7 @@ function main() {
593
632
  console.error(' for a live-credential leak, remove the secret at SOURCE (it should never be committed). Then re-run. dist/ was NOT touched.');
594
633
  process.exit(1);
595
634
  }
596
- console.error(`[package-core] no-leak gate PASSED — ${staged.length} files clean (${docCount} docs redacted, ${funcCount} functional verbatim).`);
635
+ console.error(`[package-core] no-leak gate PASSED — ${staged.length} files clean (${docCount} docs redacted, ${stubCount} agent-docs stubbed, ${funcCount} functional verbatim).`);
597
636
 
598
637
  // Advisory: brand-identity + public-IP literals that ship VERBATIM inside functional
599
638
  // files. Not a leak (these are public, not secrets; a downstream rebrands via config) —
@@ -614,6 +653,7 @@ function main() {
614
653
  redaction: {
615
654
  model: 'docs-redacted+functional-verbatim', // task 1824
616
655
  docs_redacted: docCount,
656
+ agent_docs_stubbed: stubCount, // CLAUDE.md/AGENTS.md → brand-free stub (task 2121)
617
657
  functional_verbatim: funcCount,
618
658
  rules: rules.length,
619
659
  gate_literals: sensitiveLiterals.length,
@@ -675,6 +715,8 @@ module.exports = {
675
715
  octal, tarChecksum, ustarHeader, splitUstarName, buildTar,
676
716
  // task 1824 — the consumer-core file-classification layer (pure; unit-tested)
677
717
  isDocPath, functionalSecretLeaks, processCoreFile,
718
+ // task 2121 — agent-doc (CLAUDE.md/AGENTS.md) stub so a consumer's node_modules can't auto-load core memory
719
+ isAgentDoc, CORE_AGENT_DOC_STUB,
678
720
  // task 1882 (ADR 0108 §2) — the real installable npm package
679
721
  CORE_DEP_NAMES, buildPackageJson, resolveLockDep, lockfileClosure, prunePackageLock,
680
722
  // task 2089 (ADR 0134) — private-by-default npm publish
@@ -56,6 +56,12 @@ const REPO_ROOT = path.resolve(__dirname, '..', '..');
56
56
  // plain require (its require.main guard means importing it runs no CLI).
57
57
  const oauthSecret = require('./oauth-secret');
58
58
 
59
+ // The registry-install gates + env-fed .npmrc (ADR 0134, task 2090). Lets the
60
+ // standalone scaffold OPT IN to pulling the PRIVATE @bongos/core from the npm
61
+ // registry (PROVISION_CORE_FROM_REGISTRY=1) instead of vendoring a tarball, with a
62
+ // fail-soft fallback to the vendored path when no NPM_TOKEN is present.
63
+ const { registryCoreRequested, hasNpmToken } = require('./npmrc');
64
+
59
65
  // The HTTP User-Agent the manifest conversion stamps its GitHub call with (GitHub
60
66
  // requires a UA). Overridable so a fork can identify itself; not secret.
61
67
  const MANIFEST_UA = process.env.PROVISION_GITHUB_UA || 'cloudbongos-provisioner';
@@ -118,7 +124,7 @@ const CONFIG = {
118
124
  // since the control plane and the run box are then the same machine.
119
125
  controlPlaneIp: process.env.PROVISION_CONTROL_PLANE_IP || null,
120
126
  // Parent dir for a STANDALONE instance's OWN repo checkout (hosting_shape='standalone',
121
- // ADR 0108): each lives at <standaloneBase>/<slug> with @cloudbongos/core installed as
127
+ // ADR 0108): each lives at <standaloneBase>/<slug> with @bongos/core installed as
122
128
  // an npm dep. Distinct from co-tenant (which shares THIS control-plane checkout).
123
129
  standaloneBase: process.env.PROVISION_STANDALONE_BASE || '/srv/cloudbongos',
124
130
  // ── Privilege escalation (task 2066, ADR 0128) ───────────────────────────
@@ -207,13 +213,45 @@ function migrateCmd(inst, { privileged = false } = {}) {
207
213
  : `PGDATABASE=${dbName(inst)} MEDUSA_GDS_ONLY=1 ./scripts/migrate.sh`;
208
214
  }
209
215
 
216
+ // Seed the instance's FIRST version so it can take tasks from minute one (task 2119,
217
+ // goal 35). A freshly-migrated instance DB comes up with an EMPTY versions table —
218
+ // migrate runs instance-seeds-off (ADR 0105) and `bongos init` ran --no-seed — yet
219
+ // every task requires a version (tasks.version_id NOT NULL) and POST /versions is
220
+ // Archon-only (no Archon exists until first sign-in). Without a seed here, a brand-new
221
+ // project has NOWHERE to file a task. This runs on the box's DB path (no rank gate),
222
+ // ONLY IF the versions table is empty: idempotent, safe to re-run, and it never
223
+ // disturbs a project that already has versions. Mirrors seedViaApi's shape — V1
224
+ // (status 'building') + one done-when criterion so /status has a finish line from day
225
+ // one. `track` is set EXPLICITLY: versions.track is NOT NULL with no default (mig 112
226
+ // dropped the CHECK), and 'product' matches the canonical V1 seed in 003_pms.sql.
227
+ //
228
+ // The id/name/done_when come from instanceInitSpec so the starter version has ONE
229
+ // source of truth (the same spec the greenfield `bongos init` seed uses). SQL string
230
+ // literals are DOLLAR-QUOTED ($v$…$v$) so the whole -c argument is single-quoted —
231
+ // the one form that is safe UNCHANGED across all three executors: inline (/bin/sh),
232
+ // over SSH (bash -s), AND embedded in a dedicated droplet's cloud-init (bash -lc "…"),
233
+ // with no nested-quote or $-expansion hazard.
234
+ function seedFirstVersionCmd(inst, { privileged = false } = {}) {
235
+ const v = instanceInitSpec(inst).version;
236
+ const q = (s) => `$v$${s}$v$`; // dollar-quoted literal (no value contains a single quote or $)
237
+ const sql =
238
+ `INSERT INTO versions (id, name, track, status, done_when) ` +
239
+ `SELECT ${q(v.id)}, ${q(v.name)}, $v$product$v$, $v$building$v$, ${q(v.done_when)} ` +
240
+ `WHERE NOT EXISTS (SELECT 1 FROM versions); ` +
241
+ `INSERT INTO done_when_criteria (version_id, criterion_id, criterion_md, sort_order) ` +
242
+ `SELECT ${q(v.id)}, $v$first-goal$v$, ${q(v.done_when)}, 10 ` +
243
+ `WHERE EXISTS (SELECT 1 FROM versions WHERE id = ${q(v.id)}) ` +
244
+ `AND NOT EXISTS (SELECT 1 FROM done_when_criteria WHERE version_id = ${q(v.id)} AND criterion_id = $v$first-goal$v$);`;
245
+ return asPostgres(`psql -v ON_ERROR_STOP=1 -d ${dbName(inst)} -c '${sql}'`, privileged);
246
+ }
247
+
210
248
  // ── STANDALONE shape (hosting_shape='standalone', ADR 0108, task 1978) ────────
211
249
  // A standalone instance runs from its OWN repo checkout (host content: config/,
212
- // migrations/instance/, the core PIN in package.json) with @cloudbongos/core installed
250
+ // migrations/instance/, the core PIN in package.json) with @bongos/core installed
213
251
  // as an npm DEPENDENCY — NOT the shared control-plane checkout the co-tenant shape uses.
214
252
  // It is the ADR 0108 DESTINATION model; these generators make provision.js drive the
215
253
  // scripts/deploy/deploy-instance.sh runbook API-driven instead of by hand.
216
- const CORE_PKG_DIR = 'node_modules/@cloudbongos/core';
254
+ const CORE_PKG_DIR = 'node_modules/@bongos/core';
217
255
  function standaloneRoot(inst, { base = CONFIG.standaloneBase } = {}) { return path.posix.join(base, inst.slug); }
218
256
  // Sync the instance's own repo (host content + the core pin). Mirrors deploy-instance.sh
219
257
  // step 1 — assumes the repo is already cloned at standaloneRoot (initial clone is a
@@ -281,7 +319,7 @@ function instanceInitSpec(inst, { ownerLogin } = {}) {
281
319
  const INSTANCE_GITIGNORE = ['node_modules/', 'dist/', '.env', '.env.local', '*.log', ''].join('\n');
282
320
 
283
321
  // Clone the (empty) target_ref → standaloneRoot → materialize config/ + .claude/ + a
284
- // file:-pinned @cloudbongos/core + lockfile (via `bongos init --vendor-core`, task 2053)
322
+ // file:-pinned @bongos/core + lockfile (via `bongos init --vendor-core`, task 2053)
285
323
  // → commit + push. Idempotent (skips once the repo carries a package.json) and dry-run
286
324
  // safe (every mutation goes through the injected exec/writeFile). ADR 0108 / 0125.
287
325
  async function scaffoldStandaloneRepo(inst, deps) {
@@ -327,23 +365,44 @@ async function scaffoldStandaloneRepo(inst, deps) {
327
365
  }
328
366
  exec(`git -C ${root} checkout -B main`);
329
367
 
330
- // 2. build the installable core tarball from THIS control-plane checkout (the core source).
331
- exec(`node scripts/gds/package-core.js`); // cwd = REPO_ROOT → dist/cloudbongos-core-<v>.tgz
332
- const tgz = path.join(REPO_ROOT, 'dist', `cloudbongos-core-${coreV}.tgz`);
368
+ // Core-install path (ADR 0134, task 2090). DEFAULT = vendor a tarball built from THIS
369
+ // control-plane checkout (offline, proven — task 2053). OPT-IN = pull the PRIVATE
370
+ // @bongos/core from the npm registry (PROVISION_CORE_FROM_REGISTRY=1). The registry
371
+ // path needs NPM_TOKEN at `npm ci` time; if the flag is set but no token is present we
372
+ // FAIL SOFT to the vendored path so a tokenless standup still works (never a broken
373
+ // instance). The env is injectable for tests; the runner falls back to process.env.
374
+ const env = deps.env || process.env;
375
+ const wantRegistry = registryCoreRequested(env);
376
+ const useRegistry = wantRegistry && hasNpmToken(env);
377
+ if (wantRegistry && !useRegistry) {
378
+ log(' [scaffold] PROVISION_CORE_FROM_REGISTRY set but no NPM_TOKEN in env — falling back to the vendored-tarball core (ADR 0134 fail-soft).');
379
+ }
333
380
 
334
- // 3. materialize config/ + .claude/ + a file:-pinned package.json + lockfile via `bongos init`
335
- // (--vendor-core, task 2053). --no-seed: the instance's own DB is seeded by migrate, not here.
381
+ // materialize config/ + .claude/ + a pinned package.json + lockfile via `bongos init`.
382
+ // --no-seed: the instance's own DB is seeded by migrate, not here. Two mutually
383
+ // exclusive core-resolution shapes:
384
+ // registry (--registry-core): caret pin + env-fed .npmrc; `npm ci` pulls from the
385
+ // registry. init generates the lockfile (NPM_TOKEN is present — we gated on it).
386
+ // vendored (--vendor-core, default): a tarball built from this checkout, file:-pinned.
336
387
  const spec = instanceInitSpec(inst, { ownerLogin });
337
388
  const specPath = path.join(os.tmpdir(), `bongos-init-${inst.slug}.json`);
338
389
  writeFile(specPath, JSON.stringify(spec, null, 2));
339
- exec(`node scripts/gds/init.js --from ${specPath} --dir ${root} --no-seed --force --vendor-core ${tgz}`);
390
+ if (useRegistry) {
391
+ log(` [scaffold] core install: registry — PRIVATE @bongos/core@${coreV} via env-fed NPM_TOKEN (ADR 0134)`);
392
+ exec(`node scripts/gds/init.js --from ${specPath} --dir ${root} --no-seed --force --registry-core`);
393
+ } else {
394
+ log(` [scaffold] core install: vendored tarball (offline default)`);
395
+ exec(`node scripts/gds/package-core.js`); // cwd = REPO_ROOT → dist/bongos-core-<v>.tgz
396
+ const tgz = path.join(REPO_ROOT, 'dist', `bongos-core-${coreV}.tgz`);
397
+ exec(`node scripts/gds/init.js --from ${specPath} --dir ${root} --no-seed --force --vendor-core ${tgz}`);
398
+ }
340
399
 
341
400
  // 4. commit + push the scaffold to the instance's own main (keep node_modules out).
342
401
  writeFile(path.join(root, '.gitignore'), INSTANCE_GITIGNORE);
343
402
  exec(`git -C ${root} config user.name "Cloud Bongos"`);
344
403
  exec(`git -C ${root} config user.email "bot@cloudbongos.com"`);
345
404
  exec(`git -C ${root} add -A`);
346
- exec(`git -C ${root} commit -m "chore: scaffold ${inst.slug} on a pinned @cloudbongos/core ${coreV} (provision.js, ADR 0108)"`, { allowFail: true });
405
+ exec(`git -C ${root} commit -m "chore: scaffold ${inst.slug} on a pinned @bongos/core ${coreV} (provision.js, ADR 0108)"`, { allowFail: true });
347
406
  exec(`git -C ${root} push -u origin main`);
348
407
  log(` [scaffold] pushed initial scaffold to ${repo.owner}/${repo.name}@main`);
349
408
  return { ok: true, scaffolded: true };
@@ -446,11 +505,25 @@ ${prefix}_OAUTH_ORIGIN=${origin}
446
505
  # GitHub App Manifest flow (ADR 0133) — the owner clicked one link; no secret was pasted.`
447
506
  : `# Provisioned by scripts/gds/provision.js (ADR 0111). Fill the OAuth app creds via the
448
507
  # one-click GitHub App Manifest flow (ADR 0133), or manually (oauth-secret.js place):`;
508
+ // The founding owner (firstAdmin): the GitHub login src/bongos/auth.js seats as the
509
+ // instance's first Archon on first sign-in (maybeSeatFirstAdmin checks
510
+ // <prefix>_FIRST_ADMIN before branding.firstAdmin). WITHOUT it a provisioned instance
511
+ // has NO owner — first sign-in is refused / lands rankless and the owner can never
512
+ // claim their own instance (goal 35 / criterion 165 regression, caught live 2026-07-07:
513
+ // demo had every origin override but no FIRST_ADMIN, so lars589 could not be seated).
514
+ // Derived from the instance repo owner (target_ref = "<owner>/<repo>"), the SAME source
515
+ // instanceInitSpec uses for the scaffold's branding.firstAdmin — so this env override and
516
+ // the scaffolded config/branding.json agree. (Org-owned repo: the org login is not a
517
+ // sign-in identity, so it is left unset; the owner sets <prefix>_FIRST_ADMIN by hand.)
518
+ const ownerRepo = parseTargetRef(inst.target_ref);
519
+ const firstAdminLine = ownerRepo && ownerRepo.owner
520
+ ? `${prefix}_FIRST_ADMIN=${ownerRepo.owner}\n`
521
+ : '';
449
522
  return `# /etc/${inst.slug}/web.env — per-instance secrets (chmod 600, OUTSIDE the repo).
450
523
  ${credComment}
451
524
  ${prefix}_GITHUB_CLIENT_ID=${clientId || ''}
452
525
  ${prefix}_GITHUB_CLIENT_SECRET=${clientSecret || ''}
453
- GDS_BRANDING_FILE=${PROVISIONED_BRANDING_FILE}
526
+ ${firstAdminLine}GDS_BRANDING_FILE=${PROVISIONED_BRANDING_FILE}
454
527
  ${originLines}PORT=${inst.port}
455
528
  PGDATABASE=${dbName(inst)}
456
529
  `;
@@ -808,7 +881,7 @@ async function provisionInstance(inst, deps) {
808
881
  // Co-tenant OR standalone: a process bound to an allocated port on the box it runs
809
882
  // on. Co-tenant shares that box's checkout ($0, cloudbongos.com model — under
810
883
  // remote-exec, the co-hosting box); standalone (ADR 0108) runs from its OWN repo
811
- // dir with a pinned @cloudbongos/core (synced in step 2) on the CONTROL PLANE.
884
+ // dir with a pinned @bongos/core (synced in step 2) on the CONTROL PLANE.
812
885
  // Allocate against the ports of whichever box it runs on (boxExec: remote for a
813
886
  // co-tenant, local for a standalone — task 2074), then run the rest there.
814
887
  if (!inst.port) {
@@ -838,11 +911,16 @@ async function provisionInstance(inst, deps) {
838
911
  // ── step 2: code sync (standalone) + database ─────────────────────────────
839
912
  if (local && standalone) {
840
913
  // Standalone (ADR 0108 / deploy-instance.sh steps 1-2): sync the instance's own
841
- // repo + install the pinned @cloudbongos/core, so the migrate + service below run
914
+ // repo + install the pinned @bongos/core, so the migrate + service below run
842
915
  // from the INSTANCE root, not this control-plane checkout. cwd = standaloneRoot.
843
916
  // boxExec = the control plane (where scaffoldStandaloneRepo just created the dir).
917
+ // REGISTRY CORE (ADR 0134): when the scaffold pinned the registry core, the committed
918
+ // .npmrc reads `_authToken=${NPM_TOKEN}` — so `npm ci` resolves the PRIVATE package
919
+ // only if NPM_TOKEN is in this runner's env (makeExec inherits process.env). The
920
+ // scaffold already gated on the token, so it is present; the VENDORED default needs
921
+ // no token (the tarball is committed). A tokenless registry `npm ci` 404s cleanly.
844
922
  const root = standaloneRoot(inst);
845
- log(' [code] standalone — pull instance repo + npm ci (pinned @cloudbongos/core)');
923
+ log(' [code] standalone — pull instance repo + npm ci (pinned @bongos/core)');
846
924
  boxExec(standalonePullCmd(), { cwd: root });
847
925
  boxExec(standaloneInstallCmd(), { cwd: root });
848
926
  }
@@ -851,7 +929,13 @@ async function provisionInstance(inst, deps) {
851
929
  boxExec(dbCreateCmd(inst, { privileged }), { allowFail: true }); // "already exists" is fine
852
930
  if (standalone) boxExec(standaloneMigrateCmd(inst, { privileged }), { cwd: standaloneRoot(inst) }); // instance-root migrate
853
931
  else boxExec(migrateCmd(inst, { privileged }));
854
- } else { log(' (runs via cloud-init on the droplet)'); }
932
+ // Seed the first version so the instance can take tasks from minute one (task 2119,
933
+ // goal 35). Only fires if the versions table is empty — idempotent, never disturbs an
934
+ // existing project. NOT allowFail: a re-run is a clean 0-row no-op, so a real failure
935
+ // here should surface. Dedicated bakes the same step into cloud-init (dedicatedUserData).
936
+ log(' [seed] first version (only if versions is empty)');
937
+ boxExec(seedFirstVersionCmd(inst, { privileged }));
938
+ } else { log(' (db create + migrate + first-version seed run via cloud-init on the droplet)'); }
855
939
 
856
940
  // ── step 3: config + per-instance secrets ─────────────────────────────────
857
941
  log(' [config] per-instance web.env');
@@ -929,6 +1013,7 @@ function dedicatedUserData(inst) {
929
1013
  runcmd:
930
1014
  - [ bash, -lc, "${dbCreateCmd(i)} || true" ]
931
1015
  - [ bash, -lc, "cd ${REPO_ROOT} && ${migrateCmd(i)}" ]
1016
+ - [ bash, -lc, "${seedFirstVersionCmd(i)}" ]
932
1017
  - [ bash, -lc, "systemctl daemon-reload && systemctl enable --now ${i.slug}" ]
933
1018
  `;
934
1019
  }
@@ -1310,7 +1395,7 @@ if (require.main === module) main();
1310
1395
  module.exports = {
1311
1396
  CONFIG, MAX_INTENT_ATTEMPTS,
1312
1397
  // pure step generators (tested directly)
1313
- dbName, dbCreateCmd, migrateCmd, serviceUnit, serviceUnitPath, webEnvBody, webEnvPath,
1398
+ dbName, dbCreateCmd, migrateCmd, seedFirstVersionCmd, serviceUnit, serviceUnitPath, webEnvBody, webEnvPath,
1314
1399
  standaloneRoot, standalonePullCmd, standaloneInstallCmd, standaloneMigrateCmd,
1315
1400
  // standalone scaffold (ADR 0108 / 0125, task 2055)
1316
1401
  parseTargetRef, instanceRepoRemote, instanceInitSpec, scaffoldStandaloneRepo, coreVersionSafe,
@@ -19,7 +19,7 @@ const RUNBOOK_TASK = 1981; // goal-35 canonical runbook; A05 depends on it (exte
19
19
  const TASKS = [
20
20
  { ref: 'A01', kind: 'feature', priority: 4, est: 180, crit: [CRIT], deps: [], extDeps: [],
21
21
  title: 'bongos init --adopt: mode skeleton + additive config layering (no clobber)',
22
- description: 'Add an `--adopt` mode to `scripts/gds/init.js` (ADR 0121 §Decision 1-2). Instead of the greenfield "stand up a NEW instance" path (which refuses where config/branding.json exists and whose --force OVERWRITES), --adopt LAYERS onto the existing tree: add the pinned @cloudbongos/core dependency + the lean config/ overrides + .claude/ ADDITIVELY. For any pre-existing file (package.json, an existing .claude/, a CI workflow) it MERGES or LEAVES-AND-REPORTS — it never overwrites, and it must NOT reuse the --force code path. Writes only the lean branding.json overrides (rest resolves from *.neutral.json). Serves criterion adopt-existing-repo. deps: none (the ADR-0108 configurable-root substrate is shipped).' },
22
+ description: 'Add an `--adopt` mode to `scripts/gds/init.js` (ADR 0121 §Decision 1-2). Instead of the greenfield "stand up a NEW instance" path (which refuses where config/branding.json exists and whose --force OVERWRITES), --adopt LAYERS onto the existing tree: add the pinned @bongos/core dependency + the lean config/ overrides + .claude/ ADDITIVELY. For any pre-existing file (package.json, an existing .claude/, a CI workflow) it MERGES or LEAVES-AND-REPORTS — it never overwrites, and it must NOT reuse the --force code path. Writes only the lean branding.json overrides (rest resolves from *.neutral.json). Serves criterion adopt-existing-repo. deps: none (the ADR-0108 configurable-root substrate is shipped).' },
23
23
  { ref: 'A02', kind: 'feature', priority: 4, est: 150, crit: [CRIT], deps: [], extDeps: [],
24
24
  title: 'bongos init --adopt: detect the existing stack + layout',
25
25
  description: 'Before writing anything, --adopt inspects the existing tree — language/runtime, package manager, test + CI setup, directory layout — so the enabled modules, the generated repo-map, and the core<->host fitness boundary (ADR 0062 §1) fit what is already there instead of assuming the reference layout (ADR 0121 §Decision 4). Output: a detected-stack summary consumed by A03 (real-state capture) and A04 (pre-flight). Serves criterion adopt-existing-repo. deps: none.' },
@@ -19,7 +19,7 @@
19
19
  //
20
20
  // Exit codes: 0 ok · 1 network/api error · 2 bad invocation · 3 not found.
21
21
 
22
- const { API_BASE, cliClient } = require('./cli-lib');
22
+ const { API_BASE, cliClient, assertInstanceMatch } = require('./cli-lib');
23
23
 
24
24
  // The active internal version; override with GDS_STATUS_DEFAULT_VERSION (or pass
25
25
  // --version). Bumped GDS-V4 → BONGOS-V1 (task 1709, ADR 0103): GDS-V4 was
@@ -319,6 +319,11 @@ async function main() {
319
319
  console.log('Usage: node scripts/gds/status.js [--version ID] [Cn] [--json]');
320
320
  process.exit(0);
321
321
  }
322
+ // Fail closed if this repo's configured instance != the signed-in session's (task
323
+ // 2124 / demo-audit C2): fetchProgress uses a raw fetch(API_BASE/…) that bypasses the
324
+ // apiCall/cliClient guard, so without this `bongos status` would silently print another
325
+ // instance's progress. Skip only in --hook mode (the automated card path stays fail-open).
326
+ if (!opts.hook) await assertInstanceMatch();
322
327
  const data = await fetchProgress(opts.version);
323
328
 
324
329
  // Resolve the requested criterion (if any) once — every output mode needs it.
@@ -0,0 +1,166 @@
1
+ #!/usr/bin/env node
2
+ // scripts/gds/task.js — `bongos task` : create + show a Bongos task from the terminal.
3
+ //
4
+ // task 2123 (demo-audit fix C1). The bongos CLI had start/claim/ship/status/… but no
5
+ // way to CREATE a task — so seeding the first task (a new owner's opening move) meant
6
+ // hand-driving POST /api/bongos/tasks through `bongos exec scripts/gds/api.js`. This is
7
+ // the first-class front door.
8
+ //
9
+ // Usage:
10
+ // bongos task new --title "…" --description "…" [--kind feature] [--discipline engineer]
11
+ // [--priority 3] [--version <id>] [--goal <id>] [--criteria C1,C2]
12
+ // [--touches a/,b/] [--est-minutes N] [--credits N] [--ready] [--json]
13
+ // bongos task show <id> [--json]
14
+ //
15
+ // Task CREATION is Archon-gated SERVER-SIDE (creating work shapes scope, ADR 0016). This
16
+ // CLI adds no authority — a lower rank gets a clear 403 message pointing at idea capture.
17
+
18
+ const { cliClient, requireSession, arg, hasFlag } = require('./cli-lib');
19
+
20
+ // Valid enums (mirrors the server's task-classifier vocab; a friendly client-side check
21
+ // so an obvious typo fails before the round-trip — the server stays authoritative).
22
+ const VALID_KINDS = ['feature', 'bug', 'infra', 'refactor', 'spike', 'learning-capture', 'cleanup', 'decision', 'blocker-resolution', 'verify'];
23
+ const VALID_DISCIPLINES = ['engineer', 'artist', 'ideator', 'ui'];
24
+
25
+ function csv(v) {
26
+ return String(v || '').split(',').map((s) => s.trim()).filter(Boolean);
27
+ }
28
+
29
+ // PURE argv → { body, criteria, errors } — exported for the unit test (no network).
30
+ // `args` is the argv slice AFTER the `new` subcommand.
31
+ function buildTaskBody(args) {
32
+ const errors = [];
33
+ const title = arg('--title', args);
34
+ const description = arg('--description', args);
35
+ if (!title) errors.push('--title is required');
36
+ if (!description) errors.push('--description is required');
37
+
38
+ const kind = arg('--kind', args) || 'feature';
39
+ if (!VALID_KINDS.includes(kind)) errors.push(`--kind must be one of: ${VALID_KINDS.join(', ')}`);
40
+ const discipline = arg('--discipline', args) || 'engineer';
41
+ if (!VALID_DISCIPLINES.includes(discipline)) errors.push(`--discipline must be one of: ${VALID_DISCIPLINES.join(', ')}`);
42
+
43
+ const num = (flag) => { const v = arg(flag, args); return v == null ? undefined : Number(v); };
44
+ const priority = num('--priority');
45
+ if (priority !== undefined && (!Number.isInteger(priority) || priority < 1 || priority > 5)) {
46
+ errors.push('--priority must be an integer 1–5');
47
+ }
48
+ const goalRaw = arg('--goal', args);
49
+ const goalId = goalRaw == null ? undefined : Number(goalRaw);
50
+ if (goalRaw != null && (!Number.isInteger(goalId) || goalId <= 0)) errors.push('--goal must be a positive integer task-goal id');
51
+
52
+ const body = {
53
+ title,
54
+ description,
55
+ kind,
56
+ discipline,
57
+ status: hasFlag('--ready', args) ? 'ready' : 'backlog',
58
+ source: 'bongos-task-cli',
59
+ };
60
+ const version = arg('--version', args);
61
+ if (version) body.version_id = version;
62
+ if (goalId !== undefined) body.goal_id = goalId;
63
+ if (priority !== undefined) body.priority = priority;
64
+ const est = num('--est-minutes'); if (est !== undefined) body.est_minutes = est;
65
+ const md = num('--manual-degree'); if (md !== undefined) body.manual_degree = md;
66
+ const credits = num('--credits'); if (credits !== undefined) body.credits_reward = credits;
67
+ const touches = csv(arg('--touches', args)); if (touches.length) body.touches = touches;
68
+ const criteria = csv(arg('--criteria', args));
69
+ if (criteria.length) body.criterion_ids = criteria;
70
+
71
+ return { body, criteria, errors };
72
+ }
73
+
74
+ function usage() {
75
+ console.error('usage:');
76
+ console.error(' bongos task new --title "…" --description "…" [--kind feature] [--discipline engineer]');
77
+ console.error(' [--priority 3] [--version <id>] [--goal <id>] [--criteria C1,C2]');
78
+ console.error(' [--touches a/,b/] [--est-minutes N] [--credits N] [--ready] [--json]');
79
+ console.error(' bongos task show <id> [--json]');
80
+ console.error(`\n kinds: ${VALID_KINDS.join(' | ')}`);
81
+ console.error(` disciplines: ${VALID_DISCIPLINES.join(' | ')}`);
82
+ }
83
+
84
+ function hallLink(base, id) {
85
+ try { return `${new URL(base).origin}/builders#/task/${id}`; } catch { return `task #${id}`; }
86
+ }
87
+
88
+ // The version_id a task defaults to when --version is omitted: the most-recent
89
+ // BUILDING version (the capture.js precedent, so idea-capture and task-create agree).
90
+ // Returns null when none is building — the caller then requires an explicit --version.
91
+ async function resolveDefaultVersion(api) {
92
+ const r = await api.versions.getVersions();
93
+ if (!r.ok) return null;
94
+ const building = (r.data?.versions ?? []).filter((v) => v.status === 'building');
95
+ if (building.length === 0) return null;
96
+ building.sort((a, b) => {
97
+ const ta = a.started_at ? new Date(a.started_at).getTime() : 0;
98
+ const tb = b.started_at ? new Date(b.started_at).getTime() : 0;
99
+ return tb - ta;
100
+ });
101
+ return building[0].id;
102
+ }
103
+
104
+ async function cmdNew(args) {
105
+ const { body, errors } = buildTaskBody(args);
106
+ if (errors.length) { console.error('task new: ' + errors.join('; ') + '\n'); usage(); process.exit(2); }
107
+ const api = await cliClient();
108
+ // version_id is required by the API; default to the active (building) version so a
109
+ // new owner never has to know the id, but keep --version as the explicit override.
110
+ if (!body.version_id) {
111
+ body.version_id = await resolveDefaultVersion(api);
112
+ if (!body.version_id) {
113
+ console.error('task new: could not resolve an active version — pass --version <id> (see `bongos status`).');
114
+ process.exit(2);
115
+ }
116
+ }
117
+ const r = await api.tasks.postTasks({ body });
118
+ if (r.status === 403) {
119
+ console.error('Creating a task is Archon-gated (creating work shapes scope). Your rank is lower, so the server declined.');
120
+ console.error('To propose it instead, capture an idea for triage:');
121
+ console.error(' bongos exec scripts/gds/capture.js "<your idea>"');
122
+ process.exit(1);
123
+ }
124
+ if (r.status !== 201 || !r.data?.task) {
125
+ console.error(`task new failed (${r.status}):`, r.data);
126
+ process.exit(1);
127
+ }
128
+ const task = r.data.task;
129
+ if (hasFlag('--json', args)) { console.log(JSON.stringify(task, null, 2)); return; }
130
+ const { API_BASE } = require('./cli-lib');
131
+ console.log(`✓ Created task #${task.id} [${task.status}] — ${task.title}`);
132
+ console.log(` kind=${task.kind} · discipline=${task.discipline}${task.priority ? ` · P${task.priority}` : ''}${task.goal_id ? ` · goal ${task.goal_id}` : ''}`);
133
+ console.log(` ${hallLink(API_BASE, task.id)}`);
134
+ if (task.status !== 'ready') console.log(` (status '${task.status}' — pass --ready to make it claimable now, deps permitting)`);
135
+ console.log(` next: bongos claim ${task.id}`);
136
+ }
137
+
138
+ async function cmdShow(args) {
139
+ const id = Number(args[0]);
140
+ if (!Number.isInteger(id) || id <= 0) { console.error('usage: bongos task show <id>'); process.exit(2); }
141
+ const api = await cliClient();
142
+ const r = await api.tasks.getTasksId({ id });
143
+ if (r.status === 404) { console.error(`task #${id} not found.`); process.exit(1); }
144
+ if (r.status !== 200 || !r.data?.task) { console.error(`task show failed (${r.status}):`, r.data); process.exit(1); }
145
+ const task = r.data.task;
146
+ if (hasFlag('--json', args)) { console.log(JSON.stringify(task, null, 2)); return; }
147
+ console.log(`#${task.id} [${task.status}] — ${task.title}`);
148
+ console.log(`kind=${task.kind} · discipline=${task.discipline}${task.priority ? ` · P${task.priority}` : ''}${task.goal_id ? ` · goal ${task.goal_id}` : ''}${task.version_id ? ` · ${task.version_id}` : ''}`);
149
+ if (task.description) console.log(`\n${task.description}`);
150
+ }
151
+
152
+ async function main() {
153
+ const args = process.argv.slice(2);
154
+ const sub = args[0];
155
+ if (!sub || sub === 'help' || sub === '--help' || sub === '-h') { usage(); process.exit(sub ? 0 : 2); }
156
+ await requireSession();
157
+ if (sub === 'new') return cmdNew(args.slice(1));
158
+ if (sub === 'show') return cmdShow(args.slice(1));
159
+ console.error(`task: unknown subcommand "${sub}"\n`); usage(); process.exit(2);
160
+ }
161
+
162
+ if (require.main === module) {
163
+ main().catch((err) => { console.error('fatal:', err); process.exit(1); });
164
+ }
165
+
166
+ module.exports = { buildTaskBody, VALID_KINDS, VALID_DISCIPLINES, csv, hallLink };