@bongos/core 1.17.0 → 1.17.2
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 +363 -312
- package/.claude/skills/blocker-review/SKILL.md +1 -1
- package/.claude/skills/builder-claim/SKILL.md +2 -0
- package/.claude/skills/builder-exit/SKILL.md +2 -2
- package/.claude/skills/builder-redteam/SKILL.md +3 -3
- package/.claude/skills/builder-setup/SKILL.md +2 -2
- package/.claude/skills/builder-start/SKILL.md +1 -1
- package/.claude/skills/dev/SKILL.md +1 -1
- package/.claude/skills/idea-triage/SKILL.md +2 -2
- package/.claude/skills/ideate/SKILL.md +1 -1
- package/.claude/skills/merge-mode/SKILL.md +1 -1
- package/.claude/skills/new-project/SKILL.md +3 -0
- package/.claude/skills/paint/SKILL.md +1 -1
- package/CLAUDE.md +11 -238
- package/GOVERNANCE.md +1 -1
- package/README.md +1 -1
- 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/0001-stack-choice-single-vps.md +1 -1
- package/docs/adr/0008-google-chat-oauth-user-auth.md +1 -1
- package/docs/adr/0019-red-team-bounty-payout-table.md +3 -3
- package/docs/adr/0020-security-model-public-stub.md +1 -1
- package/docs/adr/0023-kind-multipliers-and-idea-bonus.md +1 -1
- package/docs/adr/0024-cloneable-repo-local-first-memory.md +1 -1
- package/docs/adr/0026-bfg-memory-hygiene-and-cross-builder-learning.md +4 -4
- package/docs/adr/0027-bfg-session-inefficiency-evaluator.md +3 -3
- package/docs/adr/0030-builders-hall-live-updates.md +1 -1
- package/docs/adr/0031-cloud-dev-environments-for-builders.md +4 -4
- package/docs/adr/0032-discord-mirror-of-gds.md +2 -2
- package/docs/adr/0034-thetes-graduated-newcomer-rank.md +1 -1
- package/docs/adr/0044-mediterranean-palette-replacement.md +1 -1
- package/docs/adr/0044-per-box-live-game-preview.md +1 -1
- package/docs/adr/0054-session-token-reward-cost-plus.md +10 -10
- package/docs/adr/0057-container-cost-ledger.md +3 -3
- package/docs/adr/0062-medusa-instance-model-decoupling.md +4 -4
- package/docs/adr/0065-agpl-nonprofit-ai-first-governance.md +2 -2
- package/docs/adr/0085-remove-default-per-builder-spend-cap.md +1 -1
- package/docs/adr/0086-goal-scoped-work-hierarchy.md +1 -1
- package/docs/adr/0096-require-reward-before-workable.md +9 -9
- package/docs/adr/0097-retroactive-reward-backfill.md +3 -3
- package/docs/adr/0100-two-repo-instance-model.md +1 -1
- package/docs/adr/0101-goal-hierarchy-phase-5-judge-membership-reward.md +3 -3
- package/docs/adr/0103-core-first-extraction-cloud-bongos-trunk.md +3 -3
- package/docs/adr/0107-module-upstreaming-contribution-to-core-catalog.md +1 -1
- package/docs/adr/0120-pay-on-land-and-builder-owned-rebase-gate.md +6 -6
- 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/0136-update-channel-subscription-policy.md +51 -0
- package/docs/adr/0137-upstream-publish-policy-manual-owner-gated.md +40 -0
- package/docs/adr/0138-bundled-module-deps-declared-per-module.md +45 -0
- package/docs/adr/README.md +14 -10
- package/docs/api/openapi.json +315 -6
- package/docs/api-reference.md +10 -5
- package/docs/architecture.md +3 -3
- package/docs/branding-contract.md +5 -4
- package/docs/canonical-permissions.md +1 -1
- package/docs/design/vanilla-hall-ui-redesign-scope.md +1 -1
- package/docs/file-map.md +20 -19
- package/docs/handoff-template.md +2 -2
- package/docs/modules-contract.md +8 -1
- package/docs/onboarding/diagrams/README.md +7 -7
- package/docs/onboarding/drachmae-and-karma.md +18 -18
- package/docs/onboarding/primer.md +9 -9
- package/docs/recipes/audit-replay.md +2 -2
- package/docs/recipes/builder-box-lifecycle.md +2 -2
- package/docs/recipes/claude-design-loop.md +1 -1
- package/docs/recipes/instance-deploy.md +4 -4
- package/docs/recipes/instance-first-run.md +51 -0
- package/docs/recipes/ops-gotchas.md +1 -1
- package/docs/recipes/packaging-the-core.md +5 -5
- package/docs/recipes/private-npm-distribution.md +39 -12
- package/docs/recipes/self-host.md +1 -1
- package/docs/recipes/staging-environment.md +1 -1
- package/docs/recipes/upgrading-the-core.md +63 -8
- package/docs/research/cursor-competitive-analysis.md +1 -1
- package/docs/research/llm-model-agnostic-and-cheaper-grader.md +1 -1
- 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/discord/module.json +2 -1
- package/modules/economy/CLAUDE.md +11 -44
- package/modules/economy/achievements.js +9 -1
- package/modules/economy/credits.js +10 -1
- package/modules/grading/CLAUDE.md +11 -38
- package/modules/hall-ui/CLAUDE.md +11 -57
- package/modules/hall-ui/public/builders.js +71 -4
- package/modules/hall-ui/public/goal-inbox.js +191 -0
- package/modules/hall-ui/public/goals-page.js +227 -22
- package/modules/hall-ui/public/goals.html +16 -0
- package/modules/hall-ui/public/hall-widgets.js +14 -1
- package/modules/hall-ui/public/index.html +3 -0
- package/modules/hall-ui/public/new-project-wizard.js +6 -4
- package/modules/hall-ui/public/style.css +43 -0
- 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/provisioning/CLAUDE.md +15 -0
- package/modules/provisioning/migrations/provisioning_001_tables.sql +160 -0
- package/modules/provisioning/migrations/provisioning_002_standalone_shape.sql +11 -0
- package/modules/provisioning/migrations/provisioning_003_github_app_manifest.sql +95 -0
- package/modules/provisioning/module.json +17 -0
- package/modules/provisioning/onboard-plan.js +37 -0
- package/modules/provisioning/provisioning.js +539 -0
- package/modules/provisioning/rate-limit.js +35 -0
- package/modules/provisioning/routes/provisioning.js +607 -0
- package/modules/provisioning/tests/provisioning.mjs +232 -0
- package/modules/provisioning/ui/.gitkeep +0 -0
- package/modules/security/CLAUDE.md +11 -40
- package/modules/security/secret-scrub.js +10 -0
- package/modules/sessions/CLAUDE.md +11 -35
- package/modules/status-ui/CLAUDE.md +11 -43
- package/package-lock.json +2 -2
- package/package.json +7 -2
- package/public-landing/create.html +58 -12
- package/public-landing/index.html +40 -21
- package/scripts/discord/README.md +1 -1
- package/scripts/gds/CLAUDE.md +11 -140
- package/scripts/gds/claim.js +98 -48
- package/scripts/gds/claude-materialize.js +174 -20
- package/scripts/gds/claude-md-template.js +84 -0
- package/scripts/gds/cli-lib.js +112 -2
- package/scripts/gds/cutover-codemod.js +241 -0
- package/scripts/gds/fitness.js +54 -0
- package/scripts/gds/gen-api-client.js +10 -10
- package/scripts/gds/init.js +112 -16
- package/scripts/gds/lint-infra-exec.js +70 -0
- package/scripts/gds/lint-workflow-exec.js +63 -0
- package/scripts/gds/migration-namespace.js +1 -1
- package/scripts/gds/mirror-lineage-check.js +14 -5
- package/scripts/gds/mirror-redact.js +8 -0
- package/scripts/gds/module.js +118 -4
- package/scripts/gds/npmrc.js +93 -0
- package/scripts/gds/onboard.js +17 -24
- package/scripts/gds/package-core.js +74 -17
- package/scripts/gds/provision.js +102 -17
- package/scripts/gds/publish-manifest.js +60 -2
- package/scripts/gds/run-routine.js +51 -13
- package/scripts/gds/run-unit-tests.js +4 -0
- 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/update-channel.js +186 -0
- package/scripts/gds/upgrade.js +172 -25
- package/src/bongos/CLAUDE.md +11 -60
- package/src/bongos/auth.js +24 -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/bongos/serve-internal.js +10 -1
- package/src/branding.js +1 -0
- package/src/instance-config.js +1 -1
- package/src/module-api.js +24 -1
- package/src/module-loader/loader.js +36 -4
- package/src/module-loader/manifest-schema.js +16 -1
- package/tests/branding.mjs +11 -0
- package/tests/claim_error_surface.mjs +107 -0
- package/tests/claude_materialize.mjs +125 -0
- package/tests/consumer_layout_boot.mjs +302 -0
- package/tests/cutover_codemod.mjs +184 -0
- package/tests/device_flow_start.mjs +96 -0
- package/tests/first_run_engine_check.mjs +79 -0
- package/tests/goal_membership_requests.mjs +33 -3
- package/tests/hook_core_resolve.mjs +125 -0
- package/tests/hook_user_agents.mjs +62 -0
- package/tests/infra_exec_paths.mjs +104 -0
- package/tests/init.mjs +106 -6
- package/tests/instance_mismatch_guard.mjs +101 -0
- package/tests/mirror_lineage_proof.mjs +14 -0
- package/tests/module_cli.mjs +130 -2
- package/tests/module_loader.mjs +42 -0
- package/tests/module_manifest.mjs +19 -0
- package/tests/module_submissions.mjs +135 -0
- package/tests/npmrc.mjs +105 -0
- package/tests/onboard.mjs +62 -0
- package/tests/package_core.mjs +76 -22
- package/tests/platform_boot.mjs +5 -2
- package/tests/provision.mjs +107 -8
- package/tests/provisioning_github_app_routes.mjs +44 -2
- package/tests/publish_manifest.mjs +52 -1
- package/tests/run_routine.mjs +26 -1
- package/tests/run_routine_two_root.mjs +77 -0
- package/tests/scrubber_corpus.mjs +17 -0
- package/tests/served_paths_shipped.mjs +2 -2
- package/tests/task_cli.mjs +99 -0
- package/tests/update_channel.mjs +186 -0
- package/tests/upgrade.mjs +220 -25
- package/tests/workflow_exec_paths.mjs +98 -0
- package/docs/design/container-exterior-tiles.md +0 -49
- package/docs/design/container-exterior.md +0 -81
- package/docs/design/container-interior.md +0 -77
- package/docs/design/world-map-scroll.md +0 -41
- package/docs/recipes/claude-gemini-bongos-doors.md +0 -155
- package/docs/recipes/cloudbongos-staging-gate.md +0 -139
- package/docs/recipes/cloudbongos-standup.md +0 -111
- package/docs/recipes/parallel-load-results-2026-05-30.md +0 -63
- package/docs/recipes/parallel-load-test.md +0 -32
- package/docs/recipes/pixel-art-pipeline.md +0 -411
- package/docs/recipes/staging-data.md +0 -50
- package/migrations/instance/018_pms_v3_planning_seed.sql +0 -131
- package/migrations/instance/080_gds_v4_planning_seed.sql +0 -75
- package/migrations/instance/110_medusa_planning_seed.sql +0 -70
- package/migrations/instance/120_bongos_v1_planning_seed.sql +0 -69
- package/migrations/instance/144_bongos_v1_module_system_criterion.sql +0 -26
- package/migrations/instance/159_bongos_v1_goal_hierarchy_criterion.sql +0 -29
- package/migrations/instance/172_v2_world_planning_seed.sql +0 -163
- package/migrations/instance/173_bongos_v1_c7_core_modularized.sql +0 -33
- package/migrations/instance/182_gdsv4_bongos_consolidation.sql +0 -132
- package/migrations/instance/183_gdsv4_carryover_themed_goals.sql +0 -116
- package/migrations/instance/186_bongos_v1_governance_planning_seed.sql +0 -69
- package/scripts/gds/art-stage.js +0 -152
- package/server.js +0 -212
- package/tests/art_key.mjs +0 -121
- package/tests/day_cycle.mjs +0 -197
- package/tests/snapshot_picker.mjs +0 -233
- package/tests/sprite_facing.mjs +0 -35
- package/tests/terrain_picker.mjs +0 -298
- package/tests/turn_in_place.mjs +0 -280
- package/tests/walkability.mjs +0 -188
|
@@ -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>
|
|
@@ -73,6 +73,18 @@
|
|
|
73
73
|
|
|
74
74
|
// (Goals — goals.js registers itself into the other half of the Home band.)
|
|
75
75
|
|
|
76
|
+
// 0d-triage. Idea inbox triage cue — task 1471. Surfaces the open idea_inbox
|
|
77
|
+
// (count + a few recent titles) so a triager knows when triage is due. Starts
|
|
78
|
+
// hidden; builders.js fetches /inbox and un-hides ONLY for Metic+ (triage is
|
|
79
|
+
// Metic+), so no idea data loads for anyone who can't triage. Each row deep-
|
|
80
|
+
// links to #/idea/N — the task-detail overlay opens it with the Metic+ Discard
|
|
81
|
+
// control, so triage happens in place.
|
|
82
|
+
w('triage-inbox', 'Idea inbox', 26,
|
|
83
|
+
'<h2 id="triage-inbox-h" class="scroll__h">Idea inbox<span class="count-pill" id="triage-inbox-count" hidden></span></h2>' +
|
|
84
|
+
'<p class="scroll__lede">Open ideas awaiting triage. Open one to promote it to a task or discard it.</p>' +
|
|
85
|
+
'<ul class="triage-inbox" id="triage-inbox-list"><li class="profile__placeholder">…</li></ul>',
|
|
86
|
+
{ view: 'home', zone: 'home-main', labelledby: 'triage-inbox-h', hiddenByDefault: true });
|
|
87
|
+
|
|
76
88
|
// 0e. Recent work — compact 5-row table (task 1723). Filled by builders.js
|
|
77
89
|
// renderRecentShips from the same feed as the full Your-work table.
|
|
78
90
|
w('home-recent', 'Recent work', 28,
|
|
@@ -179,7 +191,8 @@
|
|
|
179
191
|
'<th class="ledger__sortable" id="lb-sort-rank" aria-sort="none" title="Sort by rank">Rank</th>' +
|
|
180
192
|
'<th class="ledger__sortable" id="lb-sort-drachmae" aria-sort="descending" title="Sort by ' + curLabel() + ' (work shipped)" data-currency-label="caps">Credits</th>' +
|
|
181
193
|
'<th class="ledger__sortable" id="lb-sort-karma" aria-sort="none" title="Sort by karma (peer recognition + approvals)">Karma</th>' +
|
|
182
|
-
|
|
194
|
+
'<th class="ledger__sortable" id="lb-sort-tokens" aria-sort="none" title="Sort by tokens used across recorded sessions — efficiency is tokens ÷ credits (hover a row for tokens-per-drachma)">Tokens</th>' +
|
|
195
|
+
'</tr></thead><tbody><tr><td colspan="6" class="ledger__empty">…</td></tr></tbody></table>' +
|
|
183
196
|
'<div class="pager">' +
|
|
184
197
|
'<button class="btn-ghost" id="lb-prev" disabled>← earlier</button>' +
|
|
185
198
|
'<span id="lb-range" class="pager__range">1–10</span>' +
|
|
@@ -107,6 +107,9 @@
|
|
|
107
107
|
<script src="/builders/hall-widgets.js?v=2026-06-27-r75" defer></script>
|
|
108
108
|
<!-- BV1.R78 (task 1694): the Goals widget registers into the same registry. -->
|
|
109
109
|
<script src="/builders/goals.js?v=2026-07-01-goals" defer></script>
|
|
110
|
+
<!-- ADR 0112 (task 1954): the home-page goal-membership inbox — invitations to
|
|
111
|
+
accept + join-requests to approve. Self-registers; hidden when empty. -->
|
|
112
|
+
<script src="/builders/goal-inbox.js?v=2026-07-10-goal-inbox" defer></script>
|
|
110
113
|
<!-- BV1-prov-P6 (task 1947): the Instances widget — self-registers, gated on the provisioning module. -->
|
|
111
114
|
<script src="/builders/new-project-wizard.js?v=2026-07-04-goal35" defer></script>
|
|
112
115
|
<script src="/builders/instances-panel.js?v=2026-07-03-prov-p6" defer></script>
|
|
@@ -74,20 +74,22 @@
|
|
|
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>
|
|
85
85
|
</div>
|
|
86
|
+
<p class="ledger__note">Terminal CLI only: after approving, enable <strong>Device Flow</strong> on the app (GitHub → Settings → Developer settings → GitHub Apps → your app → “Enable Device Flow”) so <code>bongos login</code> works. Web sign-in needs nothing.</p>
|
|
86
87
|
<details class="np-manual"><summary class="ledger__note">Prefer to do it by hand?</summary>
|
|
87
88
|
<ol class="np-steps">
|
|
88
89
|
<li><strong>Create the GitHub OAuth app</strong> — GitHub → <em>your account's</em> Settings → Developer settings → OAuth Apps → New OAuth App.
|
|
89
90
|
<div class="np-kv"><span>Homepage URL</span><code>${escapeHtml(oa.homepageUrl)}</code></div>
|
|
90
91
|
<div class="np-kv"><span>Authorization callback URL</span><code>${escapeHtml(oa.callbackUrl)}</code></div>
|
|
92
|
+
<div class="np-kv"><span>Enable Device Flow</span><code>✅ required — the terminal login uses it (off by default)</code></div>
|
|
91
93
|
</li>
|
|
92
94
|
<li><strong>Place the secret — verified</strong> (never hand-edit the env file): on the control plane run
|
|
93
95
|
<code>bongos … oauth-secret place ${escapeHtml(slug)} --client-id <id> --client-secret <secret> --apply</code>.</li>
|
|
@@ -101,7 +103,7 @@
|
|
|
101
103
|
${repoStep}
|
|
102
104
|
${dnsNote}
|
|
103
105
|
${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>
|
|
106
|
+
${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
107
|
<li><strong>Verify</strong> — <code>${escapeHtml(oa.homepageUrl)}/healthz</code> should report <code>auth_configured: true</code>.</li>` : ''}
|
|
106
108
|
</ol>
|
|
107
109
|
<p class="ledger__note">This is the same sequence the <code>/new-project</code> assistant and <code>bongos onboard</code> use.</p>
|
|
@@ -1746,3 +1746,46 @@ main [id] { scroll-margin-top: calc(var(--topbar-h) + 16px); }
|
|
|
1746
1746
|
.np-kv span { color: var(--ink-faint); font-size: 0.85rem; min-width: 12rem; }
|
|
1747
1747
|
.np-kv code, .np-steps code { background: var(--surface-2); padding: 0.1rem 0.35rem; border-radius: 4px; word-break: break-all; }
|
|
1748
1748
|
.np-done { margin-top: 0.5rem; }
|
|
1749
|
+
|
|
1750
|
+
/* ---- task 1471: idea-inbox triage cue (Home widget, Metic+) ---- */
|
|
1751
|
+
/* Shared .count-pill — work.html defines its own page-scoped copy; the hall
|
|
1752
|
+
index widget needs the same look, so it lives in the shared sheet now. */
|
|
1753
|
+
.count-pill {
|
|
1754
|
+
display: inline-flex; align-items: center; justify-content: center;
|
|
1755
|
+
min-width: 20px; height: 18px; padding: 0 6px; margin-left: 8px;
|
|
1756
|
+
border-radius: 999px; background: var(--surface-2); color: var(--ink-soft);
|
|
1757
|
+
font-size: 11px; font-weight: 600; font-variant-numeric: tabular-nums;
|
|
1758
|
+
vertical-align: 2px;
|
|
1759
|
+
}
|
|
1760
|
+
.triage-inbox { list-style: none; margin: 0; padding: 0; }
|
|
1761
|
+
.triage-inbox__item {
|
|
1762
|
+
display: flex; align-items: baseline; gap: 0.5rem;
|
|
1763
|
+
padding: 0.3rem 0; border-top: 1px solid var(--rule-soft);
|
|
1764
|
+
}
|
|
1765
|
+
.triage-inbox__item:first-child { border-top: 0; }
|
|
1766
|
+
.triage-inbox__link { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
1767
|
+
.triage-inbox__kind {
|
|
1768
|
+
flex: 0 0 auto; font-size: 11px; color: var(--ink-faint);
|
|
1769
|
+
text-transform: uppercase; letter-spacing: 0.04em;
|
|
1770
|
+
}
|
|
1771
|
+
.triage-inbox__empty { color: var(--ink-faint); padding: 0.3rem 0; }
|
|
1772
|
+
|
|
1773
|
+
/* Goal inbox (task 1954, ADR 0112) — the home-page two-sided membership queue:
|
|
1774
|
+
invitations to accept + join-requests to approve. Matches the home widgets'
|
|
1775
|
+
card language (.scroll host); each row is body-left / actions-right. */
|
|
1776
|
+
.ginbox { list-style: none; margin: 0 0 4px; padding: 0; }
|
|
1777
|
+
.ginbox__sec-h {
|
|
1778
|
+
margin: 14px 0 6px; font-size: 12px; font-weight: 600;
|
|
1779
|
+
text-transform: uppercase; letter-spacing: 0.04em; color: var(--ink-faint);
|
|
1780
|
+
}
|
|
1781
|
+
.ginbox__item {
|
|
1782
|
+
display: flex; align-items: flex-start; gap: 12px;
|
|
1783
|
+
padding: 9px 0; border-top: 1px solid var(--rule-soft);
|
|
1784
|
+
}
|
|
1785
|
+
.ginbox__item:first-child { border-top: 0; }
|
|
1786
|
+
.ginbox__body { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
|
|
1787
|
+
.ginbox__goal { font-weight: 600; font-size: 13.5px; }
|
|
1788
|
+
.ginbox__sub { font-size: 12.5px; color: var(--ink-soft); }
|
|
1789
|
+
.ginbox__note { margin: 3px 0 0; font-size: 12.5px; color: var(--ink-soft); font-style: italic; }
|
|
1790
|
+
.ginbox__actions { flex: 0 0 auto; display: flex; gap: 6px; }
|
|
1791
|
+
.ginbox__pending { font-size: 12.5px; margin: 10px 0 0; color: var(--ink-soft); }
|
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._
|
|
@@ -1,63 +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 `lifecycle` kernel PORT (ADR 0093 §3 — the headline of this carve)
|
|
12
|
-
|
|
13
|
-
The inverse of every prior carve: the lifecycle is carved last, so every capability it *invokes* (`grade`/`reward`/`onboarding`) is already a port it RESOLVES; the `lifecycle` port is the surface **core and the other modules resolve to reach the build state machine** without importing this module.
|
|
14
|
-
|
|
15
|
-
- **Provides — port `lifecycle`** → the surface in [`lifecycle.js`](lifecycle.js) (DELEGATES, does not spread, to `./db` + `./task-classifier` so test stubs bite at call time — the economy `reward` precedent). Registered in [`routes/lifecycle.js`](routes/lifecycle.js)'s factory (the loader runs it at boot + on every test router rebuild; `hasProvider`-guarded). It **retires the three ADR-0091 §3 doorway leaks** — `createTask`, `VOTABLE_TASK_STATUSES`, `tallyPeerVotes` — plus exposes `classifyTaskKind` and the work-tracking READS the staying-core read routes need.
|
|
16
|
-
- **Core + the other modules consume it WITHOUT importing this module** (all via `seams.resolveOptional('lifecycle')`):
|
|
17
|
-
- `src/bongos/routes/public.js` (the status surface) — `getTask`/`listVersions`/`versionProgress`/`recentShippedTasks`/`estimationDriftSummary`/`rollingAvgGrade`/`gradeTrendByDay`.
|
|
18
|
-
- `src/bongos/routes/me.js` (the profile reader) — `<redacted>`/`getEstVsActualRatio`/`recentGradesForBuilder`/`<redacted>`/`<redacted>`.
|
|
19
|
-
- `src/bongos/routes/builders.js` (the Archon roster) — `getBuildersRoster`/`<redacted>`/`gradeByBuilder`/`<redacted>`.
|
|
20
|
-
- `src/bongos/cascade-dispatch.js` (dormant System 3) — `classifyTaskKind`.
|
|
21
|
-
- `modules/onboarding/db.js` (newcomer-restock) + `modules/discord/db.js` (#bugs→task) — `createTask`.
|
|
22
|
-
- `modules/discord/discord-reaction.js` — `VOTABLE_TASK_STATUSES`.
|
|
23
|
-
- `modules/autonomy/pollers/routine-timers.js` — `tallyPeerVotes`.
|
|
24
|
-
- **It RESOLVES (as a transaction-participant consumer)** the `reward` + `onboarding` ports inside its own `withTx` — `claimTask`/`confirmTask`/`decideOverrideRequest`/`applyGrade` open the tx, run the state-machine SQL, and call `seams.resolveOptional('reward')|('onboarding').*(client, …)` so the credit/achievement/stage writes commit atomically with the status flip (ADR 0093 §2). It resolves via `resolveOptional` (graceful), so `consumes: []` — a vanilla instance without economy/onboarding degrades those writes to best-effort no-ops, it does not fail.
|
|
25
|
-
|
|
26
|
-
## Files
|
|
27
|
-
|
|
28
|
-
| File | What |
|
|
29
|
-
|---|---|
|
|
30
|
-
| `db.js` | the lifecycle data layer — ~97 functions: tasks/claims/versions/goals/dependencies/criteria, the claim→ship→grade→credit lifecycle (`claimTask`/`resolveClaim`/`confirmTask`/`shipTask`/`applyGrade`/`abandonTask`), overrides, estimation + work analytics, the merge lease, `tallyPeerVotes`, `createTask`. Reaches the pool/withTx/seams via the doorway; resolves `reward`/`onboarding` as a consumer. |
|
|
31
|
-
| `task-classifier.js` | the `tasks.kind`/discipline heuristic + `VOTABLE_TASK_STATUSES`/`VALID_KINDS`/`VALID_DISCIPLINES`/`TASK_FIELD_BOUNDS`. `branding()` (instance blocker-vendors) via the doorway. |
|
|
32
|
-
| `done-when.js` · `hierarchy.js` · `version-close.js` | the done-when criterion read/write surface, the shallow one-level task hierarchy, and the version-close gate evaluator. Pool via the doorway. |
|
|
33
|
-
| `github-push.js` · `conflict-resolve.js` · `publish-reconciler.js` | server-mediated branch publish ([ADR 0055](../../docs/adr/<redacted>.md)) + the [ADR 0082](../../docs/adr/<redacted>.md) generated-file conflict resolver + the confirmed→shipped reconciler. `repoInfo`/`branding`/`userAgent` via the doorway; siblings require each other. |
|
|
34
|
-
| `ship-card.js` · `ship-preflight.js` · `merge-lock.js` | the CLI lifecycle card, the ship preflight guards, and the cross-machine merge lease serializer (pure; `branding` via the doorway). |
|
|
35
|
-
| `lifecycle.js` | the `lifecycle` port provider surface (delegates to `./db` + `./task-classifier`). |
|
|
36
|
-
| `routes/{tasks,claims,versions,done-when,goals,dependencies,gate-approvals,analytics}.js` | the lifecycle HTTP surface — moved out of `src/bongos/routes/`; the loader mounts them via `contributes.routes`. Auth/validators/seams via the doorway; `./db` + libs are siblings. |
|
|
37
|
-
| `routes/lifecycle.js` | the route factory that registers the `lifecycle` port (no HTTP endpoint). |
|
|
38
|
-
| `pollers/publish-reconciler.js` | the loader poller entry (`start`/`stop`) for the publish reconciler — inverts the old `server.js` hardcoded start (the autonomy routine-timers precedent). |
|
|
39
|
-
|
|
40
|
-
## Boundary-forced duplication (keep in sync)
|
|
41
|
-
|
|
42
|
-
Two helpers are **deliberately duplicated** with core `src/bongos/db.js` (ADR 0091 §2 — "boundary-forced duplication is allowed + documented"): the same SQL runs on both sides of the kernel↔module line, and a module can't import core:
|
|
43
|
-
|
|
44
|
-
- **`sealSessionLog`** — core `deactivateBuilder` (rank/identity management, stays core) writes the same `session_logs` close row; here `resolveClaim`/`shipTask` write it.
|
|
45
|
-
- **`_applyRankChangeTx` (+ its `RANKS` / `singleRungPromotionError` deps)** — core `setBuilderRank` (Archon-driven, stays core) runs the same in-tx rank change; here it backs the automatic xenos→thetes graduation on ship (`maybeAutoGraduateToThetes`).
|
|
46
|
-
- **the `gradePassCount` SQL fragment** — core `builderRollingAvgGrade` (the grade-trend read the sessions BFG also consumes, kept on the doorway) + the module's grade reads share it.
|
|
47
|
-
|
|
48
|
-
## NOT in this module (the db.js residue — stays core)
|
|
49
|
-
|
|
50
|
-
- **Builder IDENTITY + admission** (`upsertBuilder`/`getBuilderByGithubId`/`hasInvitedAccessRequest`/`linkAnthropicAccount`/`updateBuilderDisciplines`) — `auth.js` imports these; they CANNOT live in a module.
|
|
51
|
-
- **Rank + status MANAGEMENT** (`setBuilderRank`/`_applyRankChangeTx`/`<redacted>`/`deactivate`/`reactivateBuilder`) — Archon identity ops consumed by `routes/builders.js`, NOT the task lifecycle.
|
|
52
|
-
- **AUTH sessions** (`createSession`/`lookupSession`/`revoke*`/`hasCliSession`/`SESSION_TTL_MS`) — the `builder_sessions` token store `auth.js` drives.
|
|
53
|
-
- **TWIN / social** (Discord link flow, karma feed, `listAuditLog`, the grade-trend `builderRollingAvgGrade`) + the **security_docs** store (the kernel security-PROPERTY plane).
|
|
54
|
-
- **The Gemini-key leak** (`getBuilderGeminiKeyRow`/…) — art-pipeline credential infra.
|
|
55
|
-
- **The lifecycle CLIs** — a command travels with its domain but stays a CLI under `scripts/gds/` (`ship.js`/`claim.js`/`release.js`/`merge-mode.js`/`confirm.js`/`merge-lock.js`/`version-close.js`/`main-audit.js`/`gate-review.js`/`push-main.js`/`ci-grade.js`), outside the kernel boundary — they may require this module directly (the grading/economy precedent).
|
|
56
|
-
|
|
57
|
-
## Tables
|
|
58
|
-
|
|
59
|
-
None owned. `tasks`/`claims`/`versions`/`goals`/`done_when_criteria`/`dependencies`/`task_grades`/`task_override_requests`/`merge_leases` (+ their migrations) were created before the module system and stay in core `migrations/` (the shared-schema additive rule). The module owns the *queries*; any **new** lifecycle migration belongs in `modules/lifecycle/migrations/` namespaced `lifecycle_*`.
|
|
60
|
-
|
|
61
|
-
## Tests
|
|
62
|
-
|
|
63
|
-
The DB-free carved-surface tests in `scripts/gds/coreB-carve-proof.sh` (`task_classifier_vocab`, `criterion_progress`, `github_push_*`, `ship_card`, `ship_preflight`, `merge_lock`, `conflict_resolve`, `publish_reconciler`, `goal_achievement`, `context_pack`) import from `modules/lifecycle/…` and pass byte-identical. The DB-integration tests (`grade_attempts`, `rank_gate`, `abandon_task`, `confirm_attribution`/`override_attribution`/`grade_attribution`, `goal_routes`/`goal_tier`/`goal_achievement`, `dependencies_*`, `task_create_idempotency`, `worktree_disambiguation`, `cap_newcomer_tasks`, `migration_allocation`, `task_ownership_gate`, `verify_task_guard`, `versions_route`, `gate_approvals`, `merge_lease`, `autonomous_fit_protected_paths`, …) require/read `modules/lifecycle/{db,routes/*,libs}.js`; the route-consumer tests (`onboarding_route_signals`, `onboarding_observability`, `discord_reaction`) register the `lifecycle` port. See [ADR 0093](../../docs/adr/<redacted>.md).
|
|
14
|
+
_To work ON the platform core itself, use the standalone @bongos/core source repository,
|
|
15
|
+
not this vendored copy._
|