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 throughrun,run_captured, orrun_agent. - Missing
Arg - A declared argument had no value supplied when binding a job’s args.
- Task
File - 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 surfacewarnings()rather than trust silence. The internal fields carry execution mechanics; a consumer reaches jobs throughjobsandjob, and runs them throughrun,run_captured, orrun_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 theOk). OnlyDebugis derived, becauseIowraps astd::io::Error, which is neitherClonenorPartialEq.
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_agentis the enforcement point for running. A surface exposing jobs to an agent should list only these. - find_
task_ files - Search for task files from
startup to the filesystem root, nearest first. In each ancestor directory the first oftasks.md,maskfile.md,README.mdthat 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’sset fallback, letting a project inherit a baseline of jobs from a parent). Embedders with their own project root can ignore this and callparse. - 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: valuelines set metadata. Parsing is infallible; problems are reported inTaskFile::warningsrather than dropped to silence. CRLF endings are normalized. - run
- Run
nameand itsRequires:chain across the layeredfiles, 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; onlynamereceivesargs. 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
namefor an MCP or agent surface, captured, failing closed. Enforced, in order: - run_
captured - Like
run, but captured: runnameand itsRequires:chain across the layeredfileswith output aggregated across steps into a singlestd::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.