@intentius/chant-lexicon-gitlab 0.0.6 → 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 (52) 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 +502 -0
  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 +88 -11
  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/package.ts +2 -0
  18. package/src/codegen/parse.test.ts +154 -4
  19. package/src/codegen/parse.ts +161 -49
  20. package/src/codegen/snapshot.test.ts +7 -5
  21. package/src/composites/composites.test.ts +452 -0
  22. package/src/composites/docker-build.ts +81 -0
  23. package/src/composites/index.ts +8 -0
  24. package/src/composites/node-pipeline.ts +104 -0
  25. package/src/composites/python-pipeline.ts +75 -0
  26. package/src/composites/review-app.ts +63 -0
  27. package/src/generated/index.d.ts +55 -16
  28. package/src/generated/index.ts +3 -0
  29. package/src/generated/lexicon-gitlab.json +186 -8
  30. package/src/import/generator.ts +3 -2
  31. package/src/import/parser.test.ts +3 -3
  32. package/src/import/parser.ts +12 -26
  33. package/src/index.ts +4 -0
  34. package/src/lint/post-synth/wgl012.test.ts +131 -0
  35. package/src/lint/post-synth/wgl012.ts +86 -0
  36. package/src/lint/post-synth/wgl013.test.ts +164 -0
  37. package/src/lint/post-synth/wgl013.ts +62 -0
  38. package/src/lint/post-synth/wgl014.test.ts +97 -0
  39. package/src/lint/post-synth/wgl014.ts +51 -0
  40. package/src/lint/post-synth/wgl015.test.ts +139 -0
  41. package/src/lint/post-synth/wgl015.ts +85 -0
  42. package/src/lint/post-synth/yaml-helpers.ts +65 -3
  43. package/src/lsp/completions.ts +2 -0
  44. package/src/lsp/hover.ts +2 -0
  45. package/src/plugin.test.ts +44 -19
  46. package/src/plugin.ts +671 -76
  47. package/src/serializer.test.ts +146 -6
  48. package/src/serializer.ts +64 -14
  49. package/src/validate.ts +1 -0
  50. package/src/variables.ts +4 -0
  51. package/dist/skills/gitlab-ci.md +0 -37
  52. package/src/codegen/rollback.ts +0 -26
package/src/plugin.ts CHANGED
@@ -5,7 +5,9 @@
5
5
  * for GitLab CI/CD pipelines.
6
6
  */
7
7
 
8
- import type { LexiconPlugin, IntrinsicDef, SkillDefinition } from "@intentius/chant/lexicon";
8
+ import { createRequire } from "module";
9
+ import type { LexiconPlugin, IntrinsicDef, SkillDefinition, InitTemplateSet } from "@intentius/chant/lexicon";
10
+ const require = createRequire(import.meta.url);
9
11
  import type { LintRule } from "@intentius/chant/lint/rule";
10
12
  import type { PostSynthCheck } from "@intentius/chant/lint/post-synth";
11
13
  import { gitlabSerializer } from "./serializer";
@@ -25,7 +27,11 @@ export const gitlabPlugin: LexiconPlugin = {
25
27
  postSynthChecks(): PostSynthCheck[] {
26
28
  const { wgl010 } = require("./lint/post-synth/wgl010");
27
29
  const { wgl011 } = require("./lint/post-synth/wgl011");
28
- 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];
29
35
  },
30
36
 
31
37
  intrinsics(): IntrinsicDef[] {
@@ -39,9 +45,81 @@ export const gitlabPlugin: LexiconPlugin = {
39
45
  ];
40
46
  },
41
47
 
42
- 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
43
120
  return {
44
- "config.ts": `/**
121
+ src: {
122
+ "config.ts": `/**
45
123
  * Shared pipeline configuration
46
124
  */
47
125
 
@@ -59,25 +137,33 @@ export const npmCache = new Cache({
59
137
  policy: "pull-push",
60
138
  });
61
139
  `,
62
- "test.ts": `/**
63
- * Test job
64
- */
65
-
66
- import { Job, Artifacts } from "@intentius/chant-lexicon-gitlab";
140
+ "pipeline.ts": `import { Job, Artifacts } from "@intentius/chant-lexicon-gitlab";
67
141
  import { defaultImage, npmCache } from "./config";
68
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
+
69
158
  export const test = new Job({
70
159
  stage: "test",
71
160
  image: defaultImage,
72
161
  cache: npmCache,
73
- script: ["npm ci", "npm test"],
74
- artifacts: new Artifacts({
75
- reports: { junit: "coverage/junit.xml" },
76
- paths: ["coverage/"],
77
- expireIn: "1 week",
78
- }),
162
+ script: ["npm install", "npm test"],
163
+ artifacts: testArtifacts,
79
164
  });
80
165
  `,
166
+ },
81
167
  };
82
168
  },
83
169
 
@@ -169,31 +255,6 @@ export const test = new Job({
169
255
  console.error(`Packaged ${stats.resources} entities, ${stats.ruleCount} rules, ${stats.skillCount} skills`);
170
256
  },
171
257
 
172
- async rollback(options?: { restore?: string; verbose?: boolean }): Promise<void> {
173
- const { listSnapshots, restoreSnapshot } = await import("./codegen/rollback");
174
- const { join, dirname } = await import("path");
175
- const { fileURLToPath } = await import("url");
176
-
177
- const pkgDir = dirname(dirname(fileURLToPath(import.meta.url)));
178
- const snapshotsDir = join(pkgDir, ".snapshots");
179
-
180
- if (options?.restore) {
181
- const generatedDir = join(pkgDir, "src", "generated");
182
- restoreSnapshot(String(options.restore), generatedDir);
183
- console.error(`Restored snapshot: ${options.restore}`);
184
- } else {
185
- const snapshots = listSnapshots(snapshotsDir);
186
- if (snapshots.length === 0) {
187
- console.error("No snapshots available.");
188
- } else {
189
- console.error(`Available snapshots (${snapshots.length}):`);
190
- for (const s of snapshots) {
191
- console.error(` ${s.timestamp} ${s.resourceCount} resources ${s.path}`);
192
- }
193
- }
194
- }
195
- },
196
-
197
258
  mcpTools() {
198
259
  return [
199
260
  {
@@ -283,49 +344,526 @@ export const deploy = new Job({
283
344
  skills(): SkillDefinition[] {
284
345
  return [
285
346
  {
286
- name: "gitlab-ci",
287
- description: "GitLab CI/CD best practices and common patterns",
347
+ name: "chant-gitlab",
348
+ description: "GitLab CI/CD pipeline lifecycle — build, validate, deploy, monitor, rollback, and troubleshoot",
288
349
  content: `---
289
- name: gitlab-ci
290
- description: GitLab CI/CD best practices and common patterns
350
+ skill: chant-gitlab
351
+ description: Build, validate, and deploy GitLab CI pipelines from a chant project
352
+ user-invocable: true
291
353
  ---
292
354
 
293
- # GitLab CI/CD with Chant
294
-
295
- ## Common Entity Types
296
-
297
- - \`Job\`Pipeline job definition
298
- - \`Default\` — Default settings inherited by all jobs
299
- - \`Workflow\` Pipeline-level configuration
300
- - \`Artifacts\` Job artifact configuration
301
- - \`Cache\` — Cache configuration
302
- - \`Image\` — Docker image for a job
303
- - \`Rule\` — Conditional execution rule
304
- - \`Environment\` Deployment environment
305
- - \`Trigger\` — Trigger downstream pipeline
306
- - \`Include\` Include external CI configuration
307
-
308
- ## Predefined Variables
309
-
310
- - \`CI.CommitBranch\` Current branch name
311
- - \`CI.CommitSha\` Current commit SHA
312
- - \`CI.PipelineSource\` What triggered the pipeline
313
- - \`CI.ProjectPath\` Project path (group/project)
314
- - \`CI.Registry\` — Container registry URL
315
- - \`CI.MergeRequestIid\` — MR internal ID
316
-
317
- ## Best Practices
318
-
319
- 1. **Use stages** — Organize jobs into logical stages (build, test, deploy)
320
- 2. **Cache dependencies** Cache node_modules, pip packages, etc.
321
- 3. **Use rules** — Prefer \`rules:\` over \`only:/except:\` for conditional execution
322
- 4. **Minimize artifacts** Only preserve files needed by later stages
323
- 5. **Use includes** Share common configuration across projects
324
- 6. **Set timeouts** Prevent stuck jobs from blocking pipelines
355
+ # GitLab CI/CD Operational Playbook
356
+
357
+ ## How chant and GitLab CI relate
358
+
359
+ chant is a **synthesis-only** toolit 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
363
+
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.
365
+
366
+ ## Scaffolding a new project
367
+
368
+ ### Initialize with a template
369
+
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
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
452
+ \`\`\`
453
+
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
470
+
471
+ \`\`\`bash
472
+ chant lint src/
473
+ \`\`\`
474
+
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
+
482
+ \`\`\`bash
483
+ curl --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \\
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
489
+ \`\`\`
490
+
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).
502
+
503
+ ## Diffing and change preview
504
+
505
+ ### Local diff
506
+
507
+ Compare generated \`.gitlab-ci.yml\` against the version on the remote branch:
508
+
509
+ \`\`\`bash
510
+ # Build the proposed config
511
+ chant build src/ --output .gitlab-ci.yml
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"
672
+ \`\`\`
673
+
674
+ ### Download job artifacts
675
+
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
683
+
684
+ ### How MR pipelines differ
685
+
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.
687
+
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
+ \`\`\`
704
+
705
+ ### Merged results pipelines
706
+
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.
708
+
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
+ \`\`\`
325
851
  `,
326
852
  triggers: [
327
853
  { type: "file-pattern", value: "**/*.gitlab.ts" },
854
+ { type: "file-pattern", value: "**/.gitlab-ci.yml" },
328
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",
329
867
  ],
330
868
  parameters: [],
331
869
  examples: [
@@ -346,6 +884,63 @@ description: GitLab CI/CD best practices and common patterns
346
884
  }),
347
885
  })`,
348
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
+ },
349
944
  ],
350
945
  },
351
946
  ];