@bongos/core 1.19.724 → 1.19.726

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 CHANGED
@@ -2,22 +2,22 @@
2
2
  "artifact": "bongos-core",
3
3
  "manifest_schema": 1,
4
4
  "generator": "scripts/gds/package-core.js",
5
- "core_version": "1.19.724",
6
- "core_contract": "1.19.724",
7
- "source_commit": "26fe0e32ab8d71694d4a83ebd8aec4d442ce8da0",
5
+ "core_version": "1.19.726",
6
+ "core_contract": "1.19.726",
7
+ "source_commit": "d8623af04d5a30742d38eb9ba239a3f63f775a36",
8
8
  "source_ref": "HEAD",
9
- "built_at": "2026-09-13T13:15:36.801Z",
9
+ "built_at": "2026-09-13T14:41:51.726Z",
10
10
  "redaction": {
11
11
  "model": "docs-redacted+functional-verbatim",
12
12
  "docs_redacted": 478,
13
13
  "agent_docs_stubbed": 24,
14
- "functional_verbatim": 2162,
14
+ "functional_verbatim": 2163,
15
15
  "rules": 3,
16
16
  "gate_literals": 3,
17
17
  "gate": "passed"
18
18
  },
19
- "file_count": 2664,
20
- "tree_sha256": "444d762aa2ed1f4ebcc9269d0542680f83ed9da1a928a98ac6a36ebe09c691da",
19
+ "file_count": 2665,
20
+ "tree_sha256": "a9b351048d2b02f92d5d3a8a5639a19ab5ed6731a39eb8ac8a937ac19eb22448",
21
21
  "files": [
22
22
  {
23
23
  "path": ".claude/skills/ask-for-help/SKILL.md",
@@ -2807,7 +2807,7 @@
2807
2807
  {
2808
2808
  "path": "docs/module-api-changelog.md",
2809
2809
  "mode": "0000644",
2810
- "sha256": "690b1a1f85cb9eb8e3b9e326b39cd56724bd0e3287d96539a7e84c6267231507"
2810
+ "sha256": "1d9b38322833641b859bcf8e401135219788455e5aa0d5082d7044551beefd08"
2811
2811
  },
2812
2812
  {
2813
2813
  "path": "docs/modules-contract.md",
@@ -6137,7 +6137,7 @@
6137
6137
  {
6138
6138
  "path": "modules/onboarding/approval-broadcast.js",
6139
6139
  "mode": "0000644",
6140
- "sha256": "5d88c9b42e5f76b0959a543d097456a00c3ec1f4ef1a75ca8de6334b826ba983"
6140
+ "sha256": "2225b1a2196f6195bdf1b24f38da5295e0f44cbd95395d09c6109d8680fc5b01"
6141
6141
  },
6142
6142
  {
6143
6143
  "path": "modules/onboarding/db.js",
@@ -7837,12 +7837,12 @@
7837
7837
  {
7838
7838
  "path": "package-lock.json",
7839
7839
  "mode": "0000644",
7840
- "sha256": "10ce0347095f2fef439846e19eee1535447edac0fd659fb0ef8703f50df6e60f"
7840
+ "sha256": "7637583d4d85202afbf079b2db2c89fb687377c8478d5baa8c058dc282e6d141"
7841
7841
  },
7842
7842
  {
7843
7843
  "path": "package.json",
7844
7844
  "mode": "0000644",
7845
- "sha256": "ddfcaa576b63f9169be34c591a1c0dccd629ca0399b0e27bf514a4a54fc21c6d"
7845
+ "sha256": "c9ee4d8541a9fb546d0556b13d3814e132b301e606676d6d23c2416c8c3b23b7"
7846
7846
  },
7847
7847
  {
7848
7848
  "path": "public-docs/index.html",
@@ -7907,7 +7907,7 @@
7907
7907
  {
7908
7908
  "path": "scripts/gds/adopt-preflight.js",
7909
7909
  "mode": "0000644",
7910
- "sha256": "07ff338beac12979eba50e4a6fe9c2f6dbfd842fa01abe1c412b19888d10c4a0"
7910
+ "sha256": "1e2c8a70aec49eaa0d95a9cbf37145fa1c851b46e2ec27f72f773d4198356ab9"
7911
7911
  },
7912
7912
  {
7913
7913
  "path": "scripts/gds/adr-namespace.js",
@@ -8067,7 +8067,7 @@
8067
8067
  {
8068
8068
  "path": "scripts/gds/build-cli-package.js",
8069
8069
  "mode": "0000644",
8070
- "sha256": "bd7509b408b56ded44c07f3cbad8cf361211fd8349e5139c75c49d31fcaa07a9"
8070
+ "sha256": "314004ef2af628fca5f572daca5e2404d1c2129b3ad650d715294cc26cc2fe13"
8071
8071
  },
8072
8072
  {
8073
8073
  "path": "scripts/gds/bump-version.js",
@@ -8117,7 +8117,7 @@
8117
8117
  {
8118
8118
  "path": "scripts/gds/claude-md-template.js",
8119
8119
  "mode": "0000644",
8120
- "sha256": "68f2c65ac9297328f44927ccb9a40a0fa536b91d9056aaa980a92149a64ce47c"
8120
+ "sha256": "afd825409590fa0c81cdc072bb925555e70bd543f819b8c68110760919ca67e1"
8121
8121
  },
8122
8122
  {
8123
8123
  "path": "scripts/gds/cleanup-orphans.js",
@@ -8219,6 +8219,11 @@
8219
8219
  "mode": "0000644",
8220
8220
  "sha256": "79effdf07519d26153f6fe846fd052cd4a37fa1e25b6b1a278146c9d8bcf6766"
8221
8221
  },
8222
+ {
8223
+ "path": "scripts/gds/device-flow-help.js",
8224
+ "mode": "0000644",
8225
+ "sha256": "802b8875e2aa7e51b1b95800aae461c5d289d7684ef57e6da95bebc458aa0f5e"
8226
+ },
8222
8227
  {
8223
8228
  "path": "scripts/gds/diagnose-achievements.js",
8224
8229
  "mode": "0000644",
@@ -8327,7 +8332,7 @@
8327
8332
  {
8328
8333
  "path": "scripts/gds/gate-review.js",
8329
8334
  "mode": "0000644",
8330
- "sha256": "19d739354ed6f485a842ce77379ed4b63f9b86ba848c3b9d7528809a79ad74f1"
8335
+ "sha256": "183c4592b68f059ef89b4d516ae589526af08901543ded0183b8854cd9b1e5a2"
8331
8336
  },
8332
8337
  {
8333
8338
  "path": "scripts/gds/gating.js",
@@ -8452,7 +8457,7 @@
8452
8457
  {
8453
8458
  "path": "scripts/gds/init.js",
8454
8459
  "mode": "0000644",
8455
- "sha256": "b0971625852fca93ca474d2f9e31e3601397f1b9efbef3241a0b0c47f17d925f"
8460
+ "sha256": "e81ab71bcd7d2f06b5aaf462dd099776c966f9840a109846a9753f12482b5281"
8456
8461
  },
8457
8462
  {
8458
8463
  "path": "scripts/gds/install-git-hooks.js",
@@ -8522,7 +8527,7 @@
8522
8527
  {
8523
8528
  "path": "scripts/gds/login.js",
8524
8529
  "mode": "0000644",
8525
- "sha256": "bdc7fcc0876a1185fb15339740793944c755d716d998bb68533ab3a721d39ab2"
8530
+ "sha256": "b5e00007cf68a5fa719d2603fdd7405dbc2e474a41721acde274e43bcec59ab0"
8526
8531
  },
8527
8532
  {
8528
8533
  "path": "scripts/gds/main-audit.js",
@@ -8972,7 +8977,7 @@
8972
8977
  {
8973
8978
  "path": "scripts/gds/setup.js",
8974
8979
  "mode": "0000644",
8975
- "sha256": "15243de5b4bde57313c601bf96bd2a10bb86dedceb18cb0a31495b998d1ca835"
8980
+ "sha256": "bd78df6f5f296588721afd9ebf2e477b5d463ca3d12cbee8894e115a0a6779f7"
8976
8981
  },
8977
8982
  {
8978
8983
  "path": "scripts/gds/shell.js",
@@ -9462,7 +9467,7 @@
9462
9467
  {
9463
9468
  "path": "src/bongos/permission-path-check.js",
9464
9469
  "mode": "0000644",
9465
- "sha256": "5116468286f737a3421dbcdfdc48f78ecc8f25496f7db0382f9af9e1a0110e9c"
9470
+ "sha256": "14ba2ffb603cbea7d8388c196b74f8e7db92b777fb0da7c69c7d3c7c9e27f386"
9466
9471
  },
9467
9472
  {
9468
9473
  "path": "src/bongos/platform-visibility-gate.js",
@@ -9492,7 +9497,7 @@
9492
9497
  {
9493
9498
  "path": "src/bongos/repo-info.js",
9494
9499
  "mode": "0000644",
9495
- "sha256": "ae6cd8950ec8e23935febcc14c4ba65ff39f045c01202a1237d1ad79a39153f2"
9500
+ "sha256": "38c98f7cdb9dbc9d73dc53d0ad29466bc2fc8f762cd8db3961ab40df44dae11c"
9496
9501
  },
9497
9502
  {
9498
9503
  "path": "src/bongos/route-rank-check.js",
@@ -9632,7 +9637,7 @@
9632
9637
  {
9633
9638
  "path": "src/module-api.js",
9634
9639
  "mode": "0000644",
9635
- "sha256": "e4a9cdca849f840e277b0632217f9a8aec42d570d96373548f47d04bd1fce0bd"
9640
+ "sha256": "958848fd9515e4e2c7544579ba4023f136ab5d1e5d521964ae12404682fc193c"
9636
9641
  },
9637
9642
  {
9638
9643
  "path": "src/module-loader/catalog.js",
@@ -9727,7 +9732,7 @@
9727
9732
  {
9728
9733
  "path": "tests/adopt_preflight.mjs",
9729
9734
  "mode": "0000644",
9730
- "sha256": "125edcec8c7d7536365b9675daa1fceb9fe072a30e2a4a68ee2911cd258a8c10"
9735
+ "sha256": "2489a19e7c36f53c4046a4a6fab54a55dbdfe8da6daa3452316f9e0abea01742"
9731
9736
  },
9732
9737
  {
9733
9738
  "path": "tests/adr_renumber_integrity.mjs",
@@ -10052,7 +10057,7 @@
10052
10057
  {
10053
10058
  "path": "tests/bongos_login.mjs",
10054
10059
  "mode": "0000644",
10055
- "sha256": "f273c511d9c42093da170572a0db55f78cada57787e7631aefdd622228c2fad9"
10060
+ "sha256": "abad290fe23b159a6c42a70fbc6cec68bfc0406c90776a96b57513e9b4b8ef04"
10056
10061
  },
10057
10062
  {
10058
10063
  "path": "tests/box_access.mjs",
@@ -10272,7 +10277,7 @@
10272
10277
  {
10273
10278
  "path": "tests/claude_materialize.mjs",
10274
10279
  "mode": "0000644",
10275
- "sha256": "9ae022d9051937e80b0d3a4baf3840d57560fa9d441b93beff746cfa11871d35"
10280
+ "sha256": "8292d8c7a788e7839599d38f6c78a5dbb68f6ae50a58771a66e6481a562f0ace"
10276
10281
  },
10277
10282
  {
10278
10283
  "path": "tests/clawback_confirmed_backlog.mjs",
@@ -10572,7 +10577,7 @@
10572
10577
  {
10573
10578
  "path": "tests/discord_approvals.mjs",
10574
10579
  "mode": "0000644",
10575
- "sha256": "f5acf8bac78844a0015ecec64d3f85c0546bb3e186eabb19311cb9e33cd6dc64"
10580
+ "sha256": "1b4df0a8cd7dc48f393cd2c3ca7ec91759270c7df87a50397fb3d18d3b6a29fe"
10576
10581
  },
10577
10582
  {
10578
10583
  "path": "tests/discord_autojoin.mjs",
@@ -11122,7 +11127,7 @@
11122
11127
  {
11123
11128
  "path": "tests/government_protected_surfaces.mjs",
11124
11129
  "mode": "0000644",
11125
- "sha256": "d11562d59861a5f5d294c072b3aa15f03703ddbb2102210ff92f7ccbcff5b5f6"
11130
+ "sha256": "dfde7ca6e223324317a0d56960db7adc9b30bdfc35f718269e9d6978344545ac"
11126
11131
  },
11127
11132
  {
11128
11133
  "path": "tests/government_rank_sync.mjs",
@@ -11557,7 +11562,7 @@
11557
11562
  {
11558
11563
  "path": "tests/init.mjs",
11559
11564
  "mode": "0000644",
11560
- "sha256": "4a79313db6d6393415ace3e24f985be397156c10845b6543fdfbda3944ddeec0"
11565
+ "sha256": "5b0eaf9cee1a51a4ff41197c4af844a0ba89e5a44f23d0f00de09e5ca8900f08"
11561
11566
  },
11562
11567
  {
11563
11568
  "path": "tests/instance_config.mjs",
@@ -11842,7 +11847,7 @@
11842
11847
  {
11843
11848
  "path": "tests/module_api.mjs",
11844
11849
  "mode": "0000644",
11845
- "sha256": "ac7d0fd4eb478b79cf9e72994f133e5d6bcca9caef3efd3a388779a18c58292d"
11850
+ "sha256": "53aa3732865dfce17a3216390b057dae825a6a7a8056309f3f31654c06636bee"
11846
11851
  },
11847
11852
  {
11848
11853
  "path": "tests/module_api_lazy.mjs",
@@ -12477,7 +12482,7 @@
12477
12482
  {
12478
12483
  "path": "tests/repo_info_binding.mjs",
12479
12484
  "mode": "0000644",
12480
- "sha256": "f6a95140fc8e99a7045cf8d3ccc41606fe911138fd915f40061437f2b2cc283a"
12485
+ "sha256": "384a494fab8da8cab77e1241ce8ebd954b658bcc56c578065c6f80bc0fd74f7d"
12481
12486
  },
12482
12487
  {
12483
12488
  "path": "tests/repo_map.mjs",
@@ -12742,7 +12747,7 @@
12742
12747
  {
12743
12748
  "path": "tests/setup_device_flow_fallback.mjs",
12744
12749
  "mode": "0000644",
12745
- "sha256": "f047b209ec3b0ea2fcffbee7c3b36d8cf01bb18e19cfcfa9d32267db672d834d"
12750
+ "sha256": "f59a1381d0f71f5eb25c8e691cda2347fc34ae2569db191ae074fe2f07c87f1a"
12746
12751
  },
12747
12752
  {
12748
12753
  "path": "tests/setup_smoke.mjs",
@@ -1922,5 +1922,9 @@ is load-bearing: the script throws rather than guess if it is missing, and
1922
1922
  landed since 1.19.722 with no explicit bump. run 34758720786. (task 1002620)
1923
1923
  1.19.724 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
1924
1924
  landed since 1.19.723 with no explicit bump. run 34759302425. (task 1002620)
1925
+ 1.19.725 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
1926
+ landed since 1.19.724 with no explicit bump. run 34760272743. (task 1002620)
1927
+ 1.19.726 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
1928
+ landed since 1.19.725 with no explicit bump. run 34763380132. (task 1002620)
1925
1929
  ---------------------------------------------------------------------------
1926
1930
  ```
@@ -48,12 +48,39 @@ async function postLine(channel, line, deps = {}) {
48
48
  }
49
49
  }
50
50
 
51
+ // Is the half that CLOSES this loop actually loaded? The line we post invites a 👍,
52
+ // and that reaction is only ever applied by modules/discord/discord-approvals.js —
53
+ // which lives in the `discord` module. onboarding is default-ON and discord is
54
+ // default-OFF, and nothing gated one on the other, so a default instance posted an
55
+ // approve/decline control with nothing listening: an Archon reacts, watches the
56
+ // reaction land, and believes they admitted someone. Nobody is told otherwise.
57
+ //
58
+ // A control that cannot work is worse than no message, so the outbound half now
59
+ // refuses to post without its inbound half. The hall queue is the source of truth
60
+ // either way, so nothing is lost — only a false affordance is withheld.
61
+ // Reached through the published doorway (the one core file a module may require,
62
+ // ADR 0083), never a direct require of src/modules.js. Fails CLOSED: if enablement
63
+ // can't be resolved we do not post, because that is the state we cannot vouch for.
64
+ function discordHandlerLoaded(deps = {}) {
65
+ try {
66
+ const isEnabled = deps.isModuleEnabled || require('../../src/module-api').isModuleEnabled;
67
+ return isEnabled('discord') === true;
68
+ } catch (_) {
69
+ return false;
70
+ }
71
+ }
72
+
51
73
  async function postMemberApprovalRequest(request, deps = {}) {
52
74
  if (!request || request.id == null) return { posted: false, skipped: 'no_request' };
75
+ if (!discordHandlerLoaded(deps)) {
76
+ (deps.log || console.log)('[gds] approval broadcast: skipped — the `discord` module is off, so no reaction handler would apply a 👍. Approve from the hall queue.');
77
+ return { posted: false, skipped: 'discord_module_off' };
78
+ }
53
79
  return postLine('member_approvals', memberApprovalLine(request), deps);
54
80
  }
55
81
 
56
82
  module.exports = {
57
83
  memberApprovalLine,
58
84
  postMemberApprovalRequest,
85
+ discordHandlerLoaded, // exported for unit tests — the gate is the whole fix
59
86
  };
package/package-lock.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@bongos/core",
3
- "version": "1.19.724",
3
+ "version": "1.19.726",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@bongos/core",
9
- "version": "1.19.724",
9
+ "version": "1.19.726",
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.724",
3
+ "version": "1.19.726",
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",
@@ -11,11 +11,20 @@
11
11
  // floor (ADR 0043, via gate-review.js's canonical matchesHardFloor).
12
12
  // • CI collisions — the repo's workflows vs core's expected unit/secret-scan/
13
13
  // gate-review checks.
14
+ // • IGNORED incoming files — a pre-existing .gitignore rule that would swallow what
15
+ // adopt is about to commit (task 1003400). This is the check that was missing when
16
+ // Cloud Bongos was adopted onto a repo whose .gitignore already carried `.claude/`:
17
+ // the skills, hooks and settings.json were materialized onto disk and then dropped
18
+ // by `git add`, so the adopt commit landed 8 files with no methodology surface at
19
+ // all. Nothing failed, nothing was logged, and every clone of that repo has been
20
+ // missing its /builder-* commands since. BLOCKING, because the result is a
21
+ // non-functional instance that looks fine.
14
22
  // READ-ONLY: it computes findings; the accept-gate that decides whether to proceed
15
23
  // lives in init.js runAdopt. Pure check functions + a thin fs-reading orchestrator.
16
24
 
17
25
  const nodeFs = require('node:fs');
18
26
  const nodePath = require('node:path');
27
+ const { execFileSync: nodeExecFileSync } = require('node:child_process');
19
28
  const { filenameViolation, INSTANCE_NS } = require('./migration-namespace');
20
29
  const { matchesHardFloor } = require('./gate-review');
21
30
 
@@ -79,10 +88,66 @@ function checkCiCollisions(workflows = []) {
79
88
  return [{ category: 'ci', severity: SEV.INFO, message: `${workflows.length} existing CI workflow(s); core will ADD its own unit/secret-scan/gate-review checks alongside them — confirm no required-check conflict.` }];
80
89
  }
81
90
 
91
+ // PURE — [{ path, rule }] of incoming files a .gitignore rule would swallow → findings.
92
+ // BLOCKING: an ignored incoming file is not a style problem, it is silent data loss at
93
+ // `git add` time, and the instance that results is broken in a way nothing reports.
94
+ function checkIgnoredIncoming(ignored = [], { checked = true } = {}) {
95
+ if (!checked) {
96
+ return [{ category: 'ignored-incoming', severity: SEV.INFO, message: 'could not run `git check-ignore` here (no git, or not a repo yet) — cannot confirm the incoming files are committable. Re-check after `git init` if adopt lands nothing under .claude/.' }];
97
+ }
98
+ if (!ignored.length) {
99
+ return [{ category: 'ignored-incoming', severity: SEV.INFO, message: 'no existing ignore rule swallows the files adopt writes — they will all reach the commit.' }];
100
+ }
101
+ const lines = ignored.map((i) => (i.rule ? `${i.path} (ignored by ${i.rule})` : i.path));
102
+ return [{
103
+ category: 'ignored-incoming',
104
+ severity: SEV.BLOCK,
105
+ message: `${ignored.length} file(s) adopt writes are IGNORED by this repo and would be silently dropped from the commit: ${lines.join('; ')}. `
106
+ + 'Adopt would appear to succeed and produce an instance missing that surface — a repo ignoring `.claude/` gets no /builder-* commands on any clone. '
107
+ + 'Fix the rule (delete it, or add a negation such as `!.claude/` after it), then re-run.',
108
+ }];
109
+ }
110
+
111
+ // Which of `incoming` does this repo's ignore rules match? Uses `git check-ignore -v`,
112
+ // which reports the SOURCE RULE for each match, so the finding can name the exact line
113
+ // the owner has to change rather than leaving them to hunt for it.
114
+ //
115
+ // Returns { ignored:[{path, rule}], checked:boolean }. `checked:false` means the probe
116
+ // could not run at all (no git, not a repo) — deliberately distinct from "nothing is
117
+ // ignored", because the two warrant different advice. execFileSync is injectable for
118
+ // tests; git exits 1 when nothing matches, which is a normal answer and not an error.
119
+ function probeIgnoredIncoming({ dir, incoming = INCOMING_PATHS, execFileSync = nodeExecFileSync } = {}) {
120
+ // Trailing slashes are ours (they mark dirs in the list); git wants plain pathnames.
121
+ const paths = incoming.map((p) => String(p).replace(/\/+$/, ''));
122
+ let out = '';
123
+ try {
124
+ out = execFileSync('git', ['check-ignore', '-v', '--stdin'], {
125
+ cwd: dir, input: paths.join('\n'), encoding: 'utf8', timeout: 5000,
126
+ stdio: ['pipe', 'pipe', 'ignore'],
127
+ }) || '';
128
+ } catch (err) {
129
+ // Exit 1 = "no path is ignored" (a real answer, stdout empty). Anything else means
130
+ // the probe itself could not run.
131
+ if (err && err.status === 1) return { ignored: [], checked: true };
132
+ return { ignored: [], checked: false };
133
+ }
134
+ const ignored = [];
135
+ for (const line of out.split('\n')) {
136
+ if (!line.trim()) continue;
137
+ // `<source>:<line>:<pattern>\t<pathname>`
138
+ const tab = line.lastIndexOf('\t');
139
+ if (tab === -1) continue;
140
+ const rule = line.slice(0, tab).trim();
141
+ const pathname = line.slice(tab + 1).trim();
142
+ ignored.push({ path: pathname, rule });
143
+ }
144
+ return { ignored, checked: true };
145
+ }
146
+
82
147
  // Read the repo (migrations, src/bongos, workflows) and run every check. `stack` is the
83
148
  // adopt-detect summary (task 2043); we re-read only what the checks need. Returns
84
149
  // { findings, counts:{blocking,warning,info}, hasBlocking }.
85
- function preflightAdopt({ dir, stack = {}, fs = nodeFs, path = nodePath } = {}) {
150
+ function preflightAdopt({ dir, stack = {}, fs = nodeFs, path = nodePath, execFileSync = nodeExecFileSync } = {}) {
86
151
  const listFiles = (rel) => { try { return fs.readdirSync(path.join(dir, rel)).filter((f) => { try { return fs.statSync(path.join(dir, rel, f)).isFile(); } catch { return false; } }); } catch { return []; } };
87
152
  const exists = (rel) => { try { return fs.existsSync(path.join(dir, rel)); } catch { return false; } };
88
153
 
@@ -105,6 +170,7 @@ function preflightAdopt({ dir, stack = {}, fs = nodeFs, path = nodePath } = {})
105
170
  ...checkFitnessBoundary({ topLevelDirs: stack.topLevelDirs || [], hasSrcBongos: exists('src/bongos') }),
106
171
  ...checkProtectedPaths(INCOMING_PATHS),
107
172
  ...checkCiCollisions(workflows),
173
+ ...(() => { const { ignored, checked } = probeIgnoredIncoming({ dir, execFileSync }); return checkIgnoredIncoming(ignored, { checked }); })(),
108
174
  ];
109
175
  const counts = { blocking: 0, warning: 0, info: 0 };
110
176
  for (const f of findings) counts[f.severity === SEV.BLOCK ? 'blocking' : f.severity === SEV.WARN ? 'warning' : 'info']++;
@@ -118,4 +184,4 @@ function formatPreflight(report) {
118
184
  return report.findings.map((f) => ` ${mark[f.severity] || '·'} [${f.category}] ${f.message}`);
119
185
  }
120
186
 
121
- module.exports = { preflightAdopt, formatPreflight, checkMigrationNamespace, checkFitnessBoundary, checkProtectedPaths, checkCiCollisions, INCOMING_PATHS, CORE_EXPECTED_CHECKS, SEV };
187
+ module.exports = { preflightAdopt, formatPreflight, checkMigrationNamespace, checkFitnessBoundary, checkProtectedPaths, checkCiCollisions, checkIgnoredIncoming, probeIgnoredIncoming, INCOMING_PATHS, CORE_EXPECTED_CHECKS, SEV };
@@ -51,6 +51,7 @@ const FILES = [
51
51
  'scripts/gds/login.js',
52
52
  'scripts/gds/reauth.js',
53
53
  'scripts/gds/setup.js',
54
+ 'scripts/gds/device-flow-help.js', // shared leaf: login.js + setup.js both require it (task 1003881)
54
55
  'scripts/gds/onboarding-config.js', // lazy dep of setup.js
55
56
  'scripts/gds/install-git-hooks.js', // lazy dep of setup.js
56
57
  'scripts/gds/preflight.js',
@@ -12,6 +12,14 @@
12
12
  // of a mature instance's CLAUDE.md — the instance grows its own. Coordinates with
13
13
  // task 1963 (neutralizing the core's own CLAUDE.md into the portable template).
14
14
  //
15
+ // FIRST-RUN SECTION. The template used to open by instructing the agent to run
16
+ // `bongos start` without ever saying the CLI ships inside an uninstalled dependency,
17
+ // so an agent opening a fresh clone followed its first instruction straight into
18
+ // `command not found`. The root README carries the same signpost for humans — but only
19
+ // when the repo had no README to begin with, which an ADOPTED repo always does. This
20
+ // file lands on BOTH paths and loads in full every session, so it is the one place the
21
+ // bootstrap is guaranteed to be read.
22
+ //
15
23
  // PURE (branding in → markdown out), so it unit-tests standalone and carries no
16
24
  // hardcoded host identity (everything comes from the branding object — R76-safe).
17
25
 
@@ -48,6 +56,16 @@ function renderInstanceClaudeMd(branding = {}) {
48
56
 
49
57
  Read this at the start of every session. Keep it lean — it loads in full every time.
50
58
 
59
+ ## First run — before any \`bongos\` command
60
+
61
+ **If \`node_modules/@bongos/core\` is missing, run \`npm install\` first.** The \`bongos\` CLI ships *inside* that dependency, so until the install completes there is no \`bongos\` command and every instruction below fails with \`command not found\`:
62
+
63
+ \`\`\`bash
64
+ npm install
65
+ \`\`\`
66
+
67
+ Once per machine, per clone — it is not committed (\`node_modules/\` is ignored on purpose), so a fresh \`git clone\` always needs it. If a \`bongos\` command then still misbehaves, \`bongos doctor\` checks the toolchain and session and prints the fix.
68
+
51
69
  ## What this is
52
70
 
53
71
  ${ident}
@@ -0,0 +1,72 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+ // scripts/gds/device-flow-help.js — the one explanation of a Device-Flow refusal, shared
4
+ // by `bongos setup` and `bongos login` (task 1003400).
5
+ //
6
+ // WHY IT EXISTS AT ALL. GitHub creates an App from a manifest with Device Flow OFF and
7
+ // exposes no API to switch it on — only a checkbox in the App's settings. So on every
8
+ // wizard-born instance the documented terminal sign-in is hard-broken from birth, for
9
+ // every builder, until the owner ticks that box. Nothing in the product can do it for
10
+ // them; the least we can do is say so precisely, and name a route that works meanwhile.
11
+ //
12
+ // WHY IT IS ITS OWN FILE. setup.js grew the detector + the guidance first; login.js threw
13
+ // a bare "could not start sign-in (HTTP 424)" — a dead end sitting directly downstream of
14
+ // the CLI's OWN wrong-instance advice to run `bongos login`. setup.js already requires
15
+ // login.js for runDeviceFlow, so having login.js require setup.js back would close a
16
+ // require cycle. Both now depend on this leaf instead: one source of truth, no cycle.
17
+ //
18
+ // PURE — no fs, no network, no process state; both functions unit-test standalone.
19
+
20
+ // Does this /auth/device/start response carry GitHub's device_flow_disabled refusal?
21
+ //
22
+ // Matches on TEXT, not status, deliberately: the CLI talks to many core versions, and the
23
+ // server envelope moved from 502 to 424 (a 502 body was being eaten by the CDN). Both
24
+ // register. Must NOT match auth_not_configured or any other upstream failure — those have
25
+ // different cures, and a wrong cure is worse than a generic error.
26
+ function isDeviceFlowDisabled(start) {
27
+ if (!start || start.ok) return false;
28
+ const err = start.data && start.data.error;
29
+ const text = [
30
+ typeof err === 'string' ? err : '',
31
+ err && typeof err === 'object' ? `${err.code || ''} ${err.message || ''}` : '',
32
+ start.data && start.data.message ? String(start.data.message) : '',
33
+ ].join(' ');
34
+ return /device_flow_disabled/i.test(text);
35
+ }
36
+
37
+ // The plain-language guidance: what happened, the web route that works RIGHT NOW, and the
38
+ // owner's one-checkbox repair. Returns an array of lines (the callers join them).
39
+ //
40
+ // `finish` names the caller, because only the closing move differs: setup prompts for the
41
+ // token inline ("paste it below"), login has no such prompt and must tell you to run the
42
+ // one-liner instead. Everything above that step is genuinely shared and stays shared.
43
+ function deviceFlowFallbackLines(base, { finish = 'setup' } = {}) {
44
+ const settings = `${base}/builders/settings`;
45
+ const close = finish === 'login'
46
+ ? [
47
+ ' 3. Copy the one-liner it shows and run it in this shell — that writes',
48
+ ' the same session `bongos login` would have.',
49
+ ]
50
+ : [
51
+ ' 3. Copy the token it shows and paste it below (or run the printed',
52
+ ' one-liner in another shell, then re-run setup).',
53
+ ];
54
+ return [
55
+ '',
56
+ "This project's GitHub App has Device Flow switched off, so terminal sign-in",
57
+ 'cannot start. Apps created by the one-click setup begin this way — GitHub',
58
+ 'only offers the switch as a checkbox in the App settings; no setup flow can',
59
+ 'tick it automatically.',
60
+ '',
61
+ 'Sign in through the web instead (works right now):',
62
+ ` 1. Open ${settings} and sign in with GitHub.`,
63
+ ' 2. Under "CLI Access", click "Re-issue CLI token".',
64
+ ...close,
65
+ '',
66
+ 'To repair terminal sign-in for everyone (the project owner, once):',
67
+ ' GitHub → Settings → Developer settings → GitHub Apps → your app →',
68
+ ' check "Enable Device Flow" → Save. Web sign-in is unaffected either way.',
69
+ ];
70
+ }
71
+
72
+ module.exports = { isDeviceFlowDisabled, deviceFlowFallbackLines };
@@ -38,8 +38,11 @@
38
38
 
39
39
  const { execFileSync } = require('node:child_process');
40
40
  const fs = require('node:fs');
41
- const permPath = require('../../src/bongos/permission-path-check');
42
- const { PROTECTED_GLOBS, globToRegExp } = permPath;
41
+ // Destructured AT the require, not off a held module object: knip's static pass
42
+ // cannot follow `const x = require(...); const { y } = x;` and reports `y` as an
43
+ // unused export, which reds the CI knip step (this file's own CLAUDE.md records
44
+ // the same trap for src/instance-config.js).
45
+ const { PROTECTED_GLOBS, compileGlobMatcher } = require('../../src/bongos/permission-path-check');
43
46
 
44
47
  // --- HARD FLOOR (rank-independent ESCALATE) -------------------------------------------
45
48
  // A subset of PROTECTED_GLOBS: the surfaces that, if a compromised/injected senior
@@ -89,12 +92,17 @@ const AUTO_NARROWING = {
89
92
  '.husky/secrets-allowlist.txt': certifyAllowlistNarrowing,
90
93
  };
91
94
 
92
- const COMPILED_HARD = HARD_FLOOR_GLOBS.map((g) => ({ glob: g, re: globToRegExp(g) }));
93
- const matchesHardFloor = (f) => COMPILED_HARD.some((c) => c.re.test(f));
95
+ // task 1002874: compile through the authoritative module's FOLDING matcher, not
96
+ // globToRegExp directly. Both of these used to build their own byte-exact regexes
97
+ // from the raw globs and test raw candidates, so after protected-path matching
98
+ // gained NFC + case-folding, an uppercased spelling of a gate surface still walked
99
+ // past THIS classifier — the CI auto-merge gate — which is the bypass the fix
100
+ // exists to close. isCanonicalPath below rejects non-NFC and non-ASCII, but it
101
+ // does not case-fold, so it never covered this.
102
+ const matchesHardFloor = compileGlobMatcher(HARD_FLOOR_GLOBS);
94
103
 
95
104
  // A path is "protected" per the authoritative module.
96
- const protectedRe = PROTECTED_GLOBS.map((g) => ({ glob: g, re: globToRegExp(g) }));
97
- const matchesProtected = (f) => protectedRe.some((c) => c.re.test(f));
105
+ const matchesProtected = compileGlobMatcher(PROTECTED_GLOBS);
98
106
 
99
107
  // Only [\x20-\x7e] path chars, NFC-stable, no leading/trailing whitespace. Defeats the
100
108
  // unicode-homoglyph + whitespace-alias evasions (red-team Holes 6/8).