3293cb5ff40c3eb18a599ce9113ed74d3d8d1607
12 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
3293cb5ff4 |
feat(py): mirage mcp over stdio, and invert the mypy allowlist
Two cleanup-plan items, both Python-side.
Item 29 -- `mirage mcp`. TypeScript shipped a six-tool stdio MCP server;
Python had none, so a pip-install user could not point Cursor or Claude
Desktop at a workspace and `mirage --help` differed by distribution.
Adding the entry point alone would have duplicated the tools, because
this side kept them private inside the Claude Agent SDK integration, so
the shared layer comes first:
- agents/tool_descriptions.py -- the six strings, one copy.
- agents/tool_operations.py -- MirageToolOperations, lifted out of the
SDK server's private _MirageTools.
- agents/file_version.py -- stale-write protection, which this side
lacked entirely. TS stamps stored bytes; here the stamp covers the
rendered bytes, because this read tool has always rendered and an
edit must search what the agent was actually shown.
- agents/mcp/server.py + cli/mcp.py -- the server and `mirage mcp`.
- server/workspace_config.py -- config discovery (candidates, env
names, walk up from cwd), which Python had nowhere, so every entry
point had to be handed an explicit path.
The server is the low-level MCP Server rather than FastMCP: FastMCP does
not forward a version, and TS advertises one. Handlers are bound methods,
not decorated closures, so nothing nests.
Item 28 -- the mypy allowlist. 54 modules opted *in* to annotation
checking against 1826, so the default was unchecked and every new file
joined the unchecked side. The default is now strict, with a list of
what is not yet annotated that only shrinks. 166 annotations cleared
along the way; the remainder is named module by module.
Two real defects surfaced by the annotations, neither of them typing:
- Workspace._original_open / _original_os were invented by assignment
in lifecycle.patch_process, so unpatch without a patch raised
AttributeError. Declared, and the restore is guarded.
- sed_generic declared a non-optional writer while its own docstring
and its `write_bytes is None` branch said otherwise; the builder
passes None whenever the backend cannot write.
Tests keep the PathSpec rule instead of full strict: measured, full
strict on python/tests is 2374 errors, of which 634 are `str` where a
pydantic field declares SecretStr -- which pydantic coerces at runtime --
and most of the rest is the monkeypatched-fake pattern CLAUDE.md
sanctions. The rule that is violated for real is PathSpec, 19 times, and
scripts/check_test_pathspec.py now holds that line. One of the 19 was a
latent AttributeError: tests/e2e passes a str to s3 write_bytes, which
reads .mount_path, and the test skips without a live versioned bucket.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
ae36ac41ee |
test(integ): report-and-continue pins + builder wiring gate
postgres/mongodb mixed good+missing operand facets, history cross-mount continue, test_builders_declare_only_dispatcher_params ratchet; history ls/tree/stat/find and dify find/cat wrappers ride the generics. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
836047e286 |
refactor(commands): builders become wiring; generics own the flags (T2-2)
26 builders forward **flags wholesale; each generic parses once into a frozen struct via a spec-bound FlagView (ls/du/find/sed/tar/tree + 20 mechanical). rm/rmdir/ln/touch are builder-is-implementation and read the bag through FlagView inline, mirroring the TS builders. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
daddf11965 |
Add Tier 2 coreutils support (#619)
Pre-commit / pre-commit (push) Has been cancelled
CLI exit codes / changes (push) Has been cancelled
CLI exit codes / cli-gate (push) Has been cancelled
CLI exit codes / Python CLI (push) Has been cancelled
CLI exit codes / TypeScript CLI (push) Has been cancelled
CLI exit codes / Cross-language snapshot interop (push) Has been cancelled
Test (Install) / changes (push) Has been cancelled
Test (Install) / python-install (push) Has been cancelled
Test (Install) / ts-install (push) Has been cancelled
Test (Install) / test-install-gate (push) Has been cancelled
Integ / changes (push) Has been cancelled
Integ / integ (push) Has been cancelled
Integ / integ-ts (push) Has been cancelled
Integ / integ-shared (push) Has been cancelled
Integ / integ-database (push) Has been cancelled
Integ / integ-database-ts (push) Has been cancelled
Integ / integ-data (push) Has been cancelled
Integ / integ-fuse (push) Has been cancelled
Integ / integ-fuse-windows (push) Has been cancelled
Integ / runtime-py (push) Has been cancelled
Integ / runtime-ts (push) Has been cancelled
Integ / integ-gate (push) Has been cancelled
Test (Python) / changes (push) Has been cancelled
Test (Python) / test (push) Has been cancelled
Test (Python) / import-isolation (deepagents, openai, mirage.agents.openai_agents) (push) Has been cancelled
Test (Python) / import-isolation (deepagents, pydantic-ai, mirage.agents.pydantic_ai) (push) Has been cancelled
Test (Python) / runtime (push) Has been cancelled
Test (Python) / audit (push) Has been cancelled
Test (Python) / test-python-gate (push) Has been cancelled
Test (TypeScript) / changes (push) Has been cancelled
Test (TypeScript) / test (push) Has been cancelled
Test (TypeScript) / python-fs-shim (push) Has been cancelled
Test (TypeScript) / test-typescript-gate (push) Has been cancelled
* Add Tier 2 coreutils support * Fix Tier 2 CI checks * Fix PR review findings and CI failures for Tier 2 coreutils CI: - regenerate spec/ JSON for the new and changed command specs - bump the TypeScript BUILTIN_SPECS count to 92 - wire the truncate op for redis, ssh and gridfs on the Python side (TypeScript already had all three), fixing native truncate on redis Review findings, applied to both languages: - checksum: parse BSD/--tag lines under --check, and honor -z for --tag - join: bounds-check --check-order and --header field reads so blank or short lines no longer raise IndexError, and honor --nocheck-order - mktemp: move -q into the generic, narrow it to OSError, and suppress the creation diagnostic instead of emitting it (GNU); add -q to the TypeScript generic, which had none - split: keep the empty record a second trailing separator terminates - paste: decode -d escapes with a single left-to-right scan so \0 means the empty delimiter and \\ stays a literal backslash - executor: merge positional and path-flag scopes instead of dropping the flag scopes whenever a positional path is present, so a path-flag target on another mount is routed and rejected rather than misrouted - mkdir: apply -m/--mode through set_attrs; parse_mode moves to a leaf util shared by the command layer and the executor builtins - numfmt: convert only the first field of each stdin record and copy the rest through verbatim - od: concatenate every FILE operand as one input - nl: write an empty line in place of each logical-page delimiter, and pad a one-character -d with ':' as GNU does GNU behavior for nl, join, paste and numfmt was pinned against coreutils 9.8. The nl integ expectations encoded the old delimiter handling and are corrected; six integ cases are added for findings that had no coverage. * Regenerate truncate spec after wiring redis, ssh and gridfs |
||
|
|
1c193bc4e8 |
Optional-typing sweep: path-only generic ops, NULL_INDEX, no bare generics (#541)
* refactor(types): stdin annotations use the ByteSource alias * refactor(generics): injected ops are path-only, accessor+index bind at the wrapper Generic commands no longer take accessor (or a dead index) just to thread them back into injected callables. Builders and bespoke wrappers bind both via bound_op (None-passthrough) or partial for write-side ops, mirroring the TS builders' closures. call_*/resolve_pattern/relay helpers and the cache read-through wrappers gain path-first forms; CommandIO-level ops keep the raw (accessor, path, index) shape. * refactor(index): NULL_INDEX everywhere, no index-is-None branches index is a required IndexCacheStore on every op and wrapper; callers with no real index pass NULL_INDEX (the null object built for exactly this). Dead is-None guards drop out of trello/linear/github_ci/google readdir+stat, CacheManager, and the github wrappers. * style(types): parameterize every bare generic, gate with disallow_any_generics dict -> dict[str, Any] (payloads) / dict[str, object] (flag bags), Callable -> Callable[..., Any], plus list/tuple/Awaitable/Pattern/ Task/Future/Token/AsyncMongoClient. The mypy gate now carries disallow_any_generics so bare generics cannot come back. * style: pre-commit formatting + parameterize main's new bare dicts * fix: post-merge gate fixes + make security barriers analyzer-recognizable - resolve_within_root: startswith(root + sep) guard instead of commonpath (same semantics; the shape CodeQL models as a path-injection barrier) - cli table output: trimEnd() instead of the polynomial /\s+$/ trim - example proxies: re-encode validated endpoint segments before URL construction (recognized SSRF barrier; no-op for the allowed charset) - parameterize bare generics arriving from #540 (the new mypy gate caught them) * fix(server): plain startswith barrier in resolve_within_root |
||
|
|
ea8096f155 |
Drive Python mypy to zero and enable it as a hard gate (#522)
* chore(py): shrink mypy baseline 117->84 (resource accessor typing) Narrow each resource's accessor attribute to its concrete type so the backend resolve_glob/stat calls type-check; wrap fingerprint stat() paths with PathSpec.from_str_path (fixes a latent str-to-stat bug); retype the shared hf_buckets core to _HfAccessor (common base of the hf siblings); make DevStore a RAMStore subclass (_DevFiles a dict subclass); narrow the sync redis get_state() reads (redis-py ResponseT is an async/sync union). Full Python test suite green (9139 passed; fuse excluded, macOS limit). * chore(py): shrink mypy baseline 84->74 (workspace/executor/shell/resource typing) * chore(py): clear non-generic mypy errors, shrink baseline 74->56 * chore(py): clear all generic command-layer mypy errors (127->0) * chore(py): enable mypy as a hard gate (pre-commit + CI), drop ratchet baseline * fix(py): sed -i guards write availability; dedupe mypy pre-commit hook post-merge * fix(py): pyproject trailing newline; run mypy on hosted pre-commit too |
||
|
|
753f005154 |
GNU gaps: EISDIR on directories, bash arithmetic, cross-mount paste/comm/join, operand arity (#477)
* fix(read): directory operands refuse with GNU EISDIR, implicit dirs probed via readdir (#457) * feat(shell): bash arithmetic evaluator, (( )) command and $(( )) assignments (#369) * feat(crossmount): paste/comm/join relay over the shared generics, awk streams (#445) * test(integ): arith, EISDIR, and relay-interleave coverage; OPFS dirs raise EISDIR * feat(spec): commands own operand arity, extra operands refuse with GNU errors (#452) * fix(read): implicit-dir probe confirms against the parent listing, TS stream stats first The ENOENT readdir probe trusted the operand's own listing: postgres fabricates schema children for any name (Is a directory for missing files) and lancedb raises driver errors that escaped the probe. The parent listing decides instead, and any probe failure keeps the original ENOENT. TS streamRefusingDirs now stats before reading like the Python twin, so sftp directory reads report EISDIR instead of an opaque Failure. Also updates the stale parser overflow test (#452 pass-through) and the databricks tr file-operand test (GNU tr takes no file operands). * refactor(spec,shell): CommandName enum, arith constants/errors modules, ArithParser/ArithEvaluator Arith tokenizer/limit constants move to shell/constants and ArithError to shell/errors in both languages; the parser and evaluator classes get their public names. CommandName (StrEnum / TS string enum) lands in spec/types next to OperandKind, used by the arity guards and the usage message shapes; the diff/cmp hint grouping becomes USAGE_HINT_PREFIX in spec/constants. USAGE_EXIT keys stay plain strings: types.py imports constants for flag_kwarg_name, so the table cannot import the enum without a cycle. |
||
|
|
ce39e351ce | chore(generic): type accessor as Accessor, document probe except-pass sites | ||
|
|
4da17edb3f |
refactor(paths): pure-virtual PathSpec, mount stamps resource_path
- rename original -> virtual, as_typed -> raw_path - remove prefix field and strip_prefix/key properties - add required resource_path stamped by the mount at dispatch - add strip_mount/mount_key/rekey/mount_prefix_of in utils/key_prefix - mirror in TypeScript (resourcePath, rawPath, mountKey et al) |
||
|
|
82a1d15460 | fix: align no-input behavior of generic commands with GNU /dev/null semantics (#279) | ||
|
|
12b975d5af |
fix(io): sweep remaining original-keyed records; ln/zip integ coverage
Reviewer follow-ups on the same bug class: ln (6 py backends + ts s3) and zip (4 py copies + ts s3) writes keys; dify cat/wc reads; generic base64/tr/tac/xxd/diff/sha256sum cache keys; local_audio reads/cache (py s3 + ts disk/ram). apply_io now uses the public buffered_chunks. chain_cachables unit tests cover live pull, drained replay, partial consumption, and early-stop. Integ adds lnzip_* cases (warm ls, ln -s, zip, re-ls, read back); truth regenerated and all nine backends verified byte-identical. |
||
|
|
870016c5b4 |
refactor(commands): generic dedup + cross-backend integ harness (#68)
* feat(utils): add stream/bytes conversion helpers
* feat(commands): add generic cat as async iterator
* refactor(commands): ram cat wrapper uses generic
* test(commands): expand generic cat coverage; fix $ on trailing partial line
Old _number_lines_stream had loose tests that missed three POSIX edge cases:
multi-digit line number alignment (%6d vs literal 5-space prefix), trailing
newline preservation, and $-marker placement on partial last lines. Added
unit coverage for all three, plus combined-flag scenarios and a
one-byte-at-a-time chunking test.
The trailing-$ test caught a real bug: my generic was emitting "hello$" for
`cat -E` on input without a final \n, but BSD/GNU cat only emit $ before \n.
Fixed by dropping the suffix from the trailing-partial-line branch.
* test(ram): add 1:1 test file for ram cat wrapper
Establishes src↔test correspondence (every src file has a matching test_*.py)
for the ram cat wrapper. Covers the wrapper's behavior end-to-end via
Workspace.execute, including regression tests for the two POSIX bug fixes
that landed via the generic refactor:
- cat -n multi-digit line number alignment (lines 1-12)
- no-trailing-newline preservation for both cat and cat -n
Plus multi-file concatenation, empty input, and the all-blank-lines edge case.
* refactor(commands): disk cat wrapper uses generic + 1:1 tests
The active disk cat is at disk/cat/cat.py (the disk/cat package shadows the
top-level disk/cat.py file, which is dead code). Wires through generic_cat,
preserves CachableAsyncIterator/IOResult shape, drops _number_lines_stream.
Tests cover the same POSIX edge cases as ram cat: multi-digit alignment,
no-trailing-newline preservation, empty input, all-blank-lines.
* refactor(commands): redis cat wrapper uses generic + 1:1 tests
Structurally identical to ram cat migration. Tests use the same REDIS_URL
skip pattern as tests/core/redis/conftest.py — they run when REDIS_URL is
set in the environment, skip otherwise.
* fix(commands): preserve cachable identity in cat wrappers for cache to populate
Bug: wrapping cachable in generic_cat unconditionally caused stdout and
io.reads[path] to reference different objects. mount.py:_wrap then wrapped
them separately, and wrap_cachable_streams only updated stdout when
identity matched (stdout is stream). The cache cachable was never iterated
by stdout materialization, so the background drain saw an exhausted source
and cached empty bytes.
Fix: only wrap with generic_cat when a flag (currently just -n) actually
requires line processing. For the plain 'cat path' case, return cachable
directly — matching the pre-refactor behavior and keeping stdout identical
to the value in io.reads[path].
Tests caught: test_cache_hit_serves_from_ram, test_grep_uses_cache,
test_fuse_read_uses_cache_when_populated (workspace cache layer).
* feat(commands): add generic head with -c fast path and -n line-buffered path
* refactor(commands): ram head wrapper uses generic + 1:1 tests
* refactor(commands): disk head wrapper uses generic + 1:1 tests
* refactor(commands): redis head wrapper uses generic + 1:1 tests
* test(commands): backfill generic cat/head coverage missing from old helpers
Audited tests against legacy head_helper/cat_helper test files. Added:
cat:
- empty input passthrough (no flags)
- binary passthrough across full 0..255 byte range
- show_ends only marks newlines, not other binary bytes
head:
- n=1 single-line output
- empty input (with and without -c)
- single line no newline with default n
- c=negative emits nothing
* refactor(commands): consolidate ram/disk/redis logic into generic modules
Phases K through Z extracted shared per-command logic into
mirage/commands/builtin/generic/<name>.py modules that accept VFS
callables (read_bytes, read_stream, write_bytes, stat, readdir, mkdir)
via dependency injection. Per-backend wrappers (ram, disk, redis) are
now thin shims that resolve globs, supply backend-specific callables,
and delegate to the generic.
Scope:
- 57 generic modules
- 170 backend wrappers reduced to shims
- 67 new generic-level tests under tests/commands/builtin/generic/
- Net -7,772 lines (178 files changed: +1,891 / -9,663)
Bug fixes converged during refactor (no backward compat needed):
- disk/md5 emitted only the digest; now matches coreutils `{digest} {path}`
- disk/sort and disk/nl only operated on paths[0]; now process all paths
- disk/awk lacked BEGIN/END/accumulator support; converged on ram/redis
- disk/unzip flattened archive directory structure; now preserves it
- disk/patch used offset-splice algorithm; converged on lockstep walk
(closer to real patch(1) semantics)
- rg failed to auto-recurse on bare directories; now matches ripgrep
- file command silently swallowed read errors; now logs to logger.debug
Cleanup:
- Deleted 3 dead top-level head.py files shadowed by head/ packages in
ram/disk/redis (Python package always wins over .py at same level)
- Removed 4 stray __init__.py files under tests/ per CLAUDE.md rule
- Replaced inline string literals with enums (FindType, LsSortBy)
- Removed bare `try/except: pass` for mkdir using idempotent
mkdir(parents=True) instead
* test(integ): add cross-backend equivalence harness with 240 cases
New /integ folder runs identical command suite across ram/disk/redis
and diffs each backend's output against committed integ/truth.txt
canonical fixture. CI workflow (.github/workflows/test_integ.yml)
boots a redis service and runs all three backends.
Also fixes bugs surfaced by running the suite:
- disk/cat: chain multiple files (was processing only paths[0])
- sha256sum: use original path string; -c mode resolves mount prefix
- md5: support stdin when no paths given
- jq: iterate per-record on .jsonl files (real jq semantics)
- xxd -r: strip offset prefix and ASCII suffix before unhexlify
- find -mindepth/-maxdepth: off-by-one across ram/disk/redis/s3
- find -o vs -or: handle both spellings
- awk: \$NF/\$NR resolve via field indirection; bare condition
programs (e.g. '\$2 > 28') treated as condition with default
print \$0; && and || compound conditions; BEGIN block executes;
END can read NR
- diff: default to GNU normal format (1,5c1,3 with < / > / ---);
-u flag required for unified output
- cmp: use 'char N' instead of 'byte N' (matches GNU/BSD)
Extracts:
- DiffOpTag StrEnum (equal/delete/insert/replace) in diff_types.py
- AwkCmpOp / AwkBoolOp / AwkBlock / AwkBuiltin StrEnums plus
FIELD_PREFIX / PRINT_STMT / CMP_OP_PATTERN constants in
generic/awk_types.py
* fix(integ): preserve trailing whitespace in truth.txt + redis find test
The trailing-whitespace pre-commit hook stripped tabs at line-end in
integ/truth.txt, which broke the diff against backends (paste pads
shorter columns with trailing tabs). Exclude integ/truth.txt from
end-of-file-fixer, mixed-line-ending, and trailing-whitespace hooks.
Also fix tests/core/redis/test_find.py {maxdepth, mindepth} that
asserted the previously-buggy depth math (matches the existing
ram/disk test updates).
|