hummbl-bibliography 1.0.0

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.
Files changed (95) hide show
  1. package/.cascade/rules/hummbl-base120.md +107 -0
  2. package/.github/CODEOWNERS +17 -0
  3. package/.github/ISSUE_TEMPLATE/bug_report.md +24 -0
  4. package/.github/ISSUE_TEMPLATE/feature_request.md +10 -0
  5. package/.github/ISSUE_TEMPLATE/new-entry.md +79 -0
  6. package/.github/ISSUE_TEMPLATE/quality-improvement.md +71 -0
  7. package/.github/PULL_REQUEST_TEMPLATE.md +15 -0
  8. package/.github/dependabot.yml +17 -0
  9. package/.github/workflows/ci.yml +98 -0
  10. package/.github/workflows/doi-enrichment.yml +77 -0
  11. package/.github/workflows/security-audit.yml +92 -0
  12. package/.github/workflows/stats-report.yml +59 -0
  13. package/.github/workflows/validate-models.yml +194 -0
  14. package/.github/workflows/validate.yml +152 -0
  15. package/.husky/pre-commit +15 -0
  16. package/.husky/validation-rules.json +11 -0
  17. package/CHANGELOG.md +228 -0
  18. package/CONTRIBUTING.md +110 -0
  19. package/CONTRIBUTORS.md +257 -0
  20. package/DEVELOPMENT.md +110 -0
  21. package/Day_1_Audit_Worksheet.md +64 -0
  22. package/LICENSE +21 -0
  23. package/README.md +213 -0
  24. package/SECURITY.md +16 -0
  25. package/SITREP.md +141 -0
  26. package/bibliography/T10_collaboration.bib +281 -0
  27. package/bibliography/T11_security.bib +311 -0
  28. package/bibliography/T12_complexity.bib +272 -0
  29. package/bibliography/T13_reasoning.bib +231 -0
  30. package/bibliography/T1_canonical.bib +236 -0
  31. package/bibliography/T2_empirical.bib +258 -0
  32. package/bibliography/T3_applied.bib +219 -0
  33. package/bibliography/T4_agentic.bib +281 -0
  34. package/bibliography/T5_engineering.bib +243 -0
  35. package/bibliography/T6_governance.bib +277 -0
  36. package/bibliography/T7_emerging.bib +228 -0
  37. package/bibliography/T8_cognition.bib +260 -0
  38. package/bibliography/T9_economics.bib +275 -0
  39. package/bibliography/hummbl-transformations.json +84 -0
  40. package/dist/unified-bibliography.json +5699 -0
  41. package/docs/CONTRIBUTING.md +240 -0
  42. package/docs/GAP_ANALYSIS.md +142 -0
  43. package/docs/MULTI_AGENT_COORDINATION_PROTOCOL.md +700 -0
  44. package/docs/QUALITY_AUDIT_REPORT.md +576 -0
  45. package/docs/QUALITY_STANDARDS.md +350 -0
  46. package/docs/TRANSFORMATION_GUIDE.md +337 -0
  47. package/docs/metrics/model-accuracy.md +150 -0
  48. package/governance/CAES_CANONICAL.sha256 +1 -0
  49. package/governance/CAES_SPEC.md +107 -0
  50. package/governance/CAES_VERSION +1 -0
  51. package/governance/lexicon/ALLOWLIST_POLICY.md +63 -0
  52. package/governance/lexicon/CANONICALIZATION.md +63 -0
  53. package/governance/lexicon/acronym.schema.json +153 -0
  54. package/governance/lexicon/acronym_allowlist.txt +237 -0
  55. package/governance/lexicon/acronyms.v0.2.json +2555 -0
  56. package/llms.txt +1105 -0
  57. package/mappings/arcana_citations.json +219 -0
  58. package/mappings/bki_evidence.json +384 -0
  59. package/package.json +25 -0
  60. package/reports/.gitkeep +0 -0
  61. package/reports/citation_graph.json +119335 -0
  62. package/scripts/add_nist_tags.py +437 -0
  63. package/scripts/annotate_dois.py +204 -0
  64. package/scripts/check_palace_aliases.py +200 -0
  65. package/scripts/ingest_to_open_brain.py +307 -0
  66. package/scripts/monthly-review.sh +166 -0
  67. package/scripts/setup-hooks.sh +107 -0
  68. package/scripts/test_check_palace_aliases.py +194 -0
  69. package/sources/bki.bib +57 -0
  70. package/sources/theoretical-foundations.bib +589 -0
  71. package/toolkit/README.md +360 -0
  72. package/toolkit/docs/generated/quick-reference.md +179 -0
  73. package/toolkit/package-lock.json +1140 -0
  74. package/toolkit/package.json +66 -0
  75. package/toolkit/scripts/check-memory-palace-aliases.js +230 -0
  76. package/toolkit/scripts/check-memory-palace-aliases.test.js +297 -0
  77. package/toolkit/scripts/generate-docs.js +223 -0
  78. package/toolkit/src/check-duplicates.js +225 -0
  79. package/toolkit/src/check-required-fields.js +138 -0
  80. package/toolkit/src/citation-graph.js +425 -0
  81. package/toolkit/src/extensions/beyondBase120Audit.ts +250 -0
  82. package/toolkit/src/extensions/memoryPalace.ts +438 -0
  83. package/toolkit/src/extract-keywords.js +190 -0
  84. package/toolkit/src/find-missing-dois.js +178 -0
  85. package/toolkit/src/fix-duplicates.js +140 -0
  86. package/toolkit/src/merge-entries.js +29 -0
  87. package/toolkit/src/query.js +281 -0
  88. package/toolkit/src/stats.js +244 -0
  89. package/toolkit/src/test-validation.js +117 -0
  90. package/toolkit/src/utils/modelRegistry.ts +193 -0
  91. package/toolkit/src/utils/monitorModels.ts +150 -0
  92. package/toolkit/src/utils/validateModelCode.ts +196 -0
  93. package/toolkit/src/validate.js +251 -0
  94. package/toolkit/src/watch.js +100 -0
  95. package/toolkit/tsconfig.json +25 -0
@@ -0,0 +1,438 @@
1
+ /**
2
+ * Memory Palace — Governed Extension Registry for Beyond-Base120 Mental Models
3
+ *
4
+ * Architecture:
5
+ * - Base120 is a closed, versioned canon: 6 transformations × 20 models = 120.
6
+ * It is validated by validateModelCode.ts. No model enters Base120 without a
7
+ * formal extension process and SemVer bump.
8
+ *
9
+ * - The Memory Palace is an open, governed extension library: philosophical lenses,
10
+ * enacted governance archetypes, domain frameworks, and cross-disciplinary models
11
+ * that are VALID and GOVERNED but do not (yet) have a Base120 code.
12
+ *
13
+ * Every entry in the Memory Palace must be registered here with:
14
+ * - A unique slug (kebab-case, e.g. "antifragility")
15
+ * - A source room (category / origin cluster)
16
+ * - A canonical name (how it appears in text)
17
+ * - Aliases (variant names that should resolve to the same entry)
18
+ * - A source reference (where the model originates)
19
+ * - Optional Base120 candidate code (if it's being nominated for promotion)
20
+ *
21
+ * Drift detection: if a model appears in content with a name that does NOT match
22
+ * any canonical name or alias in this registry, it is flagged as UNREGISTERED.
23
+ *
24
+ * Duplicate detection: if two entries share a canonical name or alias, the registry
25
+ * fails to load (hard error).
26
+ *
27
+ * Promotion path: an entry with a candidate_code may be promoted to Base120 via
28
+ * the standard extension process. At promotion, it is removed from Memory Palace
29
+ * and added to BASE120_MODELS in validateModelCode.ts.
30
+ */
31
+
32
+ // ---------------------------------------------------------------------------
33
+ // Types
34
+ // ---------------------------------------------------------------------------
35
+
36
+ export type MemoryPalaceRoom =
37
+ | 'ARCANA' // Philosophical lenses from the ARCANA 28-agent platform
38
+ | 'PRAXIS' // Enacted governance archetypes (Aurelius, Cincinnatus, etc.)
39
+ | 'BKI' // Belonging as Knowledge Infrastructure cluster
40
+ | 'TALEB' // Nassim Taleb framework (Antifragility, Black Swan, etc.)
41
+ | 'SYSTEMS' // Systems thinking beyond Base120 SY models
42
+ | 'EPISTEMICS' // Epistemology and reasoning models
43
+ | 'DOMAIN' // Domain-specific models (healthcare, finance, etc.)
44
+ | 'PENDING'; // Nominated but not yet assigned to a room
45
+
46
+ export type SourceType =
47
+ | 'historical-person'
48
+ | 'living-person'
49
+ | 'mythologized-historical'
50
+ | 'fictional-authored'
51
+ | 'mythological'
52
+ | 'collective-pattern';
53
+
54
+ export interface MemoryPalaceEntry {
55
+ slug: string; // Unique kebab-case identifier
56
+ room: MemoryPalaceRoom; // Organisational category
57
+ canonical_name: string; // How this model appears in governed text
58
+ aliases: readonly string[]; // Variant names that resolve to this entry
59
+ source: string; // Origin (author, work, platform)
60
+ source_type?: SourceType; // Machine-readable epistemic authority type
61
+ description: string; // One-line description
62
+ candidate_code?: string; // Base120 nomination code, if any (e.g. "SY21")
63
+ tags: readonly string[]; // Search / filter tags
64
+ }
65
+
66
+ // ---------------------------------------------------------------------------
67
+ // Memory Palace Registry
68
+ // ---------------------------------------------------------------------------
69
+
70
+ export const MEMORY_PALACE: readonly MemoryPalaceEntry[] = [
71
+
72
+ // ── TALEB cluster ─────────────────────────────────────────────────────────
73
+
74
+ {
75
+ slug: 'antifragility',
76
+ room: 'TALEB',
77
+ canonical_name: 'Antifragility',
78
+ aliases: ['Anti-fragility', 'antifragile'],
79
+ source: 'Nassim Nicholas Taleb, Antifragile (2012)',
80
+ source_type: 'living-person',
81
+ description: 'Systems that gain from disorder, uncertainty, and volatility',
82
+ tags: ['risk', 'resilience', 'uncertainty', 'taleb'],
83
+ },
84
+ {
85
+ slug: 'black-swan',
86
+ room: 'TALEB',
87
+ canonical_name: 'Black Swan',
88
+ aliases: ['Black Swan Event', 'black swan theory'],
89
+ source: 'Nassim Nicholas Taleb, The Black Swan (2007)',
90
+ source_type: 'living-person',
91
+ description: 'High-impact unpredictable events rationalized in hindsight',
92
+ tags: ['risk', 'uncertainty', 'prediction', 'taleb'],
93
+ },
94
+ {
95
+ slug: 'skin-in-the-game',
96
+ room: 'TALEB',
97
+ canonical_name: 'Skin in the Game',
98
+ aliases: ['skin-in-the-game', 'risk-bearing alignment'],
99
+ source: 'Nassim Nicholas Taleb, Skin in the Game (2018)',
100
+ source_type: 'living-person',
101
+ description: 'Alignment of decision-making with risk-bearing as governance mechanism',
102
+ tags: ['governance', 'incentives', 'accountability', 'taleb'],
103
+ },
104
+ {
105
+ slug: 'via-negativa',
106
+ room: 'TALEB',
107
+ canonical_name: 'Via Negativa',
108
+ aliases: ['subtraction principle'],
109
+ source: 'Nassim Nicholas Taleb; apophatic theology tradition',
110
+ source_type: 'living-person',
111
+ description: 'Improvement through subtraction rather than addition',
112
+ tags: ['design', 'simplicity', 'taleb'],
113
+ },
114
+ {
115
+ slug: 'lindy-effect',
116
+ room: 'TALEB',
117
+ canonical_name: 'Lindy Effect',
118
+ aliases: ['Lindy', 'lindy filter'],
119
+ source: 'Nassim Nicholas Taleb (formalised), Benoit Mandelbrot',
120
+ source_type: 'living-person',
121
+ description: 'Expected lifespan of non-perishable things scales with current age',
122
+ tags: ['longevity', 'time', 'durability', 'taleb'],
123
+ },
124
+ {
125
+ slug: 'survivorship-bias',
126
+ room: 'TALEB',
127
+ canonical_name: 'Survivorship Bias',
128
+ aliases: ['survival bias'],
129
+ source: 'Abraham Wald (WWII), popularised by Taleb',
130
+ source_type: 'historical-person',
131
+ description: 'Focusing on survivors while ignoring those that failed',
132
+ tags: ['bias', 'statistics', 'reasoning'],
133
+ },
134
+ {
135
+ slug: 'fat-tails',
136
+ room: 'TALEB',
137
+ canonical_name: 'Fat Tails',
138
+ aliases: ['fat-tail', 'heavy tails', 'power law tails'],
139
+ source: 'Nassim Nicholas Taleb, The Statistical Consequences of Fat Tails (2020)',
140
+ source_type: 'living-person',
141
+ description: 'Extreme events underweighted by standard statistical models',
142
+ tags: ['statistics', 'risk', 'distribution', 'taleb'],
143
+ },
144
+
145
+ // ── EPISTEMICS ─────────────────────────────────────────────────────────────
146
+
147
+ {
148
+ slug: 'ooda-loop',
149
+ room: 'EPISTEMICS',
150
+ canonical_name: 'OODA Loop',
151
+ aliases: ['OODA', 'observe-orient-decide-act'],
152
+ source: 'Colonel John Boyd, USAF (1970s)',
153
+ source_type: 'historical-person',
154
+ description: 'Observe-Orient-Decide-Act decision cycle for dynamic environments',
155
+ tags: ['decision-making', 'military', 'strategy', 'cycles'],
156
+ },
157
+ {
158
+ slug: 'circle-of-competence',
159
+ room: 'EPISTEMICS',
160
+ canonical_name: 'Circle of Competence',
161
+ aliases: ['competence boundary'],
162
+ source: 'Warren Buffett and Charlie Munger, Berkshire Hathaway letters',
163
+ source_type: 'living-person',
164
+ description: 'Knowing the boundaries of one\'s reliable knowledge domain',
165
+ tags: ['epistemics', 'decision-making', 'humility'],
166
+ },
167
+ {
168
+ slug: 'map-vs-territory',
169
+ room: 'EPISTEMICS',
170
+ canonical_name: 'Map vs Territory',
171
+ aliases: ['Map is not the Territory', 'map-territory distinction'],
172
+ source: 'Alfred Korzybski, Science and Sanity (1933)',
173
+ source_type: 'historical-person',
174
+ description: 'Models of reality are not reality itself; confusing them is a category error',
175
+ tags: ['epistemics', 'representation', 'abstraction'],
176
+ },
177
+ {
178
+ slug: 'occams-razor',
179
+ room: 'EPISTEMICS',
180
+ canonical_name: "Occam's Razor",
181
+ aliases: ["Ockham's razor", 'parsimony principle'],
182
+ source: 'William of Ockham (~1320)',
183
+ source_type: 'historical-person',
184
+ description: 'Prefer the simplest explanation that fits the evidence',
185
+ tags: ['epistemics', 'simplicity', 'reasoning'],
186
+ },
187
+ {
188
+ slug: 'hanlons-razor',
189
+ room: 'EPISTEMICS',
190
+ canonical_name: "Hanlon's Razor",
191
+ aliases: ['razor of charitable interpretation'],
192
+ source: 'Robert J. Hanlon (attributed)',
193
+ source_type: 'mythologized-historical',
194
+ description: 'Never attribute to malice what can be explained by incompetence',
195
+ tags: ['epistemics', 'attribution', 'reasoning'],
196
+ },
197
+ {
198
+ slug: 'regression-to-mean',
199
+ room: 'EPISTEMICS',
200
+ canonical_name: 'Regression to the Mean',
201
+ aliases: ['regression toward the mean', 'mean reversion'],
202
+ source: 'Francis Galton (1886)',
203
+ source_type: 'historical-person',
204
+ description: 'Extreme measurements tend toward the average on re-measurement',
205
+ tags: ['statistics', 'bias', 'prediction'],
206
+ },
207
+
208
+ // ── ARCANA lenses ─────────────────────────────────────────────────────────
209
+
210
+ {
211
+ slug: 'mimetic-desire',
212
+ room: 'ARCANA',
213
+ canonical_name: 'Mimetic Desire',
214
+ aliases: ['mimetic theory', 'triangular desire'],
215
+ source: 'René Girard, Deceit, Desire and the Novel (1961)',
216
+ source_type: 'historical-person',
217
+ description: 'Desire is borrowed from models/rivals, not intrinsic to objects',
218
+ tags: ['girard', 'desire', 'social', 'arcana'],
219
+ },
220
+ {
221
+ slug: 'scapegoat-mechanism',
222
+ room: 'ARCANA',
223
+ canonical_name: 'Scapegoat Mechanism',
224
+ aliases: ['scapegoating', 'sacrificial mechanism'],
225
+ source: 'René Girard, The Scapegoat (1982)',
226
+ source_type: 'historical-person',
227
+ description: 'Communities resolve mimetic crises through unanimous violence against an arbitrary victim',
228
+ tags: ['girard', 'violence', 'social', 'arcana'],
229
+ },
230
+
231
+ // ── PRAXIS lenses ─────────────────────────────────────────────────────────
232
+
233
+ {
234
+ slug: 'aurelius',
235
+ room: 'PRAXIS',
236
+ canonical_name: 'Aurelius Lens',
237
+ aliases: ['Marcus Aurelius', 'Reluctant Sovereign'],
238
+ source: 'Marcus Aurelius, Meditations (167–180 CE); PRAXIS module v0.1',
239
+ source_type: 'historical-person',
240
+ description: 'Power exercised with continuous self-audit; governance as private practice made institutional',
241
+ tags: ['praxis', 'stoicism', 'governance', 'accountability'],
242
+ },
243
+ {
244
+ slug: 'cincinnatus',
245
+ room: 'PRAXIS',
246
+ canonical_name: 'Cincinnatus Lens',
247
+ aliases: ['Cincinnatus', 'Temporary Sovereign'],
248
+ source: 'Lucius Quinctius Cincinnatus (458 BCE); PRAXIS module v0.1',
249
+ source_type: 'mythologized-historical',
250
+ description: 'Legitimacy through voluntary relinquishment of authority',
251
+ tags: ['praxis', 'governance', 'accountability', 'kill-switch'],
252
+ },
253
+ {
254
+ slug: 'demerzel',
255
+ room: 'PRAXIS',
256
+ canonical_name: 'Demerzel Lens',
257
+ aliases: ['Demerzel', 'R. Daneel Olivaw', 'Governor in Plain Sight'],
258
+ source: 'Isaac Asimov, Foundation series; PRAXIS module v0.1',
259
+ source_type: 'fictional-authored',
260
+ description: 'Governance through architecture, defaults, and incentives — not command',
261
+ tags: ['praxis', 'governance', 'architecture', 'psychohistory'],
262
+ },
263
+ {
264
+ slug: 'prospero',
265
+ room: 'PRAXIS',
266
+ canonical_name: 'Prospero Lens',
267
+ aliases: ['Prospero', 'Knowledge Architect'],
268
+ source: 'Shakespeare, The Tempest (1611); PRAXIS module v0.1',
269
+ source_type: 'fictional-authored',
270
+ description: 'Governance through information asymmetry and model opacity',
271
+ tags: ['praxis', 'governance', 'information', 'transparency'],
272
+ },
273
+ {
274
+ slug: 'janus',
275
+ room: 'PRAXIS',
276
+ canonical_name: 'Janus Lens',
277
+ aliases: ['Janus', 'Threshold Guardian'],
278
+ source: 'Roman mythology; PRAXIS module v0.1',
279
+ source_type: 'mythological',
280
+ description: 'State management at transitions — onboarding, handoffs, version changes',
281
+ tags: ['praxis', 'governance', 'transitions', 'handoff'],
282
+ },
283
+ {
284
+ slug: 'mond',
285
+ room: 'PRAXIS',
286
+ canonical_name: 'Mond Lens',
287
+ aliases: ['Mustapha Mond', 'Architect of Consent'],
288
+ source: 'Aldous Huxley, Brave New World (1932); PRAXIS module v0.1',
289
+ source_type: 'fictional-authored',
290
+ description: 'Governance by shaping what is thinkable, not by direct force',
291
+ tags: ['praxis', 'governance', 'consent', 'power'],
292
+ },
293
+
294
+ // ── BKI cluster ───────────────────────────────────────────────────────────
295
+
296
+ {
297
+ slug: 'belonging-infrastructure',
298
+ room: 'BKI',
299
+ canonical_name: 'Belonging Infrastructure',
300
+ aliases: ['BKI', 'Belonging as Knowledge Infrastructure'],
301
+ source: 'HUMMBL BKI framework; Walton & Cohen 2011; Edmondson 1999',
302
+ source_type: 'collective-pattern',
303
+ description: 'Belonging as structural precondition for knowledge creation and transmission',
304
+ tags: ['bki', 'belonging', 'knowledge', 'governance'],
305
+ },
306
+ {
307
+ slug: 'biocognitive-os',
308
+ room: 'BKI',
309
+ canonical_name: 'Biocognitive OS',
310
+ aliases: ['Biocognitive Operating System', 'BKI OS'],
311
+ source: 'HUMMBL BKI framework',
312
+ source_type: 'collective-pattern',
313
+ description: 'Six cognitive modes humans shift through depending on belonging conditions',
314
+ tags: ['bki', 'cognition', 'belonging', 'modes'],
315
+ },
316
+
317
+ ];
318
+
319
+ // ---------------------------------------------------------------------------
320
+ // Registry utilities
321
+ // ---------------------------------------------------------------------------
322
+
323
+ /**
324
+ * Build a lookup map from canonical name + all aliases → entry.
325
+ * Throws on duplicate names (registry integrity check).
326
+ */
327
+ function buildLookupMap(): Map<string, MemoryPalaceEntry> {
328
+ const map = new Map<string, MemoryPalaceEntry>();
329
+
330
+ for (const entry of MEMORY_PALACE) {
331
+ const keys = [entry.canonical_name, ...entry.aliases];
332
+ for (const key of keys) {
333
+ const lower = key.toLowerCase();
334
+ if (map.has(lower)) {
335
+ throw new Error(
336
+ `Memory Palace duplicate: "${key}" appears in both "${map.get(lower)!.slug}" and "${entry.slug}"`
337
+ );
338
+ }
339
+ map.set(lower, entry);
340
+ }
341
+ }
342
+
343
+ return map;
344
+ }
345
+
346
+ const _lookupMap = buildLookupMap();
347
+
348
+ /**
349
+ * Look up a Memory Palace entry by canonical name or alias (case-insensitive).
350
+ * Returns null if not found.
351
+ */
352
+ export function lookupMemoryPalace(name: string): MemoryPalaceEntry | null {
353
+ return _lookupMap.get(name.toLowerCase()) ?? null;
354
+ }
355
+
356
+ /**
357
+ * Check if a term is a registered Memory Palace model (canonical or alias).
358
+ */
359
+ export function isMemoryPalaceModel(name: string): boolean {
360
+ return _lookupMap.has(name.toLowerCase());
361
+ }
362
+
363
+ /**
364
+ * Get all entries for a given room.
365
+ */
366
+ export function getRoom(room: MemoryPalaceRoom): MemoryPalaceEntry[] {
367
+ return MEMORY_PALACE.filter(e => e.room === room);
368
+ }
369
+
370
+ /**
371
+ * Get all canonical names (for drift detection scanning).
372
+ */
373
+ export function getAllCanonicalNames(): string[] {
374
+ return MEMORY_PALACE.map(e => e.canonical_name);
375
+ }
376
+
377
+ /**
378
+ * Get all aliases across all entries (for alias scanning).
379
+ */
380
+ export function getAllAliases(): string[] {
381
+ return MEMORY_PALACE.flatMap(e => [...e.aliases]);
382
+ }
383
+
384
+ /**
385
+ * Get all slugs (for uniqueness checks).
386
+ */
387
+ export function getAllSlugs(): string[] {
388
+ return MEMORY_PALACE.map(e => e.slug);
389
+ }
390
+
391
+ /**
392
+ * Registry health check: returns duplicate slugs, duplicate names, and
393
+ * any entries with missing required fields.
394
+ */
395
+ export function auditRegistry(): {
396
+ duplicateSlugs: string[];
397
+ duplicateNames: string[];
398
+ missingFields: Array<{ slug: string; fields: string[] }>;
399
+ missingSourceTypes: string[];
400
+ totalEntries: number;
401
+ byRoom: Record<string, number>;
402
+ } {
403
+ const slugs = getAllSlugs();
404
+ const duplicateSlugs = slugs.filter((s, i) => slugs.indexOf(s) !== i);
405
+
406
+ const allNames = MEMORY_PALACE.flatMap(e => [e.canonical_name, ...e.aliases]);
407
+ const lowerNames = allNames.map(n => n.toLowerCase());
408
+ const duplicateNames = allNames.filter((_, i) => lowerNames.indexOf(lowerNames[i]) !== i);
409
+
410
+ const missingFields = MEMORY_PALACE
411
+ .map(e => {
412
+ const missing: string[] = [];
413
+ if (!e.slug) missing.push('slug');
414
+ if (!e.canonical_name) missing.push('canonical_name');
415
+ if (!e.source) missing.push('source');
416
+ if (!e.description) missing.push('description');
417
+ return { slug: e.slug || '(unknown)', fields: missing };
418
+ })
419
+ .filter(r => r.fields.length > 0);
420
+
421
+ const missingSourceTypes = MEMORY_PALACE
422
+ .filter(e => !e.source_type)
423
+ .map(e => e.slug);
424
+
425
+ const byRoom = {} as Record<string, number>;
426
+ for (const e of MEMORY_PALACE) {
427
+ byRoom[e.room] = (byRoom[e.room] ?? 0) + 1;
428
+ }
429
+
430
+ return {
431
+ duplicateSlugs,
432
+ duplicateNames,
433
+ missingFields,
434
+ missingSourceTypes,
435
+ totalEntries: MEMORY_PALACE.length,
436
+ byRoom,
437
+ };
438
+ }
@@ -0,0 +1,190 @@
1
+ #!/usr/bin/env node
2
+
3
+ import fs from 'fs';
4
+ import path from 'path';
5
+ import chalk from 'chalk';
6
+ import { Cite } from '@citation-js/core';
7
+ import '@citation-js/plugin-bibtex';
8
+
9
+ const args = process.argv.slice(2);
10
+ const bibDir = args[0] || '../bibliography';
11
+
12
+ class KeywordExtractor {
13
+ constructor(bibDir) {
14
+ this.bibDir = path.resolve(bibDir);
15
+ this.transformationMap = new Map();
16
+ this.entryMap = new Map();
17
+ }
18
+
19
+ parseBibTeXRaw(content) {
20
+ const rawEntries = {};
21
+ const entryRegex = /@\w+\{([^,]+),([^@]+?)(?=\n\})/gs;
22
+ let match;
23
+
24
+ while ((match = entryRegex.exec(content)) !== null) {
25
+ const key = match[1].trim();
26
+ const entryText = match[2];
27
+ const fields = {};
28
+
29
+ const lines = entryText.split('\n');
30
+ for (const line of lines) {
31
+ const fieldMatch = line.match(/^\s*(\w+)\s*=\s*\{(.+)\}\s*,?\s*$/);
32
+ if (fieldMatch) {
33
+ const [, fieldKey, value] = fieldMatch;
34
+ fields[fieldKey.toLowerCase()] = value.trim();
35
+ }
36
+ }
37
+
38
+ rawEntries[key] = fields;
39
+ }
40
+
41
+ return rawEntries;
42
+ }
43
+
44
+ extractTransformations(keywords) {
45
+ const transformations = [];
46
+ if (!keywords) return transformations;
47
+
48
+ const keywordStr = Array.isArray(keywords) ? keywords.join(', ') : keywords;
49
+ const matches = keywordStr.match(/HUMMBL:(P|IN|CO|DE|RE|SY)/g);
50
+
51
+ if (matches) {
52
+ matches.forEach(match => {
53
+ transformations.push(match.replace('HUMMBL:', ''));
54
+ });
55
+ }
56
+
57
+ return [...new Set(transformations)];
58
+ }
59
+
60
+ processEntry(entry, filename, rawEntry = {}) {
61
+ const keywords = rawEntry.keywords || '';
62
+ const transformations = this.extractTransformations(keywords);
63
+
64
+ if (transformations.length > 0) {
65
+ this.entryMap.set(entry.id, {
66
+ title: entry.title,
67
+ file: filename,
68
+ transformations: transformations
69
+ });
70
+
71
+ transformations.forEach(trans => {
72
+ if (!this.transformationMap.has(trans)) {
73
+ this.transformationMap.set(trans, []);
74
+ }
75
+ this.transformationMap.get(trans).push({
76
+ key: entry.id,
77
+ title: entry.title,
78
+ file: filename
79
+ });
80
+ });
81
+ }
82
+ }
83
+
84
+ async loadFile(filepath) {
85
+ const filename = path.basename(filepath);
86
+
87
+ try {
88
+ const content = fs.readFileSync(filepath, 'utf8');
89
+ const citation = new Cite(content, { forceType: '@bibtex/text' });
90
+ const entries = citation.data;
91
+
92
+ // Parse raw BibTeX for keywords
93
+ const rawEntries = this.parseBibTeXRaw(content);
94
+
95
+ entries.forEach(entry => {
96
+ const rawEntry = rawEntries[entry.id] || {};
97
+ this.processEntry(entry, filename, rawEntry);
98
+ });
99
+ } catch (err) {
100
+ console.error(chalk.red(`Error loading ${filename}: ${err.message}`));
101
+ }
102
+ }
103
+
104
+ printReport() {
105
+ console.log('\n' + '='.repeat(60));
106
+ console.log(chalk.cyan.bold(' HUMMBL TRANSFORMATION KEYWORDS'));
107
+ console.log('='.repeat(60));
108
+
109
+ const transNames = {
110
+ P: 'Perspective',
111
+ IN: 'Inversion',
112
+ CO: 'Composition',
113
+ DE: 'Decomposition',
114
+ RE: 'Recursion',
115
+ SY: 'Synthesis'
116
+ };
117
+
118
+ const transOrder = ['P', 'IN', 'CO', 'DE', 'RE', 'SY'];
119
+
120
+ transOrder.forEach(trans => {
121
+ const entries = this.transformationMap.get(trans) || [];
122
+ const name = transNames[trans];
123
+
124
+ console.log(chalk.cyan(`\n${trans}: ${name} (${entries.length} entries)`));
125
+
126
+ if (entries.length > 0) {
127
+ entries.forEach((entry, idx) => {
128
+ console.log(chalk.white(` ${idx + 1}. [${entry.key}] ${entry.title.substring(0, 70)}...`));
129
+ console.log(chalk.gray(` ${entry.file}`));
130
+ });
131
+ } else {
132
+ console.log(chalk.yellow(' No entries found'));
133
+ }
134
+ });
135
+
136
+ // Entries by transformation count
137
+ console.log(chalk.cyan('\n📊 ENTRIES BY TRANSFORMATION COUNT'));
138
+
139
+ const countMap = new Map();
140
+ this.entryMap.forEach((entry, key) => {
141
+ const count = entry.transformations.length;
142
+ if (!countMap.has(count)) {
143
+ countMap.set(count, []);
144
+ }
145
+ countMap.get(count).push({ key, ...entry });
146
+ });
147
+
148
+ Array.from(countMap.keys())
149
+ .sort((a, b) => b - a)
150
+ .forEach(count => {
151
+ const entries = countMap.get(count);
152
+ console.log(chalk.white(`\n ${count} transformation(s): ${entries.length} entries`));
153
+ entries.slice(0, 5).forEach(entry => {
154
+ console.log(chalk.gray(` - [${entry.key}] ${entry.transformations.join(', ')}`));
155
+ });
156
+ if (entries.length > 5) {
157
+ console.log(chalk.gray(` ... and ${entries.length - 5} more`));
158
+ }
159
+ });
160
+
161
+ console.log('\n' + '='.repeat(60) + '\n');
162
+ }
163
+
164
+ async run() {
165
+ // Find all .bib files
166
+ const bibFiles = fs.readdirSync(this.bibDir)
167
+ .filter(f => f.endsWith('.bib'))
168
+ .map(f => path.join(this.bibDir, f));
169
+
170
+ if (bibFiles.length === 0) {
171
+ console.log(chalk.red('\n❌ No .bib files found in ' + this.bibDir));
172
+ process.exit(1);
173
+ }
174
+
175
+ // Load all files
176
+ for (const filepath of bibFiles) {
177
+ await this.loadFile(filepath);
178
+ }
179
+
180
+ // Print report
181
+ this.printReport();
182
+ }
183
+ }
184
+
185
+ // Run extractor
186
+ const extractor = new KeywordExtractor(bibDir);
187
+ extractor.run().catch(err => {
188
+ console.error(chalk.red('Fatal error:'), err);
189
+ process.exit(1);
190
+ });