@bongos/core 1.21.77 → 1.21.79
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 +103 -73
- package/.claude/skills/goal-create/SKILL.md +2 -1
- package/clients/bongos-client/README.md +1 -1
- package/clients/bongos-client/bongos-client.global.js +8 -0
- package/clients/bongos-client/index.cjs +8 -0
- package/clients/bongos-client/index.d.ts +13 -0
- package/clients/bongos-client/index.mjs +8 -0
- package/docs/api/openapi.json +285 -3
- package/docs/api-reference.md +6 -2
- package/docs/copy-inventory.md +48 -38
- package/docs/copy-registry.json +152 -61
- package/docs/module-api-changelog.md +4 -0
- package/docs/page-inventory.json +4 -2
- package/docs/page-readings.json +215 -206
- package/migrations/core_273_module_install_reports.sql +66 -0
- package/modules/hall-ui/public/atlas.html +1 -1
- package/modules/hall-ui/public/blockers.html +1 -1
- package/modules/hall-ui/public/board-room.html +1 -1
- package/modules/hall-ui/public/city.html +1 -1
- package/modules/hall-ui/public/collab.html +1 -1
- package/modules/hall-ui/public/commands.html +1 -1
- package/modules/hall-ui/public/copy-desk.html +1 -1
- package/modules/hall-ui/public/deploy.html +1 -1
- package/modules/hall-ui/public/diagrams.html +1 -1
- package/modules/hall-ui/public/drachmae.html +1 -1
- package/modules/hall-ui/public/fleet.html +1 -1
- package/modules/hall-ui/public/gate.html +1 -1
- package/modules/hall-ui/public/goal-map.html +1 -1
- package/modules/hall-ui/public/goals-page.js +13 -1
- package/modules/hall-ui/public/goals-render.js +1 -1
- package/modules/hall-ui/public/goals.html +1 -1
- package/modules/hall-ui/public/government.html +1 -1
- package/modules/hall-ui/public/idea.html +1 -1
- package/modules/hall-ui/public/ideas.html +1 -1
- package/modules/hall-ui/public/index.html +1 -1
- package/modules/hall-ui/public/modules.html +1 -1
- package/modules/hall-ui/public/primer.html +1 -1
- package/modules/hall-ui/public/profile.html +1 -1
- package/modules/hall-ui/public/project-settings.html +1 -1
- package/modules/hall-ui/public/ranks.html +1 -1
- package/modules/hall-ui/public/roadmap.html +1 -1
- package/modules/hall-ui/public/roster.html +1 -1
- package/modules/hall-ui/public/sessions.html +1 -1
- package/modules/hall-ui/public/settings-usage-reports.js +96 -0
- package/modules/hall-ui/public/settings.html +19 -1
- package/modules/hall-ui/public/settings.states.json +1 -0
- package/modules/hall-ui/public/shell.js +61 -29
- package/modules/hall-ui/public/street.html +1 -1
- package/modules/hall-ui/public/studio.html +1 -1
- package/modules/hall-ui/public/task-lib.js +21 -2
- package/modules/hall-ui/public/task.html +1 -1
- package/modules/hall-ui/public/thinking.html +1 -1
- package/modules/hall-ui/public/tweak-editor.html +1 -1
- package/modules/hall-ui/public/watch.html +1 -1
- package/modules/hall-ui/public/work.html +1 -1
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/release-notes.json +24 -0
- package/scripts/gds/module-assess-installs.js +143 -0
- package/scripts/gds/module.js +29 -5
- package/scripts/hall-preview/live-identity.js +1 -0
- package/src/bongos/module-usage-reports.js +112 -0
- package/src/bongos/routes/modules.js +96 -0
- package/src/module-api.js +1 -1
- package/tests/goal_map_page.mjs +1 -1
- package/tests/goal_plan_with_claude.mjs +41 -0
- package/tests/hall_commands.mjs +1 -1
- package/tests/hall_nav.mjs +42 -0
- package/tests/hall_tweak_editor.mjs +1 -1
- package/tests/module_assess_installs.mjs +272 -0
- package/tests/module_install.mjs +3 -0
- package/tests/module_update.mjs +3 -0
- package/tests/nav_permission_atoms.mjs +1 -1
|
@@ -84,27 +84,59 @@
|
|
|
84
84
|
}
|
|
85
85
|
|
|
86
86
|
// Small stroke icon set (18px grid, currentColor) — hand-rolled, no framework.
|
|
87
|
+
//
|
|
88
|
+
// ONE GLYPH PER TAB (task 1004626). The rail is a 64px icon strip until it
|
|
89
|
+
// opens, so at rest the icon IS the tab's name — two tabs wearing one glyph
|
|
90
|
+
// are two tabs a builder cannot tell apart (nine Government tabs once shared a
|
|
91
|
+
// shield, all eight Docs tabs one page). Every NAV item names its own key here
|
|
92
|
+
// and no two items share one; tests/hall_nav.mjs fails on a repeat, on a key
|
|
93
|
+
// missing from this set, and on two keys drawing the same markup.
|
|
87
94
|
var ICONS = {
|
|
95
|
+
// the project
|
|
88
96
|
home: '<path d="M3 8.5 9 3l6 5.5V15a1 1 0 0 1-1 1h-3.5v-4h-3v4H4a1 1 0 0 1-1-1V8.5Z"/>',
|
|
89
|
-
work: '<rect x="2.5" y="5.5" width="13" height="9" rx="1.5"/><path d="M6.5 5.5V4a1.5 1.5 0 0 1 1.5-1.5h2A1.5 1.5 0 0 1 11.5 4v1.5M2.5 9.5h13"/>',
|
|
90
|
-
standing: '<circle cx="9" cy="6" r="3"/><path d="M3.5 15.5c.6-2.9 2.9-4.5 5.5-4.5s4.9 1.6 5.5 4.5"/>',
|
|
91
|
-
profile: '<rect x="2.5" y="4" width="13" height="10" rx="1.5"/><circle cx="6.3" cy="8" r="1.6"/><path d="M4.1 12c.3-1.1 1.1-1.7 2.2-1.7s1.9.6 2.2 1.7"/><path d="M10.4 7.5h3M10.4 10.3h3"/>',
|
|
92
|
-
goals: '<circle cx="9" cy="9" r="6.5"/><circle cx="9" cy="9" r="3.5"/><circle cx="9" cy="9" r="0.8" fill="currentColor"/>',
|
|
93
97
|
roadmap: '<path d="M3 15.5V3.5l4 2 4-2 4 2v10l-4-2-4 2-4-2Z"/><path d="M7 3.5v10M11 5.5v10"/>',
|
|
94
|
-
|
|
95
|
-
|
|
98
|
+
goals: '<circle cx="9" cy="9" r="6.5"/><circle cx="9" cy="9" r="3.5"/><circle cx="9" cy="9" r="0.8" fill="currentColor"/>',
|
|
99
|
+
sky: '<circle cx="3.8" cy="12.5" r="1.3"/><circle cx="8.5" cy="5" r="1.3"/><circle cx="14.2" cy="8.5" r="1.3"/><circle cx="12" cy="14.8" r="1.3"/><path d="M4.5 11.4 7.8 6.1M9.6 5.7l3.5 2.1M13.7 9.8l-1.2 3.7"/>',
|
|
100
|
+
work: '<rect x="2.5" y="5.5" width="13" height="9" rx="1.5"/><path d="M6.5 5.5V4a1.5 1.5 0 0 1 1.5-1.5h2A1.5 1.5 0 0 1 11.5 4v1.5M2.5 9.5h13"/>',
|
|
101
|
+
blockers: '<path d="M6.3 2.5h5.4l3.8 3.8v5.4l-3.8 3.8H6.3l-3.8-3.8V6.3Z"/><path d="M6 9h6"/>',
|
|
102
|
+
boardroom: '<path d="M2.5 9h13v5.5a1 1 0 0 1-1 1h-11a1 1 0 0 1-1-1V9Z"/><path d="M5.5 9V2.5h7V9"/><path d="M7.3 5.8l1.2 1.2 2.2-2.3"/>',
|
|
103
|
+
thinking: '<path d="M9 2.5a4.5 4.5 0 0 0-2.7 8.1c.5 .4 .7 .9 .7 1.4v.5h4V12c0-.5.2-1 .7-1.4A4.5 4.5 0 0 0 9 2.5Z"/><path d="M7 15h4M7.8 16.8h2.4"/>',
|
|
96
104
|
// task 1003117 (R06 of goal 1000074): the Studio (artist mode). A sprout —
|
|
97
105
|
// the zen-garden reading of "a place an artist wants to be", so it does not
|
|
98
106
|
// reuse a glyph that would read as one more docs/goals page.
|
|
99
107
|
studio: '<path d="M9 15.5V8.5"/><path d="M9 8.5C9 5.5 6.5 3.5 3.5 3.5c0 3 2.5 5 5.5 5Z"/><path d="M9 10C9 7.5 11.2 5.5 14 5.5c0 2.7-2.2 4.5-5 4.5Z"/>',
|
|
108
|
+
// You
|
|
109
|
+
standing: '<circle cx="9" cy="6" r="3"/><path d="M3.5 15.5c.6-2.9 2.9-4.5 5.5-4.5s4.9 1.6 5.5 4.5"/>',
|
|
110
|
+
profile: '<rect x="2.5" y="4" width="13" height="10" rx="1.5"/><circle cx="6.3" cy="8" r="1.6"/><path d="M4.1 12c.3-1.1 1.1-1.7 2.2-1.7s1.9.6 2.2 1.7"/><path d="M10.4 7.5h3M10.4 10.3h3"/>',
|
|
111
|
+
// Community
|
|
112
|
+
collab: '<path d="M2.5 4a1 1 0 0 1 1-1h7a1 1 0 0 1 1 1v4.5a1 1 0 0 1-1 1H6.5L4 11.5v-2h-.5a1 1 0 0 1-1-1V4Z"/><path d="M11.5 6.5h3a1 1 0 0 1 1 1V12a1 1 0 0 1-1 1H14v2l-2.5-2H8.5a1 1 0 0 1-1-1V9.5"/>',
|
|
113
|
+
roster: '<circle cx="6.5" cy="6.3" r="2.3"/><path d="M2.5 15c.4-2.3 1.9-3.7 4-3.7s3.6 1.4 4 3.7"/><circle cx="12.5" cy="5.8" r="2"/><path d="M11.4 10.5c.4-.1.7-.2 1.1-.2 1.9 0 3.3 1.3 3.7 3.5"/>',
|
|
114
|
+
// Docs
|
|
115
|
+
primer: '<path d="M9 5C7.5 3.9 5.5 3.5 2.5 3.5v10c3 0 5 .4 6.5 1.5 1.5-1.1 3.5-1.5 6.5-1.5v-10c-3 0-5 .4-6.5 1.5Z"/><path d="M9 5v10"/>',
|
|
116
|
+
ranks: '<path d="M4 7.5 9 4l5 3.5M4 11 9 7.5l5 3.5M4 14.5 9 11l5 3.5"/>',
|
|
117
|
+
economy: '<circle cx="9" cy="9" r="6.5"/><path d="M11 6.7c-.4-.6-1.1-.9-2-.9-1.2 0-2 .6-2 1.5 0 2 4 1 4 3.1 0 .9-.9 1.5-2.1 1.5-.9 0-1.7-.4-2.1-1M9 4.7v1.1M9 12.2v1.1"/>',
|
|
118
|
+
commands: '<rect x="2.5" y="3.5" width="13" height="11" rx="1.5"/><path d="M5.5 7.3 7.7 9l-2.2 1.7M9.5 11.2h3"/>',
|
|
119
|
+
diagrams: '<rect x="2.5" y="2.5" width="5.5" height="4" rx="1"/><rect x="10" y="11.5" width="5.5" height="4" rx="1"/><path d="M5.2 6.5v4a1.5 1.5 0 0 0 1.5 1.5H10"/>',
|
|
120
|
+
atlas: '<circle cx="9" cy="9" r="6.5"/><path d="M2.5 9h13M9 2.5c1.7 1.8 2.6 4 2.6 6.5s-.9 4.7-2.6 6.5c-1.7-1.8-2.6-4-2.6-6.5S7.3 4.3 9 2.5Z"/>',
|
|
121
|
+
modules: '<rect x="2.5" y="2.5" width="5.5" height="5.5" rx="1"/><rect x="10" y="2.5" width="5.5" height="5.5" rx="1"/><rect x="2.5" y="10" width="5.5" height="5.5" rx="1"/><path d="M12.75 10.5v4.5M10.5 12.75h4.5"/>',
|
|
122
|
+
apidocs: '<path d="M6.5 3C5 3 4.5 3.7 4.5 5v2c0 1-.6 2-1.5 2 .9 0 1.5 1 1.5 2v2c0 1.3.5 2 2 2M11.5 3c1.5 0 2 .7 2 2v2c0 1 .6 2 1.5 2-.9 0-1.5 1-1.5 2v2c0 1.3-.5 2-2 2"/>',
|
|
123
|
+
// Government
|
|
124
|
+
watch: '<path d="M1.8 9S4.5 4 9 4s7.2 5 7.2 5-2.7 5-7.2 5S1.8 9 1.8 9Z"/><circle cx="9" cy="9" r="2.2"/>',
|
|
125
|
+
gate: '<path d="M9 2.5 15 5v4c0 3.5-2.4 6-6 7-3.6-1-6-3.5-6-7V5l6-2.5Z"/><path d="M6.5 9l1.8 1.8 3.4-3.6"/>',
|
|
126
|
+
sessions: '<path d="M7 5h8M7 9h8M7 13h8"/><circle cx="3.8" cy="5" r=".7" fill="currentColor"/><circle cx="3.8" cy="9" r=".7" fill="currentColor"/><circle cx="3.8" cy="13" r=".7" fill="currentColor"/>',
|
|
100
127
|
settings: '<circle cx="9" cy="9" r="2.5"/><path d="M9 2.5v2m0 9v2M2.5 9h2m9 0h2M4.4 4.4l1.4 1.4m6.4 6.4 1.4 1.4m0-9.2-1.4 1.4M5.8 12.2l-1.4 1.4"/>',
|
|
101
|
-
|
|
128
|
+
deploy: '<path d="M9 12V3M5.5 6.5 9 3l3.5 3.5M3 12.5v2a1 1 0 0 0 1 1h10a1 1 0 0 0 1-1v-2"/>',
|
|
129
|
+
fleet: '<path d="M4 2.5h10v13l-1.7-1-1.6 1-1.7-1-1.7 1-1.6-1-1.7 1v-13Z"/><path d="M6.5 6h5M6.5 9h5M6.5 12h3"/>',
|
|
130
|
+
permissions: '<circle cx="6" cy="12" r="3.5"/><path d="M8.5 9.5 15 3M12.5 5.5l2 2M10.5 7.5l1.5 1.5"/>',
|
|
102
131
|
// The governor city: a pediment over columns, the Governing mode's own glyph.
|
|
103
132
|
city: '<path d="M2.5 7 9 3l6.5 4M4 7.5v6.5M7 7.5v6.5M11 7.5v6.5M14 7.5v6.5M2.5 15.5h13"/>',
|
|
133
|
+
// the foot
|
|
104
134
|
signout: '<path d="M7 3.5H4a1 1 0 0 0-1 1v9a1 1 0 0 0 1 1h3M12 12l3-3-3-3M15 9H7"/>',
|
|
105
135
|
};
|
|
136
|
+
// An unknown name draws an EMPTY frame rather than borrowing another tab's
|
|
137
|
+
// glyph: a silent fallback is how a typo becomes a duplicate icon.
|
|
106
138
|
function icon(name) {
|
|
107
|
-
return '<svg class="nav-icon" viewBox="0 0 18 18" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">' + (ICONS[name] ||
|
|
139
|
+
return '<svg class="nav-icon" viewBox="0 0 18 18" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">' + (ICONS[name] || '') + '</svg>';
|
|
108
140
|
}
|
|
109
141
|
function chevronIcon() {
|
|
110
142
|
return '<svg class="nav-group__chevron" viewBox="0 0 18 18" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M5 7l4 4 4-4"/></svg>';
|
|
@@ -166,7 +198,7 @@
|
|
|
166
198
|
// (requireBuilderPage, the tier the map sat on). The id stays 'goal-map'
|
|
167
199
|
// because the href does: Goals' own "see which goals wait on each other"
|
|
168
200
|
// link and every bookmark land here unchanged.
|
|
169
|
-
{ id: 'goal-map', label: 'Sky', icon: '
|
|
201
|
+
{ id: 'goal-map', label: 'Sky', icon: 'sky', href: '/goal-map', gate: 'member' },
|
|
170
202
|
// task 1966: Tasks (the board formerly labelled "Work") is visible to ANY
|
|
171
203
|
// signed-in builder (incl. newcomer Xenos). Claimability is controlled
|
|
172
204
|
// per-task, not by hiding the page — every row carries a rank tag and the
|
|
@@ -185,7 +217,7 @@
|
|
|
185
217
|
// page can still gate cosmetically wider in the sidebar than the API it
|
|
186
218
|
// fronts, showing the link to signed-in builders while a stranger who
|
|
187
219
|
// already has the URL can still open it.
|
|
188
|
-
{ id: 'blockers', label: 'Blockers', icon: '
|
|
220
|
+
{ id: 'blockers', label: 'Blockers', icon: 'blockers', href: '/blockers', gate: 'signed' },
|
|
189
221
|
// task 1003744 (goal 1000111): the Board Room lives HERE, in the project's
|
|
190
222
|
// own group, and gates on the atom its page gates on.
|
|
191
223
|
//
|
|
@@ -211,7 +243,7 @@
|
|
|
211
243
|
// /me (boardVotesNeed), pre-filtered to what the vote route would accept —
|
|
212
244
|
// so the badge counts ballots this viewer can actually cast, never "items
|
|
213
245
|
// open somewhere".
|
|
214
|
-
{ id: 'board-room', label: 'Board Room', icon: '
|
|
246
|
+
{ id: 'board-room', label: 'Board Room', icon: 'boardroom', href: '/board-room', gate: 'perm:board.vote.cast', count: 'board_votes' },
|
|
215
247
|
// The governor city (task 1004524): the owner put it on the sidebar on
|
|
216
248
|
// 2026-10-08, reversing "leave it off". It is the Governing mode's lead tab
|
|
217
249
|
// (craft-mode.js folds that mode's other tabs into a dropdown under it).
|
|
@@ -239,7 +271,7 @@
|
|
|
239
271
|
// that caused it. gate 'signed' — there is no permission gate on having a
|
|
240
272
|
// thought, and the route reads the VIEWER's own trail, so there is
|
|
241
273
|
// nothing here a rank could protect.
|
|
242
|
-
{ id: 'thinking', label: 'Your thinking', icon: '
|
|
274
|
+
{ id: 'thinking', label: 'Your thinking', icon: 'thinking', href: '/thinking', gate: 'signed' },
|
|
243
275
|
// task 1003117 (R06 of goal 1000074): the Studio — artist mode, "a place an
|
|
244
276
|
// artist wants to be, not a form". Here and not in Docs (a place you ACT,
|
|
245
277
|
// not reference) or You (its subject is the product, not a person).
|
|
@@ -286,12 +318,12 @@
|
|
|
286
318
|
// other builders reaching you — not your own numbers (that is You).
|
|
287
319
|
// task 1003942: the second consumer of the count badge. `help_requests` is
|
|
288
320
|
// the need id builder-needs.js emits with a `count`; applyCounts reads it.
|
|
289
|
-
{ id: 'collab', label: 'Collab', icon: '
|
|
321
|
+
{ id: 'collab', label: 'Collab', icon: 'collab', href: '/collab', gate: 'signed', count: 'help_requests' },
|
|
290
322
|
// task 1003441: was the #leaderboard hash view on the home page, which is
|
|
291
323
|
// why this group read as one real page plus a link into index.html. Now a
|
|
292
324
|
// page, so no `view` key. Labelled Builders — 'roster' is the word
|
|
293
325
|
// modules/hall-ui/CLAUDE.md uses, but Builders is what a reader scans for.
|
|
294
|
-
{ id: 'roster', label: 'Builders', icon: '
|
|
326
|
+
{ id: 'roster', label: 'Builders', icon: 'roster', href: '/roster', gate: 'signed' },
|
|
295
327
|
// task 1003444: 'People', the cross-project directory, was deleted (owner
|
|
296
328
|
// call 2026-09-25, ADR 0337 D5). Cross-project discovery is the hub's, and
|
|
297
329
|
// /people redirects to /roster.
|
|
@@ -302,8 +334,8 @@
|
|
|
302
334
|
// are for the first-session newcomer above all — so the links follow it
|
|
303
335
|
// down to 'signed'. Leaving them at 'member' would have ungated the pages
|
|
304
336
|
// while still hiding them from the only readers the change was for.
|
|
305
|
-
{ id: 'primer', label: 'Primer', icon: '
|
|
306
|
-
{ id: 'ranks', label: 'Ranks', icon: '
|
|
337
|
+
{ id: 'primer', label: 'Primer', icon: 'primer', href: '/primer', gate: 'signed' },
|
|
338
|
+
{ id: 'ranks', label: 'Ranks', icon: 'ranks', href: '/ranks', gate: 'signed' },
|
|
307
339
|
// task 2175: /drachmae had no nav entry at all — reachable only from prose
|
|
308
340
|
// links since task 2174 moved the bounty rate card onto it. It is reference
|
|
309
341
|
// material (the reward schedule + how karma differs), so it belongs in Docs,
|
|
@@ -311,16 +343,16 @@
|
|
|
311
343
|
// currency: the page covers karma too, which is not currency, and "Economy"
|
|
312
344
|
// stays right whatever `branding.currency.label` is set to. Page is public
|
|
313
345
|
// like /ranks — 'signed' is the same cosmetic "show it once you're in".
|
|
314
|
-
{ id: 'economy', label: 'Economy', icon: '
|
|
346
|
+
{ id: 'economy', label: 'Economy', icon: 'economy', href: '/drachmae', gate: 'signed' },
|
|
315
347
|
// task 1004473: every skill and every bongos CLI verb, read live. Its page and
|
|
316
348
|
// both reads behind it are sign-in gated, so 'signed' is the real floor here.
|
|
317
|
-
{ id: 'commands', label: 'Commands', icon: '
|
|
318
|
-
{ id: 'diagrams', label: 'Diagrams', icon: '
|
|
319
|
-
{ id: 'atlas', label: 'Repo Atlas', icon: '
|
|
320
|
-
{ id: 'modules', label: 'Modules', icon: '
|
|
349
|
+
{ id: 'commands', label: 'Commands', icon: 'commands', href: '/commands', gate: 'signed' },
|
|
350
|
+
{ id: 'diagrams', label: 'Diagrams', icon: 'diagrams', href: '/diagrams', gate: 'signed' },
|
|
351
|
+
{ id: 'atlas', label: 'Repo Atlas', icon: 'atlas', href: '/atlas', gate: 'member' },
|
|
352
|
+
{ id: 'modules', label: 'Modules', icon: 'modules', href: '/modules', gate: 'member' },
|
|
321
353
|
// The rendered OpenAPI reference lives on the APEX (/docs), not this builders
|
|
322
354
|
// subdomain — external so the rewrite shim doesn't turn it into /builders/docs.
|
|
323
|
-
{ id: 'apidocs', label: 'API docs', icon: '
|
|
355
|
+
{ id: 'apidocs', label: 'API docs', icon: 'apidocs', href: '/docs', gate: 'signed', external: true },
|
|
324
356
|
] },
|
|
325
357
|
// Relabeled 'Archon' → 'Governance' in ADR 0157, then → 'Government' by ADR 0174: these pages are no longer
|
|
326
358
|
// Archon-only, so a group header reading "Archon" over items a Metic can open
|
|
@@ -331,12 +363,12 @@
|
|
|
331
363
|
// xenos floor) rather than this group's rank, and a group whose audience is
|
|
332
364
|
// Metic+ oversight could not honestly hold it. Everything below IS a rank
|
|
333
365
|
// surface, so this group's gate and audience are unchanged.
|
|
334
|
-
{ id: 'watch', label: 'Watch', icon: '
|
|
335
|
-
{ id: 'gate', label: 'Gate', icon: '
|
|
336
|
-
{ id: 'sessions', label: 'Sessions', icon: '
|
|
366
|
+
{ id: 'watch', label: 'Watch', icon: 'watch', href: '/watch', gate: 'government' },
|
|
367
|
+
{ id: 'gate', label: 'Gate', icon: 'gate', href: '/gate', gate: 'government' },
|
|
368
|
+
{ id: 'sessions', label: 'Sessions', icon: 'sessions', href: '/sessions', gate: 'government' },
|
|
337
369
|
// The project's own fundamentals (task 1003280, goal 1000072). Mirrors the
|
|
338
370
|
// server's requireGovernmentPage floor; cosmetic only, as every item here is.
|
|
339
|
-
{ id: 'project-settings', label: 'Project settings', icon: '
|
|
371
|
+
{ id: 'project-settings', label: 'Project settings', icon: 'settings', href: '/project-settings', gate: 'government' },
|
|
340
372
|
// The deploy door (task 1003159, ADR 0293). Gated 'archon' here to mirror the
|
|
341
373
|
// `core.pin.move` atom its page and both its routes check — cosmetic only, as
|
|
342
374
|
// every item in this list is; the wall is server-side.
|
|
@@ -346,18 +378,18 @@
|
|
|
346
378
|
// dropped where the module is off — and since task 1004296 it STAYS dropped: /deploy
|
|
347
379
|
// is the platform hall's page alone, and every other hall takes its update from
|
|
348
380
|
// Settings → Software update, whose Update button carries the hub hand-off.
|
|
349
|
-
{ id: 'deploy', label: 'Deploy', icon: '
|
|
381
|
+
{ id: 'deploy', label: 'Deploy', icon: 'deploy', href: '/deploy', gate: 'archon', module: 'provisioning render-deploy' },
|
|
350
382
|
// task 1001949: what each hosted project costs. 'government' mirrors the metic floor
|
|
351
383
|
// of `provisioning.fleet.manage`, the atom its page and its one read check (the
|
|
352
384
|
// Deploy precedent above); dropped where the provisioning module is not enabled.
|
|
353
|
-
{ id: 'fleet', label: 'Fleet costs', icon: '
|
|
385
|
+
{ id: 'fleet', label: 'Fleet costs', icon: 'fleet', href: '/fleet', gate: 'government', module: 'provisioning' },
|
|
354
386
|
// BV1.R113: the roles-and-permissions tab (ADR 0151). The one genuinely
|
|
355
387
|
// Archon-only item the 'government' gate comment anticipated — cosmetic
|
|
356
388
|
// only. The wall is the `government.manage` atom checked on each of the
|
|
357
389
|
// tab's nine data routes (route-rank-check.js); the page SHELL itself is
|
|
358
390
|
// metic+, because R16 made /government three rooms and only this one is
|
|
359
391
|
// the archon surface.
|
|
360
|
-
{ id: 'government', label: 'Permissions', icon: '
|
|
392
|
+
{ id: 'government', label: 'Permissions', icon: 'permissions', href: '/government', gate: 'archon' } // NOT 'Ranks': /ranks is the signed-in ladder explainer and two nav items
|
|
361
393
|
// reading 'Ranks' is the confusion this rename exists to remove. This tab
|
|
362
394
|
// is where a rank's permissions are granted, so it says what it does.,
|
|
363
395
|
] },
|
|
@@ -205,7 +205,7 @@
|
|
|
205
205
|
|
|
206
206
|
<script src="/builders/shell-trail.js?v=2026-10-08-city-tab"></script>
|
|
207
207
|
<script src="/builders/craft-mode.js?v=2026-10-08-city-tab"></script>
|
|
208
|
-
<script src="/builders/shell.js?v=2026-10-08-
|
|
208
|
+
<script src="/builders/shell.js?v=2026-10-08-glyph-fix"></script>
|
|
209
209
|
<script src="/builders/palette.js" defer></script>
|
|
210
210
|
<script src="/builders/city-draw.js?v=2026-10-04-pins1"></script>
|
|
211
211
|
<script src="/builders/street-draw.js?v=2026-10-04-pins1"></script>
|
|
@@ -184,7 +184,7 @@
|
|
|
184
184
|
|
|
185
185
|
<script src="/builders/shell-trail.js?v=2026-10-08-city-tab"></script>
|
|
186
186
|
<script src="/builders/craft-mode.js?v=2026-10-08-city-tab"></script>
|
|
187
|
-
<script src="/builders/shell.js?v=2026-10-08-
|
|
187
|
+
<script src="/builders/shell.js?v=2026-10-08-glyph-fix"></script>
|
|
188
188
|
<script src="/builders/palette.js" defer></script>
|
|
189
189
|
<script src="/builders/dom-utils.js?v=2026-06-23-contrib"></script>
|
|
190
190
|
<!-- The kit, for its ONE renderer: the full-size ship-time visual figure
|
|
@@ -109,10 +109,15 @@
|
|
|
109
109
|
return c / m;
|
|
110
110
|
}
|
|
111
111
|
|
|
112
|
+
// Where the hall lives: the builders. subdomain's own origin, else the apex /builders tree.
|
|
113
|
+
function hallBaseUrl() {
|
|
114
|
+
return location.hostname.startsWith('builders.') ? location.origin : `${location.origin}/builders`;
|
|
115
|
+
}
|
|
116
|
+
|
|
112
117
|
// The paste-into-your-agent prompt for one task.
|
|
113
118
|
function buildPromptForTask(t) {
|
|
114
119
|
const desc = deHashRefs((t.description || '').trim());
|
|
115
|
-
const hallBase =
|
|
120
|
+
const hallBase = hallBaseUrl();
|
|
116
121
|
const hallUrl = `${hallBase}#/task/${t.id}`;
|
|
117
122
|
return [
|
|
118
123
|
`I am working on Bongos task ${t.id} (${t.title}) — ${hallUrl}`,
|
|
@@ -123,6 +128,20 @@
|
|
|
123
128
|
].join('\n');
|
|
124
129
|
}
|
|
125
130
|
|
|
131
|
+
// task 1004587 (ADR 0366 D4) — the "Plan with Claude" prompt for one goal. When the
|
|
132
|
+
// goal has an unshipped spec task (source_ref goal-<id>-spec, made by the hall form)
|
|
133
|
+
// the prompt is that task's; otherwise it asks a session to run /goal-create against
|
|
134
|
+
// the goal. `todoTasks` is the goal's unshipped tasks. An extra, never a requirement.
|
|
135
|
+
function buildPlanPromptForGoal(goal, todoTasks) {
|
|
136
|
+
const spec = (todoTasks || []).find((t) => t.source_ref === `goal-${goal.id}-spec`);
|
|
137
|
+
if (spec) return buildPromptForTask(spec);
|
|
138
|
+
const hallBase = hallBaseUrl();
|
|
139
|
+
return [
|
|
140
|
+
`I want to plan Bongos goal ${goal.id} (${deHashRefs(goal.title || '')}) — ${hallBase}#/goal/${goal.id}`,
|
|
141
|
+
`Run /goal-create against this existing goal to add its seed tasks. Do not create a new goal.`,
|
|
142
|
+
].join('\n');
|
|
143
|
+
}
|
|
144
|
+
|
|
126
145
|
// Turn a batch-claim refusal into a sentence. THE DEFAULT MATTERS: an unmapped
|
|
127
146
|
// code falls through to the raw string, which is how the missing MODULE_CONFLICT
|
|
128
147
|
// case leaked a machine code into the Goals page UI. Adding a server-side reason
|
|
@@ -195,7 +214,7 @@
|
|
|
195
214
|
focusVersionDefault,
|
|
196
215
|
msOf, taskActivityMs, STALE_CHOICES, isStale,
|
|
197
216
|
priorityRank, touchesConflict, conflictPeers, conflictChipHtml, rankTagHtml, deHashRefs,
|
|
198
|
-
creditsPerMinute, buildPromptForTask, friendlyBatchReason,
|
|
217
|
+
creditsPerMinute, buildPromptForTask, buildPlanPromptForGoal, friendlyBatchReason,
|
|
199
218
|
};
|
|
200
219
|
if (typeof window !== 'undefined') window.OTBTaskLib = api;
|
|
201
220
|
if (typeof module !== 'undefined' && module.exports) module.exports = api;
|
|
@@ -56,7 +56,7 @@
|
|
|
56
56
|
time so the chrome exists before the deferred page scripts execute. -->
|
|
57
57
|
<script src="/builders/shell-trail.js?v=2026-10-08-city-tab"></script>
|
|
58
58
|
<script src="/builders/craft-mode.js?v=2026-10-08-city-tab"></script>
|
|
59
|
-
<script src="/builders/shell.js?v=2026-10-08-
|
|
59
|
+
<script src="/builders/shell.js?v=2026-10-08-glyph-fix"></script>
|
|
60
60
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
61
61
|
<script src="/builders/palette.js" defer></script>
|
|
62
62
|
<!-- shared frontend toolkit (window.OTB) — loaded FIRST so task.js can pull from it. -->
|
|
@@ -140,7 +140,7 @@
|
|
|
140
140
|
|
|
141
141
|
<script src="/builders/shell-trail.js?v=2026-10-08-city-tab"></script>
|
|
142
142
|
<script src="/builders/craft-mode.js?v=2026-10-08-city-tab"></script>
|
|
143
|
-
<script src="/builders/shell.js?v=2026-10-08-
|
|
143
|
+
<script src="/builders/shell.js?v=2026-10-08-glyph-fix"></script>
|
|
144
144
|
<script src="/builders/palette.js" defer></script>
|
|
145
145
|
|
|
146
146
|
<script src="/builders/idea-objects.js?v=2026-09-27-split"></script>
|
|
@@ -144,7 +144,7 @@
|
|
|
144
144
|
|
|
145
145
|
<script src="/builders/shell-trail.js?v=2026-10-08-city-tab"></script>
|
|
146
146
|
<script src="/builders/craft-mode.js?v=2026-10-08-city-tab"></script>
|
|
147
|
-
<script src="/builders/shell.js?v=2026-10-08-
|
|
147
|
+
<script src="/builders/shell.js?v=2026-10-08-glyph-fix"></script>
|
|
148
148
|
<script src="/builders/palette.js" defer></script>
|
|
149
149
|
<script src="/builders/dom-utils.js?v=2026-06-23-contrib"></script>
|
|
150
150
|
<script src="/builders/brand-holes.js?v=2026-09-29-aqfix"></script>
|
|
@@ -167,7 +167,7 @@
|
|
|
167
167
|
chrome exists before the page script runs. -->
|
|
168
168
|
<script src="/builders/shell-trail.js?v=2026-10-08-city-tab"></script>
|
|
169
169
|
<script src="/builders/craft-mode.js?v=2026-10-08-city-tab"></script>
|
|
170
|
-
<script src="/builders/shell.js?v=2026-10-08-
|
|
170
|
+
<script src="/builders/shell.js?v=2026-10-08-glyph-fix"></script>
|
|
171
171
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
172
172
|
<script src="/builders/palette.js" defer></script>
|
|
173
173
|
<!-- shared frontend toolkit (window.OTB) — loaded before watch.js, which pulls from it. -->
|
|
@@ -173,7 +173,7 @@
|
|
|
173
173
|
time so the chrome exists before the deferred page scripts execute. -->
|
|
174
174
|
<script src="/builders/shell-trail.js?v=2026-10-08-city-tab"></script>
|
|
175
175
|
<script src="/builders/craft-mode.js?v=2026-10-08-city-tab"></script>
|
|
176
|
-
<script src="/builders/shell.js?v=2026-10-08-
|
|
176
|
+
<script src="/builders/shell.js?v=2026-10-08-glyph-fix"></script>
|
|
177
177
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
178
178
|
<script src="/builders/palette.js" defer></script>
|
|
179
179
|
<!-- shared frontend toolkit (window.OTB) — loaded FIRST so work.js can pull from it. -->
|
package/package-lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.21.
|
|
3
|
+
"version": "1.21.79",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@bongos/core",
|
|
9
|
-
"version": "1.21.
|
|
9
|
+
"version": "1.21.79",
|
|
10
10
|
"license": "AGPL-3.0-or-later",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"express": "^4.21.2",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.21.
|
|
3
|
+
"version": "1.21.79",
|
|
4
4
|
"description": "Cloud Bongos — the AI-first build platform core (GDS + platform surfaces + module system), installed as a versioned dependency (ADR 0108).",
|
|
5
5
|
"license": "AGPL-3.0-or-later",
|
|
6
6
|
"main": "src/platform-server.js",
|
package/release-notes.json
CHANGED
|
@@ -9272,5 +9272,29 @@
|
|
|
9272
9272
|
"id": "1004638",
|
|
9273
9273
|
"text": "The hall sidebar now has a solid background, so the page title no longer shows through the Builders' Hall name in light mode."
|
|
9274
9274
|
}
|
|
9275
|
+
],
|
|
9276
|
+
"1.21.78": [
|
|
9277
|
+
{
|
|
9278
|
+
"id": "1004587",
|
|
9279
|
+
"text": "Goal pages now have a Plan with Claude button that copies a ready-made planning prompt."
|
|
9280
|
+
}
|
|
9281
|
+
],
|
|
9282
|
+
"1.21.79": [
|
|
9283
|
+
{
|
|
9284
|
+
"id": "1004618",
|
|
9285
|
+
"text": "Dry-ran the hall-goal spec flow end to end; it works. The skill now documents the two rough edges found."
|
|
9286
|
+
},
|
|
9287
|
+
{
|
|
9288
|
+
"id": "1004639",
|
|
9289
|
+
"text": "The hall's new sidebar icons were stuck: one picture's drawing contained a run of numbers that looked like an internet address, so the release safety scan refused to package the update. The picture is redrawn identically witho"
|
|
9290
|
+
},
|
|
9291
|
+
{
|
|
9292
|
+
"id": "1003793",
|
|
9293
|
+
"text": "Store modules now get an Install score from real installs: each install or update quietly reports whether it worked (just yes/no and a short reason, nothing personal). Module authors can't pad their own score, and anyone can s"
|
|
9294
|
+
},
|
|
9295
|
+
{
|
|
9296
|
+
"id": "1004626",
|
|
9297
|
+
"text": "Every tab in the hall sidebar now has its own icon. Before, nineteen tabs shared just four pictures - all nine Government pages wore the same shield and every Docs page the same sheet of paper - so the collapsed sidebar was ha"
|
|
9298
|
+
}
|
|
9275
9299
|
]
|
|
9276
9300
|
}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// scripts/gds/module-assess-installs.js — the Install and Reliability parts of a
|
|
3
|
+
// module's assessment, worked out from what real installs report (task 1003793,
|
|
4
|
+
// ADR 0343 D1; reports from core_273, signal rows into core_265).
|
|
5
|
+
//
|
|
6
|
+
// WHY these two parts. Tests and Docs are things an author writes; Install and
|
|
7
|
+
// Reliability are what happens when OTHER people run the module, which is the
|
|
8
|
+
// one signal an author cannot fake. So two rules keep it honest:
|
|
9
|
+
// - the module's own author is never counted, and
|
|
10
|
+
// - each holder counts once per version for Install (core_273 keeps only their
|
|
11
|
+
// latest attempt), so one account retrying cannot outweigh many — and neither part is scored at
|
|
12
|
+
// all until MIN_HOLDERS different holders have reported (`pending` before,
|
|
13
|
+
// never a number resting on one friend's install).
|
|
14
|
+
//
|
|
15
|
+
// Install = round(100 × holders whose latest install worked / holders).
|
|
16
|
+
// Reliability = round(100 × holder-days with zero runtime errors / holder-days
|
|
17
|
+
// reported), over the last ERROR_DAY_WINDOW days.
|
|
18
|
+
// No reports → no_data (ADR 0343 D3: left out of the average, never a zero).
|
|
19
|
+
//
|
|
20
|
+
// What the reports may contain is fixed by the owner's privacy posture and
|
|
21
|
+
// enforced at intake (src/bongos/module-usage-reports.js): counts and short codes
|
|
22
|
+
// only, and nothing from a holder who switched reporting off.
|
|
23
|
+
//
|
|
24
|
+
// A new signal row is appended only when a part's result actually moves, so the
|
|
25
|
+
// append-only history (core_265) is not one row per report.
|
|
26
|
+
//
|
|
27
|
+
// node scripts/gds/module-assess-installs.js <store_module_versions.id> re-score + print
|
|
28
|
+
|
|
29
|
+
const { ERROR_DAY_WINDOW } = require('../../src/bongos/module-usage-reports');
|
|
30
|
+
|
|
31
|
+
const MIN_HOLDERS = 3;
|
|
32
|
+
|
|
33
|
+
// The not-yet-scored outcomes, shared by both parts. Pure.
|
|
34
|
+
function notYet(holders, what) {
|
|
35
|
+
if (!holders) return { outcome: 'no_data', score: null, sample_size: 0, reason: `no ${what} reported yet` };
|
|
36
|
+
return {
|
|
37
|
+
outcome: 'pending', score: null, sample_size: holders,
|
|
38
|
+
reason: `${holders} of the ${MIN_HOLDERS} installs needed have reported ${what}`,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Install from each holder's LATEST attempt ({ ok, error_code } per holder). Pure.
|
|
43
|
+
function assessInstall(latest = []) {
|
|
44
|
+
const n = latest.length;
|
|
45
|
+
const failedCodes = {};
|
|
46
|
+
for (const r of latest) if (!r.ok) failedCodes[r.error_code] = (failedCodes[r.error_code] || 0) + 1;
|
|
47
|
+
const worked = n - Object.values(failedCodes).reduce((a, b) => a + b, 0);
|
|
48
|
+
const detail = { holders: n, worked, failed: n - worked, failed_codes: failedCodes };
|
|
49
|
+
if (n < MIN_HOLDERS) return { ...notYet(n, 'an install'), detail };
|
|
50
|
+
return {
|
|
51
|
+
outcome: 'scored', score: Math.round((100 * worked) / n), sample_size: n,
|
|
52
|
+
reason: `${worked} of ${n} installs worked`, detail,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Reliability from daily counts ({ holder_kind, holder_ref, error_count } per
|
|
57
|
+
// holder-day). sample_size is distinct holders, the same unit as Install. Pure.
|
|
58
|
+
function assessReliability(days = []) {
|
|
59
|
+
const holders = new Set(days.map((d) => `${d.holder_kind}:${d.holder_ref}`)).size;
|
|
60
|
+
const clean = days.filter((d) => Number(d.error_count) === 0).length;
|
|
61
|
+
const errors = days.reduce((sum, d) => sum + Number(d.error_count), 0);
|
|
62
|
+
const detail = { holders, days: days.length, clean_days: clean, errors, window_days: ERROR_DAY_WINDOW };
|
|
63
|
+
if (holders < MIN_HOLDERS) return { ...notYet(holders, 'runtime errors'), detail };
|
|
64
|
+
return {
|
|
65
|
+
outcome: 'scored', score: Math.round((100 * clean) / days.length), sample_size: holders,
|
|
66
|
+
reason: `${clean} of ${days.length} days ran with no errors, across ${holders} installs`, detail,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// The author's own reports never count. Shared by both reads.
|
|
71
|
+
const NOT_AUTHOR = `NOT (r.holder_kind = 'builder' AND r.holder_ref = m.author_id::text)`;
|
|
72
|
+
|
|
73
|
+
async function readReports(versionId, pool) {
|
|
74
|
+
const { rows: latest } = await pool.query(
|
|
75
|
+
`SELECT r.ok, r.error_code
|
|
76
|
+
FROM module_install_reports r
|
|
77
|
+
JOIN store_module_versions v ON v.id = r.version_id
|
|
78
|
+
JOIN store_modules m ON m.module_key = v.module_key
|
|
79
|
+
WHERE r.version_id = $1 AND ${NOT_AUTHOR}`, [versionId]);
|
|
80
|
+
const { rows: days } = await pool.query(
|
|
81
|
+
`SELECT r.holder_kind, r.holder_ref, r.error_count
|
|
82
|
+
FROM module_runtime_error_counts r
|
|
83
|
+
JOIN store_module_versions v ON v.id = r.version_id
|
|
84
|
+
JOIN store_modules m ON m.module_key = v.module_key
|
|
85
|
+
WHERE r.version_id = $1 AND r.day >= current_date - $2::int AND ${NOT_AUTHOR}`,
|
|
86
|
+
[versionId, ERROR_DAY_WINDOW]);
|
|
87
|
+
return { latest, days };
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const sameResult = (cur, s) => !!cur && cur.outcome === s.outcome
|
|
91
|
+
&& (cur.score == null ? null : Number(cur.score)) === s.score
|
|
92
|
+
&& Number(cur.sample_size) === s.sample_size;
|
|
93
|
+
|
|
94
|
+
// Re-score Install and Reliability for one version from its reports, append a
|
|
95
|
+
// signal row for each part whose result moved, then re-compose the overall score
|
|
96
|
+
// (module-assess-score.js). Resolves { ok, written: [part…], score }.
|
|
97
|
+
async function recordInstallSignals(versionId, { db } = {}) {
|
|
98
|
+
const pool = db || require('../../src/bongos/pool').pool;
|
|
99
|
+
const { latest, days } = await readReports(versionId, pool);
|
|
100
|
+
const next = { install: assessInstall(latest), reliability: assessReliability(days) };
|
|
101
|
+
|
|
102
|
+
const { rows: current } = await pool.query(
|
|
103
|
+
`SELECT DISTINCT ON (part) part, outcome, score, sample_size
|
|
104
|
+
FROM module_assessment_signals WHERE version_id = $1 AND part IN ('install', 'reliability')
|
|
105
|
+
ORDER BY part, measured_at DESC, id DESC`, [versionId]);
|
|
106
|
+
const byPart = Object.fromEntries(current.map((r) => [r.part, r]));
|
|
107
|
+
|
|
108
|
+
const written = [];
|
|
109
|
+
for (const part of ['install', 'reliability']) {
|
|
110
|
+
const s = next[part];
|
|
111
|
+
if (sameResult(byPart[part], s)) continue;
|
|
112
|
+
await pool.query(
|
|
113
|
+
`INSERT INTO module_assessment_signals (version_id, part, outcome, score, sample_size, reason, detail)
|
|
114
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7)`,
|
|
115
|
+
[versionId, part, s.outcome, s.score, s.sample_size, s.reason, JSON.stringify(s.detail)]);
|
|
116
|
+
written.push(part);
|
|
117
|
+
}
|
|
118
|
+
const { score } = written.length
|
|
119
|
+
? await require('./module-assess-score').recomposeScore(versionId, { db: pool })
|
|
120
|
+
: { score: null };
|
|
121
|
+
return { ok: true, written, signals: next, score };
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
async function main(argv) {
|
|
125
|
+
const id = argv[0];
|
|
126
|
+
if (!/^\d+$/.test(String(id || ''))) {
|
|
127
|
+
console.error('usage: node scripts/gds/module-assess-installs.js <store_module_versions.id>');
|
|
128
|
+
return 2;
|
|
129
|
+
}
|
|
130
|
+
const res = await recordInstallSignals(id);
|
|
131
|
+
for (const part of ['install', 'reliability']) {
|
|
132
|
+
const s = res.signals[part];
|
|
133
|
+
console.log(`${part}: ${s.outcome}${s.score != null ? ` ${s.score}/100` : ''} — ${s.reason}${res.written.includes(part) ? '' : ' (unchanged)'}`);
|
|
134
|
+
}
|
|
135
|
+
await require('./module-assess-score').printRecomposed(id);
|
|
136
|
+
return 0;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (require.main === module) {
|
|
140
|
+
main(process.argv.slice(2)).then((code) => { process.exitCode = code; }, (e) => { console.error(e.stack || e.message); process.exitCode = 1; });
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
module.exports = { MIN_HOLDERS, assessInstall, assessReliability, recordInstallSignals };
|
package/scripts/gds/module.js
CHANGED
|
@@ -1138,14 +1138,34 @@ async function fetchVerified(key, { wanted, call, core, log, errlog, verb, shoul
|
|
|
1138
1138
|
|
|
1139
1139
|
const artifact = require('./module-artifact');
|
|
1140
1140
|
const verified = await artifact.verifyModuleArtifact(dl.buf, { key, includeFiles: true });
|
|
1141
|
-
if (!verified.ok)
|
|
1141
|
+
if (!verified.ok) {
|
|
1142
|
+
await reportInstall(call, key, described.version, 'verify_failed');
|
|
1143
|
+
return fail(1, `REFUSING: the downloaded tarball failed verification (${verified.code}): ${verified.message}`);
|
|
1144
|
+
}
|
|
1142
1145
|
if (verified.version !== described.version || verified.tarballSha256 !== described.tarball_sha256 || verified.treeSha256 !== described.tree_sha256) {
|
|
1146
|
+
await reportInstall(call, key, described.version, 'hash_mismatch');
|
|
1143
1147
|
return fail(1, `REFUSING: the download is not the ${described.version} the store described (version or hash differs).`);
|
|
1144
1148
|
}
|
|
1145
1149
|
log(` ✓ verified ${verified.files.length} file(s), ${verified.bytes} bytes — tree ${verified.treeSha256.slice(0, 12)}…`);
|
|
1146
1150
|
return { ok: true, described, verified };
|
|
1147
1151
|
}
|
|
1148
1152
|
|
|
1153
|
+
// Tell the store whether an install or update of this exact version worked (task
|
|
1154
|
+
// 1003793): it feeds the module's Install score, the part an author cannot fake.
|
|
1155
|
+
// Counts only — yes, or a short code saying where it failed; never a message, a
|
|
1156
|
+
// path or anything about this machine (owner's privacy posture, 2026-10-01). The
|
|
1157
|
+
// store drops it if the caller switched reporting off in Settings, so there is no
|
|
1158
|
+
// client-side switch to keep in step. Best-effort and silent: a report that does
|
|
1159
|
+
// not arrive changes nothing about the install. Only failures that are the
|
|
1160
|
+
// module's own count: a refused or unreachable store, or a core the module does
|
|
1161
|
+
// not support, says nothing about the module and is not reported.
|
|
1162
|
+
async function reportInstall(call, key, version, errorCode = null) {
|
|
1163
|
+
const body = errorCode ? { ok: false, error_code: errorCode } : { ok: true };
|
|
1164
|
+
try {
|
|
1165
|
+
await call('POST', `/store/modules/${encodeURIComponent(key)}/versions/${encodeURIComponent(version)}/install-report`, { json: body });
|
|
1166
|
+
} catch { /* best-effort */ }
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1149
1169
|
// Tell the store which exact version was placed. A failure here never undoes the
|
|
1150
1170
|
// placement: the files are good, and the next install or update records it.
|
|
1151
1171
|
async function recordPlaced(key, version, { call, errlog, verb }) {
|
|
@@ -1190,10 +1210,12 @@ async function cmdInstall(args, {
|
|
|
1190
1210
|
if (!got.ok) return got.code;
|
|
1191
1211
|
|
|
1192
1212
|
try { placeModule(got.verified.files, target); }
|
|
1193
|
-
catch (e) { errlog(`bongos module install: could not place the module — ${e.message}`); return 1; }
|
|
1213
|
+
catch (e) { await reportInstall(call, key, got.described.version, 'place_failed'); errlog(`bongos module install: could not place the module — ${e.message}`); return 1; }
|
|
1194
1214
|
log(` ✓ placed at ${target}`);
|
|
1195
1215
|
|
|
1196
|
-
|
|
1216
|
+
const recorded = await recordPlaced(key, got.described.version, { call, errlog, verb: 'install' });
|
|
1217
|
+
await reportInstall(call, key, got.described.version);
|
|
1218
|
+
if (!recorded) return 1;
|
|
1197
1219
|
log(`\n✓ Installed "${key}" ${got.described.version}. It is not switched on: enable it from the hall's Modules tab when you want it.`);
|
|
1198
1220
|
const howto = howtoLink(key, got.described.version, got.verified.files, call.apiBase);
|
|
1199
1221
|
if (howto) log(` How to use it: ${howto}`);
|
|
@@ -1276,10 +1298,12 @@ async function cmdUpdate(args, {
|
|
|
1276
1298
|
if (!got.ok) return got.code;
|
|
1277
1299
|
|
|
1278
1300
|
try { replaceModule(got.verified.files, target); }
|
|
1279
|
-
catch (e) { errlog(`bongos module update: could not replace the module, ${installed} is still in place — ${e.message}`); return 1; }
|
|
1301
|
+
catch (e) { await reportInstall(call, key, got.described.version, 'place_failed'); errlog(`bongos module update: could not replace the module, ${installed} is still in place — ${e.message}`); return 1; }
|
|
1280
1302
|
log(` ✓ replaced ${installed} with ${got.described.version} at ${target}`);
|
|
1281
1303
|
|
|
1282
|
-
|
|
1304
|
+
const recorded = await recordPlaced(key, got.described.version, { call, errlog, verb: 'update' });
|
|
1305
|
+
await reportInstall(call, key, got.described.version);
|
|
1306
|
+
if (!recorded) return 1;
|
|
1283
1307
|
log(`\n✓ Updated "${key}" ${installed} → ${got.described.version}. Its on/off switch is unchanged.`);
|
|
1284
1308
|
return 0;
|
|
1285
1309
|
}
|