Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
7122362
feat(cli): add query DB schema and open helpers
kgilpin May 1, 2026
74ca2be
feat(cli): port query DB importer and hook into fingerprint pipeline
kgilpin May 1, 2026
94b72ee
feat(cli): add query command with endpoints, find, and tree verbs
kgilpin May 1, 2026
0819a8d
chore(cli): drop validate-against-python script
kgilpin May 1, 2026
188d597
feat(cli): rename --db to --query-db, drop APPMAP_QUERY_DB env, add d…
kgilpin May 2, 2026
4684d60
feat(cli): hotspots verb, schema cleanup, normalized class filters
kgilpin May 2, 2026
12d91fa
feat(cli): per-find-type flag validation
kgilpin May 2, 2026
14b300b
fix(query/tree): include exception source location in tree/summary ou…
kgilpin May 2, 2026
e2ea96c
fix(query): make verb builders generic so CommandModule inference holds
kgilpin May 2, 2026
6570cd7
fix(query/find): determinism, --duration on appmaps, basename matchin…
kgilpin May 2, 2026
7eb22bd
fix(query): SQL pushdown for endpoints; canonical class match for fin…
kgilpin May 2, 2026
bd95c0c
fix(query): importer mis-link, verb-layer Class#method split, hotspot…
kgilpin May 2, 2026
7251272
chore(query): widen handler argv type for CommandModule<{}, any> assi…
kgilpin May 2, 2026
302873f
style(query): replace Array<T> with T[] to satisfy lint rule
kgilpin May 2, 2026
848a3ee
feat(query): related and compare verbs
kgilpin May 2, 2026
a63b8a3
feat(query/tree): focus, ancestors/descendants, min-elapsed-ms
kgilpin May 2, 2026
67b0259
feat(query/mcp): MCP server exposing the V3 query surface
kgilpin May 2, 2026
e85517c
feat(query/mcp): rename tools to descriptive verb-noun forms
kgilpin May 2, 2026
b221334
feat(query): document MCP tool outputs, add appmap_id, alias 'recordi…
kgilpin May 2, 2026
6d2982e
style(query): clear remaining lint errors
kgilpin May 2, 2026
2c0f6c5
chore(cli): add typecheck and verify scripts
kgilpin May 3, 2026
2382d29
docs: add CLAUDE.md with per-package verify guidance
kgilpin May 3, 2026
3cb6e4a
feat(query): expose path:lineno on function rows; add list_labels MCP…
kgilpin May 3, 2026
fc9db5a
chore: hoist verify to repo root, scoped to changed packages
kgilpin May 3, 2026
91ea859
feat(query): add find logs verb + find_logs MCP tool
kgilpin May 3, 2026
b87f5b6
feat(query): attach recent_logs to find_exceptions on opt-in
kgilpin May 3, 2026
fc160a9
feat(query): inline log calls in tree render
kgilpin May 3, 2026
2280aa0
feat(query): expose appmap://recording/{ref}/logs MCP resource
kgilpin May 3, 2026
4e72c6b
fix(query): with_logs neighborhood; project log message server-side
kgilpin May 3, 2026
57174ce
feat(query): substring filters + Page<T> pagination across list queries
kgilpin May 4, 2026
a411b32
feat(query/mcp): canonical path id, did_you_mean, truncation signal
kgilpin May 8, 2026
10eaf00
feat(query/ui): add `appmap query ui` web dashboard
kgilpin May 9, 2026
ef2cb37
fix(query/ui): polyfill `process` in embedded viewer bundle
kgilpin May 9, 2026
5cde98f
feat(query/ui): solver trajectory view
kgilpin May 10, 2026
7a5a0be
feat(query/mcp): get_call_tree auto-defaults + diagnostics
kgilpin May 10, 2026
a4a8e34
docs(query/mcp): prune tool descriptions ~60% to defer schema dumps t…
kgilpin May 10, 2026
17ad59d
feat(query/mcp): text-format byte-budgeted get_call_tree as default
kgilpin May 11, 2026
1764439
remove(query/ui): drop --trajectory; trajectory view moves to mcp-ben…
kgilpin May 11, 2026
df87525
fix(query/mcp): get_call_tree focus_value accepts METHOD-prefixed routes
kgilpin May 13, 2026
5452c82
chore(cli): gitignore Gradle test-fixture build/ output
kgilpin May 14, 2026
dde2012
feat(query/mcp): get_call_tree accepts unique project-relative paths
kgilpin May 14, 2026
1a4ab26
feat(query/mcp): surface return values in get_call_tree
kgilpin May 18, 2026
60b9564
feat(query/mcp): value-aware per-field truncation for tree return values
kgilpin May 18, 2026
fba1434
feat(query/import): capture call parameters for every call
kgilpin May 18, 2026
86445ed
feat(query): find_calls event_id filter for exact-id drilling
kgilpin May 18, 2026
ce5256b
docs(query/mcp): clarify find_calls class/method param shape
kgilpin May 21, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ notebooks
tmp/appmap
packages/*/tmp/
packages/cli/tests/unit/fixtures/malformedParentId/parent_id_49_does_not_exist/
# Gradle test fixtures regenerate their build/ trees on every run.
packages/cli/tests/unit/fixtures/java/**/build/
*.appmap.json
.navie
.run-stats
Expand Down
39 changes: 39 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Verifying changes

This is a Yarn 3 monorepo (`packages/*`). CI rejects pushes for both lint errors and tsc errors, with several-minute round-trip costs per CI run. Verify locally before committing.

## When to run verify

After any **substantial batch of changes** — multiple files touched, new functions added, tests added, refactors — run `yarn verify` from the repo root before reporting work complete or asking to commit. A one-line typo fix doesn't need it; a multi-file change does.

## How to run verify

```sh
yarn verify # check working-tree changes (staged + unstaged + untracked)
yarn verify:staged # check only staged changes
```

`scripts/verify.mjs`:

1. Reads modified files from git.
2. Groups them by `packages/<name>/`.
3. For each affected package: runs ESLint (`--quiet`, errors only) on the changed lintable files, then `tsc --noEmit` on the whole package (since TS is project-wide, you can't typecheck a single file).

Typical run on one package: ~5–7s. CI's full lint+typecheck takes ~30s; scoped is ~3× faster.

## Adding verify to a package

If you touch a package that doesn't yet participate, add a `typecheck` script (`tsc --noEmit`) to its `package.json` so `verify.mjs` includes it. The existing `lint` script is enough for ESLint coverage.

## What verify catches

- ESLint errors that CI rejects: `array-type` (use `T[]` not `Array<T>`), `no-unnecessary-type-assertion`, `prefer-function-type`, `prefer-optional-chain`, etc. Warnings (e.g. `no-unsafe-*`, `prefer-nullish-coalescing`) are suppressed by `--quiet`.
- Type errors that `tsc --noEmit` finds, including yargs `CommandModule<T, any>` assignability issues that require widening exported handler argv types.

# Driving the MCP after MCP-side changes

The `appmap query mcp` server lives in `built/cli.js`. If you change anything under `packages/cli/src/cmds/query/queries/mcp.ts` (or anywhere it transitively imports), you must run `npx tsc` (or `yarn build`) inside `packages/cli` before launching `mcp` for ad-hoc testing. A stale binary will respond to `tools/list` with the old surface — symptom is usually `unknown tool: …` from a client driving a tool the source defines.

# Recording with appmap-node from a monorepo

`npx appmap-node@latest npx jest …` invoked from the repo root can fail to parse `.ts` test files with a babel SyntaxError, because the inner jest doesn't pick up `packages/<name>/jest.config.js`'s `ts-jest` preset. Run from the package directory whose preset matters — e.g. `cd packages/cli && npx appmap-node@latest npx jest …`.
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
],
"scripts": {
"lint": "yarn workspaces foreach --exclude root -v run lint",
"verify": "node scripts/verify.mjs",
"verify:staged": "node scripts/verify.mjs --staged",
"test": "yarn workspaces foreach --exclude '{root}' -v run test",
"build": "yarn workspaces foreach -t --exclude root -v run build",
"build-native": "yarn workspaces foreach -t --exclude root -v run build-native",
Expand Down
8 changes: 7 additions & 1 deletion packages/cli/.eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,13 @@ const FIXME = [
module.exports = {
root: true,
ignorePatterns: [
'src/telemetry.ts', // symlink to a separate "package"
'.eslintrc.cjs', // self — type-aware parser doesn't include this file in tsconfig.lint
'src/telemetry.ts', // symlink to a separate "package"
// The query-ui React app has its own toolchain (esbuild + tailwind);
// the main cli ESLint/TS setup doesn't ship React types and treats
// src/html as build-only, matching how src/html/{appmap,navie}.js are
// already handled.
'src/html/query-ui/**',
],
env: {
browser: true,
Expand Down
120 changes: 120 additions & 0 deletions packages/cli/esbuild.html.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,3 +110,123 @@ esbuild
console.warn(err);
process.exit(1);
});

// Query UI React SPA — main app shell.
//
// Output goes under `built/html/query-ui/` so the cli's `query ui` verb
// can serve it (`packages/cli/src/cmds/query/lib/uiServer.ts` looks
// there). Tailwind CSS is built separately by `build:ui:css` and lands
// at `built/html/query-ui/tailwind.css`, referenced from the inline
// HTML template below.
esbuild
.build({
minify: true,
sourcemap: true,
target: 'es2021',
metafile: true,
outdir: 'built/html/query-ui',
bundle: true,
logLevel: 'info',
define: {
'process.env': '{}',
'process.env.NODE_ENV': '"production"',
global: 'window',
},
loader: { '.ts': 'tsx', '.tsx': 'tsx' },
entryPoints: ['src/html/query-ui/src/main.tsx'],
plugins: [
htmlPlugin({
files: [
{
entryPoints: ['src/html/query-ui/src/main.tsx'],
filename: 'index.html',
htmlTemplate: `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>AppMap Query UI</title>
<link rel="stylesheet" href="./tailwind.css" />
</head>
<body class="bg-gray-950 text-gray-100">
<div id="root"></div>
</body>
</html>`,
},
],
}),
],
})
.catch((err) => {
console.warn(err);
process.exit(1);
});

// Query UI — embedded AppMap viewer.
//
// Vue 2 + @appland/components mount that the React detail page loads
// via <iframe src="/viewer.html?appmap=…">. Same alias setup as the
// other Vue-based viewers (appmap.html, sequenceDiagram.html).
//
// The banner installs a `process` shim before the bundle runs because
// transitive deps (e.g. @appland/telemetry's deprecation logger) make
// unguarded `process.stderr.write(...)` calls. The other Vue bundles
// carry the same code but just don't hit that codepath in normal use;
// loadData() in the dashboard's iframe path does.
esbuild
.build({
minify: true,
sourcemap: true,
target: 'es2021',
metafile: true,
outdir: 'built/html/query-ui',
bundle: true,
logLevel: 'info',
define: {
'process.env': '{}',
'process.env.NODE_ENV': '"production"',
global: 'window',
},
banner: {
js: 'window.process=window.process||{env:{},stderr:{write:function(){}},stdout:{write:function(){}},platform:"browser",version:""};',
},
alias: {
http: require.resolve('stream-http'),
https: require.resolve('https-browserify'),
},
entryPoints: ['src/html/query-ui/src/viewer.ts'],
plugins: [
htmlPlugin({
files: [
{
entryPoints: ['src/html/query-ui/src/viewer.ts'],
filename: 'viewer.html',
htmlTemplate: `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>AppMap Viewer</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
html, body, #viewer { width: 100%; height: 100%; overflow: hidden; }
body { background: #0a0a0f; color: #e5e7eb; font-family: system-ui, sans-serif; }
#loading { display: flex; align-items: center; justify-content: center; height: 100%; color: #6b7280; }
#error { display: none; padding: 2rem; color: #ef4444; }
</style>
</head>
<body>
<div id="loading">Loading AppMap...</div>
<div id="error"></div>
<div id="viewer"></div>
</body>
</html>`,
},
],
}),
],
})
.catch((err) => {
console.warn(err);
process.exit(1);
});
11 changes: 10 additions & 1 deletion packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,15 @@
"scripts": {
"lint": "eslint src tests",
"lint:fix": "eslint src tests --fix",
"typecheck": "tsc --noEmit",
"verify": "yarn lint && yarn typecheck",
"pre-commit": "lint-staged",
"test": "jest --filter=./tests/testFilter.js",
"test:binary": "jest -c tests/binary/jest.config.js",
"build": "tsc && yarn build:html && yarn build:doc",
"build": "tsc && yarn build:html && yarn build:ui:css && yarn build:doc",
"watch": "tsc --watch",
"build:html": "ts-node esbuild.html.ts",
"build:ui:css": "tailwindcss -i src/html/query-ui/index.css -o built/html/query-ui/tailwind.css --config src/html/query-ui/tailwind.config.js --minify",
"build:doc": "rsync -av --delete ../../docs built/ && rm -f built/docs/reference/appmap-agent-js.md",
"start": "ts-node src/cli.ts",
"build-native": "yarn build && ./bin/build-native"
Expand Down Expand Up @@ -51,6 +54,8 @@
"@types/moo": "^0.5.5",
"@types/node": "^16",
"@types/proper-lockfile": "^4.1.4",
"@types/react": "^18.3.0",
"@types/react-dom": "^18.3.0",
"@types/semver": "^7.3.10",
"@types/sinon": "^10.0.2",
"@types/tmp": "^0.2.3",
Expand All @@ -71,8 +76,12 @@
"node-fetch": "2.6.7",
"pkg": "5.8.1-patched",
"prettier": "^2.7.1",
"react": "^18.3.0",
"react-dom": "^18.3.0",
"react-router-dom": "^6.26.0",
"sinon": "^11.1.2",
"stream-http": "^3.2.0",
"tailwindcss": "^3.4.0",
"tmp": "^0.2.1",
"tmp-promise": "^3.0.3",
"ts-jest": "^29.0.5",
Expand Down
103 changes: 103 additions & 0 deletions packages/cli/scripts/demo-query.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
#!/usr/bin/env bash
#
# Quick demo of the `appmap query` verbs against a fixture set.
#
# Usage:
# ./scripts/demo-query.sh # uses appmap-apm fixtures if present,
# # else bundled ruby fixtures
# ./scripts/demo-query.sh /path/to/your/appmaps # any directory of *.appmap.json files
#
# Side effects: copies the fixture set to a temp dir, builds and imports a
# query.db there, leaves the originals untouched. Cleans up on exit.

set -euo pipefail

cd "$(dirname "$0")/.." # → packages/cli

# Pick the richest fixture set available.
DEFAULT="$HOME/source/appland/appmap-apm/tests/fixtures/tmp/appmap"
[ -d "$DEFAULT" ] || DEFAULT="$(pwd)/tests/unit/fixtures/ruby"
SRC="${1:-$DEFAULT}"
[ -d "$SRC" ] || { echo "fixture dir not found: $SRC" >&2; exit 2; }

# Temp work area: copy the fixtures so `appmap index` can write fingerprint
# sidecars without touching the originals.
TMP="$(mktemp -d -t appmap-demo)"
DATA="$TMP/data"
DB="$TMP/query.db"
mkdir -p "$DATA"
cp -r "$SRC"/. "$DATA"/
export NODE_NO_WARNINGS=1
trap 'rm -rf "$TMP"' EXIT

CLI=( node "$(pwd)/built/cli.js" )

echo "Building CLI…" >&2
npx tsc 2>&1 | grep -v 'navie-local' >&2 || true

# Filter out diagnostic noise from @appland/models that the verbs themselves
# don't emit (kept loose so we don't suppress real errors).
NOISE='\[DEBUG '

banner() {
echo
echo "── \$ appmap $*"
}
run() {
banner "$@"
"${CLI[@]}" "$@" 2>&1 | grep -vE "$NOISE" || true
}
run_quiet() {
banner "$@"
"${CLI[@]}" "$@" 2>&1 | grep -vE "$NOISE" | tail -5 || true
}

cat <<HEAD
Source : $SRC
Query DB : $DB
HEAD

# Build the query DB. The index command itself is noisy (one line per file
# plus diagnostics from @appland/models) — the demo cares about the verbs,
# not the indexer, so we silence index output and report a row count.
echo
echo "── \$ appmap index --appmap-dir <DATA> --query-db <QUERY_DB>"
"${CLI[@]}" index --appmap-dir "$DATA" --query-db "$DB" >/dev/null 2>&1
COUNT=$(node -e "
const db = require('better-sqlite3')('$DB', { readonly: true });
process.stdout.write(String(db.prepare('SELECT COUNT(*) AS n FROM appmaps').get().n));
")
echo "indexed $COUNT recordings"

run query endpoints --query-db "$DB" --sort p95 --limit 5
run query find queries --query-db "$DB" --table users --limit 3 || true
run query find exceptions --query-db "$DB" --limit 5 || true
run query hotspots --query-db "$DB" --limit 5
run query hotspots --query-db "$DB" --type=sql --limit 3

# related: find passing baselines for a recording (with whatever data exists)
RELATED_SOURCE="$(node -e "
const db = require('better-sqlite3')('$DB', { readonly: true });
const r = db.prepare(\"SELECT name FROM appmaps WHERE name LIKE '%oups%' LIMIT 1\").get();
process.stdout.write(r ? r.name : '');
")"
if [ -n "$RELATED_SOURCE" ]; then
run query related "$RELATED_SOURCE" --query-db "$DB" --limit 5
fi

# Pick the recording with the most events for the tree demos.
APPMAP="$(node -e "
const db = require('better-sqlite3')('$DB', { readonly: true });
const r = db.prepare(
'SELECT name FROM appmaps WHERE event_count > 0 ORDER BY event_count DESC LIMIT 1'
).get();
process.stdout.write(r ? r.name : '');
")"

if [ -n "$APPMAP" ]; then
run query tree "$APPMAP" --query-db "$DB" --format=summary
run query tree "$APPMAP" --query-db "$DB" --filter=sql
fi

echo
echo "Done."
2 changes: 2 additions & 0 deletions packages/cli/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

import 'reflect-metadata';

const yargs = require('yargs');

Check warning on line 11 in packages/cli/src/cli.ts

View workflow job for this annotation

GitHub Actions / test_cli

Unsafe assignment of an `any` value
const { promises: fsp, readFileSync } = require('fs');
const { queue } = require('async');
const { join } = require('path');
Expand Down Expand Up @@ -40,6 +40,7 @@
import * as RpcClientCommand from './cmds/rpcClient';
import * as NavieCommand from './cmds/navie';
import * as ApplyCommand from './cmds/apply';
import * as QueryCommand from './cmds/query/query';
import * as RunTestCommand from './cmds/runTest';
import TelemetryTestCommand from './cmds/testTelemetry';
import { default as sqlErrorLog } from './lib/sqlErrorLog';
Expand Down Expand Up @@ -156,6 +157,7 @@
.command(RpcClientCommand)
.command(NavieCommand)
.command(ApplyCommand)
.command(QueryCommand)
.command(RunTestCommand)
.command(TelemetryTestCommand)
.option('verbose', {
Expand Down
Loading
Loading