Imported from lolay/nowline (
AGENTS.md). Install upstream withnpx skills add lolay/nowline. Copyright stays with the author.
AGENTS.md
Orientation for AI coding agents (Cursor, Claude Code, OpenAI Codex CLI, Aider) working in this repo. Human contributors should start with README.md and CONTRIBUTING.md — this file deliberately does not duplicate them.
What this repo is
Nowline is a text-first DSL for product/engineering roadmaps, plus the toolchain that parses, validates, lays out, and renders it. The .nowline file is the product; everything else is a view of that file. Read specs/principles.md for what's in scope (and what's deliberately not), and specs/dsl.md for the canonical language reference before touching grammar, parser, validator, or printer.
OSS boundary invariant
nowline is a standalone OSS repo. It never pushes to or triggers the
proprietary services.
Permitted outbound writes from nowline:
npm publish->@nowline/*packages on the npm registry- VS Code Marketplace + Open VSX extension publish
- Homebrew tap formula update
- Write to
lolay/nowline-action(follow-only mirror, release pipeline only) - GitHub Pages deploy of the review gallery (
review.nowline.dev, same repo;specs/ci.md§ Review gallery)
Prohibited (hard rule -- do not add exceptions without estate-level sign-off):
repository_dispatchintonowline-api,nowline-app,nowline-site, ornowline-infragh workflow run -Rtargeting those repos- Any write token held by a
nowlineworkflow scoped to those repos
Consumers pull; nowline does not push:
nowline-appinstalls@nowline/*from npm; Renovate handles updates.nowline-sitepulls docs via theexternal/nowlinegit submodule +sync-docs; its build checks out the latest release tag.- A consumer-owned schedule or consumer/human-initiated
repository_dispatchmay trigger a deploy -- but the initiator must never benowline.
After the embed CDN retirement, nowline has no remaining tie to
nowline-infra. See docs/architecture.md -> "Dependency direction" in the
workspace for the full estate-level mapping.
First boot
make init # install dependencies from the frozen lockfile
make build-fast # build every package, skipping the examples/ + tests/ render
make test # run the Vitest suites (builds first)
Everything goes through the Makefile — it is the single source of truth for build / test / lint / package / publish commands, and CI calls the same targets, so a green make pre-commit locally (alias of make ci) is the gate CI runs. Run make help for the grouped target list; Makefile.md is the narrative reference.
make build regenerates the Langium parser, bundles CLI templates, type-checks every package, and re-renders every file under examples/ and tests/; make build-fast skips that render for an inner dev loop. CLI integration tests spawn packages/cli/dist/index.js and silently skip when it's missing — make test builds first (test: build) so they always run.
Toolchain pins: Node from .nvmrc (currently 26.2.0); pnpm from packageManager in package.json. Full policy: CONTRIBUTING.md § Toolchain & Supported Versions.
Repo map
packages/ # @nowline/* workspace: core, cli, layout, renderer, lsp, exporters, vscode-extension
examples/ # User-grounded .nowline files: progressive samples + nowline --init templates
tests/ # Renderer manual-validation fixtures (one stressed axis per file). NOT Vitest.
grammars/ # TextMate grammar for editor highlighting
specs/ # Design specs — source of truth. Read before non-trivial changes.
scripts/ # Repo-wide build/render/packaging scripts
The plural tests/ (repo root, manual fixtures rendered by pnpm build) is distinct from the singular packages/<pkg>/test/ (Vitest unit/integration tests). Don't put one in the other.
Source of truth
Specs ship in-repo so PRs update them alongside code. Skim the relevant spec before non-trivial changes:
specs/principles.md— scope and explicit non-goals.specs/dsl.md— canonical language design.specs/architecture.md— package graph, tech choices,AssetResolvercontract.specs/cli.md,specs/rendering.md,specs/ide.md,specs/embed.md— surface-area specs.specs/milestones.md,specs/releasing.md— roadmap and release process.specs/ci.md— what CI runs on which platform and event, and why.specs/handoffs/— version-controlled handoffs between milestones.
Don't do this
- Don't edit
packages/core/src/generated/. Langium output, gitignored, regenerated byprebuild/pretest. Editpackages/core/src/language/nowline.langiumand runpnpm langium:generateinstead. - Don't change snapshots under
packages/integration-tests/test/__snapshots__/casually. They are the byte-stable regression gate for the renderer. Bumping them is a deliberate decision, not a side effect. - Don't drop the
.jssuffix on TS-to-TS imports inside a package. Required by Node's ESM resolver ("type": "module"everywhere). - Don't mix indentation. TypeScript is 4 spaces;
.nowlineis 2 spaces (canonical, enforced bypackages/cli/src/convert/printer.tsround-trips). - Don't add emojis to source, commit messages, or user-facing CLI output unless explicitly requested.
- Don't
process.exit()directly from CLI code. ThrowCliErrorwith the rightExitCode(Success | ValidationError | InputError | OutputError); the entry point translates it. - Don't conflate versions. The DSL version (
nowline v1, integer-only, declared inside.nowlinefiles) is independent from the npm package version (semver, lock-step across all@nowline/*). - Don't widen scope silently. Nowline has a deliberate non-goal list in
specs/principles.md(no issue tracking, no resource leveling, no whiteboard). Features that don't fit belong in external consumers of@nowline/core.
Workflow expectations
- Tooling: always use the Makefile — never call
pnpm/npm/vsce/ovsx/firebase/gclouddirectly for build, test, lint, or deploy. The Makefile is the single source of truth for those command strings (change a command there, not in the workflow), and the guardedpublish-*targets are what keep an accidental publish/deploy behind aCONFIRM_*variable — reaching for the raw tool bypasses that guard.make helplists every target;Makefile.mdis the reference. - Commits: imperative subject, no trailing period, ≤72 chars. Match
git log --onelinefor tone. Optional body explains why. No emojis. - PRs: one logical change each, squash-merged. Run
make pre-commitlocally before committing or pushing — it chains lint + build + typecheck + test, the same gate CI's canonical cell (Linux, Node 26) runs asmake ci. The other cells (Linux Node 22, macOS, Windows) runmake ci-platform: build plus the Vitest suites. A PR push runs only the two Linux cells; macOS and Windows run in the merge queue and onmain. (make lintis biome lint + format-drift + import order; skipping it is the failure mode that lets formatting regressions slip into abd59e70-style dep bump.) - Tests: when you add a feature, add a test that would fail without your change. When you fix a bug, add a regression test that reproduces it.
make pre-commitis non-negotiable: if it fails for any environmental reason (pnpm TTY error, missing binary, stale staging dir), fix the environment first — do not substitute a partial manual check (e.g. running biome directly on a subset of files, or using the IDEReadLintstool). A partial check is silent about everything it didn't cover. The correct sequence is alwaysmake pre-committhenmake pack-mcpb;pack-mcpbneeds thedist/thatpre-commit → buildproduces anyway.- Re-run
make pre-commitimmediately before staging and committing: if any file was edited after the last greenmake pre-commitrun — including by a review pass, a fix-up, or a multi-turn agent session — runmake pre-commitagain beforegit add. A green run from an earlier turn does not cover later edits.ReadLintsis not a substitute: it only invokes the IDE TypeScript language server and is blind to Biome format and lint rules entirely. - Bug retrospective: when you fix a bug that escaped to CI, a workflow run, or production, ask why
make pre-commitdidn't catch it. If a check could reasonably have caught it (a missing lint rule, an uncovered test path, a type hole), add or tighten that check in the same PR so the gate catches the class of bug next time. - Docs: if observable behavior changes, update the relevant spec under
specs/and any affectedREADME.mdin the same PR. For a full doc audit (human + agent consumption), use theoptimize-docsskill. - Changelog: user-observable changes (DSL syntax, CLI flag, AST JSON shape, exporter output, extension UX, embed/CDN surface) get a fragment file, never an edit to
CHANGELOG.md: add.changelog/<branch-slug>.txtholding a```release-note:<kind>block, where<kind>isadded/changed/deprecated/removed/fixed/security. VS Code extension UX goes inpackages/vscode-extension/.changelog/instead (or as well). Format:.changelog/README.md. Internal-only changes (build scripts, CI, dep bumps with no behavior change) get no fragment and theno-changelogPR label; theChangelogcheck fails a PR with neither, or one that adds entries toCHANGELOG.md. The release cut folds fragments into the new version section and deletes them; full contract inspecs/releasing.md. - Round-trips: changes to the AST, grammar, or printer must keep
packages/cli/test/convert/roundtrip.test.tsgreen — everyexamples/file must round-trip text → JSON → text and JSON → text → JSON without drift (modulo comment loss).
Visual changes
A change is visual if it changes the rendered SVG of any file under examples/ or tests/, or any snapshot under packages/integration-tests/test/__snapshots__/. Theme, spacing, and DSL-default changes count, whichever package you touch.
Before implementing a visual change, show the maintainer what it will do to the diagrams and stop until they agree. Use whatever is least effort and still makes the change clear: render 1 to 3 affected fixtures on the base commit (node packages/cli/dist/index.js <file> -o .scratch/<name>-before.svg) and pair each with an "after" (a hand-edited copy of the SVG, a mockup, or a quick hack rendered the same way). Add a sentence or two on what changes and which other diagrams it will touch. Revert any hack before implementing.
When the change is done, show before and after again from the real implementation: the same fixtures, plus any other diagram whose render changed. This is a report, not a gate; don't wait for sign-off.
Where new files go
| Kind | Location |
|---|---|
| Specs, RFCs, ADRs | specs/{topic}.md (lowercase-kebab, no -spec suffix) |
| Drafts, scratch work, agent plans | .scratch/ (gitignored) |
| Renderer manual-validation fixtures | tests/ (stress one axis per file) |
| Vitest unit / integration tests | packages/<pkg>/test/ |
| Milestone handoffs | specs/handoffs/handoff-m{N}-{topic}.md |
New .nowline examples |
examples/ (rendered by pnpm build; SVG output is gitignored) |
Agent triage flow
Issues in this repo may enter a four-phase AI agent flow driven by labels. Labels use three prefixes: agent- (agent owns the next move), originator- (issue filer owns the next move), maintainer- (repo maintainer owns the next move). The canonical reference is .github/AGENT_TRIAGE.md.
Labels you must respect if they appear on an issue or PR you are working on:
| Label | Meaning | Your constraint |
|---|---|---|
agent-triage |
triage phase queued | do not add other state labels; wait for triage to fire |
agent-plan |
plan phase queued | do not implement yet; planning is in progress |
agent-deep / agent-exec |
implement phase queued | proceed with implementation per the ## Plan comment on the issue |
agent-done |
terminal — resolved | do not reopen unless you have new information |
originator-input |
waiting on issue filer | do not implement; agent is waiting for clarification from the filer |
maintainer-only |
agent excluded / parked | do not act on this issue; it is outside the agent flow |
maintainer-decide |
waiting on maintainer design pick | do not implement; a maintainer must pick a routing option |
maintainer-pr-safe |
PR ready for maintainer merge (low-risk) | do not merge yourself; a maintainer reviews and clicks Approve + Merge |
maintainer-pr-review |
PR needs careful maintainer review | do not merge; a maintainer reviews the flagged concerns and decides |
Labels you may emit (as safe-outputs from a phase workflow, never directly):
agent-plan, maintainer-only (triage phase); agent-deep, agent-exec, agent-done, originator-input, maintainer-decide (plan phase); agent-done, originator-input, maintainer-decide (implement fallback); maintainer-pr-safe, maintainer-pr-review (review phase).
Never emit a label outside the safe-outputs list for your phase. A maintainer performs every merge — never call gh pr merge directly.
For more detail
Everything else — full task table, hotfix flow, branching policy, dev-build version metadata, npm link workflow — lives in CONTRIBUTING.md. The package graph and AssetResolver contract live in specs/architecture.md. When this file and CONTRIBUTING.md disagree, CONTRIBUTING.md wins; please open a PR to reconcile.
