@intentius/chant-lexicon-gitlab 0.124.31 → 0.124.32

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 (133) hide show
  1. package/dist/codegen/docs-cli.js +2 -0
  2. package/dist/codegen/docs.js +157 -0
  3. package/dist/codegen/fetch.js +93 -0
  4. package/dist/codegen/generate-cli.js +30 -0
  5. package/dist/codegen/generate-lexicon.js +36 -0
  6. package/dist/codegen/generate-typescript.js +105 -0
  7. package/dist/codegen/generate.js +125 -0
  8. package/dist/codegen/naming.js +48 -0
  9. package/dist/codegen/package.js +41 -0
  10. package/dist/codegen/parse.js +539 -0
  11. package/dist/codegen/patches.js +84 -0
  12. package/dist/codegen/typecheck.js +4 -0
  13. package/dist/components/ci-jobs.js +16 -0
  14. package/dist/components/generate-op-pipeline.js +288 -0
  15. package/dist/components/generate-op-waves-pipeline.js +125 -0
  16. package/dist/components/generate-pipeline.d.ts.map +1 -1
  17. package/dist/components/generate-pipeline.js +369 -0
  18. package/dist/composites/catalog.js +418 -0
  19. package/dist/composites/docker-build.js +38 -0
  20. package/dist/composites/index.js +5 -0
  21. package/dist/composites/mr-plan-report.js +42 -0
  22. package/dist/composites/node-pipeline.js +67 -0
  23. package/dist/composites/python-pipeline.js +45 -0
  24. package/dist/composites/review-app.js +32 -0
  25. package/dist/coverage.js +35 -0
  26. package/dist/generated/index.js +25 -0
  27. package/dist/generated/runtime.js +4 -0
  28. package/dist/graph-edges.js +202 -0
  29. package/dist/import/generator.js +196 -0
  30. package/dist/import/parser.js +111 -0
  31. package/dist/index.js +26 -0
  32. package/dist/integrity.json +3 -3
  33. package/dist/intrinsics.js +33 -0
  34. package/dist/lint/audit-catalog.js +53 -0
  35. package/dist/lint/audit-lineage.js +64 -0
  36. package/dist/lint/post-synth/index.js +84 -0
  37. package/dist/lint/post-synth/wgl010.js +34 -0
  38. package/dist/lint/post-synth/wgl011.js +48 -0
  39. package/dist/lint/post-synth/wgl012.d.ts.map +1 -1
  40. package/dist/lint/post-synth/wgl012.js +68 -0
  41. package/dist/lint/post-synth/wgl013.js +57 -0
  42. package/dist/lint/post-synth/wgl014.js +43 -0
  43. package/dist/lint/post-synth/wgl015.js +70 -0
  44. package/dist/lint/post-synth/wgl016.js +70 -0
  45. package/dist/lint/post-synth/wgl017.js +38 -0
  46. package/dist/lint/post-synth/wgl018.js +34 -0
  47. package/dist/lint/post-synth/wgl019.js +43 -0
  48. package/dist/lint/post-synth/wgl020.js +47 -0
  49. package/dist/lint/post-synth/wgl021.js +52 -0
  50. package/dist/lint/post-synth/wgl022.js +38 -0
  51. package/dist/lint/post-synth/wgl023.js +44 -0
  52. package/dist/lint/post-synth/wgl024.js +41 -0
  53. package/dist/lint/post-synth/wgl025.js +42 -0
  54. package/dist/lint/post-synth/wgl026.js +58 -0
  55. package/dist/lint/post-synth/wgl027.js +49 -0
  56. package/dist/lint/post-synth/wgl028.js +58 -0
  57. package/dist/lint/post-synth/wgl029.js +55 -0
  58. package/dist/lint/post-synth/wgl030.js +43 -0
  59. package/dist/lint/post-synth/wgl031.js +33 -0
  60. package/dist/lint/post-synth/wgl032.js +64 -0
  61. package/dist/lint/post-synth/wgl033.js +32 -0
  62. package/dist/lint/post-synth/wgl034.js +37 -0
  63. package/dist/lint/post-synth/wgl035.js +40 -0
  64. package/dist/lint/post-synth/wgl036.js +38 -0
  65. package/dist/lint/post-synth/wgl037.js +41 -0
  66. package/dist/lint/post-synth/wgl038.js +43 -0
  67. package/dist/lint/post-synth/wgl039.js +34 -0
  68. package/dist/lint/post-synth/wgl040.js +35 -0
  69. package/dist/lint/post-synth/wgl041.js +53 -0
  70. package/dist/lint/post-synth/wgl042.js +41 -0
  71. package/dist/lint/post-synth/wgl043.js +39 -0
  72. package/dist/lint/post-synth/wgl044.js +35 -0
  73. package/dist/lint/post-synth/wgl045.js +54 -0
  74. package/dist/lint/post-synth/wgl046.js +40 -0
  75. package/dist/lint/post-synth/wgl047.js +34 -0
  76. package/dist/lint/post-synth/wgl048.js +32 -0
  77. package/dist/lint/post-synth/wgl049.js +53 -0
  78. package/dist/lint/post-synth/wgl050.js +42 -0
  79. package/dist/lint/post-synth/yaml-helpers.js +432 -0
  80. package/dist/lint/rules/artifact-no-expiry.js +53 -0
  81. package/dist/lint/rules/data/known-include-sources.js +44 -0
  82. package/dist/lint/rules/data/untrusted-variables.js +30 -0
  83. package/dist/lint/rules/deprecated-only-except.js +46 -0
  84. package/dist/lint/rules/import-source.js +61 -0
  85. package/dist/lint/rules/index.js +7 -0
  86. package/dist/lint/rules/missing-script.js +67 -0
  87. package/dist/lint/rules/missing-stage.js +60 -0
  88. package/dist/lsp/completions.d.ts.map +1 -1
  89. package/dist/lsp/completions.js +18 -0
  90. package/dist/lsp/hover.d.ts.map +1 -1
  91. package/dist/lsp/hover.js +33 -0
  92. package/dist/manifest.json +1 -1
  93. package/dist/mcp/context-tools.js +262 -0
  94. package/dist/migrate/from-github/actions/index.js +17 -0
  95. package/dist/migrate/from-github/actions/registry.js +61 -0
  96. package/dist/migrate/from-github/actions/tier-1.js +298 -0
  97. package/dist/migrate/from-github/actions/tier-2.js +241 -0
  98. package/dist/migrate/from-github/actions/tier-3.js +96 -0
  99. package/dist/migrate/from-github/composites/patterns.js +151 -0
  100. package/dist/migrate/from-github/composites/rewriter.js +22 -0
  101. package/dist/migrate/from-github/diagnostics.js +53 -0
  102. package/dist/migrate/from-github/emit-yaml.js +121 -0
  103. package/dist/migrate/from-github/expressions.js +275 -0
  104. package/dist/migrate/from-github/index.js +96 -0
  105. package/dist/migrate/from-github/provenance.js +35 -0
  106. package/dist/migrate/from-github/rules.js +74 -0
  107. package/dist/migrate/from-github/security.js +189 -0
  108. package/dist/migrate/from-github/stages.js +156 -0
  109. package/dist/migrate/from-github/transformer.js +633 -0
  110. package/dist/op/activities/gitlab.js +38 -0
  111. package/dist/op/activities/index.js +10 -0
  112. package/dist/op/builders.js +26 -0
  113. package/dist/package-cli.js +21 -0
  114. package/dist/plugin.js +536 -0
  115. package/dist/rules/wgl012.ts +2 -2
  116. package/dist/serializer.js +197 -0
  117. package/dist/testdata/create-fixture.js +40 -0
  118. package/dist/testdata/load-fixtures.js +20 -0
  119. package/dist/validate-cli.js +16 -0
  120. package/dist/validate.js +29 -0
  121. package/dist/variables.js +30 -0
  122. package/package.json +8 -8
  123. package/src/codegen/fetch.ts +2 -2
  124. package/src/codegen/package.ts +2 -2
  125. package/src/components/__fixtures__/pr-loop-build.gitlab.golden.yml +3 -3
  126. package/src/components/__fixtures__/pr-loop-waves.gitlab.golden.yml +101 -0
  127. package/src/components/__fixtures__/pr-loop.gitlab.golden.yml +3 -3
  128. package/src/components/generate-pipeline.ts +60 -27
  129. package/src/components/generate-pr-pipeline.test.ts +14 -9
  130. package/src/components/generate-pr-waves-pipeline.test.ts +88 -0
  131. package/src/lint/post-synth/wgl012.ts +2 -2
  132. package/src/lsp/completions.ts +2 -1
  133. package/src/lsp/hover.ts +2 -1
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Typed step-builder wrapper for this lexicon's `gitlabPipeline` activity
3
+ * (chant #1288 Stage 2 — "regenerate the step builders as fully typed
4
+ * wrappers"). Core cannot import a lexicon's `*Args` types (lexicons depend
5
+ * on core, not the other way around), so a fully typed `gitlabPipeline` has
6
+ * to live beside {@link GitlabPipelineArgs} — here, next to
7
+ * `./activities/gitlab.ts`. `opts`'s type below IS `GitlabPipelineArgs`
8
+ * itself (via `Omit`/`WithStepRefs`) — never a hand-restated mirror.
9
+ *
10
+ * `core`'s own `gitlabPipeline` (in `@intentius/chant/op`) is UNCHANGED and
11
+ * produces a byte-identical `ActivityStep` for the same inputs — purely
12
+ * additive, and deliberately not swapped into core's barrel (see
13
+ * `lexicons/k8s/src/op/builders.ts`'s module doc for why: that would make
14
+ * core depend on this package at runtime). An author who wants the typed
15
+ * surface imports it from here — `@intentius/chant-lexicon-gitlab`.
16
+ */
17
+ import { activity, takeProfileAndId, } from "@intentius/chant/op";
18
+ /**
19
+ * Trigger and wait for a GitLab CI pipeline to complete — the fully typed
20
+ * twin of core's `gitlabPipeline`. `opts` is {@link GitlabPipelineArgs}
21
+ * itself, minus the positional `name`. Defaults to the `longInfra` profile.
22
+ */
23
+ export const gitlabPipeline = (name, opts) => {
24
+ const { args, profile, id } = takeProfileAndId(opts);
25
+ return activity("gitlabPipeline", { name, ...args }, { profile: profile ?? "longInfra", ...(id ? { id } : {}) });
26
+ };
@@ -0,0 +1,21 @@
1
+ #!/usr/bin/env tsx
2
+ /**
3
+ * Thin entry point for `npm run bundle` in lexicon-gitlab.
4
+ * Generates src/generated/ files and writes dist/ bundle.
5
+ */
6
+ import { generate, writeGeneratedFiles } from "./codegen/generate.js";
7
+ import { packageLexicon } from "./codegen/package.js";
8
+ import { writeBundleSpec } from "@intentius/chant/codegen/package";
9
+ import { join, dirname } from "path";
10
+ import { fileURLToPath } from "url";
11
+ const pkgDir = dirname(dirname(fileURLToPath(import.meta.url)));
12
+ // 1. Generate src/generated/ files
13
+ const genResult = await generate({ verbose: true });
14
+ writeGeneratedFiles(genResult, pkgDir);
15
+ console.error(`Generated ${genResult.resources} entities, ${genResult.properties} property types, ${genResult.enums} enums`);
16
+ // 2. Run package pipeline and write dist/
17
+ const { spec, stats } = await packageLexicon({ verbose: true });
18
+ const distDir = join(pkgDir, "dist");
19
+ writeBundleSpec(spec, distDir);
20
+ console.error(`Packaged ${stats.resources} entities, ${stats.ruleCount} rules, ${stats.skillCount} skills`);
21
+ console.error(`dist/ written to ${distDir}`);
package/dist/plugin.js ADDED
@@ -0,0 +1,536 @@
1
+ /**
2
+ * GitLab CI/CD lexicon plugin.
3
+ *
4
+ * Provides serializer, template detection, and code generation
5
+ * for GitLab CI/CD pipelines.
6
+ */
7
+ import { compositeCatalog } from "./composites/catalog.js";
8
+ import { postSynthChecks as postSynthCheckList } from "./lint/post-synth/index.js";
9
+ import { gitlabAuditCatalog } from "./lint/audit-catalog.js";
10
+ import { createSkillsLoader, createDiffTool, createCatalogResource } from "@intentius/chant/lexicon-plugin-helpers";
11
+ import { gitlabSerializer } from "./serializer.js";
12
+ import { gitlabGraphEdges } from "./graph-edges.js";
13
+ import { gitlabContextTools } from "./mcp/context-tools.js";
14
+ import { deprecatedOnlyExceptRule } from "./lint/rules/deprecated-only-except.js";
15
+ import { missingScriptRule } from "./lint/rules/missing-script.js";
16
+ import { missingStageRule } from "./lint/rules/missing-stage.js";
17
+ import { artifactNoExpiryRule } from "./lint/rules/artifact-no-expiry.js";
18
+ import { gitlabCompletions } from "./lsp/completions.js";
19
+ import { gitlabHover } from "./lsp/hover.js";
20
+ import { GitLabParser } from "./import/parser.js";
21
+ import { GitLabGenerator } from "./import/generator.js";
22
+ import { generateGitlabPipeline } from "./components/generate-pipeline.js";
23
+ import { generateGitlabOpPipeline } from "./components/generate-op-pipeline.js";
24
+ import { generateGitlabOpWavesPipeline } from "./components/generate-op-waves-pipeline.js";
25
+ export const gitlabPlugin = {
26
+ name: "gitlab",
27
+ auditCatalog: () => gitlabAuditCatalog,
28
+ // Generate mode (#688): synthesize a .gitlab-ci.yml from the component graph.
29
+ generateComponentPipeline: (components, options) => generateGitlabPipeline(components, options),
30
+ // Generate mode, Op counterpart (#927): an Op → one GitLab CI job, on a
31
+ // Pipeline Schedule, a merge request or a push (#2084, #2256).
32
+ generateOpPipeline: (ops, options) => generateGitlabOpPipeline(ops, options),
33
+ // Ordered waves of Op runs, each behind one gate (#3679).
34
+ generateOpWavesPipeline: (spec, options) => generateGitlabOpWavesPipeline(spec, options),
35
+ // Self-upgrade: where the pinned GitLab schema version lives + its upstream (#685).
36
+ upstreamPin: {
37
+ file: "src/codegen/fetch.ts",
38
+ pattern: /export const GITLAB_SCHEMA_VERSION\s*=\s*"([^"]+)"/,
39
+ replace: (v, line) => line.replace(/export const GITLAB_SCHEMA_VERSION\s*=\s*"[^"]+"/, `export const GITLAB_SCHEMA_VERSION = "${v}"`),
40
+ upstream: { owner: "gitlab-org", repo: "gitlab", kind: "tags", tagSuffix: "-ee" },
41
+ },
42
+ serializer: gitlabSerializer,
43
+ // `needs`, stage order, `extends`, `trigger:` and `include:` as graph edges (#3910).
44
+ graphEdges: (entities) => gitlabGraphEdges(entities),
45
+ lintRules() {
46
+ return [deprecatedOnlyExceptRule, missingScriptRule, missingStageRule, artifactNoExpiryRule];
47
+ },
48
+ postSynthChecks() {
49
+ return postSynthCheckList;
50
+ },
51
+ intrinsics() {
52
+ return [
53
+ {
54
+ name: "reference",
55
+ // Authored as a plain call — `reference("job", "script")` — not a JS
56
+ // tagged template, so `isTag` (chant #1039: "authored as a tagged
57
+ // template", consumed by the static folder to recognize `Tag`...``)
58
+ // must be false. The `!reference` YAML tag it produces is rendered
59
+ // via `ReferenceIntrinsic.toYAML()` (see ../serializer.ts's generic
60
+ // `toYAML` duck-typing) — independent of this flag.
61
+ description: "!reference tag — reference another job's properties",
62
+ outputKey: "!reference",
63
+ isTag: false,
64
+ },
65
+ ];
66
+ },
67
+ initTemplates(template) {
68
+ if (template === "node-pipeline") {
69
+ return {
70
+ src: {
71
+ "pipeline.ts": `import { NodePipeline } from "@intentius/chant-lexicon-gitlab";
72
+
73
+ export const app = NodePipeline({
74
+ nodeVersion: "22",
75
+ installCommand: "npm install",
76
+ buildScript: "build",
77
+ testScript: "test",
78
+ });
79
+ `,
80
+ },
81
+ };
82
+ }
83
+ if (template === "python-pipeline") {
84
+ return {
85
+ src: {
86
+ "pipeline.ts": `import { PythonPipeline } from "@intentius/chant-lexicon-gitlab";
87
+
88
+ export const app = PythonPipeline({
89
+ pythonVersion: "3.12",
90
+ lintCommand: null,
91
+ });
92
+ `,
93
+ },
94
+ };
95
+ }
96
+ if (template === "docker-build") {
97
+ return {
98
+ src: {
99
+ "pipeline.ts": `import { DockerBuild, Job, Image } from "@intentius/chant-lexicon-gitlab";
100
+
101
+ export const docker = DockerBuild({
102
+ dockerfile: "Dockerfile",
103
+ tagLatest: true,
104
+ });
105
+
106
+ export const test = new Job({
107
+ stage: "test",
108
+ image: new Image({ name: "node:22-alpine" }),
109
+ script: ["node test.js"],
110
+ });
111
+ `,
112
+ },
113
+ };
114
+ }
115
+ if (template === "review-app") {
116
+ return {
117
+ src: {
118
+ "pipeline.ts": `import { ReviewApp, Job, Image } from "@intentius/chant-lexicon-gitlab";
119
+
120
+ export const review = ReviewApp({
121
+ name: "review",
122
+ deployScript: "echo deploy",
123
+ });
124
+
125
+ export const test = new Job({
126
+ stage: "test",
127
+ image: new Image({ name: "node:22-alpine" }),
128
+ script: ["node test.js"],
129
+ });
130
+ `,
131
+ },
132
+ };
133
+ }
134
+ // Default template — basic pipeline with shared config
135
+ return {
136
+ src: {
137
+ "config.ts": `/**
138
+ * Shared pipeline configuration
139
+ */
140
+
141
+ import { Image, Cache } from "@intentius/chant-lexicon-gitlab";
142
+
143
+ // Default image for all jobs
144
+ export const defaultImage = new Image({
145
+ name: "node:20-alpine",
146
+ });
147
+
148
+ // Standard cache configuration
149
+ export const npmCache = new Cache({
150
+ key: "$CI_COMMIT_REF_SLUG",
151
+ paths: ["node_modules/"],
152
+ policy: "pull-push",
153
+ });
154
+ `,
155
+ "pipeline.ts": `import { Job, Artifacts } from "@intentius/chant-lexicon-gitlab";
156
+ import { defaultImage, npmCache } from "./config";
157
+
158
+ export const junitReports = { junit: "coverage/junit.xml" };
159
+
160
+ export const testArtifacts = new Artifacts({
161
+ reports: junitReports,
162
+ paths: ["coverage/"],
163
+ expire_in: "1 week",
164
+ });
165
+
166
+ export const build = new Job({
167
+ stage: "build",
168
+ image: defaultImage,
169
+ cache: npmCache,
170
+ script: ["npm install", "npm run build"],
171
+ });
172
+
173
+ export const test = new Job({
174
+ stage: "test",
175
+ image: defaultImage,
176
+ cache: npmCache,
177
+ script: ["npm install", "npm test"],
178
+ artifacts: testArtifacts,
179
+ });
180
+ `,
181
+ },
182
+ };
183
+ },
184
+ detectTemplate(data) {
185
+ if (typeof data !== "object" || data === null)
186
+ return false;
187
+ const obj = data;
188
+ // GitLab CI files typically have stages, or job-like top-level keys
189
+ if (Array.isArray(obj.stages))
190
+ return true;
191
+ if (obj.image !== undefined && obj.script !== undefined)
192
+ return true;
193
+ // Check for job-like entries (objects with "stage" or "script" properties)
194
+ for (const value of Object.values(obj)) {
195
+ if (typeof value === "object" && value !== null) {
196
+ const entry = value;
197
+ if (entry.stage !== undefined || entry.script !== undefined) {
198
+ return true;
199
+ }
200
+ }
201
+ }
202
+ return false;
203
+ },
204
+ completionProvider(ctx) {
205
+ return gitlabCompletions(ctx);
206
+ },
207
+ hoverProvider(ctx) {
208
+ return gitlabHover(ctx);
209
+ },
210
+ templateParser() {
211
+ return new GitLabParser();
212
+ },
213
+ templateGenerator() {
214
+ return new GitLabGenerator();
215
+ },
216
+ async generate(options) {
217
+ const { generate, writeGeneratedFiles } = await import("./codegen/generate.js");
218
+ const { dirname } = await import("path");
219
+ const { fileURLToPath } = await import("url");
220
+ const result = await generate({ verbose: options?.verbose ?? true });
221
+ const pkgDir = dirname(dirname(fileURLToPath(import.meta.url)));
222
+ writeGeneratedFiles(result, pkgDir);
223
+ console.error(`Generated ${result.resources} entities, ${result.properties} property types, ${result.enums} enums`);
224
+ if (result.warnings.length > 0) {
225
+ console.error(`${result.warnings.length} warnings`);
226
+ }
227
+ },
228
+ async validate(options) {
229
+ const { validate } = await import("./validate.js");
230
+ const { printValidationResult } = await import("@intentius/chant/codegen/validate");
231
+ const result = await validate();
232
+ printValidationResult(result);
233
+ },
234
+ async coverage(options) {
235
+ const { analyzeGitLabCoverage } = await import("./coverage.js");
236
+ await analyzeGitLabCoverage({
237
+ verbose: options?.verbose,
238
+ minOverall: options?.minOverall,
239
+ });
240
+ },
241
+ async package(options) {
242
+ const { packageLexicon } = await import("./codegen/package.js");
243
+ const { writeBundleSpec } = await import("@intentius/chant/codegen/package");
244
+ const { join, dirname } = await import("path");
245
+ const { fileURLToPath } = await import("url");
246
+ const { spec, stats } = await packageLexicon({ verbose: options?.verbose, force: options?.force });
247
+ const pkgDir = dirname(dirname(fileURLToPath(import.meta.url)));
248
+ const distDir = join(pkgDir, "dist");
249
+ writeBundleSpec(spec, distDir);
250
+ console.error(`Packaged ${stats.resources} entities, ${stats.ruleCount} rules, ${stats.skillCount} skills`);
251
+ },
252
+ composites() {
253
+ return compositeCatalog;
254
+ },
255
+ mcpTools() {
256
+ return [
257
+ createDiffTool(gitlabSerializer, "Compare current build output against previous output for GitLab CI", "gitlab"),
258
+ // Read-only context tools: what the pipeline does, what it pulls in, and
259
+ // its security findings — before anything runs (#327/#328).
260
+ ...gitlabContextTools(),
261
+ {
262
+ name: "gitlab:migrate",
263
+ description: "Translate a GitHub Actions workflow YAML into a GitLab CI/CD pipeline. Returns the rendered output plus diagnostic + provenance arrays.",
264
+ inputSchema: {
265
+ type: "object",
266
+ properties: {
267
+ content: { type: "string", description: "Raw .github/workflows/*.yml content" },
268
+ emit: { type: "string", enum: ["yaml", "ts"], description: "Output format (default: yaml)" },
269
+ useComposites: { type: "boolean", description: "Recognise composite patterns and emit NodePipeline/NodeCI calls" },
270
+ strict: { type: "boolean", description: "Escalate needs-review diagnostics to errors" },
271
+ security: { type: "boolean", description: "Run security-aware migration analysis (classify property fates + GitLab security checks)" },
272
+ },
273
+ required: ["content"],
274
+ },
275
+ async handler(params) {
276
+ const { transform } = await import("./migrate/from-github/index.js");
277
+ const result = await transform(params.content, {
278
+ emit: params.emit ?? "yaml",
279
+ useComposites: !!params.useComposites,
280
+ strict: !!params.strict,
281
+ security: !!params.security,
282
+ sourceFile: "<mcp-input>",
283
+ });
284
+ return {
285
+ output: result.output,
286
+ diagnostics: result.diagnostics,
287
+ provenance: result.provenance,
288
+ stages: result.stages,
289
+ securityPosture: result.securityPosture,
290
+ };
291
+ },
292
+ },
293
+ ];
294
+ },
295
+ migrationSource(from) {
296
+ if (from !== "github")
297
+ return undefined;
298
+ return {
299
+ detect(content) {
300
+ // Avoid bringing the migrate code into the import graph until needed
301
+ if (!/^\s*jobs\s*:/m.test(content))
302
+ return false;
303
+ return /^\s*on\s*:/m.test(content) || /^\s*runs-on\s*:/m.test(content);
304
+ },
305
+ async transform(content, opts) {
306
+ const { transform } = await import("./migrate/from-github/index.js");
307
+ const result = await transform(content, {
308
+ emit: opts.emit,
309
+ useComposites: opts.useComposites,
310
+ sourceFile: opts.sourceFile,
311
+ strict: opts.strict,
312
+ security: opts.security,
313
+ });
314
+ // The composites rewriter (when enabled) replaces several Job
315
+ // resources with a single Composite resource — stages: in the
316
+ // top-level YAML output is now stale for that path. The yaml
317
+ // emitter reads metadata.stages directly; no special handling
318
+ // needed at the call site.
319
+ return {
320
+ output: result.output,
321
+ provenance: result.provenance,
322
+ diagnostics: result.diagnostics,
323
+ securityPosture: result.securityPosture,
324
+ };
325
+ },
326
+ };
327
+ },
328
+ mcpResources() {
329
+ return [
330
+ createCatalogResource(import.meta.url, "GitLab CI Entity Catalog", "JSON list of all supported GitLab CI entity types", "lexicon-gitlab.json", "gitlab"),
331
+ {
332
+ uri: "examples/basic-pipeline",
333
+ name: "Basic Pipeline Example",
334
+ description: "A basic GitLab CI pipeline with build, test, and deploy stages",
335
+ mimeType: "text/typescript",
336
+ async handler() {
337
+ return `import { Job, Image, Cache, Artifacts, CI } from "@intentius/chant-lexicon-gitlab";
338
+
339
+ export const build = new Job({
340
+ stage: "build",
341
+ image: new Image({ name: "node:20" }),
342
+ cache: new Cache({ key: CI.CommitRef, paths: ["node_modules/"] }),
343
+ script: ["npm ci", "npm run build"],
344
+ artifacts: new Artifacts({ paths: ["dist/"], expireIn: "1 day" }),
345
+ });
346
+
347
+ export const test = new Job({
348
+ stage: "test",
349
+ image: new Image({ name: "node:20" }),
350
+ cache: new Cache({ key: CI.CommitRef, paths: ["node_modules/"], policy: "pull" }),
351
+ script: ["npm ci", "npm test"],
352
+ artifacts: new Artifacts({
353
+ reports: { junit: "coverage/junit.xml" },
354
+ expireIn: "1 week",
355
+ }),
356
+ });
357
+
358
+ export const deploy = new Job({
359
+ stage: "deploy",
360
+ script: ["./deploy.sh"],
361
+ rules: [{ if: "$CI_COMMIT_BRANCH == \\"main\\"", when: "manual" }],
362
+ });
363
+ `;
364
+ },
365
+ },
366
+ ];
367
+ },
368
+ async docs(options) {
369
+ const { generateDocs } = await import("./codegen/docs.js");
370
+ await generateDocs(options);
371
+ },
372
+ skills: createSkillsLoader(import.meta.url, [
373
+ {
374
+ file: "chant-gitlab.md",
375
+ name: "chant-gitlab",
376
+ description: "GitLab CI/CD pipeline lifecycle — build, validate, deploy, monitor, rollback, and troubleshoot",
377
+ triggers: [
378
+ { type: "file-pattern", value: "**/*.gitlab.ts" },
379
+ { type: "file-pattern", value: "**/.gitlab-ci.yml" },
380
+ { type: "context", value: "gitlab" },
381
+ { type: "context", value: "pipeline" },
382
+ { type: "context", value: "deploy" },
383
+ ],
384
+ preConditions: [
385
+ "chant CLI is installed (chant --version succeeds)",
386
+ "git is configured and can push to the remote",
387
+ "Project has chant source files in src/",
388
+ ],
389
+ postConditions: [
390
+ "Pipeline is in a stable state (success/manual/scheduled)",
391
+ "No failed jobs in the pipeline",
392
+ ],
393
+ parameters: [],
394
+ examples: [
395
+ {
396
+ title: "Basic test job",
397
+ description: "Create a test job with caching and artifacts",
398
+ input: "Create a test job",
399
+ output: `new Job({
400
+ stage: "test",
401
+ image: new Image({ name: "node:20" }),
402
+ script: ["npm ci", "npm test"],
403
+ cache: new Cache({
404
+ key: "$CI_COMMIT_REF_SLUG",
405
+ paths: ["node_modules/"],
406
+ }),
407
+ artifacts: new Artifacts({
408
+ reports: { junit: "coverage/junit.xml" },
409
+ }),
410
+ })`,
411
+ },
412
+ {
413
+ title: "Deploy pipeline update",
414
+ description: "Build, validate, and deploy a pipeline change via MR workflow",
415
+ input: "Deploy my pipeline changes to production",
416
+ output: `chant lint src/
417
+ chant build src/ --output .gitlab-ci.yml
418
+ git checkout -b feature/pipeline-update
419
+ git add .gitlab-ci.yml
420
+ git commit -m "Update pipeline"
421
+ git push -u origin feature/pipeline-update
422
+ # Open MR in GitLab, review pipeline diff, then merge`,
423
+ },
424
+ {
425
+ title: "Preview pipeline changes",
426
+ description: "Validate pipeline configuration via lint and CI Lint API before deploying",
427
+ input: "Check if my pipeline changes are valid before pushing",
428
+ output: `chant lint src/
429
+ chant build src/ --output .gitlab-ci.yml
430
+ # Validate via GitLab CI Lint API
431
+ curl --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \\
432
+ --header "Content-Type: application/json" \\
433
+ "https://gitlab.com/api/v4/projects/$PROJECT_ID/ci/lint" \\
434
+ --data-binary '{"content": "'$(cat .gitlab-ci.yml | jq -Rs .)'", "dry_run": true}'`,
435
+ },
436
+ {
437
+ title: "Scaffold and deploy a Node.js pipeline",
438
+ description: "Use --template to scaffold a Node.js project, build YAML, and push to GitLab",
439
+ input: "Create a Node.js CI pipeline and deploy it to GitLab",
440
+ output: `# Scaffold the project
441
+ chant init --lexicon gitlab --template node-pipeline my-node-app
442
+ cd my-node-app
443
+
444
+ # Build the YAML
445
+ chant build src/ --output .gitlab-ci.yml
446
+
447
+ # The GitLab repo needs app files — create them
448
+ echo '{"scripts":{"build":"echo build","test":"node test.js"}}' > package.json
449
+ echo 'console.log("ok")' > test.js
450
+
451
+ # Push to GitLab
452
+ git init -b main
453
+ git add .gitlab-ci.yml package.json test.js
454
+ git commit -m "Initial pipeline"
455
+ git remote add origin git@gitlab.com:YOUR_GROUP/YOUR_PROJECT.git
456
+ git push -u origin main`,
457
+ },
458
+ {
459
+ title: "Retry a failed pipeline",
460
+ description: "Retry a failed pipeline and monitor its progress",
461
+ input: "Pipeline 12345 failed, retry it",
462
+ output: `# Retry the pipeline
463
+ curl --request POST --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \\
464
+ "https://gitlab.com/api/v4/projects/$PROJECT_ID/pipelines/12345/retry"
465
+ # Monitor status
466
+ curl --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \\
467
+ "https://gitlab.com/api/v4/projects/$PROJECT_ID/pipelines/12345"`,
468
+ },
469
+ ],
470
+ },
471
+ {
472
+ file: "chant-gitlab-migrate.md",
473
+ name: "chant-gitlab-migrate",
474
+ description: "Translate GitHub Actions workflows into GitLab CI/CD pipelines via chant migrate",
475
+ triggers: [
476
+ { type: "file-pattern", value: "**/.github/workflows/*.yml" },
477
+ { type: "file-pattern", value: "**/.github/workflows/*.yaml" },
478
+ { type: "context", value: "migrate from github actions" },
479
+ { type: "context", value: "github actions to gitlab" },
480
+ { type: "context", value: "convert workflow" },
481
+ ],
482
+ preConditions: [
483
+ "chant CLI is installed (chant --version succeeds)",
484
+ "@intentius/chant-lexicon-gitlab is installed",
485
+ ],
486
+ postConditions: [
487
+ "Translated .gitlab-ci.yml or .ts source on disk",
488
+ "Migration report visible to the user (Markdown + SARIF if --report)",
489
+ ],
490
+ parameters: [],
491
+ examples: [
492
+ {
493
+ title: "Translate a single workflow file",
494
+ description: "Migrate .github/workflows/ci.yml into .gitlab-ci.yml with a SARIF report",
495
+ input: "Migrate this GitHub workflow to GitLab CI",
496
+ output: `npx chant migrate .github/workflows/ci.yml \\
497
+ --output .gitlab-ci.yml \\
498
+ --report migration.sarif`,
499
+ },
500
+ {
501
+ title: "Translate to chant TypeScript",
502
+ description: "Produce typed chant source instead of YAML so the user can maintain the pipeline in chant going forward",
503
+ input: "I want to maintain this in chant — produce TypeScript",
504
+ output: `npx chant migrate .github/workflows/ci.yml --emit ts --output src/pipeline.ts`,
505
+ },
506
+ {
507
+ title: "Recognise and emit composites",
508
+ description: "Collapse a 2-job NodePipeline-shaped workflow into a single NodePipeline() call",
509
+ input: "Use composites for the upgrade",
510
+ output: `npx chant migrate .github/workflows/ci.yml --emit ts --use-composites --output src/pipeline.ts`,
511
+ },
512
+ ],
513
+ },
514
+ {
515
+ file: "chant-gitlab-patterns.md",
516
+ name: "chant-gitlab-patterns",
517
+ description: "GitLab CI/CD pipeline stages, caching, artifacts, includes, and advanced patterns",
518
+ triggers: [
519
+ { type: "context", value: "gitlab pipeline" },
520
+ { type: "context", value: "gitlab cache" },
521
+ { type: "context", value: "gitlab artifacts" },
522
+ { type: "context", value: "gitlab include" },
523
+ { type: "context", value: "gitlab stages" },
524
+ { type: "context", value: "review app" },
525
+ ],
526
+ parameters: [],
527
+ examples: [
528
+ {
529
+ title: "Pipeline with caching",
530
+ input: "Set up a Node.js pipeline with proper caching",
531
+ output: "import { Job, Cache } from \"@intentius/chant-lexicon-gitlab\";\n\nconst cache = new Cache({ key: { files: [\"package-lock.json\"] }, paths: [\"node_modules/\"] });",
532
+ },
533
+ ],
534
+ },
535
+ ]),
536
+ };
@@ -7,6 +7,7 @@
7
7
 
8
8
  import { readFileSync } from "fs";
9
9
  import { join } from "path";
10
+ import { moduleSourceDir } from "@intentius/chant/module-source";
10
11
  import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
11
12
  import { isPropertyDeclarable } from "@intentius/chant/declarable";
12
13
 
@@ -23,8 +24,7 @@ interface LexiconEntry {
23
24
  function loadDeprecatedProperties(): Map<string, Set<string>> {
24
25
  const map = new Map<string, Set<string>>();
25
26
  try {
26
- const pkgDir = join(__dirname, "..", "..", "..");
27
- const lexiconPath = join(pkgDir, "src", "generated", "lexicon-gitlab.json");
27
+ const lexiconPath = join(moduleSourceDir(import.meta.url), "..", "..", "generated", "lexicon-gitlab.json");
28
28
  const content = readFileSync(lexiconPath, "utf-8");
29
29
  const data = JSON.parse(content) as Record<string, LexiconEntry>;
30
30