Skip to content

feat: process trace — generate markdown execution log (--argsh-trace / ARGSH_TRACE) #99

Description

@fentas

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:

  1. Env var (zero deps): ARGSH_TRACE=/path/out.md myscript deploy --env staging
  2. 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)

  1. Intercept ARGSH_TRACE in :args and :usage — set up DEBUG trap
  2. DEBUG trap writes markdown to the trace file incrementally
  3. Track: function entry/exit, :args/:usage calls, variable state after :args
  4. EXIT trap writes summary section
  5. Keep the trace handler lightweight — only write on function boundaries and :args/:usage, not every command

Phase 2: Binary wrapper (argsh-dap --trace)

  1. Add --trace <path> -- <script> [args...] mode to argsh-dap
  2. Reuse the DEBUG trap prelude (already exists for DAP debugging)
  3. Run headless: no stdin/stdout DAP protocol, just collect events from FIFO
  4. 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
  5. Write the final markdown file

Phase 3: Docker fallback

  1. If argsh-dap is not on PATH, check if Docker is available
  2. Forward the trace invocation to the argsh Docker container
  3. 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

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions