@bongos/core 1.24.75 → 1.24.77
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 +101 -41
- package/.claude/skills/blocker-review/SKILL.md +10 -0
- package/clients/bongos-client/index.d.ts +1 -1
- package/docs/adr/0307-autobongos-a-deterministic-supervisor-and-agentic-workers.md +30 -0
- package/docs/api/openapi.json +5 -1
- package/docs/api-reference.md +1 -1
- package/docs/copy-inventory.md +43 -35
- package/docs/copy-registry.json +129 -57
- package/docs/module-api-changelog.md +4 -2
- package/docs/packs/engineer.md +2 -2
- package/docs/page-inventory.json +2 -0
- package/docs/page-readings.json +8 -8
- package/modules/autonomy/gauge.js +25 -1
- package/modules/hall-ui/public/deploy.html +1 -1
- package/modules/hall-ui/public/deploy.js +56 -5
- package/modules/hall-ui/public/home-city.js +7 -2
- package/modules/hall-ui/public/home-idea-box.css +33 -0
- package/modules/hall-ui/public/home-idea-box.js +106 -0
- package/modules/hall-ui/public/index.html +2 -0
- package/modules/hall-ui/public/project-settings.css +15 -0
- package/modules/hall-ui/records/home-city.md +21 -0
- package/modules/ideas/routes/blockers.js +47 -1
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/release-notes.json +19 -1
- package/scripts/bongos/autobongos-bootstrap.ps1 +169 -0
- package/scripts/bongos/autobongos-bootstrap.sh +151 -0
- package/scripts/bongos/autobongos-host.js +869 -0
- package/scripts/bongos/autobongos-run.js +7 -1
- package/scripts/bongos/autonomy-gauge.js +11 -3
- package/scripts/bongos/blocker-recheck.js +111 -0
- package/scripts/bongos/cli-lib.js +5 -1
- package/scripts/bongos/government-walk.js +1 -1
- package/scripts/gds/autobongos-bootstrap.ps1 +8 -0
- package/scripts/gds/autobongos-bootstrap.sh +8 -0
- package/scripts/gds/autobongos-host.js +7 -0
- package/scripts/gds/blocker-recheck.js +7 -0
- package/scripts/hall-preview/server.js +1 -0
- package/src/instance-config.js +22 -3
- package/src/module-api.js +1 -1
- package/tests/autobongos_host.mjs +619 -0
- package/tests/autobongos_loop.mjs +11 -0
- package/tests/autonomy_gauge.mjs +43 -0
- package/tests/cli_sessions.mjs +16 -0
- package/tests/deploy_page_projects.mjs +49 -0
- package/tests/government_agent_asks.mjs +92 -2
- package/tests/home_idea_box.mjs +178 -0
|
@@ -265,6 +265,10 @@ const WORKER_ENV_ALLOW = [
|
|
|
265
265
|
// missing here would be set on the host and silently absent in the worker.
|
|
266
266
|
...claudeBinEnvNames(),
|
|
267
267
|
'XDG_CONFIG_HOME',
|
|
268
|
+
// The runner's own config root (task 1005095): its login, heartbeat and run log
|
|
269
|
+
// sit apart from the builder's own CLI session on the same device, and a worker
|
|
270
|
+
// that did not inherit this would claim and ship as the wrong session.
|
|
271
|
+
'BONGOS_CONFIG_ROOT',
|
|
268
272
|
// THE WORKER'S OWN CREDENTIAL, and it has to be here (task 1003907, found by
|
|
269
273
|
// running it). On a desktop, the claude CLI authenticates from the credential
|
|
270
274
|
// store under USERPROFILE/HOME, which are already allowed - so this list looked
|
|
@@ -545,7 +549,9 @@ function queueGatedRow(queueGate, now) {
|
|
|
545
549
|
async function iteration(opts, deps = {}) {
|
|
546
550
|
const runCmd = deps.run || run;
|
|
547
551
|
const spawnWork = deps.spawnWorker || spawnWorker;
|
|
548
|
-
|
|
552
|
+
// absentMeansGo: a device with no gauge file at all works until the usage limit
|
|
553
|
+
// stops it (task 1005095) — see decideCapacity in modules/autonomy/gauge.js.
|
|
554
|
+
const readGauge = deps.gauge || (() => gauge.evaluate(undefined, { absentMeansGo: true }));
|
|
549
555
|
const pick = deps.pickTask || pickTask;
|
|
550
556
|
const log = deps.record || record;
|
|
551
557
|
|
|
@@ -26,9 +26,14 @@ function gaugePath() {
|
|
|
26
26
|
catch (_) { return null; }
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
+
// null means there is NO file. A file that exists but cannot be read or parsed
|
|
30
|
+
// comes back as the string 'unreadable', which the decision treats as a bad
|
|
31
|
+
// reading (unknown) and never as "this device has no gauge" (task 1005095).
|
|
29
32
|
function loadGauge(p) {
|
|
30
33
|
if (!p) return null;
|
|
31
|
-
|
|
34
|
+
let text;
|
|
35
|
+
try { text = fs.readFileSync(p, 'utf8'); } catch (e) { return e && e.code === 'ENOENT' ? null : 'unreadable'; }
|
|
36
|
+
try { return JSON.parse(text); } catch (_) { return 'unreadable'; }
|
|
32
37
|
}
|
|
33
38
|
|
|
34
39
|
function readConfig() {
|
|
@@ -47,9 +52,12 @@ function readConfig() {
|
|
|
47
52
|
return c;
|
|
48
53
|
}
|
|
49
54
|
|
|
50
|
-
|
|
55
|
+
// `overrides` is merged over the env config. The supervisor passes
|
|
56
|
+
// { absentMeansGo: true } (task 1005095); this CLI passes nothing, so a missing
|
|
57
|
+
// file is still UNKNOWN here.
|
|
58
|
+
function evaluate(nowEpochS = Math.floor(Date.now() / 1000), overrides = {}) {
|
|
51
59
|
const p = gaugePath();
|
|
52
|
-
return { path: p, decision: decideCapacity({ gauge: loadGauge(p), now: nowEpochS, config: readConfig() }) };
|
|
60
|
+
return { path: p, decision: decideCapacity({ gauge: loadGauge(p), now: nowEpochS, config: { ...readConfig(), ...overrides } }) };
|
|
53
61
|
}
|
|
54
62
|
|
|
55
63
|
async function main(argv) {
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// scripts/bongos/blocker-recheck.js — what has shipped since a blocker was filed that may
|
|
3
|
+
// already answer it (task 1005120).
|
|
4
|
+
//
|
|
5
|
+
// WHY. A blocker is a claim about the world, made on a date, by a session that has long
|
|
6
|
+
// since ended. One project's blocker asked its owner to "apply to Plaid for Chase and Bank of
|
|
7
|
+
// America OAuth" for days after the project's own shipped work had found there was nothing
|
|
8
|
+
// to apply for — nothing ever re-read it, and it had no linked tasks, so no promotion would
|
|
9
|
+
// ever surface it. /blocker-review runs this before putting a blocker to a person again: it
|
|
10
|
+
// lists the shipped work since the filing that shares the blocker's subject, so the session
|
|
11
|
+
// reads that first and offers to close the blocker when the answer is already in.
|
|
12
|
+
//
|
|
13
|
+
// IT SUGGESTS, IT NEVER DECIDES. A word overlap is a reason to read a task, not proof the
|
|
14
|
+
// blocker is answered; closing one is still a person's verdict in the review. So the match is
|
|
15
|
+
// deliberately simple and explainable (shared subject words, each shown), never a score a
|
|
16
|
+
// reader has to trust.
|
|
17
|
+
//
|
|
18
|
+
// usage: node scripts/bongos/blocker-recheck.js <blocker-id> [--json]
|
|
19
|
+
|
|
20
|
+
'use strict';
|
|
21
|
+
|
|
22
|
+
// Words that say nothing about a blocker's SUBJECT. Short words are dropped by length.
|
|
23
|
+
const STOP = new Set([
|
|
24
|
+
'about', 'after', 'again', 'already', 'also', 'apply', 'because', 'before', 'being', 'blocker',
|
|
25
|
+
'cannot', 'could', 'does', 'done', 'each', 'from', 'have', 'into', 'just', 'make', 'more', 'must',
|
|
26
|
+
'need', 'needs', 'only', 'other', 'owner', 'should', 'some', 'still', 'than', 'that', 'their',
|
|
27
|
+
'them', 'then', 'there', 'these', 'they', 'this', 'those', 'through', 'until', 'very', 'want',
|
|
28
|
+
'were', 'what', 'when', 'where', 'which', 'while', 'will', 'with', 'without', 'would', 'your',
|
|
29
|
+
'task', 'tasks', 'work', 'page', 'pages', 'project',
|
|
30
|
+
// Words nearly every shipped summary in this ledger carries, so they say nothing about a
|
|
31
|
+
// blocker's subject (measured against a live blocker: these alone matched ten tasks).
|
|
32
|
+
'back', 'both', 'check', 'checks', 'core', 'main', 'once', 'open', 'itself', 'shipped', 'test',
|
|
33
|
+
'tests', 'version', 'versions', 'instance', 'server', 'update', 'copy', 'first', 'every', 'now',
|
|
34
|
+
]);
|
|
35
|
+
|
|
36
|
+
/** The subject words of a text: lower-cased, 4+ letters, not a stop word. */
|
|
37
|
+
function subjectWords(text) {
|
|
38
|
+
const out = new Set();
|
|
39
|
+
for (const w of String(text || '').toLowerCase().match(/[a-z][a-z0-9]{3,}/g) || []) {
|
|
40
|
+
if (!STOP.has(w)) out.add(w);
|
|
41
|
+
}
|
|
42
|
+
return out;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Shipped tasks AFTER the blocker was filed that share at least `minShared` subject words
|
|
47
|
+
* with its title + body, most shared first, then newest. PURE.
|
|
48
|
+
* Each result: { id, short_name, title, shipped_at, value_summary, shared: [words] }.
|
|
49
|
+
*/
|
|
50
|
+
function relatedShipped(blocker, tasks, { minShared = 2, max = 10 } = {}) {
|
|
51
|
+
const filed = Date.parse(blocker && blocker.created_at);
|
|
52
|
+
if (!Number.isFinite(filed)) return [];
|
|
53
|
+
// The TITLE names the subject; a long body mostly carries generic words that match
|
|
54
|
+
// everything. The body is used only when the title alone is too thin to match on.
|
|
55
|
+
let want = subjectWords(blocker.title);
|
|
56
|
+
if (want.size < minShared) want = subjectWords(`${blocker.title || ''} ${blocker.body_md || ''}`);
|
|
57
|
+
const hits = [];
|
|
58
|
+
for (const t of Array.isArray(tasks) ? tasks : []) {
|
|
59
|
+
if (!t || t.status !== 'shipped') continue;
|
|
60
|
+
const at = Date.parse(t.shipped_at);
|
|
61
|
+
if (!Number.isFinite(at) || at <= filed) continue;
|
|
62
|
+
const have = subjectWords(`${t.title || ''} ${t.value_summary || ''}`);
|
|
63
|
+
const shared = [...want].filter((w) => have.has(w)).sort();
|
|
64
|
+
if (shared.length >= minShared) {
|
|
65
|
+
hits.push({ id: t.id, short_name: t.short_name || null, title: t.title, shipped_at: t.shipped_at, value_summary: t.value_summary || null, shared });
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
hits.sort((a, b) => (b.shared.length - a.shared.length) || (Date.parse(b.shipped_at) - Date.parse(a.shipped_at)));
|
|
69
|
+
return hits.slice(0, max);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** The lines /blocker-review reads. Never "nothing is related" as proof the blocker holds. */
|
|
73
|
+
function reportLines(blocker, hits) {
|
|
74
|
+
const head = `Blocker ${blocker.id} - "${blocker.title}" (filed ${String(blocker.created_at).slice(0, 10)})`;
|
|
75
|
+
if (!hits.length) {
|
|
76
|
+
return [head, ' No shipped work since it was filed shares its subject. That is not proof it still holds: re-run the check it names, if any.'];
|
|
77
|
+
}
|
|
78
|
+
return [head, ` ${hits.length} shipped since it was filed may answer it — read these before asking again:`,
|
|
79
|
+
...hits.map((h) => ` - task ${h.id}${h.short_name ? ` - ${h.short_name}` : ''} (shipped ${String(h.shipped_at).slice(0, 10)}; shares: ${h.shared.join(', ')})${h.value_summary ? `\n ${h.value_summary}` : ''}`)];
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async function main(argv) {
|
|
83
|
+
const id = Number(argv[0]);
|
|
84
|
+
if (!Number.isInteger(id) || id <= 0) {
|
|
85
|
+
console.error('usage: node scripts/bongos/blocker-recheck.js <blocker-id> [--json]');
|
|
86
|
+
return 2;
|
|
87
|
+
}
|
|
88
|
+
const { cliClient } = require('./cli-lib');
|
|
89
|
+
const api = await cliClient();
|
|
90
|
+
const b = await api.request('GET', `/blockers/${id}`);
|
|
91
|
+
if (!b.ok || !b.data || !b.data.blocker) {
|
|
92
|
+
console.error(`blocker ${id}: could not be read (${b.status})`);
|
|
93
|
+
return 1;
|
|
94
|
+
}
|
|
95
|
+
const t = await api.request('GET', '/tasks?status=shipped&limit=1000');
|
|
96
|
+
if (!t.ok) {
|
|
97
|
+
console.error(`shipped tasks could not be read (${t.status}), so nothing was re-checked`);
|
|
98
|
+
return 1;
|
|
99
|
+
}
|
|
100
|
+
const tasks = Array.isArray(t.data) ? t.data : (t.data && t.data.tasks) || [];
|
|
101
|
+
const hits = relatedShipped(b.data.blocker, tasks);
|
|
102
|
+
if (argv.includes('--json')) console.log(JSON.stringify({ blocker_id: id, related: hits }, null, 2));
|
|
103
|
+
else console.log(reportLines(b.data.blocker, hits).join('\n'));
|
|
104
|
+
return 0;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
module.exports = { subjectWords, relatedShipped, reportLines };
|
|
108
|
+
|
|
109
|
+
if (require.main === module) {
|
|
110
|
+
main(process.argv.slice(2)).then((code) => { process.exitCode = code; }, (e) => { console.error(e.message); process.exitCode = 1; });
|
|
111
|
+
}
|
|
@@ -333,7 +333,11 @@ function sessionStoreDir() {
|
|
|
333
333
|
// Directory name deliberately NOT 'sessions': that is a live module key, and ADR 0083
|
|
334
334
|
// §Decision #4 forbids naming one in shared machinery. 'instances' is the truer name anyway —
|
|
335
335
|
// one file per instance is exactly what this holds.
|
|
336
|
-
|
|
336
|
+
// Under the config ROOT, which is ~/.config unless BONGOS_CONFIG_ROOT moves it
|
|
337
|
+
// (task 1005095) — so an Autobongos runner's login is never filed into, or read
|
|
338
|
+
// out of, the builder's own store. The root is not the brand: the anchor is
|
|
339
|
+
// still the same fixed dir name for every checkout that shares a root.
|
|
340
|
+
return path.join(ic.configRoot(), ic.FALLBACK_DIR, 'instances');
|
|
337
341
|
}
|
|
338
342
|
|
|
339
343
|
// The store's filename for an instance, derived from its api_base host. Returns null when the base
|
|
@@ -1267,7 +1267,7 @@ async function walkNewMachinery({ call, code, vote, voteAll, check, section, sta
|
|
|
1267
1267
|
});
|
|
1268
1268
|
let matter = null;
|
|
1269
1269
|
await check('With an ask, the AI session\'s blocker is filed and carries its card', '201; the blocker reads back with its question', async () => {
|
|
1270
|
-
const r = await call(CY, 'POST', '/blockers', { title: 'Walk needs a decision', ask: GOOD_ASK }, 'cli');
|
|
1270
|
+
const r = await call(CY, 'POST', '/blockers', { title: 'Walk needs a decision', ask: GOOD_ASK, checked: 'Ran the walk against the stub server and read its refusal for this exact card.' }, 'cli');
|
|
1271
1271
|
const id = r.json && r.json.blocker && r.json.blocker.id;
|
|
1272
1272
|
matter = id ? `blockers:${id}` : null;
|
|
1273
1273
|
const back = await call(CY, 'GET', `/blockers/${id}`);
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# scripts/gds/autobongos-bootstrap.ps1 -- @bongos-forwarder (task 1004848). Generated by
|
|
2
|
+
# scripts/bongos/codemod-rename-scripts-gds.js; do not edit.
|
|
3
|
+
# The real script is scripts/bongos/autobongos-bootstrap.ps1. This forwarder keeps callers that still name
|
|
4
|
+
# the old path working, and warns once so they get repointed. Task 1003706 removes it.
|
|
5
|
+
if (-not $env:BONGOS_FORWARDER_WARNED) { Write-Warning '[bongos] scripts/gds/autobongos-bootstrap.ps1 has moved to scripts/bongos/autobongos-bootstrap.ps1. This old path still works for now, but update whatever called it. The forwarders in scripts/gds/ are removed in a later release.' }
|
|
6
|
+
$env:BONGOS_FORWARDER_WARNED = '1'
|
|
7
|
+
& (Join-Path $PSScriptRoot '..\bongos\autobongos-bootstrap.ps1') @args
|
|
8
|
+
exit $LASTEXITCODE
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# scripts/gds/autobongos-bootstrap.sh — @bongos-forwarder (task 1004848). Generated by
|
|
3
|
+
# scripts/bongos/codemod-rename-scripts-gds.js; do not edit.
|
|
4
|
+
# The real script is scripts/bongos/autobongos-bootstrap.sh. This forwarder keeps callers that still name
|
|
5
|
+
# the old path working, and warns once so they get repointed. Task 1003706 removes it.
|
|
6
|
+
if [ -z "${BONGOS_FORWARDER_WARNED:-}" ]; then echo "[bongos] scripts/gds/autobongos-bootstrap.sh has moved to scripts/bongos/autobongos-bootstrap.sh. This old path still works for now, but update whatever called it. The forwarders in scripts/gds/ are removed in a later release." >&2; fi
|
|
7
|
+
export BONGOS_FORWARDER_WARNED=1
|
|
8
|
+
exec bash "$(dirname "$0")/../bongos/autobongos-bootstrap.sh" "$@"
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// scripts/gds/autobongos-host.js — @bongos-forwarder (task 1004848). Generated by
|
|
3
|
+
// scripts/bongos/codemod-rename-scripts-gds.js; do not edit.
|
|
4
|
+
// The real script is scripts/bongos/autobongos-host.js. This forwarder keeps callers that still name
|
|
5
|
+
// the old path working, and warns once so they get repointed. Task 1003706 removes it.
|
|
6
|
+
'use strict';
|
|
7
|
+
require('../bongos/forwarder.js').forward(module, 'autobongos-host.js');
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// scripts/gds/blocker-recheck.js — @bongos-forwarder (task 1004848). Generated by
|
|
3
|
+
// scripts/bongos/codemod-rename-scripts-gds.js; do not edit.
|
|
4
|
+
// The real script is scripts/bongos/blocker-recheck.js. This forwarder keeps callers that still name
|
|
5
|
+
// the old path working, and warns once so they get repointed. Task 1003706 removes it.
|
|
6
|
+
'use strict';
|
|
7
|
+
require('../bongos/forwarder.js').forward(module, 'blocker-recheck.js');
|
|
@@ -182,6 +182,7 @@ const FIXTURE_FALLBACKS = [
|
|
|
182
182
|
// and changes nothing: the harness has no state to change, and the page reads
|
|
183
183
|
// the same fixtures again on reload. Anything absent is still refused.
|
|
184
184
|
const CANNED_WRITES = [
|
|
185
|
+
['POST', /^inbox$/, 'inbox.post'],
|
|
185
186
|
['PUT', /^copy-desk\/pages\/[a-z0-9-]+:[a-z0-9-]+\/draft$/, 'copy-desk__pages__draft.put'],
|
|
186
187
|
['POST', /^copy-desk\/pages\/[a-z0-9-]+:[a-z0-9-]+\/submit$/, 'copy-desk__pages__submit.post'],
|
|
187
188
|
['POST', /^copy-desk\/pages\/[a-z0-9-]+:[a-z0-9-]+\/claim$/, 'copy-desk__pages__claim.post'],
|
package/src/instance-config.js
CHANGED
|
@@ -112,17 +112,35 @@ function resolveApiBase({ session, env = process.env, b = safeBrand() } = {}) {
|
|
|
112
112
|
return fromSession || defaultApiBase(b);
|
|
113
113
|
}
|
|
114
114
|
|
|
115
|
+
// The folder every config dir lives under: ~/.config, unless BONGOS_CONFIG_ROOT
|
|
116
|
+
// names another one (task 1005095).
|
|
117
|
+
//
|
|
118
|
+
// WHY AN OVERRIDE. An Autobongos runner on a builder's own laptop holds a login
|
|
119
|
+
// of its own (src/bongos/runner-login.js), and it must not share a session file
|
|
120
|
+
// with the builder's CLI: one slot means adding the laptop as a runner signs the
|
|
121
|
+
// builder's own tools out, and removing the runner signs them out again. The
|
|
122
|
+
// runner's device program (scripts/bongos/autobongos-host.js) sets this for the
|
|
123
|
+
// supervisor and every worker it starts, so their session, heartbeat and run log
|
|
124
|
+
// sit apart — and with a root of its own the LEGACY dirs below are not found
|
|
125
|
+
// either, so the runner can never fall back to the builder's own session.
|
|
126
|
+
// One fixed name, read directly: the branded-prefix lookup needs the brand, and
|
|
127
|
+
// this decides where the brand's own dir goes.
|
|
128
|
+
function configRoot(env = process.env) {
|
|
129
|
+
const over = typeof env.BONGOS_CONFIG_ROOT === 'string' ? env.BONGOS_CONFIG_ROOT.trim() : '';
|
|
130
|
+
return over ? path.resolve(over) : path.join(os.homedir(), '.config');
|
|
131
|
+
}
|
|
132
|
+
|
|
115
133
|
// Absolute path to the instance config dir — the WRITE target and first read.
|
|
116
134
|
function configHome(b = safeBrand()) {
|
|
117
|
-
return path.join(
|
|
135
|
+
return path.join(configRoot(), configDirName(b));
|
|
118
136
|
}
|
|
119
137
|
|
|
120
138
|
// All config dirs to try on READ, configured first then legacy, de-duped.
|
|
121
139
|
function configReadDirs(b = safeBrand()) {
|
|
122
|
-
const
|
|
140
|
+
const root = configRoot();
|
|
123
141
|
const dirs = [configHome(b)];
|
|
124
142
|
for (const d of LEGACY_DIRS) {
|
|
125
|
-
const p = path.join(
|
|
143
|
+
const p = path.join(root, d);
|
|
126
144
|
if (!dirs.includes(p)) dirs.push(p);
|
|
127
145
|
}
|
|
128
146
|
return dirs;
|
|
@@ -367,6 +385,7 @@ module.exports = {
|
|
|
367
385
|
envPrefix,
|
|
368
386
|
defaultApiBase,
|
|
369
387
|
resolveApiBase,
|
|
388
|
+
configRoot,
|
|
370
389
|
configHome,
|
|
371
390
|
configReadDirs,
|
|
372
391
|
configPath,
|
package/src/module-api.js
CHANGED
|
@@ -81,7 +81,7 @@ const { sleep } = require('./sleep');
|
|
|
81
81
|
// MAJOR (see allowBoxScope below): passes the request through untouched.
|
|
82
82
|
function deprecatedNoopMiddleware(_req, _res, next) { next(); }
|
|
83
83
|
|
|
84
|
-
const CORE_VERSION = '1.24.
|
|
84
|
+
const CORE_VERSION = '1.24.77'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
|
|
85
85
|
|
|
86
86
|
// A namespaced logger so a module's log lines are attributable + consistent.
|
|
87
87
|
// Usage: const log = api.logger('discord'); log.info('mounted');
|