@bongos/core 1.19.759 → 1.19.761
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 +13 -13
- package/docs/module-api-changelog.md +4 -0
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/scripts/gds/linkify-refs.js +71 -0
- package/src/bongos/serve-internal.js +9 -1
- package/src/module-api.js +1 -1
- package/tests/hall_page_gate_map.mjs +9 -9
- package/tests/linkify_refs.mjs +70 -0
package/.bongos-core.json
CHANGED
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
"artifact": "bongos-core",
|
|
3
3
|
"manifest_schema": 1,
|
|
4
4
|
"generator": "scripts/gds/package-core.js",
|
|
5
|
-
"core_version": "1.19.
|
|
6
|
-
"core_contract": "1.19.
|
|
7
|
-
"source_commit": "
|
|
5
|
+
"core_version": "1.19.761",
|
|
6
|
+
"core_contract": "1.19.761",
|
|
7
|
+
"source_commit": "3412fa5f1d7bb5d383191d64806396f61b136ae5",
|
|
8
8
|
"source_ref": "HEAD",
|
|
9
|
-
"built_at": "2026-09-16T03:
|
|
9
|
+
"built_at": "2026-09-16T03:36:29.589Z",
|
|
10
10
|
"redaction": {
|
|
11
11
|
"model": "docs-redacted+functional-verbatim",
|
|
12
12
|
"docs_redacted": 483,
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"gate": "passed"
|
|
18
18
|
},
|
|
19
19
|
"file_count": 2623,
|
|
20
|
-
"tree_sha256": "
|
|
20
|
+
"tree_sha256": "e9ee270455be1db1f07c7c206e5f9ba6a408a2f231634a18330a70ac78a0ed59",
|
|
21
21
|
"files": [
|
|
22
22
|
{
|
|
23
23
|
"path": ".claude/skills/ask-for-help/SKILL.md",
|
|
@@ -2512,7 +2512,7 @@
|
|
|
2512
2512
|
{
|
|
2513
2513
|
"path": "docs/module-api-changelog.md",
|
|
2514
2514
|
"mode": "0000644",
|
|
2515
|
-
"sha256": "
|
|
2515
|
+
"sha256": "94b5d8c1844e775937a9acec9a78b5c2a789ed6e8e194835c02cd0d2634a4d29"
|
|
2516
2516
|
},
|
|
2517
2517
|
{
|
|
2518
2518
|
"path": "docs/modules-contract.md",
|
|
@@ -7567,12 +7567,12 @@
|
|
|
7567
7567
|
{
|
|
7568
7568
|
"path": "package-lock.json",
|
|
7569
7569
|
"mode": "0000644",
|
|
7570
|
-
"sha256": "
|
|
7570
|
+
"sha256": "172d01adc5b82fa70d7079ec41e84a7a99b89b84c40c58d8d383b45c7a18f584"
|
|
7571
7571
|
},
|
|
7572
7572
|
{
|
|
7573
7573
|
"path": "package.json",
|
|
7574
7574
|
"mode": "0000644",
|
|
7575
|
-
"sha256": "
|
|
7575
|
+
"sha256": "7f3d59f7939972bb1a32c493f4203e0c22286a3296ae69d9a11a06ed2c5d62a9"
|
|
7576
7576
|
},
|
|
7577
7577
|
{
|
|
7578
7578
|
"path": "public-docs/index.html",
|
|
@@ -8242,7 +8242,7 @@
|
|
|
8242
8242
|
{
|
|
8243
8243
|
"path": "scripts/gds/linkify-refs.js",
|
|
8244
8244
|
"mode": "0000644",
|
|
8245
|
-
"sha256": "
|
|
8245
|
+
"sha256": "73be262f96571e9d35ed83f0c9d27e7652f1dad74016c302f8b24d1d9a724333"
|
|
8246
8246
|
},
|
|
8247
8247
|
{
|
|
8248
8248
|
"path": "scripts/gds/lint-infra-exec.js",
|
|
@@ -9352,7 +9352,7 @@
|
|
|
9352
9352
|
{
|
|
9353
9353
|
"path": "src/bongos/serve-internal.js",
|
|
9354
9354
|
"mode": "0000644",
|
|
9355
|
-
"sha256": "
|
|
9355
|
+
"sha256": "405b8069c27ea596981cd4a2fd6a57b7e3637022cf63962d46a19b452fa675fc"
|
|
9356
9356
|
},
|
|
9357
9357
|
{
|
|
9358
9358
|
"path": "src/bongos/uptime-poller.js",
|
|
@@ -9382,7 +9382,7 @@
|
|
|
9382
9382
|
{
|
|
9383
9383
|
"path": "src/module-api.js",
|
|
9384
9384
|
"mode": "0000644",
|
|
9385
|
-
"sha256": "
|
|
9385
|
+
"sha256": "3429cc6b1beee6916f92a274dd2e85be55cb7e10236d8d70c4a4a8cf7ac6387c"
|
|
9386
9386
|
},
|
|
9387
9387
|
{
|
|
9388
9388
|
"path": "src/module-loader/catalog.js",
|
|
@@ -11067,7 +11067,7 @@
|
|
|
11067
11067
|
{
|
|
11068
11068
|
"path": "tests/hall_page_gate_map.mjs",
|
|
11069
11069
|
"mode": "0000644",
|
|
11070
|
-
"sha256": "
|
|
11070
|
+
"sha256": "10d8376f94e415d20b112a61e8d1f7f8f0f539eba5f685bdac4509624a69c13e"
|
|
11071
11071
|
},
|
|
11072
11072
|
{
|
|
11073
11073
|
"path": "tests/hall_palette.mjs",
|
|
@@ -11457,7 +11457,7 @@
|
|
|
11457
11457
|
{
|
|
11458
11458
|
"path": "tests/linkify_refs.mjs",
|
|
11459
11459
|
"mode": "0000644",
|
|
11460
|
-
"sha256": "
|
|
11460
|
+
"sha256": "f4e905823f8c3b7f540d3dbc6a9873198a26e022810b21194b2ae45ef9437954"
|
|
11461
11461
|
},
|
|
11462
11462
|
{
|
|
11463
11463
|
"path": "tests/live_channel.mjs",
|
|
@@ -1992,5 +1992,9 @@ is load-bearing: the script throws rather than guess if it is missing, and
|
|
|
1992
1992
|
landed since 1.19.757 with no explicit bump. run 35049006030. (task 1002620)
|
|
1993
1993
|
1.19.759 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
1994
1994
|
landed since 1.19.758 with no explicit bump. run 35050268139. (task 1002620)
|
|
1995
|
+
1.19.760 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
1996
|
+
landed since 1.19.759 with no explicit bump. run 35052040282. (task 1002620)
|
|
1997
|
+
1.19.761 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
1998
|
+
landed since 1.19.760 with no explicit bump. run 35052410219. (task 1002620)
|
|
1995
1999
|
---------------------------------------------------------------------------
|
|
1996
2000
|
```
|
package/package-lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.19.
|
|
3
|
+
"version": "1.19.761",
|
|
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.761",
|
|
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.761",
|
|
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",
|
|
@@ -200,6 +200,72 @@ const LIST_GLUE_RE = /^[\s,–—-]*(?:and|&|\+|or|to|through)?[\s,–—-]*$/i;
|
|
|
200
200
|
// last. Each site builds its own.
|
|
201
201
|
const REF_DIGITS = '\\d{1,7}';
|
|
202
202
|
|
|
203
|
+
// ---- the ordinal-prose guard (task 1002975) --------------------------------
|
|
204
|
+
//
|
|
205
|
+
// THE BUG. With no context word, a bare `#N` was linkified on ID EXISTENCE ALONE:
|
|
206
|
+
// the prose "side door #1" became a link to task 1, while its sibling "side door
|
|
207
|
+
// #2" survived only because no id 2 exists. That is luck, not grammar, and task
|
|
208
|
+
// 1002974 had to repair a committed doc because of it.
|
|
209
|
+
//
|
|
210
|
+
// THE RULE, AND WHY IT IS A WIDTH RULE. The originating row proposed "a preceding
|
|
211
|
+
// bare noun without task/idea/blocker context words → leave alone". Measured
|
|
212
|
+
// against the actual corpus, that is far too broad: running linkifyText over every
|
|
213
|
+
// committed .md with a maximally permissive id set rewrites 231 context-less refs,
|
|
214
|
+
// 77 of them preceded by a bare word — and 49 of those are GENUINE ("the #506",
|
|
215
|
+
// "by #435", "under #436"). Skipping on the preceding noun alone would leave ~49
|
|
216
|
+
// real refs bare, which is exactly the GitHub-mis-autolink harm this script exists
|
|
217
|
+
// to prevent. So the noun test alone would have traded one defect for a worse one.
|
|
218
|
+
//
|
|
219
|
+
// What DOES separate them in the corpus is WIDTH. All 28 word-preceded refs of 1–2
|
|
220
|
+
// digits are ordinal prose — "§Decision #4" (an ADR section, 13 of them), "limiter
|
|
221
|
+
// #4", "limiter #5", "idea fixture #1", "fixture #4", "ask #1", "and #7", "with
|
|
222
|
+
// #5" — and every word-preceded ref of 3+ digits is a real task. Ordinals in
|
|
223
|
+
// English prose are small; the ids these docs cite are not. Hence: no context
|
|
224
|
+
// word, AND a bare word immediately before, AND at most two digits.
|
|
225
|
+
//
|
|
226
|
+
// Both conjuncts are load-bearing. Drop the width and 49 real refs go bare; drop
|
|
227
|
+
// the preceding-word test and "(#1)" or a line-leading "#7" — which the corpus
|
|
228
|
+
// shows are ordinarily genuine — stop linking too.
|
|
229
|
+
//
|
|
230
|
+
// Known cost, accepted: "PMS-V2 #37" and "the #54" are legacy two-digit refs that
|
|
231
|
+
// now stay bare. Two ambiguous links lost against ~26 false ones fixed, and a bare
|
|
232
|
+
// "#37" with no entity word is ambiguous to a human reader as well.
|
|
233
|
+
//
|
|
234
|
+
// This lands in linkifyText rather than beside it because `.claude/hooks/
|
|
235
|
+
// stop-refcheck.js` classifies by CALLING linkifyText — so the same prose stops
|
|
236
|
+
// being flagged in chat by the same change, with no second copy of the rule.
|
|
237
|
+
// The one context word the guard must stand down for. There is deliberately NO
|
|
238
|
+
// task entry in classifyKind — "task #170" is handled by the DEFAULT path (no
|
|
239
|
+
// kind, id exists → link), because a task is the thing this resolver is for. That
|
|
240
|
+
// leaves "task #3" looking exactly like "limiter #4" to a width rule, and the
|
|
241
|
+
// first cut of this guard ate it. Caught by its own test, which is why the test
|
|
242
|
+
// exists. Not folded into ENTITY_RE: that set means "shares the integer space
|
|
243
|
+
// with tasks but is NOT one", and adding `task` there would neutralize every
|
|
244
|
+
// "task #N" into a code span.
|
|
245
|
+
const TASK_CTX_RE = /\b(tasks?)[`'")\s(:]*$/i;
|
|
246
|
+
// Words that INTRODUCE a reference rather than being the noun it counts. The row
|
|
247
|
+
// asked for "a preceding bare NOUN", and these are not nouns: "see #2" is a real
|
|
248
|
+
// ref and the Stop hook is right to block it in chat, which is how this omission
|
|
249
|
+
// was caught — tests/stop_refcheck.mjs went red on exactly that string.
|
|
250
|
+
//
|
|
251
|
+
// Kept deliberately short, and checked against the corpus rather than guessed: the
|
|
252
|
+
// 1–2 digit word-preceded refs in committed docs are preceded by pms-v2 · task ·
|
|
253
|
+
// the · ask · and · decision · limiter · fixture · sr · with · fixing. None of
|
|
254
|
+
// these six appears there, so exempting them re-breaks nothing that was measured.
|
|
255
|
+
// Words like "the", "and", "with" and "of" are NOT here on purpose — they precede
|
|
256
|
+
// ordinal phrases at least as often as references ("ask #1 and #7" means the
|
|
257
|
+
// questions, not two tasks).
|
|
258
|
+
const ORDINAL_EXEMPT_RE = /\b(see|from|per|via|in|at)[`'")\s(:]*$/i;
|
|
259
|
+
const ORDINAL_MAX_DIGITS = 2;
|
|
260
|
+
// A bare word (letters, then letters/digits/'/-) immediately before the '#',
|
|
261
|
+
// separated by spaces or tabs or nothing at all. Anchored at the end of the left
|
|
262
|
+
// context. The whitespace is OPTIONAL because the corpus contains "SR#11" — a
|
|
263
|
+
// security-review finding number, unspaced and just as much a false positive as
|
|
264
|
+
// the spaced ones. Widening it is safe precisely because the width conjunct is
|
|
265
|
+
// what protects real refs: every word-adjacent ref of 3+ digits is untouched
|
|
266
|
+
// either way, so this only ever admits more of the same two-digit class.
|
|
267
|
+
const ORDINAL_PROSE_LEFT_RE = /[A-Za-z][A-Za-z0-9'-]*[ \t]*$/;
|
|
268
|
+
|
|
203
269
|
const RESOLVER_LINK_RE = new RegExp(`\\[#(${REF_DIGITS})\\]\\((?:https?:\\/\\/[^/)\\s]+)?\\/builders#\\/task\\/(${REF_DIGITS})\\)`, 'g');
|
|
204
270
|
|
|
205
271
|
// Classify a ref by its left context (+ list-glue carry). Returns one of
|
|
@@ -313,6 +379,11 @@ function linkifySegment(segment, ids, leftContext, stats) {
|
|
|
313
379
|
} else if (kind === 'github') {
|
|
314
380
|
// "PR #475" — genuinely GitHub's number space; the bare autolink is CORRECT.
|
|
315
381
|
skipReason = 'github-namespace';
|
|
382
|
+
} else if (!kind && !TASK_CTX_RE.test(left) && !ORDINAL_EXEMPT_RE.test(left)
|
|
383
|
+
&& ORDINAL_PROSE_LEFT_RE.test(left) && num.length <= ORDINAL_MAX_DIGITS) {
|
|
384
|
+
// "side door #1", "limiter #4", "§Decision #4" — a noun followed by a small
|
|
385
|
+
// ordinal, not a ref. See ORDINAL_MAX_DIGITS for the corpus evidence.
|
|
386
|
+
skipReason = 'ordinal-prose';
|
|
316
387
|
} else if (!kind && !ids.tasks.has(Number(num))) {
|
|
317
388
|
// No context and not a known task id — too ambiguous to touch (a bare PR
|
|
318
389
|
// ref, a future task, a numeric color). Left bare.
|
|
@@ -1164,8 +1164,16 @@ function mountInternalSurfaces(app) {
|
|
|
1164
1164
|
// task 1003172 (C02): `collab` joins the same tier — every read behind it is
|
|
1165
1165
|
// any-builder and scoped to the caller by the SERVER (GET /help-requests/for-me
|
|
1166
1166
|
// derives crafts from the viewer's own profile), so signed-in is the right floor.
|
|
1167
|
+
// task 1003629 (idea 1000907): the BARE `task` and `idea` slugs join the tier
|
|
1168
|
+
// their own /:id forms already sit on. /builders/task/:id and /builders/idea/:id
|
|
1169
|
+
// are gated by TASK_DETAIL_PAGE_RE / IDEA_DETAIL_PAGE_RE below, but those
|
|
1170
|
+
// regexes require a numeric id, so the id-less slugs fell through to
|
|
1171
|
+
// BUILDERS_PAGE_RE — which resolves them to the SAME task.html / idea.html
|
|
1172
|
+
// shells — and matched no gate at all. The shell carries no data and every
|
|
1173
|
+
// read behind it is gated server-side, so this closed an inconsistency rather
|
|
1174
|
+
// than an exposure; the point is that one URL cannot be two tiers at once.
|
|
1167
1175
|
const SIGNEDIN_BUILDERS_PAGE_RE =
|
|
1168
|
-
/^\/builders\/(?:work|pair|settings|atlas|modules|goals|roadmap|copy-desk|studio|ideas|collab|primer(?:\.md)?|diagrams)(?:\.html)?\/?$/;
|
|
1176
|
+
/^\/builders\/(?:work|pair|settings|atlas|modules|goals|roadmap|copy-desk|studio|ideas|collab|task|idea|primer(?:\.md)?|diagrams)(?:\.html)?\/?$/;
|
|
1169
1177
|
// The diagram image assets under /builders/diagrams/ ride the same tier as the
|
|
1170
1178
|
// page that embeds them.
|
|
1171
1179
|
const SIGNEDIN_BUILDERS_PREFIX_RE = /^\/builders\/diagrams\//;
|
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.761'; // 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');
|
|
@@ -122,15 +122,15 @@ const ORACLE = {
|
|
|
122
122
|
'/builders/drachmae': null,
|
|
123
123
|
'/builders/profile': null,
|
|
124
124
|
|
|
125
|
-
//
|
|
126
|
-
// (task 1003448)
|
|
127
|
-
//
|
|
128
|
-
//
|
|
129
|
-
//
|
|
130
|
-
//
|
|
131
|
-
//
|
|
132
|
-
'/builders/task':
|
|
133
|
-
'/builders/idea':
|
|
125
|
+
// The bare detail slugs, gated by task 1003629 — the decision idea 1000907
|
|
126
|
+
// (task 1003448) asked for. They resolve to the same task.html / idea.html
|
|
127
|
+
// shells their /:id forms do, so they now sit on the same tier: the id-less
|
|
128
|
+
// form was never a different page, only a different spelling that the
|
|
129
|
+
// id-requiring TASK_DETAIL_PAGE_RE / IDEA_DETAIL_PAGE_RE could not see. A
|
|
130
|
+
// gate, not a redirect, because the shell is a real page at signed-in tier
|
|
131
|
+
// and redirecting would have to invent an id it does not have.
|
|
132
|
+
'/builders/task': 'requireBuilderPage',
|
|
133
|
+
'/builders/idea': 'requireBuilderPage',
|
|
134
134
|
|
|
135
135
|
// Gone, not public — the hall ships no my-projects page at all since task
|
|
136
136
|
// 1003308 (owner's decision: "my projects" is a user-level page the hub owns,
|
package/tests/linkify_refs.mjs
CHANGED
|
@@ -311,5 +311,75 @@ t('discoverDocs finds CLAUDE.md, recurses docs/ subdirs (only .md), skips the ge
|
|
|
311
311
|
}
|
|
312
312
|
});
|
|
313
313
|
|
|
314
|
+
// ---- the ordinal-prose guard (task 1002975) --------------------------------
|
|
315
|
+
//
|
|
316
|
+
// THE BUG task 1002974 had to repair a committed doc for: with no context word, a
|
|
317
|
+
// bare `#N` was linkified on ID EXISTENCE ALONE. "side door #1" became a link to
|
|
318
|
+
// task 1; its sibling "side door #2" survived only because no id 2 exists. Luck,
|
|
319
|
+
// not grammar.
|
|
320
|
+
//
|
|
321
|
+
// The rule is NOT "a preceding noun means skip" — measured over every committed
|
|
322
|
+
// .md, that would leave ~49 genuine refs bare ("the #506", "by #435"), which is
|
|
323
|
+
// the GitHub-mis-autolink harm this script exists to prevent. It is a WIDTH rule:
|
|
324
|
+
// no context word AND a bare word immediately before AND at most two digits.
|
|
325
|
+
// In the corpus all 28 word-preceded 1–2 digit refs are ordinals; every
|
|
326
|
+
// word-preceded 3+ digit ref is a real task.
|
|
327
|
+
|
|
328
|
+
t('ordinal prose after a noun is left alone — the reported "side door #1"', () => {
|
|
329
|
+
const ids = new Set([1, 2]);
|
|
330
|
+
assert.equal(run('the side door #1 is open', ids), 'the side door #1 is open');
|
|
331
|
+
assert.equal(run('the side door #2 is open', ids), 'the side door #2 is open');
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
t('the ordinals found in our OWN docs are left alone', () => {
|
|
335
|
+
const ids = new Set([1, 4, 5, 11]);
|
|
336
|
+
for (const s of [
|
|
337
|
+
'joins rate-limit.js as limiter #4: 120 reads',
|
|
338
|
+
'ADR 0083 §Decision #4 forbids kernel machinery from naming one',
|
|
339
|
+
'live proof against a local origin (idea fixture #1)',
|
|
340
|
+
'the edge cookie-strip rule (ADR 0053 / SR#11)', // unspaced — still ordinal prose
|
|
341
|
+
]) assert.equal(run(s, ids), s, s);
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
t('a WORD-preceded ref of three or more digits still linkifies — the width is what separates them', () => {
|
|
345
|
+
const ids = new Set([506, 435]);
|
|
346
|
+
assert.equal(run('the #506 evaluator', ids), `the ${link(506)} evaluator`);
|
|
347
|
+
assert.equal(run('captured by #435 last night', ids), `captured by ${link(435)} last night`);
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
t('a SMALL ref with no preceding word still linkifies — the noun is half the rule', () => {
|
|
351
|
+
const ids = new Set([7]);
|
|
352
|
+
assert.equal(run('#7 is the row', ids), `${link(7)} is the row`);
|
|
353
|
+
assert.equal(run('see (#7) for detail', ids), `see (${link(7)}) for detail`);
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
t('an explicit entity word still wins over the ordinal guard', () => {
|
|
357
|
+
// "task #3" is a real ref even though it is one digit after a word: classifyKind
|
|
358
|
+
// runs first, so the guard never sees it.
|
|
359
|
+
const ids = new Set([3]);
|
|
360
|
+
assert.equal(run('task #3 sat in blocked for days', ids), `task ${link(3)} sat in blocked for days`);
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
t('a REFERRING word is not a noun — "see #2" still linkifies', () => {
|
|
364
|
+
// The Stop hook classifies by calling linkifyText, so this is also what stops it
|
|
365
|
+
// blocking a real bare ref in chat. tests/stop_refcheck.mjs went red on exactly
|
|
366
|
+
// this string when the first cut of the guard treated every preceding word as a
|
|
367
|
+
// noun — which is how the omission was found.
|
|
368
|
+
const ids = new Set([2]);
|
|
369
|
+
for (const [s, expect] of [
|
|
370
|
+
['see #2 for details', `see ${link(2)} for details`],
|
|
371
|
+
['from #2 onward', `from ${link(2)} onward`],
|
|
372
|
+
['landed in #2 last week', `landed in ${link(2)} last week`],
|
|
373
|
+
]) assert.equal(run(s, ids), expect, s);
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
t('the guard is reported as its own skip reason, not folded into not-a-task', () => {
|
|
377
|
+
const stats = { skipped: {}, linked: 0, rewritten: 0, unlinked: 0 };
|
|
378
|
+
linkifyText('the side door #1 is open', new Set([1]), stats);
|
|
379
|
+
assert.equal(stats.skipped['ordinal-prose'], 1);
|
|
380
|
+
assert.equal(stats.skipped['not-a-task'], undefined,
|
|
381
|
+
'an ordinal is skipped BECAUSE it reads as prose, not because the id is missing');
|
|
382
|
+
});
|
|
383
|
+
|
|
314
384
|
console.log(`\nlinkify_refs: ${passed} passed, ${failed} failed`);
|
|
315
385
|
process.exit(failed ? 1 : 0);
|