CommandGraph is a DSL and execution engine for declaring CLI command dependencies as a DAG. Two formats (.cg brace-delimited, .cgr indentation-based) produce identical ASTs. Development source lives in cgr_src/; the shipped single-file artifact cgr.py is generated from those modules. The CLI is cgr.
Recent engine changes to keep in mind:
- Sub-graph inclusion as a step unit:
[deploy app] from ./deploy_app.cgr: - Wait gates:
wait for webhook "..."andwait for file "..." - Machine-readable apply output:
cgr apply FILE --output json - Execution timing persisted in state via per-wave and per-run metric records
cgr_src/ # Source modules used during development
cgr_dev.py # Dev entrypoint that imports `cgr_src.cli:main()`
cgr.py # Generated single-file artifact shipped to users
MODULE_MAP.md # Quick lookup for where to make changes
ide.html # Web IDE frontend, served by `cgr serve`
MANUAL.md # Authoritative syntax reference
COMMANDGRAPH_SPEC.md # Formal language spec (PEG grammar, for code generators)
repo/ # Template repository (44 .cgr stdlib templates)
testing/ # Container demos (7 local demos)
testing-ssh/ # Container SSH demos (5 two-node demos)
benchmarks/ # Parallelism benchmarks
.claude/docs/ # Design docs (historical)
.cg → §1 Lexer → §3 Parser ─┐
├→ §2 AST → §4 Repo → §5 Resolver → waves
.cgr → §3b CGR Parser ────────┘ │
§6 Plan/Apply + §6b State
§7 DOT │ §8 HTML Viz │ §9 IDE+CLI
- Crash safety: A resource is written to
.stateonly after execution completes. Missing from state means it must re-run on resume. - Resume matrix:
success/skip_check/skip_whenskip.warned/failed/timeoutre-run. Missing means run. - Both formats stay in sync: The resolver does not know which parser produced the AST. Parser changes must preserve parity between
.cgand.cgrunless the difference is explicitly intentional and documented. - Parallel desugaring:
parallel,race,each, andstageall desugar into gate → fork → join DAG patterns. Do not add executor-only special cases unless that architecture is being intentionally changed. - Cross-node dedup is disabled: The identity hash includes
node_name, so identical commands on different hosts are never deduplicated. - HTTP steps are first-class:
get/post/put/patch/deletedesugar into the same(rc, stdout, stderr)execution model. Local targets useurllib; SSH targets constructcurlcommands. Auth tokens must remain redacted. - Sub-graph inclusion is an execution type, not resolver flattening: the parent graph tracks the outer step, and the child graph keeps its own state file.
- Wait gates are first-class steps: preserve timeout, cancellation, and resume behavior; do not replace them with ad hoc shell polling unless intentionally redesigning the feature.
- State journals now contain both resource records and metric records (
_wave,_run). Any state tooling or compaction logic must preserve both.
- Heredocs break the
.cgrparser. Useprintfor.cginstead. - No rollback. The system is forward-only by design.
- Race cancellation does not kill subprocesses that already started.
state testonly works for resources withcheckclauses.- HTTP syntax is only supported in the
.cgrparser, not.cg, at present. - Sub-graph inclusion currently uses
.cgrstep syntax; there is no matching.cgsurface syntax yet. wait for webhookis a lightweight local listener forapply, not a general distributed signal system.- State files are not run-instance unique: the state file is always
FILE.cgr.state, keyed only to the graph file path, not to--setvalues. Two concurrent runs of the same graph with different parameters share one state file and identical resource IDs, so they can clobber each other. The advisory PID lock prevents simultaneous runs on the same machine, but it does not isolate distinct parameterized invocations. A future--run-idor--state FILEflag would be needed for per-invocation isolation.
# Compile check
python3 -c "import py_compile; py_compile.compile('cgr.py', doraise=True)"
# Pytest suite
python3 -m pytest test_commandgraph.py -x -q
# Validate root feature-exercise example files
cgr validate nginx_setup.cg && cgr validate nginx_setup.cgr
cgr validate webserver.cg --repo ./repo && cgr validate webserver.cgr --repo ./repo
cgr validate parallel_test.cgr && cgr validate multinode_test.cgr && cgr validate multinode_test.cg
cgr validate system_audit.cgr --repo ./repo
cgr validate api_integration.cgr
# Container demos (if available)
testing/run-demos.sh # 7 local demos
testing-ssh/run-ssh-demos.sh # 5 SSH demos- Prefer changes that preserve English-like syntax. In
.cgr, usefirst, notafter,before, orneeds. - Make code changes in
cgr_src/, notcgr.py; the canonical rebuild path ispython3 cgr_dev.py apply build.cgr(orpython3 cgr.py apply build.cgr) after changing source modules,ide.html, orvisualize_template.py. - Treat
MANUAL.mdas the syntax authority andCOMMANDGRAPH_SPEC.mdas the formal grammar reference. - Use
.claude/docs/design_doc_*.mdfor historical design rationale when behavior is unclear. - Keep
.cgand.cgrbehavior aligned. If you touch one parser or syntax path, inspect the corresponding path in the other format. - If you touch apply output, keep
--output jsonand--report FILE.jsonaligned by deriving both from the same execution/result data when possible. - If you touch state handling, inspect both resource-entry behavior and
_wave/_runmetric behavior. - If you touch sub-graph inclusion, preserve the contract that the child graph runs as a unit rather than as flattened parent resources.
- Test against the root feature-exercise graphs after changes, not just unit tests.
- Templates may exist in either
.cgror.cg, but the stdlib inrepo/is.cgr, and the repo loader prefers.cgr. - Be cautious with state semantics, dependency resolution, and desugaring changes. Small regressions in those areas can silently break resume behavior or execution ordering.