@dmthepm/commune 0.1.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 (48) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +468 -0
  3. package/bin/commune.mjs +29 -0
  4. package/lib/cli/check.d.ts +13 -0
  5. package/lib/cli/check.js +58 -0
  6. package/lib/cli/errors.d.ts +29 -0
  7. package/lib/cli/errors.js +41 -0
  8. package/lib/cli/gate.d.ts +34 -0
  9. package/lib/cli/gate.js +165 -0
  10. package/lib/cli/main.d.ts +20 -0
  11. package/lib/cli/main.js +175 -0
  12. package/lib/cli/query.d.ts +30 -0
  13. package/lib/cli/query.js +103 -0
  14. package/lib/cli/related.d.ts +17 -0
  15. package/lib/cli/related.js +177 -0
  16. package/lib/cli/render.d.ts +20 -0
  17. package/lib/cli/render.js +32 -0
  18. package/lib/cli/root.d.ts +11 -0
  19. package/lib/cli/root.js +28 -0
  20. package/lib/cli/usage.d.ts +3 -0
  21. package/lib/cli/usage.js +46 -0
  22. package/lib/cli/version.d.ts +24 -0
  23. package/lib/cli/version.js +29 -0
  24. package/lib/integration.d.ts +24 -0
  25. package/lib/integration.js +111 -0
  26. package/lib/lib/graph.d.ts +354 -0
  27. package/lib/lib/graph.js +774 -0
  28. package/lib/markdown.d.ts +30 -0
  29. package/lib/markdown.js +24 -0
  30. package/lib/rehype-external-links.d.ts +15 -0
  31. package/lib/rehype-external-links.js +46 -0
  32. package/lib/remark-wikilinks.d.ts +25 -0
  33. package/lib/remark-wikilinks.js +108 -0
  34. package/package.json +101 -0
  35. package/src/components/Backlinks.astro +17 -0
  36. package/src/components/BacklinksScript.astro +117 -0
  37. package/src/components/Footer.astro +35 -0
  38. package/src/components/Header.astro +250 -0
  39. package/src/components/HeaderStarScript.astro +380 -0
  40. package/src/components/HomeFooterCards.astro +155 -0
  41. package/src/components/MarkdownLink.astro +36 -0
  42. package/src/components/PlausibleScript.astro +13 -0
  43. package/src/components/RelatedNotes.astro +77 -0
  44. package/src/components/SearchModal.astro +213 -0
  45. package/src/components/StarredLinksScript.astro +92 -0
  46. package/src/components/panes.ts +61 -0
  47. package/src/styles/design-system.css +157 -0
  48. package/src/styles/notes.css +85 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025-2026 Devon Meadows
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,468 @@
1
+ # Commune Wiki
2
+
3
+ An Astro wiki engine โ€” WikiLinks, sliding panes, backlinks, static search โ€” and a `commune` CLI that queries the content graph and checks links.
4
+
5
+ **License**: MIT ยท **Live example**: [devonmeadows.com](https://devonmeadows.com)
6
+
7
+ ---
8
+
9
+ ## โœจ Features
10
+
11
+ - ๐Ÿ”— **WikiLinks**: `[[Note Title]]` automatically converts to links
12
+ - ๐Ÿ“‘ **Sliding Panes**: Andy Matuschak-style cascading note navigation
13
+ - ๐Ÿ‘๏ธ **Hover Previews**: See note content on hover before clicking
14
+ - ๐Ÿ”„ **Backlinks**: Auto-generated bidirectional link graph
15
+ - ๐ŸŽจ **Design System**: Custom CSS variables with light/dark mode
16
+ - ๐Ÿ” **Search**: Cmd-K palette with Pagefind static search
17
+ - ๐Ÿ“ **Markdown-First**: Git-backed content, version controlled
18
+ - ๐Ÿš€ **Fast**: Static site generation (no runtime database)
19
+ - ๐ŸŽฏ **Zero Config**: Works out of the box, customize as needed
20
+
21
+ ---
22
+
23
+ ## ๐ŸŽฏ Who Is This For?
24
+
25
+ **Personal Knowledge Management**:
26
+ - Researchers building interconnected notes (Zettelkasten/Evergreen Notes)
27
+ - Writers managing drafts, research, and published content
28
+ - Developers documenting code, decisions, and learnings
29
+ - Anyone tired of silo'd notes in proprietary apps
30
+
31
+ **vs. Other Tools**:
32
+ | Tool | Approach | Commune Wiki |
33
+ |------|----------|--------------|
34
+ | Obsidian | Desktop app, proprietary sync | Web-first, self-hosted, MIT |
35
+ | Notion | Cloud SaaS, vendor lock-in | Git-backed, own your data |
36
+ | Roam | SaaS, $15/mo | Free, open source, MIT |
37
+ | Logseq | Local-first, complex setup | Simple Astro build, deploy anywhere |
38
+
39
+ ---
40
+
41
+ ## ๐Ÿš€ Quick Start
42
+
43
+ ### Prerequisites
44
+
45
+ - Node.js 22.18+ and pnpm
46
+
47
+ ### Install & Run
48
+
49
+ ```bash
50
+ # Clone repository
51
+ git clone git@github.com:dmthepm/commune-wiki.git
52
+ cd commune-wiki
53
+
54
+ # Install dependencies
55
+ pnpm install
56
+
57
+ # Start dev server (http://localhost:4321)
58
+ pnpm dev
59
+
60
+ # Build for production
61
+ pnpm build
62
+
63
+ # Preview production build
64
+ pnpm preview
65
+ ```
66
+
67
+ ### Install
68
+
69
+ The engine is a package. Add it to an Astro 7 project:
70
+
71
+ ```bash
72
+ pnpm add @dmthepm/commune
73
+ ```
74
+
75
+ npm and yarn take the same line. The published tarball ships `lib/` already
76
+ compiled, so nothing builds on install, no build script needs approving, and a
77
+ consumer needs only Node 22.12+ โ€” Astro's own floor.[^git]
78
+
79
+ [^git]: **Before `v0.1.0` reaches npm, install from a git ref instead** โ€”
80
+ `pnpm add github:dmthepm/commune-wiki#<tag>` โ€” and pnpm consumers need one
81
+ extra line for it, because a git dependency arrives as source and compiles
82
+ itself in its `prepare` script. pnpm 10 refuses to run that unless your
83
+ project names the package, and without the entry the install fails with
84
+ `ERR_PNPM_GIT_DEP_PREPARE_NOT_ALLOWED`. The details are folded below; all
85
+ of it goes away on the first npm publish, which is what this footnote is
86
+ counting down to.
87
+
88
+ <details>
89
+ <summary>Installing from a git ref, in full</summary>
90
+
91
+ ```jsonc
92
+ // your package.json
93
+ {
94
+ "dependencies": {
95
+ "@dmthepm/commune": "github:dmthepm/commune-wiki#<tag>"
96
+ },
97
+ "pnpm": {
98
+ "onlyBuiltDependencies": ["@dmthepm/commune@github:dmthepm/commune-wiki#<tag>"]
99
+ }
100
+ }
101
+ ```
102
+
103
+ Note the entry is `name@spec`, not the bare name โ€” a bare name approves a
104
+ package from the registry, and for a git dependency pnpm matches the whole
105
+ specifier, so a name on its own is silently not a match. It has to be the same
106
+ specifier you wrote in `dependencies`, which means it changes when you bump the
107
+ tag. The same entry works in `pnpm-workspace.yaml` if you keep pnpm settings
108
+ there.
109
+
110
+ **Older pnpm 10 wants the other spelling.** Around 10.19 the `name@spec` form is
111
+ rejected with `ERR_PNPM_INVALID_VERSION_UNION` ("Use exact versions only") and
112
+ the bare `"@dmthepm/commune"` is what works โ€” those releases also approve a git
113
+ dependency's build scripts on their own, so you may need nothing at all. Do not
114
+ guess which side of the line you are on: run the install and read the error.
115
+ pnpm prints the exact entry your version expects.
116
+
117
+ npm and yarn need nothing extra โ€” they run a git dependency's `prepare` without
118
+ asking.
119
+
120
+ </details>
121
+
122
+ ### Create Your First Note
123
+
124
+ ```bash
125
+ # Create a note in src/content/notes/
126
+ cat > src/content/notes/hello-world.md << 'MDEOF'
127
+ ---
128
+ title: "Hello World"
129
+ visibility: "public"
130
+ status: "evergreen"
131
+ summary: "My first note"
132
+ tags: [getting-started]
133
+ ---
134
+
135
+ Welcome to your personal wiki!
136
+
137
+ Link to other notes with [[Note Title]] syntax.
138
+ MDEOF
139
+
140
+ # Start dev server and visit http://localhost:4321
141
+ pnpm dev
142
+ ```
143
+
144
+ ---
145
+
146
+ ## ๐Ÿ“ Project Structure
147
+
148
+ ```
149
+ commune-wiki/
150
+ โ”œโ”€โ”€ src/
151
+ โ”‚ โ”œโ”€โ”€ content/
152
+ โ”‚ โ”‚ โ”œโ”€โ”€ config.ts # Content collection schemas
153
+ โ”‚ โ”‚ โ””โ”€โ”€ notes/ # Your markdown notes
154
+ โ”‚ โ”œโ”€โ”€ components/
155
+ โ”‚ โ”‚ โ”œโ”€โ”€ Header.astro # Site header
156
+ โ”‚ โ”‚ โ”œโ”€โ”€ SearchModal.astro
157
+ โ”‚ โ”‚ โ””โ”€โ”€ Backlinks.astro
158
+ โ”‚ โ”œโ”€โ”€ pages/
159
+ โ”‚ โ”‚ โ”œโ”€โ”€ index.astro # Homepage
160
+ โ”‚ โ”‚ โ””โ”€โ”€ notes/
161
+ โ”‚ โ”‚ โ””โ”€โ”€ [...slug].astro # Note pages + pane logic
162
+ โ”‚ โ””โ”€โ”€ styles/
163
+ โ”‚ โ”œโ”€โ”€ design-system.css # Custom CSS variables
164
+ โ”‚ โ””โ”€โ”€ notes.css # Note typography
165
+ โ”œโ”€โ”€ public/
166
+ โ”‚ โ””โ”€โ”€ backlinks.json # Auto-generated backlinks graph
167
+ โ”œโ”€โ”€ astro.config.mjs # Astro config + remark plugins
168
+ โ””โ”€โ”€ package.json
169
+ ```
170
+
171
+ ---
172
+
173
+ ## โœ๏ธ Writing Notes
174
+
175
+ ### Note Schema
176
+
177
+ Every note requires frontmatter:
178
+
179
+ ```markdown
180
+ ---
181
+ title: "Note Title"
182
+ visibility: "public" # public | private | draft
183
+ status: "evergreen" # seed | growing | evergreen
184
+ summary: "Brief description for previews"
185
+ tags: [tag1, tag2]
186
+ aliases: ["Short Name"]
187
+ updated: 2025-10-21
188
+ ---
189
+
190
+ Your note content here with [[WikiLinks]] to other notes.
191
+ ```
192
+
193
+ **Visibility**:
194
+ - `public` - Published to site (default: only public notes shown)
195
+ - `private` - Not published
196
+ - `draft` - Work in progress, not indexed
197
+
198
+ **Status**:
199
+ - `seed` - Early idea, needs development
200
+ - `growing` - Actively being refined
201
+ - `evergreen` - Well-developed, stable
202
+
203
+ ### WikiLinks Syntax
204
+
205
+ ```markdown
206
+ [[Note Title]] โ†’ Links to note
207
+ [[Note Title|Display Text]] โ†’ Custom text
208
+ [[Multi-word Note]] โ†’ Normalized matching
209
+ ```
210
+
211
+ **How it works**:
212
+ 1. Build-time plugin scans all notes
213
+ 2. Creates title โ†’ slug lookup index
214
+ 3. Transforms `[[Title]]` to `<a href="/notes/slug/">`
215
+ 4. Broken links render as plain text (not clickable)
216
+
217
+ ---
218
+
219
+ ## ๐ŸŽจ Customization
220
+
221
+ ### Design System
222
+
223
+ Edit `src/styles/design-system.css`:
224
+
225
+ ```css
226
+ :root {
227
+ --c-bg: #0a0a0b;
228
+ --c-accent: #8b7bff;
229
+ --c-text: #e8e6e3;
230
+ /* ... customize colors ... */
231
+ }
232
+
233
+ [data-theme="light"] {
234
+ --c-bg: #fafaf9;
235
+ /* ... light mode overrides ... */
236
+ }
237
+ ```
238
+
239
+ ### Typography
240
+
241
+ Edit `src/styles/notes.css` for note-specific styling (headings, lists, code blocks).
242
+
243
+ ### Pane Behavior
244
+
245
+ Pane logic in `src/pages/notes/[...slug].astro`:
246
+
247
+ ```javascript
248
+ // Customize pane behavior:
249
+ setupPanes() // Initialize
250
+ openPane(url) // Open new pane
251
+ closePane(pane) // Remove pane
252
+ ```
253
+
254
+ ---
255
+
256
+ ## ๐Ÿ” Search
257
+
258
+ **Pagefind** generates a static search index at build time:
259
+
260
+ - No server required
261
+ - Instant client-side search
262
+ - Automatically indexes all public notes
263
+ - Cmd-K hotkey to open search modal
264
+
265
+ **Dev mode**: Falls back to backlinks.json when Pagefind not available.
266
+
267
+ ---
268
+
269
+ ## ๐Ÿ“Š Backlinks
270
+
271
+ Backlinks are auto-generated at build time via the `src/integration.ts` integration:
272
+
273
+ 1. Scans all notes for WikiLinks
274
+ 2. Creates bidirectional graph
275
+ 3. Outputs to `public/backlinks.json` and `<outDir>/backlinks.json`
276
+ 4. Displayed in `Backlinks.astro` component ("Links to this note")
277
+
278
+ ---
279
+
280
+ ## ๐Ÿš€ Deployment
281
+
282
+ ### Static Hosting (Recommended)
283
+
284
+ **Cloudflare Pages / Vercel / Netlify**:
285
+
286
+ ```bash
287
+ # Build command
288
+ pnpm build
289
+
290
+ # Output directory
291
+ dist/
292
+
293
+ # Deploy
294
+ # Connect GitHub repo, auto-deploy on push
295
+ ```
296
+
297
+ ### Self-Hosted (Caddy)
298
+
299
+ ```yaml
300
+ # docker-compose.yml
301
+ caddy:
302
+ image: caddy:alpine
303
+ volumes:
304
+ - ./dist:/srv:ro
305
+ - ./Caddyfile:/etc/caddy/Caddyfile
306
+ ports:
307
+ - "80:80"
308
+ - "443:443"
309
+ ```
310
+
311
+ ```Caddyfile
312
+ # Caddyfile
313
+ yourdomain.com {
314
+ root * /srv
315
+ file_server
316
+ try_files {path} {path}/ /index.html
317
+ encode gzip
318
+ }
319
+ ```
320
+
321
+ ### Self-Hosted (Railway)
322
+
323
+ ```bash
324
+ # Install Railway CLI
325
+ npm install -g railway
326
+
327
+ # Deploy
328
+ railway init
329
+ railway up
330
+ ```
331
+
332
+ Railway auto-detects Astro and builds with `pnpm build`.
333
+
334
+ ---
335
+
336
+ ## ๐Ÿ› ๏ธ Development
337
+
338
+ ### Commands
339
+
340
+ ```bash
341
+ pnpm dev # Start dev server (port 4321)
342
+ pnpm build # Build production site
343
+ pnpm preview # Preview production build
344
+ ```
345
+
346
+ ### Testing
347
+
348
+ ```bash
349
+ # Run the test suite
350
+ pnpm test
351
+
352
+ # Check the content graph (broken links, duplicate names, ambiguous targets)
353
+ node bin/commune.mjs check --json
354
+
355
+ # Preview before deploying
356
+ pnpm preview
357
+ ```
358
+
359
+ ### Debugging WikiLinks
360
+
361
+ **Issue**: Links not working?
362
+
363
+ ```bash
364
+ # Check cache consistency (should show same count each time)
365
+ pnpm build 2>&1 | grep "Lookup built with"
366
+
367
+ # Find broken links
368
+ pnpm build 2>&1 | grep "Broken link"
369
+ ```
370
+
371
+ ---
372
+
373
+ ## ๐Ÿ“ฆ Tech Stack
374
+
375
+ - **Astro** - Static site generator
376
+ - **Tailwind CSS** - Utility-first styling
377
+ - **Pagefind** - Static search index
378
+ - **remark-wikilinks** - WikiLink transformation plugin (custom, `src/remark-wikilinks.ts`)
379
+ - **No framework dependencies** - Vanilla JS for interactivity
380
+
381
+ ---
382
+
383
+ ## ๐Ÿ“– Documentation
384
+
385
+ **For Contributors**:
386
+ - Architecture details in original README (check git history)
387
+ - Pane system implementation in `src/pages/notes/[...slug].astro`
388
+ - WikiLink plugin in `src/remark-wikilinks.ts`
389
+ - Backlinks integration in `src/integration.ts`
390
+
391
+ **For Users**:
392
+ - This README covers installation and usage
393
+ - See [devonmeadows.com](https://devonmeadows.com) for live example
394
+ - Issues/questions: [GitHub Issues](https://github.com/dmthepm/commune-wiki/issues)
395
+
396
+ ---
397
+
398
+ ## ๐Ÿค Contributing
399
+
400
+ This is an open-source project under the MIT License. Contributions welcome!
401
+
402
+ **How to contribute**:
403
+ 1. Fork the repository
404
+ 2. Create a feature branch (`git checkout -b feature/your-feature`)
405
+ 3. Make changes and test locally (`pnpm dev`)
406
+ 4. Build to verify (`pnpm build`)
407
+ 5. Commit with clear message
408
+ 6. Push and create Pull Request
409
+
410
+ **Areas for contribution**:
411
+ - [ ] Automated tests (Puppeteer or Playwright)
412
+ - [ ] Additional themes/design systems
413
+ - [ ] Search improvements (fuzzy matching, ranking)
414
+ - [ ] Graph visualization of backlinks
415
+ - [ ] Mobile responsiveness improvements
416
+ - [ ] Performance optimizations
417
+
418
+ ---
419
+
420
+ ## ๐Ÿ› Known Issues
421
+
422
+ ### WikiLink Cache Bug (FIXED)
423
+
424
+ **Symptom**: Links only work on last note built.
425
+
426
+ **Fix**: Ensure cache size check in `src/remark-wikilinks.ts`:
427
+
428
+ ```typescript
429
+ if (notesCache && notesCache.size > 0) { // MUST check .size!
430
+ return buildFromCache();
431
+ }
432
+ ```
433
+
434
+ ### Pane Styling Not Applied
435
+
436
+ **Symptom**: Panes don't stack correctly.
437
+
438
+ **Fix**: Use `<style is:global>` in `[...slug].astro` for dynamic panes.
439
+
440
+ ---
441
+
442
+ ## ๐Ÿ“„ License
443
+
444
+ MIT - See [LICENSE](LICENSE) file.
445
+
446
+ **What this means**:
447
+ - Free to use, modify, distribute, and sell
448
+ - Commercial use allowed, with no obligation to open-source your changes
449
+ - Keep the copyright notice; that's the whole obligation
450
+
451
+ ---
452
+
453
+ ## ๐Ÿ”— Related Projects
454
+
455
+ **Commune Ecosystem**:
456
+ - **Devon's Homelab** - Personal infrastructure (private, showcase only)
457
+
458
+ **Inspired by**:
459
+ - [Andy Matuschak's Notes](https://notes.andymatuschak.org/)
460
+ - [Maggie Appleton's Digital Garden](https://maggieappleton.com/garden)
461
+ - [Obsidian](https://obsidian.md/) (proprietary alternative)
462
+ - [Logseq](https://logseq.com/) (local-first alternative)
463
+
464
+ ---
465
+
466
+ **Created by**: [Devon Meadows](https://devonmeadows.com)
467
+ **Repository**: [dmthepm/commune-wiki](https://github.com/dmthepm/commune-wiki)
468
+ **Support**: [GitHub Issues](https://github.com/dmthepm/commune-wiki/issues)
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * The `commune` entry point.
5
+ *
6
+ * One line on purpose, and this is the line #18 promised would change: it now
7
+ * imports the compiled CLI rather than the TypeScript source. Node refuses to
8
+ * strip types from any file under a `node_modules` path, so a `.ts` import here
9
+ * works from a checkout and dies the moment this package is installed.
10
+ *
11
+ * Two things have to be true for the import below to resolve on an install,
12
+ * and both are in `package.json`. `prepare` runs `tsc`, which pnpm executes
13
+ * inside its own clone of a git dependency, so `lib/` gets *built*. And the
14
+ * `files` allowlist names `lib`, so `lib/` gets *packed* โ€” without that field
15
+ * the pack falls back to `.gitignore`, which ignores build output, and the
16
+ * installed package would arrive with a bin and nothing for it to run.
17
+ * `tests/install.test.mjs` and the CI stranger-install step exist to catch
18
+ * exactly that, because a checkout never notices it.
19
+ *
20
+ * The package compiles to `lib/`, not `dist/`: `dist/` is Astro's, and
21
+ * `astro build` empties its output directory before every run, so a site build
22
+ * would delete the CLI that is about to check it.
23
+ *
24
+ * If `lib/` is missing in a checkout, `pnpm build:lib` writes it.
25
+ */
26
+
27
+ import { run } from '../lib/cli/main.js';
28
+
29
+ process.exitCode = await run(process.argv.slice(2));
@@ -0,0 +1,13 @@
1
+ /**
2
+ * `commune check` โ€” link integrity as a payload.
3
+ *
4
+ * Exits 0 whether or not it finds anything. The exit code answers "did the
5
+ * command finish", not "is your content clean" โ€” those are different questions
6
+ * and an agent that cannot tell them apart has to parse stderr to find out
7
+ * whether the tool crashed. `commune gate` is the documented exception โ€” it keeps
8
+ * its exit 1, because a gate's job *is* to fail.
9
+ *
10
+ * v1 is scoped to link integrity so it does not block on #17's collection
11
+ * collapse. Frontmatter drift is a follow-up.
12
+ */
13
+ export declare function checkCommand(root: string, json: boolean): Promise<number>;
@@ -0,0 +1,58 @@
1
+ /**
2
+ * `commune check` โ€” link integrity as a payload.
3
+ *
4
+ * Exits 0 whether or not it finds anything. The exit code answers "did the
5
+ * command finish", not "is your content clean" โ€” those are different questions
6
+ * and an agent that cannot tell them apart has to parse stderr to find out
7
+ * whether the tool crashed. `commune gate` is the documented exception โ€” it keeps
8
+ * its exit 1, because a gate's job *is* to fail.
9
+ *
10
+ * v1 is scoped to link integrity so it does not block on #17's collection
11
+ * collapse. Frontmatter drift is a follow-up.
12
+ */
13
+ import { buildGraph, checkEntries, loadContentEntries, } from "../lib/graph.js";
14
+ import { SCHEMA, writeJson, writeLines } from "./render.js";
15
+ import { EXIT_OK } from "./errors.js";
16
+ const RULES = [
17
+ 'broken-link',
18
+ 'ambiguous-target',
19
+ 'duplicate-name',
20
+ 'noncanonical-title',
21
+ ];
22
+ /** A finding as it appears in the payload: no internal rendering fields. */
23
+ function toFinding(diagnostic) {
24
+ return {
25
+ rule: diagnostic.rule,
26
+ severity: diagnostic.severity,
27
+ file: diagnostic.file,
28
+ ...(diagnostic.line !== undefined ? { line: diagnostic.line } : {}),
29
+ message: diagnostic.message,
30
+ ...(diagnostic.target !== undefined ? { target: diagnostic.target } : {}),
31
+ ...(diagnostic.candidates ? { candidates: diagnostic.candidates } : {}),
32
+ ...(diagnostic.canonical !== undefined ? { canonical: diagnostic.canonical } : {}),
33
+ };
34
+ }
35
+ export async function checkCommand(root, json) {
36
+ const entries = await loadContentEntries({ root });
37
+ const graph = buildGraph(entries);
38
+ const findings = checkEntries(entries, graph);
39
+ const byRule = Object.fromEntries(RULES.map((rule) => [rule, findings.filter((finding) => finding.rule === rule).length]));
40
+ const summary = {
41
+ entries: Object.keys(graph.nodes).length,
42
+ // Resolved edges. Every resolved outbound link is one inbound link on the
43
+ // far side, so this is the same number counted from either end.
44
+ edges: graph.totalBacklinks,
45
+ errors: findings.filter((finding) => finding.severity === 'error').length,
46
+ warnings: findings.filter((finding) => finding.severity === 'warning').length,
47
+ byRule,
48
+ };
49
+ if (json) {
50
+ writeJson({ schema: SCHEMA, root, summary, findings: findings.map(toFinding) });
51
+ return EXIT_OK;
52
+ }
53
+ writeLines([
54
+ ...findings.map((finding) => `${finding.severity}\t${finding.rule}\t${finding.file}\t${finding.message}`),
55
+ `${summary.entries} entries, ${summary.edges} edges, ${summary.errors} errors, ${summary.warnings} warnings`,
56
+ ]);
57
+ return EXIT_OK;
58
+ }
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Exit codes and the errors that produce them.
3
+ *
4
+ * The codes report *completion*, not findings: a `check` that reports 28 broken
5
+ * links has finished its job and exits 0. An agent that cannot tell "your
6
+ * content has problems" from "the tool fell over" has to parse stderr to find
7
+ * out, which is the failure mode this contract exists to prevent.
8
+ */
9
+ /** The command ran to completion. Findings, if any, are in the payload. */
10
+ export declare const EXIT_OK = 0;
11
+ /** The command could not finish: bad root, unreadable file, unparseable frontmatter. */
12
+ export declare const EXIT_FAILED = 1;
13
+ /** The command was invoked wrongly: unknown flag, missing value, unknown subcommand. */
14
+ export declare const EXIT_USAGE = 2;
15
+ export type ErrorCode = 'EUSAGE' | 'ENOCONTENT' | 'EPARSE' | 'EINTERNAL';
16
+ /** An error the CLI knows how to render on either side of the `--json` switch. */
17
+ export declare class CliError extends Error {
18
+ readonly code: ErrorCode;
19
+ readonly exitCode: number;
20
+ /** Extra lines for text mode only โ€” usage, for instance. Never in the JSON object. */
21
+ readonly detail?: string;
22
+ constructor(code: ErrorCode, message: string, exitCode: number, detail?: string);
23
+ }
24
+ export declare function usageError(message: string, detail?: string): CliError;
25
+ export declare function failure(code: ErrorCode, message: string): CliError;
26
+ /** `parseArgs` throws typed errors; every one of them means the invocation was wrong. */
27
+ export declare function isParseArgsError(error: unknown): error is Error & {
28
+ code: string;
29
+ };
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Exit codes and the errors that produce them.
3
+ *
4
+ * The codes report *completion*, not findings: a `check` that reports 28 broken
5
+ * links has finished its job and exits 0. An agent that cannot tell "your
6
+ * content has problems" from "the tool fell over" has to parse stderr to find
7
+ * out, which is the failure mode this contract exists to prevent.
8
+ */
9
+ /** The command ran to completion. Findings, if any, are in the payload. */
10
+ export const EXIT_OK = 0;
11
+ /** The command could not finish: bad root, unreadable file, unparseable frontmatter. */
12
+ export const EXIT_FAILED = 1;
13
+ /** The command was invoked wrongly: unknown flag, missing value, unknown subcommand. */
14
+ export const EXIT_USAGE = 2;
15
+ /** An error the CLI knows how to render on either side of the `--json` switch. */
16
+ export class CliError extends Error {
17
+ code;
18
+ exitCode;
19
+ /** Extra lines for text mode only โ€” usage, for instance. Never in the JSON object. */
20
+ detail;
21
+ constructor(code, message, exitCode, detail) {
22
+ super(message);
23
+ this.name = 'CliError';
24
+ this.code = code;
25
+ this.exitCode = exitCode;
26
+ this.detail = detail;
27
+ }
28
+ }
29
+ export function usageError(message, detail) {
30
+ return new CliError('EUSAGE', message, EXIT_USAGE, detail);
31
+ }
32
+ export function failure(code, message) {
33
+ return new CliError(code, message, EXIT_FAILED);
34
+ }
35
+ /** `parseArgs` throws typed errors; every one of them means the invocation was wrong. */
36
+ export function isParseArgsError(error) {
37
+ if (!(error instanceof Error))
38
+ return false;
39
+ const code = error.code;
40
+ return typeof code === 'string' && code.startsWith('ERR_PARSE_ARGS_');
41
+ }
@@ -0,0 +1,34 @@
1
+ /**
2
+ * `commune gate` โ€” the build check, as a verb.
3
+ *
4
+ * This is the one command in the CLI whose exit code answers a question about
5
+ * your *content* rather than about the command. Everywhere else the contract is
6
+ * "0 means I finished, findings or not", precisely so an agent can tell a dirty
7
+ * vault from a broken tool. A gate inverts that on purpose: its whole job is to
8
+ * stop a build, and a build stops on a non-zero exit. `usage.ts` says so out
9
+ * loud, because a reader who has internalised the rule needs to be told where
10
+ * the exception is.
11
+ *
12
+ * It was `scripts/test-search-index.mjs`, which imported the graph core by
13
+ * relative path. That works from a checkout and is unreachable from
14
+ * `node_modules`, so the one repo that most needs this check โ€” a wiki built
15
+ * with the package โ€” was the one repo that could not run it. The three
16
+ * assertions are unchanged; only the way you invoke them is.
17
+ *
18
+ * Three assertions:
19
+ * 1. every page in the `pages` collection is present in the search index
20
+ * 2. every WikiLink that resolves uses the target's exact title (no pipes,
21
+ * no case drift) โ€” the canonical-title rule
22
+ * 3. WikiLinks pointing at standalone pages actually render as hrefs
23
+ *
24
+ * The canonical-title rule itself lives in the graph core, where `commune
25
+ * check` reports it as a `noncanonical-title` finding. This verb is the *gate*:
26
+ * same rule, same findings, but a build that violates it stops. Two copies of
27
+ * one rule is the bug #3 was opened to kill, so there is only ever one.
28
+ */
29
+ /** Which assertion failed, and what it saw. */
30
+ export interface GateFailure {
31
+ assertion: 'pages-indexed' | 'canonical-titles' | 'page-links-rendered';
32
+ message: string;
33
+ }
34
+ export declare function gateCommand(root: string, dist: string, json: boolean): Promise<number>;