@intentius/chant-lexicon-aws 0.0.5 → 0.0.8

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.
@@ -152,7 +152,7 @@ Produces this CloudFormation resource:
152
152
  }
153
153
  \`\`\`
154
154
 
155
- Notice how \`dataBucket\` becomes \`DataBucket\` (PascalCase logical ID), and \`bucketName\` becomes \`BucketName\`. This mapping is automatic.
155
+ Notice how \`dataBucket\` becomes \`DataBucket\` (PascalCase logical ID). Property names like \`BucketName\` use the CloudFormation spec-native PascalCase directly.
156
156
 
157
157
  ## Resource types and naming
158
158
 
@@ -171,17 +171,13 @@ Common resources get fixed short names for stability. When two services define t
171
171
 
172
172
  **Discovering available resources:** Your editor's autocomplete is the best tool — every resource is a named export from the lexicon. You can also run \`chant list\` to see all resource types, or browse the generated TypeScript types.
173
173
 
174
- ## The barrel file
174
+ ## Imports and cross-file references
175
175
 
176
- Every chant project has a barrel file (conventionally \`_.ts\`) that re-exports the lexicon and provides cross-file references:
177
-
178
- {{file:getting-started/src/_.ts}}
179
-
180
- Other files import the barrel and use \`$\` to reference sibling exports:
176
+ Chant projects use standard TypeScript imports. Lexicon types come from the lexicon package, and sibling exports are imported directly from the file that defines them:
181
177
 
182
178
  {{file:getting-started/src/data-bucket.ts}}
183
179
 
184
- The \`$\` proxy lazily resolves exports from other files in the same directory. When the serializer encounters \`_.$.encryptionDefault\`, it resolves to the actual exported value and serializes the reference as \`Fn::GetAtt\` or \`Ref\` as appropriate. This is how cross-file references work without circular imports.
180
+ When you reference a resource or attribute from another file (e.g. \`dataBucket.arn\`), the serializer resolves it to \`Fn::GetAtt\` or \`Ref\` as appropriate. This is how cross-file references work standard imports, no indirection.
185
181
 
186
182
  ## Parameters
187
183
 
@@ -203,23 +199,13 @@ Produces:
203
199
 
204
200
  Reference parameters with \`Ref\`:
205
201
 
206
- \`\`\`typescript
207
- import { Ref } from "@intentius/chant-lexicon-aws";
208
-
209
- export const bucket = new Bucket({
210
- bucketName: Sub\`\${Ref("Environment")}-data\`,
211
- });
212
- \`\`\`
202
+ {{file:docs-snippets/src/parameter-ref.ts}}
213
203
 
214
204
  ## Outputs
215
205
 
216
206
  Use \`output()\` to create explicit stack outputs. Cross-resource \`AttrRef\` usage is also auto-detected and promoted to outputs when needed.
217
207
 
218
- \`\`\`typescript
219
- import { output } from "@intentius/chant";
220
-
221
- export const bucketArn = output(dataBucket.arn, "DataBucketArn");
222
- \`\`\`
208
+ {{file:docs-snippets/src/output-explicit.ts}}
223
209
 
224
210
  Produces:
225
211
 
@@ -235,11 +221,7 @@ Produces:
235
221
 
236
222
  Runtime context values available in every template, accessed via the \`AWS\` namespace:
237
223
 
238
- \`\`\`typescript
239
- import { AWS, Sub } from "@intentius/chant-lexicon-aws";
240
-
241
- const endpoint = Sub\`https://s3.\${AWS.Region}.\${AWS.URLSuffix}\`;
242
- \`\`\`
224
+ {{file:docs-snippets/src/pseudo-params.ts}}
243
225
 
244
226
  | Pseudo-parameter | Description |
245
227
  |---|---|
@@ -280,58 +262,17 @@ The \`PolicyDocument\` interface and its supporting types:
280
262
 
281
263
  Policy documents use **PascalCase keys** (\`Effect\`, \`Action\`, \`Resource\`) because they follow the IAM JSON Policy Language spec — CloudFormation passes them through to IAM as-is, unlike resource properties which are automatically converted from camelCase.
282
264
 
283
- The recommended pattern is to extract policies into your \`defaults.ts\` and reference them via the barrel:
284
-
285
- \`\`\`typescript
286
- // defaults.ts — shared trust policies and permission policies
287
- import { Sub, AWS, type PolicyDocument } from "@intentius/chant-lexicon-aws";
288
-
289
- export const lambdaTrustPolicy: PolicyDocument = {
290
- Version: "2012-10-17",
291
- Statement: [{
292
- Effect: "Allow",
293
- Principal: { Service: "lambda.amazonaws.com" },
294
- Action: "sts:AssumeRole",
295
- }],
296
- };
265
+ The recommended pattern is to extract policies into your \`defaults.ts\` and import them directly:
297
266
 
298
- export const s3ReadPolicy: PolicyDocument = {
299
- Statement: [{
300
- Effect: "Allow",
301
- Action: ["s3:GetObject", "s3:ListBucket"],
302
- Resource: "*",
303
- }],
304
- };
305
- \`\`\`
267
+ {{file:docs-snippets/src/policy-trust.ts}}
306
268
 
307
269
  Then reference them from resource files:
308
270
 
309
- \`\`\`typescript
310
- // role.ts
311
- import * as _ from "./_";
312
-
313
- export const functionRole = new _.Role({
314
- assumeRolePolicyDocument: _.$.lambdaTrustPolicy,
315
- });
316
-
317
- export const readPolicy = new _.ManagedPolicy({
318
- policyDocument: _.$.s3ReadPolicy,
319
- roles: [_.$.functionRole],
320
- });
321
- \`\`\`
271
+ {{file:docs-snippets/src/policy-role.ts}}
322
272
 
323
273
  For scoped resource ARNs, use \`Sub\` in the policy constant:
324
274
 
325
- \`\`\`typescript
326
- // defaults.ts
327
- export const bucketWritePolicy: PolicyDocument = {
328
- Statement: [{
329
- Effect: "Allow",
330
- Action: ["s3:PutObject"],
331
- Resource: Sub\`arn:aws:s3:::\${AWS.StackName}-data/*\`,
332
- }],
333
- };
334
- \`\`\`
275
+ {{file:docs-snippets/src/policy-scoped.ts}}
335
276
 
336
277
  The \`IamPolicyPrincipal\` type supports all principal forms — wildcard (\`"*"\`), AWS accounts, services, and federated providers:
337
278
 
@@ -361,44 +302,17 @@ CloudFormation \`Conditions\` blocks are recognized by the serializer when impor
361
302
 
362
303
  CloudFormation Mappings are a static lookup mechanism. In chant, use TypeScript objects instead — they're evaluated at build time and produce the same result:
363
304
 
364
- \`\`\`typescript
365
- const regionAMIs: Record<string, string> = {
366
- "us-east-1": "ami-12345678",
367
- "us-west-2": "ami-87654321",
368
- "eu-west-1": "ami-abcdef01",
369
- };
370
-
371
- // Use directly in resource properties
372
- export const server = new Instance({
373
- imageId: regionAMIs["us-east-1"],
374
- instanceType: "t3.micro",
375
- });
376
- \`\`\`
305
+ {{file:docs-snippets/src/mappings.ts}}
377
306
 
378
307
  For deploy-time region lookups, combine \`AWS.Region\` with \`If\` or use \`Fn::Sub\` with SSM parameter store references.
379
308
 
380
309
  ## Nested stacks
381
310
 
382
- CloudFormation nested stacks (\`AWS::CloudFormation::Stack\`) let you decompose large templates into smaller, reusable child templates. Use \`nestedStack()\` to reference a child project directory — a subdirectory with its own barrel file that builds independently:
311
+ CloudFormation nested stacks (\`AWS::CloudFormation::Stack\`) let you decompose large templates into smaller, reusable child templates. Use \`nestedStack()\` to reference a child project directory — a subdirectory that builds independently:
383
312
 
384
- \`\`\`typescript
385
- // Child project declares outputs with stackOutput()
386
- // src/network/outputs.ts
387
- export const vpcId = stackOutput(_.$.vpc.vpcId);
388
- export const subnetId = stackOutput(_.$.subnet.subnetId);
389
- export const lambdaSgId = stackOutput(_.$.lambdaSg.groupId);
390
-
391
- // Parent references the child project
392
- // src/app.ts
393
- const network = _.nestedStack("network", import.meta.dir + "/network");
394
-
395
- export const handler = new _.Function({
396
- vpcConfig: {
397
- subnetIds: [network.outputs.subnetId], // cross-stack ref
398
- securityGroupIds: [network.outputs.lambdaSgId],
399
- },
400
- });
401
- \`\`\`
313
+ {{file:nested-stacks/src/network/outputs.ts}}
314
+
315
+ {{file:nested-stacks/src/app.ts}}
402
316
 
403
317
  chant handles the wiring: child template gets an \`Outputs\` section, parent uses \`Fn::GetAtt\` on the stack resource. A \`TemplateBasePath\` parameter lets you configure child template URLs per environment.
404
318
 
@@ -412,14 +326,7 @@ Tags are standard CloudFormation \`Key\`/\`Value\` arrays. Pass them on any reso
412
326
 
413
327
  To apply tags across all members of a composite, use [\`propagate\`](./composites#propagate--shared-properties):
414
328
 
415
- \`\`\`typescript
416
- import { propagate } from "@intentius/chant";
417
-
418
- export const api = propagate(
419
- LambdaApi({ name: "myApi", code: lambdaCode }),
420
- { tags: [{ key: "env", value: "prod" }] },
421
- );
422
- \`\`\``,
329
+ {{file:docs-snippets/src/propagate.ts}}`,
423
330
  },
424
331
  {
425
332
  slug: "intrinsics",
@@ -435,18 +342,7 @@ Here is a complete example using all intrinsic functions:
435
342
 
436
343
  Tagged template literal that produces \`Fn::Sub\`. The most common intrinsic — use it for dynamic naming with pseudo-parameters and attribute references:
437
344
 
438
- \`\`\`typescript
439
- // Simple pseudo-parameter substitution
440
- const bucketName = Sub\`\${AWS.StackName}-data\`;
441
- // → { "Fn::Sub": "\${AWS::StackName}-data" }
442
-
443
- // Multiple pseudo-parameters
444
- const arn = Sub\`arn:aws:s3:::\${AWS.AccountId}:\${AWS.Region}:*\`;
445
-
446
- // With resource attribute references
447
- const url = Sub\`https://\${bucket.domainName}/path\`;
448
- // → { "Fn::Sub": "https://\${DataBucket.DomainName}/path" }
449
- \`\`\`
345
+ {{file:docs-snippets/src/intrinsics-detail.ts:3-5}}
450
346
 
451
347
  \`Sub\` is a tagged template — use it with backticks, not as a function call.
452
348
 
@@ -454,89 +350,45 @@ const url = Sub\`https://\${bucket.domainName}/path\`;
454
350
 
455
351
  References a resource's physical ID or a parameter's value:
456
352
 
457
- \`\`\`typescript
458
- // Reference a parameter
459
- const envRef = Ref("Environment");
460
- // → { "Ref": "Environment" }
461
-
462
- // Reference a resource (returns its physical ID)
463
- const bucketRef = Ref("DataBucket");
464
- // → { "Ref": "DataBucket" }
465
- \`\`\`
353
+ {{file:docs-snippets/src/intrinsics-detail.ts:7-9}}
466
354
 
467
- In most cases you don't need \`Ref\` directly — the serializer automatically generates \`Ref\` when you reference a resource via the barrel (e.g. \`_.$.dataBucket\`).
355
+ In most cases you don't need \`Ref\` directly — the serializer automatically generates \`Ref\` when you reference an imported resource (e.g. \`dataBucket\` imported from another file).
468
356
 
469
357
  ## \`GetAtt\` — resource attributes
470
358
 
471
- Retrieves an attribute from a resource:
472
-
473
- \`\`\`typescript
474
- // Explicit GetAtt
475
- const bucketArn = GetAtt("DataBucket", "Arn");
476
- // → { "Fn::GetAtt": ["DataBucket", "Arn"] }
477
- \`\`\`
478
-
479
- **Preferred:** Use AttrRef directly via the resource's typed properties. When you write \`$.dataBucket.arn\`, the serializer automatically emits \`Fn::GetAtt\`. Explicit \`GetAtt\` is only needed for dynamic or imported resource names.
359
+ **Preferred:** Use AttrRef directly via the resource's typed properties. When you write \`dataBucket.arn\` (imported from the file that defines it), the serializer automatically emits \`Fn::GetAtt\`. Explicit \`GetAtt\` is only needed for dynamic or imported resource names.
480
360
 
481
361
  ## \`If\` — conditional values
482
362
 
483
363
  Returns one of two values based on a condition:
484
364
 
485
- \`\`\`typescript
486
- const value = If("IsProduction", "prod-value", "dev-value");
487
- // → { "Fn::If": ["IsProduction", "prod-value", "dev-value"] }
488
- \`\`\`
489
-
490
- Use with \`AWS.NoValue\` to conditionally omit a property:
365
+ {{file:docs-snippets/src/intrinsics-detail.ts:11-12}}
491
366
 
492
- \`\`\`typescript
493
- export const bucket = new Bucket({
494
- bucketName: "my-bucket",
495
- accelerateConfiguration: If("EnableAcceleration",
496
- { accelerationStatus: "Enabled" },
497
- AWS.NoValue,
498
- ),
499
- });
500
- \`\`\`
367
+ Use with \`AWS.NoValue\` to conditionally omit a property — see [Conditions](#conditions) on the CloudFormation Concepts page.
501
368
 
502
369
  ## \`Join\` — join values
503
370
 
504
371
  Joins values with a delimiter:
505
372
 
506
- \`\`\`typescript
507
- const joined = Join("-", ["prefix", AWS.StackName, "suffix"]);
508
- // → { "Fn::Join": ["-", ["prefix", { "Ref": "AWS::StackName" }, "suffix"]] }
509
- \`\`\`
373
+ {{file:docs-snippets/src/intrinsics-detail.ts:14-15}}
510
374
 
511
375
  ## \`Select\` — select by index
512
376
 
513
377
  Selects a value from a list by index:
514
378
 
515
- \`\`\`typescript
516
- const first = Select(0, Split(",", "a,b,c"));
517
- // → { "Fn::Select": [0, { "Fn::Split": [",", "a,b,c"] }] }
518
- \`\`\`
379
+ {{file:docs-snippets/src/intrinsics-detail.ts:17-18}}
519
380
 
520
381
  ## \`Split\` — split string
521
382
 
522
383
  Splits a string by a delimiter:
523
384
 
524
- \`\`\`typescript
525
- const parts = Split(",", "a,b,c");
526
- // → { "Fn::Split": [",", "a,b,c"] }
527
- \`\`\`
385
+ {{file:docs-snippets/src/intrinsics-detail.ts:20-21}}
528
386
 
529
387
  ## \`Base64\` — encode to Base64
530
388
 
531
389
  Encodes a string to Base64, commonly used for EC2 user data:
532
390
 
533
- \`\`\`typescript
534
- const userData = Base64(Sub\`#!/bin/bash
535
- echo "Stack: \${AWS.StackName}"
536
- yum update -y
537
- \`);
538
- // → { "Fn::Base64": { "Fn::Sub": "..." } }
539
- \`\`\``,
391
+ {{file:docs-snippets/src/intrinsics-detail.ts:23-27}}`,
540
392
  },
541
393
  {
542
394
  slug: "composites",
@@ -544,46 +396,11 @@ yum update -y
544
396
  description: "Composite resources, withDefaults presets, and propagate in the AWS CloudFormation lexicon",
545
397
  content: `Composites group related resources into reusable factories. See also the core [Composite Resources](/guide/composite-resources/) guide.
546
398
 
547
- \`\`\`typescript
548
- import * as _ from "./_";
549
-
550
- export const LambdaApi = _.Composite<LambdaApiProps>((props) => {
551
- const role = new _.Role({
552
- assumeRolePolicyDocument: _.$.lambdaTrustPolicy,
553
- managedPolicyArns: [_.$.lambdaBasicExecutionArn],
554
- policies: props.policies,
555
- });
556
-
557
- const func = new _.Function({
558
- functionName: props.name,
559
- runtime: props.runtime,
560
- handler: props.handler,
561
- code: props.code,
562
- role: role.arn,
563
- timeout: props.timeout,
564
- memorySize: props.memorySize,
565
- });
566
-
567
- const permission = new _.Permission({
568
- functionName: func.arn,
569
- action: "lambda:InvokeFunction",
570
- principal: "apigateway.amazonaws.com",
571
- });
572
-
573
- return { role, func, permission };
574
- }, "LambdaApi");
575
- \`\`\`
399
+ {{file:advanced/src/lambda-api.ts}}
576
400
 
577
401
  Instantiate and export:
578
402
 
579
- \`\`\`typescript
580
- export const healthApi = LambdaApi({
581
- name: Sub\`\${AWS.StackName}-health\`,
582
- runtime: "nodejs20.x",
583
- handler: "index.handler",
584
- code: { zipFile: \`exports.handler = async () => ({ statusCode: 200 });\` },
585
- });
586
- \`\`\`
403
+ {{file:advanced/src/health-api.ts}}
587
404
 
588
405
  During build, composites expand to flat CloudFormation resources: \`healthApi_role\` → \`HealthApiRole\`, \`healthApi_func\` → \`HealthApiFunc\`, \`healthApi_permission\` → \`HealthApiPermission\`.
589
406
 
@@ -591,25 +408,7 @@ During build, composites expand to flat CloudFormation resources: \`healthApi_ro
591
408
 
592
409
  Wrap a composite with pre-applied defaults. Defaulted props become optional:
593
410
 
594
- \`\`\`typescript
595
- import { withDefaults } from "@intentius/chant";
596
-
597
- const SecureApi = withDefaults(LambdaApi, {
598
- runtime: "nodejs20.x",
599
- handler: "index.handler",
600
- timeout: 10,
601
- memorySize: 256,
602
- });
603
-
604
- // Only name and code are required now
605
- export const healthApi = SecureApi({
606
- name: Sub\`\${AWS.StackName}-health\`,
607
- code: { zipFile: \`exports.handler = async () => ({ statusCode: 200 });\` },
608
- });
609
-
610
- // Composable — stack defaults on top of defaults
611
- const HighMemoryApi = withDefaults(SecureApi, { memorySize: 2048, timeout: 25 });
612
- \`\`\`
411
+ {{file:docs-snippets/src/with-defaults.ts}}
613
412
 
614
413
  \`withDefaults\` preserves the original composite's identity — same \`_id\` and \`compositeName\`, no new registry entry.
615
414
 
@@ -617,15 +416,7 @@ const HighMemoryApi = withDefaults(SecureApi, { memorySize: 2048, timeout: 25 })
617
416
 
618
417
  Attach properties that merge into every member during expansion:
619
418
 
620
- \`\`\`typescript
621
- import { propagate } from "@intentius/chant";
622
-
623
- export const api = propagate(
624
- LambdaApi({ name: "myApi", code: lambdaCode }),
625
- { tags: [{ key: "env", value: "prod" }] },
626
- );
627
- // role, func, and permission all receive the env tag
628
- \`\`\`
419
+ {{file:docs-snippets/src/propagate.ts}}
629
420
 
630
421
  Merge semantics:
631
422
  - **Scalars** — member-specific value wins over shared
@@ -634,17 +425,9 @@ Merge semantics:
634
425
 
635
426
  ## Nested stacks
636
427
 
637
- When resources should produce a separate CloudFormation template instead of expanding into the parent, use a **child project** — a subdirectory with its own barrel file (\`_.ts\`) that builds independently. The parent references it with \`nestedStack()\`:
428
+ When resources should produce a separate CloudFormation template instead of expanding into the parent, use a **child project** — a subdirectory that builds independently to its own CloudFormation template. The parent references it with \`nestedStack()\`:
638
429
 
639
- \`\`\`typescript
640
- // src/app.ts — parent references child project directory
641
- const network = _.nestedStack("network", import.meta.dir + "/network");
642
-
643
- // Cross-stack reference via outputs proxy
644
- export const handler = new _.Function({
645
- vpcConfig: { subnetIds: [network.outputs.subnetId] },
646
- });
647
- \`\`\`
430
+ {{file:nested-stacks/src/app.ts}}
648
431
 
649
432
  See [Nested Stacks](./nested-stacks) for the full guide.`,
650
433
  },
@@ -652,18 +435,16 @@ See [Nested Stacks](./nested-stacks) for the full guide.`,
652
435
  slug: "nested-stacks",
653
436
  title: "Nested Stacks",
654
437
  description: "Splitting resources into child CloudFormation templates with automatic cross-stack reference wiring",
655
- content: `CloudFormation nested stacks (\`AWS::CloudFormation::Stack\`) let you decompose large templates into smaller, reusable child templates. The AWS lexicon's \`nestedStack()\` function references a **child project directory** — a subdirectory with its own barrel file that builds independently to a valid CloudFormation template.
438
+ content: `CloudFormation nested stacks (\`AWS::CloudFormation::Stack\`) let you decompose large templates into smaller, reusable child templates. The AWS lexicon's \`nestedStack()\` function references a **child project directory** — a subdirectory that builds independently to a valid CloudFormation template.
656
439
 
657
440
  ## Project structure
658
441
 
659
- A nested stack is a child project — a subdirectory with its own \`_.ts\` barrel, resource files, and explicit \`stackOutput()\` declarations:
442
+ A nested stack is a child project — a subdirectory with its own resource files and explicit \`stackOutput()\` declarations:
660
443
 
661
444
  \`\`\`
662
445
  src/
663
- _.ts # parent barrel
664
446
  app.ts # parent resources
665
447
  network/ # ← child project (nested stack)
666
- _.ts # its own barrel
667
448
  vpc.ts # VPC, subnet, internet gateway, routing
668
449
  security.ts # security group for Lambda
669
450
  outputs.ts # declares cross-stack outputs
@@ -673,15 +454,7 @@ src/
673
454
 
674
455
  Use \`stackOutput()\` to mark values that the parent can reference. Each \`stackOutput()\` becomes an entry in the child template's \`Outputs\` section:
675
456
 
676
- \`\`\`typescript
677
- // src/network/outputs.ts
678
- import * as _ from "./_";
679
- import { stackOutput } from "@intentius/chant";
680
-
681
- export const vpcId = stackOutput(_.$.vpc.vpcId, { description: "VPC ID" });
682
- export const subnetId = stackOutput(_.$.subnet.subnetId, { description: "Public subnet ID" });
683
- export const lambdaSgId = stackOutput(_.$.lambdaSg.groupId, { description: "Lambda security group ID" });
684
- \`\`\`
457
+ {{file:nested-stacks/src/network/outputs.ts}}
685
458
 
686
459
  The child can be built independently:
687
460
 
@@ -694,24 +467,7 @@ chant build src/network/ -o network.json
694
467
 
695
468
  Use \`nestedStack()\` in the parent to reference a child project directory. It returns an object with an \`outputs\` proxy for cross-stack references:
696
469
 
697
- \`\`\`typescript
698
- // src/app.ts
699
- import * as _ from "./_";
700
-
701
- const network = _.nestedStack("network", import.meta.dir + "/network");
702
-
703
- export const handler = new _.Function({
704
- functionName: _.Sub\`\${_.AWS.StackName}-handler\`,
705
- runtime: "nodejs20.x",
706
- handler: "index.handler",
707
- role: _.Ref("LambdaExecutionRole"),
708
- code: { zipFile: "exports.handler = async () => ({ statusCode: 200 });" },
709
- vpcConfig: {
710
- subnetIds: [network.outputs.subnetId],
711
- securityGroupIds: [network.outputs.lambdaSgId],
712
- },
713
- });
714
- \`\`\`
470
+ {{file:nested-stacks/src/app.ts}}
715
471
 
716
472
  \`network.outputs.subnetId\` produces a \`NestedStackOutputRef\` that serializes to \`{ "Fn::GetAtt": ["Network", "Outputs.SubnetId"] }\`.
717
473
 
@@ -759,7 +515,9 @@ Child templates also receive the \`TemplateBasePath\` parameter so it propagates
759
515
  Pass CloudFormation Parameters to child stacks with the \`parameters\` option:
760
516
 
761
517
  \`\`\`typescript
762
- const network = _.nestedStack("network", import.meta.dir + "/network", {
518
+ import { nestedStack } from "@intentius/chant-lexicon-aws";
519
+
520
+ const network = nestedStack("network", import.meta.dirname + "/network", {
763
521
  parameters: { Environment: "prod", CidrBlock: "10.0.0.0/16" },
764
522
  });
765
523
  \`\`\`
@@ -770,16 +528,12 @@ Child projects can themselves reference grandchild projects. Each level produces
770
528
 
771
529
  \`\`\`
772
530
  src/
773
- _.ts
774
531
  app.ts
775
532
  infra/
776
- _.ts
777
533
  network/
778
- _.ts
779
534
  vpc.ts
780
535
  outputs.ts
781
536
  database/
782
- _.ts
783
537
  cluster.ts
784
538
  outputs.ts
785
539
  \`\`\`
@@ -941,67 +695,17 @@ See also [Custom Lint Rules](./custom-rules) for writing project-specific rules.
941
695
 
942
696
  ## Anatomy of a lint rule
943
697
 
944
- \`\`\`typescript
945
- import type { LintRule, LintDiagnostic, LintContext } from "@intentius/chant/lint/rule";
946
- import * as ts from "typescript";
947
-
948
- export const apiTimeoutRule: LintRule = {
949
- id: "WAW012", // unique ID (WAW = AWS-specific prefix)
950
- severity: "error", // "error" | "warning"
951
- category: "correctness", // "correctness" | "style" | "security"
952
-
953
- check(context: LintContext): LintDiagnostic[] {
954
- const { sourceFile } = context;
955
- const diagnostics: LintDiagnostic[] = [];
956
-
957
- function visit(node: ts.Node): void {
958
- // Walk the AST looking for violations
959
- if (ts.isCallExpression(node)) {
960
- // Inspect arguments, report diagnostics
961
- }
962
- ts.forEachChild(node, visit);
963
- }
964
-
965
- visit(sourceFile);
966
- return diagnostics;
967
- },
968
- };
969
- \`\`\`
698
+ The advanced example includes a full custom rule implementation:
970
699
 
971
- ## Example: API Gateway timeout (WAW012)
700
+ {{file:advanced/src/lint/api-timeout.ts}}
972
701
 
973
- The advanced example includes a rule that flags Lambda API composites with \`timeout > 29\` API Gateway's synchronous limit:
974
-
975
- \`\`\`typescript
976
- const API_FACTORIES = new Set(["LambdaApi", "SecureApi", "HighMemoryApi"]);
977
-
978
- export const apiTimeoutRule: LintRule = {
979
- id: "WAW012",
980
- severity: "error",
981
- category: "correctness",
982
-
983
- check(context: LintContext): LintDiagnostic[] {
984
- // Walks AST for calls to API factory functions,
985
- // inspects the timeout property, reports if > 29
986
- },
987
- };
988
- \`\`\`
702
+ The \`check\` function receives a \`LintContext\` containing the TypeScript \`sourceFile\` and returns an array of diagnostics with file, line, column, and message.
989
703
 
990
704
  ## Registering custom rules
991
705
 
992
706
  Add a \`chant.config.ts\` to your project:
993
707
 
994
- \`\`\`typescript
995
- export default {
996
- lint: {
997
- extends: ["@intentius/chant/lint/presets/strict"],
998
- rules: {
999
- COR004: "off", // disable a built-in rule
1000
- },
1001
- plugins: ["./lint/api-timeout.ts"], // load custom rules
1002
- },
1003
- };
1004
- \`\`\`
708
+ {{file:advanced/src/chant.config.ts}}
1005
709
 
1006
710
  The \`plugins\` array accepts relative paths. Each plugin module should export a \`LintRule\` object.`,
1007
711
  },
@@ -1025,9 +729,8 @@ bun test # runs the example's tests
1025
729
 
1026
730
  \`\`\`
1027
731
  src/
1028
- ├── _.ts # Barrel — re-exports lexicon + auto-discovers siblings
1029
732
  ├── defaults.ts # Shared config: encryption, versioning, public access block
1030
- ├── data-bucket.ts # S3 bucket using barrel defaults
733
+ ├── data-bucket.ts # S3 bucket using shared defaults
1031
734
  ├── logs-bucket.ts # S3 bucket for access logs
1032
735
  ├── role.ts # IAM role with Lambda assume-role policy
1033
736
  └── handler.ts # Lambda function referencing role and bucket
@@ -1035,9 +738,9 @@ src/
1035
738
 
1036
739
  **Patterns demonstrated:**
1037
740
 
1038
- 1. **Barrel file** — \`_.ts\` re-exports the AWS lexicon and creates the \`$\` proxy for cross-file references
1039
- 2. **Shared defaults** — \`defaults.ts\` exports reusable property objects (\`encryptionDefault\`, \`publicAccessBlock\`) that other files reference via \`_.$\`
1040
- 3. **Cross-resource references** — \`_.$.dataBucket.arn\` in \`handler.ts\` serializes to \`Fn::GetAtt\` in the template
741
+ 1. **Direct imports** — lexicon types come from \`@intentius/chant-lexicon-aws\`, sibling exports are imported from the file that defines them
742
+ 2. **Shared defaults** — \`defaults.ts\` exports reusable property objects (\`BucketEncryption\`, \`PublicAccessBlockConfiguration\`) that other files import directly
743
+ 3. **Cross-resource references** — \`dataBucket.Arn\` in \`handler.ts\` serializes to \`Fn::GetAtt\` in the template
1041
744
  4. **Intrinsics** — \`Sub\` tagged templates with pseudo-parameters for dynamic naming
1042
745
 
1043
746
  {{file:getting-started/src/handler.ts}}
@@ -1048,7 +751,6 @@ src/
1048
751
 
1049
752
  \`\`\`
1050
753
  src/
1051
- ├── _.ts # Barrel + re-exports Composite from core
1052
754
  ├── chant.config.ts # Lint config: strict preset + custom plugin
1053
755
  ├── defaults.ts # Encryption, versioning, access block, Lambda trust policy
1054
756
  ├── data-bucket.ts # S3 bucket
@@ -1076,10 +778,8 @@ The example produces 10 CloudFormation resources: 1 S3 bucket + 3 composites ×
1076
778
 
1077
779
  \`\`\`
1078
780
  src/
1079
- ├── _.ts # Parent barrel
1080
781
  ├── app.ts # Lambda function (references network outputs)
1081
782
  └── network/ # Child project (nested stack)
1082
- ├── _.ts # Child barrel
1083
783
  ├── vpc.ts # VPC, subnet, internet gateway, route table
1084
784
  ├── security.ts # Security group for Lambda
1085
785
  └── outputs.ts # stackOutput() declarations
@@ -1087,7 +787,7 @@ src/
1087
787
 
1088
788
  **Patterns demonstrated:**
1089
789
 
1090
- 1. **Child project** — \`network/\` is a separate project directory with its own barrel, resources, and \`stackOutput()\` exports
790
+ 1. **Child project** — \`network/\` is a separate project directory with its own resources and \`stackOutput()\` exports
1091
791
  2. **Cross-stack references** — \`app.ts\` accesses \`network.outputs.subnetId\` and \`network.outputs.lambdaSgId\`, which serialize to \`Fn::GetAtt\` on the parent's \`AWS::CloudFormation::Stack\` resource
1092
792
  3. **Multi-file output** — build produces \`template.json\` (parent) and \`network.template.json\` (child)
1093
793
  4. **TemplateBasePath** — auto-generated parameter for configuring child template URLs per environment
@@ -1098,6 +798,61 @@ src/
1098
798
 
1099
799
  See [Nested Stacks](./nested-stacks) for the full guide.`,
1100
800
  },
801
+ {
802
+ slug: "skills",
803
+ title: "AI Skills",
804
+ description: "AI agent skills bundled with the AWS CloudFormation lexicon",
805
+ content: `The AWS lexicon ships an AI skill called **chant-aws** that teaches AI coding agents (like Claude Code) how to build, validate, and deploy CloudFormation templates from a chant project.
806
+
807
+ ## What are skills?
808
+
809
+ 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.
810
+
811
+ ## Installation
812
+
813
+ When you scaffold a new project with \`chant init --lexicon aws\`, the skill is installed to \`.claude/skills/chant-aws/SKILL.md\` for automatic discovery by Claude Code.
814
+
815
+ For existing projects, create the file manually:
816
+
817
+ \`\`\`
818
+ .claude/
819
+ skills/
820
+ chant-aws/
821
+ SKILL.md # skill content (see below)
822
+ \`\`\`
823
+
824
+ ## Skill: chant-aws
825
+
826
+ The \`chant-aws\` skill covers the full deployment lifecycle:
827
+
828
+ - **Build** — \`chant build src/ --output stack.json\`
829
+ - **Validate** — \`chant lint src/\` + \`aws cloudformation validate-template\`
830
+ - **Deploy** — \`aws cloudformation deploy\` with capabilities
831
+ - **Update** — change sets for preview, or direct deploy
832
+ - **Delete** — \`aws cloudformation delete-stack\`
833
+ - **Status** — \`describe-stacks\` and \`describe-stack-events\`
834
+ - **Troubleshooting** — event inspection, rollback recovery, drift detection
835
+
836
+ The skill is invocable as a slash command: \`/chant-aws\`
837
+
838
+ ## MCP integration
839
+
840
+ The lexicon also provides MCP (Model Context Protocol) tools and resources that AI agents can use programmatically:
841
+
842
+ | MCP tool | Description |
843
+ |----------|-------------|
844
+ | \`build\` | Build the chant project |
845
+ | \`lint\` | Run lint rules |
846
+ | \`explain\` | Summarize project resources |
847
+ | \`scaffold\` | Generate starter files |
848
+ | \`search\` | Search available resource types |
849
+ | \`aws:diff\` | Compare current build output against previous |
850
+
851
+ | MCP resource | Description |
852
+ |--------------|-------------|
853
+ | \`resource-catalog\` | JSON list of all supported CloudFormation resource types |
854
+ | \`examples/basic-stack\` | Example stack with S3 bucket and IAM role |`,
855
+ },
1101
856
  ],
1102
857
  };
1103
858