Imported from grethel-labs/excaliplant (
AGENTS.md). Install upstream withnpx skills add grethel-labs/excaliplant. Copyright stays with the author.
Repository Instructions for Editing Agents
This file is the canonical project guide for Copilot and other editing agents. Keep it focused on rules that affect most code changes. Do not duplicate these instructions into tool-specific files. System-specific adapter files may exist only as short pointers back to this file.
Current adapter files:
.github/copilot-instructions.mdfor GitHub Copilot.CLAUDE.mdfor Claude Code.GEMINI.mdfor Gemini CLI..cursor/rules/repository-instructions.mdcfor Cursor..windsurf/rules/repository-instructions.mdfor Windsurf..clinerulesfor Cline..roo/rules/repository-instructions.mdfor Roo Code.
Project Shape
The library is a small deterministic pipeline:
PlantUML text -> parsePlantUml -> Diagram/SequenceDiagram model -> layoutDiagram -> exportDiagram -> Excalidraw JSON -> optional SVG/PNG
Respect the layer boundaries:
- Parser code recognizes PlantUML syntax, but does not render or layout.
- Model classes describe input-agnostic structure, not Excalidraw/SVG details.
- Layout mutates geometry onto the model and returns the same model.
- Renderers translate already laid-out models into Excalidraw/SVG/PNG output.
- CLI code handles I/O and options only; keep business logic in
src/.
Important files:
src/diagrams/base/: base diagram module and facet contracts.src/diagrams/{sequence,class,component}/: concrete diagram modules.src/diagrams/shared/: shared diagram-family code such as graph parsing.src/main/parser.mjs: parser dispatch, default plugin lists, parse limits.src/main/pipeline.mjs,src/main/registry.mjs: orchestration and registry.src/util/parser_engine.mjs,src/util/plantuml_utils.mjs: generic parser engine and helpers.src/general/model/diagram.mjs: public model classes.src/general/layout/elk_layout.mjs,src/diagrams/sequence/layout_engine.mjs: layout passes.src/general/render/excalidraw.mjs,src/general/render/svg.mjs,src/general/render/canvas_svg.mjs,src/general/render/png.mjs: renderers.src/general/style/,src/general/platform/: style and platform services.index.mjs: public top-level API.
Language and Style
- Runtime is Node
>=18; source is ESM-only.mjs. - Use ESM imports with explicit
.mjsextensions. - Do not introduce CommonJS (
require,module.exports) insrc/. - This repo uses JSDoc plus TypeScript
checkJs; there is no TypeScript source. - Public API needs clear JSDoc with
@param,@returns, and usually@public. - Code comments and JSDoc are English. Explain contracts, edge cases, or security reasoning; avoid comments that narrate obvious assignments.
- Formatting is Prettier-controlled. Do not hand-format around it.
- Prefer small concrete modules over broad abstractions.
Naming conventions:
- Classes:
PascalCase(Diagram,SequenceDiagram,Box). - Functions and variables:
camelCase(parsePlantUml,layoutDiagram). - Constants:
UPPER_SNAKE_CASE(DEFAULT_PARSE_LIMITS). - Source files under
src/generally usesnake_case.mjs. - Parser plugin exports usually end in
Plugin(connectionPlugin). - Plugin
namevalues are stable dotted strings (component.connection).
Parser Work
New PlantUML syntax should usually be implemented as a parser plugin:
- Add a focused plugin under the owning diagram module, for example
src/diagrams/sequence/plugins/or a newsrc/diagrams/<kind>/plugins/. - Implement
{ name, tryLine?, tryStart? }according tosrc/util/parser_engine.mjs. - Register it in the owning module parser contract such as
src/diagrams/sequence/parser.mjs,src/diagrams/class/parser.mjs, orsrc/diagrams/component/parser.mjs. - Add a regression test under
tests/.
Parser rules:
- Keep
src/util/parser_engine.mjsgeneric. New PlantUML constructs should not need engine changes. - Plugin order matters. Block plugins must run before generic regex plugins; greedy connection parsing belongs last.
- A plugin should return
trueonly when it is responsible for the line. - Block plugins return an object with
onLineandtryEnd. - Use context helpers from
src/diagrams/shared/graph_context.mjsandsrc/diagrams/sequence/context.mjsinstead of mutating unrelated model internals. - Unknown lines are intentionally tolerant by default. Diagnostics use
unknownLines: "warn" | "strict". - Do not hand-roll quote/comment parsing when helpers exist. Prefer utilities
such as
stripComment,stripQuotes,explodeBraces,unescapeLabel,slug, andclassifyArrowfromsrc/util/plantuml_utils.mjs.
Model Work
- Component-style diagrams use
Diagram -> Plane/Subplane -> BoxplusConnection. - Sequence diagrams use
SequenceDiagram -> Participant/Message/SequenceNote. - Model classes must not know about parser regexes, PlantUML source lines, Excalidraw element fields, or SVG strings.
- New model fields need JSDoc, constructor defaults, and tests.
- Geometry/layout fields should initialize to
0,null, or empty arrays. - Maintain explicit backlinks such as
box.parent,connection.from/to, and participant/box lookup behavior. - Preserve stable IDs, declaration order, and public getters unless intentionally making a breaking change.
Layout and Rendering
- Component/class layout runs through ELK in
src/general/layout/elk_layout.mjs; sizing lives insrc/general/layout/sizing.mjs. - Sequence layout is deterministic and table-like in
src/diagrams/sequence/layout_engine.mjs. - Renderers should not infer PlantUML syntax or perform late layout.
- Text measurement/wrapping should use existing helpers from
src/general/style/text.mjs. - Excalidraw output must stay deterministic by default. Do not scatter
Math.random; default IDs/seeds derive fromstableHash32oversourceLabel|diagram.titleand the seeded RNG helpers insrc/general/render/rng.mjs. - For new Excalidraw primitives, follow existing helpers such as
baseElement,rect,text,arrow,line, andellipse. - SVG output is an injection surface. Escape text and attributes with the existing SVG escape helpers.
- Canvas SVG export in
src/general/render/canvas_svg.mjswraps plain SVG output in a fixed-aspect canvas; keep its background escaping and width clamps intact. - Validate or clamp canvas/PNG sizes and other expensive rendering options.
Security Expectations
This package parses untrusted PlantUML and emits JSON/SVG/PNG. Treat parser and renderer inputs as hostile.
- Do not bypass
DEFAULT_PARSE_LIMITSor CLI resource caps. - CLI input defaults to a 10 MiB cap with a 200 MiB
--max-input-bytesceiling; CLI, canvas-SVG, and PNG widths are bounded to[16, 16000]px. - Do not add file-system access to parser or renderer paths that process PlantUML input.
- Avoid prototype-pollution hazards. Prefer
Mapor carefully controlled class instances for attacker-controlled keys. - Review regex changes for ReDoS risk. Prefer scanner-style helpers for quote-aware or brace-aware parsing.
- Never interpolate untrusted strings directly into SVG or HTML-like output.
- Add or update
tests/security.test.mjsfor security-relevant changes. - Keep dependency and native-rendering changes compatible with
npm audit --omit=dev --audit-level=high.
Tests and Validation
Before a PR, run the relevant subset locally. For broad or user-visible changes, run the full gate:
npm test
npm run typecheck
npm run format:check
npm audit --omit=dev --audit-level=high
npm run build
node docs/scripts/check-build-manifest.mjs
For public API or JSDoc changes, npm run build:api is the minimum API-docs
check; npm run build runs both docs and API generation.
Test placement:
tests/edge_cases.test.mjs: focused edge cases.tests/functional_more.test.mjs: broad feature and renderer coverage.tests/plantuml.test.mjs: core parser/render behavior.tests/security.test.mjs: XSS, ReDoS, prototype pollution, limits, CLI hardening.tests/self_introspection.test.mjs: repo-generated architecture diagrams.tests/module_coverage.test.mjs: shared coverage inventory that renders every diagram-module example through SVG.
New parser, renderer, security, or public API behavior needs a regression test. Avoid tests that only assert "renders something" when a more specific contract is available.
Diagram-module coverage examples should follow the sequence-diagram coverage
model. Each module owns examples in
src/diagrams/<kind>/docs/coverage_examples.mjs; keep several small, focused
examples for individual syntax/renderer decisions and at least one large
combination example that intentionally mixes supported features. The large
example should exercise edge cases, overlap-prone layouts, long labels,
wrapping/multiline text, functional choices, and deliberate design decisions so
the final SVG validates the diagram type beyond a smoke render. When a diagram
type naturally represents repository structure or dependencies, prefer an
additional repo-derived dynamic example. Wire examples into the generated docs
through docs/scripts/build-module-coverage.mjs and keep tests using the same
coverage inventory so documentation and validation cannot drift apart.
Docs, Generated Files, and Releases
README.mdis generated. Editdocs/README.template.md.njkinstead, then runnpm run build:docs.- Do not manually patch generated docs or
docs/ressources/generated/output. docs/module-coverage.mdanddocs/ressources/module-coverage/are generated from module coverage examples; edit the owning example source or template, then runnpm run build:docs.- Generated artifact merge conflicts use the
keep-generatedmerge driver; resolve source/template changes, then regenerate withnpm run build:docs. - Preserve
@diagramJSDoc blocks unless deliberately changing generated architecture diagrams. - User-visible behavior changes should update
CHANGELOG.mdin the next appropriate section. - New public exports belong in
index.mjsand, when package-importable, inpackage.jsonexports. - Check
package.jsonfileswhen adding package-relevant assets. - PRs into
mainneed exactly one release label:release:major,release:minor, orrelease:patch. - For PRs targeting
main, branch names should include exactly one release token (MAJOR,MINOR, orPATCH, case-insensitive). CI maps that token to the matching release label when none is set, and falls back torelease:patchif no token is present. - CI runs tests on Node 18/20/22 across Ubuntu, macOS, and Windows.
Feature Checklist
Use this checklist before finishing substantial changes:
- Is the change in the correct layer: parser, model, layout, renderer, CLI, or docs?
- Can parser support be a plugin instead of an engine change?
- Is output deterministic and stable for review?
- Are untrusted strings escaped, validated, or clamped at every output boundary?
- Are expensive paths bounded by existing or new limits?
- Are JSDoc types complete enough for
tsc --checkJs? - Are functional and, when relevant, security tests included?
- Were generated docs rebuilt through the template pipeline when user-facing docs changed?
- Was
CHANGELOG.mdupdated for user-visible behavior? - Has the relevant local gate been run?
PR Completion Process
After completing a feature or patch, finish it through the repository release path instead of leaving it only as local changes:
- Create a PR branch whose name contains exactly one release token
(
PATCH,MINOR, orMAJOR) matching the intended release impact. - Commit the implementation, tests, docs, and generated artefacts that belong to the change.
- Open a pull request into
mainand ensure it has exactly one matching release label. The automation may derive the label from the branch name. - Wait for all required PR pipelines to finish. If workflow automation amends the branch, wait for the new head commit checks too.
- Merge the PR only after the required checks pass.
- After merge, inspect the
mainworkflows that decide publication:auto-release.ymlcreates av*tag only whenpackage.jsonchanges onmain, andrelease.ymlpublishes to npm only for that tag push. - Report the exact publication outcome. If npm was not updated, identify the concrete reason, such as no version change, an existing tag, a failed tag push, a skipped release workflow, a failing release job, or an npm publish error.