# Working in this repo Decisions a reader would otherwise relitigate, and the rules for every collaborator, human or agent. Using the library: `README.md`. What is still to build: `todo.md`. ## 1. Three formats, ADF is the hub ADF, one markdown flavour, one HTML dialect. Six directions exposed, but markdown↔HTML compose through ADF: four conversions exist to keep correct — never write a fifth. No fourth format, ever; each one doubles the directions. ## 2. The round-trip is the product `markdownToAdf(adfToMarkdown(doc))` and `htmlToAdf(adfToHtml(doc))` must equal `doc` — anything less silently destroys content an editor could not represent, in a document it did not author. When losslessness and readability conflict, losslessness wins. The other direction is a canonical fixpoint, not byte-identity: human markdown normalizes, the way back yields the library's canonical spelling, and that spelling round-trips byte-identically. Round-trip equality is a property tested over a corpus, not a claim made in prose. ## 3. Unknown input policy - Unknown ADF node: carried opaquely — raw JSON rides a dedicated syntax in both formats and restores to a deep-equal node. The round-trip holds for documents newer than the library. - Unmappable foreign HTML element: error result naming the element — never a silent drop. - Bare `@name` / `:smile:` in typed text: stays a text node. Only directives produce mention/emoji/media nodes; resolving names to ids needs I/O, which is the consumer's job. ## 4. The flavour - Directives, one grammar for everything markdown lacks: `:::panel info` … `:::` blocks, `:mention[@Mikael]{id=5b10a2}` inline. Prior art: CommonMark's generic-directives proposal. - Plain CommonMark is a subset: the flavour adds syntax, never changes CommonMark meaning. - Tables: one header row plus plain inline cells → pipe table; anything richer → directive form. - Identity-bearing nodes carry their ids in attributes; a document is only portable within its site — accepted. - The HTML dialect mirrors this: semantic elements, stable `adf-*` classes, `data-*` for what HTML cannot express, text always escaped. No stylesheet ships. ## 5. Dependencies `dependencies` is empty. A runtime dependency enters only through a decision entry here stating why ~20 lines of own code cannot do the job, who maintains it, and what auditing it costs. So the CommonMark and HTML parsers are written in this repo. `devDependencies`: few, each earning its keep; they never reach a consumer. ## 6. The package contract - ESM only — no CommonJS build, no dual-package hazard. - One entrypoint: built JavaScript, `.d.ts` beside it. Do not add a TypeScript-source entrypoint — Node refuses to type-strip under `node_modules` (`ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING`), so it cannot serve an npm consumer. - Published to public npmjs as `@larvit/atlassian-adf-converter`. Public source: the Gitea repo goes public, LICENSE in place, before the first publish. - Exact versions: `save-exact=true` in `.npmrc`. ## 7. Nothing about any consumer No Jira client, no HTTP, no REST shapes, no issue keys, no actual consumer named anywhere. Design against the README's personas. ## 8. Semver: the formats are API The emitted markdown and HTML are contracts. After 1.0: previously-emitted output parsing differently, or not at all, is MAJOR; new syntax while old output still round-trips is MINOR. Pre-1.0, normal 0.x rules. ## 9. Release automation - `package.json` version on `main` is the source of truth. CI on `main`: tests green and version differs from npm → publish and tag `vX.Y.Z`. No bump, no deploy; the bump is each shipping PR's deliberate semver judgment. - Renovate watches devDependencies, Docker pins and action tags; automerges everything on green CI. - Docker images pin the full patch version (`node:24.19.0-alpine3.24`, never `node:24`); actions pin semver tags. ## 10. Tests first, in Docker Test for the behaviour wanted first, then implement until green. `node --test`, beside the code. Node, tsc and npm never run on the host — only via the pinned images (§9). Tests are independent, coverage does not decline, containers are torn down after a run. The corpus, all checked in: hand-built fixtures per node and combination; real sanitized ADF from live Atlassian APIs; property-generated ADF trees; the CommonMark spec suite against `markdownToAdf` and `markdownToHtml`. ## 11. Code rules - Two-space indent, strict TypeScript, English everywhere. Alphabetical order wherever order carries no meaning. - Failures are values: everything returns `Result` — `{ ok: true; value } | { ok: false; error: ConvertError }` — nothing throws. `try/catch` only wrapped tightly around a call that genuinely throws, converted to a result on the spot. - No casts: `as`, `as unknown as`, non-null `!`. A boundary owes a type guard validating the fields it claims (`isAdfDocument`); past it everything is typed. Make invalid states unrepresentable. - Explicit over implicit; descriptive names; no catch-all files (`utils`, `helpers`, `misc`). - Reuse before adding; the smallest sufficient diff is the benchmark; no speculative generality — a second consumer, or it goes. ## 12. Prose to a minimum Applies everywhere: comments, every markdown file in this repo (this one included), PR text. - Default is no comment. One earns its single line only by naming an invariant, footgun or external constraint the code cannot show — never restatement, history, absence or arrangement. A second line belongs in the commit message or a decision entry here. - Every prose comment in a diff is a review question; the default answer is delete. - A doc paragraph says what the repo cannot say for itself, or it goes. The fix for a redundant one is deletion, not trimming. A false claim in any doc is a bug, fixed where found. - Published text — npm README, error messages, API docs — never references internal systems, tickets or repos. ## 13. Commits and PRs One-line commit messages and PR titles; short PR summaries. No AI-attribution markers, ever. ## 14. Non-goals No wiki markup (§1), no network or filesystem I/O, no name→id resolution (§3), no ADF schema validation or exported validator, no shipped CSS (§4), no streaming APIs, no performance budget — conversions are O(n), real documents are kilobytes. A CLI is a later goal (`todo.md`), not a non-goal.