# 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`](./README.md) and [`CONTRIBUTING.md`](./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`](./specs/principles.md) for what's in scope (and what's deliberately not), and [`specs/dsl.md`](./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](./specs/ci.md#review-gallery)) Prohibited (hard rule -- do not add exceptions without estate-level sign-off): - `repository_dispatch` into `nowline-api`, `nowline-app`, `nowline-site`, or `nowline-infra` - `gh workflow run -R` targeting those repos - Any write token held by a `nowline` workflow scoped to those repos **Consumers pull; `nowline` does not push:** - `nowline-app` installs `@nowline/*` from npm; Renovate handles updates. - `nowline-site` pulls docs via the `external/nowline` git submodule + `sync-docs`; its build checks out the latest release tag. - A consumer-owned schedule or consumer/human-initiated `repository_dispatch` may trigger a deploy -- but the initiator must never be `nowline`. 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 ```bash 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`](./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`](./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`](./.nvmrc) (currently `26.13.0`); pnpm from `packageManager` in [`package.json`](./package.json). Full policy: [`CONTRIBUTING.md` § Toolchain & Supported Versions](./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 plugins/ # Agent plugin bundling the MCP server (listed by .claude-plugin/marketplace.json) 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//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`](./specs/principles.md) — scope and explicit non-goals. - [`specs/dsl.md`](./specs/dsl.md) — canonical language design. - [`specs/architecture.md`](./specs/architecture.md) — package graph, tech choices, `AssetResolver` contract. - [`specs/cli.md`](./specs/cli.md), [`specs/rendering.md`](./specs/rendering.md), [`specs/ide.md`](./specs/ide.md), [`specs/embed.md`](./specs/embed.md) — surface-area specs. - [`specs/milestones.md`](./specs/milestones.md), [`specs/releasing.md`](./specs/releasing.md) — roadmap and release process. - [`specs/ci.md`](./specs/ci.md) — what CI runs on which platform and event, and why. - [`specs/handoffs/`](./specs/handoffs/) — version-controlled handoffs between milestones. - [`specs/milestones.md` § Review gate](./specs/milestones.md#review-gate) — a milestone that lands on `main` gets a review of `main`, run by the session that starts the next milestone, before planning it. ## Don't do this - **Don't edit `packages/core/src/generated/`.** Langium output, gitignored, regenerated by `prebuild` / `pretest`. Edit [`packages/core/src/language/nowline.langium`](./packages/core/src/language/nowline.langium) and run `pnpm langium:generate` instead. - **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 `.js` suffix 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; `.nowline` is 2 spaces (canonical, enforced by [`packages/cli/src/convert/printer.ts`](./packages/cli/src/convert/printer.ts) round-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.** Throw [`CliError`](./packages/cli/src/io/exit-codes.ts) with the right `ExitCode` (`Success | ValidationError | InputError | OutputError`); the entry point translates it. - **Don't conflate versions.** The DSL version (`nowline v1`, integer-only, declared inside `.nowline` files) 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`](./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` / `gcloud` directly 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 guarded `publish-*` targets are what keep an accidental publish/deploy behind a `CONFIRM_*` variable — reaching for the raw tool bypasses that guard. `make help` lists every target; [`Makefile.md`](./Makefile.md) is the reference. - **GitHub variables**: none. Non-secret identifiers a workflow needs (such as the release App id) are written into the workflow; an unset `vars.X` expands to an empty string without complaint. Only secrets live in repository settings. - **Commits**: imperative subject, no trailing period, ≤72 chars. Match `git log --oneline` for tone. Optional body explains *why*. No emojis. - **PRs**: one logical change each, squash-merged. Run `make pre-commit` locally before committing or pushing — it chains lint + build + typecheck + test, the same gate CI's canonical cell (Linux, Node 26) runs as `make ci`. The other cells (Linux Node 22, macOS, Windows) run `make 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 on `main`. (`make lint` is biome lint + format-drift + import order; skipping it is the failure mode that lets formatting regressions slip into a `bd59e70`-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-commit` is 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 IDE `ReadLints` tool). A partial check is silent about everything it didn't cover. The correct sequence is always `make pre-commit` then `make pack-mcpb`; `pack-mcpb` needs the `dist/` that `pre-commit → build` produces anyway. - **Re-run `make pre-commit` immediately before staging and committing**: if any file was edited after the last green `make pre-commit` run — including by a review pass, a fix-up, or a multi-turn agent session — run `make pre-commit` again before `git add`. A green run from an earlier turn does not cover later edits. `ReadLints` is 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-commit` didn'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/`](./specs/) and any affected `README.md` in the same PR. For a full doc audit (human + agent consumption), use the [`optimize-docs` skill](.cursor/skills/optimize-docs/SKILL.md). - **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/.txt` holding a ` ```release-note: ` block, where `` is `added` / `changed` / `deprecated` / `removed` / `fixed` / `security`. VS Code extension UX goes in `packages/vscode-extension/.changelog/` instead (or as well). Format: [`.changelog/README.md`](./.changelog/README.md). Internal-only changes (build scripts, CI, dep bumps with no behavior change) get no fragment and the `no-changelog` PR label; the `Changelog` check fails a PR with neither, or one that adds entries to `CHANGELOG.md`. The release cut folds fragments into the new version section and deletes them; full contract in [`specs/releasing.md`](./specs/releasing.md#changelog-workflow). - **Round-trips**: changes to the AST, grammar, or printer must keep [`packages/cli/test/convert/roundtrip.test.ts`](./packages/cli/test/convert/roundtrip.test.ts) green — every `examples/` 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 -o .scratch/-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). With each pair, quote the `.nowline` source that draws the affected part in a fenced block: the relevant lines, not the whole file, with the fixture path so the reader can open the rest. 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, each with its `.nowline` excerpt as above. 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//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`](./.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). Only the phase workflows apply `agent-*`, `originator-*` and `maintainer-*` labels, and `agent-review` runs only on Copilot PRs (the `copilot-pr` label). On any other PR (Claude, Cursor, human) apply none of them; a label added by hand makes `human-action-comment.yml` post a comment claiming an agent review ran. The one label you may apply is `no-changelog`, where the changelog rule above says so. 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`](./CONTRIBUTING.md). The package graph and `AssetResolver` contract live in [`specs/architecture.md`](./specs/architecture.md). When this file and `CONTRIBUTING.md` disagree, `CONTRIBUTING.md` wins; please open a PR to reconcile.