Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
67 commits
Select commit Hold shift + click to select a range
f8375be
Track D: --flatten-cells json for CSV/TSV, with NotWritable.hints
hakimjonas May 2, 2026
ad449db
Track D audit: structured Hint type instead of baked multi-surface st…
hakimjonas May 2, 2026
d2094e1
Track C: --ndjson mode for line-delimited JSON input
hakimjonas May 2, 2026
3f6741c
Functional test coverage for CLI + MCP wiring
hakimjonas May 2, 2026
8b67969
Track B: richer --explain output (runtime/trivial warnings + JSON)
hakimjonas May 2, 2026
9c96f7c
Gap closure: structured shapes in --explain-json, cascade cover
hakimjonas May 2, 2026
8979e44
Track A design doc: schema-typed queries with SOptional
hakimjonas May 2, 2026
c61ae44
Track A step 1: SOptional shape variant
hakimjonas May 2, 2026
48b3780
Track A step 2: JSON Schema subset parser
hakimjonas May 2, 2026
6a93b9a
Track A step 3: schema loader with sibling auto-detect + merge
hakimjonas May 2, 2026
ebb1460
Track A step 4: JSON Schema renderer with round-trip invariant
hakimjonas May 2, 2026
a5da7d6
Track A step 5: CLI rewiring with --schema and --print-shape
hakimjonas May 2, 2026
5ec6c5e
Track A self-review fixes
hakimjonas May 2, 2026
4941e37
Track A step 6: REPL schema integration
hakimjonas May 2, 2026
878abec
Track A step 7: MCP server schema integration
hakimjonas May 2, 2026
6e36008
Track A step 8: CLI integration tests for --schema and --print-shape
hakimjonas May 2, 2026
7a099b7
Pre-docs: add lambe_explain MCP tool
hakimjonas May 2, 2026
3f4e3c4
Track A step 9: docs polish for 0.9.0
hakimjonas May 2, 2026
0b8d3a5
Install ergonomics: install.sh with checksum verification
hakimjonas May 3, 2026
e50c681
Release prep audit + tool/release_prep.sh
hakimjonas May 3, 2026
14f757b
Pre-push cleanup: gitignore local tool cache dir, reframe schema-desi…
hakimjonas May 3, 2026
f951f8a
feat(parser+eval): list literals, // alternative, jq-ism keyword aliases
hakimjonas May 20, 2026
a1be733
feat(errors): jq-idiom hints for parse failures
hakimjonas May 20, 2026
2965892
perf(parser): migrate operator precedence from chainl1 ladder to Pratt
hakimjonas May 18, 2026
8529aba
refactor(parser): use rumil's cFamilyPrecedence preset
hakimjonas May 19, 2026
f0f6f4b
chore(deps): bump rumil family to ^0.7.0; drop pubspec_overrides
hakimjonas May 20, 2026
24702e8
chore: gitignore *.scratch.md for local scratch notes
hakimjonas May 20, 2026
e7dc161
0.9.0: pipe-op AST consolidation + REPL highlighter migration + Tier …
hakimjonas May 21, 2026
ee83616
chore: gitignore lam binary; fix to_entries doc example output
hakimjonas May 21, 2026
d2e07e2
feat(cli): --print-shape composes with query expression
hakimjonas May 21, 2026
9da5547
fix(explain): suppress writability when runtime-rejection fires
hakimjonas May 21, 2026
8501256
feat(cli): -n / --null-input flag
hakimjonas May 21, 2026
710f9e2
feat(parser): jq idiom hints for try/recurse/paths/range/@csv
hakimjonas May 21, 2026
fad6c9a
fix(shape): heterogeneous list rendering hint
hakimjonas May 21, 2026
29abef5
chore: as(fmt) ambiguity investigation; soften As class doc
hakimjonas May 21, 2026
e53fa22
docs: as(fmt) bridges reference in recipes.md
hakimjonas May 21, 2026
82168f6
docs(CHANGELOG): Tier B entries
hakimjonas May 21, 2026
4d0e3bb
chore(deps): bump rumil_parsers usage; update HCL block-shape tests
hakimjonas May 22, 2026
d5588f8
feat(eval): markdown text extraction op
hakimjonas May 22, 2026
34fe6d2
docs(non-goals): enumerate deliberate omissions; cross-link from READ…
hakimjonas May 22, 2026
8c62b68
fix(parser): object construction accepts JSON-string keys
hakimjonas May 22, 2026
8ac995f
docs: -r option semantics precision
hakimjonas May 22, 2026
feffe75
docs(CHANGELOG): Tier C entries; bench harness for C4
hakimjonas May 22, 2026
2392089
chore(deps): bump rumil_parsers to ^0.8.0; update JsonNumber consumer
hakimjonas May 22, 2026
76b02e1
docs(CHANGELOG): record measured 0.8.0 → 0.9.0 perf numbers
hakimjonas May 23, 2026
3958e0f
tool: lint_changelog.sh — validate CHANGELOG via lambé itself
hakimjonas May 23, 2026
dfdfaca
ci: gate every push on tool/lint_changelog.sh
hakimjonas May 23, 2026
4ecf429
docs(recipes): CHANGELOG querying via lambé itself
hakimjonas May 23, 2026
9f5b440
docs(CHANGELOG): note self-validation tooling under 0.9.0
hakimjonas May 23, 2026
14cc10f
chore(test): annotate empty list literal to clear inference warning
hakimjonas May 23, 2026
3d8bd4f
docs(syntax): rewrite all examples as runnable invocations with real …
hakimjonas May 23, 2026
1f60870
docs(CHANGELOG): refresh perf numbers post rumil_parsers cross-format…
hakimjonas May 23, 2026
96ead7d
fix(repl): pipe op names highlight as keywords; redraw on every keyst…
hakimjonas May 23, 2026
9909403
feat(completer): bare pipe-op completion inside parameterised pipe ops
hakimjonas May 23, 2026
1a885bc
docs(CHANGELOG): record REPL highlighter and completer fixes from smo…
hakimjonas May 23, 2026
7a65a7a
fix(text): soft_break contributes ' ', hard_break contributes '\n'
hakimjonas May 23, 2026
7c631a6
feat(completer): heterogeneous-list completion via data sampling
hakimjonas May 23, 2026
5de9d6d
docs(CHANGELOG): record soft/hard break and heterogeneous-list comple…
hakimjonas May 23, 2026
4922808
chore: scrub package payload — drop AOT binary, scratch notes, dev probe
hakimjonas May 23, 2026
b1d4a3f
chore: exclude server.json from pub.dev publish payload
hakimjonas May 23, 2026
72e45fd
refactor: convert four is-cascades to switch expressions over typed JSON
hakimjonas May 23, 2026
999ee57
refactor(mcp): _errorResult helper; fix stale .children[0].text recom…
hakimjonas May 23, 2026
ab8d696
refactor(api): queryString delegates to parseAst, no duplicated Resul…
hakimjonas May 23, 2026
fab027d
docs(agents): consolidate AGENTS.md + AI.md; exclude agent docs from …
hakimjonas May 23, 2026
ec1c441
feat(skill): ship Agent Skills package at .claude/skills/lambe/
hakimjonas May 23, 2026
f53a744
ci: pin SDK to 3.12.0; reformat under 3.12.0's formatter
hakimjonas May 23, 2026
bc65968
test(ndjson): skip stdin-streaming timing test on CI
hakimjonas May 23, 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
142 changes: 142 additions & 0 deletions .claude/skills/lambe/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
---
name: lambe
description: Query, filter, transform, validate, and convert structured data files (JSON, YAML, TOML, HCL/Terraform, CSV, TSV, Markdown) using the `lam` CLI. Use when the user asks to extract a field, filter records, aggregate values, check structure, validate against a schema, or convert between formats. Works on config files, API responses, deployment manifests, data exports, and Markdown documents (parsed as a typed AST). Bounded — no recursion, no `def`, no regex; for those the user should reach for a real programming language.
license: MIT
metadata:
homepage: https://pub.dev/packages/lambe
repository: https://github.com/hakimjonas/lambe
---

# Lambé (`lam`) — structured data queries

Lambé is on the user's PATH after `dart pub global activate lambe`. You
invoke it via shell. The binary is named `lam`.

## When to reach for `lam`

The user wants to do something with a **structured data file**:
- "Get the X field from this JSON"
- "Filter the Y where Z"
- "Sum / count / list / sort the items"
- "What's the structure of this file?"
- "Check that the deployment has at least 2 replicas"
- "Convert this YAML to TOML"
- "List all the headings in this README"

Don't reach for `lam` when:
- The data is binary, in a database, or a stream.
- The user explicitly asked for jq syntax (use jq).
- The query needs recursion, `try`/`catch`, regex, or accumulating state — write code instead.

## Core moves

```bash
# Extract — single value or path
lam '.database.host' config.toml

# Filter + project
lam '.users | filter(.age > 30) | map(.name)' data.json

# Aggregate
lam '.items | map(.price) | sum' data.json

# Inspect structure (returns JSON Schema)
lam --print-shape data.json

# Static query trace (no execution; surfaces shape per stage + warnings)
lam --explain '.config | flatten | as(toml)' data.json

# CI assertion (exit 0 on true, 1 on false)
lam --assert '.replicas >= 2' deployment.yaml

# Convert format
lam --to yaml '.config' data.json

# Run without input (literal-only queries)
lam -n '[1, 2, 2, 3] | unique'

# Markdown headings (use `text` op, not `.children[0].text`)
lam '.children | filter(.type == "heading") | map(text)' README.md
```

## Syntax in 30 seconds

**Property access**: `.field`, `.users[0]`, `.users[-1]`, `.tags[1:3]`,
`.["x-axis"]` (bracket form for non-identifier keys).

**Pipeline ops** chained with `|`:
`filter(p)`, `map(e)`, `sort`, `sort_by(k)`, `group_by(k)`, `unique`,
`unique_by(k)`, `flatten`, `reverse`, `length`, `first`, `last`,
`sum`, `avg`, `min`, `max`, `keys`, `values`, `has("k")`,
`to_entries`, `from_entries`, `to_number`, `type`,
`filter_values(p)`, `map_values(e)`, `filter_keys(p)`, `text` (markdown),
`as(fmt)` (cross-format bridge).

**Expressions**: arithmetic `+ - * / %`, comparison `< > <= >= == !=`,
boolean `&& || !`, null fallback `//`, conditional `if c then a else b`,
object construction `{name, total: .price * .qty}`, string interpolation
`"\(.name) is \(.age)"`, list literal `[1, 2, 3]`.

**Boolean keywords**: lambé's logic operators are `&&` `||` `!`. Don't
write `and` `or` `not` — the parser will tell you, but save the round trip.

## Markdown data model

Markdown parses to a CommonMark AST. Root is `{type: "document", children: [...]}`.
Every node has a `type`. Container nodes have `children`; leaves carry
content directly.

Common queries:

```bash
# Heading texts (use `text` op for prose extraction)
lam '.children | filter(.type == "heading") | map(text)' doc.md

# Headings with levels
lam '.children | filter(.type == "heading") | map({level, title: text})' doc.md

# Code blocks by language
lam '.children | filter(.type == "code_block") | map({language, code})' doc.md

# Whole document as plain text
lam '. | text' doc.md
```

The `text` op walks any node tree and concatenates prose recursively
(text + code + code_block + image alt). Use it instead of
`.children[0].text` — that pattern only sees the first immediate child
and misses nested emphasis, links, and inline code.

## Common gotchas

- **Output is pretty-printed JSON by default.** Pass `--no-pretty` for
compact output, or `-r` for raw top-level strings (no quotes).
- **Lambé's null contract**: navigation returns null (`.missing` is null,
doesn't throw); computation throws (`.missing + 5` errors). Use
`.field == null` to test, or `.field // default` to substitute.
- **Empty-list policy**: `first`/`last` return null on empty;
`min`/`max`/`avg` throw; `sum` returns 0.
- **Heterogeneous lists** widen to `any` in shape inference. Real-world
markdown children, mixed JSON arrays. The shape system is honest about
this; `--print-shape` shows the widening.
- **Output format errors give actionable hints.** If `lam --to toml ...`
rejects the shape, the error names the `as(...)` bridge to apply.
- **`--explain` is your friend** when a query is unexpectedly empty or
errors. It prints the shape at every stage statically, plus warnings
for runtime-rejected ops and provably-empty filters.

## What lambé deliberately doesn't do

`..` recursive descent, `def` user functions, `try`/`catch`, regex,
`getpath`/`setpath`, in-place mutation, streaming. If you draft a
query needing any of these, lambé will tell you with an "unknown
pipe op" error or a `_jqIdiomHint`. The repo's `doc/non-goals.md`
documents the lambé idiom that replaces each omission.

## When you hit something this skill doesn't cover

The repo's `AGENTS.md` has the broader reference (more examples, full
pipeline op list, error pattern table, format auto-detect rules).
`doc/syntax.md` is the language reference. `doc/recipes.md` has
end-to-end examples. The MCP server `lam-mcp` is available for
sandboxed agents that can't shell out.
17 changes: 17 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ jobs:
steps:
- uses: actions/checkout@v6
- uses: dart-lang/setup-dart@v1.7.2
with:
sdk: 3.12.0
- run: dart pub get
- run: cd lambe_test && dart pub get && cd ..
- run: dart analyze --fatal-infos
Expand All @@ -21,6 +23,8 @@ jobs:
steps:
- uses: actions/checkout@v6
- uses: dart-lang/setup-dart@v1.7.2
with:
sdk: 3.12.0
- run: dart pub get
- run: dart format --set-exit-if-changed .

Expand All @@ -29,6 +33,19 @@ jobs:
steps:
- uses: actions/checkout@v6
- uses: dart-lang/setup-dart@v1.7.2
with:
sdk: 3.12.0
- run: dart pub get
- run: dart test
- run: cd lambe_test && dart pub get && dart test

lint-changelog:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: dart-lang/setup-dart@v1.7.2
with:
sdk: 3.12.0
- run: dart pub get
- run: dart compile exe bin/lam.dart -o lam
- run: ./tool/lint_changelog.sh
12 changes: 11 additions & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ jobs:
steps:
- uses: actions/checkout@v6
- uses: dart-lang/setup-dart@v1.7.2
with:
sdk: 3.12.0

- run: dart pub get
- run: dart run tool/gen_version.dart
Expand Down Expand Up @@ -56,6 +58,14 @@ jobs:
path: artifacts
merge-multiple: true

- name: Generate checksums.txt
run: |
cd artifacts
# One SHA256 per line, matching sha256sum / shasum -a 256 format.
# install.sh reads this to verify downloaded binaries.
sha256sum lam-* > checksums.txt
cat checksums.txt

- uses: softprops/action-gh-release@v3
with:
files: artifacts/*
Expand Down Expand Up @@ -88,7 +98,7 @@ jobs:
"\$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
"name": "io.github.hakimjonas/lambe",
"title": "Lambe",
"description": "Query JSON, YAML, TOML, HCL, CSV, TSV, and Markdown with a composable pipeline syntax.",
"description": "A query language for structured data that shows you what you're working with. Shape-aware --explain, JSON Schema input, format bridges.",
"repository": {
"url": "https://github.com/hakimjonas/lambe.git",
"source": "github"
Expand Down
14 changes: 14 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,21 @@ doc/api/
*.iml
.vscode/

# Claude Code local state (settings, session caches). The
# .claude/skills/ subdirectory is the exception — it ships an Agent
# Skills package to GitHub for AI coding agents working on lambé and
# is tracked deliberately. .pubignore separately excludes the whole
# .claude/ tree from the pub.dev publish payload.
# `.claude/*` (not `.claude/`) so the negation can re-include
# subdirectories — git won't descend into a directory ignored by name.
.claude/*
!.claude/skills/

.DS_Store
Thumbs.db

# Compiled binaries
lam
lam-mcp

# Local dependency overrides
Expand All @@ -26,3 +37,6 @@ bench-results-*.json

# Session handover notes (internal workflow, not code)
HANDOVER_*.md

# Local scratch notes (release planning, status snapshots, etc.)
*.scratch.md
41 changes: 38 additions & 3 deletions .pubignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,43 @@ HANDOVER_*.md
bench-results-*.json
tool/bench/
doc/api/
# Stale local AOT build; per-platform binaries are published via CI

# Stale local AOT builds; per-platform binaries are published via CI
# to the GitHub release and consumed by the MCP registry. Pub clients
# rebuild from source on `dart pub global activate`, so shipping this
# is dead weight.
# rebuild from source on `dart pub global activate`, so shipping these
# is dead weight (and locks pub.dev consumers to a Linux x86_64 binary
# that's useless on other platforms).
lam
lam-mcp

# Local scratch notes (release planning, status snapshots, etc.).
# Same intent as the matching `.gitignore` rule, repeated here because
# `.pubignore` does not inherit from `.gitignore`.
*.scratch.md

# Claude Code session state and skill bundles. The .claude/skills/
# subdirectory ships an Agent Skills package for AI coding agents
# working in a clone of this repo; pub.dev consumers (Dart developers)
# don't load skills, so it would just be noise on the package page.
.claude/

# Cross-vendor Agent Skills path (recognised by Gemini CLI and others
# as the interoperable alias for .claude/skills/). Same exclusion logic.
.agents/

# Agent-facing instruction docs. Read by Cursor, Copilot, Claude Code,
# Gemini CLI, Devin, and other agents inspecting a cloned repo.
# pub.dev consumers (Dart developers) don't need them.
AGENTS.md

# Local pubspec overrides.
pubspec_overrides.yaml

# MCP registry tokens.
.mcpregistry_*

# MCP registry manifest template — regenerated by .github/workflows/release.yml
# at release time and consumed by the MCP registry publish flow. Pub.dev
# consumers never use it, and the placeholder version in the file would be
# misleading if shipped.
server.json
Loading