Summary
Add a process trace feature that generates a structured, readable markdown file documenting a script's execution flow — every function call, :args/:usage dispatch, variable state, and timing.
Two activation methods:
- Env var (zero deps):
ARGSH_TRACE=/path/out.md myscript deploy --env staging
- Binary wrapper (rich, argsh-aware):
argsh-dap --trace out.md -- myscript deploy --env staging
If the binary isn't available locally, the env var path forwards to the Docker container (same pattern as argsh lint, argsh test, etc.).
Use cases
- CI debugging: attach trace to failing pipeline runs
- Documentation: auto-generate "how this script works" docs from a real execution
- Code review: show reviewers the actual execution path of a change
- Onboarding: new team members can see what a script does without reading all the code
- Bug reports: attach a trace showing exactly what happened
Activation
Method 1: Environment variable (pure bash)
ARGSH_TRACE=./trace.md myscript deploy --env staging
- Intercepted early in
:args / :usage (before user code runs)
- Sets up a
trap DEBUG handler that writes markdown
- Pure bash — no binary dependency
- Output: functional but basic (no argsh type analysis)
Method 2: Binary wrapper (full analysis)
argsh-dap --trace ./trace.md -- myscript deploy --env staging
- Uses the existing DAP debug prelude + FIFO infrastructure
- Runs headless (no VSCode, no DAP protocol — just collects data and writes markdown)
- Has full
argsh_syntax analysis: field types, :usage tree, import resolution
- Output: enriched with argsh-specific annotations
If argsh-dap is not installed, falls back to Docker:
docker run --rm -v "$(pwd):/workspace" -w /workspace \
-e ARGSH_TRACE=/workspace/trace.md \
ghcr.io/arg-sh/argsh:latest myscript deploy --env staging
Markdown output structure
# Process Trace: myscript deploy --env staging
> Generated: 2026-04-18 12:00:00
> Script: `/path/to/myscript`
> Exit code: 0 | Duration: 1.2s | Steps: 47
---
## Command Tree
Shows the `:usage` dispatch path taken during this execution:
myscript
└── deploy
├── :args "Deploy to environment"
│ ├── env|e:! = "staging" (required flag, --env staging)
│ ├── verbose|v:+ = 0 (boolean flag, default)
│ └── target = "all" (positional, default)
└── :usage "Deploy commands"
└── run → deploy::run
---
## Execution
### → `main` (myscript:3)
| # | Line | Command | Duration |
|---|------|---------|----------|
| 1 | 5 | `:args "Deploy" "${@}"` | <1ms |
| 2 | 7 | `deploy::validate` | 2ms |
| 3 | 12 | `echo "Deploying to staging"` | <1ms |
<details>
<summary>Variables after :args (line 5)</summary>
| Variable | Value | Type | Source |
|----------|-------|------|--------|
| `env` | `"staging"` | string :! | `--env staging` |
| `verbose` | `0` | boolean :+ | default |
| `target` | `"all"` | string | positional default |
</details>
### → `deploy::validate` (deploy.sh:20)
| # | Line | Command | Duration |
|---|------|---------|----------|
| 4 | 22 | `[[ -f "${config}" ]]` | <1ms |
| 5 | 23 | `source "${config}"` | 1ms |
### ← `deploy::validate` returned (exit: 0)
### → `deploy::run` (deploy.sh:35)
| # | Line | Command | Duration |
|---|------|---------|----------|
| 6 | 37 | `:args "Run deployment" "${@}"` | <1ms |
| 7 | 40 | `kubectl apply -f "${manifest}"` | 850ms |
<details>
<summary>Variables after :args (line 37)</summary>
| Variable | Value | Type | Source |
|----------|-------|------|--------|
| `manifest` | `"k8s/deploy.yaml"` | file :~file | positional |
</details>
### ← `deploy::run` returned (exit: 0)
### ← `main` returned (exit: 0)
---
## Import Tree
Files loaded via `import` or `source` during execution:
| Module | Path | Loaded at |
|--------|------|-----------|
| `deploy` | `./lib/deploy.sh` | main:4 |
| `utils` | `./lib/utils.sh` | deploy.sh:2 |
---
## Summary
| Metric | Value |
|--------|-------|
| Total steps | 47 |
| Functions called | 5 |
| `:args` parsed | 2 |
| `:usage` dispatched | 1 |
| Imports loaded | 2 |
| Exit code | 0 |
| Wall time | 1.2s |
## Final Variable State
<details>
<summary>All variables at exit</summary>
```bash
declare -- env="staging"
declare -- verbose="0"
declare -- target="all"
declare -- manifest="k8s/deploy.yaml"
```
Implementation plan
Phase 1: Env var (pure bash)
- Intercept
ARGSH_TRACE in :args and :usage — set up DEBUG trap
- DEBUG trap writes markdown to the trace file incrementally
- Track: function entry/exit,
:args/:usage calls, variable state after :args
- EXIT trap writes summary section
- Keep the trace handler lightweight — only write on function boundaries and
:args/:usage, not every command
Phase 2: Binary wrapper (argsh-dap --trace)
- Add
--trace <path> -- <script> [args...] mode to argsh-dap
- Reuse the DEBUG trap prelude (already exists for DAP debugging)
- Run headless: no stdin/stdout DAP protocol, just collect events from FIFO
- After script exits, use
argsh_syntax analysis to enrich the trace:
- Add field type annotations to variable tables
- Build the command tree section from
:usage analysis
- Add import tree from resolver
- Write the final markdown file
Phase 3: Docker fallback
- If
argsh-dap is not on PATH, check if Docker is available
- Forward the trace invocation to the argsh Docker container
- Mount the output path so the trace file lands on the host
Files to change
| File |
Change |
libraries/args.sh |
Detect ARGSH_TRACE env var, set up DEBUG trap |
builtin/src/args.rs |
Same detection in the Rust builtin |
crates/argsh-lsp/src/bin/argsh-dap.rs |
Add --trace headless mode |
docs/development/tools/debugger.mdx |
Document the trace feature |
libraries/main.bats |
Tests for env var trace |
crates/argsh-lsp/tests/dap_integration.rs |
Tests for --trace mode |
Not in scope
- Reverse debugging (see todos/dap-subshell-and-reverse.md)
- Interactive trace (that's the DAP debugger)
- Trace filtering (only trace specific functions) — future enhancement
Summary
Add a process trace feature that generates a structured, readable markdown file documenting a script's execution flow — every function call,
:args/:usagedispatch, variable state, and timing.Two activation methods:
ARGSH_TRACE=/path/out.md myscript deploy --env stagingargsh-dap --trace out.md -- myscript deploy --env stagingIf the binary isn't available locally, the env var path forwards to the Docker container (same pattern as
argsh lint,argsh test, etc.).Use cases
Activation
Method 1: Environment variable (pure bash)
:args/:usage(before user code runs)trap DEBUGhandler that writes markdownMethod 2: Binary wrapper (full analysis)
argsh_syntaxanalysis: field types,:usagetree, import resolutionIf
argsh-dapis not installed, falls back to Docker:docker run --rm -v "$(pwd):/workspace" -w /workspace \ -e ARGSH_TRACE=/workspace/trace.md \ ghcr.io/arg-sh/argsh:latest myscript deploy --env stagingMarkdown output structure
myscript
└── deploy
├── :args "Deploy to environment"
│ ├── env|e:! = "staging" (required flag, --env staging)
│ ├── verbose|v:+ = 0 (boolean flag, default)
│ └── target = "all" (positional, default)
└── :usage "Deploy commands"
└── run → deploy::run
Implementation plan
Phase 1: Env var (pure bash)
ARGSH_TRACEin:argsand:usage— set up DEBUG trap:args/:usagecalls, variable state after:args:args/:usage, not every commandPhase 2: Binary wrapper (argsh-dap --trace)
--trace <path> -- <script> [args...]mode toargsh-dapargsh_syntaxanalysis to enrich the trace::usageanalysisPhase 3: Docker fallback
argsh-dapis not on PATH, check if Docker is availableFiles to change
libraries/args.shARGSH_TRACEenv var, set up DEBUG trapbuiltin/src/args.rscrates/argsh-lsp/src/bin/argsh-dap.rs--traceheadless modedocs/development/tools/debugger.mdxlibraries/main.batscrates/argsh-lsp/tests/dap_integration.rs--tracemodeNot in scope