@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.
- package/.bongos-core.json +154 -83
- package/CLAUDE.md +11 -238
- package/bin/bongos.js +1 -0
- package/clients/bongos-client/README.md +4 -4
- package/clients/bongos-client/bongos-client.global.js +14 -1
- package/clients/bongos-client/index.cjs +14 -1
- package/clients/bongos-client/index.d.ts +19 -2
- package/clients/bongos-client/index.mjs +11 -1
- package/clients/bongos-client/package.json +1 -1
- package/docs/adr/0133-github-app-manifest-onboarding-signin.md +2 -0
- package/docs/adr/0134-private-first-npm-distribution.md +20 -6
- package/docs/adr/0135-module-upstream-submission-interim-queue.md +34 -0
- package/docs/adr/README.md +5 -4
- package/docs/api/openapi.json +315 -6
- package/docs/api-reference.md +10 -5
- package/docs/architecture.md +1 -1
- package/docs/file-map.md +2 -2
- package/docs/modules-contract.md +2 -0
- package/docs/recipes/instance-deploy.md +4 -4
- package/docs/recipes/instance-first-run.md +51 -0
- package/docs/recipes/packaging-the-core.md +5 -5
- package/docs/recipes/private-npm-distribution.md +22 -12
- package/docs/recipes/self-host.md +1 -1
- package/docs/recipes/upgrading-the-core.md +8 -8
- package/migrations/core_191_module_submissions.sql +45 -0
- package/modules/autonomy/CLAUDE.md +11 -45
- package/modules/builder-settings/CLAUDE.md +11 -54
- package/modules/dev-box/CLAUDE.md +11 -34
- package/modules/discord/CLAUDE.md +11 -46
- package/modules/economy/CLAUDE.md +11 -44
- package/modules/grading/CLAUDE.md +11 -38
- package/modules/hall-ui/CLAUDE.md +11 -57
- package/modules/hall-ui/public/goals-page.js +227 -22
- package/modules/hall-ui/public/goals.html +16 -0
- package/modules/hall-ui/public/new-project-wizard.js +5 -4
- package/modules/ideas/CLAUDE.md +11 -33
- package/modules/lifecycle/CLAUDE.md +11 -59
- package/modules/lifecycle/routes/goals.js +80 -0
- package/modules/memory/CLAUDE.md +11 -36
- package/modules/onboarding/CLAUDE.md +11 -51
- package/modules/security/CLAUDE.md +11 -40
- package/modules/sessions/CLAUDE.md +11 -35
- package/modules/status-ui/CLAUDE.md +11 -43
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/public-landing/create.html +58 -12
- package/public-landing/index.html +40 -21
- package/scripts/gds/CLAUDE.md +11 -140
- package/scripts/gds/claude-materialize.js +151 -18
- package/scripts/gds/claude-md-template.js +84 -0
- package/scripts/gds/cli-lib.js +112 -2
- package/scripts/gds/gen-api-client.js +10 -10
- package/scripts/gds/init.js +53 -13
- package/scripts/gds/migration-namespace.js +1 -1
- package/scripts/gds/module.js +118 -4
- package/scripts/gds/npmrc.js +93 -0
- package/scripts/gds/onboard.js +13 -24
- package/scripts/gds/package-core.js +56 -14
- package/scripts/gds/provision.js +102 -17
- package/scripts/gds/seed-adopt-tasks.js +1 -1
- package/scripts/gds/status.js +6 -1
- package/scripts/gds/task.js +166 -0
- package/scripts/gds/upgrade.js +58 -18
- package/src/bongos/CLAUDE.md +11 -60
- package/src/bongos/auth.js +11 -0
- package/src/bongos/module-submissions.js +56 -0
- package/src/bongos/route-rank-check.js +6 -0
- package/src/bongos/routes/CLAUDE.md +11 -39
- package/src/bongos/routes/auth.js +6 -1
- package/src/bongos/routes/modules.js +123 -21
- package/src/instance-config.js +1 -1
- package/src/module-api.js +13 -1
- package/tests/claude_materialize.mjs +105 -0
- package/tests/device_flow_start.mjs +73 -0
- package/tests/first_run_engine_check.mjs +79 -0
- package/tests/goal_membership_requests.mjs +33 -3
- package/tests/hook_user_agents.mjs +62 -0
- package/tests/init.mjs +59 -6
- package/tests/instance_mismatch_guard.mjs +101 -0
- package/tests/module_cli.mjs +130 -2
- package/tests/module_submissions.mjs +135 -0
- package/tests/npmrc.mjs +105 -0
- package/tests/onboard.mjs +49 -0
- package/tests/package_core.mjs +68 -22
- package/tests/platform_boot.mjs +5 -2
- package/tests/provision.mjs +107 -8
- package/tests/provisioning_github_app_routes.mjs +29 -2
- package/tests/served_paths_shipped.mjs +2 -2
- package/tests/task_cli.mjs +99 -0
- package/tests/upgrade.mjs +89 -25
|
@@ -1,42 +1,15 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @bongos/core — installed platform dependency (not your project's CLAUDE.md)
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
This file is a placeholder shipped inside the **@bongos/core** package. The core's own
|
|
4
|
+
internal agent notes are intentionally omitted from the published artifact so they cannot
|
|
5
|
+
load into your instance's context.
|
|
4
6
|
|
|
5
|
-
|
|
7
|
+
**It does not govern this repository.** Your own root `CLAUDE.md`, your `.claude/`
|
|
8
|
+
directory, and the live Bongos database are the source of truth for how work happens here
|
|
9
|
+
and who may do what.
|
|
6
10
|
|
|
7
|
-
|
|
11
|
+
You are seeing this only because an agent opened a file under `node_modules/@bongos/core/`.
|
|
12
|
+
Nothing in here needs to be read to use the platform — run `bongos help` for commands.
|
|
8
13
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
## The `grade` kernel PORT (ADR 0091 §4 — the headline of this carve)
|
|
12
|
-
|
|
13
|
-
- **Provides — port `grade`** → the grader module surface. Registered in `routes/grade.js`'s factory (the loader runs it at boot, and again whenever a test rebuilds the GDS router — `hasProvider`-guarded so re-instantiation never trips the single-provider duplicate guard).
|
|
14
|
-
- **Core consumes it WITHOUT importing this module:**
|
|
15
|
-
- `routes/tasks.js` (the LIFECYCLE ship path, `POST /tasks/:id/grade`) resolves it via `seams.resolveOptional('grade')` and calls `grade.validateAndScore(...)` then `grade.serverSideGuards(...)`. A grading-disabled instance gets a clean `503 grading_unavailable` instead of a thrown `resolve()`.
|
|
16
|
-
- `routes/public.js` (`GET /public/grades`) resolves it the same way and reads `grade.RUBRIC.{threshold,version}`, falling back to `null` when grading is off.
|
|
17
|
-
- This replaced the former `const grader = require('../grader')` core→domain import in both files — the edge ADR 0091 §4 set out to sever.
|
|
18
|
-
|
|
19
|
-
Why a route factory for a module with **no HTTP routes**: `mountModuleRoutes` is the only loader hook that runs both in the live server and when a test rebuilds the GDS router, so it's where the port registration belongs. The factory returns an empty `express.Router()` purely as the mount vehicle.
|
|
20
|
-
|
|
21
|
-
## Files
|
|
22
|
-
|
|
23
|
-
| File | What |
|
|
24
|
-
|---|---|
|
|
25
|
-
| `grader.js` | the quality gate (~86 KB): v4 adversarial panel + `validateAndScore`/`serverSideGuards`/`gradeSubagent`/`RUBRIC`. The deterministic pre-passes reach `routeRankCheck`/`permissionPathCheck` through the doorway; `runSubagentCached` wraps spawns in the doorway's `llmCache`. |
|
|
26
|
-
| `grader-workers/` | the four worker modules (`narc`/`quality`/`hacker`/`efficiency`) + shared kit (`fence`, `worker-prompt`, `worker-verdict`, `rubric`, `extract-json`, `index`). All intra-module relative requires — unchanged by the move. |
|
|
27
|
-
| `grader-rubric.json` | the single-source scoring rubric (thresholds, per-worker config). Read by `grader.js` + `grader-workers/rubric.js` via `__dirname`-relative paths — survives the move. |
|
|
28
|
-
| `routes/grade.js` | registers the `grade` port (above); no HTTP endpoint. |
|
|
29
|
-
|
|
30
|
-
## NOT in this module (deliberately left in core)
|
|
31
|
-
|
|
32
|
-
- **`cascade-dispatch.js`** — stays in `src/bongos/`. It is dormant System 3 (model-tier dispatch, `OTB_AUTONOMY_ENABLED`-gated; nothing calls it but its mock unit test), and its real dependencies are core `task-classifier` (lifecycle) + `skill-prefs` (builder-settings), **not** the grader (the grader is an injected `runner`, never imported). Moving it would force two gratuitous doorway domain-leaks (`classifyTaskKind`, `skill-prefs.getForBuilder`) for capabilities grading never uses — the "ceremony with no boundary benefit" ADR 0091 §Alternatives rejects. It stays a core lib (scoped to `grading` in `module-scope-map.js`) until autonomy/dispatch is carved (tranche 2). The grading scope-map glob lists it as a core file, not part of this dir.
|
|
33
|
-
- **`scripts/gds/grade-smoke.js`** + the other grading-owned CLIs — a command travels with its domain but stays under `scripts/gds/` (the memory-carve precedent). They `require('../../modules/grading/grader')`.
|
|
34
|
-
- **`llm-cache.js`** — shared LLM-cost infra core also uses (`routes/llm-cache.js`, the cost scripts); it stays core and is exposed to this module on the doorway as `api.llmCache` (a tracked R70 leak).
|
|
35
|
-
|
|
36
|
-
## Tables
|
|
37
|
-
|
|
38
|
-
None owned. The grade-attempt rows (`grades`, `grade_attempts`, …) are written by core `db.applyGrade` on the lifecycle side; grading only computes the verdict the route then persists. No `modules/grading/migrations/` yet.
|
|
39
|
-
|
|
40
|
-
## Tests
|
|
41
|
-
|
|
42
|
-
`tests/grader.mjs`, `tests/grader_panel.mjs`, `tests/grader_prompt_fencing.mjs`, `tests/grade_server_authoritative.mjs`, `tests/grade_unavailable_display.mjs` import from `modules/grading/…`. `tests/cascade-dispatch.mjs` still imports `src/bongos/cascade-dispatch.js` (it stayed core). `scripts/gds/grade-smoke.js` is the live panel smoke.
|
|
14
|
+
_To work ON the platform core itself, use the standalone @bongos/core source repository,
|
|
15
|
+
not this vendored copy._
|
|
@@ -1,61 +1,15 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @bongos/core — installed platform dependency (not your project's CLAUDE.md)
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
This file is a placeholder shipped inside the **@bongos/core** package. The core's own
|
|
4
|
+
internal agent notes are intentionally omitted from the published artifact so they cannot
|
|
5
|
+
load into your instance's context.
|
|
4
6
|
|
|
5
|
-
|
|
7
|
+
**It does not govern this repository.** Your own root `CLAUDE.md`, your `.claude/`
|
|
8
|
+
directory, and the live Bongos database are the source of truth for how work happens here
|
|
9
|
+
and who may do what.
|
|
6
10
|
|
|
7
|
-
|
|
11
|
+
You are seeing this only because an agent opened a file under `node_modules/@bongos/core/`.
|
|
12
|
+
Nothing in here needs to be read to use the platform — run `bongos help` for commands.
|
|
8
13
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
- `module.json` — declares one **`contributes.webSurfaces`** entry `{ host: "builders.", dir: "public" }`.
|
|
12
|
-
|
|
13
|
-
**But the hall is NOT served by the generic web-surface loop that serves status-UI.** The status page is a plain static surface; the hall has far richer serving that MUST be byte-identical:
|
|
14
|
-
- the apex `/builders/*` tree + the `builders.<apex>` subdomain **rewrite shim** (#726),
|
|
15
|
-
- the **canonical-URL redirects** (#744: `/builders/ranks` → `/ranks` on the subdomain; #739: apex `/builders/*` → `builders.<apex>`),
|
|
16
|
-
- the **citizen/Archon page gates** (#488/#770: primer, diagrams, work, pair, settings, atlas are sign-in-gated; watch, harbor, gate are Archon-only — the real curl-proof access control),
|
|
17
|
-
- per-page **asset-stamping** (#521 content-hash `?v=`),
|
|
18
|
-
- the **module hall-widget injection** (BV1.R75 — `injectModuleHallWidgets`),
|
|
19
|
-
- the diagram PNG + `primer.md` static mounts.
|
|
20
|
-
|
|
21
|
-
So the manifest `webSurfaces` declaration is the **discovery + scope-map + `window.__MODULES__` record**; the bytes are served by the hall's dedicated block in `serve-internal.js`.
|
|
22
|
-
|
|
23
|
-
## The serving seam (how the hall keeps its dedicated block + is still a module)
|
|
24
|
-
|
|
25
|
-
`src/bongos/serve-internal.js` resolves `loader.moduleWebSurfaces({ isEnabled })` — the same seam status-UI uses — but **PARTITIONS the hall out** of the generic static loop on the builders-host predicate (`isBuildersHost`), so:
|
|
26
|
-
- the **generic loop** owns every web surface EXCEPT the hall (status-UI today),
|
|
27
|
-
- the hall's **dedicated block** owns the builders host and reads from `BUILDERS_DIR = modules/hall-ui/public/`.
|
|
28
|
-
|
|
29
|
-
The filter is on the **host** (`isBuildersHost`), not the `"hall-ui"` key, so the kernel still names no module key ([ADR 0083](../../docs/adr/<redacted>.md) §Decision #4). `isModuleSurfaceHost` (which the dedicated `/builders` guards use to skip the GENERIC status surface) therefore deliberately EXCLUDES the builders host — the dedicated block is what serves it, so it must not self-skip.
|
|
30
|
-
|
|
31
|
-
## The boundary
|
|
32
|
-
|
|
33
|
-
This module reaches **nothing** in core (no doorway require — it has no JS the server requires; `builders.js` etc. are browser code). Core, in turn, must never `require()` a file in here — `serve-internal.js` resolves the surface through `loader.moduleWebSurfaces`/`BUILDERS_DIR`, never an import of `modules/hall-ui/*`. Both directions are enforced by the BV1.R44/R45 fitness checks.
|
|
34
|
-
|
|
35
|
-
## The data it renders STAYS shared core (the judgment call)
|
|
36
|
-
|
|
37
|
-
The `/api/gds/me`, `/api/gds/public/*`, and `/api/gds/leaderboard` read endpoints the hall polls **did not move** — they live in `src/bongos/routes/{me,public,builders}.js` and are consumed by the status surface and the off-box mirror too, not just this hall. A read shared by multiple surfaces is correctly core/shared, not hall-exclusive (the same call status-UI / economy / ideas made). So the carve moves the *surface*, not the reads it happens to render.
|
|
38
|
-
|
|
39
|
-
## The hall-widget registry (BV1.R75)
|
|
40
|
-
|
|
41
|
-
The hall's `<main>` is filled from a **registry** (`public/dom-utils.js` `contributeHallWidget`; the core set in `public/hall-widgets.js`; a module's own client script otherwise). No widget may be hardcoded as a literal `<section class="scroll">` in `index.html` — a fitness check (`scripts/gds/fitness.js`, `HALL_INDEX` now points at `modules/hall-ui/public/index.html`) reds the build if one creeps back. Module-contributed hall widgets are injected via `serve-internal.js`'s `injectModuleHallWidgets` (the `hallWidgetScripts` loader seam), served at `/modules/<key>/public/hall-widget.js`. (Known pre-existing limitation, idea #465: that asset path 404s on the builders *subdomain* because the rewrite shim shadows it — injection works on both hosts; serving works on the apex. No module ships a hall-widget today so it's a live no-op.)
|
|
42
|
-
|
|
43
|
-
## Files & tooling
|
|
44
|
-
|
|
45
|
-
| Path | What |
|
|
46
|
-
|---|---|
|
|
47
|
-
| `public/index.html` · `public/builders.js` · `public/style.css` | the hall shell + core script + stylesheet (no build step, no framework) |
|
|
48
|
-
| `public/{work,roadmap,watch,harbor,gate,pair,settings,ranks,sessions,primer,diagrams,atlas,not-ready}.{html,js}` | the per-page surfaces |
|
|
49
|
-
| `public/task.html` + `public/task.js` | the task-detail page (`/task/:id`, task 751) — a real, shareable page that replaced the old `#/task/N` overlay. Irregular routing: `serve-internal.js`'s `TASK_DETAIL_PAGE_RE` resolves the two-segment `/builders/task/:id` path to this one file (every other page is a single-segment slug). (The old read-only Task Board twin `board.html`/`board.js` was removed in task 1965 — `/board` now 301s to `/work`.) |
|
|
50
|
-
| `public/goals.html` + `public/goals-page.js` | the Goals page (irregular basename — `goals.js` is the index's Goals hall-widget) |
|
|
51
|
-
| `public/dom-utils.js` · `public/hall-widgets.js` | the hall-widget + settings-panel registries (browser IIFEs) |
|
|
52
|
-
| `public/instances-panel.js` | the "Instances" hall widget (provisioning module, [ADR 0111](../../docs/adr/<redacted>.md) §1, task 1947) — self-registers **gated on `module:'provisioning'`** (`contributeHallWidget` drops it when the module is off); a self-fetching widget (the `goals.js` pattern) that reads the P1 `/provisioning/*` routes and renders own instances always + the Archon fleet + cost-ledger total when `/me` is `archon`. Read/state only. |
|
|
53
|
-
| `public/new-project-wizard.js` | the "Start a project" hall widget (provisioning module, [ADR 0111](../../docs/adr/<redacted>.md) / goal 35, task 1983) — the **CREATE** flow the instances-panel deferred: the most accessible form of the canonical onboarding runbook (the `/new-project` skill task 1981 + `bongos onboard` CLI task 1982). Same `module:'provisioning'` gating; a form that files a **standalone** provisioning request (`POST /provisioning/instances` — the web tier only ENQUEUES, trust boundary intact) then renders the irreducibly-human steps UI-first (create repo, OAuth app with the pinned apex callback, first sign-in) + points at the Instances panel for status. |
|
|
54
|
-
| `public/cursors/` · `public/chime.wav` | bronze cursors + the volume-slider preview chime |
|
|
55
|
-
| `module.json` | `contributes.webSurfaces: [{ host: "builders.", dir: "public" }]`; no port, no routes |
|
|
56
|
-
|
|
57
|
-
Generators that WRITE into this surface live under `scripts/` and were re-pointed at `modules/hall-ui/public/`: `scripts/gds/gen-atlas.js` (the `atlas.json` Repo Atlas data, gitignored — regenerated on deploy), `scripts/art/gen-cursors.py` (cursors), `.claude/sounds/generate.js` (the chime mirror).
|
|
58
|
-
|
|
59
|
-
## Tests
|
|
60
|
-
|
|
61
|
-
The loader seam is covered in `tests/module_loader.mjs` (`moduleWebSurfaces` + the live hall-ui surface); the manifest `webSurfaces` validation in `tests/module_manifest.mjs`; the carve discovery-key membership in `tests/fitness.mjs` / `tests/module_loader.mjs` / `tests/modules.mjs`; the browser settings-panel seam in `tests/module_contributions.mjs` (reads `public/dom-utils.js`); the Repo Atlas renderer contract in `tests/atlas.mjs`; and the carve proof in `scripts/gds/coreB-carve-proof.sh` (leg 4i).
|
|
14
|
+
_To work ON the platform core itself, use the standalone @bongos/core source repository,
|
|
15
|
+
not this vendored copy._
|
|
@@ -94,6 +94,13 @@
|
|
|
94
94
|
todo: { page: 1, perPage: loadPerPageFor(WORK_PER_PAGE_KEY.todo) },
|
|
95
95
|
done: { page: 1, perPage: loadPerPageFor(WORK_PER_PAGE_KEY.done) },
|
|
96
96
|
},
|
|
97
|
+
// task 1953 — private goals: my own pending join-request on the OPEN goal (row
|
|
98
|
+
// from GET /goals/inbox's myPendingRequests, filtered to this goal), or null.
|
|
99
|
+
myPendingRequest: null,
|
|
100
|
+
// task 1953 — the open goal's own pending invite/join-request queue (manager
|
|
101
|
+
// view, GET /goals/:id/requests), or null while loading / on error / not a
|
|
102
|
+
// manager. Feature-detected: an older server 404s and the panel just stays hidden.
|
|
103
|
+
pendingRequests: null,
|
|
97
104
|
};
|
|
98
105
|
|
|
99
106
|
async function getJSON(path) {
|
|
@@ -112,6 +119,13 @@
|
|
|
112
119
|
if (!res.ok) { const e = new Error(`${path} → ${res.status}`); e.status = res.status; e.data = data; throw e; }
|
|
113
120
|
return data;
|
|
114
121
|
}
|
|
122
|
+
// task 1953 — the visibility toggle is the only PATCH this page makes.
|
|
123
|
+
async function patchJSON(path, body) {
|
|
124
|
+
const res = await api.request('PATCH', path, { body: body || {} });
|
|
125
|
+
const data = res.data || {};
|
|
126
|
+
if (!res.ok) { const e = new Error(`${path} → ${res.status}`); e.status = res.status; e.data = data; throw e; }
|
|
127
|
+
return data;
|
|
128
|
+
}
|
|
115
129
|
|
|
116
130
|
const curLabel = () => window.OTBBranding?.currencyLabel?.() ?? 'credits';
|
|
117
131
|
// task 1957 — a task's drachmae is an ESTIMATE until it SETTLES (confirmed/
|
|
@@ -857,38 +871,74 @@
|
|
|
857
871
|
const depsSpan = deps.length
|
|
858
872
|
? `<span class="goal-deps${(g.status === 'open' && unmet.length) ? ' goal-deps--blocked' : ''}" title="This goal is blocked until each prerequisite goal is achieved">${(g.status === 'open' && unmet.length) ? '🔒 ' : ''}depends on: ${deps.map((dep) => `${escapeHtml(dep.title || ('goal ' + dep.id))} ${dep.satisfied ? '✓' : '○'}`).join(', ')}</span>`
|
|
859
873
|
: '';
|
|
860
|
-
|
|
874
|
+
// task 1953 — a private goal is join-GATED, not hidden, so the lock badge is
|
|
875
|
+
// informational for every viewer (the toggle that flips it lives in Actions,
|
|
876
|
+
// manager-only).
|
|
877
|
+
const lockSpan = g.visibility === 'private'
|
|
878
|
+
? '<span class="goal-badge goal-badge--locked" title="Private — join is by invitation or an approved request">🔒 Private</span>' : '';
|
|
879
|
+
meta.innerHTML = `<span>${escapeHtml(g.version_id || '')}</span> ${statusBadge(g.status)} ${lockSpan} ${scope} ${depsSpan}`;
|
|
861
880
|
}
|
|
862
881
|
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
}
|
|
882
|
+
// task 1762: the goal's own owner/manager (or an Archon) can invite/manage
|
|
883
|
+
// without an Archon in the loop — mirrors the server's authorizeGoalManagement
|
|
884
|
+
// gate. A protected-scope goal stays Archon-only on the SERVER (goals.js);
|
|
885
|
+
// this client gate deliberately doesn't re-derive the protected-module list
|
|
886
|
+
// (that roster lives server-side only, module-scope-map.js) — a non-Archon
|
|
887
|
+
// owner/manager sees the control on a protected-scope goal and gets a clear
|
|
888
|
+
// toast if the server 403s (see inviteMember's error handling below).
|
|
889
|
+
const isOwner = sameId(g.created_by, selfId());
|
|
890
|
+
const canManage = g.status === 'open' && (isArchon() || isOwner || iAmLead);
|
|
891
|
+
renderActions(g, { joined, canManage });
|
|
873
892
|
|
|
874
893
|
// task 1828 — Completion + Members each render into their band column AND
|
|
875
894
|
// their dedicated full view (capped in the band, "View all →" into the view).
|
|
876
895
|
renderCompletion();
|
|
877
|
-
|
|
878
|
-
// an Archon in the loop — mirrors the server's authorizeGoalManagement gate.
|
|
879
|
-
// A protected-scope goal stays Archon-only on the SERVER (goals.js); this
|
|
880
|
-
// client gate deliberately doesn't re-derive the protected-module list (that
|
|
881
|
-
// roster lives server-side only, module-scope-map.js) — a non-Archon owner/
|
|
882
|
-
// manager sees the control on a protected-scope goal and gets a clear toast
|
|
883
|
-
// if the server 403s (see inviteMember's error handling below).
|
|
884
|
-
const isOwner = sameId(g.created_by, selfId());
|
|
885
|
-
const canInvite = g.status === 'open' && (isArchon() || isOwner || iAmLead);
|
|
886
|
-
renderInviteBox(g.id, canInvite, members);
|
|
896
|
+
renderInviteBox(g.id, canManage, members);
|
|
887
897
|
renderMembers();
|
|
888
898
|
|
|
889
899
|
loadGoalTasks(g.id);
|
|
900
|
+
// task 1953 — my own pending request (any viewer) + the goal's pending queue
|
|
901
|
+
// (managers only); async, re-renders Actions + the pending panel on arrival.
|
|
902
|
+
loadGoalMembership(g.id, canManage);
|
|
890
903
|
void sat; void crit; // now read inside renderCompletion()
|
|
891
|
-
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
// task 1953 — the goal-detail Actions row: Leave/Join (unchanged), a manager's
|
|
907
|
+
// Private/Public toggle (PATCH /goals/:id/visibility), and — for a non-member
|
|
908
|
+
// viewer of a PRIVATE goal — Request to join / Requested (withdraw) in place of
|
|
909
|
+
// Join (private goals are join-GATED: POST /goals/:id/join 409s goal_private for
|
|
910
|
+
// a non-Archon non-member). An Archon keeps the plain Join button: the server
|
|
911
|
+
// lets them self-join any goal regardless of visibility (POST /goals/:id/join),
|
|
912
|
+
// so "request" would be a pointless extra step for them.
|
|
913
|
+
function renderActions(g, { joined, canManage }) {
|
|
914
|
+
const actions = $('#goal-detail-actions');
|
|
915
|
+
if (!actions) return;
|
|
916
|
+
const parts = [];
|
|
917
|
+
if (canManage) {
|
|
918
|
+
const nextVisibility = g.visibility === 'private' ? 'public' : 'private';
|
|
919
|
+
const label = g.visibility === 'private' ? '🔒 Private — make public' : 'Make private';
|
|
920
|
+
parts.push(`<button class="btn-ghost" id="goal-visibility-btn" type="button" data-next-visibility="${escapeHtml(nextVisibility)}">${label}</button>`);
|
|
921
|
+
}
|
|
922
|
+
if (g.status === 'open') {
|
|
923
|
+
if (joined) {
|
|
924
|
+
parts.push('<button class="btn-ghost" id="goal-leave-btn" type="button">Leave goal</button>');
|
|
925
|
+
} else if (g.visibility === 'private' && !isArchon()) {
|
|
926
|
+
if (state.myPendingRequest) {
|
|
927
|
+
parts.push('<span class="role-pill" title="Awaiting the owner/manager\'s approval">Requested</span>');
|
|
928
|
+
parts.push('<button class="btn-ghost" id="goal-withdraw-btn" type="button">Withdraw</button>');
|
|
929
|
+
} else {
|
|
930
|
+
parts.push('<button class="btn-ghost" id="goal-request-btn" type="button">🔒 Request to join</button>');
|
|
931
|
+
}
|
|
932
|
+
} else {
|
|
933
|
+
parts.push('<button class="landing__btn" id="goal-join-btn" type="button">Join goal</button>');
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
actions.innerHTML = parts.join(' ');
|
|
937
|
+
$('#goal-join-btn')?.addEventListener('click', () => joinLeave(g.id, 'join'));
|
|
938
|
+
$('#goal-leave-btn')?.addEventListener('click', () => joinLeave(g.id, 'leave'));
|
|
939
|
+
$('#goal-visibility-btn')?.addEventListener('click', (e) => toggleVisibility(g.id, e.currentTarget.dataset.nextVisibility));
|
|
940
|
+
$('#goal-request-btn')?.addEventListener('click', () => requestToJoin(g.id));
|
|
941
|
+
$('#goal-withdraw-btn')?.addEventListener('click', () => withdrawRequest(g.id, state.myPendingRequest && state.myPendingRequest.id));
|
|
892
942
|
}
|
|
893
943
|
|
|
894
944
|
// task 1828 — the active-claim feed (all builders' in-flight claims) powers the
|
|
@@ -1056,12 +1106,153 @@
|
|
|
1056
1106
|
} catch (err) {
|
|
1057
1107
|
const code = err?.data?.error;
|
|
1058
1108
|
if (code === 'archon_approval_required') toast('This goal is Archon-mediated — an Archon must add you.');
|
|
1109
|
+
else if (code === 'goal_private') toast('This goal is private — request to join instead.');
|
|
1059
1110
|
else if (code === 'not_a_member') toast('You are not a member of this goal.');
|
|
1060
1111
|
else if (code === 'goal_not_open') toast('This goal is not open.');
|
|
1061
1112
|
else toast(`Could not ${action} just now.`);
|
|
1062
1113
|
}
|
|
1063
1114
|
}
|
|
1064
1115
|
|
|
1116
|
+
// task 1953 — a manager (or Archon) flips a goal public|private.
|
|
1117
|
+
async function toggleVisibility(goalId, visibility) {
|
|
1118
|
+
try {
|
|
1119
|
+
await patchJSON(`${API}/goals/${encodeURIComponent(goalId)}/visibility`, { visibility });
|
|
1120
|
+
toast(visibility === 'private' ? 'Goal is now private.' : 'Goal is now public.');
|
|
1121
|
+
await loadDetail(goalId, { force: true });
|
|
1122
|
+
renderDetail(goalId);
|
|
1123
|
+
renderList();
|
|
1124
|
+
} catch (err) {
|
|
1125
|
+
const code = err?.data?.error;
|
|
1126
|
+
if (code === 'not_owner_or_manager') toast('Only this goal\'s owner or a manager (or an Archon) may do this.');
|
|
1127
|
+
else if (code === 'goal_not_open') toast('Only an open goal can be managed.');
|
|
1128
|
+
else toast('Could not update visibility just now.');
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
|
|
1132
|
+
// task 1953 — a non-member asks to join a private (or protected) goal.
|
|
1133
|
+
// renderDetail (below) re-derives canManage/joined and re-triggers
|
|
1134
|
+
// loadGoalMembership, so a plain reload-and-rerender is enough here — same
|
|
1135
|
+
// shape as joinLeave/inviteMember.
|
|
1136
|
+
async function requestToJoin(goalId) {
|
|
1137
|
+
try {
|
|
1138
|
+
await postJSON(`${API}/goals/${encodeURIComponent(goalId)}/requests`, {});
|
|
1139
|
+
toast('Request sent — the owner or a manager will review it.');
|
|
1140
|
+
await loadDetail(goalId, { force: true });
|
|
1141
|
+
renderDetail(goalId);
|
|
1142
|
+
} catch (err) {
|
|
1143
|
+
const code = err?.data?.error;
|
|
1144
|
+
if (code === 'request_pending') toast('You already have a pending request or invitation for this goal.');
|
|
1145
|
+
else if (code === 'already_a_member') toast('You are already a member of this goal.');
|
|
1146
|
+
else if (code === 'goal_is_open') toast('This goal is open — joining directly instead.');
|
|
1147
|
+
else toast('Could not send the request just now.');
|
|
1148
|
+
}
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1151
|
+
// task 1953 — withdraw MY OWN pending join-request (self-served, no manager needed).
|
|
1152
|
+
async function withdrawRequest(goalId, reqId) {
|
|
1153
|
+
if (reqId == null) return;
|
|
1154
|
+
try {
|
|
1155
|
+
await postJSON(`${API}/goals/${encodeURIComponent(goalId)}/requests/${encodeURIComponent(reqId)}/withdraw`, {});
|
|
1156
|
+
toast('Request withdrawn.');
|
|
1157
|
+
await loadDetail(goalId, { force: true });
|
|
1158
|
+
renderDetail(goalId);
|
|
1159
|
+
} catch (err) {
|
|
1160
|
+
toast((err && err.data && err.data.message) || 'Could not withdraw that request just now.');
|
|
1161
|
+
}
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1164
|
+
// task 1953 — a manager approves/denies a pending join-request from the goal's
|
|
1165
|
+
// own pending panel (mirrors the home-inbox action, POST …/requests/:reqId/respond).
|
|
1166
|
+
const pendingActionsPending = new Set();
|
|
1167
|
+
async function respondToJoinRequest(goalId, reqId, action, btn) {
|
|
1168
|
+
const key = `respond:${reqId}`;
|
|
1169
|
+
if (pendingActionsPending.has(key)) return;
|
|
1170
|
+
pendingActionsPending.add(key);
|
|
1171
|
+
if (btn) btn.disabled = true;
|
|
1172
|
+
try {
|
|
1173
|
+
await postJSON(`${API}/goals/${encodeURIComponent(goalId)}/requests/${encodeURIComponent(reqId)}/respond`, { action });
|
|
1174
|
+
toast(action === 'approve' ? 'Approved — they’re now a member.' : 'Request denied.');
|
|
1175
|
+
await loadDetail(goalId, { force: true });
|
|
1176
|
+
renderDetail(goalId);
|
|
1177
|
+
renderList();
|
|
1178
|
+
} catch (err) {
|
|
1179
|
+
toast((err && err.data && err.data.message) || 'Could not resolve that request just now.');
|
|
1180
|
+
if (btn) btn.disabled = false;
|
|
1181
|
+
} finally {
|
|
1182
|
+
pendingActionsPending.delete(key);
|
|
1183
|
+
}
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1186
|
+
// task 1953 — every pending row on THIS goal, rendered for a manager: join
|
|
1187
|
+
// requests get Approve/Deny; outstanding invites are informational (only the
|
|
1188
|
+
// invitee can accept/decline theirs — the server has no manager-cancel route).
|
|
1189
|
+
function pendingRequestWho(r) {
|
|
1190
|
+
return r.display_name || (r.github_login ? `@${r.github_login}` : `builder #${r.builder_id}`);
|
|
1191
|
+
}
|
|
1192
|
+
function pendingRequestsHtml(rows) {
|
|
1193
|
+
const requests = rows.filter((r) => r.kind === 'request');
|
|
1194
|
+
const invites = rows.filter((r) => r.kind === 'invite');
|
|
1195
|
+
const requestLi = (r) => `<li class="pending-row">
|
|
1196
|
+
<span class="pending-row__who">${escapeHtml(pendingRequestWho(r))}</span>
|
|
1197
|
+
${r.note ? `<span class="pending-row__note">“${escapeHtml(r.note)}”</span>` : ''}
|
|
1198
|
+
<span class="pending-row__actions">
|
|
1199
|
+
<button class="btn-ghost" type="button" data-approve-req="${escapeHtml(String(r.id))}">Approve</button>
|
|
1200
|
+
<button class="btn-ghost" type="button" data-deny-req="${escapeHtml(String(r.id))}">Deny</button>
|
|
1201
|
+
</span>
|
|
1202
|
+
</li>`;
|
|
1203
|
+
const inviteLi = (r) => `<li class="pending-row">
|
|
1204
|
+
<span class="pending-row__who">${escapeHtml(pendingRequestWho(r))}</span>
|
|
1205
|
+
<span class="pending-row__note">invited — awaiting their response</span>
|
|
1206
|
+
</li>`;
|
|
1207
|
+
const parts = [];
|
|
1208
|
+
if (requests.length) parts.push(`<div class="pending-group"><h3 class="pending-group__h">Join requests<span class="goal-count">${requests.length}</span></h3><ul class="pending-list">${requests.map(requestLi).join('')}</ul></div>`);
|
|
1209
|
+
if (invites.length) parts.push(`<div class="pending-group"><h3 class="pending-group__h">Pending invitations<span class="goal-count">${invites.length}</span></h3><ul class="pending-list">${invites.map(inviteLi).join('')}</ul></div>`);
|
|
1210
|
+
return parts.join('');
|
|
1211
|
+
}
|
|
1212
|
+
function renderPendingRequests() {
|
|
1213
|
+
const box = $('#goal-pending');
|
|
1214
|
+
if (!box) return;
|
|
1215
|
+
const rows = state.pendingRequests;
|
|
1216
|
+
if (!rows || !rows.length) { box.hidden = true; box.innerHTML = ''; return; }
|
|
1217
|
+
box.hidden = false;
|
|
1218
|
+
box.innerHTML = pendingRequestsHtml(rows);
|
|
1219
|
+
}
|
|
1220
|
+
|
|
1221
|
+
// task 1953 — async membership-queue load, kicked off (fire-and-forget) from
|
|
1222
|
+
// renderDetail like loadGoalTasks: my own pending request on this goal (any
|
|
1223
|
+
// viewer, from GET /goals/inbox's myPendingRequests) + — for a manager — the
|
|
1224
|
+
// goal's own pending queue (GET /goals/:id/requests, task 1953's new route).
|
|
1225
|
+
// Feature-detected the way the conflicts panel 404-detects the server-side
|
|
1226
|
+
// detector: an older server (or a non-manager 403) just hides the panel.
|
|
1227
|
+
async function loadGoalMembership(goalId, canManage) {
|
|
1228
|
+
try {
|
|
1229
|
+
const inbox = await getJSON(`${API}/goals/inbox`);
|
|
1230
|
+
if (!sameId(state.current, goalId)) return;
|
|
1231
|
+
state.myPendingRequest = (inbox.myPendingRequests || []).find((r) => sameId(r.goal_id, goalId)) || null;
|
|
1232
|
+
} catch (err) {
|
|
1233
|
+
if (err && err.message === 'redirecting to auth') return;
|
|
1234
|
+
if (!sameId(state.current, goalId)) return;
|
|
1235
|
+
state.myPendingRequest = null;
|
|
1236
|
+
}
|
|
1237
|
+
if (canManage) {
|
|
1238
|
+
try {
|
|
1239
|
+
const r = await getJSON(`${API}/goals/${encodeURIComponent(goalId)}/requests`);
|
|
1240
|
+
if (!sameId(state.current, goalId)) return;
|
|
1241
|
+
state.pendingRequests = r.requests || [];
|
|
1242
|
+
} catch (err) {
|
|
1243
|
+
if (err && err.message === 'redirecting to auth') return;
|
|
1244
|
+
if (!sameId(state.current, goalId)) return;
|
|
1245
|
+
state.pendingRequests = null;
|
|
1246
|
+
}
|
|
1247
|
+
} else {
|
|
1248
|
+
state.pendingRequests = null;
|
|
1249
|
+
}
|
|
1250
|
+
if (!sameId(state.current, goalId)) return;
|
|
1251
|
+
renderPendingRequests();
|
|
1252
|
+
const d = currentDetail();
|
|
1253
|
+
if (d) renderActions(d.goal, { joined: (d.members || []).some((m) => sameId(m.builder_id, selfId())), canManage });
|
|
1254
|
+
}
|
|
1255
|
+
|
|
1065
1256
|
/* ---------------- data + routing ---------------- */
|
|
1066
1257
|
|
|
1067
1258
|
async function loadDetail(goalId, { force = false } = {}) {
|
|
@@ -1084,6 +1275,12 @@
|
|
|
1084
1275
|
state.claimsByTask = new Map();
|
|
1085
1276
|
state.conflictPairs = null;
|
|
1086
1277
|
state.conflictSource = null;
|
|
1278
|
+
// task 1953 — a freshly opened goal starts with no known membership-queue
|
|
1279
|
+
// state; loadGoalMembership (kicked off from renderDetail) fills it in.
|
|
1280
|
+
state.myPendingRequest = null;
|
|
1281
|
+
state.pendingRequests = null;
|
|
1282
|
+
const pending = $('#goal-pending');
|
|
1283
|
+
if (pending) { pending.hidden = true; pending.innerHTML = ''; }
|
|
1087
1284
|
setWorkTab('todo');
|
|
1088
1285
|
['#goal-todo-count', '#goal-done-count', '#goal-conflicts-count', '#goal-conflicts-view-count',
|
|
1089
1286
|
'#goal-members-count', '#goal-members-view-count', '#goal-completion-count'].forEach((sel) => {
|
|
@@ -1290,6 +1487,14 @@
|
|
|
1290
1487
|
if (row && row.dataset.taskId && window.OTBTaskDetail) window.OTBTaskDetail.open(row.dataset.taskId);
|
|
1291
1488
|
});
|
|
1292
1489
|
});
|
|
1490
|
+
// task 1953 — Approve/Deny on the goal's own pending-requests panel.
|
|
1491
|
+
$('#goal-pending')?.addEventListener('click', (e) => {
|
|
1492
|
+
if (state.current == null) return;
|
|
1493
|
+
const approveBtn = e.target.closest('[data-approve-req]');
|
|
1494
|
+
if (approveBtn) { respondToJoinRequest(state.current, approveBtn.dataset.approveReq, 'approve', approveBtn); return; }
|
|
1495
|
+
const denyBtn = e.target.closest('[data-deny-req]');
|
|
1496
|
+
if (denyBtn) { respondToJoinRequest(state.current, denyBtn.dataset.denyReq, 'deny', denyBtn); return; }
|
|
1497
|
+
});
|
|
1293
1498
|
window.addEventListener('hashchange', route);
|
|
1294
1499
|
wireDragReorder();
|
|
1295
1500
|
|
|
@@ -150,6 +150,18 @@
|
|
|
150
150
|
.goal-invite__form { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
|
|
151
151
|
.goal-invite__form select#goal-invite-builder { flex: 1; min-width: 180px; }
|
|
152
152
|
|
|
153
|
+
/* task 1953 — private goals: the lock badge (meta line) + the goal's own
|
|
154
|
+
pending invite/join-request queue (manager view, next to the invite box). */
|
|
155
|
+
.goal-badge--locked { background: var(--warn-weak); color: var(--warn); }
|
|
156
|
+
.goal-pending { margin: 4px 0 10px; display: flex; flex-direction: column; gap: 10px; }
|
|
157
|
+
.pending-group__h { font-size: 12px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.02em; color: var(--ink-faint); margin: 0 0 4px; display: flex; align-items: center; gap: 6px; }
|
|
158
|
+
.pending-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; }
|
|
159
|
+
.pending-row { display: flex; align-items: center; gap: 8px; padding: 6px 0; border-bottom: 1px solid var(--rule-soft); font-size: 13px; flex-wrap: wrap; }
|
|
160
|
+
.pending-row:last-child { border-bottom: none; }
|
|
161
|
+
.pending-row__who { font-weight: 600; }
|
|
162
|
+
.pending-row__note { color: var(--ink-faint); font-size: 12px; flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
163
|
+
.pending-row__actions { display: flex; gap: 6px; margin-left: auto; }
|
|
164
|
+
|
|
153
165
|
.conflict { border: 1px solid color-mix(in srgb, var(--warn) 30%, var(--border)); background: var(--warn-weak); border-radius: var(--radius-sm); padding: 10px 14px; font-size: 13px; margin: 10px 0 0; }
|
|
154
166
|
.conflict__pair { font-weight: 600; }
|
|
155
167
|
.conflict__paths { font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace; font-size: 11.5px; color: var(--ink-soft); margin-top: 4px; word-break: break-all; }
|
|
@@ -246,6 +258,10 @@
|
|
|
246
258
|
<h2 class="scroll__h">Members<span class="goal-count" id="goal-members-count" hidden></span></h2>
|
|
247
259
|
<p class="scroll__lede" id="goal-members-lede"></p>
|
|
248
260
|
<div class="goal-invite" id="goal-invite" hidden></div>
|
|
261
|
+
<!-- task 1953 — the goal's own pending invite/join-request queue
|
|
262
|
+
(manager view; feature-detected, hidden on an older server or
|
|
263
|
+
when the viewer isn't owner/manager/Archon). -->
|
|
264
|
+
<div class="goal-pending" id="goal-pending" hidden></div>
|
|
249
265
|
<ul class="member-list" id="goal-members"></ul>
|
|
250
266
|
<div class="goal-col__foot" id="goal-members-foot" hidden></div>
|
|
251
267
|
</div>
|
|
@@ -74,11 +74,11 @@
|
|
|
74
74
|
const oa = domain ? oauthAppValues(domain) : null;
|
|
75
75
|
const dnsNote = domain ? '' : '<li>Add a domain you own, then re-run to wire the address (a domain is needed before sign-in works).</li>';
|
|
76
76
|
const repoStep = adopt
|
|
77
|
-
? `<li><strong>Layer onto your existing repo</strong> — in your repo run <code>bongos init --adopt</code>. It shows a conflict pre-flight (migrations, CI, protected paths) and writes <em>nothing</em> until you accept; it never overwrites your files, and it imports your existing backlog (GitHub issues / a TODO/ROADMAP file). Commit + push.</li>`
|
|
78
|
-
: '<li><strong>Create the empty GitHub repo</strong> — github.com → New repository, leave it empty.</li>';
|
|
77
|
+
? `<li data-onboard-step="github-repo"><strong>Layer onto your existing repo</strong> — in your repo run <code>bongos init --adopt</code>. It shows a conflict pre-flight (migrations, CI, protected paths) and writes <em>nothing</em> until you accept; it never overwrites your files, and it imports your existing backlog (GitHub issues / a TODO/ROADMAP file). Commit + push.</li>`
|
|
78
|
+
: '<li data-onboard-step="github-repo"><strong>Create the empty GitHub repo</strong> — github.com → New repository, leave it empty.</li>';
|
|
79
79
|
// The one-click GitHub sign-in step + a collapsible manual fallback (only when a
|
|
80
80
|
// domain is set — the App needs a real callback host).
|
|
81
|
-
const signInStep = oa ? `<li><strong>Set up GitHub sign-in — one click</strong>. We’ll open GitHub with a pre-filled app; approve it, and we place the credentials on your instance automatically (no secret to copy).
|
|
81
|
+
const signInStep = oa ? `<li data-onboard-step="oauth-app"><strong>Set up GitHub sign-in — one click</strong>. We’ll open GitHub with a pre-filled app; approve it, and we place the credentials on your instance automatically (no secret to copy).
|
|
82
82
|
<div class="np-signin-actions">
|
|
83
83
|
<button id="np-github-app" type="button" class="np-create-btn" data-instance="${escapeHtml(String(instanceId))}">Set up GitHub sign-in</button>
|
|
84
84
|
<span id="np-github-app-msg" class="ledger__note"></span>
|
|
@@ -88,6 +88,7 @@
|
|
|
88
88
|
<li><strong>Create the GitHub OAuth app</strong> — GitHub → <em>your account's</em> Settings → Developer settings → OAuth Apps → New OAuth App.
|
|
89
89
|
<div class="np-kv"><span>Homepage URL</span><code>${escapeHtml(oa.homepageUrl)}</code></div>
|
|
90
90
|
<div class="np-kv"><span>Authorization callback URL</span><code>${escapeHtml(oa.callbackUrl)}</code></div>
|
|
91
|
+
<div class="np-kv"><span>Enable Device Flow</span><code>✅ required — the terminal login uses it (off by default)</code></div>
|
|
91
92
|
</li>
|
|
92
93
|
<li><strong>Place the secret — verified</strong> (never hand-edit the env file): on the control plane run
|
|
93
94
|
<code>bongos … oauth-secret place ${escapeHtml(slug)} --client-id <id> --client-secret <secret> --apply</code>.</li>
|
|
@@ -101,7 +102,7 @@
|
|
|
101
102
|
${repoStep}
|
|
102
103
|
${dnsNote}
|
|
103
104
|
${signInStep}
|
|
104
|
-
${oa ? `<li><strong>First sign-in</strong> — once the instance is live, open <code>${escapeHtml(oa.homepageUrl)}</code> and sign in with GitHub; that makes you the owner (Archon).</li>
|
|
105
|
+
${oa ? `<li data-onboard-step="first-sign-in"><strong>First sign-in</strong> — once the instance is live, open <code>${escapeHtml(oa.homepageUrl)}</code> and sign in with GitHub; that makes you the owner (Archon).</li>
|
|
105
106
|
<li><strong>Verify</strong> — <code>${escapeHtml(oa.homepageUrl)}/healthz</code> should report <code>auth_configured: true</code>.</li>` : ''}
|
|
106
107
|
</ol>
|
|
107
108
|
<p class="ledger__note">This is the same sequence the <code>/new-project</code> assistant and <code>bongos onboard</code> use.</p>
|
package/modules/ideas/CLAUDE.md
CHANGED
|
@@ -1,37 +1,15 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @bongos/core — installed platform dependency (not your project's CLAUDE.md)
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
This file is a placeholder shipped inside the **@bongos/core** package. The core's own
|
|
4
|
+
internal agent notes are intentionally omitted from the published artifact so they cannot
|
|
5
|
+
load into your instance's context.
|
|
4
6
|
|
|
5
|
-
|
|
7
|
+
**It does not govern this repository.** Your own root `CLAUDE.md`, your `.claude/`
|
|
8
|
+
directory, and the live Bongos database are the source of truth for how work happens here
|
|
9
|
+
and who may do what.
|
|
6
10
|
|
|
7
|
-
|
|
11
|
+
You are seeing this only because an agent opened a file under `node_modules/@bongos/core/`.
|
|
12
|
+
Nothing in here needs to be read to use the platform — run `bongos help` for commands.
|
|
8
13
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
## The `ideas.capture` kernel PORT (ADR 0093 §5 — the headline of this carve)
|
|
12
|
-
|
|
13
|
-
- **Provides — port `ideas.capture`** → `inbox.captureIdea`. Registered in `routes/inbox.js`'s factory (the loader runs it at boot, and again whenever a test rebuilds the GDS router — `hasProvider`-guarded so re-instantiation never trips the single-provider duplicate guard). Mirrors `modules/economy/routes/reward.js` (a capability registered from a route factory).
|
|
14
|
-
- **It retires the `captureIdea` doorway leak.** ADR 0091 §3 parked `captureIdea` on `src/module-api.js` as a temporary "domain-leak-pending-carve" because the ideas domain wasn't a module yet. This carve deletes that re-export; the discord `#ideas` inbound (`modules/discord/discord-inbound.js`, the **sole** consumer) now resolves `ideas.capture` at call time instead of importing the doorway helper. This is the codebase's **first module→module port** (every prior port is consumed by core); discord declares it in `consumes`.
|
|
15
|
-
- **Why a verb-noun port, not a doorway re-export:** the same reason `grade`/`reward` are ports — a module capability another module invokes goes behind the kernel switchboard, never a direct import (ADR 0091 §4).
|
|
16
|
-
|
|
17
|
-
## Files
|
|
18
|
-
|
|
19
|
-
| File | What |
|
|
20
|
-
|---|---|
|
|
21
|
-
| `inbox.js` | the `idea_inbox` domain: `captureIdea` (the port provider) + the keyword `classify` fallback, `listOpenIdeas`/`getIdea`/`countOpenIdeas` (with the V3.R70 skill-friction rank-visibility gate), `updateIdea`, and the `ratifyCriterionProposal` transaction (criterion-proposal → `done_when_criteria` + `karma_log` + `criterion_proposal_audit`, all in one `pool.connect()` tx). Reaches the shared pool through the doorway. |
|
|
22
|
-
| `blockers.js` | the `blockers` domain: `listOpenBlockers`/`getBlocker`/`createBlocker`/`resolveBlocker`/`linkBlockerToTask`. `resolveBlocker` relies on the DB trigger on `blockers.status` to auto-promote linked `blocked` tasks. |
|
|
23
|
-
| `routes/inbox.js` | `POST/GET /inbox`, `GET /inbox/:id`, `PATCH /inbox/:id` (Metic+ triage), `POST /inbox/:id/ratify` (Archon). Its factory **registers the `ideas.capture` port**. |
|
|
24
|
-
| `routes/blockers.js` | `GET /blockers` (**public** — the status page reads it), `GET /blockers/:id`, `POST /blockers` (any builder files), `POST /blockers/:id/{resolve,link}` (Metic+). |
|
|
25
|
-
|
|
26
|
-
## NOT in this module (deliberately left in core)
|
|
27
|
-
|
|
28
|
-
- **The public idea/blocker projections** — `routes/public.js` (`projectPublicIdea`/`projectPublicBlocker`) reads `idea_inbox`/`blockers` with its **own** inline `pool.query` SELECTs (existence/ref checks for the hall's `#NNN` deep-link resolver + the public sitemap). Core querying a shared table with its own SQL is allowed; it does **not** import this module, so there is no vendored twin to keep in sync — the boundary is about code imports, not table access.
|
|
29
|
-
- **The ideas/blockers CLIs** — `scripts/gds/{capture,triage,render-ideas}.js` travel with the domain but stay under `scripts/`; they now `require('../../modules/ideas/inbox')` (the economy/memory CLI precedent — `scripts/gds/autonomy-gate.js` does the same for its idea-inbox read).
|
|
30
|
-
|
|
31
|
-
## Tables
|
|
32
|
-
|
|
33
|
-
None owned. `idea_inbox`, `blockers`, `task_blockers`, `criterion_proposal_audit` (+ the `done_when_criteria`/`karma_log` rows the ratify tx touches) were created before the module system and stay in core `migrations/` (the `schema_migrations` stem-key makes a later relocation a no-op). The module owns the *queries*; any **new** ideas migration belongs in `modules/ideas/migrations/` namespaced `ideas_*` (the additive rule).
|
|
34
|
-
|
|
35
|
-
## Tests
|
|
36
|
-
|
|
37
|
-
`tests/criterion_proposal.mjs` imports `modules/ideas/inbox` (the ratify transaction). `tests/discord_inbound.mjs` exercises the consumer (it injects a stub `captureIdea` via dependency injection, so it never hits the real port). `tests/route_validation.mjs` exercises the inbox/blocker write-schemas via the dependency-free `validate()` helper (it imports `_helpers`, not these routes). The doorway-surface contract (`tests/module_api.mjs`) asserts `captureIdea` is **gone** and `parseId` is present. See ADR 0093.
|
|
14
|
+
_To work ON the platform core itself, use the standalone @bongos/core source repository,
|
|
15
|
+
not this vendored copy._
|