Imported from bircni/actions-visualizer-extension (
AGENTS.md). Install upstream withnpx skills add bircni/actions-visualizer-extension. Copyright stays with the author.
AI Agent Guide for Actions Visualizer
Quick Start Checklist
- Read this file completely before changing anything.
- Understand the project structure below — especially the split between
src/workflow/(pure) andsrc/preview/(VS Code wiring). - Run
npm run validateto confirm you are starting from a clean state. - Make your change under
src/. - Add or update tests in
src/tests/. - Run
npm run validateagain before committing.
Project Context
What This Project Does
- Renders GitHub Actions and Gitea Actions workflow files as a dependency graph in a webview panel beside the YAML, laid out the way GitHub draws a run.
- Parses
on:triggers,jobs:and theirneeds:edges, plus steps, matrices, reusable workflow calls andworkflow_dispatchinputs. - Simulates a run: the user picks an event, a ref and input values, and every job's
if:is evaluated against them so skipped jobs dim in place. - Surfaces graph-level problems the YAML itself will not catch: unresolved
needs:targets and circular dependencies. - Keeps the graph in sync with the editor, and jumps to the source when a job is clicked.
Tech Stack
- TypeScript, strict, with
noUncheckedIndexedAccessandexactOptionalPropertyTypes. Node version is in.node-version. - VS Code Extension API:
WebviewPanel, commands, workspace events. yaml(document API, for source offsets) and@dagrejs/dagre(layered DAG layout).- A hand-written lexer, Pratt parser and evaluator for the GitHub Actions expression language.
- Vitest for unit tests,
@vscode/test-electron+ Mocha for extension-host tests, Playwright for the webview. - esbuild for bundling,
vscefor packaging, oxlint (type-aware) + oxfmt for quality.
Project Structure
src/
extension.ts activate/deactivate, command registration
logger.ts structured logging into the "Actions Visualizer" output channel
workflow/ PURE — no `vscode` import allowed in this directory
detect.ts is this path/document a workflow?
model.ts WorkflowModel and friends
parse.ts YAML -> WorkflowModel, with source ranges
simulate.ts which jobs and steps would run for a given event
playthrough.ts walks a run, replaying the user's decisions
outputs.ts the outputs a step is expected to produce
filters.ts GitHub's `branches:`/`tags:` pattern matching
lint.ts workflow checks reported as editor diagnostics
graph.ts WorkflowModel -> GraphModel (cards + edges, no coordinates)
layout.ts GraphModel -> PositionedGraph (dagre + elbow routing)
expression/ the GitHub Actions expression language
ast.ts node types and the syntax error
parse.ts lexer + Pratt parser
evaluate.ts evaluator, GitHub coercion rules, UNKNOWN propagation
preview/
graphMessage.ts the ONE place the `graph` message is assembled
diagnostics.ts publishes lint findings to the Problems panel
previewController.ts PURE — all VS Code calls arrive as injected lambdas
previewPanel.ts the single panel, and the only place that touches
WebviewPanel and workspace events
previewConfig.ts reads and clamps `actions-visualizer.*` settings
previewTestBridge.ts `__test.*` hook for extension-host tests
webview/
content.html single file: styles, markup and the inline SVG renderer script
content.ts loads the template and fills its placeholders
tests/*.test.ts unit tests
e2e/ extension-host and Playwright launcher tests
test/graph.browser.test.js the Playwright spec itself
.fixtures/workflows/*.yml sample workflows used by tests
Other directories: dist/ is build output — do not edit. scripts/ holds build and release
helpers. assets/ holds the icon.
Available Commands
# Build and development
npm run compile # type-check only (emits declarations)
npm run bundle # esbuild -> dist/extension.js
npm run copy:webview # copy content.html and codicons into dist/webview
npm run build # compile + bundle + copy:webview + package
npm run watch
# Testing
npm test # unit tests
npm run test:coverage # unit tests with coverage thresholds
npm run test:e2e # extension host + Playwright webview tests
# Quality
npm run lint
npm run format
npm run check-unused
# Release
npm run release # git-cliff changelog, version bump, tag
Critical Rules for AI Agents
1. File Modification Boundaries
Do: modify files under src/; add or update tests in src/tests/*.test.ts; update package.json
for dependencies, scripts and contribution points; update README.md when user-facing behaviour or
a setting changes.
Do not: edit dist/; change .vscodeignore or build config without a reason; edit CHANGELOG.md
by hand — it is generated by the release process.
2. The Graph Is Cards, Not Nodes
GitHub does not draw one box per job, and neither do we. Jobs at the same depth that share the same
needs: become rows in one card; matrix jobs get their own card with a tab. Triggers are not
nodes — they live in the header as clickable chips. Keep it that way: it is the thing that makes the
preview recognisable as a workflow graph rather than a generic DAG.
3. Unknown Is A First-Class Result
The expression evaluator has three outcomes, not two: true, false and UNKNOWN. A static preview
cannot know secrets, steps or a job's outputs, and guessing would be worse than admitting it.
UNKNOWN propagates through every operation except a short circuit that is already decided
(false && secrets.X is false). Never collapse it to false for convenience.
The user can pin a value for any unresolved path, which is why unresolvedReferences exists. Note
the distinction it rests on: a property of a {@link PartialRecord} we did not model is unknown,
while a missing key of a fully known object is absent. That is what makes
needs.build.outputs.declared pinnable and needs.build.outputs.typo decidably false.
Contexts are also scoped. GitHub gives a job-level if: only github, needs, vars and
inputs; a step-level one additionally gets env, matrix, job, runner, steps and
strategy. buildContexts takes a scope for exactly this reason — do not widen it for convenience,
and lint.ts reports the mistake when a workflow gets it wrong.
4. Layout Runs In The Host, Not The Webview
This is the load-bearing architectural decision. src/workflow/layout.ts runs dagre in the extension
host and posts a fully positioned graph to the webview, which only draws it. Keep it that way:
- The webview must never compute layout. If a card's size changes, the host recomputes and re-posts.
METRICSinlayout.tsmust stay in sync with the geometry incontent.html. The renderer draws from the width and height it is given rather than measuring text.- This is why layout is testable as plain Node code, with no browser and no second bundle.
5. Playthrough Stores Decisions, Not State
playthrough.ts keeps only the ordered list of decisions the user has made and replays them to
derive everything else. Do not add incremental mutation: undo is decisions.slice(0, -1), restart is
an empty list, and both stay correct only while replay is the single source of truth.
The walk stops at the first step that would run and has no decision yet. Everything after it is
pending, because what happens next genuinely depends on the answer.
buildContexts takes optional RunFacts and evaluateCondition an optional EvaluationRuntime.
Both default to the static behaviour, so the preview without a playthrough is byte-for-byte what it
was. Keep those defaults.
6. One Graph Message, One Builder
buildGraphMessage in preview/graphMessage.ts is the only place the graph message is assembled.
The controller, the JSDOM harness and scripts/build-browser-fixture.ts all call it. They used to
each build their own, and a new field meant editing three places with nothing to catch the one you
missed — do not reintroduce that.
7. One Panel, Following The Editor
There is exactly one preview panel. onDidChangeActiveTextEditor retargets it when the user switches
to another workflow, and leaves it alone for anything else — the Markdown-preview model. Retargeting
rebuilds the controller rather than reusing it, because expansion and simulation state belong to the
workflow being shown; carrying a previous file's selected event across would be wrong. Any pending
debounced render is dropped at the same time so it cannot fire against the new document.
8. Purity Boundaries
src/workflow/** and src/preview/previewController.ts must not import * as vscode. Every VS Code
API the controller needs arrives through PreviewDeps as a lambda. previewPanel.ts is the single
place that constructs those lambdas. When you need a new VS Code capability in the controller, add a
field to PreviewDeps — do not import vscode.
9. Parsing Must Never Throw
parseWorkflow is called on every keystroke against a file that is usually half-written. A YAML
syntax error becomes fatalError; anything merely unexpected becomes a diagnostic. Never throw,
and never assume a key has the shape the schema says it does — needs: can be a string or an array,
runs-on: can be a string, an array or a map, on: can be all three.
10. Testing
Tests live in src/tests/. Unit-test the pure modules directly; use the vi.mock("vscode", ...)
pattern from previewPanel.test.ts and extension.test.ts for the wiring; use the JSDOM harness in
webview.harness.test.ts for the renderer. Coverage thresholds are enforced (80/75/80/80).
Add a fixture to .fixtures/workflows/ when you need a new workflow shape, and reuse the existing
ones otherwise. The Playwright payloads come from scripts/build-browser-fixture.ts, which runs the
real pipeline — never hand-write a graph shape in the browser spec.
Colours must come in matched pairs. --vscode-badge-background goes with
--vscode-badge-foreground, not with --vscode-textLink-foreground. Pairing a background token with
an unrelated foreground token has produced unreadable text twice; the browser spec now asserts a WCAG
contrast ratio on the header in both default themes.
11. Code Style
TypeScript strict, no any. Types are declared with type, not interface. Reuse existing
primitives (WorkflowModel, GraphModel, PositionedGraph, parseWorkflow, buildGraph,
layoutGraph, simulateJobs, evaluateCondition, getInitialHtml) rather than introducing
parallel ones. PascalCase for classes,
camelCase for functions and variables.
12. Git and Commits
Conventional Commits, scoped by subarea: feat(graph): …, fix(parse): …, feat(webview): …,
chore(deps): ….
13. Validate Before Committing
npm run validate must pass. Fix every failure before committing.
Common Tasks and Patterns
| Task | Where |
|---|---|
| Support a new workflow key | workflow/parse.ts (extract it), workflow/model.ts (type it), workflow/graph.ts (surface it on a row) |
| Change how a card or row looks | workflow/graph.ts for its content, workflow/layout.ts METRICS for its size, webview/content.html for its drawing |
| Change how jobs group into cards | the card-id logic in workflow/graph.ts |
| Add an expression function or context | workflow/expression/evaluate.ts (callFunction) or workflow/simulate.ts (buildContexts) |
| Change what the simulation knows | workflow/simulate.ts; anything unknowable must stay UNKNOWN |
| Add a new graph-level warning | workflow/graph.ts, pushed onto warnings |
| Add a setting | contributes.configuration in package.json, then preview/previewConfig.ts, then thread it through previewController.ts |
| Add a webview interaction | content.html posts a message, previewController.ts handles it in handleMessage |
| Change layout direction or spacing | workflow/layout.ts METRICS |
Definition of Done
-
npm run compilepasses -
npm testpasses - Tests added or updated for the change
-
npm run lintpasses -
npm run formatpasses -
npm run validatepasses - Conventional Commit message
- Change is focused and minimal
Quick Reference
- Source:
src/ - Tests:
src/tests/*.test.ts - Fixtures:
.fixtures/workflows/ - Build output:
dist/— do not edit - Commit format:
feat(graph): render matrix combinations
When in doubt, follow existing patterns in the codebase.