@intentius/chant-lexicon-aws 0.0.16 → 0.0.22

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.
@@ -73,6 +73,10 @@ function matchesCondition(condition: unknown, properties: Record<string, unknown
73
73
  if ("enum" in s && Array.isArray(s.enum)) {
74
74
  if (!s.enum.includes(properties[propName])) return false;
75
75
  }
76
+ if ("pattern" in s && typeof s.pattern === "string") {
77
+ const val = properties[propName];
78
+ if (typeof val !== "string" || !new RegExp(s.pattern).test(val)) return false;
79
+ }
76
80
  }
77
81
  }
78
82
 
package/src/plugin.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { createRequire } from "module";
2
- import type { LexiconPlugin, IntrinsicDef, SkillDefinition } from "@intentius/chant/lexicon";
2
+ import type { LexiconPlugin, IntrinsicDef, SkillDefinition, ResourceMetadata } from "@intentius/chant/lexicon";
3
3
  const require = createRequire(import.meta.url);
4
4
  import type { LintRule } from "@intentius/chant/lint/rule";
5
5
  import type { PostSynthCheck } from "@intentius/chant/lint/post-synth";
@@ -392,7 +392,7 @@ user-invocable: true
392
392
 
393
393
  ## How chant and CloudFormation relate
394
394
 
395
- chant is a **synthesis-only** tool — it compiles TypeScript source files into CloudFormation JSON (or YAML). chant does NOT call AWS APIs. Your job as an agent is to bridge the two:
395
+ chant is a **synthesis compiler** — it compiles TypeScript source files into CloudFormation JSON (or YAML). \`chant build\` does not call AWS APIs; synthesis is pure and deterministic. The optional \`chant state snapshot\` command queries AWS APIs to capture deployment metadata (physical IDs, status, outputs) for observability. Your job as an agent is to bridge synthesis and deployment:
396
396
 
397
397
  - Use **chant** for: build, lint, diff (local template comparison)
398
398
  - Use **AWS CLI** for: validate-template, deploy, change sets, rollback, drift detection, and all stack operations
@@ -955,6 +955,91 @@ aws cloudformation wait stack-update-complete --stack-name my-app-prod`,
955
955
  return awsHover(ctx);
956
956
  },
957
957
 
958
+ async describeResources(options: {
959
+ environment: string;
960
+ buildOutput: string;
961
+ entityNames: string[];
962
+ }): Promise<Record<string, ResourceMetadata>> {
963
+ const { getRuntime } = await import("@intentius/chant/runtime-adapter");
964
+ const rt = getRuntime();
965
+ const resources: Record<string, ResourceMetadata> = {};
966
+
967
+ // Derive stack name: environment-based convention
968
+ // Try to parse the build output to detect stack name from Metadata or use convention
969
+ const stackName = `${options.environment}`;
970
+
971
+ // Describe stack resources
972
+ const listResult = await rt.spawn([
973
+ "aws", "cloudformation", "describe-stack-resources",
974
+ "--stack-name", stackName,
975
+ "--output", "json",
976
+ ]);
977
+
978
+ if (listResult.exitCode !== 0) {
979
+ throw new Error(`Failed to describe stack "${stackName}": ${listResult.stderr}`);
980
+ }
981
+
982
+ const data = JSON.parse(listResult.stdout) as {
983
+ StackResources: Array<{
984
+ LogicalResourceId: string;
985
+ ResourceType: string;
986
+ PhysicalResourceId: string;
987
+ ResourceStatus: string;
988
+ Timestamp: string;
989
+ }>;
990
+ };
991
+
992
+ // Map logical names from build to stack resources
993
+ const stackResourceMap = new Map<string, typeof data.StackResources[0]>();
994
+ for (const r of data.StackResources) {
995
+ stackResourceMap.set(r.LogicalResourceId, r);
996
+ }
997
+
998
+ // Get stack outputs
999
+ const describeResult = await rt.spawn([
1000
+ "aws", "cloudformation", "describe-stacks",
1001
+ "--stack-name", stackName,
1002
+ "--output", "json",
1003
+ ]);
1004
+
1005
+ let stackOutputs: Record<string, string> = {};
1006
+ if (describeResult.exitCode === 0) {
1007
+ const stacks = JSON.parse(describeResult.stdout) as {
1008
+ Stacks: Array<{ Outputs?: Array<{ OutputKey: string; OutputValue: string }> }>;
1009
+ };
1010
+ if (stacks.Stacks[0]?.Outputs) {
1011
+ for (const o of stacks.Stacks[0].Outputs) {
1012
+ stackOutputs[o.OutputKey] = o.OutputValue;
1013
+ }
1014
+ }
1015
+ }
1016
+
1017
+ for (const entityName of options.entityNames) {
1018
+ const stackResource = stackResourceMap.get(entityName);
1019
+ if (!stackResource) continue;
1020
+
1021
+ const attributes: Record<string, unknown> = {};
1022
+ // Include stack outputs as attributes (scrub sensitive ones)
1023
+ for (const [key, value] of Object.entries(stackOutputs)) {
1024
+ if (/password|secret|token|key/i.test(key)) {
1025
+ attributes[key] = "[REDACTED]";
1026
+ } else {
1027
+ attributes[key] = value;
1028
+ }
1029
+ }
1030
+
1031
+ resources[entityName] = {
1032
+ type: stackResource.ResourceType,
1033
+ physicalId: stackResource.PhysicalResourceId,
1034
+ status: stackResource.ResourceStatus,
1035
+ lastUpdated: stackResource.Timestamp,
1036
+ attributes: Object.keys(attributes).length > 0 ? attributes : undefined,
1037
+ };
1038
+ }
1039
+
1040
+ return resources;
1041
+ },
1042
+
958
1043
  mcpTools(): McpToolContribution[] {
959
1044
  return [
960
1045
  {
@@ -33,6 +33,9 @@ AWS Lexicon (CloudFormation) K8s Lexicon (kubectl apply)
33
33
  # Build CloudFormation template
34
34
  chant build src/infra/ --output infra.json
35
35
 
36
+ # See what changed since last deploy (compares current build against last snapshot's digest)
37
+ chant state diff staging aws
38
+
36
39
  # Deploy
37
40
  aws cloudformation deploy \
38
41
  --template-file infra.json \