You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Before: workflow values and conditions are built from the untyped TextExpression tree, with manual Evaluate() and Concat calls and a hand-written context catalogue. For example: TextExpressions.Target.ParamOutput(...).Contains("-").EqualTo(false).
After: everything starts from one entry point, Tex. Conditions are typed BoolExpr values written with C# operators, such as !Tex.ParamOutput(...).Contains("-") and Tex.GitHub.EventName.Eq("push") && Tex.Success(). YAML values are Templates built from interpolated strings, such as $"{Tex.GitHub.Workflow}-{Tex.GitHub.Ref}". The same expressions print as GitHub Actions or Azure DevOps syntax.
This is a breaking change: the TextExpression API is removed. The branch's breaking: commit bumps main to 3.0.0 (the last release is v2.1.0).
How:
Core: adds a typed expression core: Expr with Bool, String, Number and Json roots; the Tex entry point; a Template interpolated-string handler; and an ExpressionPrinter base class.
Platform contexts:Tex.GitHub and Tex.DevOps are C# 14 extension members. The GitHub catalogue is generated by an incremental source generator from Contexts/github.contexts.
GitHub Actions: the model and writer use the new types. Conditions that start with ! are wrapped in ${{ }} so YAML doesn't read them as tags.
Azure DevOps: the model and writer use the new types. References are embedded as macros ($(x)), template expressions (${{ }}) or runtime expressions ($[ ]) by evaluation phase. Combinations Azure DevOps can't evaluate throw NotSupportedException with guidance, instead of writing YAML that misbehaves.
Docs: the README, docs pages, api/index.md and AGENTS.md are rewritten. Runnable samples live in samples/ and are checked by SamplesTests.
Checked:
424 tests pass, and the public API snapshots are accepted. Every docs sample was compiled, and its output was checked against the writers. DocFX builds without warnings.
Atom was ported against locally packed packages (Atom branch claude/project-thread-snwqnb). All Atom tests pass. The Atom 5.0 PR follows once this release candidate is published.
All eleven repos were regenerated with this code and compared against Atom main. The only change is the deliberate one: if: contains(...) == false becomes if: ${{ !contains(...) }}.
This proof of concept introduces the v3 typed expression API and ports GitHub Actions generation to it. Azure DevOps, Dependabot, and documentation migration remain outside this PR’s scope.
Changes:
Adds typed expressions, interpolated templates, and expression printers.
Generates the GitHub context catalogue from a data file.
Migrates GitHub Actions models, writer tests, and public API snapshots.
A typed comparison can start with a string literal: new StringLiteral("main").Eq(GitHubContexts.Ref) prints 'main' == github.ref. The writer leaves this unchanged, producing invalid YAML (if: 'main' == github.ref) because the quoted scalar ends before the comparison. Wrap conditions beginning with quotes as well as !, checking after leading whitespace so raw negated conditions are also protected. Add a writer regression test for a literal-first comparison.
The implicit primitive conversions are declared only on the typed subclasses, so C# does not consider them when the target parameter type is Expr. As a result, Fn.Format("{0}", "hello") and Fn.FromJson("[1]").Contains(1) do not compile; callers must explicitly cast the literals to StringExpr or NumberExpr. Support primitive arguments consistently through conversions on Expr or suitable helper overloads, and add tests that compile these calls.
This assertion fails on Windows even when the input's carriage returns are normalized correctly. StructuredTextWriter.WriteLine uses StringBuilder.AppendLine, so generated lines end with \r\n on Windows. Remove valid CRLF pairs before checking for stray carriage returns.
Keep this core XML summary platform-neutral, as required by AGENTS.md:90–92. Describe platform extensions generically here; the concrete Tex.GitHub example already belongs in Contexts/TexGitHubExtensions.cs in the GitHub Actions package.
- samples/Invex.StructuredText.Samples: conditions, values, references,
functions and escape hatches, and a complete CI workflow. SamplesTests
snapshots each sample's output, and samples/README.md shows it.
- The writer now writes `group:` under `concurrency:` (it was missing).
- Multi-line run steps are always written as a literal block (`run: |`).
After another property they were written as a YAML list, and block lines
were quoted, which changed the script.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UBhDUQU8aoPYyyfUXRi1ct
- Generator: top-level members are keyed by full path, so github.env and the
env context no longer merge. Colliding github.* members are named GitHubEnv
and GitHubJob; Env and Job are the contexts. Duplicate names now report STG002.
- Indexers keep the key whole. The printer writes ['key'] (escaped) where
.key isn't valid, for contexts and for parsed JSON alike.
- PrintCondition checks the first non-whitespace character for '!'.
- Raw text that isn't a plain dotted path has the lowest precedence, so it is
parenthesized under !, && and ||, and still written verbatim on its own.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UBhDUQU8aoPYyyfUXRi1ct
…syntax
- Multi-line values keep their blank lines (only trailing ones are dropped),
in both the literal-block and scalar paths, so here-documents survive.
- Conditions whose first non-whitespace character is YAML syntax (a quote,
!, &, *, [, {, ...) are wrapped in ${{ }}, e.g. 'main' == github.ref.
- As a safety net, Format quotes any value that starts with YAML syntax or has
leading or trailing whitespace.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UBhDUQU8aoPYyyfUXRi1ct
A value with a trailing newline was quoted with the newline still in it,
leaving the closing quote unindented on the next line.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UBhDUQU8aoPYyyfUXRi1ct
…epted
- A first-property run value containing only '\r' line breaks now takes the
literal-block path instead of being written inline.
- Expr has implicit conversions from string, double and bool, so
Fn.Format("{0}", "hello") and FromJson(...).Contains(1) compile.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UBhDUQU8aoPYyyfUXRi1ct
…icit context Path
- A literal block whose first line is indented now gets `|2`, so YAML
doesn't take its indentation from that line and drop the rest.
- `$"flag={true}"` renders `flag=true` instead of an expression.
- Generated context records implement IContextPath.Path explicitly, so a
catalogue member called path compiles; github.event.workflow_run.path
added as the regression case.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UBhDUQU8aoPYyyfUXRi1ct
…nt size
The `- key: |` path places content 2 * IndentSize - 2 in from its key and
the plain `key: |` path IndentSize, so a writer with a custom indent size
gets the right indicator. Tests cover four-space indentation for a script
and an env value, and the CR-only test no longer trips on Windows newlines.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UBhDUQU8aoPYyyfUXRi1ct
- Tex replaces Ref, Status and Fn, and adds Join and Raw, so everything
that creates an expression is one "Tex." away.
- Tex.Raw returns a RawExpr: a condition that also converts to string,
number and JSON expressions (replaces the per-type Raw factories).
- The generated GitHub catalogue is now an instance class, reached
through a Tex.GitHub extension property.
- `using static Invex.StructuredText.Expressions.Tex;` drops the prefix.
Generated output is unchanged; only sample labels moved.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UBhDUQU8aoPYyyfUXRi1ct
Indent size 1 or 6+ would produce |0 or |10 for a block whose first line
is indented; the writer now throws a clear NotSupportedException instead
of writing invalid YAML. Other blocks are unaffected.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UBhDUQU8aoPYyyfUXRi1ct
- DevopsExpressionPrinter replaces DevopsExpressionFormatter. In templates,
secrets, variables, matrix values and step outputs render as $(name),
job outputs and Tex.DevOps.Runtime(...) as $[ ... ], and anything else
as ${{ ... }}. Conditions use and()/eq()/succeeded() and so on.
- Model values are Template, conditions are BoolExpr and lists are
IReadOnlyList<Template>, matching the GitHub Actions package.
- Tex.DevOps adds common predefined variables (Build, System, Agent,
Pipeline.Workspace) and Runtime(...).
- ExpressionPrinter gets a RenderEmbedded hook for platforms with more
than one embedding syntax.
Writer output is unchanged: every pipeline writer test passes as before.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UBhDUQU8aoPYyyfUXRi1ct
Nothing uses TextExpression, TextExpressions, TextExpressionFormatter,
WorkflowExpression<T> or the collection wrappers any more: both writers
and Atom are on Tex, Expr and Template. Their tests go with them; the
typed core is covered by the GitHub Actions and Azure DevOps printer,
writer and sample tests.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UBhDUQU8aoPYyyfUXRi1ct
Rewrites the README, AGENTS.md, api/index.md and the docs pages for Tex, Expr and
Template, and removes the old TextExpression material. Every code sample was compiled
and its output checked against the writers.
Also renames the escape-hatch sample label to Tex.Raw and deletes the orphaned
PublicApiSurfaceTests snapshots (the test class is PublicApiTests).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UBhDUQU8aoPYyyfUXRi1ct
- Templates that read a job output or result, or contain a runtime marker, are
rendered as $[ ... ], including through casts and compound expressions.
- A runtime expression combined with other text throws NotSupportedException,
since Azure DevOps only evaluates $[ ... ] as a whole value.
- join takes the separator first in Azure DevOps, with ',' when none is given.
- Array membership (In, JsonExpr.Contains) prints containsValue in Azure DevOps
and contains in GitHub Actions.
- A runtime marker nested in an expression no longer adds grouping parentheses.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UBhDUQU8aoPYyyfUXRi1ct
- Inputs (parameters) in conditions or runtime expressions throw, since
parameters only exist at compile time. Conditions go through a new
DevopsExpressionPrinter.PrintCondition.
- Secrets, matrix values and step outputs inside a compound compile-time
expression throw; interpolating them directly still writes a macro.
- Names that aren't valid Azure DevOps identifiers (such as dry-run) use
['name'] syntax.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UBhDUQU8aoPYyyfUXRi1ct
A value such as Tex.Format("build-{0}", Tex.DevOps.Build.BuildId) is rendered as ${{ format('build-{0}', variables['Build.BuildId']) }}. Build.BuildId is unavailable during Azure template expansion, so the run ID is lost even though direct interpolation works as a macro. In DevopsExpressionPrinter.RenderEmbedded, compound VariableRef expressions bypass the runtime checks used for secrets, matrix values and step outputs. Preserve runtime availability for these references, or reject unsupported compositions and require Tex.DevOps.Runtime(...). Add a writer regression test using the predefined build ID.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Requested by Dec · project thread
Before: workflow values and conditions are built from the untyped
TextExpressiontree, with manualEvaluate()andConcatcalls and a hand-written context catalogue. For example:TextExpressions.Target.ParamOutput(...).Contains("-").EqualTo(false).After: everything starts from one entry point,
Tex. Conditions are typedBoolExprvalues written with C# operators, such as!Tex.ParamOutput(...).Contains("-")andTex.GitHub.EventName.Eq("push") && Tex.Success(). YAML values areTemplates built from interpolated strings, such as$"{Tex.GitHub.Workflow}-{Tex.GitHub.Ref}". The same expressions print as GitHub Actions or Azure DevOps syntax.This is a breaking change: the
TextExpressionAPI is removed. The branch'sbreaking:commit bumps main to 3.0.0 (the last release is v2.1.0).How:
Exprwith Bool, String, Number and Json roots; theTexentry point; aTemplateinterpolated-string handler; and anExpressionPrinterbase class.Tex.GitHubandTex.DevOpsare C# 14 extension members. The GitHub catalogue is generated by an incremental source generator fromContexts/github.contexts.!are wrapped in${{ }}so YAML doesn't read them as tags.$(x)), template expressions (${{ }}) or runtime expressions ($[ ]) by evaluation phase. Combinations Azure DevOps can't evaluate throwNotSupportedExceptionwith guidance, instead of writing YAML that misbehaves.api/index.mdand AGENTS.md are rewritten. Runnable samples live insamples/and are checked bySamplesTests.Checked:
claude/project-thread-snwqnb). All Atom tests pass. The Atom 5.0 PR follows once this release candidate is published.if: contains(...) == falsebecomesif: ${{ !contains(...) }}.🤖 Generated with Claude Code
https://claude.ai/code/session_01UBhDUQU8aoPYyyfUXRi1ct