@intentius/chant-lexicon-gitlab 0.0.8 → 0.0.9

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 (45) hide show
  1. package/dist/integrity.json +10 -6
  2. package/dist/manifest.json +1 -1
  3. package/dist/meta.json +186 -8
  4. package/dist/rules/wgl012.ts +86 -0
  5. package/dist/rules/wgl013.ts +62 -0
  6. package/dist/rules/wgl014.ts +51 -0
  7. package/dist/rules/wgl015.ts +85 -0
  8. package/dist/rules/yaml-helpers.ts +65 -3
  9. package/dist/skills/chant-gitlab.md +467 -24
  10. package/dist/types/index.d.ts +55 -16
  11. package/package.json +2 -2
  12. package/src/codegen/__snapshots__/snapshot.test.ts.snap +58 -0
  13. package/src/codegen/docs.ts +32 -9
  14. package/src/codegen/generate-lexicon.ts +6 -1
  15. package/src/codegen/generate.ts +45 -50
  16. package/src/codegen/naming.ts +3 -0
  17. package/src/codegen/parse.test.ts +154 -4
  18. package/src/codegen/parse.ts +161 -49
  19. package/src/codegen/snapshot.test.ts +7 -5
  20. package/src/composites/composites.test.ts +452 -0
  21. package/src/composites/docker-build.ts +81 -0
  22. package/src/composites/index.ts +8 -0
  23. package/src/composites/node-pipeline.ts +104 -0
  24. package/src/composites/python-pipeline.ts +75 -0
  25. package/src/composites/review-app.ts +63 -0
  26. package/src/generated/index.d.ts +55 -16
  27. package/src/generated/index.ts +3 -0
  28. package/src/generated/lexicon-gitlab.json +186 -8
  29. package/src/import/generator.ts +3 -2
  30. package/src/index.ts +4 -0
  31. package/src/lint/post-synth/wgl012.test.ts +131 -0
  32. package/src/lint/post-synth/wgl012.ts +86 -0
  33. package/src/lint/post-synth/wgl013.test.ts +164 -0
  34. package/src/lint/post-synth/wgl013.ts +62 -0
  35. package/src/lint/post-synth/wgl014.test.ts +97 -0
  36. package/src/lint/post-synth/wgl014.ts +51 -0
  37. package/src/lint/post-synth/wgl015.test.ts +139 -0
  38. package/src/lint/post-synth/wgl015.ts +85 -0
  39. package/src/lint/post-synth/yaml-helpers.ts +65 -3
  40. package/src/plugin.test.ts +39 -13
  41. package/src/plugin.ts +636 -40
  42. package/src/serializer.test.ts +140 -0
  43. package/src/serializer.ts +63 -5
  44. package/src/validate.ts +1 -0
  45. package/src/variables.ts +4 -0
package/src/plugin.ts CHANGED
@@ -6,7 +6,7 @@
6
6
  */
7
7
 
8
8
  import { createRequire } from "module";
9
- import type { LexiconPlugin, IntrinsicDef, SkillDefinition } from "@intentius/chant/lexicon";
9
+ import type { LexiconPlugin, IntrinsicDef, SkillDefinition, InitTemplateSet } from "@intentius/chant/lexicon";
10
10
  const require = createRequire(import.meta.url);
11
11
  import type { LintRule } from "@intentius/chant/lint/rule";
12
12
  import type { PostSynthCheck } from "@intentius/chant/lint/post-synth";
@@ -27,7 +27,11 @@ export const gitlabPlugin: LexiconPlugin = {
27
27
  postSynthChecks(): PostSynthCheck[] {
28
28
  const { wgl010 } = require("./lint/post-synth/wgl010");
29
29
  const { wgl011 } = require("./lint/post-synth/wgl011");
30
- return [wgl010, wgl011];
30
+ const { wgl012 } = require("./lint/post-synth/wgl012");
31
+ const { wgl013 } = require("./lint/post-synth/wgl013");
32
+ const { wgl014 } = require("./lint/post-synth/wgl014");
33
+ const { wgl015 } = require("./lint/post-synth/wgl015");
34
+ return [wgl010, wgl011, wgl012, wgl013, wgl014, wgl015];
31
35
  },
32
36
 
33
37
  intrinsics(): IntrinsicDef[] {
@@ -41,9 +45,81 @@ export const gitlabPlugin: LexiconPlugin = {
41
45
  ];
42
46
  },
43
47
 
44
- initTemplates(): Record<string, string> {
48
+ initTemplates(template?: string): InitTemplateSet {
49
+ if (template === "node-pipeline") {
50
+ return {
51
+ src: {
52
+ "pipeline.ts": `import { NodePipeline } from "@intentius/chant-lexicon-gitlab";
53
+
54
+ export const app = NodePipeline({
55
+ nodeVersion: "22",
56
+ installCommand: "npm install",
57
+ buildScript: "build",
58
+ testScript: "test",
59
+ });
60
+ `,
61
+ },
62
+ };
63
+ }
64
+
65
+ if (template === "python-pipeline") {
66
+ return {
67
+ src: {
68
+ "pipeline.ts": `import { PythonPipeline } from "@intentius/chant-lexicon-gitlab";
69
+
70
+ export const app = PythonPipeline({
71
+ pythonVersion: "3.12",
72
+ lintCommand: null,
73
+ });
74
+ `,
75
+ },
76
+ };
77
+ }
78
+
79
+ if (template === "docker-build") {
80
+ return {
81
+ src: {
82
+ "pipeline.ts": `import { DockerBuild, Job, Image } from "@intentius/chant-lexicon-gitlab";
83
+
84
+ export const docker = DockerBuild({
85
+ dockerfile: "Dockerfile",
86
+ tagLatest: true,
87
+ });
88
+
89
+ export const test = new Job({
90
+ stage: "test",
91
+ image: new Image({ name: "node:22-alpine" }),
92
+ script: ["node test.js"],
93
+ });
94
+ `,
95
+ },
96
+ };
97
+ }
98
+
99
+ if (template === "review-app") {
100
+ return {
101
+ src: {
102
+ "pipeline.ts": `import { ReviewApp, Job, Image } from "@intentius/chant-lexicon-gitlab";
103
+
104
+ export const review = ReviewApp({
105
+ name: "review",
106
+ deployScript: "echo deploy",
107
+ });
108
+
109
+ export const test = new Job({
110
+ stage: "test",
111
+ image: new Image({ name: "node:22-alpine" }),
112
+ script: ["node test.js"],
113
+ });
114
+ `,
115
+ },
116
+ };
117
+ }
118
+
119
+ // Default template — basic pipeline with shared config
45
120
  return {
46
- "config.ts": `/**
121
+ src: {
122
+ "config.ts": `/**
47
123
  * Shared pipeline configuration
48
124
  */
49
125
 
@@ -61,25 +137,33 @@ export const npmCache = new Cache({
61
137
  policy: "pull-push",
62
138
  });
63
139
  `,
64
- "test.ts": `/**
65
- * Test job
66
- */
67
-
68
- import { Job, Artifacts } from "@intentius/chant-lexicon-gitlab";
140
+ "pipeline.ts": `import { Job, Artifacts } from "@intentius/chant-lexicon-gitlab";
69
141
  import { defaultImage, npmCache } from "./config";
70
142
 
143
+ export const junitReports = { junit: "coverage/junit.xml" };
144
+
145
+ export const testArtifacts = new Artifacts({
146
+ reports: junitReports,
147
+ paths: ["coverage/"],
148
+ expire_in: "1 week",
149
+ });
150
+
151
+ export const build = new Job({
152
+ stage: "build",
153
+ image: defaultImage,
154
+ cache: npmCache,
155
+ script: ["npm install", "npm run build"],
156
+ });
157
+
71
158
  export const test = new Job({
72
159
  stage: "test",
73
160
  image: defaultImage,
74
161
  cache: npmCache,
75
- script: ["npm ci", "npm test"],
76
- artifacts: new Artifacts({
77
- reports: { junit: "coverage/junit.xml" },
78
- paths: ["coverage/"],
79
- expireIn: "1 week",
80
- }),
162
+ script: ["npm install", "npm test"],
163
+ artifacts: testArtifacts,
81
164
  });
82
165
  `,
166
+ },
83
167
  };
84
168
  },
85
169
 
@@ -261,70 +345,525 @@ export const deploy = new Job({
261
345
  return [
262
346
  {
263
347
  name: "chant-gitlab",
264
- description: "GitLab CI/CD pipeline managementworkflows, patterns, and troubleshooting",
348
+ description: "GitLab CI/CD pipeline lifecyclebuild, validate, deploy, monitor, rollback, and troubleshoot",
265
349
  content: `---
266
350
  skill: chant-gitlab
267
351
  description: Build, validate, and deploy GitLab CI pipelines from a chant project
268
352
  user-invocable: true
269
353
  ---
270
354
 
271
- # Deploying GitLab CI Pipelines from Chant
355
+ # GitLab CI/CD Operational Playbook
356
+
357
+ ## How chant and GitLab CI relate
358
+
359
+ chant is a **synthesis-only** tool — it compiles TypeScript source files into \`.gitlab-ci.yml\` (YAML). chant does NOT call GitLab APIs. Your job as an agent is to bridge the two:
360
+
361
+ - Use **chant** for: build, lint, diff (local YAML comparison)
362
+ - Use **git + GitLab API** for: push, merge requests, pipeline monitoring, job logs, rollback, and all deployment operations
272
363
 
273
- This project defines GitLab CI jobs as TypeScript in \`src/\`. Use these steps to build, validate, and deploy.
364
+ The source of truth for pipeline configuration is the TypeScript in \`src/\`. The generated \`.gitlab-ci.yml\` is an intermediate artifact — never edit it by hand.
274
365
 
275
- ## Build the pipeline
366
+ ## Scaffolding a new project
367
+
368
+ ### Initialize with a template
276
369
 
277
370
  \`\`\`bash
371
+ chant init --lexicon gitlab # default: config.ts + pipeline.ts
372
+ chant init --lexicon gitlab --template node-pipeline # NodePipeline composite
373
+ chant init --lexicon gitlab --template python-pipeline # PythonPipeline composite
374
+ chant init --lexicon gitlab --template docker-build # DockerBuild composite
375
+ chant init --lexicon gitlab --template review-app # ReviewApp composite
376
+ \`\`\`
377
+
378
+ This creates \`src/\` with chant pipeline definitions. It does NOT create application files — you bring your own app code.
379
+
380
+ ### Available templates
381
+
382
+ | Template | What it generates | Best for |
383
+ |----------|-------------------|----------|
384
+ | *(default)* | \`config.ts\` + \`pipeline.ts\` with build/test jobs | Custom pipelines from scratch |
385
+ | \`node-pipeline\` | \`NodePipeline\` composite with npm install/build/test | Node.js apps |
386
+ | \`python-pipeline\` | \`PythonPipeline\` composite with venv/pytest | Python apps |
387
+ | \`docker-build\` | \`DockerBuild\` composite + test job | Containerized apps |
388
+ | \`review-app\` | \`ReviewApp\` composite + test job | Apps needing per-MR environments |
389
+
390
+ ## Deploying to GitLab
391
+
392
+ ### What goes in the GitLab repo
393
+
394
+ The GitLab repo needs TWO things:
395
+ 1. **\`.gitlab-ci.yml\`** — generated by \`chant build\`
396
+ 2. **Your application files** — whatever the pipeline scripts reference
397
+
398
+ chant only generates the YAML. Application files (\`package.json\`, \`Dockerfile\`, \`requirements.txt\`, source code, tests) are your responsibility.
399
+
400
+ ### Typical project structure in the GitLab repo
401
+
402
+ **Node.js project:**
403
+ \`\`\`
404
+ .gitlab-ci.yml # generated by chant
405
+ package.json # your app's package.json with build/test scripts
406
+ index.js # your app code
407
+ test.js # your tests
408
+ \`\`\`
409
+
410
+ **Python project:**
411
+ \`\`\`
412
+ .gitlab-ci.yml
413
+ requirements.txt # must include pytest, pytest-cov if using PythonPipeline defaults
414
+ app.py
415
+ test_app.py
416
+ \`\`\`
417
+
418
+ **Docker project:**
419
+ \`\`\`
420
+ .gitlab-ci.yml
421
+ Dockerfile
422
+ src/ # your app source
423
+ \`\`\`
424
+
425
+ ### Important: npm ci vs npm install
426
+
427
+ The \`NodePipeline\` composite defaults to \`npm ci\`, which requires a \`package-lock.json\` in the repo. If your repo does not have a lockfile, override with:
428
+
429
+ \`\`\`typescript
430
+ NodePipeline({
431
+ installCommand: "npm install", // use instead of npm ci
432
+ ...
433
+ })
434
+ \`\`\`
435
+
436
+ Or generate a lockfile: \`npm install && git add package-lock.json\`.
437
+
438
+ ### Step-by-step: push to GitLab
439
+
440
+ \`\`\`bash
441
+ # 1. Build the YAML from chant source
278
442
  chant build src/ --output .gitlab-ci.yml
443
+
444
+ # 2. Initialize git (if needed) and commit everything
445
+ git init -b main
446
+ git add .gitlab-ci.yml package.json index.js test.js # add your app files
447
+ git commit -m "Initial pipeline"
448
+
449
+ # 3. Push to GitLab
450
+ git remote add origin git@gitlab.com:YOUR_GROUP/YOUR_PROJECT.git
451
+ git push -u origin main
279
452
  \`\`\`
280
453
 
281
- ## Validate before pushing
454
+ The pipeline triggers automatically on push. Do NOT commit the chant \`src/\` directory, \`node_modules/\`, or \`.chant/\` to the GitLab repo — those are local development files.
455
+
456
+ ## Build and validate
457
+
458
+ ### Build the pipeline
459
+
460
+ \`\`\`bash
461
+ chant build src/ --output .gitlab-ci.yml
462
+ \`\`\`
463
+
464
+ Options:
465
+ - \`--format yaml\` — emit YAML (default for GitLab)
466
+ - \`--watch\` — rebuild on source changes
467
+ - \`--output <path>\` — write to a specific file
468
+
469
+ ### Lint the source
282
470
 
283
471
  \`\`\`bash
284
472
  chant lint src/
285
473
  \`\`\`
286
474
 
287
- For API-level validation against your GitLab instance:
475
+ Options:
476
+ - \`--fix\` — auto-fix violations where possible
477
+ - \`--format sarif\` — SARIF output for CI integration
478
+ - \`--watch\` — re-lint on changes
479
+
480
+ ### Validate with GitLab CI Lint API
481
+
288
482
  \`\`\`bash
289
483
  curl --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \\
290
- "https://gitlab.com/api/v4/ci/lint" \\
291
- --data "{\\"content\\": \\"$(cat .gitlab-ci.yml)\\"}"
484
+ --header "Content-Type: application/json" \\
485
+ "https://gitlab.com/api/v4/projects/$PROJECT_ID/ci/lint" \\
486
+ --data-binary @- <<EOF
487
+ {"content": $(cat .gitlab-ci.yml | jq -Rs .)}
488
+ EOF
292
489
  \`\`\`
293
490
 
294
- ## Deploy
491
+ Add \`"dry_run": true, "include_merged_yaml": true\` for full expansion with includes resolved.
492
+
493
+ ### What each step catches
494
+
495
+ | Step | Catches | When to run |
496
+ |------|---------|-------------|
497
+ | \`chant lint\` | Deprecated only/except (WGL001), missing script (WGL002), missing stage (WGL003), artifacts without expiry (WGL004) | Every edit |
498
+ | \`chant build\` | Post-synth checks: undefined stages (WGL010), unreachable jobs (WGL011), deprecated properties (WGL012), invalid needs targets (WGL013), invalid extends targets (WGL014), circular needs chains (WGL015) | Before push |
499
+ | CI Lint API | GitLab-specific validation: include resolution, variable expansion, YAML schema errors | Before merge to default branch |
500
+
501
+ Always run all three before deploying. Lint catches things the API cannot (and vice versa).
295
502
 
296
- Commit and push the generated \`.gitlab-ci.yml\` — GitLab runs the pipeline automatically:
503
+ ## Diffing and change preview
504
+
505
+ ### Local diff
506
+
507
+ Compare generated \`.gitlab-ci.yml\` against the version on the remote branch:
297
508
 
298
509
  \`\`\`bash
510
+ # Build the proposed config
299
511
  chant build src/ --output .gitlab-ci.yml
300
- git add .gitlab-ci.yml
301
- git commit -m "Update pipeline"
302
- git push
512
+
513
+ # Diff against the remote version
514
+ git diff origin/main -- .gitlab-ci.yml
515
+ \`\`\`
516
+
517
+ ### MR pipeline preview
518
+
519
+ Push to a branch and open a merge request — GitLab shows the pipeline that would run without executing it. This is the safest way to preview pipeline changes for production.
520
+
521
+ ### CI Lint API with dry run
522
+
523
+ \`\`\`bash
524
+ curl --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \\
525
+ --header "Content-Type: application/json" \\
526
+ "https://gitlab.com/api/v4/projects/$PROJECT_ID/ci/lint" \\
527
+ --data-binary @- <<EOF
528
+ {"content": $(cat .gitlab-ci.yml | jq -Rs .), "dry_run": true, "include_merged_yaml": true}
529
+ EOF
530
+ \`\`\`
531
+
532
+ This resolves all \`include:\` directives and expands the full pipeline — useful for catching issues with cross-file references.
533
+
534
+ ### Safe preview checklist
535
+
536
+ Before merging pipeline changes, verify:
537
+ 1. All jobs have valid \`stage:\` values and stages are defined
538
+ 2. \`needs:\` references point to existing job names
539
+ 3. \`rules:\` conditions match the intended branches/events
540
+ 4. Environment names and \`on_stop\` references are correct
541
+ 5. Docker images are accessible from your runners
542
+ 6. Secrets/variables referenced in scripts exist in project settings
543
+
544
+ ## Deploying pipeline changes
545
+
546
+ ### Safe path (production pipelines)
547
+
548
+ 1. Build: \`chant build src/ --output .gitlab-ci.yml\`
549
+ 2. Lint: \`chant lint src/\`
550
+ 3. Validate: CI Lint API (see above)
551
+ 4. Push to feature branch: \`git push -u origin feature/pipeline-update\`
552
+ 5. Open MR — review pipeline diff in the MR widget
553
+ 6. Merge — pipeline runs on the default branch
554
+
555
+ ### Fast path (dev/iteration)
556
+
557
+ \`\`\`bash
558
+ chant build src/ --output .gitlab-ci.yml
559
+ git add .gitlab-ci.yml && git commit -m "Update pipeline" && git push
560
+ \`\`\`
561
+
562
+ ### Which path to use
563
+
564
+ | Scenario | Path |
565
+ |----------|------|
566
+ | Production pipeline with deploy jobs | Safe path (MR review) |
567
+ | Pipeline with environment/secrets changes | Safe path (MR review) |
568
+ | Dev/test pipeline iteration | Fast path (direct push) |
569
+ | CI/CD with approval gates or protected environments | Safe path + protected branch |
570
+
571
+ ## Environment lifecycle
572
+
573
+ Environments are created by jobs with an \`environment:\` keyword. They track deployments and enable rollback.
574
+
575
+ ### Review apps pattern
576
+
577
+ Deploy on MR, auto-stop when MR is merged or closed:
578
+
579
+ \`\`\`typescript
580
+ new Job({
581
+ stage: "deploy",
582
+ environment: new Environment({
583
+ name: "review/$CI_COMMIT_REF_SLUG",
584
+ url: "https://$CI_COMMIT_REF_SLUG.example.com",
585
+ onStop: "stop_review",
586
+ autoStopIn: "1 week",
587
+ }),
588
+ script: ["./deploy-review.sh"],
589
+ rules: [{ if: "$CI_MERGE_REQUEST_IID" }],
590
+ });
591
+ \`\`\`
592
+
593
+ ### Environment promotion
594
+
595
+ Deploy through environments in order: dev → staging → production. Use \`rules:\` and \`when: manual\` to gate promotions.
596
+
597
+ ### Rollback to a previous deployment
598
+
599
+ \`\`\`bash
600
+ # List deployments for an environment
601
+ curl --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \\
602
+ "https://gitlab.com/api/v4/projects/$PROJECT_ID/environments/$ENV_ID/deployments?order_by=created_at&sort=desc&per_page=5"
603
+
604
+ # Re-deploy a previous deployment's commit
605
+ curl --request POST --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \\
606
+ "https://gitlab.com/api/v4/projects/$PROJECT_ID/deployments" \\
607
+ --data "environment=production&sha=$PREVIOUS_SHA&ref=main&tag=false&status=created"
608
+ \`\`\`
609
+
610
+ Alternatively, revert the MR that introduced the change and let the pipeline re-run.
611
+
612
+ ## Pipeline and job states
613
+
614
+ ### Pipeline states
615
+
616
+ | State | Meaning | Action |
617
+ |-------|---------|--------|
618
+ | \`created\` | Pipeline created, not yet started | Wait |
619
+ | \`waiting_for_resource\` | Waiting for runner | Check runner availability |
620
+ | \`preparing\` | Job is being prepared | Wait |
621
+ | \`pending\` | Waiting for runner to pick up | Check runner tags/availability |
622
+ | \`running\` | Pipeline is executing | Monitor |
623
+ | \`success\` | All jobs passed | None — healthy |
624
+ | \`failed\` | One or more jobs failed | Check failed job logs |
625
+ | \`canceled\` | Pipeline was canceled | Re-run if needed |
626
+ | \`skipped\` | Pipeline was skipped by rules | Check rules configuration |
627
+ | \`manual\` | Pipeline waiting for manual action | Trigger manual job or cancel |
628
+ | \`scheduled\` | Waiting for scheduled time | Wait |
629
+
630
+ ### Job states
631
+
632
+ | State | Meaning | Action |
633
+ |-------|---------|--------|
634
+ | \`created\` | Job created | Wait |
635
+ | \`pending\` | Waiting for runner | Check runner tags |
636
+ | \`running\` | Job executing | Monitor logs |
637
+ | \`success\` | Job passed | None |
638
+ | \`failed\` | Job failed | Read trace log |
639
+ | \`canceled\` | Job canceled | Re-run if needed |
640
+ | \`skipped\` | Job skipped by rules/needs | Check rules |
641
+ | \`manual\` | Waiting for manual trigger | Play or skip |
642
+ | \`allowed_failure\` | Failed but allowed | Review — may indicate flaky test |
643
+
644
+ ## Monitoring pipelines
645
+
646
+ ### Check pipeline status
647
+
648
+ \`\`\`bash
649
+ curl --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \\
650
+ "https://gitlab.com/api/v4/projects/$PROJECT_ID/pipelines/$PIPELINE_ID"
651
+ \`\`\`
652
+
653
+ ### List recent pipelines for a branch
654
+
655
+ \`\`\`bash
656
+ curl --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \\
657
+ "https://gitlab.com/api/v4/projects/$PROJECT_ID/pipelines?ref=main&per_page=5"
658
+ \`\`\`
659
+
660
+ ### Get jobs in a pipeline
661
+
662
+ \`\`\`bash
663
+ curl --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \\
664
+ "https://gitlab.com/api/v4/projects/$PROJECT_ID/pipelines/$PIPELINE_ID/jobs"
665
+ \`\`\`
666
+
667
+ ### Stream job logs
668
+
669
+ \`\`\`bash
670
+ curl --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \\
671
+ "https://gitlab.com/api/v4/projects/$PROJECT_ID/jobs/$JOB_ID/trace"
303
672
  \`\`\`
304
673
 
305
- ## Check pipeline status
674
+ ### Download job artifacts
306
675
 
307
- - GitLab UI: project → CI/CD → Pipelines
308
- - API: \`curl --header "PRIVATE-TOKEN: $GITLAB_TOKEN" "https://gitlab.com/api/v4/projects/$PROJECT_ID/pipelines?per_page=5"\`
676
+ \`\`\`bash
677
+ curl --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \\
678
+ --output artifacts.zip \\
679
+ "https://gitlab.com/api/v4/projects/$PROJECT_ID/jobs/$JOB_ID/artifacts"
680
+ \`\`\`
681
+
682
+ ## Merge request pipeline workflow
309
683
 
310
- ## Retry a failed job
684
+ ### How MR pipelines differ
311
685
 
312
- - GitLab UI: click Retry on the failed job
313
- - API: \`curl --request POST --header "PRIVATE-TOKEN: $GITLAB_TOKEN" "https://gitlab.com/api/v4/projects/$PROJECT_ID/jobs/$JOB_ID/retry"\`
686
+ MR pipelines run in a merge request context with \`CI_MERGE_REQUEST_IID\` available. Branch pipelines run on push with \`CI_COMMIT_BRANCH\`. A job cannot have both — use \`rules:\` to target one or the other.
314
687
 
315
- ## Cancel a running pipeline
688
+ ### Common rules patterns
689
+
690
+ \`\`\`yaml
691
+ # Run only on MR pipelines
692
+ rules:
693
+ - if: $CI_MERGE_REQUEST_IID
694
+
695
+ # Run only on the default branch
696
+ rules:
697
+ - if: $CI_COMMIT_BRANCH == "main"
698
+
699
+ # Run on MRs and the default branch (but not both at once)
700
+ rules:
701
+ - if: $CI_MERGE_REQUEST_IID
702
+ - if: $CI_COMMIT_BRANCH == "main"
703
+ \`\`\`
316
704
 
317
- - API: \`curl --request POST --header "PRIVATE-TOKEN: $GITLAB_TOKEN" "https://gitlab.com/api/v4/projects/$PROJECT_ID/pipelines/$PIPELINE_ID/cancel"\`
705
+ ### Merged results pipelines
318
706
 
319
- ## Troubleshooting
707
+ Enable in project settings → CI/CD → General pipelines → "Merged results pipelines". These test the result of merging your branch into the target — catching integration issues before merge.
320
708
 
321
- - Check job logs in GitLab UI: project → CI/CD → Jobs → click the job
322
- - \`chant lint src/\` catches: missing scripts (WGL002), deprecated only/except (WGL001), missing stages (WGL003), artifacts without expiry (WGL004)
323
- - Post-synth checks (WGL010, WGL011) run during build
709
+ ### Merge trains
710
+
711
+ Merge trains queue MRs and test each one merged on top of the previous. Enable in project settings → Merge requests → "Merge trains". Requires merged results pipelines.
712
+
713
+ ## Troubleshooting decision tree
714
+
715
+ ### Step 1: Check pipeline status
716
+
717
+ \`\`\`bash
718
+ curl --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \\
719
+ "https://gitlab.com/api/v4/projects/$PROJECT_ID/pipelines/$PIPELINE_ID" | jq '.status'
720
+ \`\`\`
721
+
722
+ ### Step 2: Branch on status
723
+
724
+ - **\`running\` / \`pending\` / \`created\`** → Wait. Do not take action while the pipeline is in progress.
725
+ - **\`failed\`** → Read the failed job logs (Step 3).
726
+ - **\`success\`** → Pipeline is healthy. If behavior is wrong, check job scripts and configuration.
727
+ - **\`canceled\`** → Re-run if needed: \`curl --request POST ... /pipelines/$PIPELINE_ID/retry\`
728
+ - **\`skipped\`** → All jobs were filtered out by \`rules:\`. Check rule conditions.
729
+
730
+ ### Step 3: Read failed job logs
731
+
732
+ \`\`\`bash
733
+ # Get failed jobs
734
+ curl --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \\
735
+ "https://gitlab.com/api/v4/projects/$PROJECT_ID/pipelines/$PIPELINE_ID/jobs?scope=failed" | jq '.[].id'
736
+
737
+ # Read the trace for a failed job
738
+ curl --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \\
739
+ "https://gitlab.com/api/v4/projects/$PROJECT_ID/jobs/$JOB_ID/trace"
740
+ \`\`\`
741
+
742
+ ### Step 4: Diagnose by error pattern
743
+
744
+ | Error pattern | Likely cause | Fix |
745
+ |---------------|-------------|-----|
746
+ | "no matching runner" | No runner with matching tags | Check runner tags, register a runner |
747
+ | "image pull failed" | Docker image not found or auth failed | Check image name, registry credentials |
748
+ | "script exit code 1" | Script command failed | Read job log for the failing command |
749
+ | "artifact upload failed" | Artifact path doesn't exist or too large | Check \`artifacts.paths\`, size limits |
750
+ | "cache not found" | Cache key mismatch or first run | Expected on first run; check \`cache.key\` |
751
+ | "yaml invalid" | Syntax error in generated YAML | Run \`chant lint src/\` and CI Lint API |
752
+ | "pipeline filtered out" | All jobs filtered by rules | Check \`rules:\` conditions |
753
+ | "job timed out" | Job exceeded timeout | Increase \`timeout:\` or optimize job |
754
+ | "stuck or pending" | No available runner | Check runner status, tags, executor capacity |
755
+ | "environment does not exist" | \`on_stop\` references non-existent job | Check \`on_stop\` job name matches expanded name |
756
+ | "needs job not found" | \`needs:\` references non-existent job | Check job names, stage ordering |
757
+
758
+ ## Variable management
759
+
760
+ ### Variable types and precedence
761
+
762
+ Variables are resolved in this order (highest priority first):
763
+ 1. Job-level \`variables:\`
764
+ 2. Project CI/CD variables (Settings → CI/CD → Variables)
765
+ 3. Group CI/CD variables
766
+ 4. Instance CI/CD variables
767
+
768
+ ### Protected and masked variables
769
+
770
+ - **Protected**: only available in pipelines on protected branches/tags
771
+ - **Masked**: hidden in job logs (value must meet masking requirements)
772
+
773
+ ### Managing variables via API
774
+
775
+ \`\`\`bash
776
+ # List project variables
777
+ curl --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \\
778
+ "https://gitlab.com/api/v4/projects/$PROJECT_ID/variables"
779
+
780
+ # Create a variable
781
+ curl --request POST --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \\
782
+ "https://gitlab.com/api/v4/projects/$PROJECT_ID/variables" \\
783
+ --form "key=DEPLOY_TOKEN" --form "value=secret" --form "masked=true" --form "protected=true"
784
+
785
+ # Update a variable
786
+ curl --request PUT --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \\
787
+ "https://gitlab.com/api/v4/projects/$PROJECT_ID/variables/DEPLOY_TOKEN" \\
788
+ --form "value=new-secret"
789
+
790
+ # Delete a variable
791
+ curl --request DELETE --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \\
792
+ "https://gitlab.com/api/v4/projects/$PROJECT_ID/variables/DEPLOY_TOKEN"
793
+ \`\`\`
794
+
795
+ ## Quick reference
796
+
797
+ ### Pipeline info commands
798
+
799
+ \`\`\`bash
800
+ # List recent pipelines
801
+ curl --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \\
802
+ "https://gitlab.com/api/v4/projects/$PROJECT_ID/pipelines?per_page=5"
803
+
804
+ # Get pipeline status
805
+ curl --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \\
806
+ "https://gitlab.com/api/v4/projects/$PROJECT_ID/pipelines/$PIPELINE_ID"
807
+
808
+ # Get jobs in a pipeline
809
+ curl --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \\
810
+ "https://gitlab.com/api/v4/projects/$PROJECT_ID/pipelines/$PIPELINE_ID/jobs"
811
+
812
+ # Read job log
813
+ curl --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \\
814
+ "https://gitlab.com/api/v4/projects/$PROJECT_ID/jobs/$JOB_ID/trace"
815
+
816
+ # Retry a failed pipeline
817
+ curl --request POST --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \\
818
+ "https://gitlab.com/api/v4/projects/$PROJECT_ID/pipelines/$PIPELINE_ID/retry"
819
+
820
+ # Cancel a running pipeline
821
+ curl --request POST --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \\
822
+ "https://gitlab.com/api/v4/projects/$PROJECT_ID/pipelines/$PIPELINE_ID/cancel"
823
+ \`\`\`
824
+
825
+ ### Full build-to-deploy pipeline
826
+
827
+ \`\`\`bash
828
+ # 1. Lint
829
+ chant lint src/
830
+
831
+ # 2. Build
832
+ chant build src/ --output .gitlab-ci.yml
833
+
834
+ # 3. Validate via API
835
+ curl --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \\
836
+ --header "Content-Type: application/json" \\
837
+ "https://gitlab.com/api/v4/projects/$PROJECT_ID/ci/lint" \\
838
+ --data-binary @- <<EOF
839
+ {"content": $(cat .gitlab-ci.yml | jq -Rs .)}
840
+ EOF
841
+
842
+ # 4. Push to feature branch
843
+ git checkout -b feature/pipeline-update
844
+ git add .gitlab-ci.yml
845
+ git commit -m "Update pipeline"
846
+ git push -u origin feature/pipeline-update
847
+
848
+ # 5. Open MR, review pipeline diff, merge
849
+ # Pipeline runs automatically on the default branch after merge
850
+ \`\`\`
324
851
  `,
325
852
  triggers: [
326
853
  { type: "file-pattern", value: "**/*.gitlab.ts" },
854
+ { type: "file-pattern", value: "**/.gitlab-ci.yml" },
327
855
  { type: "context", value: "gitlab" },
856
+ { type: "context", value: "pipeline" },
857
+ { type: "context", value: "deploy" },
858
+ ],
859
+ preConditions: [
860
+ "chant CLI is installed (chant --version succeeds)",
861
+ "git is configured and can push to the remote",
862
+ "Project has chant source files in src/",
863
+ ],
864
+ postConditions: [
865
+ "Pipeline is in a stable state (success/manual/scheduled)",
866
+ "No failed jobs in the pipeline",
328
867
  ],
329
868
  parameters: [],
330
869
  examples: [
@@ -345,6 +884,63 @@ git push
345
884
  }),
346
885
  })`,
347
886
  },
887
+ {
888
+ title: "Deploy pipeline update",
889
+ description: "Build, validate, and deploy a pipeline change via MR workflow",
890
+ input: "Deploy my pipeline changes to production",
891
+ output: `chant lint src/
892
+ chant build src/ --output .gitlab-ci.yml
893
+ git checkout -b feature/pipeline-update
894
+ git add .gitlab-ci.yml
895
+ git commit -m "Update pipeline"
896
+ git push -u origin feature/pipeline-update
897
+ # Open MR in GitLab, review pipeline diff, then merge`,
898
+ },
899
+ {
900
+ title: "Preview pipeline changes",
901
+ description: "Validate pipeline configuration via lint and CI Lint API before deploying",
902
+ input: "Check if my pipeline changes are valid before pushing",
903
+ output: `chant lint src/
904
+ chant build src/ --output .gitlab-ci.yml
905
+ # Validate via GitLab CI Lint API
906
+ curl --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \\
907
+ --header "Content-Type: application/json" \\
908
+ "https://gitlab.com/api/v4/projects/$PROJECT_ID/ci/lint" \\
909
+ --data-binary '{"content": "'$(cat .gitlab-ci.yml | jq -Rs .)'", "dry_run": true}'`,
910
+ },
911
+ {
912
+ title: "Scaffold and deploy a Node.js pipeline",
913
+ description: "Use --template to scaffold a Node.js project, build YAML, and push to GitLab",
914
+ input: "Create a Node.js CI pipeline and deploy it to GitLab",
915
+ output: `# Scaffold the project
916
+ chant init --lexicon gitlab --template node-pipeline my-node-app
917
+ cd my-node-app
918
+
919
+ # Build the YAML
920
+ chant build src/ --output .gitlab-ci.yml
921
+
922
+ # The GitLab repo needs app files — create them
923
+ echo '{"scripts":{"build":"echo build","test":"node test.js"}}' > package.json
924
+ echo 'console.log("ok")' > test.js
925
+
926
+ # Push to GitLab
927
+ git init -b main
928
+ git add .gitlab-ci.yml package.json test.js
929
+ git commit -m "Initial pipeline"
930
+ git remote add origin git@gitlab.com:YOUR_GROUP/YOUR_PROJECT.git
931
+ git push -u origin main`,
932
+ },
933
+ {
934
+ title: "Retry a failed pipeline",
935
+ description: "Retry a failed pipeline and monitor its progress",
936
+ input: "Pipeline 12345 failed, retry it",
937
+ output: `# Retry the pipeline
938
+ curl --request POST --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \\
939
+ "https://gitlab.com/api/v4/projects/$PROJECT_ID/pipelines/12345/retry"
940
+ # Monitor status
941
+ curl --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \\
942
+ "https://gitlab.com/api/v4/projects/$PROJECT_ID/pipelines/12345"`,
943
+ },
348
944
  ],
349
945
  },
350
946
  ];