Skip to content

Repository files navigation

WAYMARK_v3_DEMO.mp4

waymark

Human oversight as a property of the system, not a property of the agent.

Waymark is MCP middleware that sits between an AI agent and the filesystem. Every write_file and bash call is evaluated against your policy before it touches anything — blocked, held for approval, or logged and allowed. Nothing the agent does is permanent until you say so.

npm downloads npm downloads npm version docs

npm download chart

Updated every 6 hours via GitHub Actions


Three Pillars

Control — enforce the rules you set, unconditionally

Policy lives in waymark.config.json, not in a system prompt. An agent cannot forget a rule, be jailbroken past it, or accumulate enough context to override it. Every tool call is evaluated in Node.js process space before the filesystem is touched.

blockedPaths    → BLOCK    (hard deny — secrets, system files)
requireApproval → PENDING  (human must approve before execution)
allowedPaths    → ALLOW    (execute immediately, log it)
(default)       → BLOCK    (unknown paths are rejected)

Observe — know what is happening, in real time and historically

The action ledger records every tool call to SQLite with decision, reason, output, and timestamps. The dashboard at http://localhost:47000 is always available. waymark watch gives you an ANSI terminal view. The Agent Monitor (/agents) shows every AI session on the machine — model, context%, tokens, turns, rate limits — as a live btop-style table.

Recover — every write is undoable

Before-snapshots are captured at write time. A single action can be rolled back. An entire session can be atomically undone in reverse order. Approving a write knowing you can roll it back is categorically different from approving without a net.


60-Second Quickstart

Option A — Zero global install

cd your-project
npx @way_marks/cli init --yes   # create waymark.config.json + CLAUDE.md, register MCP
npx @way_marks/cli start        # start server, open dashboard at localhost:<port>

Restart Claude Desktop or reload your Claude Code session to pick up the new MCP server. Done.

Option B — Global install with daemon (recommended for v5)

npm install -g @way_marks/cli
waymark global-setup            # register universal MCP entry once across all hosts
cd your-project
waymark init                    # project-specific init (idempotent — safe to re-run)
waymark daemon start            # start one daemon at localhost:47000 for all projects
waymark daemon status           # list registered projects + uptime
waymark status                  # check this project's status + pending count
waymark logs --pending          # show pending actions waiting for approval

The daemon serves all your registered projects from a single process. Switch projects in the dashboard via the Active Project dropdown.


Part of a Suite

Waymark is the control plane of a four-tool developer suite:

Tool Role What it does
devloop Build Multi-agent dev pipeline — architect → worker → reviewer loop
waymark Run Policy enforcement + observability for AI agents
teststop Break Adversarial scenario testing — acts as a real, impatient user
docuflow Document Decision-context wiki for AI agents

Adversarial testing: teststop runs 52 predicted risk scenarios against waymark's live API, targeting edge cases a real user would hit — concurrent approvals, stale tabs, rollback races, policy bypass attempts. See examples/waymark-demo for the full scenario set and confidence report. The v5.0.13–v5.0.16 safety fixes were all surfaced by this suite.


Platform Support

Platform Status Features Setup
Claude Desktop ✅ Recommended Full features waymark init
Claude Code ✅ Recommended Full features waymark init
GitHub Copilot CLI ✅ Supported Full features waymark init
GitHub Copilot Chat ⏳ Future Waiting for GitHub MCP See Platform Guide
CodeWhisperer, Codeium, others ⏳ Future Waiting for MCP adoption See Platform Guide

👉 For detailed platform information, see docs/README_PLATFORMS.md


📚 Documentation

Full documentation site: shaifulshabuj.github.io/waymark

Covers installation, quickstart, CLI reference, API reference, policy engine, approvals, rollback, agent monitor, philosophy, and changelog.

User stories and feature walkthroughs: docs/user-stories/README.md


Changelog

See CHANGELOG.md for the full release history.

Recent highlights:

  • v5.0.20 — maxBashTimeoutMs policy: per-call bash timeout clamping via waymark.config.json
  • v5.0.16 — 30+ robustness fixes + complete Vitest migration (624 tests, 33 files)
  • v5.0.13 — 17 P0 safety fixes surfaced by teststop adversarial testing
  • v5.0.0 — Unified daemon architecture: one process, all projects, one URL

Architecture

┌──────────────────────────────────────────────────┐
│                 mcp/server.ts                    │
│                                                  │
│  1. loadConfig()          → policies/engine.ts   │
│  2. checkFileAction()                            │
│     or checkBashAction()                         │
│                                                  │
│  3a. decision=block  → insertAction(blocked)     │
│                         throw Error (no exec)    │
│                                                  │
│  3b. decision=pending → insertAction(pending)    │
│                         notifyPendingAction()    │
│                         return message+action_id │
│                         (agent can poll status)  │
│                                                  │
│  3c. decision=allow  → insertAction(allow)       │
│                         execute tool             │
│                         updateAction(result)     │
└──────────────────────────────────────────────────┘
         │                        ▲
         │ SQLite                 │ REST API
         ▼                        │
┌─────────────┐        ┌──────────────────────────┐
│ data/       │        │      api/server.ts        │
│ waymark.db  │◄───────│  GET  /api/actions        │
└─────────────┘        │  GET  /api/actions/:id    │
                       │  POST /api/actions/:id/   │
                       │        approve            │
                       │  POST /api/actions/:id/   │
                       │        reject             │
                       │  GET  /api/actions/:id/   │
                       │        status             │
                       │  POST /api/actions/:id/   │
                       │        rollback           │
                       │                           │
                       │  [Phase 1: Sessions]      │
                       │  GET  /api/sessions       │
                       │  GET  /api/sessions/:id   │
                       │  GET  /api/sessions/:id/  │
                       │        actions            │
                       │  POST /api/sessions/:id/  │
                       │        rollback           │
                       │  GET  /api/sessions/:id/  │
                       │        status             │
                       │                           │
                       │  POST /api/slack/interact │
                       │  GET  /api/config         │
                       │  GET  /  → index.html     │
                       └──────────────────────────┘
                                  ▲
                          Browser http://localhost:&lt;port&gt;

Three packages, seven server modules, two runtime processes. The dashboard is a separate React + Vite app (packages/web) whose build output is served statically by the API process.

packages/server/ — MCP server + API server + UI host

File Process What it does Imports
packages/server/src/mcp/server.ts MCP (stdio) Exposes tools to AI agent, enforces policy, logs actions, sends Slack notifications db/database, policies/engine, notifications/slack
packages/server/src/api/server.ts API (default port from 47000-47999) REST API + serves the built React dashboard from src/ui-dist/. Port set via WAYMARK_PORT env var by the CLI; pinnable via port in waymark.config.json or waymark start --port <n>. db/database, policies/engine, approvals/handler
packages/server/src/db/database.ts both SQLite schema, prepared statements, CRUD functions better-sqlite3
packages/server/src/policies/engine.ts both Loads config, evaluates file/bash actions against rules micromatch
packages/server/src/notifications/slack.ts MCP (stdio) Sends Slack Block Kit message for pending actions; silent if webhook not configured db/database (ActionRow type)
packages/server/src/approvals/handler.ts API (port from CLI) Re-executes approved write_file actions; marks approved/rejected in DB db/database, fs
packages/server/src/ui-dist/ browser Vite build output — the React dashboard (produced by packages/web). If missing, the API serves a small setup page pointing to the build command. —
packages/server/src/api/events.ts API (port from CLI) Server-Sent Events broadcaster; emits per-topic events on every mutation so the UI can invalidate without polling. —
packages/server/src/collectors/ API only (single setInterval, .unref()d) btop-style agent monitor (added in 4.1). MultiCollector.tick() orchestrates claude.ts / codex.ts / copilot.ts collectors on a 2 s fast tick + 10 s slow tick (ps, lsof, git); applies normalizeSession() to every row (number/array defaults, status clamp, redactSecrets() over free text); JSONL parsing wraps each JSON.parse(line) in try/catch so a corrupted line skips rather than poisons the tick. Outputs CollectorSnapshot { sessions, rateLimits, orphanPorts, collectedAt }. process.ts, secrets.ts, rate-limit.ts, types.ts
packages/server/src/api/routes/agent-monitor.ts API (port from CLI) REST endpoints under /api/agent-monitor/* (sessions, rate-limits, ports, snapshot). Filterable by ?agent= and ?status=. collectors/multi-collector
packages/server/src/mcp/tools/agent-monitor.ts MCP (stdio) Three read-only MCP tools — list_agent_sessions, get_rate_limits, get_agent_ports. Bypass the action log. The MCP process does not run its own collector; fetchSnapshotFromApi() calls http://127.0.0.1:<port>/api/agent-monitor/snapshot on demand (returns an empty snapshot if the API is offline). Single source of truth, no parallel ps polls. api/routes/agent-monitor (via HTTP)

packages/web/ — React + Vite dashboard (added in 2.0.3)

Area Purpose
vite.config.ts Build output goes to ../server/src/ui-dist/; dev server on :5173 with /api proxied to the running Waymark server (set WAYMARK_API env var to override the proxy target)
src/api/ Typed fetch client + TanStack Query hooks (3-second refetchInterval, invalidate-on-success mutations)
src/components/AppShell 232 px sidebar + topbar with Cmd-K search + live MCP status footer
src/components/{Drawer,ActionRow,SessionGroup,Icon,TweaksPopover,ConfirmModal,ToastContext} Cross-screen primitives
src/features/actions/ Feature-complete Actions screen (filter pills, session groups, approve/reject/rollback)
src/features/agent-monitor/ btop-style live agent view (added in 4.1) — AgentMonitorView, SessionCard, RateLimitBadge, PortsList. Polls /api/agent-monitor/* every 3 s.
src/features/ComingSoon.tsx Placeholders for Sessions/Approvals/Policy/Stats/Settings (Phases 2–4)
src/store/ui.ts Zustand store for theme, density, grouping, accent, filter, search, selectedActionId; persisted to localStorage as waymark:ui
src/styles/{tokens,global}.css oklch-based dark + light tokens, IBM Plex Sans/Mono self-hosted, density variants, semantic colors, responsive breakpoints

packages/cli/ — waymark CLI (init, start, stop, status, logs)

File What it does
packages/cli/src/index.ts Entry point — routes process.argv[2] to command modules
packages/cli/src/commands/init.ts waymark init — creates config, CLAUDE.md (and COPILOT.md when copilot-cli is selected), updates .gitignore, registers MCP in Claude config and ~/.copilot/mcp-config.json
packages/cli/src/commands/agents.ts waymark agents (added in 4.1) — fixed-column table of live AI coding agent sessions (Claude, Codex, Copilot CLI). Flags: --agent, --active, --json, --limit
packages/cli/src/commands/start.ts waymark start — spawns API + MCP servers as detached background daemons, writes .waymark/waymark.pid, opens browser, exits immediately
packages/cli/src/commands/stop.ts waymark stop — reads .waymark/waymark.pid, SIGTERMs both processes, deletes PID file
packages/cli/src/commands/status.ts waymark status — reads PID file, uses process.kill(pid, 0) liveness check, prints pending count and server state; auto-cleans stale PID file on crash
packages/cli/src/commands/logs.ts waymark logs — prints action table; supports --pending, --blocked, --limit N

The two server processes run concurrently and share the same SQLite file. The MCP process writes; the API process reads. SQLite handles concurrent access safely because all database calls are synchronous (better-sqlite3).


CLI Reference

waymark init               # first-time setup (idempotent — safe to re-run)
waymark init --yes         # non-interactive, accept all defaults
waymark init --dry-run     # preview what would be created without writing files
waymark global-setup       # register universal MCP entry across all supported hosts
waymark cleanup-mcp        # remove stale per-project waymark-* entries

waymark daemon start       # start global daemon (port 47000, serves all projects)
waymark daemon stop        # stop the daemon
waymark daemon status      # list registered projects + uptime
waymark daemon restart     # reload after upgrading

waymark service install    # auto-start daemon on login (launchd/systemd)
waymark service uninstall  # remove auto-start

waymark start              # start per-project API + MCP server in background
waymark stop               # stop the background servers
waymark status             # check if server is running + pending count + port

waymark logs               # print recent action table
waymark logs --pending     # only pending actions
waymark logs --blocked     # only blocked actions
waymark logs --limit 50    # show up to 50 rows (default: 20)

waymark agents             # live table of AI coding agent sessions
waymark agents --json      # full token / tool / turn breakdown
waymark watch              # live ANSI terminal dashboard (2s refresh)

waymark explain <id>       # human-readable summary of any action
waymark doctor             # validate dependencies and configuration
waymark update             # self-upgrade to latest version
waymark relocate           # update registry after rename/move

waymark global-config init # create global defaults in ~/.waymark/global.config.json
waymark team add <email>   # add a team member
waymark workspace create   # group related projects

waymark.config.json Reference

Waymark reads waymark.config.json from the project root fresh on every tool call — no restart needed when you edit it.

{
  "version": "1",
  "policies": {
    "allowedPaths":    ["./src/**", "./data/**", "./README.md"],
    "blockedPaths":    ["./.env", "./.env.*", "./package-lock.json", "/etc/**", "/usr/**"],
    "blockedCommands": [
      "rm -rf",
      "DROP TABLE",
      "regex:\\|\\s*bash",
      "regex:\\|\\s*sh\\b"
    ],
    "requireApproval": ["./src/db/**", "./waymark.config.json"],
    "maxBashOutputBytes": 10000,
    "maxBashTimeoutMs": 120000
  }
}

Field reference

Field Type Description
allowedPaths string[] Glob patterns for paths agents may read/write. Relative patterns resolve from the project root.
blockedPaths string[] Glob patterns that are always denied. Checked before allowedPaths.
blockedCommands string[] Bash rules. Plain strings match as substrings; regex: prefix enables regex matching.
requireApproval string[] Glob patterns that are logged as pending and not executed — manual approval required.
maxBashOutputBytes number Cap on stdout/stderr bytes returned per bash call (default: 10000). Output beyond this limit is truncated with [OUTPUT TRUNCATED].
maxBashTimeoutMs number Per-call bash timeout cap in ms (clamped to 1000–600000). Overrides the MCP client's default.

File action precedence

For every write_file or read_file call, the path is tested in this order:

1. blockedPaths?    → decision: block   (throws error, file not touched)
2. requireApproval? → decision: pending (returns message with action_id, file not touched)
3. allowedPaths?    → decision: allow   (proceeds)
4. (default)        → decision: block   ("Path not in allowedPaths")

blockedCommands: plain string vs regex

// Substring match — blocks any command containing "rm -rf"
"rm -rf"

// Regex match — blocks any pipe to bash (curl url | bash, cat x | bash, etc.)
"regex:\\|\\s*bash"

// Regex match — blocks command substitution with curl
"regex:\\$\\(curl"

Regex rules use JavaScript RegExp with the i (case-insensitive) flag. Invalid regex entries are skipped with a console.warn.

Pattern matching

Relative patterns (./src/**) are resolved to absolute paths from the project root before matching. Absolute patterns (/etc/**) pass through unchanged. Matching uses micromatch with { dot: true } so dotfiles (.env) are included.


REST API Reference

All endpoints are served by api/server.ts on the project's allocated port (default range 47000-47999; pin with port in waymark.config.json or --port flag). Run waymark status to see the live URL.

Method Path Response Notes
GET /api/actions ActionRow[] Last 100 actions, newest first
GET /api/actions?count=true { count: number } Count of pending actions; used by UI badge
GET /api/actions/:action_id ActionRow Single action by UUID
GET /api/actions/:action_id/status { status, decision, approved_by?, approved_at?, rejected_reason?, rejected_at? } Lightweight status for agent polling
POST /api/actions/:action_id/approve { success, action } Executes the original action; sets approved_by='ui'
POST /api/actions/:action_id/reject { success, action } Body: { reason?: string }; marks rejected, does not execute
POST /api/actions/:action_id/rollback { success, action, message } write_file only; see Rollback section
POST /api/slack/interact { text } Slack interactive components endpoint; requires public URL (use ngrok locally)
GET /api/sessions { session_id, action_count, latest }[] Grouped by MCP server process start
GET /api/config WaymarkConfig Current parsed waymark.config.json
GET / HTML Web dashboard (src/ui/index.html)

Pending action flow

When decision=pending, the MCP server:

  1. Logs the action to DB with status='pending'
  2. Fires a Slack notification (if WAYMARK_SLACK_WEBHOOK_URL is set in .env)
  3. Returns a message to the agent containing the action_id and the polling URL:
    Action requires approval.
    Action ID: <uuid>
    Check status: GET /api/actions/<uuid>/status
    
    The agent can poll /api/actions/:id/status and continue other work while waiting.

On approve: approvePendingAction re-executes the original tool (writes the file for write_file), sets status='success', decision='allow', and records approved_by + approved_at.

On reject: rejectPendingAction sets status='rejected' and records rejected_reason + rejected_at. The file is never written.

Slack setup

Slack integration is optional. If WAYMARK_SLACK_WEBHOOK_URL is empty, notifications are silently skipped and approval works entirely from the UI dashboard.

There are two independent pieces:

  1. Incoming webhook — posts a notification card when an action goes pending (one-way, read-only setup)
  2. Interactive components — lets you approve/reject directly from the Slack message (requires a public URL)

You can use (1) without (2). Notifications will arrive but the buttons won't work.

Step 1 — Create a Slack App

  1. Go to https://api.slack.com/apps → Create New App → From scratch
  2. Name it Waymark, pick your workspace → Create App

Step 2 — Incoming Webhook (notifications)

  1. In your app's sidebar: Incoming Webhooks → toggle Activate Incoming Webhooks ON
  2. Click Add New Webhook to Workspace → pick the channel (e.g. #engineering) → Allow
  3. Copy the webhook URL — it looks like https://hooks.slack.com/services/T.../B.../...
  4. Add to .env:
WAYMARK_SLACK_WEBHOOK_URL=https://hooks.slack.com/services/T.../B.../...
WAYMARK_SLACK_CHANNEL=#engineering
WAYMARK_BASE_URL=http://localhost:47000

WAYMARK_BASE_URL is used to build the "View in Dashboard" link inside the Slack card. Set it to whatever URL your Waymark UI is reachable at.

Restart the server. The next pending action will post a card to Slack.

Step 3 — Interactive Components (approve/reject buttons)

The Approve and Reject buttons in the Slack card POST to POST /api/slack/interact. Slack requires this endpoint to be publicly reachable over HTTPS.

For local development — use ngrok:

ngrok http 47000

Copy the https:// forwarding URL (e.g. https://abc123.ngrok-free.app).

  1. In your Slack app sidebar: Interactivity & Shortcuts → toggle Interactivity ON
  2. Set Request URL to: https://abc123.ngrok-free.app/api/slack/interact
  3. Click Save Changes

ngrok URLs change on every restart. Update the Request URL in Slack each time.

For production — set a stable public URL:

Deploy Waymark behind a reverse proxy (nginx, Caddy, etc.) with TLS, then set the Request URL once to https://your-domain.com/api/slack/interact. Update WAYMARK_BASE_URL in .env to match.

Step 4 — Verify

  1. Trigger a pending action (write to a requireApproval path via the MCP tool)
  2. A card should appear in your Slack channel with Approve and Reject buttons
  3. Click Approve — the button should update the message and the UI dashboard should show the action as success / allow

Troubleshooting

Symptom Cause Fix
No Slack message appears WAYMARK_SLACK_WEBHOOK_URL empty or wrong Check .env, restart server
Buttons do nothing Request URL not set or ngrok restarted Update Interactivity URL in Slack app settings
dispatch_failed error in Slack Waymark returned non-200 or timed out Check server logs; ensure /api/slack/interact is reachable
cannot_parse_subscription Webhook URL is malformed Re-copy URL from Slack app settings
Buttons appear but action stays pending approved_by not set Check server logs for handler errors

Database Schema

Single table: action_log in data/waymark.db.

Column Type Nullable Description
id INTEGER NO Auto-increment primary key
action_id TEXT NO UUID — unique per tool call
session_id TEXT NO UUID — shared across all calls in one MCP server process
tool_name TEXT NO write_file, read_file, or bash
target_path TEXT YES Absolute file path for write/read; null for bash
input_payload TEXT NO JSON-serialized tool arguments
before_snapshot TEXT YES File content before write (write_file only); null if file didn't exist
after_snapshot TEXT YES File content after write, or file content for reads
status TEXT NO pending → success / error / blocked
error_message TEXT YES Error detail when status=error
stdout TEXT YES bash stdout output
stderr TEXT YES bash stderr output
rolled_back INTEGER NO 0 or 1
rolled_back_at TEXT YES ISO datetime of rollback
created_at DATETIME NO Row creation time
decision TEXT NO Policy decision: allow, block, or pending
policy_reason TEXT YES Human-readable reason for the decision
matched_rule TEXT YES The specific rule that matched (e.g. ./.env, rm -rf, regex:|\s*bash)
approved_at TEXT YES ISO datetime when approved
approved_by TEXT YES 'ui' or 'slack'
rejected_at TEXT YES ISO datetime when rejected
rejected_reason TEXT YES Human-provided rejection reason

Schema migrations use try { ALTER TABLE ADD COLUMN ... } catch {} — safe to run against existing databases.


Rollback

Rollback is available for write_file actions only. The UI shows a "rollback" or "delete (new file)" button in the Action column.

Overwrite rollback: before_snapshot is non-null → the file existed before. Rollback writes before_snapshot back to target_path.

New file rollback: before_snapshot is null → the file was created from scratch. Rollback calls fs.unlinkSync(target_path) — the file is deleted.

Blocked actions never modify the filesystem, so they have no rollback button.

Pending actions also never modify the filesystem until explicitly approved. After approval, the approved action row shows a ✅ Approved badge instead of a rollback button.

Once rolled back, rolled_back=1 is set and the action cannot be rolled back again.


Plan Mode Logging & Observability

Waymark logs execution-phase tool calls only — not plan-mode reads, not internal reasoning. Only actual MCP tool calls (write_file, read_file, bash) appear in the ledger. This is a protocol-level constraint: MCP has no mechanism for "I'm thinking about this file" vs "I'm executing with this file."


For AI Agents

Key facts for working with this codebase:

Module dependency order (bottom-up):

micromatch, better-sqlite3, express, dotenv, fetch (Node 18+ built-in)
    ↓
packages/server/src/db/database.ts            (no imports from waymark modules)
packages/server/src/policies/engine.ts        (no imports from waymark modules)
    ↓
packages/server/src/notifications/slack.ts    (imports db/database for ActionRow type only)
packages/server/src/approvals/handler.ts      (imports db/database)
    ↓
packages/server/src/mcp/server.ts             (imports database + engine + notifications/slack)
packages/server/src/api/server.ts             (imports database + engine + approvals/handler)

Path resolution — WAYMARK_PROJECT_ROOT: Both database.ts and policies/engine.ts resolve their data/config paths via:

const PROJECT_ROOT = process.env.WAYMARK_PROJECT_ROOT || process.cwd();
const DATA_DIR = path.join(PROJECT_ROOT, 'data');           // database.ts
const CONFIG_PATH = path.join(PROJECT_ROOT, 'waymark.config.json');  // engine.ts

WAYMARK_PROJECT_ROOT is set by the --project-root CLI argument parsed at the very top of mcp/server.ts before any imports (because database.ts and engine.ts read the env var at module load time). When running via waymark start, process.cwd() is the project directory so the fallback works correctly.

Database is synchronous: better-sqlite3 is used throughout. There is no await on any database call. Do not introduce async database wrappers.

Config is read fresh every call: loadConfig() in engine.ts reads and parses waymark.config.json on every invocation. No caching. Changes to the config file take effect immediately without restarting the MCP server.

MCP server must be restarted to pick up recompiled code: The policy config is hot-reloaded, but the TypeScript-compiled JavaScript is not. After npm run build, restart Claude Desktop (or the MCP process) for engine changes to take effect.

SESSION_ID is a UUID generated once per MCP server process start (const SESSION_ID = uuidv4() at module level in mcp/server.ts). It groups all tool calls within one session.

Environment variables (loaded via dotenv/config as first import in both server files):

  • WAYMARK_PROJECT_ROOT — absolute path to the project being monitored; determines where data/waymark.db and waymark.config.json are read from. Set via --project-root CLI arg or waymark start. Falls back to process.cwd().
  • WAYMARK_SLACK_WEBHOOK_URL — Slack incoming webhook; if empty, Slack notifications are silently skipped
  • WAYMARK_SLACK_CHANNEL — informational only, not used in code (webhook targets its own channel)
  • WAYMARK_BASE_URL — used to build "View in Dashboard" link in Slack messages (default: http://localhost:47000)

Route registration order in api/server.ts is significant: /approve, /reject, /status sub-routes are registered before the /:action_id catch-all GET, otherwise Express would match the sub-routes as action IDs.

Before modifying mcp/server.ts: the policy check must remain before insertAction for blocked/pending cases — these actions must never touch the filesystem. For pending, the Slack notification fires after insertAction with an inline ActionRow object (no DB round-trip).

Before modifying policies/engine.ts: the precedence order (blocked → pending → allowed → default deny) is load-bearing. Changing it changes observable behavior for all callers.

Known gaps:

  • No rate limiting on API endpoints
  • getActions() is hardcoded to LIMIT 100 with no pagination

Documentation

The full documentation lives in docs/. Start at docs/README.md for a grouped index of every guide.

Most-used pages:


Development

npm run build                   # compile both packages (tsc in each workspace)
cd packages/server && npm run dev:api    # API server only (ts-node)
cd packages/server && npm run dev:mcp   # MCP server only (ts-node)
cd packages/server && npm run db:reset  # delete data/waymark.db and recreate schema
npm test                        # run the full test suite (vitest, 624 tests, 33 files)

Building the CLI: cd packages/cli && npm run build compiles to packages/cli/dist/. Test locally with node packages/cli/dist/index.js <command>.

Extending tools: Add new tool schemas to ListToolsRequestSchema handler and a new else if (name === '...') branch in CallToolRequestSchema handler in packages/server/src/mcp/server.ts. Follow the existing pattern: policy check → insertAction → execute → updateAction.


Release Operations

Automated Release (Recommended)

npm run release major|minor|patch

The script validates git state, bumps versions in both packages/cli/package.json and packages/server/package.json, updates CHANGELOG.md, commits, tags, and pushes. GitHub Actions release.yml then publishes to npm and creates the GitHub Release.

Important: Both package versions must always match — the CLI declares a hard dependency on the exact server version.

Manual Release

  1. Bump versions in packages/server/package.json and packages/cli/package.json (both must match).
  2. Update the server dependency reference inside packages/cli/package.json → dependencies["@way_marks/server"] to the same new version.
  3. Regenerate the lock file: npm install --package-lock-only
  4. Add a release/CHANGELOG.md entry for the new version.
  5. Add a root CHANGELOG.md entry.
  6. Commit, then tag: git tag v<version> && git push origin v<version>

Known CI/CD failure: E403 "cannot publish over previously published version"

Cause: The version in either packages/cli/package.json or packages/server/package.json was not bumped before tagging. npm does not allow re-publishing an already-released version.

Fix: Bump both package versions, update the @way_marks/server dependency in packages/cli/package.json, run npm install --package-lock-only, commit the version bump, re-tag, and push.

About

Control what AI agents can do in your codebase — documentation

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages