@bongos/core 1.19.837 → 1.19.839
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 +62 -32
- package/config/branding.neutral.json +2 -1
- package/docs/adr/0302-the-third-identical-failure-stops-the-session-and-asks.md +74 -0
- package/docs/adr/README.md +1 -0
- package/docs/branding-contract.md +1 -1
- package/docs/branding-fill-prompt.md +1 -1
- package/docs/copy-inventory.md +3 -3
- package/docs/copy-registry.json +5 -5
- package/docs/file-map.md +2 -0
- package/docs/module-api-changelog.md +4 -0
- package/modules/agents/migrations/agents_003_output_ref_idx.sql +41 -0
- package/modules/agents/module.json +1 -1
- package/modules/agents/pollers/event-router.js +737 -0
- package/modules/discord/module.json +1 -1
- package/modules/discord/routes/discord.js +25 -0
- package/modules/status-ui/public/index.html +2 -1
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/scripts/gds/fitness-checks-identity.js +9 -1
- package/scripts/gds/fitness.js +34 -1
- package/scripts/gds/plain-cards.js +2 -0
- package/scripts/status-mirror-sync.js +6 -2
- package/src/bongos/serve-internal.js +9 -2
- package/src/branding.js +22 -2
- package/src/module-api.js +1 -1
- package/tests/agents_event_router.mjs +523 -0
- package/tests/fitness.mjs +47 -0
- package/tests/hooks_drain.mjs +1 -1
- package/tests/host_topology_skips.mjs +1 -0
- package/tests/status_company_attribution.mjs +60 -0
- package/tests/stuck_detector.mjs +233 -0
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"pollers": ["discord-bot"],
|
|
12
12
|
"uiSections": []
|
|
13
13
|
},
|
|
14
|
-
"provides": ["discord.isLinked"],
|
|
14
|
+
"provides": ["discord.isLinked", "discord.post", "discord.hasChannel"],
|
|
15
15
|
"consumes": ["ideas.capture", "government", "lifecycle"],
|
|
16
16
|
"dependencies": { "discord.js": "^14.26.4" }
|
|
17
17
|
}
|
|
@@ -22,6 +22,7 @@ const db = require('../db');
|
|
|
22
22
|
const shipBroadcast = require('../ship-broadcast');
|
|
23
23
|
const boardBroadcast = require('../board-broadcast');
|
|
24
24
|
const roleSync = require('../discord-role-sync');
|
|
25
|
+
const webhook = require('../webhook');
|
|
25
26
|
|
|
26
27
|
// The module's kernel seams, registered when this factory runs (the loader runs
|
|
27
28
|
// it at boot, already gated to enabled instances — ADR 0083 / BV1.R47). They are
|
|
@@ -60,6 +61,30 @@ function registerSeams() {
|
|
|
60
61
|
return { guildId, roleIds: roleId ? [roleId] : [] };
|
|
61
62
|
});
|
|
62
63
|
}
|
|
64
|
+
// `discord.post` — the GENERIC outbound line (task 1002494). Until now every
|
|
65
|
+
// post this module makes was driven by a listener on an event it already knew
|
|
66
|
+
// about, so a module with something to say and no event of its own — the
|
|
67
|
+
// agents event-router, whose findings declare a `discord` sink — had nothing
|
|
68
|
+
// to deliver to, and could not require a sibling module to get one (ADR 0083).
|
|
69
|
+
//
|
|
70
|
+
// THE CALLER NAMES A CHANNEL KEY, NEVER A URL. `webhook.js` owns the
|
|
71
|
+
// key→webhook allowlist and throws on an unknown key, so this port cannot be
|
|
72
|
+
// used to post anywhere the operator has not configured — and `hasWebhook`
|
|
73
|
+
// lets a caller check that BEFORE doing expensive work it would then throw
|
|
74
|
+
// away. Still outbound only (ADR 0033 §8): nothing here reads from Discord,
|
|
75
|
+
// and nothing posted through it carries authority.
|
|
76
|
+
if (!api.hasProvider('discord.post')) {
|
|
77
|
+
api.registerProvider('discord.post', async ({ channel, text } = {}) => {
|
|
78
|
+
if (!webhook.hasWebhook(channel)) {
|
|
79
|
+
return { posted: false, reason: 'no_webhook_for_channel', channel: channel || null };
|
|
80
|
+
}
|
|
81
|
+
await webhook.postWebhook(channel, String(text == null ? '' : text));
|
|
82
|
+
return { posted: true, channel };
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
if (!api.hasProvider('discord.hasChannel')) {
|
|
86
|
+
api.registerProvider('discord.hasChannel', (channel) => webhook.hasWebhook(channel));
|
|
87
|
+
}
|
|
63
88
|
if (_shipListenerOff) _shipListenerOff();
|
|
64
89
|
_shipListenerOff = api.on('task.shipped', (task) => shipBroadcast.postShipBroadcast(task));
|
|
65
90
|
if (_strandListenerOff) _strandListenerOff();
|
|
@@ -352,7 +352,8 @@
|
|
|
352
352
|
<svg><use href="#orn-diamond"/></svg>
|
|
353
353
|
</div>
|
|
354
354
|
<p class="foot__seal">By order of the Senate</p>
|
|
355
|
-
|
|
355
|
+
<!-- task 1002853: every company field is optional, so each clause drops whole when its pack field is empty -->
|
|
356
|
+
<!--brand-if:companyName--><p>{{worldName}} is the work of <!--brand-if:companySite--><a href="{{companySite}}" target="_blank" rel="noopener"><!--/brand-if:companySite--><strong>{{companyName}}</strong><!--brand-if:companySite--></a><!--/brand-if:companySite--><!--brand-if:companyTagline-->, {{companyTagline}}<!--/brand-if:companyTagline-->.</p><!--/brand-if:companyName-->
|
|
356
357
|
<p>{{Currency}} are wrought by the lawmen's review; redemption awaits their seal.</p>
|
|
357
358
|
</footer>
|
|
358
359
|
|
package/package-lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.19.
|
|
3
|
+
"version": "1.19.839",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@bongos/core",
|
|
9
|
-
"version": "1.19.
|
|
9
|
+
"version": "1.19.839",
|
|
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.19.
|
|
3
|
+
"version": "1.19.839",
|
|
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",
|
|
@@ -41,6 +41,9 @@ const { CORE_ROOT, MODULE_ROOTS, REPO_ROOT, rel } = require('./fitness-lib.js');
|
|
|
41
41
|
const PROMPT_IDENTITY_LITERALS = [
|
|
42
42
|
/off the boats/i,
|
|
43
43
|
/amazonprimea/i,
|
|
44
|
+
// The host's operating company (task 1002853): the scan already covered the status
|
|
45
|
+
// page's HTML, but with only the product listed its footer credited this company for years.
|
|
46
|
+
/hermeslines/i,
|
|
44
47
|
];
|
|
45
48
|
// Scan roots (recursive) + kernel-prompt CLIs that live outside them.
|
|
46
49
|
// scripts/gds joined at task 1002427: the builder CLI was UNSCANNED, which is how
|
|
@@ -101,7 +104,7 @@ const IDENTITY_SCAN_EXTRA_FILES = [];
|
|
|
101
104
|
const IDENTITY_SCAN_ALLOWED = {
|
|
102
105
|
// task 1003206 moved Check 13's own regexes out of fitness.js into THIS file
|
|
103
106
|
// when the monolith was split, so the detector's self-carve-out moved with them.
|
|
104
|
-
'scripts/gds/fitness-checks-identity.js':
|
|
107
|
+
'scripts/gds/fitness-checks-identity.js': 3,
|
|
105
108
|
'scripts/gds/package-core.js': 1,
|
|
106
109
|
// audit-replay.sh + db-backup-{nightly,offsite}.sh were carved out here as
|
|
107
110
|
// "host-pinned ops"; task 1002757 RETIRED that justification for them — each
|
|
@@ -125,6 +128,11 @@ const IDENTITY_SCAN_ALLOWED = {
|
|
|
125
128
|
'scripts/gds/seed-productization-tasks.js': 6,
|
|
126
129
|
'scripts/gds/seed-provisioning-tasks.js': 2,
|
|
127
130
|
'scripts/gds/version-close.js': 1,
|
|
131
|
+
// task 1002853 (the company literal joined the list): audit-authorship.sh is itself a
|
|
132
|
+
// detector — its ops allowlist names the founder's commit address so it can exempt it; grade-smoke.js
|
|
133
|
+
// is a protected grader probe whose fixture task prose names the company, the seed-*.js rationale.
|
|
134
|
+
'scripts/gds/audit-authorship.sh': 1,
|
|
135
|
+
'scripts/gds/grade-smoke.js': 1,
|
|
128
136
|
};
|
|
129
137
|
// `.sh` joined at task 1002567: the extension set was `.js/.html/.css`, so NO shell
|
|
130
138
|
// script had ever been scanned — which is how scripts/gds/lib.sh kept a hardcoded
|
package/scripts/gds/fitness.js
CHANGED
|
@@ -113,6 +113,12 @@ const ROOT_CHAR_HARD_MAX = 42000; // ~10.5K tokens — the always-loaded file mu
|
|
|
113
113
|
const ROOT_CHAR_TARGET = 32000; // ~8K tokens — aspirational, mirrors the ~200-line aim.
|
|
114
114
|
const estTokens = (n) => Math.round(n / 4); // ~4 chars/token for markdown prose.
|
|
115
115
|
|
|
116
|
+
// Where the charter BODY lives since task 1003687. It is guidance for EDITING a
|
|
117
|
+
// CLAUDE.md, so it is paths-scoped (`paths:` frontmatter, same mechanism as
|
|
118
|
+
// .claude/rules/hook-authoring.md) and costs nothing in the sessions — the large
|
|
119
|
+
// majority — that never open one. The kernel keeps the heading and a pointer.
|
|
120
|
+
const CHARTER_RULE_REL = '.claude/rules/claudemd-authoring.md';
|
|
121
|
+
|
|
116
122
|
function checkClaudeMdBudget(opts = {}) {
|
|
117
123
|
const p = path.join(INSTANCE_ROOT, 'CLAUDE.md'); // the root CLAUDE.md is INSTANCE content (materialized per instance; ADR 0108)
|
|
118
124
|
const text = opts.text != null ? opts.text : fs.readFileSync(p, 'utf8');
|
|
@@ -133,8 +139,35 @@ function checkClaudeMdBudget(opts = {}) {
|
|
|
133
139
|
}
|
|
134
140
|
if (!text.includes('## Working with this file (content charter)')) {
|
|
135
141
|
violations.push('CLAUDE.md is missing the content-charter section (ADR 0061) — the per-line test + "what goes where" table must stay.');
|
|
142
|
+
} else {
|
|
143
|
+
// Task 1003687 relocated the charter BODY to a paths-scoped rule, so the kernel
|
|
144
|
+
// keeps the heading and a pointer. That makes the heading cheap to fake: a section
|
|
145
|
+
// with nothing behind it satisfies a substring check while the rules it names are
|
|
146
|
+
// gone. So the charter must resolve to something — either inline here, or through a
|
|
147
|
+
// pointer whose target actually exists and still carries the two rules + the table.
|
|
148
|
+
const namesRule = text.includes(CHARTER_RULE_REL);
|
|
149
|
+
const inlineCharter = text.includes('Per-line test:');
|
|
150
|
+
if (namesRule) {
|
|
151
|
+
const rulePath = path.join(INSTANCE_ROOT, CHARTER_RULE_REL); // .claude/ is materialized INSTANCE content (ADR 0108 §3)
|
|
152
|
+
if (!fs.existsSync(rulePath)) {
|
|
153
|
+
violations.push(`CLAUDE.md points its content charter at ${CHARTER_RULE_REL}, but that file does not exist — a pointer to nothing is worse than no pointer (ADR 0061 / task 1003687).`);
|
|
154
|
+
} else {
|
|
155
|
+
const rule = fs.readFileSync(rulePath, 'utf8');
|
|
156
|
+
const missing = [];
|
|
157
|
+
if (!/^---\r?\n[\s\S]*?\bpaths:[\s\S]*?["']\*\*\/CLAUDE\.md["'][\s\S]*?^---$/m.test(rule)) {
|
|
158
|
+
missing.push('`paths:` frontmatter matching `**/CLAUDE.md` (without it the rule never loads, and the charter is simply gone)');
|
|
159
|
+
}
|
|
160
|
+
if (!rule.includes('Per-line test:')) missing.push('the per-line test');
|
|
161
|
+
if (!rule.includes('## What goes where')) missing.push('the "what goes where" table');
|
|
162
|
+
if (missing.length) {
|
|
163
|
+
violations.push(`${CHARTER_RULE_REL} is missing ${missing.join(', ')} — the kernel delegates the charter to it, so the charter is only as real as that file (ADR 0061 / task 1003687).`);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
} else if (!inlineCharter) {
|
|
167
|
+
violations.push('The CLAUDE.md content-charter section is hollow — it has the heading but neither the per-line test inline nor a pointer to `' + CHARTER_RULE_REL + '`. The heading exists to make the rules findable; on its own it only satisfies this check (ADR 0061).');
|
|
168
|
+
}
|
|
136
169
|
}
|
|
137
|
-
return { name: 'root CLAUDE.md budget + charter', ok: violations.length === 0, hardFail: violations.length > 0, violations, warnings, note: `≤ ${ROOT_HARD_MAX} lines / ${ROOT_CHAR_HARD_MAX} chars (~${estTokens(ROOT_CHAR_HARD_MAX)} tok); targets ~${ROOT_TARGET} lines / ~${estTokens(ROOT_CHAR_TARGET)} tok; charter
|
|
170
|
+
return { name: 'root CLAUDE.md budget + charter', ok: violations.length === 0, hardFail: violations.length > 0, violations, warnings, note: `≤ ${ROOT_HARD_MAX} lines / ${ROOT_CHAR_HARD_MAX} chars (~${estTokens(ROOT_CHAR_HARD_MAX)} tok); targets ~${ROOT_TARGET} lines / ~${estTokens(ROOT_CHAR_TARGET)} tok; charter resolves (inline or via ${CHARTER_RULE_REL}).` };
|
|
138
171
|
}
|
|
139
172
|
|
|
140
173
|
// ============================================================================
|
|
@@ -333,6 +333,8 @@ function cardFor(code) {
|
|
|
333
333
|
// AGENT SURFACE ONLY: a card is written in the agent's voice ("I will…") for a
|
|
334
334
|
// session to relay. A person running the `bongos` CLI reads the text above it directly,
|
|
335
335
|
// and several cards name slash commands that do not exist there (tests/cli_surface.mjs).
|
|
336
|
+
// The labels below are READ BACK by .claude/hooks/stuck-detector.js as its failure
|
|
337
|
+
// signature (task 1003990); tests/stuck_detector.mjs reds if they drift.
|
|
336
338
|
function plainCardLines(code, env = process.env) {
|
|
337
339
|
const c = isTerminal(env) ? null : cardFor(code);
|
|
338
340
|
if (!c) return [];
|
|
@@ -48,7 +48,7 @@ const { execFileSync } = require('node:child_process');
|
|
|
48
48
|
|
|
49
49
|
const REPO_ROOT = path.resolve(__dirname, '..');
|
|
50
50
|
const SRC = path.join(REPO_ROOT, 'modules', 'status-ui', 'public');
|
|
51
|
-
const { branding, clientBranding } = require('../src/branding');
|
|
51
|
+
const { branding, clientBranding, applyBrandConditionals } = require('../src/branding');
|
|
52
52
|
|
|
53
53
|
// Stamp instance-identity tokens into the mirror HTML. The off-box mirror is
|
|
54
54
|
// served by a static Pages host, NOT the branding-injecting Node server — so this
|
|
@@ -63,15 +63,19 @@ function brandTokens(html, brand = {}, publicOrigin = '', buildersOrigin = '') {
|
|
|
63
63
|
const tagline = (brand.copy && brand.copy.tagline) || '';
|
|
64
64
|
const cur = (brand.currency && brand.currency.label) || 'credits';
|
|
65
65
|
const Cur = cur ? cur.charAt(0).toUpperCase() + cur.slice(1) : cur;
|
|
66
|
+
const company = (brand.identity && brand.identity.company) || {};
|
|
66
67
|
const esc = (s) => String(s == null ? '' : s)
|
|
67
68
|
.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')
|
|
68
69
|
.replace(/"/g, '"').replace(/'/g, ''');
|
|
69
|
-
return html
|
|
70
|
+
return applyBrandConditionals(html, { companyName: company.name, companySite: company.site, companyTagline: company.tagline })
|
|
70
71
|
.split('{{worldName}}').join(esc(world))
|
|
71
72
|
.split('{{productName}}').join(esc(product))
|
|
72
73
|
.split('{{Currency}}').join(esc(Cur))
|
|
73
74
|
.split('{{currency}}').join(esc(cur))
|
|
74
75
|
.split('{{tagline}}').join(esc(tagline))
|
|
76
|
+
.split('{{companyName}}').join(esc(company.name))
|
|
77
|
+
.split('{{companySite}}').join(esc(company.site))
|
|
78
|
+
.split('{{companyTagline}}').join(esc(company.tagline))
|
|
75
79
|
.split('{{publicOrigin}}').join(esc(publicOrigin || ''))
|
|
76
80
|
.split('{{buildersOrigin}}').join(esc(buildersOrigin || publicOrigin || ''));
|
|
77
81
|
}
|
|
@@ -46,7 +46,7 @@ const { buildStampedHtml } = require('../asset-version');
|
|
|
46
46
|
const { buildInfo } = require('../build-info');
|
|
47
47
|
const { buildDevboxDownloads } = require('../devbox-downloads');
|
|
48
48
|
const { buildBongosDownloads } = require('../bongos-downloads');
|
|
49
|
-
const { clientBranding, branding } = require('../branding');
|
|
49
|
+
const { clientBranding, branding, applyBrandConditionals } = require('../branding');
|
|
50
50
|
const { prelaunchGate } = require('./prelaunch-gate');
|
|
51
51
|
const { platformVisibilityGate } = require('./platform-visibility-gate');
|
|
52
52
|
const { clientModules, isModuleEnabled } = require('../modules');
|
|
@@ -205,6 +205,8 @@ function injectBrandingGlobal(html, brand = clientBranding(), mods = clientModul
|
|
|
205
205
|
// served page can carry a correct cross-host href or canonical/og: URL: a client
|
|
206
206
|
// fill cannot reach them, and a baked one would make every instance advertise
|
|
207
207
|
// whichever instance happened to author the file (task 1002748, direction C5/C6).
|
|
208
|
+
// {{companyName}} {{companySite}} {{companyTagline}} are optional, so a page wraps
|
|
209
|
+
// each clause in a brand-if region that drops whole when its field is empty (task 1002853).
|
|
208
210
|
// Pure + exported for the test; both origins are injectable so it drives with zero I/O.
|
|
209
211
|
function applyBrandTokens(html, brand = clientBranding(), origin = safePublicOrigin(), hallOrigin = safeBuildersOrigin(), sourceRepo = safeSourceRepoUrl(), instanceBase = safeInstanceBase(), hubOrigin = safeHubOrigin()) {
|
|
210
212
|
const world = (brand.identity && brand.identity.worldName) || '';
|
|
@@ -213,12 +215,17 @@ function applyBrandTokens(html, brand = clientBranding(), origin = safePublicOri
|
|
|
213
215
|
const cur = (brand.currency && brand.currency.label) || 'credits';
|
|
214
216
|
const Cur = cur ? cur.charAt(0).toUpperCase() + cur.slice(1) : cur;
|
|
215
217
|
const hall = hallOrigin || origin || '';
|
|
216
|
-
|
|
218
|
+
const company = (brand.identity && brand.identity.company) || {};
|
|
219
|
+
const conditioned = applyBrandConditionals(html == null ? '' : html, { companyName: company.name, companySite: company.site, companyTagline: company.tagline });
|
|
220
|
+
return dropEmptyHrefLinks(conditioned, { publicOrigin: origin || '', buildersOrigin: hall, sourceRepoUrl: sourceRepo || '' })
|
|
217
221
|
.split('{{worldName}}').join(esc(world))
|
|
218
222
|
.split('{{productName}}').join(esc(product))
|
|
219
223
|
.split('{{Currency}}').join(esc(Cur))
|
|
220
224
|
.split('{{currency}}').join(esc(cur))
|
|
221
225
|
.split('{{tagline}}').join(esc(tagline))
|
|
226
|
+
.split('{{companyName}}').join(esc(company.name))
|
|
227
|
+
.split('{{companySite}}').join(esc(company.site))
|
|
228
|
+
.split('{{companyTagline}}').join(esc(company.tagline))
|
|
222
229
|
.split('{{publicOrigin}}').join(esc(origin || ''))
|
|
223
230
|
// a pack that names no hall of its own resolves to its OWN apex — never to
|
|
224
231
|
// whichever instance authored the file
|
package/src/branding.js
CHANGED
|
@@ -221,7 +221,11 @@ function clientBranding(b = branding()) {
|
|
|
221
221
|
identity: {
|
|
222
222
|
productName: b.identity?.productName || '',
|
|
223
223
|
worldName: b.identity?.worldName || '',
|
|
224
|
-
company: {
|
|
224
|
+
company: {
|
|
225
|
+
name: b.identity?.company?.name || '',
|
|
226
|
+
site: b.identity?.company?.site || '',
|
|
227
|
+
tagline: b.identity?.company?.tagline || '',
|
|
228
|
+
},
|
|
225
229
|
},
|
|
226
230
|
currency: {
|
|
227
231
|
label: b.currency?.label || 'credits',
|
|
@@ -308,4 +312,20 @@ function clientReward() {
|
|
|
308
312
|
}
|
|
309
313
|
}
|
|
310
314
|
|
|
311
|
-
|
|
315
|
+
// Resolve the `<!--brand-if:KEY-->…<!--/brand-if:KEY-->` regions of a served page:
|
|
316
|
+
// kept (markers removed) when `values[KEY]` is non-empty, dropped whole otherwise,
|
|
317
|
+
// so an optional pack field removes its entire clause instead of rendering half a
|
|
318
|
+
// sentence (task 1002853). Lives here, not in serve-internal, because the off-box
|
|
319
|
+
// status mirror stamps the same page without loading the server. Repeats until
|
|
320
|
+
// stable so a region nested inside a kept one resolves too.
|
|
321
|
+
const BRAND_IF_REGION = /<!--brand-if:(\w+)-->([\s\S]*?)<!--\/brand-if:\1-->/g;
|
|
322
|
+
function applyBrandConditionals(html, values) {
|
|
323
|
+
let out = String(html);
|
|
324
|
+
for (let prev; prev !== out;) {
|
|
325
|
+
prev = out;
|
|
326
|
+
out = out.replace(BRAND_IF_REGION, (_, key, inner) => (values[key] ? inner : ''));
|
|
327
|
+
}
|
|
328
|
+
return out;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
module.exports = { resolveBranding, loadBranding, deepMerge, branding, clientBranding, userAgent, applyBrandConditionals, NEUTRAL_PATH, INSTANCE_PATH };
|
package/src/module-api.js
CHANGED
|
@@ -71,7 +71,7 @@ const { responsibilityFor, ROLE_RESPONSIBILITIES } = require('./role-responsibil
|
|
|
71
71
|
// there. scripts/gds/bump-version.js still rewrites the literal below; it appends
|
|
72
72
|
// the entry to that file. Look for a version's history there, not here.
|
|
73
73
|
// ---------------------------------------------------------------------------
|
|
74
|
-
const CORE_VERSION = '1.19.
|
|
74
|
+
const CORE_VERSION = '1.19.839'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
|
|
75
75
|
|
|
76
76
|
// A namespaced logger so a module's log lines are attributable + consistent.
|
|
77
77
|
// Usage: const log = api.logger('dev-box'); log.info('mounted');
|