@intentius/chant-lexicon-github 0.0.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (106) hide show
  1. package/dist/integrity.json +31 -0
  2. package/dist/manifest.json +15 -0
  3. package/dist/meta.json +135 -0
  4. package/dist/rules/deprecated-action-version.ts +49 -0
  5. package/dist/rules/detect-secrets.ts +53 -0
  6. package/dist/rules/extract-inline-structs.ts +62 -0
  7. package/dist/rules/file-job-limit.ts +49 -0
  8. package/dist/rules/gha006.ts +58 -0
  9. package/dist/rules/gha009.ts +42 -0
  10. package/dist/rules/gha011.ts +40 -0
  11. package/dist/rules/gha017.ts +32 -0
  12. package/dist/rules/gha018.ts +40 -0
  13. package/dist/rules/gha019.ts +72 -0
  14. package/dist/rules/job-timeout.ts +59 -0
  15. package/dist/rules/missing-recommended-inputs.ts +61 -0
  16. package/dist/rules/no-hardcoded-secrets.ts +46 -0
  17. package/dist/rules/no-raw-expressions.ts +51 -0
  18. package/dist/rules/suggest-cache.ts +71 -0
  19. package/dist/rules/use-condition-builders.ts +45 -0
  20. package/dist/rules/use-matrix-builder.ts +44 -0
  21. package/dist/rules/use-typed-actions.ts +47 -0
  22. package/dist/rules/validate-concurrency.ts +66 -0
  23. package/dist/rules/yaml-helpers.ts +129 -0
  24. package/dist/skills/chant-github.md +29 -0
  25. package/dist/skills/github-actions-patterns.md +93 -0
  26. package/dist/types/index.d.ts +358 -0
  27. package/package.json +33 -0
  28. package/src/codegen/docs-cli.ts +3 -0
  29. package/src/codegen/docs.ts +1138 -0
  30. package/src/codegen/generate-cli.ts +36 -0
  31. package/src/codegen/generate-lexicon.ts +58 -0
  32. package/src/codegen/generate-typescript.ts +149 -0
  33. package/src/codegen/generate.ts +141 -0
  34. package/src/codegen/naming.ts +57 -0
  35. package/src/codegen/package.ts +65 -0
  36. package/src/codegen/parse.ts +700 -0
  37. package/src/codegen/patches.ts +46 -0
  38. package/src/composites/cache.ts +25 -0
  39. package/src/composites/checkout.ts +31 -0
  40. package/src/composites/composites.test.ts +675 -0
  41. package/src/composites/deploy-environment.ts +77 -0
  42. package/src/composites/docker-build.ts +120 -0
  43. package/src/composites/download-artifact.ts +24 -0
  44. package/src/composites/go-ci.ts +91 -0
  45. package/src/composites/index.ts +26 -0
  46. package/src/composites/node-ci.ts +71 -0
  47. package/src/composites/node-pipeline.ts +151 -0
  48. package/src/composites/python-ci.ts +92 -0
  49. package/src/composites/setup-go.ts +24 -0
  50. package/src/composites/setup-node.ts +26 -0
  51. package/src/composites/setup-python.ts +24 -0
  52. package/src/composites/upload-artifact.ts +27 -0
  53. package/src/coverage.ts +49 -0
  54. package/src/expression.test.ts +147 -0
  55. package/src/expression.ts +214 -0
  56. package/src/generated/index.d.ts +358 -0
  57. package/src/generated/index.ts +29 -0
  58. package/src/generated/lexicon-github.json +135 -0
  59. package/src/generated/runtime.ts +4 -0
  60. package/src/import/generator.test.ts +110 -0
  61. package/src/import/generator.ts +119 -0
  62. package/src/import/parser.test.ts +98 -0
  63. package/src/import/parser.ts +73 -0
  64. package/src/index.ts +53 -0
  65. package/src/lint/post-synth/gha006.ts +58 -0
  66. package/src/lint/post-synth/gha009.ts +42 -0
  67. package/src/lint/post-synth/gha011.ts +40 -0
  68. package/src/lint/post-synth/gha017.ts +32 -0
  69. package/src/lint/post-synth/gha018.ts +40 -0
  70. package/src/lint/post-synth/gha019.ts +72 -0
  71. package/src/lint/post-synth/post-synth.test.ts +318 -0
  72. package/src/lint/post-synth/yaml-helpers.ts +129 -0
  73. package/src/lint/rules/data/deprecated-versions.ts +13 -0
  74. package/src/lint/rules/data/known-actions.ts +13 -0
  75. package/src/lint/rules/data/recommended-inputs.ts +10 -0
  76. package/src/lint/rules/data/secret-patterns.ts +31 -0
  77. package/src/lint/rules/deprecated-action-version.ts +49 -0
  78. package/src/lint/rules/detect-secrets.ts +53 -0
  79. package/src/lint/rules/extract-inline-structs.ts +62 -0
  80. package/src/lint/rules/file-job-limit.ts +49 -0
  81. package/src/lint/rules/index.ts +17 -0
  82. package/src/lint/rules/job-timeout.ts +59 -0
  83. package/src/lint/rules/missing-recommended-inputs.ts +61 -0
  84. package/src/lint/rules/no-hardcoded-secrets.ts +46 -0
  85. package/src/lint/rules/no-raw-expressions.ts +51 -0
  86. package/src/lint/rules/rules.test.ts +365 -0
  87. package/src/lint/rules/suggest-cache.ts +71 -0
  88. package/src/lint/rules/use-condition-builders.ts +45 -0
  89. package/src/lint/rules/use-matrix-builder.ts +44 -0
  90. package/src/lint/rules/use-typed-actions.ts +47 -0
  91. package/src/lint/rules/validate-concurrency.ts +66 -0
  92. package/src/lsp/completions.test.ts +9 -0
  93. package/src/lsp/completions.ts +20 -0
  94. package/src/lsp/hover.test.ts +9 -0
  95. package/src/lsp/hover.ts +38 -0
  96. package/src/package-cli.ts +42 -0
  97. package/src/plugin.test.ts +128 -0
  98. package/src/plugin.ts +408 -0
  99. package/src/serializer.test.ts +270 -0
  100. package/src/serializer.ts +383 -0
  101. package/src/skills/github-actions-patterns.md +93 -0
  102. package/src/spec/fetch.ts +55 -0
  103. package/src/validate-cli.ts +19 -0
  104. package/src/validate.test.ts +12 -0
  105. package/src/validate.ts +32 -0
  106. package/src/variables.ts +44 -0
@@ -0,0 +1,1138 @@
1
+ /**
2
+ * Documentation generation for GitHub Actions lexicon.
3
+ */
4
+
5
+ import { dirname, join } from "path";
6
+ import { fileURLToPath } from "url";
7
+ import { docsPipeline, writeDocsSite, type DocsConfig } from "@intentius/chant/codegen/docs";
8
+
9
+ function serviceFromType(resourceType: string): string {
10
+ const parts = resourceType.split("::");
11
+ return parts.length >= 2 ? parts[1] : "Actions";
12
+ }
13
+
14
+ const overview = `The **GitHub Actions** lexicon provides typed constructors for GitHub Actions
15
+ workflow configuration. It covers workflows, jobs, steps, triggers, strategy,
16
+ permissions, concurrency, and more.
17
+
18
+ Install it with:
19
+
20
+ \`\`\`bash
21
+ npm install --save-dev @intentius/chant-lexicon-github
22
+ \`\`\`
23
+
24
+ ## Quick Start
25
+
26
+ {{file:docs-snippets/src/quickstart.ts}}
27
+
28
+ The lexicon provides **2 resources** (Workflow, Job), **13 composites** (Checkout, SetupNode, SetupGo, SetupPython, CacheAction, UploadArtifact, DownloadArtifact, NodeCI, NodePipeline, PythonCI, DockerBuild, DeployEnvironment, GoCI) + **3 presets** (BunPipeline, PnpmPipeline, YarnPipeline), a typed **Expression** system with 24 GitHub and 5 Runner context variables, and **13 lint rules** + **6 post-synth checks**.
29
+ `;
30
+
31
+ const outputFormat = `The GitHub Actions lexicon serializes resources into **\`.github/workflows/*.yml\`** YAML files.
32
+ Keys use kebab-case for job properties and snake_case for trigger event names.
33
+
34
+ ## Building
35
+
36
+ Run \`chant build\` to produce workflow YAML from your declarations:
37
+
38
+ \`\`\`bash
39
+ chant build src/ --output .github/workflows/ci.yml
40
+ # Or build all workflow files
41
+ chant build
42
+ \`\`\`
43
+
44
+ The generated file includes:
45
+
46
+ - \`name:\` — workflow display name
47
+ - \`on:\` — trigger events (push, pull_request, schedule, workflow_dispatch, etc.)
48
+ - \`permissions:\` — workflow-level GITHUB_TOKEN permissions
49
+ - \`jobs:\` — job definitions with kebab-case keys
50
+
51
+ ## Key conversions
52
+
53
+ | Chant (TypeScript) | YAML output | Rule |
54
+ |--------------------|-------------|------|
55
+ | \`export const buildApp = new Job({...})\` | \`jobs: build-app:\` | Export name → kebab-case job key |
56
+ | \`"runs-on": "ubuntu-latest"\` | \`runs-on: ubuntu-latest\` | Property names match GitHub spec |
57
+ | \`timeoutMinutes: 15\` | \`timeout-minutes: 15\` | camelCase → kebab-case for job properties |
58
+ | \`new Step({ uses: "actions/checkout@v4" })\` | \`- uses: actions/checkout@v4\` | Steps serialize as sequence entries |
59
+
60
+ ## Validating locally
61
+
62
+ The output is standard GitHub Actions YAML. Validate locally with \`act\` or push to GitHub:
63
+
64
+ \`\`\`bash
65
+ # Using act for local testing
66
+ act -W .github/workflows/ci.yml
67
+
68
+ # Using GitHub's workflow validation (requires push)
69
+ git add .github/workflows/ci.yml
70
+ git push
71
+ \`\`\`
72
+
73
+ ## Compatibility
74
+
75
+ The output is compatible with:
76
+ - GitHub Actions (any GitHub.com or GitHub Enterprise Server)
77
+ - \`act\` local runner
78
+ - VS Code GitHub Actions extension
79
+ - Any tool that processes \`.github/workflows/*.yml\` files`;
80
+
81
+ /**
82
+ * Generate documentation for the GitHub Actions lexicon.
83
+ */
84
+ export async function generateDocs(opts?: { verbose?: boolean }): Promise<void> {
85
+ const pkgDir = dirname(dirname(dirname(fileURLToPath(import.meta.url))));
86
+
87
+ const config: DocsConfig = {
88
+ name: "github",
89
+ displayName: "GitHub Actions",
90
+ description: "Typed constructors for GitHub Actions workflow configuration",
91
+ distDir: join(pkgDir, "dist"),
92
+ outDir: join(pkgDir, "docs"),
93
+ overview,
94
+ outputFormat,
95
+ serviceFromType,
96
+ suppressPages: ["intrinsics", "rules"],
97
+ examplesDir: join(pkgDir, "examples"),
98
+ extraPages: [
99
+ {
100
+ slug: "getting-started",
101
+ title: "Getting Started",
102
+ description: "Step-by-step guide to building your first GitHub Actions workflow with chant",
103
+ content: `## 1. Install
104
+
105
+ \`\`\`bash
106
+ mkdir my-project && cd my-project
107
+ npm init -y
108
+ npm install --save-dev @intentius/chant @intentius/chant-lexicon-github typescript
109
+ \`\`\`
110
+
111
+ ## 2. Create your workflow
112
+
113
+ Create \`src/ci.ts\`:
114
+
115
+ {{file:docs-snippets/src/quickstart.ts}}
116
+
117
+ ## 3. Build
118
+
119
+ \`\`\`bash
120
+ chant build src/ --output .github/workflows/ci.yml
121
+ \`\`\`
122
+
123
+ This produces \`.github/workflows/ci.yml\`:
124
+
125
+ \`\`\`yaml
126
+ name: CI
127
+ on:
128
+ push:
129
+ branches:
130
+ - main
131
+ pull_request:
132
+ branches:
133
+ - main
134
+ permissions:
135
+ contents: read
136
+ jobs:
137
+ build:
138
+ runs-on: ubuntu-latest
139
+ timeout-minutes: 15
140
+ steps:
141
+ - name: Checkout
142
+ uses: actions/checkout@v4
143
+ - name: Setup Node.js
144
+ uses: actions/setup-node@v4
145
+ with:
146
+ node-version: '22'
147
+ cache: npm
148
+ - name: Install
149
+ run: npm ci
150
+ - name: Build
151
+ run: npm run build
152
+ - name: Test
153
+ run: npm test
154
+ \`\`\`
155
+
156
+ ## 4. Lint
157
+
158
+ \`\`\`bash
159
+ chant lint src/
160
+ \`\`\`
161
+
162
+ Lint checks for common issues — missing timeouts, hardcoded secrets, raw expression strings, and more. See the [Lint Rules](/chant/lexicons/github/lint-rules/) page for the full list.
163
+
164
+ ## 5. Deploy
165
+
166
+ Commit the generated YAML and push:
167
+
168
+ \`\`\`bash
169
+ git add .github/workflows/ci.yml
170
+ git commit -m "Add CI workflow"
171
+ git push
172
+ \`\`\`
173
+
174
+ GitHub automatically picks up any \`.github/workflows/*.yml\` files and runs them on the configured triggers.
175
+
176
+ ## Next steps
177
+
178
+ - [Workflow Concepts](/chant/lexicons/github/workflow-concepts/) — resource types, triggers, permissions
179
+ - [Expressions](/chant/lexicons/github/expressions/) — typed expression system and condition helpers
180
+ - [Composites](/chant/lexicons/github/composites/) — pre-built action wrappers (Checkout, SetupNode, etc.)
181
+ - [Lint Rules](/chant/lexicons/github/lint-rules/) — 13 lint rules and 6 post-synth checks`,
182
+ },
183
+ {
184
+ slug: "workflow-concepts",
185
+ title: "Workflow Concepts",
186
+ description: "Resource types, triggers, jobs, steps, permissions, and strategy in the GitHub Actions lexicon",
187
+ content: `## Resource types
188
+
189
+ The lexicon provides 2 resource types and several property types:
190
+
191
+ ### Resources
192
+
193
+ | Type | Description |
194
+ |------|-------------|
195
+ | \`Workflow\` | Top-level workflow configuration — name, triggers, permissions, concurrency |
196
+ | \`Job\` | A job within a workflow — runs-on, steps, strategy, needs, outputs |
197
+
198
+ ### Property types
199
+
200
+ | Type | Used in | Description |
201
+ |------|---------|-------------|
202
+ | \`Step\` | Job | A single step — run command or action usage |
203
+ | \`Strategy\` | Job | Matrix strategy for parallel job execution |
204
+ | \`Permissions\` | Workflow, Job | GITHUB_TOKEN permission scopes |
205
+ | \`Concurrency\` | Workflow, Job | Concurrency group and cancel-in-progress settings |
206
+ | \`PushTrigger\` | Workflow (on) | Push event trigger with branch/tag/path filters |
207
+ | \`PullRequestTrigger\` | Workflow (on) | Pull request event trigger with filters |
208
+ | \`ScheduleTrigger\` | Workflow (on) | Cron-based schedule trigger |
209
+ | \`WorkflowDispatchTrigger\` | Workflow (on) | Manual dispatch with typed inputs |
210
+ | \`Environment\` | Job | Deployment environment with protection rules |
211
+ | \`Output\` | Job | Job output values for downstream jobs |
212
+
213
+ ## Triggers
214
+
215
+ Triggers define when a workflow runs. Pass them in the \`on:\` field:
216
+
217
+ \`\`\`typescript
218
+ import { Workflow } from "@intentius/chant-lexicon-github";
219
+
220
+ export const workflow = new Workflow({
221
+ name: "CI",
222
+ on: {
223
+ push: { branches: ["main", "release/*"] },
224
+ pull_request: {
225
+ branches: ["main"],
226
+ types: ["opened", "synchronize"],
227
+ },
228
+ schedule: [{ cron: "0 0 * * 1" }], // Weekly on Monday
229
+ workflow_dispatch: { // Manual trigger
230
+ inputs: {
231
+ environment: {
232
+ description: "Deploy target",
233
+ required: true,
234
+ type: "choice",
235
+ options: ["staging", "production"],
236
+ },
237
+ },
238
+ },
239
+ },
240
+ });
241
+ \`\`\`
242
+
243
+ ## Jobs and steps
244
+
245
+ Each exported \`Job\` becomes a job entry under \`jobs:\`. Steps run sequentially within a job:
246
+
247
+ \`\`\`typescript
248
+ import { Job, Step, Checkout, SetupNode } from "@intentius/chant-lexicon-github";
249
+
250
+ export const test = new Job({
251
+ "runs-on": "ubuntu-latest",
252
+ timeoutMinutes: 10,
253
+ steps: [
254
+ Checkout({}).step,
255
+ SetupNode({ nodeVersion: "22", cache: "npm" }).step,
256
+ new Step({ name: "Install", run: "npm ci" }),
257
+ new Step({ name: "Test", run: "npm test" }),
258
+ ],
259
+ });
260
+ \`\`\`
261
+
262
+ Key job properties:
263
+ - \`runs-on\` — **required**. Runner label (\`ubuntu-latest\`, \`macos-latest\`, \`windows-latest\`).
264
+ - \`steps\` — **required**. Array of \`Step\` objects.
265
+ - \`timeoutMinutes\` — maximum job duration (recommended, flagged by GHA014 if missing).
266
+ - \`needs\` — job dependencies for execution ordering.
267
+ - \`if\` — conditional execution using Expressions.
268
+ - \`strategy\` — matrix builds for parallel execution.
269
+
270
+ ## Matrix strategy
271
+
272
+ Run a job across multiple configurations:
273
+
274
+ \`\`\`typescript
275
+ import { Job, Step, Checkout, SetupNode } from "@intentius/chant-lexicon-github";
276
+
277
+ export const test = new Job({
278
+ "runs-on": "ubuntu-latest",
279
+ strategy: {
280
+ matrix: {
281
+ "node-version": ["18", "20", "22"],
282
+ os: ["ubuntu-latest", "macos-latest"],
283
+ },
284
+ "fail-fast": false,
285
+ },
286
+ steps: [
287
+ Checkout({}).step,
288
+ SetupNode({ nodeVersion: "\${{ matrix.node-version }}", cache: "npm" }).step,
289
+ new Step({ name: "Test", run: "npm test" }),
290
+ ],
291
+ });
292
+ \`\`\`
293
+
294
+ ## Permissions
295
+
296
+ Control GITHUB_TOKEN permissions at the workflow or job level:
297
+
298
+ \`\`\`typescript
299
+ import { Workflow, Job, Step } from "@intentius/chant-lexicon-github";
300
+
301
+ // Workflow-level (applies to all jobs)
302
+ export const workflow = new Workflow({
303
+ name: "Release",
304
+ on: { push: { tags: ["v*"] } },
305
+ permissions: {
306
+ contents: "write",
307
+ packages: "write",
308
+ "id-token": "write",
309
+ },
310
+ });
311
+
312
+ // Job-level (overrides workflow permissions for this job)
313
+ export const publish = new Job({
314
+ "runs-on": "ubuntu-latest",
315
+ permissions: { contents: "read", packages: "write" },
316
+ steps: [
317
+ new Step({ name: "Publish", run: "npm publish" }),
318
+ ],
319
+ });
320
+ \`\`\`
321
+
322
+ Available permission scopes: \`actions\`, \`checks\`, \`contents\`, \`deployments\`, \`id-token\`, \`issues\`, \`packages\`, \`pages\`, \`pull-requests\`, \`repository-projects\`, \`security-events\`, \`statuses\`. Values: \`"read"\`, \`"write"\`, \`"none"\`.
323
+
324
+ ## Concurrency
325
+
326
+ Prevent concurrent runs of the same workflow or job:
327
+
328
+ \`\`\`typescript
329
+ import { Workflow } from "@intentius/chant-lexicon-github";
330
+
331
+ export const workflow = new Workflow({
332
+ name: "Deploy",
333
+ on: { push: { branches: ["main"] } },
334
+ concurrency: {
335
+ group: "deploy-\${{ github.ref }}",
336
+ "cancel-in-progress": true,
337
+ },
338
+ });
339
+ \`\`\`
340
+
341
+ ## Job dependencies
342
+
343
+ Use \`needs\` to order jobs and pass outputs between them:
344
+
345
+ \`\`\`typescript
346
+ import { Job, Step } from "@intentius/chant-lexicon-github";
347
+
348
+ export const build = new Job({
349
+ "runs-on": "ubuntu-latest",
350
+ outputs: { version: "\${{ steps.version.outputs.value }}" },
351
+ steps: [
352
+ new Step({
353
+ id: "version",
354
+ name: "Get version",
355
+ run: 'echo "value=$(node -p \\"require(\'./package.json\').version\\")" >> $GITHUB_OUTPUT',
356
+ }),
357
+ ],
358
+ });
359
+
360
+ export const deploy = new Job({
361
+ "runs-on": "ubuntu-latest",
362
+ needs: ["build"],
363
+ steps: [
364
+ new Step({
365
+ name: "Deploy",
366
+ run: "echo Deploying version \${{ needs.build.outputs.version }}",
367
+ }),
368
+ ],
369
+ });
370
+ \`\`\``,
371
+ },
372
+ {
373
+ slug: "expressions",
374
+ title: "Expressions",
375
+ description: "Typed expression system, condition helpers, and utility functions for GitHub Actions",
376
+ content: `The Expression system provides type-safe access to GitHub Actions \`\${{ }}\` expressions. Instead of writing raw strings, use the typed helpers for better IDE support and lint coverage.
377
+
378
+ {{file:docs-snippets/src/expressions-usage.ts}}
379
+
380
+ ## Expression class
381
+
382
+ The \`Expression\` class wraps a raw expression string and provides operator methods:
383
+
384
+ | Method | Example | Result |
385
+ |--------|---------|--------|
386
+ | \`.and(other)\` | \`github.ref.eq("refs/heads/main").and(isPR)\` | \`\${{ github.ref == 'refs/heads/main' && ... }}\` |
387
+ | \`.or(other)\` | \`isMain.or(isDev)\` | \`\${{ ... \\|\\| ... }}\` |
388
+ | \`.not()\` | \`isPR.not()\` | \`\${{ !(...) }}\` |
389
+ | \`.eq(value)\` | \`github.ref.eq("refs/heads/main")\` | \`\${{ github.ref == 'refs/heads/main' }}\` |
390
+ | \`.ne(value)\` | \`github.eventName.ne("schedule")\` | \`\${{ github.event_name != 'schedule' }}\` |
391
+
392
+ ## Context accessors
393
+
394
+ The \`github\` and \`runner\` objects provide typed access to context properties:
395
+
396
+ \`\`\`typescript
397
+ import { github, runner } from "@intentius/chant-lexicon-github";
398
+
399
+ github.ref // ${{ github.ref }}
400
+ github.sha // ${{ github.sha }}
401
+ github.actor // ${{ github.actor }}
402
+ runner.os // ${{ runner.os }}
403
+ runner.arch // ${{ runner.arch }}
404
+ \`\`\`
405
+
406
+ See [Variables](/chant/lexicons/github/variables/) for the full reference table.
407
+
408
+ ## Dynamic context accessors
409
+
410
+ Access dynamic context values — secrets, matrix, step outputs, job outputs, inputs, vars, env:
411
+
412
+ \`\`\`typescript
413
+ import { secrets, matrix, steps, needs, inputs, vars, env } from "@intentius/chant-lexicon-github";
414
+
415
+ secrets("NPM_TOKEN") // ${{ secrets.NPM_TOKEN }}
416
+ matrix("node-version") // ${{ matrix.node-version }}
417
+ steps("build").outputs("path") // ${{ steps.build.outputs.path }}
418
+ needs("build").outputs("version") // ${{ needs.build.outputs.version }}
419
+ inputs("environment") // ${{ inputs.environment }}
420
+ vars("API_URL") // ${{ vars.API_URL }}
421
+ env("NODE_ENV") // ${{ env.NODE_ENV }}
422
+ \`\`\`
423
+
424
+ ## Condition helpers
425
+
426
+ Status check functions for job and step \`if:\` fields:
427
+
428
+ | Function | Expression | Description |
429
+ |----------|-----------|-------------|
430
+ | \`always()\` | \`\${{ always() }}\` | Always run, regardless of status |
431
+ | \`failure()\` | \`\${{ failure() }}\` | Run only if a previous step failed |
432
+ | \`success()\` | \`\${{ success() }}\` | Run only if all previous steps succeeded (default) |
433
+ | \`cancelled()\` | \`\${{ cancelled() }}\` | Run only if the workflow was cancelled |
434
+
435
+ Use them in \`if:\` conditions:
436
+
437
+ \`\`\`typescript
438
+ import { Job, Step, failure, always } from "@intentius/chant-lexicon-github";
439
+
440
+ export const test = new Job({
441
+ "runs-on": "ubuntu-latest",
442
+ steps: [
443
+ new Step({ name: "Test", run: "npm test" }),
444
+ new Step({
445
+ name: "Upload coverage",
446
+ if: always(),
447
+ run: "npx codecov",
448
+ }),
449
+ new Step({
450
+ name: "Notify failure",
451
+ if: failure(),
452
+ run: "curl -X POST $SLACK_WEBHOOK",
453
+ }),
454
+ ],
455
+ });
456
+ \`\`\`
457
+
458
+ ## Function helpers
459
+
460
+ Utility functions that produce expression strings:
461
+
462
+ | Function | Example | Expression |
463
+ |----------|---------|-----------|
464
+ | \`contains(haystack, needle)\` | \`contains(github.event, "bug")\` | \`\${{ contains(github.event, 'bug') }}\` |
465
+ | \`startsWith(value, prefix)\` | \`startsWith(github.ref, "refs/tags/")\` | \`\${{ startsWith(github.ref, 'refs/tags/') }}\` |
466
+ | \`toJSON(value)\` | \`toJSON(github.event)\` | \`\${{ toJSON(github.event) }}\` |
467
+ | \`fromJSON(json)\` | \`fromJSON(steps("meta").outputs("matrix"))\` | \`\${{ fromJSON(steps.meta.outputs.matrix) }}\` |
468
+ | \`format(template, ...args)\` | \`format("v{0}.{1}", major, minor)\` | \`\${{ format('v{0}.{1}', ...) }}\` |
469
+
470
+ ## Convenience helpers
471
+
472
+ Shorthand functions for common checks:
473
+
474
+ \`\`\`typescript
475
+ import { branch, tag } from "@intentius/chant-lexicon-github";
476
+
477
+ branch("main") // github.ref == 'refs/heads/main'
478
+ tag("v1") // startsWith(github.ref, 'refs/tags/v1')
479
+ \`\`\`
480
+
481
+ These are useful in job \`if:\` conditions:
482
+
483
+ \`\`\`typescript
484
+ import { Job, Step, branch } from "@intentius/chant-lexicon-github";
485
+
486
+ export const deploy = new Job({
487
+ "runs-on": "ubuntu-latest",
488
+ if: branch("main"),
489
+ steps: [
490
+ new Step({ name: "Deploy", run: "npm run deploy" }),
491
+ ],
492
+ });
493
+ \`\`\``,
494
+ },
495
+ {
496
+ slug: "variables",
497
+ title: "Variables",
498
+ description: "GitHub and Runner context variable references for GitHub Actions workflows",
499
+ content: `The \`GitHub\` and \`Runner\` objects provide type-safe access to GitHub Actions context variables. These expand to \`\${{ github.* }}\` and \`\${{ runner.* }}\` expressions in the generated YAML.
500
+
501
+ {{file:docs-snippets/src/variables-usage.ts}}
502
+
503
+ ## GitHub context
504
+
505
+ | Property | Expression | Description |
506
+ |----------|-----------|-------------|
507
+ | \`GitHub.Ref\` | \`\${{ github.ref }}\` | Full ref (e.g. \`refs/heads/main\`, \`refs/tags/v1.0\`) |
508
+ | \`GitHub.RefName\` | \`\${{ github.ref_name }}\` | Short ref name (e.g. \`main\`, \`v1.0\`) |
509
+ | \`GitHub.RefType\` | \`\${{ github.ref_type }}\` | Ref type: \`branch\` or \`tag\` |
510
+ | \`GitHub.Sha\` | \`\${{ github.sha }}\` | Full commit SHA |
511
+ | \`GitHub.Actor\` | \`\${{ github.actor }}\` | Username that triggered the workflow |
512
+ | \`GitHub.TriggeringActor\` | \`\${{ github.triggering_actor }}\` | Username that triggered the workflow run |
513
+ | \`GitHub.Repository\` | \`\${{ github.repository }}\` | Owner/repo (e.g. \`octocat/hello-world\`) |
514
+ | \`GitHub.RepositoryOwner\` | \`\${{ github.repository_owner }}\` | Repository owner (e.g. \`octocat\`) |
515
+ | \`GitHub.EventName\` | \`\${{ github.event_name }}\` | Triggering event name (\`push\`, \`pull_request\`, etc.) |
516
+ | \`GitHub.Event\` | \`\${{ github.event }}\` | Full event payload object |
517
+ | \`GitHub.RunId\` | \`\${{ github.run_id }}\` | Unique run ID |
518
+ | \`GitHub.RunNumber\` | \`\${{ github.run_number }}\` | Run number for this workflow |
519
+ | \`GitHub.RunAttempt\` | \`\${{ github.run_attempt }}\` | Attempt number for this run |
520
+ | \`GitHub.Workflow\` | \`\${{ github.workflow }}\` | Workflow name |
521
+ | \`GitHub.WorkflowRef\` | \`\${{ github.workflow_ref }}\` | Workflow ref path |
522
+ | \`GitHub.Workspace\` | \`\${{ github.workspace }}\` | Default working directory on the runner |
523
+ | \`GitHub.Token\` | \`\${{ github.token }}\` | Automatically-generated GITHUB_TOKEN |
524
+ | \`GitHub.Job\` | \`\${{ github.job }}\` | Current job ID |
525
+ | \`GitHub.HeadRef\` | \`\${{ github.head_ref }}\` | PR head branch (pull_request events only) |
526
+ | \`GitHub.BaseRef\` | \`\${{ github.base_ref }}\` | PR base branch (pull_request events only) |
527
+ | \`GitHub.ServerUrl\` | \`\${{ github.server_url }}\` | GitHub server URL |
528
+ | \`GitHub.ApiUrl\` | \`\${{ github.api_url }}\` | GitHub API URL |
529
+ | \`GitHub.GraphqlUrl\` | \`\${{ github.graphql_url }}\` | GitHub GraphQL API URL |
530
+ | \`GitHub.Action\` | \`\${{ github.action }}\` | Action name or step ID |
531
+ | \`GitHub.ActionPath\` | \`\${{ github.action_path }}\` | Path where the action is located |
532
+
533
+ ## Runner context
534
+
535
+ | Property | Expression | Description |
536
+ |----------|-----------|-------------|
537
+ | \`Runner.Os\` | \`\${{ runner.os }}\` | Runner operating system (\`Linux\`, \`Windows\`, \`macOS\`) |
538
+ | \`Runner.Arch\` | \`\${{ runner.arch }}\` | Runner architecture (\`X86\`, \`X64\`, \`ARM\`, \`ARM64\`) |
539
+ | \`Runner.Name\` | \`\${{ runner.name }}\` | Runner name |
540
+ | \`Runner.Temp\` | \`\${{ runner.temp }}\` | Path to a temporary directory on the runner |
541
+ | \`Runner.ToolCache\` | \`\${{ runner.tool_cache }}\` | Path to the tool cache directory |
542
+
543
+ ## Common patterns
544
+
545
+ ### Branch checks
546
+
547
+ \`\`\`typescript
548
+ import { Job, Step, GitHub } from "@intentius/chant-lexicon-github";
549
+ import { branch } from "@intentius/chant-lexicon-github";
550
+
551
+ // Deploy only on main
552
+ export const deploy = new Job({
553
+ "runs-on": "ubuntu-latest",
554
+ if: branch("main"),
555
+ steps: [
556
+ new Step({ name: "Deploy", run: "npm run deploy" }),
557
+ ],
558
+ });
559
+ \`\`\`
560
+
561
+ ### Event filtering
562
+
563
+ \`\`\`typescript
564
+ import { Job, Step, GitHub } from "@intentius/chant-lexicon-github";
565
+ import { github } from "@intentius/chant-lexicon-github";
566
+
567
+ // Skip scheduled runs
568
+ export const test = new Job({
569
+ "runs-on": "ubuntu-latest",
570
+ if: github.eventName.ne("schedule"),
571
+ steps: [
572
+ new Step({ name: "Test", run: "npm test" }),
573
+ ],
574
+ });
575
+ \`\`\`
576
+
577
+ ### Runner-specific logic
578
+
579
+ \`\`\`typescript
580
+ import { Job, Step, runner } from "@intentius/chant-lexicon-github";
581
+
582
+ export const build = new Job({
583
+ "runs-on": "ubuntu-latest",
584
+ steps: [
585
+ new Step({
586
+ name: "Platform-specific setup",
587
+ if: runner.os.eq("Linux"),
588
+ run: "sudo apt-get update && sudo apt-get install -y jq",
589
+ }),
590
+ new Step({ name: "Build", run: "npm run build" }),
591
+ ],
592
+ });
593
+ \`\`\``,
594
+ },
595
+ {
596
+ slug: "composites",
597
+ title: "Composites",
598
+ description: "Pre-built composites — action wrappers, CI pipelines, Docker builds, and deploy environments",
599
+ content: `Composites are pre-built abstractions that produce typed GitHub Actions resources. They range from single-action wrappers to full multi-job workflow pipelines.
600
+
601
+ {{file:docs-snippets/src/composites-usage.ts}}
602
+
603
+ ## Checkout
604
+
605
+ Wraps \`actions/checkout@v4\`. Clones the repository.
606
+
607
+ \`\`\`typescript
608
+ import { Checkout } from "@intentius/chant-lexicon-github";
609
+
610
+ Checkout({}).step // Default checkout
611
+ Checkout({ fetchDepth: 0 }).step // Full history
612
+ Checkout({ ref: "develop" }).step // Specific branch
613
+ Checkout({ submodules: "recursive" }).step // With submodules
614
+ \`\`\`
615
+
616
+ **Props:** \`ref?\`, \`repository?\`, \`fetchDepth?\`, \`token?\`, \`submodules?\`, \`sshKey?\`
617
+
618
+ ## SetupNode
619
+
620
+ Wraps \`actions/setup-node@v4\`. Installs Node.js with optional dependency caching.
621
+
622
+ \`\`\`typescript
623
+ import { SetupNode } from "@intentius/chant-lexicon-github";
624
+
625
+ SetupNode({ nodeVersion: "22" }).step // Node 22
626
+ SetupNode({ nodeVersion: "22", cache: "npm" }).step // With npm cache
627
+ SetupNode({ nodeVersion: "20", cache: "pnpm" }).step // pnpm cache
628
+ \`\`\`
629
+
630
+ **Props:** \`nodeVersion?\`, \`cache?\` (\`"npm"\` | \`"pnpm"\` | \`"yarn"\`), \`registryUrl?\`
631
+
632
+ ## SetupGo
633
+
634
+ Wraps \`actions/setup-go@v5\`. Installs Go.
635
+
636
+ \`\`\`typescript
637
+ import { SetupGo } from "@intentius/chant-lexicon-github";
638
+
639
+ SetupGo({ goVersion: "1.22" }).step
640
+ SetupGo({ goVersion: "stable" }).step
641
+ \`\`\`
642
+
643
+ **Props:** \`goVersion?\`, \`cache?\`
644
+
645
+ ## SetupPython
646
+
647
+ Wraps \`actions/setup-python@v5\`. Installs Python.
648
+
649
+ \`\`\`typescript
650
+ import { SetupPython } from "@intentius/chant-lexicon-github";
651
+
652
+ SetupPython({ pythonVersion: "3.12" }).step
653
+ SetupPython({ pythonVersion: "3.12", cache: "pip" }).step
654
+ \`\`\`
655
+
656
+ **Props:** \`pythonVersion?\`, \`cache?\` (\`"pip"\` | \`"pipenv"\` | \`"poetry"\`), \`architecture?\`
657
+
658
+ ## CacheAction
659
+
660
+ Wraps \`actions/cache@v4\`. Caches files between workflow runs.
661
+
662
+ \`\`\`typescript
663
+ import { CacheAction } from "@intentius/chant-lexicon-github";
664
+
665
+ CacheAction({
666
+ path: "~/.npm",
667
+ key: "npm-\${{ runner.os }}-\${{ hashFiles('**/package-lock.json') }}",
668
+ restoreKeys: ["npm-\${{ runner.os }}-"],
669
+ }).step
670
+ \`\`\`
671
+
672
+ **Props:** \`path\`, \`key\`, \`restoreKeys?\`
673
+
674
+ ## UploadArtifact
675
+
676
+ Wraps \`actions/upload-artifact@v4\`. Uploads files as workflow artifacts.
677
+
678
+ \`\`\`typescript
679
+ import { UploadArtifact } from "@intentius/chant-lexicon-github";
680
+
681
+ UploadArtifact({
682
+ name: "build-output",
683
+ path: "dist/",
684
+ retentionDays: 7,
685
+ }).step
686
+ \`\`\`
687
+
688
+ **Props:** \`name\`, \`path\`, \`retentionDays?\`, \`ifNoFilesFound?\`
689
+
690
+ ## DownloadArtifact
691
+
692
+ Wraps \`actions/download-artifact@v4\`. Downloads previously uploaded artifacts.
693
+
694
+ \`\`\`typescript
695
+ import { DownloadArtifact } from "@intentius/chant-lexicon-github";
696
+
697
+ DownloadArtifact({
698
+ name: "build-output",
699
+ path: "dist/",
700
+ }).step
701
+ \`\`\`
702
+
703
+ **Props:** \`name\`, \`path?\`
704
+
705
+ ## NodeCI
706
+
707
+ A batteries-included composite that produces a full CI workflow and job. Generates a \`Workflow\` (push + PR on main) and a \`Job\` with checkout, setup-node, install, build, and test steps.
708
+
709
+ \`\`\`typescript
710
+ import { NodeCI } from "@intentius/chant-lexicon-github";
711
+
712
+ // Default: Node 22, npm, "build" + "test" scripts
713
+ const { workflow, job } = NodeCI({});
714
+
715
+ // Customized
716
+ const { workflow: w, job: j } = NodeCI({
717
+ nodeVersion: "20",
718
+ packageManager: "pnpm",
719
+ buildScript: "compile",
720
+ testScript: "test:ci",
721
+ });
722
+ \`\`\`
723
+
724
+ **Props:** \`nodeVersion?\`, \`packageManager?\` (\`"npm"\` | \`"pnpm"\` | \`"yarn"\` | \`"bun"\`), \`buildScript?\`, \`testScript?\`, \`installCommand?\`
725
+
726
+ The returned \`workflow\` and \`job\` can be exported directly:
727
+
728
+ \`\`\`typescript
729
+ const ci = NodeCI({ nodeVersion: "22", packageManager: "npm" });
730
+ export const workflow = ci.workflow;
731
+ export const build = ci.job;
732
+ \`\`\`
733
+
734
+ ## NodePipeline
735
+
736
+ A production-grade Node pipeline with separate build and test jobs connected by artifact passing. The build job uploads artifacts; the test job downloads them and runs tests with \`needs: ["build"]\`.
737
+
738
+ \`\`\`typescript
739
+ import { NodePipeline } from "@intentius/chant-lexicon-github";
740
+
741
+ const { workflow, buildJob, testJob } = NodePipeline({
742
+ nodeVersion: "22",
743
+ packageManager: "pnpm",
744
+ buildScript: "build",
745
+ testScript: "test:ci",
746
+ buildArtifactPaths: ["dist/", "lib/"],
747
+ });
748
+ \`\`\`
749
+
750
+ **Props:** \`nodeVersion?\`, \`packageManager?\` (\`"npm"\` | \`"pnpm"\` | \`"yarn"\` | \`"bun"\`), \`buildScript?\`, \`testScript?\`, \`installCommand?\`, \`buildArtifactPaths?\`, \`artifactName?\`, \`artifactRetentionDays?\`, \`runsOn?\`
751
+
752
+ ### Presets
753
+
754
+ \`\`\`typescript
755
+ import { BunPipeline, PnpmPipeline, YarnPipeline } from "@intentius/chant-lexicon-github";
756
+
757
+ const bun = BunPipeline({}); // packageManager: "bun", uses oven-sh/setup-bun@v2
758
+ const pnpm = PnpmPipeline({}); // packageManager: "pnpm"
759
+ const yarn = YarnPipeline({}); // packageManager: "yarn"
760
+ \`\`\`
761
+
762
+ ## PythonCI
763
+
764
+ Python CI with test and optional lint jobs. Supports pip and Poetry workflows.
765
+
766
+ \`\`\`typescript
767
+ import { PythonCI } from "@intentius/chant-lexicon-github";
768
+
769
+ const { workflow, testJob, lintJob } = PythonCI({
770
+ pythonVersion: "3.12",
771
+ testCommand: "pytest --junitxml=report.xml --cov",
772
+ lintCommand: "ruff check .",
773
+ });
774
+
775
+ // Omit lint job
776
+ const { workflow: w, testJob: t } = PythonCI({ lintCommand: null });
777
+
778
+ // Poetry mode
779
+ const poetry = PythonCI({ usePoetry: true });
780
+ \`\`\`
781
+
782
+ **Props:** \`pythonVersion?\`, \`testCommand?\`, \`lintCommand?\` (null to omit), \`requirementsFile?\`, \`usePoetry?\`, \`runsOn?\`
783
+
784
+ ## DockerBuild
785
+
786
+ Docker build and push using official Docker actions (login, setup-buildx, metadata, build-push). Configured for GitHub Container Registry by default.
787
+
788
+ \`\`\`typescript
789
+ import { DockerBuild } from "@intentius/chant-lexicon-github";
790
+
791
+ const { workflow, job } = DockerBuild({
792
+ registry: "ghcr.io",
793
+ imageName: "ghcr.io/my-org/my-app",
794
+ dockerfile: "Dockerfile",
795
+ platforms: ["linux/amd64", "linux/arm64"],
796
+ });
797
+ \`\`\`
798
+
799
+ **Props:** \`tag?\`, \`dockerfile?\`, \`context?\`, \`registry?\`, \`imageName?\`, \`tagLatest?\`, \`buildArgs?\`, \`push?\`, \`platforms?\`, \`runsOn?\`
800
+
801
+ ## DeployEnvironment
802
+
803
+ Deploy and cleanup job pair using GitHub Environments with concurrency control.
804
+
805
+ \`\`\`typescript
806
+ import { DeployEnvironment } from "@intentius/chant-lexicon-github";
807
+
808
+ const { deployJob, cleanupJob } = DeployEnvironment({
809
+ name: "staging",
810
+ deployScript: ["npm run build", "npm run deploy"],
811
+ cleanupScript: "npm run teardown",
812
+ url: "https://staging.example.com",
813
+ });
814
+ \`\`\`
815
+
816
+ **Props:** \`name\` (required), \`deployScript\` (required), \`cleanupScript?\`, \`url?\`, \`concurrencyGroup?\`, \`cancelInProgress?\`, \`runsOn?\`
817
+
818
+ ## GoCI
819
+
820
+ Go CI with build, test, and optional lint jobs. Uses \`golangci-lint-action\` for linting.
821
+
822
+ \`\`\`typescript
823
+ import { GoCI } from "@intentius/chant-lexicon-github";
824
+
825
+ const { workflow, buildJob, testJob, lintJob } = GoCI({
826
+ goVersion: "1.22",
827
+ buildCommand: "go build ./...",
828
+ testCommand: "go test ./... -v -race",
829
+ });
830
+
831
+ // Without lint
832
+ const noLint = GoCI({ lintCommand: null });
833
+ \`\`\`
834
+
835
+ **Props:** \`goVersion?\`, \`testCommand?\`, \`buildCommand?\`, \`lintCommand?\` (null to omit), \`runsOn?\``,
836
+ },
837
+ {
838
+ slug: "lint-rules",
839
+ title: "Lint Rules",
840
+ description: "Built-in lint rules and post-synth checks for GitHub Actions workflows",
841
+ content: `The GitHub Actions lexicon ships lint rules that run during \`chant lint\` and post-synth checks that validate the serialized YAML after \`chant build\`.
842
+
843
+ ## Lint rules
844
+
845
+ Lint rules analyze your TypeScript source code before build.
846
+
847
+ ### GHA001 — Use typed action composites
848
+
849
+ **Severity:** warning | **Category:** style
850
+
851
+ Flags raw \`uses:\` strings when a typed composite wrapper is available (e.g. \`actions/checkout@v4\` → \`Checkout({})\`). Typed composites provide better IDE support and catch configuration errors at compile time.
852
+
853
+ ### GHA002 — Use Expression helpers
854
+
855
+ **Severity:** warning | **Category:** style
856
+
857
+ Flags raw \`\${{ }}\` strings in \`if:\` conditions. Use the typed Expression helpers (\`github.ref.eq(...)\`, \`branch("main")\`, \`failure()\`) instead for type safety and lint coverage.
858
+
859
+ ### GHA003 — No hardcoded secrets
860
+
861
+ **Severity:** error | **Category:** security
862
+
863
+ Flags hardcoded GitHub tokens, AWS keys, and other secret patterns in source code. Use \`secrets("...")\` expressions instead.
864
+
865
+ {{file:docs-snippets/src/lint-gha003.ts}}
866
+
867
+ ### GHA004 — Extract inline matrix
868
+
869
+ **Severity:** info | **Category:** style
870
+
871
+ Flags inline matrix objects and suggests extracting them to named constants for readability.
872
+
873
+ ### GHA005 — Extract deeply nested objects
874
+
875
+ **Severity:** info | **Category:** style
876
+
877
+ Flags deeply nested inline objects and suggests extracting them to named constants.
878
+
879
+ ### GHA007 — Too many jobs per file
880
+
881
+ **Severity:** warning | **Category:** style
882
+
883
+ Flags files with more than 10 job exports. Split large workflows into separate files for maintainability.
884
+
885
+ ### GHA008 — Avoid raw expression strings
886
+
887
+ **Severity:** info | **Category:** style
888
+
889
+ Flags raw \`\${{ }}\` expression strings outside of \`if:\` fields. Use the typed expression helpers (\`github.*\`, \`secrets()\`, \`matrix()\`) for better type safety.
890
+
891
+ ### GHA010 — Setup action missing version
892
+
893
+ **Severity:** warning | **Category:** correctness
894
+
895
+ Flags setup action composites (SetupNode, SetupGo, SetupPython) without a version input. Pinning the version ensures reproducible builds.
896
+
897
+ ### GHA012 — Deprecated action version
898
+
899
+ **Severity:** warning | **Category:** correctness
900
+
901
+ Flags action \`uses:\` references that point to deprecated versions. Upgrade to the recommended version.
902
+
903
+ ### GHA014 — Missing timeout
904
+
905
+ **Severity:** warning | **Category:** correctness
906
+
907
+ Flags jobs without \`timeoutMinutes\`. Jobs without a timeout default to 360 minutes (6 hours), which can waste runner minutes if stuck.
908
+
909
+ ### GHA015 — Setup without cache
910
+
911
+ **Severity:** warning | **Category:** performance
912
+
913
+ Flags setup action composites (SetupNode, SetupGo, SetupPython) without a paired \`CacheAction\` or built-in \`cache\` option. Caching dependencies significantly speeds up builds.
914
+
915
+ ### GHA016 — Concurrency missing group
916
+
917
+ **Severity:** warning | **Category:** correctness
918
+
919
+ Flags \`concurrency\` with \`cancel-in-progress: true\` but no \`group\` specified. Without a group, all runs of the workflow share a single concurrency slot.
920
+
921
+ ### GHA020 — Potential secret detected
922
+
923
+ **Severity:** error | **Category:** security
924
+
925
+ Flags string literals that match common secret patterns (API keys, tokens, passwords). Use \`secrets()\` or environment variables instead.
926
+
927
+ ## Post-synth checks
928
+
929
+ Post-synth checks run against the serialized YAML after build. They catch issues only visible in the final output.
930
+
931
+ ### GHA006 — Duplicate workflow names
932
+
933
+ **Severity:** error
934
+
935
+ Flags multiple workflows that share the same \`name:\` value. Duplicate names cause confusion in the GitHub Actions UI.
936
+
937
+ ### GHA009 — Empty matrix dimension
938
+
939
+ **Severity:** error
940
+
941
+ Flags matrix strategy dimensions with an empty values array. An empty dimension causes the job to be skipped entirely.
942
+
943
+ ### GHA011 — Invalid needs target
944
+
945
+ **Severity:** error
946
+
947
+ Flags jobs whose \`needs:\` entries reference a job not defined in the workflow. This causes a workflow validation error on GitHub.
948
+
949
+ ### GHA017 — Missing permissions block
950
+
951
+ **Severity:** info
952
+
953
+ Flags workflows without an explicit \`permissions:\` block. Omitting permissions uses the repository default (often overly broad). Following least-privilege by declaring explicit permissions is a security best practice.
954
+
955
+ ### GHA018 — pull_request_target with checkout
956
+
957
+ **Severity:** warning
958
+
959
+ Flags workflows triggered by \`pull_request_target\` that include \`actions/checkout\`. This combination can be a security risk because the workflow runs with write permissions in the context of the base branch while checking out potentially untrusted PR code.
960
+
961
+ ### GHA019 — Circular needs chain
962
+
963
+ **Severity:** error
964
+
965
+ Detects cycles in the \`needs:\` dependency graph. If job A needs B and B needs A (directly or transitively), GitHub rejects the workflow. Reports the full cycle chain in the diagnostic message.
966
+
967
+ ## Running lint
968
+
969
+ \`\`\`bash
970
+ # Lint your chant project
971
+ chant lint src/
972
+
973
+ # Lint with auto-fix where supported
974
+ chant lint --fix src/
975
+ \`\`\`
976
+
977
+ To suppress a rule on a specific line:
978
+
979
+ \`\`\`typescript
980
+ // chant-disable-next-line GHA001
981
+ new Step({ uses: "actions/checkout@v4" });
982
+ \`\`\`
983
+
984
+ To suppress globally in \`chant.config.ts\`:
985
+
986
+ \`\`\`typescript
987
+ export default {
988
+ lint: {
989
+ rules: {
990
+ GHA014: "off",
991
+ },
992
+ },
993
+ };
994
+ \`\`\``,
995
+ },
996
+ {
997
+ slug: "examples",
998
+ title: "Examples",
999
+ description: "Walkthrough of GitHub Actions examples — workflows, composites, and patterns",
1000
+ content: `Runnable examples live in the lexicon's \`examples/\` directory. Clone the repo and try them:
1001
+
1002
+ \`\`\`bash
1003
+ cd examples/getting-started
1004
+ bun install
1005
+ chant build # produces .github/workflows/ci.yml
1006
+ chant lint # runs lint rules
1007
+ bun test # runs the example's tests
1008
+ \`\`\`
1009
+
1010
+ ## Getting Started
1011
+
1012
+ \`examples/getting-started/\` — a CI workflow with build and test steps for a Node.js project.
1013
+
1014
+ \`\`\`
1015
+ src/
1016
+ └── ci.ts # Workflow + Job definitions
1017
+ \`\`\`
1018
+
1019
+ ### Source
1020
+
1021
+ {{file:getting-started/src/ci.ts}}
1022
+
1023
+ ### Generated output
1024
+
1025
+ \`chant build\` produces \`.github/workflows/ci.yml\`:
1026
+
1027
+ \`\`\`yaml
1028
+ name: CI
1029
+ on:
1030
+ push:
1031
+ branches:
1032
+ - main
1033
+ pull_request:
1034
+ branches:
1035
+ - main
1036
+ permissions:
1037
+ contents: read
1038
+ jobs:
1039
+ build:
1040
+ runs-on: ubuntu-latest
1041
+ timeout-minutes: 15
1042
+ steps:
1043
+ - name: Checkout
1044
+ uses: actions/checkout@v4
1045
+ - name: Setup Node.js
1046
+ uses: actions/setup-node@v4
1047
+ with:
1048
+ node-version: '22'
1049
+ cache: npm
1050
+ - name: Install
1051
+ run: npm ci
1052
+ - name: Build
1053
+ run: npm run build
1054
+ - name: Test
1055
+ run: npm test
1056
+ \`\`\`
1057
+
1058
+ **Patterns demonstrated:**
1059
+
1060
+ 1. **Typed composites** — \`Checkout({})\` and \`SetupNode({...})\` instead of raw \`uses:\` strings
1061
+ 2. **Permissions** — explicit \`contents: read\` following least-privilege
1062
+ 3. **Timeout** — \`timeoutMinutes: 15\` prevents runaway jobs
1063
+ 4. **Trigger scoping** — push and PR on \`main\` only`,
1064
+ },
1065
+ {
1066
+ slug: "skills",
1067
+ title: "AI Skills",
1068
+ description: "AI agent skills bundled with the GitHub Actions lexicon",
1069
+ content: `The GitHub Actions lexicon ships AI skills that teach AI coding agents (like Claude Code) how to build, validate, and deploy GitHub Actions workflows from a chant project.
1070
+
1071
+ ## What are skills?
1072
+
1073
+ Skills are structured markdown documents bundled with a lexicon. When an AI agent works in a chant project, it discovers and loads relevant skills automatically — giving it operational knowledge about the deployment workflow without requiring the user to explain each step.
1074
+
1075
+ ## Installation
1076
+
1077
+ When you scaffold a new project with \`chant init --lexicon github\`, skills are installed to \`.claude/skills/\` for automatic discovery by Claude Code.
1078
+
1079
+ For existing projects, create the files manually:
1080
+
1081
+ \`\`\`
1082
+ .claude/
1083
+ skills/
1084
+ chant-github/
1085
+ SKILL.md # skill content
1086
+ \`\`\`
1087
+
1088
+ ## Skill: chant-github
1089
+
1090
+ The inline \`chant-github\` skill covers the full workflow lifecycle:
1091
+
1092
+ - **Build** — \`chant build src/ --output .github/workflows/ci.yml\`
1093
+ - **Validate** — \`chant lint src/\`
1094
+ - **Deploy** — commit and push the generated YAML
1095
+ - **Status** — GitHub Actions UI or \`gh run list\`
1096
+ - **Troubleshooting** — lint rule codes (GHA001–GHA020), post-synth checks (GHA006–GHA019)
1097
+
1098
+ The skill is invocable as a slash command: \`/chant-github\`
1099
+
1100
+ ## Skill: github-actions-patterns
1101
+
1102
+ A file-based skill loaded from \`src/skills/github-actions-patterns.md\`. It provides pattern knowledge for:
1103
+
1104
+ - **Workflow structure** — name, on, permissions, jobs
1105
+ - **Trigger patterns** — push, pull_request, schedule, workflow_dispatch
1106
+ - **Matrix strategy** — multi-OS, multi-version builds
1107
+ - **Caching** — SetupNode cache option, CacheAction composite
1108
+ - **Permissions** — least-privilege patterns
1109
+ - **Reusable workflows** — ReusableWorkflowCallJob
1110
+ - **Artifacts** — upload/download between jobs
1111
+ - **Concurrency** — group + cancel-in-progress
1112
+
1113
+ ## MCP integration
1114
+
1115
+ The lexicon provides MCP (Model Context Protocol) tools and resources that AI agents can use programmatically:
1116
+
1117
+ | MCP tool | Description |
1118
+ |----------|-------------|
1119
+ | \`diff\` | Compare current build output against previous output |
1120
+
1121
+ | MCP resource | Description |
1122
+ |--------------|-------------|
1123
+ | \`resource-catalog\` | JSON list of all supported GitHub Actions entity types |
1124
+ | \`examples/basic-ci\` | Example CI workflow with TypeScript source |`,
1125
+ },
1126
+ ],
1127
+ basePath: "/chant/lexicons/github/",
1128
+ };
1129
+
1130
+ const result = await docsPipeline(config);
1131
+ writeDocsSite(config, result);
1132
+
1133
+ if (opts?.verbose) {
1134
+ console.error(
1135
+ `Generated docs: ${result.stats.resources} resources, ${result.stats.properties} properties, ${result.stats.services} services, ${result.stats.rules} rules`,
1136
+ );
1137
+ }
1138
+ }