Skip to main content

Crate mdtask_core

Crate mdtask_core 

Source
Expand description

mdtask-core parses a markdown task file into a typed job tree and runs jobs from it. It is embeddable, execution-capable, and dependency-free.

A task file is ordinary markdown (a tasks.md, a maskfile.md, or a project README.md): a heading is a job, the first fenced code block under it is the script, and Key: value lines in the body carry metadata. The format is its own grammar, a graceful superset that borrows xc’s metadata vocabulary and mask’s runtime shape (per-fence interpreter, positional args). It reads cleanly in those tools where the features overlap, but claims no compatibility.

let tf = mdtask_core::parse("\
# greet\n\
\n\
Args: name\n\
\n\
```sh\n\
echo \"hello {{ name }}\"\n\
```\n");
let job = tf.job("greet").unwrap();
assert_eq!(job.args[0].name, "name");

Parsing is pure. A consumer sees only jobs and their metadata: interpreter selection, argv building, working-directory resolution, and spawning are all internal. Three entry points run a job and its Requires: chain: run inherits stdio (streaming, for a CLI), run_captured captures the aggregated output (for an embedder), and run_agent adds the agent allow gate and the injection guard (for an MCP or agent surface). The parser is line-based (no CommonMark dependency), so a # or Key: inside a fenced block is never mistaken for structure.

Structs§

Arg
One declared positional argument.
Job
One job: a named script with its metadata. The script, its interpreter language, its Opts: flags, and its extra environment are internal mechanics; a consumer deals in the name, description, declared args, dependencies, and the agent gate, and runs the job through run, run_captured, or run_agent.
MissingArg
A declared argument had no value supplied when binding a job’s args.
TaskFile
A parsed task file: the jobs, any file-level environment hoisted to all of them (an Env: under a section heading applies to every job regardless of where in the document it appears; hoisting is not positional), and any parse warnings (an unterminated fence, a duplicate job, an unknown fence language). Parsing is infallible. A malformed file still yields what it can, so an embedder should surface warnings() rather than trust silence. The internal fields carry execution mechanics; a consumer reaches jobs through jobs and job, and runs them through run, run_captured, or run_agent.

Enums§

DepError
A Requires: dependency chain could not be resolved.
RunError
Why a run* call could not complete. It reports the failure to resolve or dispatch a job; a job that runs to a non-zero exit is not an error here (the exit status rides back in the Ok). Only Debug is derived, because Io wraps a std::io::Error, which is neither Clone nor PartialEq.

Functions§

agent_jobs
The jobs a set of layered files exposes to an agent or MCP surface: one per name using the nearest definition (so a nearer non-allowed job shadows a farther allowed one, matching run semantics), keeping only those whose nearest definition carries Agent: allow. This is the enforcement point for listing; run_agent is the enforcement point for running. A surface exposing jobs to an agent should list only these.
find_task_files
Search for task files from start up to the filesystem root, nearest first. In each ancestor directory the first of tasks.md, maskfile.md, README.md that parses to at least one job is taken. The CLI layers these child-first, so a nearer file shadows a farther one by job name (like just’s set fallback, letting a project inherit a baseline of jobs from a parent). Embedders with their own project root can ignore this and call parse.
parse
Parse a markdown task file. It is line-based (no CommonMark dependency): a heading starts a job, the first fenced block under it is the script, and Key: value lines set metadata. Parsing is infallible; problems are reported in TaskFile::warnings rather than dropped to silence. CRLF endings are normalized.
run
Run name and its Requires: chain across the layered files, inheriting the parent’s stdio so output streams straight through (the CLI path: a job is an interactive command, not a captured subprocess). Dependencies run first, each once, and each with its own defaults; only name receives args. Returns the first failing step’s exit status, or the last step’s on full success. This is trusted: it applies no agent gate, so a caller must not hand it a name from an untrusted source.
run_agent
The agent gate: run name for an MCP or agent surface, captured, failing closed. Enforced, in order:
run_captured
Like run, but captured: run name and its Requires: chain across the layered files with output aggregated across steps into a single std::process::Output (its status is the failing step’s, or the last on success). For an embedder (a TUI, an editor) that wants the text rather than a stream. Also trusted: no agent gate.