The DAP debugger needs to map a runtime IStep back to a source line
when answering `stackTrace` requests. The renderer (#PR1b) produces
the YAML and per-entry start lines from an immutable list, but the
debugger's view grows over the job's lifetime: post steps register
lazily, and the integration layer needs O(1) IStep -> line lookup
at every pause.
This commit adds JobExecutionView, a stateful append-only wrapper
around the renderer. It maintains:
- the current entry list,
- the most recent rendered YAML,
- a Dictionary<IStep, int> for fast line lookup.
Each Append can register an entry in one of three modes:
- with a stepIdentity: registers the IStep -> line mapping
immediately;
- with a matchKey: registers an unclaimed placeholder that a
later TryClaim binds to a real IStep (used when an entry is
predicted before the runner materializes its IStep, e.g. a
Post-step placeholder synthesized at job-init from an action's
metadata);
- with neither: a static informational entry that needs no line
lookup.
This is part 3 of 5. The DAP-integration PR that consumes this
container is the final follow-up.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The DAP debugger serves a synthesized YAML document as the job's
`source`. That document is a 1:1 representation of how the runner
sees the job — not the workflow file — so pre and post action steps
appear as their own 'lines' that the user can pause on (and
eventually breakpoint, set in a follow-up PR).
This commit adds the core rendering algorithm: given a list of
phase-tagged entries (`JobExecutionViewEntry`), produce the
phase-keyed YAML plus a parallel array of 1-based line numbers
pointing at each entry's `- step:` key. The line numbers are what
later powers the DAP `stackTrace` handler.
Why hand-emit the skeleton instead of serializing a DTO?
Per-entry line offsets must be tracked at emission time. Using a
generic YAML serializer would force a second pass to scan the
output for `- step:` lines, which is fragile and breaks the moment
indentation conventions shift. Scalar values still go through the
library (via YamlScalarFormatter from #PR1a), so we don't carry
quoting rules.
Example output for a typical job (build, build, post step):
# Job: build
# Runner execution plan — read-only.
setup:
- step: Setup job
main:
- step: Run actions/checkout@v6
uses: actions/checkout@v6
if: success()
- step: Cache Primes
id: cache-primes
uses: actions/cache@v5
if: success()
with:
path: prime-numbers
key: ${{ runner.os }}-primes
post:
- step: Post Cache Primes
action: actions/cache@v5
cleanup:
- step: Complete job
This is part 2 of 5 splitting the previously-monolithic foundation
for review tractability. The wiring that turns runner state into
these entries lives in the next PRs.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Remove the redundant second `TrimEnd('\n')` from the return path.
The earlier trim already removes any trailing newline before the
`\n...` doc-end check; the marker-removal substring does not
re-introduce one, so the second trim was dead code.
- Surface full exception (`ex.ToString()`) in the test round-trip
helper so YAML parse failures show stack + inner exception, not
just the top-level message.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The upcoming DAP execution-view renderer serves a synthesized YAML
document as the job's debugger source. The skeleton is hand-emitted
so we can track per-step line offsets, but scalar values (step names,
action refs, etc.) need quote-safe formatting that respects YAML's
reserved chars, leading/trailing whitespace, and embedded `: `/`#`
sequences. Doing this by hand is bug-prone and easy to get wrong on
edge cases (empty strings, expressions, multiline content).
This commit adds a thin wrapper around YamlDotNet's `Emitter` that
emits a single scalar, strips the surrounding document markers, and
forces LF line breaks (`StringWriter` otherwise picks up Windows's
CRLF via `Environment.NewLine` and corrupts the document-end
stripping).
No caller yet — the renderer that uses it lands in a follow-up PR.
This is part 1 of 5 splitting the previously-monolithic foundation
for review tractability.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>