From e880bfe898c0c2ff605662bbf74d71ab3497c97d Mon Sep 17 00:00:00 2001 From: Adrian Elton-Browning Date: Tue, 6 Oct 2026 16:40:02 +0100 Subject: [PATCH 1/4] fix(cli): --meta takes one key=value per flag; fix CLI examples that pass directories, several files or globs (#54) --- .bumpy/meta-flag.md | 5 + .intent/review-state.json | 78 ++++++++++--- _artifacts/domain_map.yaml | 4 +- _artifacts/skill_spec.md | 10 +- examples/CLI_EXAMPLES.md | 111 ++++++++++--------- packages/mdcode/README.md | 95 +++++++++------- packages/mdcode/src/cli.ts | 28 ++--- packages/mdcode/src/types.ts | 5 +- packages/usage/tests/cli-integration.test.ts | 33 ++++++ 9 files changed, 242 insertions(+), 127 deletions(-) create mode 100644 .bumpy/meta-flag.md diff --git a/.bumpy/meta-flag.md b/.bumpy/meta-flag.md new file mode 100644 index 0000000..7643cfd --- /dev/null +++ b/.bumpy/meta-flag.md @@ -0,0 +1,5 @@ +--- +mdcode-ts: patch +--- + +Fixed `--meta` swallowing the Markdown file after it. `mdcode list --meta type=example README.md` used to read `README.md` as a second `key=value`, read stdin instead and report no blocks. `--meta` now takes one pair per flag; repeat it to require several (`-m type=example -m region=main`). A value containing `=` is now kept whole. The README and CLI examples no longer pass directories or several files to `list`, `run` and `dump`, or globs to `--file`, which matches `file=` exactly. diff --git a/.intent/review-state.json b/.intent/review-state.json index 7772fcf..fc5cf66 100644 --- a/.intent/review-state.json +++ b/.intent/review-state.json @@ -3,15 +3,15 @@ "baseline": "5254174a2a13060d110f34dbc0a4421b5302f5cb", "items": { "skill:packages/mdcode/skills/sync-markdown-code-blocks/SKILL.md": { - "fingerprint": "beaa8202e375f5130b2c3ef22daf8b69906fffc982b49001a3a9a5985ab0f9b6", + "fingerprint": "db81fcd519169f99174098c758528ce372b433bae5574fe7c5f57128118dea2a", "snapshot": { "examples/ci/check-docs-sync.mjs": "7a1dc943571637883dfc28de98825c3c595d6c5a5561239373bacf1dfbf1dc72", "examples/ci/validate-snippets.mjs": "d177fba60f830179c285a25ea0a878d98dc044684114447f687366907e9e722d", - "packages/mdcode/README.md": "7e629127cc7300619ae4017572f07670cdb57120f0f150abd9ec9009346e55a2", + "packages/mdcode/README.md": "c657f3b7dd7cb7fe5ddd6efd014dea85b592a564d52dd82f59f7b618bbb93df7", "packages/mdcode/skills/sync-markdown-code-blocks/SKILL.md": "bfbfe9fb88fb1bb1ef18eb55338632d2ac6408d023de7c05c60dc307c8626033", "packages/mdcode/skills/sync-markdown-code-blocks/references/json-results.md": "ccbcb240d796eccc02c6f1974573df1f8a9052ea37808b78a15420f9e66f1f63", "packages/mdcode/skills/sync-markdown-code-blocks/references/transformers.md": "b35f5caf45b6334d21e534facb2d22a76c52f576d1640e3a5732fd1d6119cf13", - "packages/mdcode/src/cli.ts": "ac83b806571195eaf0298f69a2be6de503e676803c656b7dce33a7da8146819a", + "packages/mdcode/src/cli.ts": "bbad86849efb1e2818562c4fb1f790d8fbd283ee45eec9b3bcb3e63570442f13", "packages/mdcode/src/commands/dump.ts": "c51ca44f0159c59b849c5cb725e2de36ca5ef0578356bc9f921330ce8677d346", "packages/mdcode/src/commands/extract.ts": "8a1897da665e95e20a6e645565632cf4aff17c2c5cdc4f4fae3f904bdd3c074d", "packages/mdcode/src/commands/list.ts": "d919894dd08249bb1eb0d12bcd5c21016cb87c0a6cba484de7da492bbe3c5c75", @@ -32,26 +32,26 @@ "packages/usage/tests/skills/sync-markdown-code-blocks/fixture/src/greet.ts": "a2938e7cf0fa1f29393bd2ac1cb3e1f7c8a6fe0ff4c9ea8b1c47becffd4fa168", "packages/usage/tests/skills/sync-markdown-code-blocks/task.md": "69f373db5a34c8165e1abe370e63230a6652c1412f2fa44a413dfd8534d84dbc" }, - "head": "5254174a2a13060d110f34dbc0a4421b5302f5cb", - "outcome": "updated", - "reason": "Source mappings extended to the examples; SKILL.md frontmatter changed, body guidance verified unchanged against them.", + "head": "1d5875a70d51efab8c48ee12071e0d3d68fa66da", + "outcome": "no-change", + "reason": "The skill already states list/run/dump read one file and --file matches exactly; its `mdcode list --meta runnable=true README.md` example was broken by the variadic --meta and now works as written. README.md source changes fix examples to agree with the skill.", "evidence": [ - "Added source mappings for the worked examples the skill's region, outline and CI guidance rests on: tests/examples/fibonacci (outline=true plus region blocks, fibonacci.js), tests/examples/factorial (region in a test file), examples/ci/check-docs-sync.mjs (named in references/json-results.md for telling drift from breakage) and examples/ci/validate-snippets.mjs (runnable=true snippets, the adjacent task the description excludes). Guidance unchanged: `mdcode update --check` passes on both example READMEs and pnpm docs:check covers them; the skill's statements about check-docs-sync.mjs exit codes (0/1/2) match the script header. intent validate packages/mdcode/skills: passed (1 skill). Task check packages/usage/tests/skill-sync-markdown-code-blocks.test.ts: accepts `update --apply --project`; rejects plan-only, applying without the configuration's sourceRoot, and changing the source to match the docs (4/4 pass). CLI claims verified by running packages/mdcode/dist/main.js built from this revision (update modes and exit codes, --check drift, region_language_mismatch, --base and unsafe_path for ../, extract splice/skip exit 2, run without --allow-shell refused, transform, outline, --json envelope, exact-match --file). Packed mdcode-ts-0.0.4.tgz contains skills/sync-markdown-code-blocks/{SKILL.md,references/*} and no _artifacts. Consumer run with intent install + intent load passed the unchanged grader, but was not isolated (it also read this repo's agent instructions); discovery unverified." + "cli-integration.test.ts '--meta takes one key=value, so a file after it is still the file to read' failed on the variadic build and passes after; `mdcode list --json --meta runnable=true README.md` now returns the runnable block (it returned none before); a scan of packages/mdcode/README.md and examples/CLI_EXAMPLES.md finds no list/run/dump with several files or a directory and no glob --file value; skill task check still passes." ] }, "planning:_artifacts": { - "fingerprint": "0abac9a56351477cb11e01a5951b50ade6c4755250368e8459c126d8b797cfbf", + "fingerprint": "a9e25508205d66595ed0beaf9a482047bfcfaec4c44c1bca17a4c8143bd39057", "snapshot": { - "_artifacts/domain_map.yaml": "cb60c619ea31f10641d88e63124e086ffdd74541f84f30dc7395a39eac215aa0", - "_artifacts/skill_spec.md": "5b6317e7ca22eab9413b000a41acbb7ca5061f672a919aecbcf95384c5991c2d", + "_artifacts/domain_map.yaml": "47b4f2603fd08462d894e310bc9f49d169af9f27e958a5528ea74f0cad2828cf", + "_artifacts/skill_spec.md": "6924a904a1406221d435ee89535804f767bfbaf46dd3394001fa4ad7ebbc3281", "_artifacts/skill_tree.yaml": "2490b6a1d7a2abab698fe1a91e036ddec9dc76de5cfb15dedd8639ebf2875229", "examples/ci/check-docs-sync.mjs": "7a1dc943571637883dfc28de98825c3c595d6c5a5561239373bacf1dfbf1dc72", "examples/ci/validate-snippets.mjs": "d177fba60f830179c285a25ea0a878d98dc044684114447f687366907e9e722d", - "packages/mdcode/README.md": "7e629127cc7300619ae4017572f07670cdb57120f0f150abd9ec9009346e55a2", + "packages/mdcode/README.md": "c657f3b7dd7cb7fe5ddd6efd014dea85b592a564d52dd82f59f7b618bbb93df7", "packages/mdcode/skills/sync-markdown-code-blocks/SKILL.md": "bfbfe9fb88fb1bb1ef18eb55338632d2ac6408d023de7c05c60dc307c8626033", "packages/mdcode/skills/sync-markdown-code-blocks/references/json-results.md": "ccbcb240d796eccc02c6f1974573df1f8a9052ea37808b78a15420f9e66f1f63", "packages/mdcode/skills/sync-markdown-code-blocks/references/transformers.md": "b35f5caf45b6334d21e534facb2d22a76c52f576d1640e3a5732fd1d6119cf13", - "packages/mdcode/src/cli.ts": "ac83b806571195eaf0298f69a2be6de503e676803c656b7dce33a7da8146819a", + "packages/mdcode/src/cli.ts": "bbad86849efb1e2818562c4fb1f790d8fbd283ee45eec9b3bcb3e63570442f13", "packages/mdcode/src/commands/dump.ts": "c51ca44f0159c59b849c5cb725e2de36ca5ef0578356bc9f921330ce8677d346", "packages/mdcode/src/commands/extract.ts": "8a1897da665e95e20a6e645565632cf4aff17c2c5cdc4f4fae3f904bdd3c074d", "packages/mdcode/src/commands/list.ts": "d919894dd08249bb1eb0d12bcd5c21016cb87c0a6cba484de7da492bbe3c5c75", @@ -72,11 +72,11 @@ "packages/usage/tests/skills/sync-markdown-code-blocks/fixture/src/greet.ts": "a2938e7cf0fa1f29393bd2ac1cb3e1f7c8a6fe0ff4c9ea8b1c47becffd4fa168", "packages/usage/tests/skills/sync-markdown-code-blocks/task.md": "69f373db5a34c8165e1abe370e63230a6652c1412f2fa44a413dfd8534d84dbc" }, - "head": "39d66fe7b8410862fb87ecc0ceacfdde180f1176", + "head": "1d5875a70d51efab8c48ee12071e0d3d68fa66da", "outcome": "updated", - "reason": "skill_spec.md consumer-session entry corrected: the agent was not given only the task text, and the consumer setup used intent.skills [\"*\"] rather than a least-privilege selection. domain_map.yaml and skill_tree.yaml unchanged and accurate.", + "reason": "domain_map.yaml gap for the README/CLI mismatch marked resolved; skill_spec.md gains batch 2 for #54 and the Remaining Gaps row is resolved. skill_tree.yaml unchanged.", "evidence": [ - "One-line edit to skill_spec.md Checks; intent maintainer sync reports nothing to synchronize. Consumer evidence remains marked non-isolated; least-privilege install and discovery unverified." + "cli-integration.test.ts '--meta takes one key=value, so a file after it is still the file to read' failed on the variadic build and passes after; `mdcode list --json --meta runnable=true README.md` now returns the runnable block (it returned none before); a scan of packages/mdcode/README.md and examples/CLI_EXAMPLES.md finds no list/run/dump with several files or a directory and no glob --file value; skill task check still passes." ] }, "source:.bumpy/agent-skill.md": { @@ -102,6 +102,54 @@ "evidence": [ "Diff adds one bullet describing skill-sync-markdown-code-blocks.test.ts and SKILL_TASK_DIR." ] + }, + "source:.bumpy/meta-flag.md": { + "fingerprint": "fe9000fcd07453a546f36af2c96aad7ca268a37f8d3bb8235022b61b0514084d", + "snapshot": { + ".bumpy/meta-flag.md": "988ae101f024fcce1ca393cf5682499fab1a7e3111e0163aa3fbb68fd5d84dcc" + }, + "head": "1d5875a70d51efab8c48ee12071e0d3d68fa66da", + "outcome": "out-of-scope", + "reason": "Changelog entry for the --meta fix and the docs corrections.", + "evidence": [ + "Patch bump file describing the user-visible change." + ] + }, + "source:examples/CLI_EXAMPLES.md": { + "fingerprint": "eb007d8ec6029a55f5007980a18fc545b427467544d62833740034d498a160df", + "snapshot": { + "examples/CLI_EXAMPLES.md": "2a6329468cc318d2c03fce0daa08e48f5d2d0e11784830416287243209470a08" + }, + "head": "1d5875a70d51efab8c48ee12071e0d3d68fa66da", + "outcome": "no-change", + "reason": "Examples corrected to the CLI's real arity and exact --file matching; the skill already described that behaviour and does not cite this file.", + "evidence": [ + "cli-integration.test.ts '--meta takes one key=value, so a file after it is still the file to read' failed on the variadic build and passes after; `mdcode list --json --meta runnable=true README.md` now returns the runnable block (it returned none before); a scan of packages/mdcode/README.md and examples/CLI_EXAMPLES.md finds no list/run/dump with several files or a directory and no glob --file value; skill task check still passes." + ] + }, + "source:packages/mdcode/src/types.ts": { + "fingerprint": "fc145c1d287bec875b7607a84a628b90e0ba152fa9907c5b27202ee7ceca9afb", + "snapshot": { + "packages/mdcode/src/types.ts": "414b5e0db31b144d8f6d3fcda7cd5d9c2499285be6bc1dabadf7f97843e053f6" + }, + "head": "1d5875a70d51efab8c48ee12071e0d3d68fa66da", + "outcome": "no-change", + "reason": "JSDoc for TransformerMeta corrected: file and region are the block's metadata and match exactly as filters. The skill's transformer reference already says meta holds only file and region.", + "evidence": [ + "Comment-only change; lint:ts passes." + ] + }, + "source:packages/usage/tests/cli-integration.test.ts": { + "fingerprint": "0fc685f78cefa4dc0ae3597176c941aeb5cc89f4b7a33b51752566f0027f6305", + "snapshot": { + "packages/usage/tests/cli-integration.test.ts": "d95b6f11091013d9b21683b124670ebbc5b5c206fe94c3faae5dba847f4a8abd" + }, + "head": "1d5875a70d51efab8c48ee12071e0d3d68fa66da", + "outcome": "no-change", + "reason": "New regression test for --meta; it pins behaviour the skill relies on but changes no guidance.", + "evidence": [ + "cli-integration.test.ts '--meta takes one key=value, so a file after it is still the file to read' failed on the variadic build and passes after; `mdcode list --json --meta runnable=true README.md` now returns the runnable block (it returned none before); a scan of packages/mdcode/README.md and examples/CLI_EXAMPLES.md finds no list/run/dump with several files or a directory and no glob --file value; skill task check still passes." + ] } } } diff --git a/_artifacts/domain_map.yaml b/_artifacts/domain_map.yaml index e9300d3..4722304 100644 --- a/_artifacts/domain_map.yaml +++ b/_artifacts/domain_map.yaml @@ -127,8 +127,8 @@ cross_references: [] gaps: - skill: sync-markdown-code-blocks question: Package README and examples/CLI_EXAMPLES.md show globs, directories and --file patterns the CLI rejects - context: The skill states the real behaviour, verified against the CLI; the docs are tracked in issue #54 - status: open + context: Fixed in issue #54. The examples now pass one file to list, run and dump and an exact --file value, and --meta takes one pair per flag so a file after it is read + status: resolved - skill: sync-markdown-code-blocks question: Fresh-consumer session and discovery checks context: Recorded in skill_spec.md with what was and was not verified diff --git a/_artifacts/skill_spec.md b/_artifacts/skill_spec.md index df37df2..e19b48b 100644 --- a/_artifacts/skill_spec.md +++ b/_artifacts/skill_spec.md @@ -42,7 +42,7 @@ ships the `mdcode` CLI and a library API from the workspace package `packages/md | Skill | Question | Status | | --- | --- | --- | -| sync-markdown-code-blocks | The package README and examples/CLI_EXAMPLES.md show globs, directories and --file patterns the CLI rejects (issue #54). The skill states the verified behaviour; README.md stays a source for the parts that are accurate. | open | +| sync-markdown-code-blocks | The package README and examples/CLI_EXAMPLES.md showed globs, directories and --file patterns the CLI rejects. Fixed in #54. | resolved | | sync-markdown-code-blocks | Discovery (whether agents load the skill from its description for unrelated phrasings) is unverified. | open | ## Coverage and batch history @@ -61,4 +61,10 @@ ships the `mdcode` CLI and a library API from the workspace package `packages/md - Task check `packages/usage/tests/skill-sync-markdown-code-blocks.test.ts` (fixture in `packages/usage/tests/skills/sync-markdown-code-blocks/`). It accepts `update --apply --project`. It rejects plan-only, applying without the configuration's `sourceRoot`, and changing the source to match the docs. - Consumer session: a separate agent got the task text and was pointed at a disposable project with the packed `mdcode-ts-0.0.4.tgz` and `@tanstack/intent@0.5.4`. The project was set up with `intent install` choosing "Enable all", which writes `intent.skills: ["*"]`; that is broader than a consumer should configure, and a least-privilege selection of just this skill was not tested. The agent loaded `mdcode-ts#sync-markdown-code-blocks` through `intent load`, ran `update --diff --project`, then `update --apply --project`, and the unchanged grader passed on its result. **Not an isolated run:** the session also read this repository's own agent instructions and edited an unrelated file here (reverted), so it is not independent fresh-consumer evidence. - Discovery: not verified. -- **Remaining work:** repeat the consumer run in a sandbox that exposes only the consumer project; run discovery checks; revisit the README sources once #54 lands. +- **Remaining work:** repeat the consumer run in a sandbox that exposes only the consumer project; run discovery checks. + +### Batch 2 — 2026-10-06, mdcode-ts 0.0.4 (issue #54) + +- **Change:** `--meta` is now repeatable, one `key=value` per flag, instead of variadic. Before, it took every following argument, so `mdcode list --meta runnable=true README.md` read stdin and found nothing. That included the skill's own `--meta` example. A value may now contain `=`. The package README and `examples/CLI_EXAMPLES.md` now pass one file to `list`, `run` and `dump` and an exact `--file` value. The flags reference no longer calls `--file` a pattern. +- **Guidance:** no change. The skill already said `list`, `run` and `dump` read one file and that `--file` is exact, and its `--meta` example is now correct as written. README.md remains a source; the parts that disagreed with the CLI are fixed. +- **Checks:** `cli-integration.test.ts` adds "--meta takes one key=value, so a file after it is still the file to read". It failed before the change and passes after. The skill task check still passes. diff --git a/examples/CLI_EXAMPLES.md b/examples/CLI_EXAMPLES.md index 9874906..bca253b 100644 --- a/examples/CLI_EXAMPLES.md +++ b/examples/CLI_EXAMPLES.md @@ -180,7 +180,7 @@ documents the full contract. ```bash # Long form mdcode list --lang js README.md -mdcode list --lang python docs/*.md +mdcode list --lang python docs/guide.md # Short form mdcode list -l js README.md @@ -189,14 +189,16 @@ mdcode list -l sql API.md ### Filter by File Metadata +`--file` selects blocks whose `file=` is exactly the value given; it is not a glob. + ```bash # Long form mdcode list --file app.js README.md -mdcode list --file "*.test.js" docs/ +mdcode list --file app.test.js docs/guide.md # Short form mdcode list -f app.js README.md -mdcode list -f server.py docs/ +mdcode list -f server.py docs/guide.md ``` ### Filter by Custom Metadata @@ -204,7 +206,7 @@ mdcode list -f server.py docs/ ```bash # Long form mdcode list --meta region=main README.md -mdcode list --meta type=example docs/ +mdcode list --meta type=example docs/guide.md # Short form mdcode list -m region=main README.md @@ -222,8 +224,8 @@ mdcode list --lang js --file app.js --meta region=main README.md # Short forms mdcode list -l js -f app.js -m region=main README.md -# Filter JavaScript test files -mdcode list -l js -f "*.test.js" docs/ +# Filter one JavaScript test file +mdcode list -l js -f app.test.js docs/guide.md ``` ### JSON with Filters @@ -233,7 +235,7 @@ mdcode list -l js -f "*.test.js" docs/ mdcode list --json --lang js README.md # JSON with multiple filters -mdcode list --json -l python -m type=example docs/ +mdcode list --json -l python -m type=example docs/guide.md ``` --- @@ -286,7 +288,7 @@ mdcode extract --dir ./extracted docs/API.md # Short form mdcode extract -d output README.md -mdcode extract -d ./build docs/ +mdcode extract -d ./build docs/*.md ``` ### Quiet Mode @@ -313,11 +315,11 @@ mdcode extract -l js -d ./src docs/*.md # Extract specific file mdcode extract --file app.js README.md -mdcode extract -f server.py -d ./src docs/ +mdcode extract -f server.py -d ./src docs/*.md # Extract with metadata filter mdcode extract --meta type=component README.md -mdcode extract -m region=main -d ./lib docs/ +mdcode extract -m region=main -d ./lib docs/*.md ``` ### Combined Examples @@ -447,8 +449,8 @@ Combine transformers with filters to target specific blocks: # Transform only SQL blocks mdcode update --apply --transform ./uppercase.js --lang sql README.md -# Transform only test files -mdcode update --apply -t ./add-headers.js -f "*.test.js" docs/API.md +# Transform only the blocks for one test file +mdcode update --apply -t ./add-headers.js -f app.test.js docs/API.md # Transform JavaScript blocks in examples mdcode update --apply -t ./format.js -l js -m type=example docs/API.md @@ -517,7 +519,7 @@ mdcode run --allow-shell "node {file}" README.md mdcode run --allow-shell "python {file}" --lang python README.md # Compile and run C code -mdcode run --allow-shell "gcc {file} -o out && ./out" --lang c docs/ +mdcode run --allow-shell "gcc {file} -o out && ./out" --lang c docs/guide.md ``` ### Filter by Language @@ -530,8 +532,11 @@ mdcode run --allow-shell --lang js "node {file}" README.md mdcode run --allow-shell -l js "node {file}" README.md # Multiple languages (run separately) -mdcode run --allow-shell -l python "python {file}" docs/*.md -mdcode run --allow-shell -l js "node {file}" docs/*.md +mdcode run --allow-shell -l python "python {file}" docs/guide.md +mdcode run --allow-shell -l js "node {file}" docs/guide.md + +# run reads one Markdown file; loop for several +for doc in docs/*.md; do mdcode run --allow-shell -l js "node {file}" "$doc"; done ``` ### Filter by Name @@ -546,7 +551,7 @@ mdcode run --allow-shell --name test-example "node {file}" README.md mdcode run --allow-shell -n calculate "python {file}" docs/API.md # With language filter -mdcode run --allow-shell -l js -n integration-test "node {file}" tests/ +mdcode run --allow-shell -l js -n integration-test "node {file}" tests/README.md ``` ### Custom Working Directory @@ -558,7 +563,7 @@ Specify where to save temporary files and run commands: mdcode run --allow-shell --dir /tmp/mdcode "node {file}" README.md # Short form -mdcode run --allow-shell -d ./temp "python {file}" docs/ +mdcode run --allow-shell -d ./temp "python {file}" docs/guide.md # With filters mdcode run --allow-shell -l js -d ./build "node {file}" README.md @@ -573,7 +578,7 @@ Preserve temporary directory after execution (useful for debugging): mdcode run --allow-shell --keep "node {file}" README.md # Short form -mdcode run --allow-shell -k "python {file}" docs/ +mdcode run --allow-shell -k "python {file}" docs/guide.md # The command will print the temp directory location ``` @@ -585,13 +590,13 @@ mdcode run --allow-shell -k "python {file}" docs/ mdcode run --allow-shell -l js -n test -k -d ./temp "node {file}" README.md # Run Python examples in custom directory -mdcode run --allow-shell -l python -m type=example -d ./examples "python {file}" docs/ +mdcode run --allow-shell -l python -m type=example -d ./examples "python {file}" docs/guide.md # Run and keep files, filter by file metadata mdcode run --allow-shell -k -f "calculator.py" "python {file}" README.md # Run with multiple filters -mdcode run --allow-shell -l js -f "*.test.js" -n unit "npm test {file}" docs/ +mdcode run --allow-shell -l js -f app.test.js -n unit "npm test {file}" docs/guide.md ``` ### Advanced Usage @@ -601,13 +606,13 @@ mdcode run --allow-shell -l js -f "*.test.js" -n unit "npm test {file}" docs/ mdcode run --allow-shell -l js "eslint {file}" README.md # Format code blocks -mdcode run --allow-shell -l python "black {file}" docs/*.md +mdcode run --allow-shell -l python "black {file}" docs/guide.md # Type check TypeScript blocks mdcode run --allow-shell -l typescript "tsc --noEmit {file}" API.md # Run tests with coverage -mdcode run --allow-shell -l js -n test "jest --coverage {file}" docs/ +mdcode run --allow-shell -l js -n test "jest --coverage {file}" docs/guide.md # Compile and analyze mdcode run --allow-shell -l c "gcc -Wall -Wextra {file} && valgrind ./a.out" examples.md @@ -635,7 +640,7 @@ Create a tar archive of all code blocks. mdcode dump README.md > code-blocks.tar # Pipe to tar command -mdcode dump docs/*.md | tar -x +mdcode dump docs/guide.md | tar -x ``` ### Output to File @@ -658,7 +663,7 @@ mdcode dump -o examples-$(date +%Y%m%d).tar README.md mdcode dump --quiet --out archive.tar README.md # Short form -mdcode dump -q -o archive.tar docs/ +mdcode dump -q -o archive.tar docs/guide.md # Quiet to stdout mdcode dump -q README.md > archive.tar @@ -669,14 +674,14 @@ mdcode dump -q README.md > archive.tar ```bash # Dump only JavaScript files mdcode dump --lang js -o js-blocks.tar README.md -mdcode dump -l js -o javascript.tar docs/*.md +mdcode dump -l js -o javascript.tar docs/guide.md -# Dump specific file patterns -mdcode dump --file "*.py" -o python.tar docs/ +# Dump the blocks for one file= value +mdcode dump --file build.py -o python.tar docs/guide.md mdcode dump -f server.js -o server.tar README.md # Dump by metadata -mdcode dump --meta type=example -o examples.tar docs/ +mdcode dump --meta type=example -o examples.tar docs/guide.md mdcode dump -m region=main -o main.tar API.md ``` @@ -684,7 +689,7 @@ mdcode dump -m region=main -o main.tar API.md ```bash # Dump JavaScript examples quietly -mdcode dump -q -l js -m type=example -o js-examples.tar docs/ +mdcode dump -q -l js -m type=example -o js-examples.tar docs/guide.md # Dump Python files to archive mdcode dump -l python -o python-code.tar README.md @@ -696,7 +701,7 @@ mdcode dump -m region=tests -o tests.tar docs/API.md mdcode dump -l js README.md | tar -xv # Create dated archive with filters -mdcode dump -q -l typescript -o "ts-$(date +%Y%m%d).tar" docs/ +mdcode dump -q -l typescript -o "ts-$(date +%Y%m%d).tar" docs/guide.md ``` ### Extract Tar Archive @@ -733,12 +738,12 @@ mdcode dump -l sql -o queries.tar API.md # By file metadata mdcode list -f app.js README.md -mdcode extract -f "*.test.js" docs/ +mdcode extract -f app.test.js docs/*.md mdcode run --allow-shell -f server.py "python {file}" README.md # By custom metadata mdcode list -m region=main README.md -mdcode extract -m type=example docs/ +mdcode extract -m type=example docs/*.md mdcode update --apply -m author=admin API.md ``` @@ -751,7 +756,7 @@ When you combine filters, ALL filters must match: mdcode list -l js -f app.js README.md # Language AND metadata -mdcode extract -l python -m type=example docs/ +mdcode extract -l python -m type=example docs/*.md # File AND metadata mdcode dump -f server.js -m region=main -o server.tar README.md @@ -760,33 +765,35 @@ mdcode dump -f server.js -m region=main -o server.tar README.md mdcode list -l js -f app.js -m region=main README.md ``` -### Wildcards and Patterns +### No Wildcards + +Filters match exactly: `-f "*.test.js"` selects only a block whose `file=` is literally `*.test.js`, +and `-m "region=*"` only one whose `region=` is `*`. To select a group of blocks, give them a shared +metadata value and filter on that. To cover several documents with `list`, `run` or `dump`, which read +one file each, loop in the shell: ```bash -# File patterns -mdcode extract -f "*.test.js" docs/ -mdcode list -f "server.*" README.md -mdcode dump -f "**/*.py" -o python.tar docs/ +# Mark the blocks with kind=test in the Markdown, then select them together +mdcode extract -m kind=test -d ./tests docs/*.md -# Metadata patterns (exact match) -mdcode list -m "region=*" README.md -mdcode extract -m type=component docs/ +# One document at a time +for doc in docs/*.md; do mdcode list -m kind=test "$doc"; done ``` ### Complex Filtering Scenarios ```bash -# Extract all test files that are JavaScript -mdcode extract -l js -f "*.test.js" -d ./tests docs/ +# Extract one JavaScript test file +mdcode extract -l js -f app.test.js -d ./tests docs/*.md # List Python examples in main region -mdcode list -l python -m type=example -m region=main docs/ +mdcode list -l python -m type=example -m region=main docs/guide.md # Run tests only for specific component mdcode run --allow-shell -l js -f "auth.test.js" -n "login-test" "node {file}" README.md -# Dump production code (exclude tests) -mdcode dump -f "src/**/*.js" -o production.tar docs/ +# Dump the blocks for one source file +mdcode dump -f src/index.js -o index.tar docs/guide.md # Update only SQL queries in specific file mdcode update -l sql -f queries.sql --stdout README.md @@ -978,14 +985,14 @@ mdcode update --apply README.md ### Workflow: Test All Code Blocks ```bash -# Extract test files -mdcode extract -l js -f "*.test.js" -d ./tests docs/ +# Extract the blocks marked kind=test +mdcode extract -l js -m kind=test -d ./tests docs/guide.md -# Run all tests -mdcode run --allow-shell -l js -f "*.test.js" "npm test {file}" docs/ +# Run each of them +mdcode run --allow-shell -l js -m kind=test "node --test {file}" docs/guide.md # If tests pass, create archive -mdcode dump -l js -f "*.test.js" -o tests.tar docs/ +mdcode dump -l js -m kind=test -o tests.tar docs/guide.md ``` ### Workflow: Transform and Publish @@ -1061,7 +1068,7 @@ mdcode extract -l js --force -d temp README.md && \ mdcode update --apply README.md # Check for syntax errors -mdcode run --allow-shell -l python "python -m py_compile {file}" docs/*.md +for doc in docs/*.md; do mdcode run --allow-shell -l python "python -m py_compile {file}" "$doc"; done ``` ### 3. Using Stdin Effectively diff --git a/packages/mdcode/README.md b/packages/mdcode/README.md index 676e985..02255df 100644 --- a/packages/mdcode/README.md +++ b/packages/mdcode/README.md @@ -286,7 +286,7 @@ In the text output, a named block is listed by its name, as in `[1] quick start ```bash # Long form mdcode list --lang js README.md -mdcode list --lang python docs/*.md +mdcode list --lang python docs/guide.md # Short form mdcode list -l js README.md @@ -295,14 +295,16 @@ mdcode list -l sql API.md ### Filter by File Metadata +`--file` selects blocks whose `file=` is exactly the value given; it is not a glob. + ```bash # Long form mdcode list --file app.js README.md -mdcode list --file "*.test.js" docs/ +mdcode list --file app.test.js docs/guide.md # Short form mdcode list -f app.js README.md -mdcode list -f server.py docs/ +mdcode list -f server.py docs/guide.md ``` ### Filter by Custom Metadata @@ -310,7 +312,7 @@ mdcode list -f server.py docs/ ```bash # Long form mdcode list --meta region=main README.md -mdcode list --meta type=example docs/ +mdcode list --meta type=example docs/guide.md # Short form mdcode list -m region=main README.md @@ -328,8 +330,8 @@ mdcode list --lang js --file app.js --meta region=main README.md # Short forms mdcode list -l js -f app.js -m region=main README.md -# Filter JavaScript test files -mdcode list -l js -f "*.test.js" docs/ +# Filter one JavaScript test file +mdcode list -l js -f app.test.js docs/guide.md ``` --- @@ -402,7 +404,7 @@ mdcode extract --dir ./extracted docs/API.md # Short form mdcode extract -d output README.md -mdcode extract -d ./build docs/ +mdcode extract -d ./build docs/*.md ``` ### Quiet Mode @@ -429,11 +431,11 @@ mdcode extract -l js -d ./src docs/*.md # Extract specific file mdcode extract --file app.js README.md -mdcode extract -f server.py -d ./src docs/ +mdcode extract -f server.py -d ./src docs/*.md # Extract with metadata filter mdcode extract --meta type=component README.md -mdcode extract -m region=main -d ./lib docs/ +mdcode extract -m region=main -d ./lib docs/*.md ``` ### Combined Examples @@ -736,8 +738,8 @@ Combine transformers with filters to target specific blocks: # Transform only SQL blocks mdcode update --apply --transform ./uppercase.js --lang sql README.md -# Transform only test files -mdcode update --apply -t ./add-headers.js -f "*.test.js" docs/API.md +# Transform only the blocks for one test file +mdcode update --apply -t ./add-headers.js -f app.test.js docs/API.md # Transform JavaScript blocks in examples mdcode update --apply -t ./format.js -l js -m type=example docs/API.md @@ -872,7 +874,7 @@ mdcode run --allow-shell "node {file}" --lang javascript README.md mdcode run --allow-shell "python {file}" --lang python README.md # Compile and run C code -mdcode run --allow-shell "gcc {file} -o out && ./out" --lang c docs/ +mdcode run --allow-shell "gcc {file} -o out && ./out" --lang c docs/guide.md ``` ### Filter by Language @@ -885,8 +887,11 @@ mdcode run --allow-shell --lang js "node {file}" README.md mdcode run --allow-shell -l js "node {file}" README.md # Multiple languages (run separately) -mdcode run --allow-shell -l python "python {file}" docs/*.md -mdcode run --allow-shell -l js "node {file}" docs/*.md +mdcode run --allow-shell -l python "python {file}" docs/guide.md +mdcode run --allow-shell -l js "node {file}" docs/guide.md + +# run reads one Markdown file; loop for several +for doc in docs/*.md; do mdcode run --allow-shell -l js "node {file}" "$doc"; done ``` ### Filter by Name @@ -902,7 +907,7 @@ mdcode run --allow-shell --name test-example "node {file}" README.md mdcode run --allow-shell -n calculate "python {file}" docs/API.md # With language filter -mdcode run --allow-shell -l js -n integration-test "node {file}" tests/ +mdcode run --allow-shell -l js -n integration-test "node {file}" tests/README.md ``` ### Custom Working Directory @@ -914,7 +919,7 @@ Specify where to save temporary files and run commands: mdcode run --allow-shell --dir /tmp/mdcode "node {file}" README.md # Short form -mdcode run --allow-shell -d ./temp "python {file}" docs/ +mdcode run --allow-shell -d ./temp "python {file}" docs/guide.md # With filters mdcode run --allow-shell -l js -d ./build "node {file}" README.md @@ -929,7 +934,7 @@ Preserve temporary directory after execution (useful for debugging): mdcode run --allow-shell --keep "node {file}" README.md # Short form -mdcode run --allow-shell -k "python {file}" docs/ +mdcode run --allow-shell -k "python {file}" docs/guide.md # After the blocks, the command prints "Working directory: " ``` @@ -941,7 +946,7 @@ mdcode run --allow-shell -k "python {file}" docs/ mdcode run --allow-shell -l js -n test -k -d ./temp "node {file}" README.md # Run Python examples in custom directory -mdcode run --allow-shell -l python -m type=example -d ./examples "python {file}" docs/ +mdcode run --allow-shell -l python -m type=example -d ./examples "python {file}" docs/guide.md # Run and keep files, filter by file metadata mdcode run --allow-shell -k -f "calculator.py" "python {file}" README.md @@ -954,13 +959,13 @@ mdcode run --allow-shell -k -f "calculator.py" "python {file}" README.md mdcode run --allow-shell -l js "eslint {file}" README.md # Format code blocks -mdcode run --allow-shell -l python "black {file}" docs/*.md +mdcode run --allow-shell -l python "black {file}" docs/guide.md # Type check TypeScript blocks mdcode run --allow-shell -l typescript "tsc --noEmit {file}" API.md # Run tests with coverage -mdcode run --allow-shell -l js -n test "jest --coverage {file}" docs/ +mdcode run --allow-shell -l js -n test "jest --coverage {file}" docs/guide.md ``` ### Validating Runnable Snippets in CI @@ -1085,7 +1090,7 @@ produced. mdcode dump README.md > code-blocks.tar # Pipe to tar command -mdcode dump docs/*.md | tar -x +mdcode dump docs/guide.md | tar -x ``` ### Output to File @@ -1108,7 +1113,7 @@ mdcode dump -o examples-$(date +%Y%m%d).tar README.md mdcode dump --quiet --out archive.tar README.md # Short form -mdcode dump -q -o archive.tar docs/ +mdcode dump -q -o archive.tar docs/guide.md # Quiet to stdout mdcode dump -q README.md > archive.tar @@ -1119,14 +1124,14 @@ mdcode dump -q README.md > archive.tar ```bash # Dump only JavaScript files mdcode dump --lang js -o js-blocks.tar README.md -mdcode dump -l js -o javascript.tar docs/*.md +mdcode dump -l js -o javascript.tar docs/guide.md -# Dump specific file patterns -mdcode dump --file "*.py" -o python.tar docs/ +# Dump the blocks for one file= value +mdcode dump --file build.py -o python.tar docs/guide.md mdcode dump -f server.js -o server.tar README.md # Dump by metadata -mdcode dump --meta type=example -o examples.tar docs/ +mdcode dump --meta type=example -o examples.tar docs/guide.md mdcode dump -m region=main -o main.tar API.md ``` @@ -1149,7 +1154,9 @@ tar -tf code-blocks.tar ## Filtering Examples -All commands support the same filtering options. Here are comprehensive filtering examples: +All commands support the same filtering options. Here are comprehensive filtering examples. `--file` +matches a block's `file=` exactly, and `list`, `run` and `dump` read one Markdown file, while +`extract`, `update` and `validate` take several: ### Single Filters @@ -1161,12 +1168,12 @@ mdcode dump -l sql -o queries.tar API.md # By file metadata mdcode list -f app.js README.md -mdcode extract -f "*.test.js" docs/ +mdcode extract -f app.test.js docs/*.md mdcode run --allow-shell -f server.py "python {file}" README.md # By custom metadata mdcode list -m region=main README.md -mdcode extract -m type=example docs/ +mdcode extract -m type=example docs/*.md mdcode update --apply -m author=admin API.md ``` @@ -1179,7 +1186,7 @@ When you combine filters, ALL filters must match: mdcode list -l js -f app.js README.md # Language AND metadata -mdcode extract -l python -m type=example docs/ +mdcode extract -l python -m type=example docs/*.md # File AND metadata mdcode dump -f server.js -m region=main -o server.tar README.md @@ -1191,11 +1198,11 @@ mdcode list -l js -f app.js -m region=main README.md ### Complex Filtering Scenarios ```bash -# Extract all test files that are JavaScript -mdcode extract -l js -f "*.test.js" -d ./tests docs/ +# Extract one JavaScript test file +mdcode extract -l js -f app.test.js -d ./tests docs/*.md # List Python examples in main region -mdcode list -l python -m type=example -m region=main docs/ +mdcode list -l python -m type=example -m region=main docs/guide.md # Run tests only for specific component mdcode run --allow-shell -l js -f "auth.test.js" -n "login-test" "node {file}" README.md @@ -1474,8 +1481,10 @@ mdcode-ts gates its own releases the same way; see its All commands support these common flags: - `-l, --lang ` - Filter by language -- `-f, --file ` - Filter by file metadata pattern -- `-m, --meta ` - Filter by custom metadata (can specify multiple times) +- `-f, --file ` - Select blocks whose `file=` is exactly this value (not a glob) +- `-m, --meta ` - Select blocks with this metadata; repeat to require several, as in + `-m type=example -m region=main`. Each `--meta` takes one pair, so a file name after it is still read + as the document - `-n, --name ` - Select the block with this `name` metadata; repeat to select several - `--json` - Print one versioned JSON envelope instead of text (every command except `watch`); see [JSON Contract](#json-contract) @@ -2091,6 +2100,10 @@ Exit codes are the same with and without `--json`: `region=` failed; it used to write that document's other blocks. A document where only a transformer threw is still written. - The new `watch` command reports drift after each change to a document or a file its blocks read. +- `--meta` takes one `key=value` per flag; repeat it for several. It used to take every following + argument, so `mdcode list --meta type=example README.md` read `README.md` as a second pair, read + stdin instead, and found nothing. A value containing `=`, as in `--meta expr=a=b`, is now kept + whole. --- @@ -2272,7 +2285,7 @@ All functions support filtering: // Filter by language parse({ source: markdown, filter: { lang: 'js' } }); -// Filter by file pattern +// Filter by file= (exact match) parse({ source: markdown, filter: { file: 'app.js' } }); // Filter by custom metadata @@ -2624,14 +2637,14 @@ mdcode update --apply README.md ### Workflow: Test All Code Blocks ```bash -# Extract test files -mdcode extract -l js -f "*.test.js" -d ./tests docs/ +# Extract the blocks marked kind=test +mdcode extract -l js -m kind=test -d ./tests docs/guide.md -# Run all tests -mdcode run --allow-shell -l js -f "*.test.js" "npm test {file}" docs/ +# Run each of them +mdcode run --allow-shell -l js -m kind=test "node --test {file}" docs/guide.md # If tests pass, create archive -mdcode dump -l js -f "*.test.js" -o tests.tar docs/ +mdcode dump -l js -m kind=test -o tests.tar docs/guide.md ``` ### Workflow: Transform and Publish diff --git a/packages/mdcode/src/cli.ts b/packages/mdcode/src/cli.ts index c73550d..efc2fcd 100644 --- a/packages/mdcode/src/cli.ts +++ b/packages/mdcode/src/cli.ts @@ -45,7 +45,7 @@ type FilterCliOptions = { lang?: string; name?: Array; file?: string; - meta?: Record; + meta?: Array; json?: boolean; }; @@ -55,6 +55,7 @@ function collect(value: string, previous: Array | undefined): Array 0 && value) { filter.meta[key] = value; } } @@ -380,7 +382,7 @@ export async function Execute( .argument("[file]", "Markdown file to read (default: stdin)") .option("-l, --lang ", "Filter by language") .option("-f, --file ", "Filter by file metadata") - .option("-m, --meta ", "Filter by custom metadata") + .option("-m, --meta ", META_FLAG_HELP, collect) .option("-n, --name ", NAME_FLAG_HELP, collect) .option("--json", "Print one versioned JSON result instead of text") .action(async (file: string | undefined, options: FilterCliOptions) => { @@ -398,7 +400,7 @@ export async function Execute( .argument("[files...]", DOCUMENTS_HELP) .option("-l, --lang ", "Filter by language") .option("-f, --file ", "Filter by file metadata") - .option("-m, --meta ", "Filter by custom metadata") + .option("-m, --meta ", META_FLAG_HELP, collect) .option("-n, --name ", NAME_FLAG_HELP, collect) .option("-d, --dir ", "Directory file= paths resolve against and must stay inside; absolute paths and paths leading out, including through symlinks, are refused (default: the configuration's outputRoot, else the current directory)") .option("-q, --quiet", "Suppress status messages") @@ -523,7 +525,7 @@ export async function Execute( .argument("[file]", "Markdown file to read (default: stdin)") .option("-l, --lang ", "Filter by language") .option("-f, --file ", "Filter by file metadata") - .option("-m, --meta ", "Filter by custom metadata") + .option("-m, --meta ", META_FLAG_HELP, collect) .option("-n, --name ", NAME_FLAG_HELP, collect) .option("-k, --keep", "Keep temporary directory after execution") .option("-d, --dir ", "Working directory for command execution (default: temp directory)") @@ -567,7 +569,7 @@ export async function Execute( .argument("[files...]", DOCUMENTS_HELP) .option("-l, --lang ", "Filter by language") .option("-f, --file ", "Filter by file metadata") - .option("-m, --meta ", "Filter by custom metadata") + .option("-m, --meta ", META_FLAG_HELP, collect) .option("-n, --name ", NAME_FLAG_HELP, collect) .option("-t, --transform ", "Path to transformer function file (must export default)") .option("-q, --quiet", "Suppress status messages") @@ -801,7 +803,7 @@ export async function Execute( .default("update")) .option("-l, --lang ", "Filter by language") .option("-f, --file ", "Filter by file metadata") - .option("-m, --meta ", "Filter by custom metadata") + .option("-m, --meta ", META_FLAG_HELP, collect) .option("-n, --name ", NAME_FLAG_HELP, collect) .option("--strict", "Require file= metadata on every selected block") .option("--base ", "With --for update: directory file= paths resolve against and must stay inside, as for update") @@ -855,7 +857,7 @@ export async function Execute( .argument("[files...]", "Markdown files to watch (default: the configuration's documents with --project or --config)") .option("-l, --lang ", "Filter by language") .option("-f, --file ", "Filter by file metadata") - .option("-m, --meta ", "Filter by custom metadata") + .option("-m, --meta ", META_FLAG_HELP, collect) .option("-n, --name ", NAME_FLAG_HELP, collect) .option("--base ", "Directory file= paths resolve against and must stay inside, as for update") .option("--apply", "Write drifted blocks into the markdown after each change, as update --apply does") @@ -911,7 +913,7 @@ export async function Execute( .argument("[file]", "Markdown file to read (default: stdin)") .option("-l, --lang ", "Filter by language") .option("-f, --file ", "Filter by file metadata") - .option("-m, --meta ", "Filter by custom metadata") + .option("-m, --meta ", META_FLAG_HELP, collect) .option("-n, --name ", NAME_FLAG_HELP, collect) .option("-q, --quiet", "Suppress status messages") .option("-o, --out ", "Output file (default: stdout; required with --json)") diff --git a/packages/mdcode/src/types.ts b/packages/mdcode/src/types.ts index 8182af0..e88f76d 100644 --- a/packages/mdcode/src/types.ts +++ b/packages/mdcode/src/types.ts @@ -33,9 +33,10 @@ export type WalkerFunction = (block: Block) => Block | null | Promise { + const dir = await mkdtemp(join(tmpdir(), "mdcode-meta-")); + await writeFile(join(dir, "doc.md"), [ + "```js env=prod tier=web name=web", + "const web = 1;", + "```", + "", + "```js env=prod tier=db name=db", + "const db = 1;", + "```", + "", + "```js env=dev expr=a=b name=dev", + "const dev = 1;", + "```", + "", + ].join("\n"), "utf-8"); + + const names = async (...filters: Array): Promise> => { + const { exitCode, stdout } = await execCli([ "list", "--json", ...filters, "doc.md" ], { cwd: dir }); + assert.equal(exitCode, 0); + return JSON.parse(stdout).result.blocks.map(({ name }: { name: string; }) => name); + }; + + try { + assert.deepEqual(await names("--meta", "env=prod"), [ "web", "db" ], "doc.md must be read, not taken as a second key=value"); + assert.deepEqual(await names("-m", "env=prod", "-m", "tier=db"), [ "db" ], "repeated --meta must all match"); + assert.deepEqual(await names("--meta", "expr=a=b"), [ "dev" ], "a value may contain ="); + } + finally { + await rm(dir, { recursive: true, force: true }); + } + }); }); describe("block names", () => { From 6e14491571606cb34c41fd8a932c6f758b27fe82 Mon Sep 17 00:00:00 2001 From: Adrian Elton-Browning Date: Tue, 6 Oct 2026 16:44:04 +0100 Subject: [PATCH 2/4] test(cli): every command reads the file that follows --meta --- .intent/review-state.json | 10 +++--- packages/usage/tests/cli-integration.test.ts | 33 ++++++++++++++++++++ 2 files changed, 38 insertions(+), 5 deletions(-) diff --git a/.intent/review-state.json b/.intent/review-state.json index fc5cf66..f86ef2f 100644 --- a/.intent/review-state.json +++ b/.intent/review-state.json @@ -140,15 +140,15 @@ ] }, "source:packages/usage/tests/cli-integration.test.ts": { - "fingerprint": "0fc685f78cefa4dc0ae3597176c941aeb5cc89f4b7a33b51752566f0027f6305", + "fingerprint": "b25382b7ae8989834f89d3ab788edc35da848362fa32b6105ddf5e234103b399", "snapshot": { - "packages/usage/tests/cli-integration.test.ts": "d95b6f11091013d9b21683b124670ebbc5b5c206fe94c3faae5dba847f4a8abd" + "packages/usage/tests/cli-integration.test.ts": "ccc6b9b7a0f4c9a41c3fb6c7563e7f55ce1f38d8c04e79dcaf9c76000a3b2d0f" }, - "head": "1d5875a70d51efab8c48ee12071e0d3d68fa66da", + "head": "e880bfe898c0c2ff605662bbf74d71ab3497c97d", "outcome": "no-change", - "reason": "New regression test for --meta; it pins behaviour the skill relies on but changes no guidance.", + "reason": "Adds a table test that each of list, update, validate, extract, run and dump reads the document after --meta; it pins the CLI behaviour the skill describes and changes no guidance.", "evidence": [ - "cli-integration.test.ts '--meta takes one key=value, so a file after it is still the file to read' failed on the variadic build and passes after; `mdcode list --json --meta runnable=true README.md` now returns the runnable block (it returned none before); a scan of packages/mdcode/README.md and examples/CLI_EXAMPLES.md finds no list/run/dump with several files or a directory and no glob --file value; skill task check still passes." + "The test fails on origin/main's cli.ts ('list must read doc.md after --meta') and passes on this branch." ] } } diff --git a/packages/usage/tests/cli-integration.test.ts b/packages/usage/tests/cli-integration.test.ts index f47ee9c..055c004 100644 --- a/packages/usage/tests/cli-integration.test.ts +++ b/packages/usage/tests/cli-integration.test.ts @@ -249,6 +249,39 @@ const y = 2; await rm(dir, { recursive: true, force: true }); } }); + + it("every command reads the file that follows --meta", async () => { + const dir = await mkdtemp(join(tmpdir(), "mdcode-meta-each-")); + await writeFile(join(dir, "doc.md"), "```sh kind=keep name=keep\necho keep\n```\n\n```sh name=other\necho other\n```\n", "utf-8"); + + type Json = { result: Record; }; + const blocks = (envelope: Json): Array<{ name: string; }> => envelope.result.blocks as Array<{ name: string; }>; + const inDocuments = (key: string) => (envelope: Json): Array<{ name: string; }> => + (envelope.result.documents as Array; }>>>) + .flatMap(document => document[key]!) + .flatMap(entry => entry.blocks ?? [ entry ]); + + const commands: Array<[ Array, (envelope: Json) => Array<{ name: string; }> ]> = [ + [[ "list" ], blocks ], + [[ "update" ], inDocuments("blocks") ], + [[ "validate", "--for", "extract" ], inDocuments("blocks") ], + [[ "extract", "-d", "out" ], inDocuments("targets") ], + [[ "run", "--allow-shell", "true" ], blocks ], + [[ "dump", "-o", "out.tar" ], envelope => envelope.result.files as Array<{ name: string; }> ], + ]; + + try { + for (const [ [ command, ...flags ], selected ] of commands) { + const { exitCode, stdout } = await execCli([ command!, "--json", ...flags, "--meta", "kind=keep", "doc.md" ], { cwd: dir }); + + assert.equal(exitCode, 0, `${command}: ${stdout}`); + assert.deepEqual(selected(JSON.parse(stdout)).map(({ name }) => name), [ "keep" ], `${command} must read doc.md after --meta`); + } + } + finally { + await rm(dir, { recursive: true, force: true }); + } + }); }); describe("block names", () => { From 47705d8f2d87d0c8d1ecddbe4f5571b91cae5bd8 Mon Sep 17 00:00:00 2001 From: Adrian Elton-Browning Date: Tue, 6 Oct 2026 16:44:42 +0100 Subject: [PATCH 3/4] test(cli): every command reads the file that follows --meta --- packages/usage/tests/cli-integration.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/usage/tests/cli-integration.test.ts b/packages/usage/tests/cli-integration.test.ts index 055c004..630a71c 100644 --- a/packages/usage/tests/cli-integration.test.ts +++ b/packages/usage/tests/cli-integration.test.ts @@ -271,7 +271,7 @@ const y = 2; ]; try { - for (const [ [ command, ...flags ], selected ] of commands) { + for (const [[ command, ...flags ], selected ] of commands) { const { exitCode, stdout } = await execCli([ command!, "--json", ...flags, "--meta", "kind=keep", "doc.md" ], { cwd: dir }); assert.equal(exitCode, 0, `${command}: ${stdout}`); From ed2d63891e7603b6bde8107659a102cf313051b7 Mon Sep 17 00:00:00 2001 From: Adrian Elton-Browning Date: Tue, 6 Oct 2026 16:46:20 +0100 Subject: [PATCH 4/4] chore(intent): record review of the test lint fix --- .intent/review-state.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.intent/review-state.json b/.intent/review-state.json index f86ef2f..ba54608 100644 --- a/.intent/review-state.json +++ b/.intent/review-state.json @@ -140,15 +140,15 @@ ] }, "source:packages/usage/tests/cli-integration.test.ts": { - "fingerprint": "b25382b7ae8989834f89d3ab788edc35da848362fa32b6105ddf5e234103b399", + "fingerprint": "66381fd8482be689bdb8f5b47081edc43d913907e75825d6becb7bc9cdd0ad51", "snapshot": { - "packages/usage/tests/cli-integration.test.ts": "ccc6b9b7a0f4c9a41c3fb6c7563e7f55ce1f38d8c04e79dcaf9c76000a3b2d0f" + "packages/usage/tests/cli-integration.test.ts": "b6d519515c11fbca0632b50925c02a9af55f11a4928e0e0f10d462a9856cda38" }, - "head": "e880bfe898c0c2ff605662bbf74d71ab3497c97d", + "head": "47705d8f2d87d0c8d1ecddbe4f5571b91cae5bd8", "outcome": "no-change", - "reason": "Adds a table test that each of list, update, validate, extract, run and dump reads the document after --meta; it pins the CLI behaviour the skill describes and changes no guidance.", + "reason": "Whitespace-only lint fix to the --meta table test (array bracket spacing); no guidance affected.", "evidence": [ - "The test fails on origin/main's cli.ts ('list must read doc.md after --meta') and passes on this branch." + "Changed: packages/usage/tests/cli-integration.test.ts at 47705d8f2d87d0c8d1ecddbe4f5571b91cae5bd8" ] } }