@intentius/chant-lexicon-github 0.0.18 → 0.0.24

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 (78) hide show
  1. package/dist/integrity.json +14 -4
  2. package/dist/manifest.json +1 -1
  3. package/dist/rules/gha020.ts +40 -0
  4. package/dist/rules/gha021.ts +48 -0
  5. package/dist/rules/gha022.ts +50 -0
  6. package/dist/rules/gha023.ts +44 -0
  7. package/dist/rules/gha024.ts +42 -0
  8. package/dist/rules/gha025.ts +42 -0
  9. package/dist/rules/gha026.ts +40 -0
  10. package/dist/rules/gha027.ts +57 -0
  11. package/dist/rules/gha028.ts +37 -0
  12. package/dist/skills/{github-actions-patterns.md → chant-github-patterns.md} +2 -1
  13. package/dist/skills/chant-github-security.md +88 -0
  14. package/dist/skills/chant-github.md +569 -4
  15. package/package.json +20 -2
  16. package/src/codegen/docs.test.ts +19 -0
  17. package/src/codegen/generate.test.ts +12 -0
  18. package/src/codegen/package.test.ts +8 -0
  19. package/src/composites/cache.ts +8 -3
  20. package/src/composites/checkout.ts +8 -3
  21. package/src/composites/composites.test.ts +106 -0
  22. package/src/composites/deploy-environment.ts +11 -5
  23. package/src/composites/docker-build.ts +11 -5
  24. package/src/composites/download-artifact.ts +8 -3
  25. package/src/composites/go-ci.ts +17 -9
  26. package/src/composites/node-ci.ts +11 -5
  27. package/src/composites/node-pipeline.ts +14 -7
  28. package/src/composites/python-ci.ts +14 -7
  29. package/src/composites/setup-go.ts +8 -3
  30. package/src/composites/setup-node.ts +8 -3
  31. package/src/composites/setup-python.ts +8 -3
  32. package/src/composites/upload-artifact.ts +8 -3
  33. package/src/coverage.test.ts +23 -0
  34. package/src/import/roundtrip.test.ts +206 -0
  35. package/src/lint/post-synth/gha006.test.ts +56 -0
  36. package/src/lint/post-synth/gha009.test.ts +56 -0
  37. package/src/lint/post-synth/gha011.test.ts +61 -0
  38. package/src/lint/post-synth/gha017.test.ts +51 -0
  39. package/src/lint/post-synth/gha018.test.ts +66 -0
  40. package/src/lint/post-synth/gha019.test.ts +66 -0
  41. package/src/lint/post-synth/gha020.test.ts +66 -0
  42. package/src/lint/post-synth/gha020.ts +40 -0
  43. package/src/lint/post-synth/gha021.test.ts +67 -0
  44. package/src/lint/post-synth/gha021.ts +48 -0
  45. package/src/lint/post-synth/gha022.test.ts +45 -0
  46. package/src/lint/post-synth/gha022.ts +50 -0
  47. package/src/lint/post-synth/gha023.test.ts +52 -0
  48. package/src/lint/post-synth/gha023.ts +44 -0
  49. package/src/lint/post-synth/gha024.test.ts +67 -0
  50. package/src/lint/post-synth/gha024.ts +42 -0
  51. package/src/lint/post-synth/gha025.test.ts +65 -0
  52. package/src/lint/post-synth/gha025.ts +42 -0
  53. package/src/lint/post-synth/gha026.test.ts +65 -0
  54. package/src/lint/post-synth/gha026.ts +40 -0
  55. package/src/lint/post-synth/gha027.test.ts +48 -0
  56. package/src/lint/post-synth/gha027.ts +57 -0
  57. package/src/lint/post-synth/gha028.test.ts +48 -0
  58. package/src/lint/post-synth/gha028.ts +37 -0
  59. package/src/lint/rules/deprecated-action-version.test.ts +26 -0
  60. package/src/lint/rules/detect-secrets.test.ts +25 -0
  61. package/src/lint/rules/extract-inline-structs.test.ts +25 -0
  62. package/src/lint/rules/file-job-limit.test.ts +28 -0
  63. package/src/lint/rules/job-timeout.test.ts +31 -0
  64. package/src/lint/rules/missing-recommended-inputs.test.ts +26 -0
  65. package/src/lint/rules/no-hardcoded-secrets.test.ts +31 -0
  66. package/src/lint/rules/no-raw-expressions.test.ts +31 -0
  67. package/src/lint/rules/suggest-cache.test.ts +25 -0
  68. package/src/lint/rules/use-condition-builders.test.ts +31 -0
  69. package/src/lint/rules/use-matrix-builder.test.ts +25 -0
  70. package/src/lint/rules/use-typed-actions.test.ts +39 -0
  71. package/src/lint/rules/validate-concurrency.test.ts +31 -0
  72. package/src/plugin.test.ts +1 -1
  73. package/src/plugin.ts +70 -145
  74. package/src/skills/{github-actions-patterns.md → chant-github-patterns.md} +2 -1
  75. package/src/skills/chant-github-security.md +88 -0
  76. package/src/skills/chant-github.md +594 -0
  77. package/src/validate.ts +14 -1
  78. package/src/variables.test.ts +48 -0
package/src/plugin.ts CHANGED
@@ -6,10 +6,13 @@
6
6
  */
7
7
 
8
8
  import { createRequire } from "module";
9
- import type { LexiconPlugin, IntrinsicDef, SkillDefinition, InitTemplateSet } from "@intentius/chant/lexicon";
9
+ import type { LexiconPlugin, IntrinsicDef, InitTemplateSet } from "@intentius/chant/lexicon";
10
10
  const require = createRequire(import.meta.url);
11
11
  import type { LintRule } from "@intentius/chant/lint/rule";
12
- import type { PostSynthCheck } from "@intentius/chant/lint/post-synth";
12
+ import { discoverPostSynthChecks } from "@intentius/chant/lint/discover";
13
+ import { createSkillsLoader, createDiffTool, createCatalogResource } from "@intentius/chant/lexicon-plugin-helpers";
14
+ import { join, dirname } from "path";
15
+ import { fileURLToPath } from "url";
13
16
  import { githubSerializer } from "./serializer";
14
17
 
15
18
  export const githubPlugin: LexiconPlugin = {
@@ -47,14 +50,9 @@ export const githubPlugin: LexiconPlugin = {
47
50
  ];
48
51
  },
49
52
 
50
- postSynthChecks(): PostSynthCheck[] {
51
- const { gha006 } = require("./lint/post-synth/gha006");
52
- const { gha009 } = require("./lint/post-synth/gha009");
53
- const { gha011 } = require("./lint/post-synth/gha011");
54
- const { gha017 } = require("./lint/post-synth/gha017");
55
- const { gha018 } = require("./lint/post-synth/gha018");
56
- const { gha019 } = require("./lint/post-synth/gha019");
57
- return [gha006, gha009, gha011, gha017, gha018, gha019];
53
+ postSynthChecks() {
54
+ const postSynthDir = join(dirname(fileURLToPath(import.meta.url)), "lint", "post-synth");
55
+ return discoverPostSynthChecks(postSynthDir, import.meta.url);
58
56
  },
59
57
 
60
58
  intrinsics(): IntrinsicDef[] {
@@ -228,48 +226,12 @@ export const build = new Job({
228
226
  },
229
227
 
230
228
  mcpTools() {
231
- return [
232
- {
233
- name: "diff",
234
- description: "Compare current build output against previous output for GitHub Actions",
235
- inputSchema: {
236
- type: "object" as const,
237
- properties: {
238
- path: {
239
- type: "string",
240
- description: "Path to the infrastructure project directory",
241
- },
242
- },
243
- },
244
- async handler(params: Record<string, unknown>): Promise<unknown> {
245
- const { diffCommand } = await import("@intentius/chant/cli/commands/diff");
246
- const result = await diffCommand({
247
- path: (params.path as string) ?? ".",
248
- serializers: [githubSerializer],
249
- });
250
- return result;
251
- },
252
- },
253
- ];
229
+ return [createDiffTool(githubSerializer, "Compare current build output against previous output for GitHub Actions")];
254
230
  },
255
231
 
256
232
  mcpResources() {
257
233
  return [
258
- {
259
- uri: "resource-catalog",
260
- name: "GitHub Actions Entity Catalog",
261
- description: "JSON list of all supported GitHub Actions entity types",
262
- mimeType: "application/json",
263
- async handler(): Promise<string> {
264
- const lexicon = require("./generated/lexicon-github.json") as Record<string, { resourceType: string; kind: string }>;
265
- const entries = Object.entries(lexicon).map(([className, entry]) => ({
266
- className,
267
- resourceType: entry.resourceType,
268
- kind: entry.kind,
269
- }));
270
- return JSON.stringify(entries);
271
- },
272
- },
234
+ createCatalogResource(import.meta.url, "GitHub Actions Entity Catalog", "JSON list of all supported GitHub Actions entity types", "lexicon-github.json"),
273
235
  {
274
236
  uri: "examples/basic-ci",
275
237
  name: "Basic CI Example",
@@ -308,101 +270,64 @@ export const build = new Job({
308
270
  await generateDocs(options);
309
271
  },
310
272
 
311
- skills(): SkillDefinition[] {
312
- const skills: SkillDefinition[] = [
313
- {
314
- name: "chant-github",
315
- description: "GitHub Actions workflow lifecycle — build, validate, deploy",
316
- content: `---
317
- skill: chant-github
318
- description: Build, validate, and deploy GitHub Actions workflows from a chant project
319
- user-invocable: true
320
- ---
321
-
322
- # GitHub Actions Operational Playbook
323
-
324
- ## How chant and GitHub Actions relate
325
-
326
- chant is a **synthesis-only** tool it compiles TypeScript source files into \`.github/workflows/*.yml\` (YAML). chant does NOT call GitHub APIs.
327
-
328
- - Use **chant** for: build, lint, diff (local YAML comparison)
329
- - Use **git + GitHub** for: push, pull requests, workflow monitoring
330
-
331
- ## Build and validate
332
-
333
- \`\`\`bash
334
- chant build src/ --output .github/workflows/ci.yml
335
- chant lint src/
336
- \`\`\`
337
-
338
- ## Deploy
339
-
340
- \`\`\`bash
341
- git add .github/workflows/ci.yml
342
- git commit -m "Update workflow"
343
- git push
344
- \`\`\`
345
- `,
346
- triggers: [
347
- { type: "file-pattern", value: "**/*.github.ts" },
348
- { type: "file-pattern", value: "**/.github/workflows/*.yml" },
349
- { type: "context", value: "github actions" },
350
- { type: "context", value: "workflow" },
351
- ],
352
- parameters: [],
353
- examples: [
354
- {
355
- title: "Basic CI workflow",
356
- description: "Create a CI workflow with build and test",
357
- input: "Create a CI workflow",
358
- output: `new Workflow({ name: "CI", on: { push: { branches: ["main"] }, pull_request: { branches: ["main"] } } })`,
359
- },
360
- ],
361
- },
362
- ];
363
-
364
- // Load file-based skills
365
- const { readFileSync } = require("fs");
366
- const { join, dirname } = require("path");
367
- const { fileURLToPath } = require("url");
368
- const dir = dirname(fileURLToPath(import.meta.url));
369
-
370
- const skillFiles = [
371
- {
372
- file: "github-actions-patterns.md",
373
- name: "github-actions-patterns",
374
- description: "GitHub Actions workflow patterns — triggers, jobs, matrix, caching, artifacts",
375
- triggers: [
376
- { type: "context" as const, value: "github actions" },
377
- { type: "context" as const, value: "workflow" },
378
- { type: "context" as const, value: "matrix" },
379
- { type: "context" as const, value: "cache" },
380
- ],
381
- parameters: [],
382
- examples: [
383
- {
384
- title: "Matrix strategy",
385
- input: "Set up a Node.js matrix build",
386
- output: `new Strategy({ matrix: { "node-version": ["18", "20", "22"] } })`,
387
- },
388
- ],
389
- },
390
- ];
391
-
392
- for (const skill of skillFiles) {
393
- try {
394
- const content = readFileSync(join(dir, "skills", skill.file), "utf-8");
395
- skills.push({
396
- name: skill.name,
397
- description: skill.description,
398
- content,
399
- triggers: skill.triggers,
400
- parameters: skill.parameters,
401
- examples: skill.examples,
402
- });
403
- } catch { /* skip missing skills */ }
404
- }
405
-
406
- return skills;
407
- },
273
+ skills: createSkillsLoader(import.meta.url, [
274
+ {
275
+ file: "chant-github.md",
276
+ name: "chant-github",
277
+ description: "GitHub Actions workflow lifecycle — build, validate, deploy",
278
+ triggers: [
279
+ { type: "file-pattern", value: "**/*.github.ts" },
280
+ { type: "file-pattern", value: "**/.github/workflows/*.yml" },
281
+ { type: "context", value: "github actions" },
282
+ { type: "context", value: "workflow" },
283
+ ],
284
+ parameters: [],
285
+ examples: [
286
+ {
287
+ title: "Basic CI workflow",
288
+ description: "Create a CI workflow with build and test",
289
+ input: "Create a CI workflow",
290
+ output: `new Workflow({ name: "CI", on: { push: { branches: ["main"] }, pull_request: { branches: ["main"] } } })`,
291
+ },
292
+ ],
293
+ },
294
+ {
295
+ file: "chant-github-patterns.md",
296
+ name: "chant-github-patterns",
297
+ description: "GitHub Actions workflow patterns — triggers, jobs, matrix, caching, artifacts",
298
+ triggers: [
299
+ { type: "context", value: "github actions" },
300
+ { type: "context", value: "workflow" },
301
+ { type: "context", value: "matrix" },
302
+ { type: "context", value: "cache" },
303
+ ],
304
+ parameters: [],
305
+ examples: [
306
+ {
307
+ title: "Matrix strategy",
308
+ input: "Set up a Node.js matrix build",
309
+ output: `new Strategy({ matrix: { "node-version": ["18", "20", "22"] } })`,
310
+ },
311
+ ],
312
+ },
313
+ {
314
+ file: "chant-github-security.md",
315
+ name: "chant-github-security",
316
+ description: "GitHub Actions security — secret scanning, OIDC, permissions hardening, supply chain",
317
+ triggers: [
318
+ { type: "context", value: "github security" },
319
+ { type: "context", value: "workflow security" },
320
+ { type: "context", value: "oidc" },
321
+ { type: "context", value: "permissions" },
322
+ ],
323
+ parameters: [],
324
+ examples: [
325
+ {
326
+ title: "Permissions hardening",
327
+ input: "Lock down workflow permissions",
328
+ output: `new Workflow({ permissions: { contents: "read" } })`,
329
+ },
330
+ ],
331
+ },
332
+ ]),
408
333
  };
@@ -1,6 +1,7 @@
1
1
  ---
2
- skill: github-actions-patterns
2
+ skill: chant-github-patterns
3
3
  description: GitHub Actions workflow patterns — triggers, jobs, matrix, caching, artifacts, permissions, reusable workflows
4
+ user-invocable: true
4
5
  ---
5
6
 
6
7
  # GitHub Actions Patterns
@@ -0,0 +1,88 @@
1
+ ---
2
+ skill: chant-github-security
3
+ description: GitHub Actions security best practices — secret scanning, OIDC, permissions hardening, supply chain security
4
+ user-invocable: true
5
+ ---
6
+
7
+ # GitHub Actions Security Playbook
8
+
9
+ ## Permissions Hardening
10
+
11
+ Always set the minimum required permissions at the workflow level:
12
+
13
+ ```typescript
14
+ new Workflow({
15
+ name: "CI",
16
+ on: { push: { branches: ["main"] } },
17
+ permissions: { contents: "read" },
18
+ });
19
+ ```
20
+
21
+ For deployments that need write access, scope it to the job:
22
+
23
+ ```typescript
24
+ new Job({
25
+ "runs-on": "ubuntu-latest",
26
+ permissions: { contents: "read", "id-token": "write" },
27
+ steps: [...],
28
+ });
29
+ ```
30
+
31
+ ## Pin Actions by SHA
32
+
33
+ Never use mutable tags like `@v4`. Pin to a full commit SHA:
34
+
35
+ ```typescript
36
+ new Step({
37
+ uses: "actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11", // v4.1.1
38
+ })
39
+ ```
40
+
41
+ ## OIDC for Cloud Providers
42
+
43
+ Use OpenID Connect instead of long-lived secrets:
44
+
45
+ ```typescript
46
+ // AWS
47
+ new Step({
48
+ uses: "aws-actions/configure-aws-credentials@v4",
49
+ with: {
50
+ "role-to-assume": "arn:aws:iam::123456789012:role/deploy",
51
+ "aws-region": "us-east-1",
52
+ },
53
+ })
54
+ ```
55
+
56
+ ## Secret Scanning
57
+
58
+ - Never echo secrets in `run:` steps
59
+ - Use `environment` protection rules for production secrets
60
+ - Rotate secrets regularly and audit access logs
61
+
62
+ ## Supply Chain Security
63
+
64
+ - Use `permissions: {}` (empty) as a baseline, then grant only what each job needs
65
+ - Avoid `pull_request_target` with `actions/checkout` (code injection risk)
66
+ - Use Dependabot or Renovate to keep action versions current
67
+ - Add `concurrency` blocks to prevent parallel deploys
68
+
69
+ ## Concurrency Control
70
+
71
+ ```typescript
72
+ new Concurrency({
73
+ group: "${{ github.workflow }}-${{ github.ref }}",
74
+ "cancel-in-progress": true,
75
+ })
76
+ ```
77
+
78
+ ## Job Timeouts
79
+
80
+ Always set timeouts to prevent runaway jobs:
81
+
82
+ ```typescript
83
+ new Job({
84
+ "runs-on": "ubuntu-latest",
85
+ "timeout-minutes": 30,
86
+ steps: [...],
87
+ });
88
+ ```