@intentius/chant-lexicon-k8s 0.0.12

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 (123) hide show
  1. package/dist/integrity.json +32 -0
  2. package/dist/manifest.json +8 -0
  3. package/dist/meta.json +1413 -0
  4. package/dist/rules/hardcoded-namespace.ts +56 -0
  5. package/dist/rules/k8s-helpers.ts +149 -0
  6. package/dist/rules/wk8005.ts +59 -0
  7. package/dist/rules/wk8006.ts +68 -0
  8. package/dist/rules/wk8041.ts +73 -0
  9. package/dist/rules/wk8042.ts +48 -0
  10. package/dist/rules/wk8101.ts +65 -0
  11. package/dist/rules/wk8102.ts +42 -0
  12. package/dist/rules/wk8103.ts +45 -0
  13. package/dist/rules/wk8104.ts +69 -0
  14. package/dist/rules/wk8105.ts +45 -0
  15. package/dist/rules/wk8201.ts +55 -0
  16. package/dist/rules/wk8202.ts +46 -0
  17. package/dist/rules/wk8203.ts +46 -0
  18. package/dist/rules/wk8204.ts +54 -0
  19. package/dist/rules/wk8205.ts +56 -0
  20. package/dist/rules/wk8207.ts +45 -0
  21. package/dist/rules/wk8208.ts +45 -0
  22. package/dist/rules/wk8209.ts +45 -0
  23. package/dist/rules/wk8301.ts +51 -0
  24. package/dist/rules/wk8302.ts +46 -0
  25. package/dist/rules/wk8303.ts +96 -0
  26. package/dist/skills/chant-k8s.md +433 -0
  27. package/dist/types/index.d.ts +2934 -0
  28. package/package.json +30 -0
  29. package/src/actions/actions.test.ts +83 -0
  30. package/src/actions/apps.ts +23 -0
  31. package/src/actions/batch.ts +9 -0
  32. package/src/actions/core.ts +62 -0
  33. package/src/actions/index.ts +50 -0
  34. package/src/actions/networking.ts +15 -0
  35. package/src/actions/rbac.ts +13 -0
  36. package/src/codegen/docs-cli.ts +3 -0
  37. package/src/codegen/docs.ts +1147 -0
  38. package/src/codegen/generate-cli.ts +41 -0
  39. package/src/codegen/generate-lexicon.ts +69 -0
  40. package/src/codegen/generate-typescript.ts +97 -0
  41. package/src/codegen/generate.ts +144 -0
  42. package/src/codegen/naming.test.ts +63 -0
  43. package/src/codegen/naming.ts +187 -0
  44. package/src/codegen/package.ts +56 -0
  45. package/src/codegen/patches.ts +108 -0
  46. package/src/codegen/snapshot.test.ts +95 -0
  47. package/src/codegen/typecheck.test.ts +24 -0
  48. package/src/codegen/typecheck.ts +4 -0
  49. package/src/codegen/versions.ts +43 -0
  50. package/src/composites/autoscaled-service.ts +236 -0
  51. package/src/composites/composites.test.ts +1109 -0
  52. package/src/composites/cron-workload.ts +167 -0
  53. package/src/composites/index.ts +14 -0
  54. package/src/composites/namespace-env.ts +163 -0
  55. package/src/composites/node-agent.ts +224 -0
  56. package/src/composites/stateful-app.ts +134 -0
  57. package/src/composites/web-app.ts +180 -0
  58. package/src/composites/worker-pool.ts +230 -0
  59. package/src/coverage.test.ts +27 -0
  60. package/src/coverage.ts +35 -0
  61. package/src/crd/loader.ts +112 -0
  62. package/src/crd/parser.test.ts +217 -0
  63. package/src/crd/parser.ts +279 -0
  64. package/src/crd/types.ts +54 -0
  65. package/src/default-labels.test.ts +111 -0
  66. package/src/default-labels.ts +122 -0
  67. package/src/generated/index.d.ts +2934 -0
  68. package/src/generated/index.ts +203 -0
  69. package/src/generated/lexicon-k8s.json +1413 -0
  70. package/src/generated/runtime.ts +4 -0
  71. package/src/import/generator.test.ts +121 -0
  72. package/src/import/generator.ts +285 -0
  73. package/src/import/parser.test.ts +156 -0
  74. package/src/import/parser.ts +204 -0
  75. package/src/import/roundtrip.test.ts +86 -0
  76. package/src/index.ts +38 -0
  77. package/src/lint/post-synth/k8s-helpers.test.ts +219 -0
  78. package/src/lint/post-synth/k8s-helpers.ts +149 -0
  79. package/src/lint/post-synth/post-synth.test.ts +969 -0
  80. package/src/lint/post-synth/wk8005.ts +59 -0
  81. package/src/lint/post-synth/wk8006.ts +68 -0
  82. package/src/lint/post-synth/wk8041.ts +73 -0
  83. package/src/lint/post-synth/wk8042.ts +48 -0
  84. package/src/lint/post-synth/wk8101.ts +65 -0
  85. package/src/lint/post-synth/wk8102.ts +42 -0
  86. package/src/lint/post-synth/wk8103.ts +45 -0
  87. package/src/lint/post-synth/wk8104.ts +69 -0
  88. package/src/lint/post-synth/wk8105.ts +45 -0
  89. package/src/lint/post-synth/wk8201.ts +55 -0
  90. package/src/lint/post-synth/wk8202.ts +46 -0
  91. package/src/lint/post-synth/wk8203.ts +46 -0
  92. package/src/lint/post-synth/wk8204.ts +54 -0
  93. package/src/lint/post-synth/wk8205.ts +56 -0
  94. package/src/lint/post-synth/wk8207.ts +45 -0
  95. package/src/lint/post-synth/wk8208.ts +45 -0
  96. package/src/lint/post-synth/wk8209.ts +45 -0
  97. package/src/lint/post-synth/wk8301.ts +51 -0
  98. package/src/lint/post-synth/wk8302.ts +46 -0
  99. package/src/lint/post-synth/wk8303.ts +96 -0
  100. package/src/lint/rules/hardcoded-namespace.ts +56 -0
  101. package/src/lint/rules/rules.test.ts +69 -0
  102. package/src/lsp/completions.test.ts +64 -0
  103. package/src/lsp/completions.ts +20 -0
  104. package/src/lsp/hover.test.ts +69 -0
  105. package/src/lsp/hover.ts +68 -0
  106. package/src/package-cli.ts +28 -0
  107. package/src/plugin.test.ts +209 -0
  108. package/src/plugin.ts +915 -0
  109. package/src/serializer.test.ts +275 -0
  110. package/src/serializer.ts +278 -0
  111. package/src/spec/fetch.test.ts +24 -0
  112. package/src/spec/fetch.ts +68 -0
  113. package/src/spec/parse.test.ts +102 -0
  114. package/src/spec/parse.ts +477 -0
  115. package/src/testdata/manifests/configmap.yaml +7 -0
  116. package/src/testdata/manifests/deployment.yaml +22 -0
  117. package/src/testdata/manifests/full-app.yaml +61 -0
  118. package/src/testdata/manifests/secret.yaml +7 -0
  119. package/src/testdata/manifests/service.yaml +15 -0
  120. package/src/validate-cli.ts +21 -0
  121. package/src/validate.test.ts +29 -0
  122. package/src/validate.ts +46 -0
  123. package/src/variables.ts +36 -0
@@ -0,0 +1,1147 @@
1
+ /**
2
+ * Documentation generation for the Kubernetes lexicon.
3
+ *
4
+ * Generates Starlight MDX pages for K8s entities using the core docs pipeline.
5
+ */
6
+
7
+ import { dirname, join } from "path";
8
+ import { fileURLToPath } from "url";
9
+ import { docsPipeline, writeDocsSite, type DocsConfig } from "@intentius/chant/codegen/docs";
10
+
11
+ /**
12
+ * Extract service name from K8s type: "K8s::Apps::Deployment" → "Apps"
13
+ */
14
+ function serviceFromType(resourceType: string): string {
15
+ const parts = resourceType.split("::");
16
+ return parts.length >= 2 ? parts[1] : "Core";
17
+ }
18
+
19
+ const overview = `The **Kubernetes** lexicon provides typed constructors for Kubernetes resource
20
+ manifests. It covers Deployments, Services, ConfigMaps, StatefulSets, Jobs,
21
+ Ingress, RBAC, and 50+ additional resource and property types.
22
+
23
+ New? Start with the [Getting Started](/chant/lexicons/k8s/getting-started/) guide.
24
+
25
+ Install it with:
26
+
27
+ \`\`\`bash
28
+ npm install --save-dev @intentius/chant-lexicon-k8s
29
+ \`\`\`
30
+
31
+ ## Quick Start
32
+
33
+ \`\`\`typescript
34
+ import { Deployment, Service, Container, Probe } from "@intentius/chant-lexicon-k8s";
35
+
36
+ export const deployment = new Deployment({
37
+ metadata: { name: "my-app", labels: { "app.kubernetes.io/name": "my-app" } },
38
+ spec: {
39
+ replicas: 2,
40
+ selector: { matchLabels: { "app.kubernetes.io/name": "my-app" } },
41
+ template: {
42
+ metadata: { labels: { "app.kubernetes.io/name": "my-app" } },
43
+ spec: {
44
+ containers: [
45
+ new Container({
46
+ name: "app",
47
+ image: "my-app:1.0",
48
+ ports: [{ containerPort: 8080, name: "http" }],
49
+ livenessProbe: new Probe({ httpGet: { path: "/healthz", port: 8080 } }),
50
+ readinessProbe: new Probe({ httpGet: { path: "/readyz", port: 8080 } }),
51
+ }),
52
+ ],
53
+ },
54
+ },
55
+ },
56
+ });
57
+
58
+ export const service = new Service({
59
+ metadata: { name: "my-app" },
60
+ spec: {
61
+ selector: { "app.kubernetes.io/name": "my-app" },
62
+ ports: [{ port: 80, targetPort: 8080, name: "http" }],
63
+ },
64
+ });
65
+ \`\`\`
66
+
67
+ The lexicon provides **50+ resource types** (Deployment, Service, ConfigMap, StatefulSet, and more), **35+ property types** (Container, Probe, Volume, SecurityContext, etc.), and composites (WebApp, StatefulApp, CronWorkload, AutoscaledService, WorkerPool, NamespaceEnv, NodeAgent) for common patterns.
68
+ `;
69
+
70
+ const outputFormat = `The Kubernetes lexicon serializes resources into **multi-document YAML** with
71
+ \`---\` separators between resources. Each resource gets the standard K8s
72
+ structure: \`apiVersion\`, \`kind\`, \`metadata\`, and \`spec\`.
73
+
74
+ ## Building
75
+
76
+ Run \`chant build\` to produce Kubernetes manifests from your declarations:
77
+
78
+ \`\`\`bash
79
+ chant build
80
+ # Writes dist/manifests.yaml
81
+ \`\`\`
82
+
83
+ The generated file includes:
84
+
85
+ - Multi-document YAML with \`---\` separators
86
+ - Correct \`apiVersion\` and \`kind\` for each resource
87
+ - \`metadata.name\` auto-generated from export names (camelCase → kebab-case)
88
+ - Default labels and annotations injected from \`defaultLabels()\`/\`defaultAnnotations()\`
89
+
90
+ ## Key conversions
91
+
92
+ | Chant (TypeScript) | YAML output | Rule |
93
+ |--------------------|-------------|------|
94
+ | \`export const myApp = new Deployment({...})\` | \`metadata.name: my-app\` | Export name → kebab-case |
95
+ | \`new Container({...})\` | Inline container spec | Property types expanded inline |
96
+ | \`defaultLabels({...})\` | Merged into all resources | Project-wide label injection |
97
+
98
+ ## Applying
99
+
100
+ The output is standard Kubernetes YAML. Apply with kubectl:
101
+
102
+ \`\`\`bash
103
+ # Dry run first
104
+ kubectl apply -f dist/manifests.yaml --dry-run=server
105
+
106
+ # Apply
107
+ kubectl apply -f dist/manifests.yaml
108
+
109
+ # Diff before applying
110
+ kubectl diff -f dist/manifests.yaml
111
+ \`\`\`
112
+
113
+ ## Compatibility
114
+
115
+ The output is compatible with:
116
+ - kubectl apply/diff
117
+ - Helm (as raw manifests)
118
+ - ArgoCD / Flux GitOps controllers
119
+ - Kustomize (as a base)
120
+ - Any tool that processes Kubernetes YAML`;
121
+
122
+ /**
123
+ * Generate documentation for the Kubernetes lexicon.
124
+ */
125
+ export async function generateDocs(opts?: { verbose?: boolean }): Promise<void> {
126
+ const pkgDir = dirname(dirname(dirname(fileURLToPath(import.meta.url))));
127
+
128
+ const config: DocsConfig = {
129
+ name: "k8s",
130
+ displayName: "Kubernetes",
131
+ description: "Typed constructors for Kubernetes resource manifests",
132
+ distDir: join(pkgDir, "dist"),
133
+ outDir: join(pkgDir, "docs"),
134
+ overview,
135
+ outputFormat,
136
+ serviceFromType,
137
+ suppressPages: ["pseudo-parameters"],
138
+ extraPages: [
139
+ {
140
+ slug: "getting-started",
141
+ title: "Getting Started",
142
+ description: "Install chant and deploy your first Kubernetes manifest in 5 minutes",
143
+ content: `## What is chant?
144
+
145
+ Chant is a TypeScript-to-YAML compiler for Kubernetes. You write typed TypeScript declarations, and chant outputs kubectl-ready manifests.
146
+
147
+ ## Install
148
+
149
+ \`\`\`bash
150
+ npm install --save-dev @intentius/chant @intentius/chant-lexicon-k8s
151
+ \`\`\`
152
+
153
+ ## Your first deployment
154
+
155
+ The fastest path is the **WebApp** composite — one function call that produces a Deployment, Service, and optional Ingress:
156
+
157
+ \`\`\`typescript
158
+ // src/infra.k8s.ts
159
+ import { WebApp } from "@intentius/chant-lexicon-k8s";
160
+
161
+ const app = WebApp({
162
+ name: "hello",
163
+ image: "nginx:1.25",
164
+ port: 80,
165
+ replicas: 2,
166
+ });
167
+
168
+ export const { deployment, service } = app;
169
+ \`\`\`
170
+
171
+ Build and deploy:
172
+
173
+ \`\`\`bash
174
+ # Generate YAML manifests
175
+ chant build --output dist/manifests.yaml
176
+
177
+ # Validate against the cluster API (no changes applied)
178
+ kubectl apply -f dist/manifests.yaml --dry-run=server
179
+
180
+ # Apply for real
181
+ kubectl apply -f dist/manifests.yaml
182
+ \`\`\`
183
+
184
+ ## Using resource constructors
185
+
186
+ Composites are convenient, but you can also use the lower-level resource constructors directly:
187
+
188
+ \`\`\`typescript
189
+ // src/infra.k8s.ts
190
+ import { Deployment, Service, Container, Probe } from "@intentius/chant-lexicon-k8s";
191
+
192
+ export const deployment = new Deployment({
193
+ metadata: { name: "hello", labels: { "app.kubernetes.io/name": "hello" } },
194
+ spec: {
195
+ replicas: 2,
196
+ selector: { matchLabels: { "app.kubernetes.io/name": "hello" } },
197
+ template: {
198
+ metadata: { labels: { "app.kubernetes.io/name": "hello" } },
199
+ spec: {
200
+ containers: [
201
+ new Container({
202
+ name: "web",
203
+ image: "nginx:1.25",
204
+ ports: [{ containerPort: 80, name: "http" }],
205
+ livenessProbe: new Probe({ httpGet: { path: "/", port: 80 } }),
206
+ readinessProbe: new Probe({ httpGet: { path: "/", port: 80 } }),
207
+ }),
208
+ ],
209
+ },
210
+ },
211
+ },
212
+ });
213
+
214
+ export const service = new Service({
215
+ metadata: { name: "hello" },
216
+ spec: {
217
+ selector: { "app.kubernetes.io/name": "hello" },
218
+ ports: [{ port: 80, targetPort: 80, name: "http" }],
219
+ },
220
+ });
221
+ \`\`\`
222
+
223
+ Composites return plain prop objects. Resource constructors (\`new Deployment(...)\`) accept the same shape. Both produce identical YAML.
224
+
225
+ ## Next steps
226
+
227
+ - [Kubernetes Concepts](/chant/lexicons/k8s/kubernetes-concepts/) — how K8s resources map to chant constructs
228
+ - [Examples: Composites](/chant/lexicons/k8s/composite-examples/) — WebApp, CronWorkload, AutoscaledService, and more
229
+ - [Lint Rules](/chant/lexicons/k8s/lint-rules/) — built-in checks for security, reliability, and best practices`,
230
+ },
231
+ {
232
+ slug: "kubernetes-concepts",
233
+ title: "Kubernetes Concepts",
234
+ description: "How Kubernetes resources map to chant constructs — apiVersion, kind, metadata, spec",
235
+ content: `Every exported resource declaration becomes a Kubernetes manifest document in the generated YAML. The serializer handles the translation automatically:
236
+
237
+ - Resolves the correct \`apiVersion\` and \`kind\` from the resource type
238
+ - Converts the export name to a kebab-case \`metadata.name\`
239
+ - Nests user properties under \`spec\` (or at the top level for specless types like ConfigMap)
240
+ - Merges default labels and annotations from \`defaultLabels()\`/\`defaultAnnotations()\`
241
+
242
+ ## Resource structure
243
+
244
+ Every Kubernetes resource has four standard fields:
245
+
246
+ | Field | Source | Example |
247
+ |-------|--------|---------|
248
+ | \`apiVersion\` | Resolved from resource type | \`apps/v1\` |
249
+ | \`kind\` | Resolved from resource type | \`Deployment\` |
250
+ | \`metadata\` | From \`metadata\` property | \`{ name: "my-app", labels: {...} }\` |
251
+ | \`spec\` | From \`spec\` property or remaining props | Resource-specific configuration |
252
+
253
+ ## API groups
254
+
255
+ K8s resources are organized by API group:
256
+
257
+ | Group | apiVersion | Resources |
258
+ |-------|-----------|-----------|
259
+ | Core | \`v1\` | Pod, Service, ConfigMap, Secret, Namespace, ServiceAccount |
260
+ | Apps | \`apps/v1\` | Deployment, StatefulSet, DaemonSet, ReplicaSet |
261
+ | Batch | \`batch/v1\` | Job, CronJob |
262
+ | Networking | \`networking.k8s.io/v1\` | Ingress, NetworkPolicy |
263
+ | RBAC | \`rbac.authorization.k8s.io/v1\` | Role, ClusterRole, RoleBinding, ClusterRoleBinding |
264
+ | Autoscaling | \`autoscaling/v2\` | HorizontalPodAutoscaler |
265
+ | Policy | \`policy/v1\` | PodDisruptionBudget |
266
+
267
+ ## Property types
268
+
269
+ Nested objects like containers, probes, and volumes are expressed as property types:
270
+
271
+ \`\`\`typescript
272
+ import { Deployment, Container, Probe, ResourceRequirements } from "@intentius/chant-lexicon-k8s";
273
+
274
+ export const app = new Deployment({
275
+ metadata: { name: "my-app" },
276
+ spec: {
277
+ replicas: 2,
278
+ selector: { matchLabels: { app: "my-app" } },
279
+ template: {
280
+ metadata: { labels: { app: "my-app" } },
281
+ spec: {
282
+ containers: [
283
+ new Container({
284
+ name: "app",
285
+ image: "my-app:1.0",
286
+ resources: new ResourceRequirements({
287
+ limits: { cpu: "500m", memory: "256Mi" },
288
+ requests: { cpu: "100m", memory: "128Mi" },
289
+ }),
290
+ livenessProbe: new Probe({
291
+ httpGet: { path: "/healthz", port: 8080 },
292
+ initialDelaySeconds: 10,
293
+ }),
294
+ }),
295
+ ],
296
+ },
297
+ },
298
+ },
299
+ });
300
+ \`\`\`
301
+
302
+ ## Specless types
303
+
304
+ Some K8s resources (ConfigMap, Secret, Namespace, ServiceAccount) don't have a \`spec\` field. Their data goes directly on the manifest:
305
+
306
+ \`\`\`typescript
307
+ import { ConfigMap, Secret } from "@intentius/chant-lexicon-k8s";
308
+
309
+ export const config = new ConfigMap({
310
+ metadata: { name: "app-config" },
311
+ data: { DATABASE_URL: "postgres://localhost:5432/mydb" },
312
+ });
313
+
314
+ export const secret = new Secret({
315
+ metadata: { name: "app-secret" },
316
+ stringData: { API_KEY: "changeme" },
317
+ });
318
+ \`\`\`
319
+
320
+ ## Default labels and annotations
321
+
322
+ Use \`defaultLabels()\` and \`defaultAnnotations()\` to inject metadata into all resources:
323
+
324
+ \`\`\`typescript
325
+ import { defaultLabels } from "@intentius/chant-lexicon-k8s";
326
+
327
+ export const labels = defaultLabels({
328
+ "app.kubernetes.io/managed-by": "chant",
329
+ "app.kubernetes.io/part-of": "my-system",
330
+ });
331
+ \`\`\`
332
+
333
+ Explicit labels on individual resources take precedence over defaults.
334
+
335
+ > **Tip:** Avoid hardcoding \`metadata.namespace\` — the [WK8001 lint rule](/chant/lexicons/k8s/lint-rules/) will flag it. Use a config variable or pass namespace at deploy time instead.`,
336
+ },
337
+ {
338
+ slug: "lint-rules",
339
+ title: "Lint Rules",
340
+ description: "Built-in lint rules and post-synth checks for Kubernetes manifests",
341
+ content: `The Kubernetes lexicon ships lint rules that run during \`chant lint\` and post-synth checks that validate the serialized YAML after \`chant build\`.
342
+
343
+ ## Lint rules
344
+
345
+ Lint rules analyze your TypeScript source code before build.
346
+
347
+ ### WK8001 — Hardcoded namespace
348
+
349
+ **Severity:** warning | **Category:** correctness
350
+
351
+ Flags hardcoded namespace strings in resource constructors. Namespaces should be parameterized or derived from configuration.
352
+
353
+ \`\`\`typescript
354
+ // Bad — hardcoded namespace
355
+ new Deployment({ metadata: { namespace: "production" } });
356
+
357
+ // Good — parameterized
358
+ new Deployment({ metadata: { namespace: config.namespace } });
359
+ \`\`\`
360
+
361
+ ## Post-synth checks
362
+
363
+ Post-synth checks run against the serialized YAML after build.
364
+
365
+ ### Security
366
+
367
+ | Rule | Description |
368
+ |------|-------------|
369
+ | WK8005 | Hardcoded secrets in environment variables |
370
+ | WK8041 | API keys detected in env values |
371
+ | WK8042 | Private keys in ConfigMaps or Secrets |
372
+ | WK8202 | Privileged container (\`privileged: true\`) |
373
+ | WK8203 | Writable root filesystem (\`readOnlyRootFilesystem\` not set) |
374
+ | WK8204 | Container running as root (\`runAsNonRoot\` not set) |
375
+ | WK8205 | Capabilities not dropped (\`drop: ["ALL"]\` missing) |
376
+ | WK8207 | Host network access (\`hostNetwork: true\`) |
377
+ | WK8208 | Host PID namespace (\`hostPID: true\`) |
378
+ | WK8209 | Host IPC namespace (\`hostIPC: true\`) |
379
+
380
+ ### Best practices
381
+
382
+ | Rule | Description |
383
+ |------|-------------|
384
+ | WK8006 | Latest image tag or untagged image |
385
+ | WK8101 | Deployment selector doesn't match template labels |
386
+ | WK8102 | Resource missing metadata.labels |
387
+ | WK8103 | Container missing name |
388
+ | WK8104 | Unnamed container ports |
389
+ | WK8105 | Missing imagePullPolicy |
390
+
391
+ ### Reliability
392
+
393
+ | Rule | Description |
394
+ |------|-------------|
395
+ | WK8201 | Container missing resource limits |
396
+ | WK8301 | Container missing health probes (skips Jobs/CronJobs) |
397
+ | WK8302 | Single replica Deployment |
398
+ | WK8303 | HA Deployment without PodDisruptionBudget |
399
+
400
+ ## Running lint
401
+
402
+ \`\`\`bash
403
+ # Lint your chant project
404
+ chant lint
405
+
406
+ # Build (also runs post-synth checks)
407
+ chant build
408
+ \`\`\`
409
+
410
+ To suppress a rule on a specific line:
411
+
412
+ \`\`\`typescript
413
+ // chant-disable-next-line WK8001
414
+ export const deploy = new Deployment({ metadata: { namespace: "prod" } });
415
+ \`\`\`
416
+
417
+ To suppress globally in \`chant.config.ts\`:
418
+
419
+ \`\`\`typescript
420
+ export default {
421
+ lint: {
422
+ rules: {
423
+ WK8001: "off",
424
+ },
425
+ },
426
+ };
427
+ \`\`\`
428
+ `,
429
+ },
430
+ {
431
+ slug: "examples",
432
+ title: "Examples: Resources",
433
+ description: "Walkthrough of Kubernetes resource examples — deployments, services, autoscaling",
434
+ content: `## Basic Deployment
435
+
436
+ A Deployment with Service — the most common pattern for stateless web apps:
437
+
438
+ \`\`\`typescript
439
+ import { Deployment, Service, Container, Probe } from "@intentius/chant-lexicon-k8s";
440
+
441
+ export const deployment = new Deployment({
442
+ metadata: { name: "web", labels: { "app.kubernetes.io/name": "web" } },
443
+ spec: {
444
+ replicas: 3,
445
+ selector: { matchLabels: { "app.kubernetes.io/name": "web" } },
446
+ template: {
447
+ metadata: { labels: { "app.kubernetes.io/name": "web" } },
448
+ spec: {
449
+ containers: [
450
+ new Container({
451
+ name: "web",
452
+ image: "nginx:1.25",
453
+ ports: [{ containerPort: 80, name: "http" }],
454
+ livenessProbe: new Probe({ httpGet: { path: "/", port: 80 } }),
455
+ readinessProbe: new Probe({ httpGet: { path: "/", port: 80 } }),
456
+ }),
457
+ ],
458
+ },
459
+ },
460
+ },
461
+ });
462
+
463
+ export const service = new Service({
464
+ metadata: { name: "web" },
465
+ spec: {
466
+ selector: { "app.kubernetes.io/name": "web" },
467
+ ports: [{ port: 80, targetPort: 80 }],
468
+ },
469
+ });
470
+ \`\`\`
471
+
472
+ ## Microservice with HPA
473
+
474
+ Production-ready microservice with autoscaling and disruption budget:
475
+
476
+ \`\`\`typescript
477
+ import {
478
+ Deployment, Service, HorizontalPodAutoscaler, PodDisruptionBudget,
479
+ Container, Probe, ResourceRequirements,
480
+ } from "@intentius/chant-lexicon-k8s";
481
+
482
+ export const deployment = new Deployment({
483
+ metadata: { name: "api", labels: { "app.kubernetes.io/name": "api" } },
484
+ spec: {
485
+ replicas: 2,
486
+ selector: { matchLabels: { "app.kubernetes.io/name": "api" } },
487
+ template: {
488
+ metadata: { labels: { "app.kubernetes.io/name": "api" } },
489
+ spec: {
490
+ containers: [
491
+ new Container({
492
+ name: "api",
493
+ image: "api:1.0",
494
+ ports: [{ containerPort: 8080, name: "http" }],
495
+ resources: new ResourceRequirements({
496
+ limits: { cpu: "500m", memory: "256Mi" },
497
+ requests: { cpu: "100m", memory: "128Mi" },
498
+ }),
499
+ livenessProbe: new Probe({ httpGet: { path: "/healthz", port: 8080 } }),
500
+ readinessProbe: new Probe({ httpGet: { path: "/readyz", port: 8080 } }),
501
+ }),
502
+ ],
503
+ },
504
+ },
505
+ },
506
+ });
507
+
508
+ export const hpa = new HorizontalPodAutoscaler({
509
+ metadata: { name: "api" },
510
+ spec: {
511
+ scaleTargetRef: { apiVersion: "apps/v1", kind: "Deployment", name: "api" },
512
+ minReplicas: 2,
513
+ maxReplicas: 10,
514
+ metrics: [
515
+ { type: "Resource", resource: { name: "cpu", target: { type: "Utilization", averageUtilization: 70 } } },
516
+ ],
517
+ },
518
+ });
519
+
520
+ export const pdb = new PodDisruptionBudget({
521
+ metadata: { name: "api" },
522
+ spec: {
523
+ minAvailable: 1,
524
+ selector: { matchLabels: { "app.kubernetes.io/name": "api" } },
525
+ },
526
+ });
527
+ \`\`\`
528
+
529
+ ## Stateful Application
530
+
531
+ Database deployment with persistent storage using the StatefulApp composite:
532
+
533
+ \`\`\`typescript
534
+ import { StatefulApp } from "@intentius/chant-lexicon-k8s";
535
+
536
+ const { statefulSet, service } = StatefulApp({
537
+ name: "postgres",
538
+ image: "postgres:16",
539
+ port: 5432,
540
+ storageSize: "20Gi",
541
+ replicas: 3,
542
+ env: [{ name: "POSTGRES_DB", value: "mydb" }],
543
+ });
544
+ \`\`\`
545
+ `,
546
+ },
547
+ {
548
+ slug: "composite-examples",
549
+ title: "Examples: Composites",
550
+ description: "Composite examples — WebApp, CronWorkload, AutoscaledService, WorkerPool, NamespaceEnv, NodeAgent",
551
+ content: `Composites are higher-level constructs that produce multiple coordinated K8s resources from a single function call.
552
+
553
+ ## WebApp
554
+
555
+ Quick deployment with Deployment + Service + optional Ingress:
556
+
557
+ \`\`\`typescript
558
+ import { WebApp } from "@intentius/chant-lexicon-k8s";
559
+
560
+ const { deployment, service, ingress } = WebApp({
561
+ name: "frontend",
562
+ image: "frontend:1.0",
563
+ port: 3000,
564
+ replicas: 3,
565
+ ingressHost: "frontend.example.com",
566
+ ingressTlsSecret: "frontend-tls",
567
+ });
568
+ \`\`\`
569
+
570
+ ## CronWorkload
571
+
572
+ Scheduled workload with RBAC:
573
+
574
+ \`\`\`typescript
575
+ import { CronWorkload } from "@intentius/chant-lexicon-k8s";
576
+
577
+ const { cronJob, serviceAccount, role, roleBinding } = CronWorkload({
578
+ name: "db-backup",
579
+ image: "postgres:16",
580
+ schedule: "0 2 * * *",
581
+ command: ["pg_dump", "-h", "postgres", "mydb"],
582
+ rbacRules: [
583
+ { apiGroups: [""], resources: ["secrets"], verbs: ["get"] },
584
+ ],
585
+ });
586
+ \`\`\`
587
+
588
+ ## AutoscaledService
589
+
590
+ Production HTTP service with HPA, PDB, and configurable probes:
591
+
592
+ \`\`\`typescript
593
+ import { AutoscaledService } from "@intentius/chant-lexicon-k8s";
594
+
595
+ const { deployment, service, hpa, pdb } = AutoscaledService({
596
+ name: "api",
597
+ image: "api:2.0",
598
+ port: 8080,
599
+ maxReplicas: 10,
600
+ minReplicas: 3,
601
+ targetCPUPercent: 60,
602
+ targetMemoryPercent: 80,
603
+ cpuRequest: "200m",
604
+ memoryRequest: "256Mi",
605
+ cpuLimit: "1",
606
+ memoryLimit: "1Gi",
607
+ livenessPath: "/healthz",
608
+ readinessPath: "/readyz",
609
+ topologySpread: true,
610
+ namespace: "production",
611
+ });
612
+ \`\`\`
613
+
614
+ ## WorkerPool
615
+
616
+ Background queue worker with RBAC and optional autoscaling:
617
+
618
+ \`\`\`typescript
619
+ import { WorkerPool } from "@intentius/chant-lexicon-k8s";
620
+
621
+ const { deployment, serviceAccount, role, roleBinding, configMap, hpa } = WorkerPool({
622
+ name: "email-worker",
623
+ image: "worker:1.0",
624
+ command: ["bundle", "exec", "sidekiq"],
625
+ config: { REDIS_URL: "redis://redis:6379", QUEUE: "emails" },
626
+ autoscaling: { minReplicas: 2, maxReplicas: 20, targetCPUPercent: 60 },
627
+ });
628
+ \`\`\`
629
+
630
+ Pass \`rbacRules: []\` to opt out of RBAC resource creation entirely.
631
+
632
+ ## NamespaceEnv
633
+
634
+ Multi-tenant namespace with resource guardrails and network isolation:
635
+
636
+ \`\`\`typescript
637
+ import { NamespaceEnv } from "@intentius/chant-lexicon-k8s";
638
+
639
+ const { namespace, resourceQuota, limitRange, networkPolicy } = NamespaceEnv({
640
+ name: "team-alpha",
641
+ cpuQuota: "8",
642
+ memoryQuota: "16Gi",
643
+ maxPods: 50,
644
+ defaultCpuRequest: "100m",
645
+ defaultMemoryRequest: "128Mi",
646
+ defaultCpuLimit: "500m",
647
+ defaultMemoryLimit: "512Mi",
648
+ defaultDenyIngress: true,
649
+ defaultDenyEgress: true,
650
+ });
651
+ \`\`\`
652
+
653
+ Setting a ResourceQuota without LimitRange defaults will emit a warning — pods without explicit resource requests will fail to schedule.
654
+
655
+ ## NodeAgent
656
+
657
+ Per-node DaemonSet agent with cluster-wide RBAC and host path mounts:
658
+
659
+ \`\`\`typescript
660
+ import { NodeAgent } from "@intentius/chant-lexicon-k8s";
661
+
662
+ const { daemonSet, serviceAccount, clusterRole, clusterRoleBinding, configMap } = NodeAgent({
663
+ name: "log-collector",
664
+ image: "fluentd:v1.16",
665
+ port: 24224,
666
+ hostPaths: [
667
+ { name: "varlog", hostPath: "/var/log", mountPath: "/var/log" },
668
+ { name: "containers", hostPath: "/var/lib/docker/containers", mountPath: "/var/lib/docker/containers" },
669
+ ],
670
+ config: { "fluent.conf": "<source>\\n @type tail\\n path /var/log/*.log\\n</source>" },
671
+ rbacRules: [
672
+ { apiGroups: [""], resources: ["pods", "namespaces"], verbs: ["get", "list", "watch"] },
673
+ ],
674
+ namespace: "monitoring",
675
+ });
676
+ \`\`\`
677
+
678
+ ## Deploying composites
679
+
680
+ Composites produce plain prop objects. To deploy them, write each resource to a \`.k8s.ts\` file then use the standard chant build → kubectl apply workflow.
681
+
682
+ ### Step 1 — Write a chant source file
683
+
684
+ \`\`\`typescript
685
+ // src/infra.k8s.ts
686
+ import { AutoscaledService, NamespaceEnv, WorkerPool, NodeAgent } from "@intentius/chant-lexicon-k8s";
687
+
688
+ // Namespace with guardrails
689
+ const nsEnv = NamespaceEnv({
690
+ name: "production",
691
+ cpuQuota: "16",
692
+ memoryQuota: "32Gi",
693
+ defaultCpuRequest: "100m",
694
+ defaultMemoryRequest: "128Mi",
695
+ defaultCpuLimit: "1",
696
+ defaultMemoryLimit: "512Mi",
697
+ });
698
+ export const { namespace, resourceQuota, limitRange, networkPolicy } = nsEnv;
699
+
700
+ // API with autoscaling
701
+ const api = AutoscaledService({
702
+ name: "api",
703
+ image: "api:1.0",
704
+ port: 8080,
705
+ maxReplicas: 10,
706
+ cpuRequest: "200m",
707
+ memoryRequest: "256Mi",
708
+ topologySpread: true,
709
+ namespace: "production",
710
+ });
711
+ export const { deployment, service, hpa, pdb } = api;
712
+
713
+ // Background workers
714
+ const workers = WorkerPool({
715
+ name: "email-worker",
716
+ image: "worker:1.0",
717
+ command: ["bundle", "exec", "sidekiq"],
718
+ config: { REDIS_URL: "redis://redis:6379" },
719
+ autoscaling: { minReplicas: 2, maxReplicas: 20 },
720
+ namespace: "production",
721
+ });
722
+ export const workerDeployment = workers.deployment;
723
+ export const workerSA = workers.serviceAccount;
724
+ export const workerRole = workers.role;
725
+ export const workerRoleBinding = workers.roleBinding;
726
+ export const workerConfig = workers.configMap;
727
+ export const workerHPA = workers.hpa;
728
+ \`\`\`
729
+
730
+ ### Step 2 — Build and validate
731
+
732
+ \`\`\`bash
733
+ # Build YAML manifests
734
+ chant build src/ --output manifests.yaml
735
+
736
+ # Lint for common issues
737
+ chant lint src/
738
+
739
+ # Server-side dry run (validates with admission webhooks)
740
+ kubectl apply -f manifests.yaml --dry-run=server
741
+ \`\`\`
742
+
743
+ ### Step 3 — Deploy
744
+
745
+ \`\`\`bash
746
+ # Diff before applying
747
+ kubectl diff -f manifests.yaml
748
+
749
+ # Apply
750
+ kubectl apply -f manifests.yaml
751
+
752
+ # Verify rollout
753
+ kubectl rollout status deployment/api -n production
754
+ kubectl get pods,svc,hpa -n production
755
+ \`\`\`
756
+
757
+ ### k3d local validation
758
+
759
+ For local testing before pushing to a real cluster:
760
+
761
+ \`\`\`bash
762
+ cd lexicons/k8s
763
+
764
+ # Create a k3d cluster, apply composites, verify, and delete
765
+ just k3d-validate-composites
766
+
767
+ # Keep the cluster for manual inspection
768
+ just k3d-validate-composites --keep-cluster
769
+
770
+ # Reuse an existing cluster
771
+ just k3d-validate-composites --reuse-cluster --verbose
772
+ \`\`\`
773
+
774
+ The \`/chant-k8s\` AI skill covers the full lifecycle — scaffold, build, lint, apply, rollback, and troubleshooting.
775
+ `,
776
+ },
777
+ {
778
+ slug: "operational-playbook",
779
+ title: "Operational Playbook",
780
+ description: "Build, deploy, debug, and troubleshoot Kubernetes manifests produced by chant",
781
+ content: `This playbook covers the full lifecycle of chant-produced Kubernetes manifests \u2014 from build through production debugging. The same content is available to AI agents via the \`/chant-k8s\` skill.
782
+
783
+ ## Build & validate
784
+
785
+ | Step | Command | What it catches |
786
+ |------|---------|-----------------|
787
+ | Lint source | \`chant lint src/\` | Hardcoded namespaces (WK8001) |
788
+ | Build manifests | \`chant build src/ --output manifests.yaml\` | Post-synth: secrets in env (WK8005), latest tags (WK8006), API keys (WK8041), missing probes (WK8301), no resource limits (WK8201), privileged containers (WK8202), and more |
789
+ | Server dry-run | \`kubectl apply -f manifests.yaml --dry-run=server\` | K8s API validation: schema errors, admission webhooks |
790
+
791
+ Run lint on every edit. Run build + dry-run before every apply.
792
+
793
+ ## Deploy to Kubernetes
794
+
795
+ \`\`\`bash
796
+ # Build
797
+ chant build src/ --output manifests.yaml
798
+
799
+ # Diff before applying
800
+ kubectl diff -f manifests.yaml
801
+
802
+ # Dry run (validates with admission webhooks)
803
+ kubectl apply -f manifests.yaml --dry-run=server
804
+
805
+ # Apply
806
+ kubectl apply -f manifests.yaml
807
+ \`\`\`
808
+
809
+ ## Rollout & rollback
810
+
811
+ \`\`\`bash
812
+ # Watch rollout progress
813
+ kubectl rollout status deployment/my-app --timeout=300s
814
+
815
+ # Check rollout history
816
+ kubectl rollout history deployment/my-app
817
+
818
+ # Undo last rollout
819
+ kubectl rollout undo deployment/my-app
820
+
821
+ # Roll back to a specific revision
822
+ kubectl rollout undo deployment/my-app --to-revision=2
823
+ \`\`\`
824
+
825
+ ## Debugging strategies
826
+
827
+ ### Pod status and events
828
+
829
+ \`\`\`bash
830
+ # Overview
831
+ kubectl get pods -l app.kubernetes.io/name=my-app
832
+ kubectl get events --sort-by=.lastTimestamp -n <namespace>
833
+
834
+ # Deep dive into a specific pod
835
+ kubectl describe pod <pod-name>
836
+
837
+ # Logs (current and previous crash)
838
+ kubectl logs <pod-name>
839
+ kubectl logs <pod-name> --previous
840
+ kubectl logs <pod-name> -c <container-name> # specific container
841
+ kubectl logs deployment/my-app --all-containers
842
+
843
+ # Debug containers (K8s 1.25+)
844
+ kubectl debug <pod-name> -it --image=busybox --target=<container>
845
+
846
+ # Port-forwarding for local testing
847
+ kubectl port-forward svc/my-app 8080:80
848
+ kubectl port-forward pod/<pod-name> 8080:8080
849
+ \`\`\`
850
+
851
+ ### Resource inspection
852
+
853
+ \`\`\`bash
854
+ # Get all resources in namespace
855
+ kubectl get all -n <namespace>
856
+
857
+ # YAML output for debugging
858
+ kubectl get deployment/my-app -o yaml
859
+
860
+ # Check resource usage
861
+ kubectl top pods -l app.kubernetes.io/name=my-app
862
+ kubectl top nodes
863
+ \`\`\`
864
+
865
+ ## Common error patterns
866
+
867
+ | Status | Meaning | Diagnostic command | Typical fix |
868
+ |--------|---------|-------------------|-------------|
869
+ | Pending | Not scheduled | \`kubectl describe pod\` \u2192 Events | Check resource requests, node selectors, taints, PVC binding |
870
+ | CrashLoopBackOff | App crashing on start | \`kubectl logs --previous\` | Fix app startup, check probe config, increase initialDelaySeconds |
871
+ | ImagePullBackOff | Image not found | \`kubectl describe pod\` \u2192 Events | Verify image name/tag, check imagePullSecrets, registry auth |
872
+ | OOMKilled | Out of memory | \`kubectl describe pod\` \u2192 Last State | Increase memory limit, profile app memory usage |
873
+ | Evicted | Node disk/memory pressure | \`kubectl describe node\` | Increase limits, add node capacity, check for log/tmp bloat |
874
+ | CreateContainerError | Container config issue | \`kubectl describe pod\` \u2192 Events | Check volume mounts, configmap/secret refs, security context |
875
+ | Init:CrashLoopBackOff | Init container failing | \`kubectl logs -c <init-container>\` | Fix init container command, check dependencies |
876
+
877
+ ## Deployment strategies
878
+
879
+ - **RollingUpdate** (default): Gradually replaces pods. Set \`maxSurge\` and \`maxUnavailable\`.
880
+ - **Recreate**: All pods terminated before new ones created. Use for stateful apps that cannot run multiple versions.
881
+ - **Canary**: Deploy a second Deployment with 1 replica + same selector labels. Route percentage via Ingress annotations or service mesh.
882
+ - **Blue/Green**: Two full Deployments (blue/green), switch Service selector between them.
883
+
884
+ ## Production safety
885
+
886
+ ### Pre-apply validation
887
+
888
+ \`\`\`bash
889
+ # Always diff before applying
890
+ kubectl diff -f manifests.yaml
891
+
892
+ # Server-side dry run (validates with admission webhooks)
893
+ kubectl apply -f manifests.yaml --dry-run=server
894
+
895
+ # Client-side dry run (fast, but no webhook validation)
896
+ kubectl apply -f manifests.yaml --dry-run=client
897
+ \`\`\`
898
+
899
+ Use server-side dry-run before production applies \u2014 it catches schema errors and runs admission webhooks. Client-side dry-run is faster but only validates locally.
900
+
901
+ ## Troubleshooting reference
902
+
903
+ | Symptom | Likely cause | Resolution |
904
+ |---------|-------------|------------|
905
+ | Pod stuck in Pending | Insufficient CPU/memory on nodes | Scale up cluster or reduce resource requests |
906
+ | Pod stuck in Pending | PVC not bound | Check StorageClass exists, PV available |
907
+ | Pod stuck in Pending | Node selector/affinity mismatch | Verify node labels match selectors |
908
+ | Pod stuck in ContainerCreating | ConfigMap/Secret not found | Ensure referenced ConfigMaps/Secrets exist |
909
+ | Pod stuck in ContainerCreating | Volume mount failure | Check PVC status, CSI driver health |
910
+ | Service returns 503 | No ready endpoints | Check pod readiness probes, selector match |
911
+ | Service returns 503 | Wrong port configuration | Verify targetPort matches containerPort |
912
+ | Ingress returns 404 | Backend service not found | Check Ingress rules, service name/port |
913
+ | Ingress returns 404 | Wrong path matching | Check pathType (Prefix vs Exact) |
914
+ | HPA not scaling | Metrics server not installed | Install metrics-server |
915
+ | HPA not scaling | Resource requests not set | Add CPU/memory requests to containers |
916
+ | CronJob not running | Invalid cron expression | Validate cron syntax (5-field format) |
917
+ | NetworkPolicy blocking | Default deny applied | Add explicit allow rules for required traffic |
918
+ | RBAC permission denied | Missing Role/RoleBinding | Check ServiceAccount bindings and verb permissions |`,
919
+ },
920
+ {
921
+ slug: "importing-yaml",
922
+ title: "Importing Existing YAML",
923
+ description: "Convert existing Kubernetes YAML manifests into typed TypeScript source files",
924
+ content: `Chant can parse existing Kubernetes YAML manifests and generate typed TypeScript source files. This is useful for migrating existing infrastructure to chant.
925
+
926
+ ## How it works
927
+
928
+ \`\`\`
929
+ Input YAML \u2192 parse \u2192 generate TypeScript \u2192 export typed resources
930
+ \`\`\`
931
+
932
+ The importer reads multi-document YAML, identifies each resource\u2019s \`apiVersion\` and \`kind\`, and generates the corresponding typed constructor call. The output is a valid \`.k8s.ts\` file you can immediately build with \`chant build\`.
933
+
934
+ ## Running the import roundtrip
935
+
936
+ From the \`lexicons/k8s\` directory:
937
+
938
+ \`\`\`bash
939
+ # Full roundtrip \u2014 clones kubernetes/examples, imports, serializes, compares
940
+ just full-roundtrip
941
+
942
+ # Skip clone if repo is already cached
943
+ just full-roundtrip --skip-clone
944
+
945
+ # Verbose output + filter to a specific manifest
946
+ just full-roundtrip --skip-clone --verbose --manifest guestbook
947
+
948
+ # Skip the serialize phase (parse + generate only)
949
+ just full-roundtrip --skip-clone --skip-serialize
950
+ \`\`\`
951
+
952
+ ## Parse-only mode
953
+
954
+ For quick validation without the full serialize cycle:
955
+
956
+ \`\`\`bash
957
+ just import-samples --skip-clone --verbose
958
+
959
+ # Filter to a specific manifest
960
+ just import-samples --skip-clone --verbose --manifest guestbook
961
+ \`\`\`
962
+
963
+ This runs the YAML \u2192 TypeScript generation but skips the round-trip comparison.
964
+
965
+ ## What "pass" means
966
+
967
+ A passing roundtrip means the serialized YAML, when re-parsed, produces the **same number of resources with matching \`kind\` values**. Exact YAML comparison is intentionally skipped \u2014 key ordering, quoting, and comments differ between input and output.
968
+
969
+ ## Validating against a cluster
970
+
971
+ To verify the serialized output is valid Kubernetes YAML, apply it to a local k3d cluster:
972
+
973
+ \`\`\`bash
974
+ # Create a k3d cluster, apply serialized manifests, verify, tear down
975
+ just k3d-validate
976
+
977
+ # Keep the cluster for manual inspection
978
+ just k3d-validate --keep-cluster
979
+
980
+ # Reuse an existing cluster
981
+ just k3d-validate --reuse-cluster --verbose
982
+ \`\`\`
983
+
984
+ See [Testing & Validation](/chant/lexicons/k8s/testing/) for full details on the k3d validation workflow.
985
+
986
+ ## Limitations
987
+
988
+ The import pipeline does not support:
989
+
990
+ - **Helm charts** \u2014 template syntax (\`{{ .Values.x }}\`) is not valid YAML
991
+ - **Kustomize overlays** \u2014 overlays are processed by kustomize before producing YAML
992
+ - **Custom Resource Definitions (CRDs)** \u2014 only built-in K8s resource types are recognized
993
+ - **Cloud-specific volumes** \u2014 provider-specific volume types (awsElasticBlockStore, gcePersistentDisk) are parsed but excluded from k3d cluster validation (k3d doesn\u2019t provide cloud volume drivers)`,
994
+ },
995
+ {
996
+ slug: "testing",
997
+ title: "Testing & Validation",
998
+ description: "Roundtrip tests and k3d cluster validation for the Kubernetes lexicon",
999
+ content: `The Kubernetes lexicon includes scripts to verify the full import–serialize roundtrip and validate serialized YAML against a real cluster.
1000
+
1001
+ ## Roundtrip tests
1002
+
1003
+ The roundtrip test suite clones \`kubernetes/examples\` and runs each manifest through the full pipeline:
1004
+
1005
+ \`\`\`
1006
+ Input YAML → parse → generate TS → dynamic import → serialize YAML → re-parse → compare
1007
+ \`\`\`
1008
+
1009
+ **"Pass" means** the serialized YAML, when re-parsed, produces the same number of resources with matching \`kind\` values. Exact YAML comparison is intentionally skipped — key ordering, quoting, and comments differ between input and output.
1010
+
1011
+ ### Running
1012
+
1013
+ \`\`\`bash
1014
+ cd lexicons/k8s
1015
+
1016
+ # Full roundtrip (clones repo on first run)
1017
+ just full-roundtrip
1018
+
1019
+ # Skip clone if repo is already cached
1020
+ just full-roundtrip --skip-clone
1021
+
1022
+ # Parse + generate only (skip serialize phase)
1023
+ just full-roundtrip --skip-clone --skip-serialize
1024
+
1025
+ # Verbose output + filter to a specific manifest
1026
+ just full-roundtrip --skip-clone --verbose --manifest guestbook
1027
+ \`\`\`
1028
+
1029
+ The test requires \`just generate\` to have been run first so the generated index exports real constructors. If the generated index is empty, the test falls back to parse-only mode automatically.
1030
+
1031
+ ### Parse-only roundtrip
1032
+
1033
+ The original parse-only roundtrip test is still available:
1034
+
1035
+ \`\`\`bash
1036
+ just import-samples --skip-clone --verbose
1037
+ \`\`\`
1038
+
1039
+ ## k3d cluster validation
1040
+
1041
+ The k3d validation script applies serialized YAML to a real Kubernetes cluster to verify the output is valid and deployable.
1042
+
1043
+ ### Prerequisites
1044
+
1045
+ - [k3d](https://k3d.io/) — lightweight K8s cluster in Docker
1046
+ - [kubectl](https://kubernetes.io/docs/tasks/tools/) — Kubernetes CLI
1047
+ - Docker running
1048
+
1049
+ ### Running
1050
+
1051
+ \`\`\`bash
1052
+ cd lexicons/k8s
1053
+
1054
+ # Create cluster, run tests, delete cluster
1055
+ just k3d-validate
1056
+
1057
+ # Keep cluster for manual inspection
1058
+ just k3d-validate --keep-cluster
1059
+
1060
+ # Reuse an existing cluster
1061
+ just k3d-validate --reuse-cluster --verbose
1062
+ \`\`\`
1063
+
1064
+ After \`--keep-cluster\`, inspect resources manually:
1065
+
1066
+ \`\`\`bash
1067
+ kubectl get all
1068
+ kubectl get deployments,services
1069
+ \`\`\`
1070
+
1071
+ ### Safe manifest allowlist
1072
+
1073
+ The script uses a curated allowlist of ~14 manifests known to work on bare k3d (no cloud volumes, CRDs, or GPUs). These include:
1074
+
1075
+ - **Guestbook** — Deployments and Services for frontend + Redis
1076
+ - **Cassandra** — Headless Service
1077
+ - **vLLM** — Service
1078
+
1079
+ ### What it validates
1080
+
1081
+ For each manifest the script:
1082
+
1083
+ 1. Runs the full roundtrip (YAML → Chant DSL → YAML)
1084
+ 2. Applies the serialized YAML with \`kubectl apply\`
1085
+ 3. Verifies the resource exists with \`kubectl get\`
1086
+
1087
+ A failure at any stage is reported with the specific phase that failed.
1088
+
1089
+ ## See also
1090
+
1091
+ - [Importing Existing YAML](/chant/lexicons/k8s/importing-yaml/) — convert existing K8s manifests into typed TypeScript`,
1092
+ },
1093
+ {
1094
+ slug: "skills",
1095
+ title: "AI Skills",
1096
+ description: "AI agent skills bundled with the Kubernetes lexicon",
1097
+ content: `The Kubernetes lexicon ships an AI skill called **chant-k8s** that teaches AI coding agents how to build, validate, and deploy Kubernetes manifests from a chant project.
1098
+
1099
+ ## What are skills?
1100
+
1101
+ Skills are structured markdown documents bundled with a lexicon. When an AI agent works in a chant project, it discovers and loads relevant skills automatically — giving it operational knowledge about the deployment workflow without requiring the user to explain each step.
1102
+
1103
+ ## Installation
1104
+
1105
+ When you scaffold a new project with \`chant init --lexicon k8s\`, the skill is installed to \`.claude/skills/chant-k8s/SKILL.md\` for automatic discovery by Claude Code.
1106
+
1107
+ ## Skill: chant-k8s
1108
+
1109
+ The \`chant-k8s\` skill covers the full deployment lifecycle:
1110
+
1111
+ - **Build** — \`chant build src/ --output manifests.yaml\`
1112
+ - **Lint** — \`chant lint src/\` + post-synth checks (20 rules)
1113
+ - **Apply** — \`kubectl apply -f manifests.yaml\`
1114
+ - **Status** — \`kubectl get pods,svc,deploy\`
1115
+ - **Rollback** — \`kubectl rollout undo deployment/my-app\`
1116
+ - **Troubleshooting** — pod status, logs, events, common error patterns
1117
+
1118
+ The skill is invocable as a slash command: \`/chant-k8s\`
1119
+
1120
+ The full playbook is also available as a [documentation page](/chant/lexicons/k8s/operational-playbook/).
1121
+
1122
+ ## MCP integration
1123
+
1124
+ The lexicon also provides MCP (Model Context Protocol) tools and resources:
1125
+
1126
+ | MCP tool | Description |
1127
+ |----------|-------------|
1128
+ | \`diff\` | Compare current build output against previous |
1129
+
1130
+ | MCP resource | Description |
1131
+ |--------------|-------------|
1132
+ | \`resource-catalog\` | JSON list of all supported K8s resource types |
1133
+ | \`examples/basic-deployment\` | Example Deployment + Service code |`,
1134
+ },
1135
+ ],
1136
+ basePath: "/chant/lexicons/k8s/",
1137
+ };
1138
+
1139
+ const result = await docsPipeline(config);
1140
+ writeDocsSite(config, result);
1141
+
1142
+ if (opts?.verbose) {
1143
+ console.error(
1144
+ `Generated docs: ${result.stats.resources} resources, ${result.stats.properties} properties, ${result.stats.services} services, ${result.stats.rules} rules`,
1145
+ );
1146
+ }
1147
+ }