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.
Cancel
A handle for stopping a run from another thread.
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.
Requirement
One entry in a Requires: list: a job to run first, and the arguments to run it with.
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 upward, nearest first. In each directory the first of tasks.md, maskfile.md, README.md that parses to at least one job is taken.
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_agent_cancellable
run_agent, stoppable through a Cancel handle held by another thread.
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.