@bongos/core 1.19.909 → 1.19.910
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 +14 -14
- package/docs/file-map.md +3 -0
- package/docs/module-api-changelog.md +2 -0
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/scripts/gds/fitness.js +1 -1
- package/scripts/gds/gen-file-map.js +96 -2
- package/scripts/gds/ship-check.js +1 -1
- package/src/module-api.js +1 -1
- package/tests/file_map.mjs +59 -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.910",
|
|
6
|
+
"core_contract": "1.19.910",
|
|
7
|
+
"source_commit": "ab1d1de5cfa5ac30120e07b1974440f17945eefa",
|
|
8
8
|
"source_ref": "HEAD",
|
|
9
|
-
"built_at": "2026-09-
|
|
9
|
+
"built_at": "2026-09-20T15:29:42.443Z",
|
|
10
10
|
"redaction": {
|
|
11
11
|
"model": "docs-redacted+functional-verbatim",
|
|
12
12
|
"docs_redacted": 503,
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"gate": "passed"
|
|
18
18
|
},
|
|
19
19
|
"file_count": 2812,
|
|
20
|
-
"tree_sha256": "
|
|
20
|
+
"tree_sha256": "40fde6b52d1d23e9b6eba83c0721d3584999cbfd55401262111678a035e8f07b",
|
|
21
21
|
"files": [
|
|
22
22
|
{
|
|
23
23
|
"path": ".claude/skills/ask-for-help/SKILL.md",
|
|
@@ -2627,7 +2627,7 @@
|
|
|
2627
2627
|
{
|
|
2628
2628
|
"path": "docs/file-map.md",
|
|
2629
2629
|
"mode": "0000644",
|
|
2630
|
-
"sha256": "
|
|
2630
|
+
"sha256": "8f386030b89f390fe4fece85308f87177e487c373d9bf7ba6758cb80febfa8df"
|
|
2631
2631
|
},
|
|
2632
2632
|
{
|
|
2633
2633
|
"path": "docs/handoff-template.md",
|
|
@@ -2637,7 +2637,7 @@
|
|
|
2637
2637
|
{
|
|
2638
2638
|
"path": "docs/module-api-changelog.md",
|
|
2639
2639
|
"mode": "0000644",
|
|
2640
|
-
"sha256": "
|
|
2640
|
+
"sha256": "01618cef9838c895da164b81675efd8db38b97151ea2e3b6882f08a03009bd8f"
|
|
2641
2641
|
},
|
|
2642
2642
|
{
|
|
2643
2643
|
"path": "docs/modules-contract.md",
|
|
@@ -7787,12 +7787,12 @@
|
|
|
7787
7787
|
{
|
|
7788
7788
|
"path": "package-lock.json",
|
|
7789
7789
|
"mode": "0000644",
|
|
7790
|
-
"sha256": "
|
|
7790
|
+
"sha256": "adc1e3888e98381ca917459b11366a25c6da5e00e72411b1bc3e22aad10c4f69"
|
|
7791
7791
|
},
|
|
7792
7792
|
{
|
|
7793
7793
|
"path": "package.json",
|
|
7794
7794
|
"mode": "0000644",
|
|
7795
|
-
"sha256": "
|
|
7795
|
+
"sha256": "09320d2a344a74fcb4db29bb52b658f61df23191bdf4247916c017277c31b25d"
|
|
7796
7796
|
},
|
|
7797
7797
|
{
|
|
7798
7798
|
"path": "public-docs/index.html",
|
|
@@ -8372,7 +8372,7 @@
|
|
|
8372
8372
|
{
|
|
8373
8373
|
"path": "scripts/gds/fitness.js",
|
|
8374
8374
|
"mode": "0000644",
|
|
8375
|
-
"sha256": "
|
|
8375
|
+
"sha256": "b05634f4916387ed35b33774e8b27176a1d3ce14c8ea6cb98485f289395afc6b"
|
|
8376
8376
|
},
|
|
8377
8377
|
{
|
|
8378
8378
|
"path": "scripts/gds/gate-review.js",
|
|
@@ -8432,7 +8432,7 @@
|
|
|
8432
8432
|
{
|
|
8433
8433
|
"path": "scripts/gds/gen-file-map.js",
|
|
8434
8434
|
"mode": "0000644",
|
|
8435
|
-
"sha256": "
|
|
8435
|
+
"sha256": "a8531787384fc528eb75dde142ca8d24982e47e226c934d6e692c53e6e738a0a"
|
|
8436
8436
|
},
|
|
8437
8437
|
{
|
|
8438
8438
|
"path": "scripts/gds/gen-freshness.js",
|
|
@@ -9102,7 +9102,7 @@
|
|
|
9102
9102
|
{
|
|
9103
9103
|
"path": "scripts/gds/ship-check.js",
|
|
9104
9104
|
"mode": "0000644",
|
|
9105
|
-
"sha256": "
|
|
9105
|
+
"sha256": "24e94cd822d5d9e75d2596ac85af854a6d965eb94a7859996792ddfd0f83e898"
|
|
9106
9106
|
},
|
|
9107
9107
|
{
|
|
9108
9108
|
"path": "scripts/gds/ship-deploy-target.js",
|
|
@@ -9787,7 +9787,7 @@
|
|
|
9787
9787
|
{
|
|
9788
9788
|
"path": "src/module-api.js",
|
|
9789
9789
|
"mode": "0000644",
|
|
9790
|
-
"sha256": "
|
|
9790
|
+
"sha256": "fef1ab161dad460d5e2e95cfea66403745c0658e4551a90528a1318b9525ad7f"
|
|
9791
9791
|
},
|
|
9792
9792
|
{
|
|
9793
9793
|
"path": "src/module-loader/catalog.js",
|
|
@@ -11037,7 +11037,7 @@
|
|
|
11037
11037
|
{
|
|
11038
11038
|
"path": "tests/file_map.mjs",
|
|
11039
11039
|
"mode": "0000644",
|
|
11040
|
-
"sha256": "
|
|
11040
|
+
"sha256": "e426eea6852d2ea63b79d27e17b71c53031ca65ee22644192eb4807df26f7482"
|
|
11041
11041
|
},
|
|
11042
11042
|
{
|
|
11043
11043
|
"path": "tests/filing_abuse_matrix.mjs",
|
package/docs/file-map.md
CHANGED
|
@@ -140,6 +140,9 @@
|
|
|
140
140
|
│ ├── ui-design/ ← CORE-DOMAIN module (task 1003324 / ADR 0197, default-on): the design capability every builder on every instance gets — ships METHOD, never a world. Absorbs ADR 0081's layer: adapters/{claude-design,figma}/index.js (moved from src/ui/adapters/), scripts/{design-sync,figma-design-sync,validate-design}.js (moved from scripts/gds/, which keeps thin shims), docs/design-contract.md (moved from docs/). Declaration-only: contributes.skills design (the /design playbook, new) + design-sync + figma-design-sync; no routes/port/migrations, so disabling it touches no served surface. CLAUDE.md separates the platform FLOORS (the Fifteen Rule, the dark twins, no page :root, the 24px floor, AA in both modes, the kill switch) from the instance's WORLD (its packs + DESIGN.md). kit/ (task 1003320) is the look-before-you-ship kit: lib.js (the floors, the states/probes contracts, the action + expectation vocabularies, the WCAG maths, the stub spawner), serve.js (any surface through the doorway's real transforms + fixtures/), render.js (states × 1440/390/320 × dark/light, audited), probe.js (PASS/FAIL contracts), check-mock.js (platform rules + the instance's DESIGN.md taste bans, then the detector), tells.js (task 1003329 / ADR 0223: the anti-pattern detector as two tiers — floors FAIL, craft tells WARN, kit-ignore waivers per file; the rendered twins live in lib.js factsInPage), contrast.js + png.js; a page's <page>.states.json / <page>.probes.json sit BESIDE the surface (modules/public-landing/public/, modules/hall-ui/public/). Recipe: docs/recipes/ui-look-before-you-ship.md. skills/ (task 1003321 / ADR 0198) is the vendoring policy: policy.json (licence allowlist, verdicts that may land, sidecar fields, preamble markers), PREAMBLE.md (the platform preamble every vendored SKILL.md opens with), README.md (the roster — both origins scanned dangerous on 2026-08-28, landed as rebuild specs; nothing vendored — and the landing recipe); a landable copy goes under skills/vendor/<name>/ with PROVENANCE.md + LICENSE and is materialised into an instance's .claude/skills/ by claude-materialize.js step 1b. skills/<name>/ (task 1003325 / ADR 0220) are the in-house rebuilds from the specs, first-party and counted: design-taste-frontend (the default taste skill), design-taste-frontend-v1 (the earlier generation, kept distinct), high-end-visual-design (the polish pass), redesign-existing-projects (the in-place upgrade); each opens with the same preamble and carries a "Rebuilt from" section instead of a sidecar. impeccable/ (task 1003326 / ADR 0221) is the design-craft method as ONE skill with sub-commands: SKILL.md (the router, the menu, the craft floor, the bounded finish) + reference/<sub-command>.md (eighteen playbooks, one loaded per request); no hooks, no bypass agent, no telemetry, no live server. minimalist-ui/, industrial-brutalist-ui/, gpt-taste/, stitch-design-taste/ (task 1003327 / ADR 0230) are the four looks, composition grammars with no colours of their own: the palette is the instance's pack and the texture is a named entry in styles/; a substrate is an archetype that still ships both modes, gpt-taste's selection is derived from the route rather than rolled, and stitch-design-taste writes a DESIGN.md-shaped contract document whose thirteen are transcribed from the pack. image-to-code/, imagegen-frontend-web/, imagegen-frontend-mobile/, brandkit/ (task 1003328 / ADR 0222) are the image family under the hero-only rule: plates and hero objects only, the pair (JPEG dark plate + WebP alpha cutout) with a <file>.json sidecar beside each, through the instance's art pipeline key; the grove specimen's styles/grove/assets/ is the worked example. style/ (task 1003475 / ADR 0231) is the module's first FIRST-PARTY skill (no upstream, so a `## First party` section instead of `## Rebuilt from`): the /style session in which an owner authors their own look in four bounded steps, rendering the nearest library entries before asking, drafting inside the contract, retuning by measurement, and handing over the one-step adoption without ever writing config/branding.json. styles/ (task 1003323 / ADR 0219) is the style library: <name>/pack.json (the theme.ui overlay, exactly the fifteen) + DESIGN.md (the look's derived recipes, thirteen in lockstep with the pack) + mock.html (the specimen); chrome-world (pinned to the neutral pack), expedition, grove, blueprint (the first cool look, authored through /style at task 1003475); STYLE=<name> on the kit lays a look over the instance's pack through GDS_BRANDING_FILE; tests/ui_design_styles.mjs runs the world/cosmos/hall token suites once per alternate (UI_DESIGN_PACK). config/design-tokens.* + design-sources.json STAY in config/ as the host boundary; public-landing keeps its own key (first customer, not owner).
|
|
141
141
|
│ ├── character-anim/ ← consistent-character generative animation (ADR 0088) — graded painterly frame sets + manifest, the otb-character-review skill. CLAUDE.md, module.json.
|
|
142
142
|
│ ├── provisioning/ ← stands up + operates a Cloud Bongos instance's own infra (server/DB/domain/DNS/TLS) via the API (ADR 0111); web-tier request+state only, no cloud tokens — a separate control-plane runner drains the intents. Distinct from the `hosting` module (ADR 0092), which brokers compute workloads. CLAUDE.md, module.json.
|
|
143
|
+
│ ├── government/ ← CORE-DOMAIN module (ADR 0151 / BV1 goal 1000030, renamed governance→government by ADR 0174, default-on): Discord-style configurable ranks + permissions. The PERMISSION is the atomic authority unit and the four ranks (xenos/thetes/metic/archon) are the positions in the government; the module owns the code-defined permission catalog — each key flagged `system` (trust-boundary), `substrate` (enforced out of band) or operational (delegable) — plus the compatibility seed that reproduces the old rank thresholds exactly. ADR 0016 is preserved: authority is still resolved server-side, per request, uncached. The word `role` is deliberately absent — it is reserved for the crafts. CLAUDE.md, module.json, catalog.js, ranks.js, resolver.js, config.js, db.js, board.js + board-membership.js + membership-predicate.js, session-scopes.{js,json}, protected-surfaces.{js,json}, routes/ (government + rank-sync), pollers/ (board-expiry), migrations/.
|
|
144
|
+
│ ├── specialities/ ← CORE-DOMAIN module (task 1003855 / ADR 0296, default-on): role SUB-SPECIALITIES — a named bundle of knowledge, skills and an interaction contract sitting UNDER a craft. A craft says HOW you work (engineer/ideator/artist/ui); a speciality says WHAT you work on, and how a subject-matter expert wants to be engaged inside it. Deliberately thin over three surfaces that already exist — discipline-modes.json for craft→playbook, behavior_prefs.interaction for the per-session contract (a speciality contract rides the SAME GET /me → Conductor path rather than adding a second one), and the ADR 0083 manifest for declared skills; the only new machinery is the knowledge bundle. Training is two-fold: documents the builder supplied, plus learnings captured from sessions worked in the speciality (pointing at existing `learnings` rows, never a second corpus). Owned per-builder OR per-project, each carrying its owner's sharing decision (private/project/public) and a sellable flag that records INTENT only — ADR 0107 defers the marketplace, so there is nothing to list on. CLAUDE.md, module.json, specialities.js, db.js, routes/, migrations/.
|
|
145
|
+
│ ├── agents/ ← declarative agent registry (BV1 goal 1000038 Phase 1) — the one module that ships `default:false`, so shipped is not enabled and an instance opts in per ADR 0083. `.claude/agents/*.md` are reconciled at deploy into `agents_definitions` plus the per-fire `agents_runs` ledger; files-are-source (an instance-authored DB row is never clobbered), and `author_rank` is stamped from the author's LIVE DB rank because a committed file cannot grant authority (ADR 0016). Spawns consume the EXISTING read-only `grade` port, so v1 agents are observe-and-report only — no write principals — and every fire bills through the shared LLM cost cache. `spend.requiresPayer` (task 1003884): a fire spends real money for whoever called it, so the core REFUSES to enable the module on an instance whose join door is `open` with no payer configured — an account created seconds ago would otherwise spend the instance's money with nobody to bill. lib/spend-ceiling.js is the per-payer monthly ceiling, failing closed when it is exhausted OR unreadable; lib/fire-budget.js is the separate RATE ceiling. CLAUDE.md, module.json, spawn.js, lib/, routes/, pollers/ (event-router), migrations/.
|
|
143
146
|
│ └── demo/ ← BV1.R52 acceptance-proof module: one rank-gated write route + one namespaced migration proving the loader mounts a scaffolded module with zero core edits. CLAUDE.md, module.json.
|
|
144
147
|
│
|
|
145
148
|
├── scripts/
|
|
@@ -2292,5 +2292,7 @@ is load-bearing: the script throws rather than guess if it is missing, and
|
|
|
2292
2292
|
landed since 1.19.907 with no explicit bump. run 35516559010. (task 1002620)
|
|
2293
2293
|
1.19.909 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
2294
2294
|
landed since 1.19.908 with no explicit bump. run 35517426559. (task 1002620)
|
|
2295
|
+
1.19.910 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
2296
|
+
landed since 1.19.909 with no explicit bump. run 35519713709. (task 1002620)
|
|
2295
2297
|
---------------------------------------------------------------------------
|
|
2296
2298
|
```
|
package/package-lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.19.
|
|
3
|
+
"version": "1.19.910",
|
|
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.910",
|
|
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.910",
|
|
4
4
|
"description": "Cloud Bongos — the AI-first build platform core (GDS + platform surfaces + module system), installed as a versioned dependency (ADR 0108).",
|
|
5
5
|
"license": "AGPL-3.0-or-later",
|
|
6
6
|
"main": "src/platform-server.js",
|
package/scripts/gds/fitness.js
CHANGED
|
@@ -225,7 +225,7 @@ function checkGeneratedArtifactsFresh() {
|
|
|
225
225
|
try {
|
|
226
226
|
execFileSync(process.execPath, [path.join(__dirname, 'gen-file-map.js'), '--check'], { cwd: REPO_ROOT, stdio: 'pipe' });
|
|
227
227
|
} catch {
|
|
228
|
-
violations.push('docs/file-map.md generated sections (.claude/skills/, .claude/scheduled-tasks/) are STALE
|
|
228
|
+
violations.push('docs/file-map.md generated sections (.claude/skills/, .claude/scheduled-tasks/) are STALE, a skill/routine has no note in docs/file-map.notes.json, or a directory under modules/ has no file-map entry at all — run `node scripts/gds/gen-file-map.js --check` for the exact list, then fix and commit (ADR 0066; module coverage: task 1004066).');
|
|
229
229
|
}
|
|
230
230
|
try {
|
|
231
231
|
// Also structurally validates the spec (exit 1 on invalid OpenAPI 3.1) beyond the drift check (exit 2). Task 1918.
|
|
@@ -16,6 +16,16 @@
|
|
|
16
16
|
// • every skill/routine dir on disk MUST have a note → a new skill can't land undescribed;
|
|
17
17
|
// • every note MUST map to a dir that exists → the map can't carry ghosts;
|
|
18
18
|
// • the rendered block MUST match what's committed → no stale tree.
|
|
19
|
+
// MODULE COVERAGE (task 1004066). The same honor system that let a skill land unmapped let a whole
|
|
20
|
+
// MODULE land unmapped: `modules/agents`, `modules/government` and `modules/specialities` were all
|
|
21
|
+
// on disk with nothing in the map, and the two that got fixed before this got fixed BY HAND — which
|
|
22
|
+
// is the point, since hand-patching a drifting map does not stop the drift. The modules/ block is
|
|
23
|
+
// not generated (each entry is a paragraph of prose no directory walk could write, and generating
|
|
24
|
+
// the fenced repo tree is the expensive option this task ruled out), so what is enforced is the
|
|
25
|
+
// cheap half: a set comparison asserting every directory under modules/ is documented SOMEWHERE in
|
|
26
|
+
// the map — either a `modules/<key>/…` path reference or a `<key>/` entry inside the modules block.
|
|
27
|
+
// A new module now fails --check until someone writes its line.
|
|
28
|
+
//
|
|
19
29
|
// scripts/gds/ is deliberately NOT generated: it is a curated highlight reel of ~20 of ~70 files
|
|
20
30
|
// (generating it would bloat it with low-signal entries), and it sits mid-tree inside the big
|
|
21
31
|
// fenced repo tree where HTML-comment markers can't live cleanly. It stays hand-curated.
|
|
@@ -51,6 +61,13 @@ const SECTIONS = [
|
|
|
51
61
|
{ id: 'scheduled-tasks', dir: '.claude/scheduled-tasks', notesKey: 'scheduled-tasks' },
|
|
52
62
|
];
|
|
53
63
|
|
|
64
|
+
// modules/ is NOT generated (task 1004066): each entry is a paragraph of prose no directory walk
|
|
65
|
+
// could write, and generating it would force the whole fenced repo tree through a generator —
|
|
66
|
+
// explicitly out of scope. What IS enforceable is COVERAGE: every module on disk must be documented
|
|
67
|
+
// somewhere in the map. That set comparison is the cheap half of the same contract the skills
|
|
68
|
+
// section already carries, and it is what stops a whole vertical slice landing unmapped.
|
|
69
|
+
const MODULES_DIR = 'modules';
|
|
70
|
+
|
|
54
71
|
const begin = (id) => `<!-- BEGIN GENERATED FILE-MAP ${id} (scripts/gds/gen-file-map.js — do not hand-edit; notes live in docs/file-map.notes.json) -->`;
|
|
55
72
|
const end = (id) => `<!-- END GENERATED FILE-MAP ${id} -->`;
|
|
56
73
|
|
|
@@ -107,6 +124,72 @@ function buildModel() {
|
|
|
107
124
|
return SECTIONS.map((s) => buildSection(s, notes));
|
|
108
125
|
}
|
|
109
126
|
|
|
127
|
+
// ── module coverage (task 1004066) ────────────────────────────────────────────
|
|
128
|
+
// A module counts as documented when the map either references its PATH (`modules/<key>/…`
|
|
129
|
+
// anywhere) or carries a `<key>/` entry line inside the `modules/` tree block. Both forms are in
|
|
130
|
+
// use today, so the check accepts both rather than inventing a third the map would fail on.
|
|
131
|
+
|
|
132
|
+
// Both matchers CAPTURE the whole path segment rather than testing for a given key. That is what
|
|
133
|
+
// makes the prefix hazard structural instead of something a lookahead has to remember: a mention of
|
|
134
|
+
// `modules/agents-extra` yields the key `agents-extra`, so it can never vouch for `agents`. It also
|
|
135
|
+
// means the map is scanned ONCE per run instead of twice per module.
|
|
136
|
+
const PATH_REF_RE = /modules\/([A-Za-z0-9_.-]+)/g;
|
|
137
|
+
const ENTRY_RE = /[├└]──\s+([A-Za-z0-9_.-]+)\//g;
|
|
138
|
+
|
|
139
|
+
// The slice of the fenced repo tree between the `├── modules/` header and the next sibling at the
|
|
140
|
+
// same depth. Scoping the entry search here matters: `.claude/skills/` lists dirs in the same
|
|
141
|
+
// `├── name/` shape, so an unscoped match would let a SKILL named after a module vouch for it.
|
|
142
|
+
function modulesBlock(mapText) {
|
|
143
|
+
const lines = String(mapText).split(/\r?\n/);
|
|
144
|
+
const start = lines.findIndex((l) => new RegExp(`^[├└]── ${MODULES_DIR}/`).test(l));
|
|
145
|
+
if (start === -1) return '';
|
|
146
|
+
let stop = lines.length;
|
|
147
|
+
for (let i = start + 1; i < lines.length; i += 1) {
|
|
148
|
+
if (/^[├└]── /.test(lines[i])) { stop = i; break; }
|
|
149
|
+
}
|
|
150
|
+
return lines.slice(start, stop).join('\n');
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Every module key the map documents: any `modules/<key>` path reference anywhere, plus any
|
|
154
|
+
// `<key>/` entry line inside the modules block. Both forms are in use today, so both count.
|
|
155
|
+
function documentedModuleKeys(mapText, block) {
|
|
156
|
+
const keys = new Set();
|
|
157
|
+
for (const m of String(mapText).matchAll(PATH_REF_RE)) keys.add(m[1]);
|
|
158
|
+
for (const m of String(block).matchAll(ENTRY_RE)) keys.add(m[1]);
|
|
159
|
+
return keys;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// The single-key form — the readable one to assert against.
|
|
163
|
+
function isModuleDocumented(mapText, block, key) {
|
|
164
|
+
return documentedModuleKeys(mapText, block).has(key);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Every immediate subdirectory of modules/, or [] when the tree carries no modules/ at all (a
|
|
168
|
+
// consumer instance that enabled none — nothing to document).
|
|
169
|
+
function listModules() {
|
|
170
|
+
return listSubdirs(path.join(REPO_ROOT, MODULES_DIR));
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// The set comparison. Returns { onDisk, undocumented, defects } — `defects` is the non-vacuity
|
|
174
|
+
// channel: this check breaks by comparing two empty sets and reporting nothing, so a state that
|
|
175
|
+
// makes the comparison meaningless (modules on disk but no readable map, or no modules/ block to
|
|
176
|
+
// read entries out of) is reported as a SCAN DEFECT rather than passing quietly.
|
|
177
|
+
function moduleCoverage(mapText) {
|
|
178
|
+
const onDisk = listModules();
|
|
179
|
+
const defects = [];
|
|
180
|
+
if (!onDisk.length) return { onDisk, undocumented: [], defects };
|
|
181
|
+
const text = typeof mapText === 'string' ? mapText : (fs.existsSync(MAP_PATH) ? fs.readFileSync(MAP_PATH, 'utf8') : '');
|
|
182
|
+
if (!text.trim()) {
|
|
183
|
+
defects.push(`${onDisk.length} module(s) on disk but docs/file-map.md is missing or empty — the module-coverage check cannot run`);
|
|
184
|
+
return { onDisk, undocumented: [], defects };
|
|
185
|
+
}
|
|
186
|
+
const block = modulesBlock(text);
|
|
187
|
+
if (!block) defects.push('docs/file-map.md has no `modules/` block in its repo tree — module entries cannot be read (path references are still checked)');
|
|
188
|
+
const documented = documentedModuleKeys(text, block);
|
|
189
|
+
const undocumented = onDisk.filter((key) => !documented.has(key));
|
|
190
|
+
return { onDisk, undocumented, defects };
|
|
191
|
+
}
|
|
192
|
+
|
|
110
193
|
// Render one section's fenced tree block (the bytes that live between its markers). Arrows are
|
|
111
194
|
// column-aligned to the longest label, matching the hand-formatted look of the rest of the map.
|
|
112
195
|
function renderSection(model) {
|
|
@@ -144,6 +227,7 @@ function computeWrites() {
|
|
|
144
227
|
const stale = model.flatMap((m) => m.stale);
|
|
145
228
|
const writes = [];
|
|
146
229
|
let markersMissing = [];
|
|
230
|
+
let mapText = '';
|
|
147
231
|
if (fs.existsSync(MAP_PATH)) {
|
|
148
232
|
let cur = fs.readFileSync(MAP_PATH, 'utf8');
|
|
149
233
|
let next = cur;
|
|
@@ -152,8 +236,10 @@ function computeWrites() {
|
|
|
152
236
|
next = injectSection(next, m);
|
|
153
237
|
}
|
|
154
238
|
if (!sameContent(next, cur)) writes.push({ full: MAP_PATH, content: next, rel: 'docs/file-map.md' });
|
|
239
|
+
mapText = next; // coverage reads the POST-generation text, so a regen never changes the verdict
|
|
155
240
|
}
|
|
156
|
-
|
|
241
|
+
const modules = moduleCoverage(mapText);
|
|
242
|
+
return { model, writes, missing, stale, markersMissing, modules };
|
|
157
243
|
}
|
|
158
244
|
|
|
159
245
|
function main() {
|
|
@@ -162,7 +248,7 @@ function main() {
|
|
|
162
248
|
process.stdout.write(JSON.stringify(buildModel(), null, 2) + '\n');
|
|
163
249
|
return;
|
|
164
250
|
}
|
|
165
|
-
const { writes, missing, stale, markersMissing } = computeWrites();
|
|
251
|
+
const { writes, missing, stale, markersMissing, modules } = computeWrites();
|
|
166
252
|
|
|
167
253
|
// A missing note (dir without a note) or a stale note (note without a dir) is a hard failure in
|
|
168
254
|
// BOTH modes: in --check it fails CI; in write mode we still report it (and render a visible
|
|
@@ -171,6 +257,8 @@ function main() {
|
|
|
171
257
|
if (missing.length) problems.push(`notes MISSING for: ${missing.join(', ')} — add a one-line note to docs/file-map.notes.json`);
|
|
172
258
|
if (stale.length) problems.push(`notes are STALE (no such dir) — remove from docs/file-map.notes.json: ${stale.join(', ')}`);
|
|
173
259
|
if (markersMissing.length) problems.push(`docs/file-map.md is missing generated markers for: ${markersMissing.join(', ')}`);
|
|
260
|
+
if (modules.undocumented.length) problems.push(`modules with NO docs/file-map.md entry: ${modules.undocumented.map((k) => `${MODULES_DIR}/${k}`).join(', ')} — add an entry to the modules/ block of docs/file-map.md (task 1004066)`);
|
|
261
|
+
for (const d of modules.defects) problems.push(`module-coverage SCAN DEFECT — ${d}`);
|
|
174
262
|
|
|
175
263
|
if (checkOnly) {
|
|
176
264
|
if (writes.length || problems.length) {
|
|
@@ -197,6 +285,12 @@ if (require.main === module) main();
|
|
|
197
285
|
module.exports = {
|
|
198
286
|
materializedNames,
|
|
199
287
|
SECTIONS,
|
|
288
|
+
MODULES_DIR,
|
|
289
|
+
listModules,
|
|
290
|
+
modulesBlock,
|
|
291
|
+
documentedModuleKeys,
|
|
292
|
+
isModuleDocumented,
|
|
293
|
+
moduleCoverage,
|
|
200
294
|
begin,
|
|
201
295
|
end,
|
|
202
296
|
buildModel,
|
|
@@ -27,7 +27,7 @@ const STEPS = [
|
|
|
27
27
|
{ key: 'fitness', label: 'fitness.js (architecture + budgets + freshness gate)', argv: ['scripts/gds/fitness.js'], fix: 'read the ✗ lines above; each names its fix' },
|
|
28
28
|
{ key: 'repo-map', label: 'nested CLAUDE.md symbol blocks fresh', argv: ['scripts/gds/gen-repo-map.js', '--check-block'], fix: 'node scripts/gds/gen-repo-map.js' },
|
|
29
29
|
{ key: 'session-index', label: 'CLAUDE.md §13 session snippet fresh', argv: ['scripts/gds/gen-session-index.js', '--check-block'], fix: 'node scripts/gds/gen-session-index.js' },
|
|
30
|
-
{ key: 'file-map', label: 'docs/file-map.md skills/routines sections fresh', argv: ['scripts/gds/gen-file-map.js', '--check'], fix: 'node scripts/gds/gen-file-map.js (+ a note in docs/file-map.notes.json for a new skill)' },
|
|
30
|
+
{ key: 'file-map', label: 'docs/file-map.md skills/routines sections fresh + every module documented', argv: ['scripts/gds/gen-file-map.js', '--check'], fix: 'node scripts/gds/gen-file-map.js (+ a note in docs/file-map.notes.json for a new skill, or a modules/ entry in docs/file-map.md for a new module)' },
|
|
31
31
|
{ key: 'api-docs', label: 'docs/api/openapi.json + api-reference fresh', argv: ['scripts/gds/gen-api-docs.js', '--check'], fix: 'node scripts/gds/gen-api-docs.js' },
|
|
32
32
|
{ key: 'copy-registry', label: 'docs/copy-registry.json fresh', argv: ['scripts/gds/copy-inventory.js', '--check'], fix: 'node scripts/gds/copy-inventory.js', optional: true },
|
|
33
33
|
{ key: 'linkify', label: 'no bare #NNN task refs in committed markdown', argv: ['scripts/gds/linkify-refs.js', '--check'], fix: 'node scripts/gds/linkify-refs.js', optional: true },
|
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.910'; // 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');
|
package/tests/file_map.mjs
CHANGED
|
@@ -93,6 +93,65 @@ test('generation is deterministic + idempotent (a second run is a no-op)', () =>
|
|
|
93
93
|
assert.match(r2.stdout, /already current/);
|
|
94
94
|
});
|
|
95
95
|
|
|
96
|
+
// ── module coverage (task 1004066) ────────────────────────────────────────────
|
|
97
|
+
// The check is a set comparison over a directory listing, and a set comparison breaks by
|
|
98
|
+
// comparing two empty sets and passing. Every case below is written so that a matcher which
|
|
99
|
+
// vouches for everything, or a scan which enumerates nothing, FAILS here.
|
|
100
|
+
|
|
101
|
+
test('every dir under modules/ is documented in file-map.md — and the set being compared is not empty', () => {
|
|
102
|
+
const cov = gen.moduleCoverage();
|
|
103
|
+
assert.ok(cov.onDisk.length > 0, 'modules/ enumerated to nothing — the coverage check would be vacuous');
|
|
104
|
+
assert.deepEqual(cov.defects, [], `module-coverage scan defects: ${cov.defects.join('; ')}`);
|
|
105
|
+
assert.deepEqual(cov.undocumented, [], `modules with no file-map entry: ${cov.undocumented.join(', ')}`);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
test('--check FAILS (exit 2) on a module with no file-map entry — the trap case', () => {
|
|
109
|
+
const probe = path.join(ROOT, 'modules', '__filemap_probe__');
|
|
110
|
+
try {
|
|
111
|
+
mkdirSync(probe, { recursive: true });
|
|
112
|
+
writeFileSync(path.join(probe, 'module.json'), '{"key":"__filemap_probe__"}\n');
|
|
113
|
+
const cov = gen.moduleCoverage();
|
|
114
|
+
assert.ok(cov.onDisk.includes('__filemap_probe__'), 'the probe module must be enumerated');
|
|
115
|
+
assert.deepEqual(cov.undocumented, ['__filemap_probe__'], 'an undocumented module must be the ONLY finding');
|
|
116
|
+
const r = run(['--check']);
|
|
117
|
+
assert.equal(r.code, 2, 'an undocumented module must fail the gate');
|
|
118
|
+
assert.match(r.stderr, /modules with NO docs\/file-map\.md entry/);
|
|
119
|
+
assert.match(r.stderr, /__filemap_probe__/);
|
|
120
|
+
} finally {
|
|
121
|
+
rmSync(probe, { recursive: true, force: true });
|
|
122
|
+
}
|
|
123
|
+
// Asserted on the module finding rather than the exit code: --check reports every problem class
|
|
124
|
+
// at once, and a checkout carrying unrelated local noise (materialised module skills with no
|
|
125
|
+
// note) would otherwise make this read as "the probe is still there".
|
|
126
|
+
const after = run(['--check']);
|
|
127
|
+
assert.equal(gen.moduleCoverage().undocumented.length, 0, 'coverage is clean once the probe module is removed');
|
|
128
|
+
assert.doesNotMatch(after.stderr || '', /modules with NO docs\/file-map\.md entry/);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
test('the matcher discriminates: a real module passes, an absent one does not, and a prefix cannot vouch', () => {
|
|
132
|
+
const md = readFileSync(path.join(ROOT, 'docs', 'file-map.md'), 'utf8');
|
|
133
|
+
const block = gen.modulesBlock(md);
|
|
134
|
+
assert.ok(block.length > 0, 'the modules/ tree block must be findable — an empty block would make entries unreadable');
|
|
135
|
+
assert.ok(gen.isModuleDocumented(md, block, 'government'), 'a documented module reads as documented');
|
|
136
|
+
assert.equal(gen.isModuleDocumented(md, block, '__no_such_module__'), false, 'an absent module must NOT read as documented');
|
|
137
|
+
// A bare substring test would let `modules/agents-extra` vouch for `agents`.
|
|
138
|
+
assert.equal(
|
|
139
|
+
gen.isModuleDocumented('see modules/agents-extra/thing.js', '', 'agents'),
|
|
140
|
+
false,
|
|
141
|
+
'a longer path sharing the prefix must not count as an entry',
|
|
142
|
+
);
|
|
143
|
+
// A skill dir listed in the same `├── name/` shape elsewhere in the map must not vouch either:
|
|
144
|
+
// the entry search is scoped to the modules/ block.
|
|
145
|
+
assert.equal(gen.modulesBlock('├── .claude/skills/\n├── recall/ ← a skill\n'), '', 'no modules/ block → no block to read entries from');
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
test('module coverage reports a SCAN DEFECT rather than passing when the map cannot be read', () => {
|
|
149
|
+
const cov = gen.moduleCoverage('');
|
|
150
|
+
assert.ok(cov.onDisk.length > 0);
|
|
151
|
+
assert.equal(cov.defects.length, 1, 'an unreadable map is a defect, not a pass');
|
|
152
|
+
assert.match(cov.defects[0], /cannot run/);
|
|
153
|
+
});
|
|
154
|
+
|
|
96
155
|
test('every note maps to a real skill/routine (sidecar has no dangling keys)', () => {
|
|
97
156
|
const notes = JSON.parse(readFileSync(path.join(ROOT, 'docs', 'file-map.notes.json'), 'utf8'));
|
|
98
157
|
for (const section of gen.SECTIONS) {
|