Skip to content
🚧 Early alpha — building the foundation. See the roadmap →

Contributing

Created
  1. Fork the repository on GitHub
  2. Clone your fork and set up the development environment
  3. Create a feature branch: git checkout -b feature/my-feature
  4. Make your changes
  5. Run the relevant test suites (see testing)
  6. Commit and push — no AI attribution (hard rule)
  7. Open a pull request against main

Root-level CONTRIBUTING.md in the repo has the quick-start version of the same thing. This page is the longer-form reference with all the conventions.

Crosswalker has three contribution surfaces, each with its own conventions:

SurfaceLives inTechTest suite
Plugin codesrc/TypeScript + Bun + esbuildJest unit tests, WebdriverIO E2E
Docs sitedocs/src/content/docs/Astro Starlight, MDXPlaywright E2E
Python CLIframeworks_to_obsidian.pyPython + Pandasmanual

Most contributions touch the plugin or docs. The Python CLI is the original batch importer and still shipped as a secondary tool, but new capability work happens in the plugin.

From the repo root, bun run serve is an interactive menu wrapping every local workflow:

━━━ Crosswalker local serve ━━━

  1) Docs dev server          http://localhost:4321   HMR
  2) Docs preview (built)     http://localhost:4321
  3) Docs build only          → docs/dist
  4) Plugin dev (watch)       → test-vault
  5) Docs + plugin dev        both in parallel
  6) Share docs (Tailscale)   tailnet only
  7) Share docs (Cloudflare)  public URL
  8) Docs e2e tests           playwright test:local

Shortcuts that skip the menu:

bun run serve:docs       # Option 1
bun run serve:plugin     # Option 4
bun run serve:both       # Option 5
bun run serve:share      # Option 6

The orchestrator handles cross-OS node_modules contamination automatically (e.g. rollup native binary mismatches when bouncing between WSL and Windows) and auto-installs docs/node_modules on first run.

  • TypeScript with strict types
  • ESLint with eslint-plugin-obsidianmd — required for community plugin submission
  • Sentence case for all user-facing UI text (e.g. “Add new rule”, not “Add New Rule”)
  • Settings headings: use Setting.setHeading() instead of createEl('h3')
  • Don’t include the plugin name in settings section headings (Obsidian provides it)
  • Avoid “settings” or “options” in heading text — "Behavior" not "Behavior settings"

Run the linter before committing:

bun run lint            # Check
bun run lint:fix        # Auto-fix what can be fixed

Keep them descriptive, focused on the “why” rather than the “what”. First line is a short summary. Body explains non-obvious context and design rationale. Match the existing commit log style by running git log --oneline -20 before writing yours.

Never include AI attribution (Co-Authored-By: Claude ... or similar) — this is a hard rule in the project’s CLAUDE.md. Commit as the human contributor only.

To add support for a new file format:

  1. Create src/import/parsers/<format>-parser.ts
  2. Export a parse<Format>(content, options) function returning ParsedData
  3. Add the format to the file picker in import-wizard.ts
  4. Add unit tests in tests/<format>-parser.test.ts
  5. Update docs/src/content/docs/features/import-wizard.mdx to mention the new format

The docs site is built with Astro Starlight and lives in the docs/ folder.

Every docs page has an “Edit page” link in the sidebar. Click it to edit the page directly on GitHub and submit a pull request — no local setup needed.

For larger changes or new pages:

bun run serve:docs       # Starts Astro dev server on :4321
# or use the interactive menu:
bun run serve            # → option 1

Edit files in docs/src/content/docs/. Changes hot-reload in the browser.

Docs use .mdx (Markdown with JSX components). Standard Markdown works — you only need JSX for Starlight components like <Card>, <Tabs>, or inline HTML/SVG:

import { Card, CardGrid } from '@astrojs/starlight/components';

<CardGrid>
  <Card title="My card">Content here.</Card>
</CardGrid>

The docs/ folder is not an Obsidian vault — the files are .mdx, not .md. For rich editing, use VS Code with the MDX extension.

Every content page starts with YAML frontmatter. The canonical fields:

---
title: Page title
description: Short description used in meta tags, search results, and listing pages
tags: [frameworks-mapping, data-model]   # must be in tags.yml
date: 2026-04-10                          # for logs, challenges, and registry entries
volatility: stable                        # registry entries: stable or evolving
sidebar:
  label: "Short sidebar label"
  order: -20260410                         # logs: negative dates sort newest first
---

date: convention: log files parse the creation date from their YYYY-MM-DD-* filename prefix. Challenge and registry entries use the git first-commit date by convention. The PageTitle override displays a CREATED label pulled from this field, alongside a git-based UPDATED label. Splash pages opt out of the meta row via sidebar.hidden: true.

Tags: must be in docs/tags.yml — unknown tags are rejected at build time by starlight-tags. Common ones: frameworks-mapping, data-model-graph, data-model-evolution, evidence-mapping-links, obsidian-metadata, architecture.

MDX silent-breakage gotchas — two rules you will hit if you write diagrams or code examples

Section titled “MDX silent-breakage gotchas — two rules you will hit if you write diagrams or code examples”

Both of these bite agents and humans alike. Both produce cryptic errors (or worse, pages that visually look wrong with no error at all). Internalize the rules before writing any inline SVG, HTML, or <code> blocks containing code examples in an .mdx file.

Rule 1: Inline SVG attributes — kebab-case only

Section titled “Rule 1: Inline SVG attributes — kebab-case only”

This is the single most expensive bug to hit in this codebase — it produces no error, just visually broken diagrams. MDX parses inline <svg> subtrees in HTML mode, not JSX mode. The HTML5 parser silently lowercases all attribute names except a small whitelist (viewBox, preserveAspectRatio, baseProfile). So strokeWidth="2" becomes strokewidth="2" — an invalid SVG attribute that browsers silently ignore, leaving the diagram with default 1px strokes, misaligned text, broken dashed borders, and no error message anywhere. You’ll see a diagram that “just looks wrong” and chase CSS for an hour before finding it.

Always use kebab-case for inline SVG attributes:

{/* Correct */}
<circle cx="30" cy="50" r="32" stroke="#00d4aa" stroke-width="2" />
<text x="30" y="120" font-size="11" text-anchor="middle" font-weight="600">Label</text>
<path d="..." stroke-dasharray="4 3" />

{/* Wrong — silently broken at render time, no error */}
<circle cx="30" cy="50" r="32" stroke="#00d4aa" strokeWidth="2" />
<text x="30" y="120" fontSize="11" textAnchor="middle" fontWeight="600">Label</text>
<path d="..." strokeDasharray="4 3" />

Exceptions that preserve case (HTML whitelist): viewBox, preserveAspectRatio, baseProfile, attributeName, xlink:href. Everything else: kebab-case.

CSS properties inside style={{...}} JSX object props should use camelCase — those are JavaScript object keys, not HTML attributes. style={{fontSize: '11px', textAlign: 'center'}} is correct in that context.

Rule 2: Curly braces inside HTML tags — escape with HTML entities

Section titled “Rule 2: Curly braces inside HTML tags — escape with HTML entities”

MDX parses {...} inside JSX context (including inside HTML elements like <td>, <code>, <span>) as a JSX expression. If you write a code example containing literal braces — a YAML example, a TypeScript object literal, a Bases formula — the parser tries to evaluate the brace contents as JavaScript and crashes at build time with a loud but confusing error:

MDXError: Could not parse expression with acorn
Unexpected content after expression
{/* Wrong — parser tries to evaluate `{status: covered}` as a JS expression: */}
<td><code>[[AC-2]] {status: covered}</code></td>
<td><code>implements: [{target, status, reviewer}]</code></td>

{/* Correct — escape curly braces with HTML entities: */}
<td><code>[[AC-2]] &#123;status: covered&#125;</code></td>
<td><code>implements: [&#123;target, status, reviewer&#125;]</code></td>

Braces inside markdown backticks are safe — MDX protects inline code from JSX parsing. Only braces inside HTML element content (<code>...</code>, <td>...</td>, etc.) need escaping. Rule of thumb: if the braces are inside an HTML tag, use &#123; / &#125;. For comparison operators like < and > inside HTML <code> blocks (e.g., a Bases formula), use &lt; / &gt;.

A lightweight MDX syntax checker lives at scripts/check-mdx.mjs that parses every .mdx file under docs/src/content/docs/ using @mdx-js/mdx directly — no Astro, no Starlight, no build. Runs in ~1–2 seconds on ~100 files:

bun run check:mdx                           # Check every .mdx under docs/
bun run check:mdx docs/src/content/docs/concepts/   # Check a specific directory
bun run check:mdx path/to/file.mdx          # Check one file

# Or via the orchestrator:
bun run serve                               # → option 9

Run this after any MDX edit before committing. It catches Rule 2 (curly brace parse errors) reliably, plus unclosed JSX tags, bad template literals inside <style> blocks, and invalid frontmatter YAML. Exit code is non-zero on any failure, so you can wire it into a pre-commit hook or CI.

It does not catch Rule 1 (SVG attribute lowercasing) — that’s HTML parsing that happens at a later stage in the pipeline, not MDX-parse time. For Rule 1 you still need to mentally review inline SVG attributes before committing or run the full dev server (bun run serve:docs). The checker catches MDX syntax errors; the dev server catches rendered output errors.

For full validation coverage (cross-link 404s, tag vocabulary enforcement, Starlight schema validation) you still need bun run serve:docs or bun run build. The check:mdx script is the fast first pass.

Links are critical in this knowledge base. Every page should aggressively cross-link to related concepts, decisions, and definitions:

  1. Link terms to their terminology definitions on first mention
  2. Link concepts to the pages that explore them deeper (log entries, concept pages)
  3. Every log page must have a ## Related section at the bottom with links to related pages
  4. Roadmap items should link to their log entries, research pages, and concept pages
  5. When a term has aliases (e.g., “interlingua / pivot” = “synthetic spine” = “meta-framework”), mention the aliases and link to the terminology entry
  6. Link to the project’s philosophical pillars (vision, problem, what makes Crosswalker unique) where design decisions connect to them

The goal: a reader should be able to follow any concept from any page to its definition, rationale, and related decisions without dead ends.

The log / challenge / roadmap / decision lifecycle

Section titled “The log / challenge / roadmap / decision lifecycle”

The docs site follows a specific lifecycle for research and architectural decisions:

1. Log (agent-context/zz-log/YYYY-MM-DD-topic-slug.mdx)

Section titled “1. Log (agent-context/zz-log/YYYY-MM-DD-topic-slug.mdx)”

Dated notes, research synthesis, working ideas, decision records. Write these freely — they’re chronological and permanent. Every significant architectural decision gets a log entry with rationale. Filename prefix parses to the date: frontmatter field automatically.

Structure a typical log as:

---
title: "Short title — what this is about"
description: One-line summary used in meta/search
tags: [research, architecture, ...]
date: YYYY-MM-DD
sidebar:
  label: "MM-DD · Short label"
  order: -YYYYMMDD     # negative for newest-first sort
---

## Why this log exists
## What was decided / found
## Rationale
## What this forces (decisions, next research items)
## Related

2. Challenge (agent-context/zz-challenges/NN-topic-slug.mdx)

Section titled “2. Challenge (agent-context/zz-challenges/NN-topic-slug.mdx)”

Fresh-agent research briefs. When you want an independent perspective on an architectural assumption, write a challenge brief and hand it to a new agent with no prior context. Findings flow back as dated log entries, not as edits to the challenge itself — this keeps the challenge re-runnable with different agents.

See Challenges 06 (synthetic spine) and 07 (link metadata edge model) for the current format.

Roadmap items should link to their log entries and challenges for rationale. Every significant item has a “why we picked this” that traces back to a log.

Once a decision crystallizes, the stable version lives as a concept page. Concept pages are “here’s how it is now,” not “here’s how we got here” — that stays in the log.

New terms get entries with aliases in the term column. Alias every concept with multiple common names so readers searching any term land on the canonical entry. Example: **Interlingua / pivot** — also: pivot ontology, synthetic spine, hub-and-spoke mapping, meta-framework.

Canonical facts about external orgs, standards, methodologies, and publications (NIST, SCF, SKOS, SSSOM, STRM, BFO, etc.) that the KB cites in multiple places. Each entry is a compact reference card (~40 lines) with:

---
title: SHORT_NAME
description: One-line summary with aliases where relevant
tags: [frameworks-mapping, ...]
volatility: stable | evolving
sidebar:
  order: NN
date: YYYY-MM-DD
---

## What it is
## How it works (or key concepts)
## Who authored / formalized it
## In Crosswalker
## Links (external references)

See existing registry entries for format.

# Run the Playwright E2E suite
cd docs && bun run test:local

# Or via the orchestrator:
bun run serve            # → option 8

See the testing page for the full breakdown of the three test surfaces (plugin Jest, plugin WebdriverIO, docs Playwright).


File issues at github.com/cybersader/crosswalker/issues with:

  • Clear title and description
  • Steps to reproduce
  • Expected vs. actual behavior
  • Sample data if relevant (anonymized if sensitive)
  • Obsidian version and OS
  • Plugin version from manifest.json