@intentius/chant 0.0.18 → 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.
Files changed (60) hide show
  1. package/bin/chant +4 -1
  2. package/package.json +20 -1
  3. package/src/build.test.ts +4 -2
  4. package/src/build.ts +3 -0
  5. package/src/builder.test.ts +3 -0
  6. package/src/cli/commands/__fixtures__/init-lexicon-output/docs/astro.config.mjs +0 -3
  7. package/src/cli/commands/build.ts +5 -12
  8. package/src/cli/commands/diff.test.ts +2 -1
  9. package/src/cli/commands/diff.ts +2 -1
  10. package/src/cli/commands/init-lexicon.test.ts +0 -9
  11. package/src/cli/commands/init-lexicon.ts +0 -94
  12. package/src/cli/commands/init.ts +2 -20
  13. package/src/cli/handlers/build.ts +3 -3
  14. package/src/cli/handlers/lint.ts +2 -2
  15. package/src/cli/handlers/spell.ts +396 -0
  16. package/src/cli/handlers/state.ts +230 -0
  17. package/src/cli/lsp/server.test.ts +4 -0
  18. package/src/cli/main.ts +37 -3
  19. package/src/cli/mcp/server.test.ts +13 -9
  20. package/src/cli/mcp/server.ts +220 -6
  21. package/src/cli/mcp/tools/build.ts +2 -1
  22. package/src/cli/plugins.ts +1 -1
  23. package/src/cli/reporters/stylish.test.ts +2 -2
  24. package/src/cli/reporters/stylish.ts +1 -1
  25. package/src/composite.test.ts +1 -1
  26. package/src/config.ts +4 -0
  27. package/src/declarable.test.ts +2 -1
  28. package/src/declarable.ts +1 -1
  29. package/src/discovery/graph.test.ts +40 -0
  30. package/src/discovery/import.test.ts +5 -5
  31. package/src/discovery/resolve.test.ts +20 -0
  32. package/src/discovery/resolve.ts +2 -2
  33. package/src/index.ts +2 -0
  34. package/src/lexicon.ts +24 -0
  35. package/src/lint/rule-options.test.ts +3 -3
  36. package/src/lint/rule-registry.test.ts +1 -1
  37. package/src/lint/rules/composite-scope.ts +1 -1
  38. package/src/serializer-walker.ts +2 -1
  39. package/src/spell/discovery.ts +183 -0
  40. package/src/spell/index.ts +3 -0
  41. package/src/spell/prompt.ts +133 -0
  42. package/src/spell/types.ts +89 -0
  43. package/src/state/digest.ts +88 -0
  44. package/src/state/git.ts +317 -0
  45. package/src/state/index.ts +4 -0
  46. package/src/state/snapshot.ts +179 -0
  47. package/src/state/types.ts +59 -0
  48. package/src/types.ts +2 -1
  49. package/src/utils.test.ts +16 -3
  50. package/src/utils.ts +31 -1
  51. package/src/validation.test.ts +11 -0
  52. package/src/cli/commands/__fixtures__/init-lexicon-output/docs/src/content/docs/getting-started.mdx +0 -6
  53. package/src/cli/commands/__fixtures__/init-lexicon-output/docs/src/content/docs/lint-rules.mdx +0 -6
  54. package/src/cli/commands/__fixtures__/init-lexicon-output/docs/src/content/docs/serialization.mdx +0 -6
  55. package/src/cli/commands/__fixtures__/init-lexicon-output/src/actions/.gitkeep +0 -0
  56. package/src/cli/commands/__fixtures__/init-lexicon-output/src/composites/.gitkeep +0 -0
  57. package/src/cli/commands/__fixtures__/init-lexicon-output/src/coverage.ts +0 -11
  58. package/src/cli/commands/__fixtures__/init-lexicon-output/src/import/generator.ts +0 -10
  59. package/src/cli/commands/__fixtures__/init-lexicon-output/src/import/parser.ts +0 -10
  60. package/src/cli/commands/__fixtures__/init-lexicon-output/src/lint/post-synth/.gitkeep +0 -0
package/bin/chant CHANGED
@@ -15,6 +15,9 @@ MAIN_TS="$SCRIPT_DIR/../src/cli/main.ts"
15
15
 
16
16
  if command -v bun >/dev/null 2>&1; then
17
17
  exec bun "$MAIN_TS" "$@"
18
- else
18
+ elif command -v npx >/dev/null 2>&1; then
19
19
  exec npx tsx "$MAIN_TS" "$@"
20
+ else
21
+ echo "error: chant requires Bun (https://bun.sh) or Node.js (https://nodejs.org)" >&2
22
+ exit 1
20
23
  fi
package/package.json CHANGED
@@ -1,7 +1,26 @@
1
1
  {
2
2
  "name": "@intentius/chant",
3
- "version": "0.0.18",
3
+ "version": "0.0.22",
4
+ "description": "Declarative infrastructure-as-code toolkit — TypeScript on Bun",
4
5
  "license": "Apache-2.0",
6
+ "homepage": "https://intentius.io/chant",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/intentius/chant.git",
10
+ "directory": "packages/core"
11
+ },
12
+ "bugs": {
13
+ "url": "https://github.com/intentius/chant/issues"
14
+ },
15
+ "keywords": [
16
+ "infrastructure-as-code",
17
+ "iac",
18
+ "typescript",
19
+ "declarative",
20
+ "cloudformation",
21
+ "aws",
22
+ "devops"
23
+ ],
5
24
  "type": "module",
6
25
  "files": [
7
26
  "src/",
package/src/build.test.ts CHANGED
@@ -189,10 +189,12 @@ export const betaEntity = {
189
189
  expect(result.outputs.has("alpha")).toBe(true);
190
190
  expect(result.outputs.has("beta")).toBe(true);
191
191
 
192
- const alphaOutput = JSON.parse(result.outputs.get("alpha")!);
192
+ const alphaRaw = result.outputs.get("alpha")!;
193
+ const alphaOutput = JSON.parse(typeof alphaRaw === "string" ? alphaRaw : alphaRaw.primary);
193
194
  expect(alphaOutput.alpha).toContain("alphaEntity");
194
195
 
195
- const betaOutput = JSON.parse(result.outputs.get("beta")!);
196
+ const betaRaw = result.outputs.get("beta")!;
197
+ const betaOutput = JSON.parse(typeof betaRaw === "string" ? betaRaw : betaRaw.primary);
196
198
  expect(betaOutput.beta).toContain("betaEntity");
197
199
  });
198
200
 
package/src/build.ts CHANGED
@@ -29,6 +29,8 @@ export interface BuildResult {
29
29
  outputs: Map<string, string | SerializerResult>;
30
30
  /** Map of entity name to Declarable entity */
31
31
  entities: Map<string, Declarable>;
32
+ /** Resource-level dependency graph from discovery */
33
+ dependencies: Map<string, Set<string>>;
32
34
  /** Array of warnings encountered during the build */
33
35
  warnings: string[];
34
36
  /** Array of errors encountered during discovery and build */
@@ -430,6 +432,7 @@ export async function build(
430
432
  return {
431
433
  outputs,
432
434
  entities: discoveryResult.entities,
435
+ dependencies: discoveryResult.dependencies,
433
436
  warnings,
434
437
  errors,
435
438
  manifest,
@@ -36,6 +36,7 @@ class SimpleResourceBuilder extends Builder<TestResource> {
36
36
  throw new Error("Name and type are required");
37
37
  }
38
38
  return {
39
+ lexicon: "test",
39
40
  entityType: "TestResource",
40
41
  [DECLARABLE_MARKER]: true,
41
42
  name: this.name,
@@ -72,6 +73,7 @@ class ValidatedResourceBuilder extends Builder<TestResource> {
72
73
  throw new Error("Name and type are required");
73
74
  }
74
75
  return {
76
+ lexicon: "test",
75
77
  entityType: "TestResource",
76
78
  [DECLARABLE_MARKER]: true,
77
79
  name: this.name,
@@ -103,6 +105,7 @@ class DefaultsResourceBuilder extends Builder<TestResource> {
103
105
 
104
106
  build(): TestResource {
105
107
  return {
108
+ lexicon: "test",
106
109
  entityType: "TestResource",
107
110
  [DECLARABLE_MARKER]: true,
108
111
  name: this.name,
@@ -8,9 +8,6 @@ export default defineConfig({
8
8
  title: 'Fixture',
9
9
  sidebar: [
10
10
  { label: 'Overview', slug: '' },
11
- { label: 'Getting Started', slug: 'getting-started' },
12
- { label: 'Serialization', slug: 'serialization' },
13
- { label: 'Lint Rules', slug: 'lint-rules' },
14
11
  ],
15
12
  }),
16
13
  ],
@@ -3,6 +3,7 @@ import type { Serializer, SerializerResult } from "../../serializer";
3
3
  import type { LexiconPlugin } from "../../lexicon";
4
4
  import { runPostSynthChecks } from "../../lint/post-synth";
5
5
  import type { PostSynthCheck } from "../../lint/post-synth";
6
+ import { sortedJsonReplacer } from "../../utils";
6
7
  import { formatError, formatWarning, formatSuccess, formatBold, formatInfo } from "../format";
7
8
  import { writeFileSync } from "fs";
8
9
  import { resolve, dirname, join } from "path";
@@ -214,6 +215,10 @@ export async function buildCommand(options: BuildOptions): Promise<BuildResult>
214
215
  const resourceCount = result.entities.size;
215
216
  const fileCount = result.sourceFileCount;
216
217
 
218
+ if (fileCount === 0 && errors.length === 0) {
219
+ console.error(formatInfo("No source files found — create .ts files in the target directory"));
220
+ }
221
+
217
222
  if (options.verbose && errors.length === 0) {
218
223
  console.error(
219
224
  formatSuccess(
@@ -231,18 +236,6 @@ export async function buildCommand(options: BuildOptions): Promise<BuildResult>
231
236
  };
232
237
  }
233
238
 
234
- /**
235
- * JSON.stringify replacer that sorts object keys for deterministic output
236
- */
237
- function sortedJsonReplacer(_key: string, value: unknown): unknown {
238
- if (value && typeof value === "object" && !Array.isArray(value)) {
239
- return Object.fromEntries(
240
- Object.entries(value as Record<string, unknown>).sort(([a], [b]) => a.localeCompare(b))
241
- );
242
- }
243
- return value;
244
- }
245
-
246
239
  /**
247
240
  * Simple JSON to YAML converter
248
241
  */
@@ -86,7 +86,8 @@ export const myBucket = {
86
86
  const combined: Record<string, unknown> = {};
87
87
  const sortedSerializerNames = [...buildResult.outputs.keys()].sort();
88
88
  for (const serializerName of sortedSerializerNames) {
89
- combined[serializerName] = JSON.parse(buildResult.outputs.get(serializerName)!);
89
+ const raw = buildResult.outputs.get(serializerName)!;
90
+ combined[serializerName] = JSON.parse(typeof raw === "string" ? raw : raw.primary);
90
91
  }
91
92
 
92
93
  // Sort keys to match diffCommand behavior
@@ -46,7 +46,8 @@ export async function diffCommand(options: DiffOptions): Promise<DiffResult> {
46
46
  const combined: Record<string, unknown> = {};
47
47
  const sortedLexiconNames = [...result.outputs.keys()].sort();
48
48
  for (const lexiconName of sortedLexiconNames) {
49
- combined[lexiconName] = JSON.parse(result.outputs.get(lexiconName)!);
49
+ const raw = result.outputs.get(lexiconName)!;
50
+ combined[lexiconName] = JSON.parse(typeof raw === "string" ? raw : raw.primary);
50
51
  }
51
52
  const currentOutput = JSON.stringify(combined, sortedJsonReplacer, 2);
52
53
 
@@ -51,9 +51,6 @@ describe("initLexiconCommand", () => {
51
51
  "src/lsp/hover.ts",
52
52
  "src/lsp/completions.test.ts",
53
53
  "src/lsp/hover.test.ts",
54
- "src/import/parser.ts",
55
- "src/import/generator.ts",
56
- "src/coverage.ts",
57
54
  "src/validate.ts",
58
55
  "src/validate-cli.ts",
59
56
  "package.json",
@@ -66,15 +63,9 @@ describe("initLexiconCommand", () => {
66
63
  "docs/astro.config.mjs",
67
64
  "docs/src/content.config.ts",
68
65
  "docs/src/content/docs/index.mdx",
69
- "docs/src/content/docs/getting-started.mdx",
70
- "docs/src/content/docs/serialization.mdx",
71
- "docs/src/content/docs/lint-rules.mdx",
72
66
  "examples/getting-started/package.json",
73
67
  "examples/getting-started/src/infra.ts",
74
68
  "src/generated/.gitkeep",
75
- "src/composites/.gitkeep",
76
- "src/actions/.gitkeep",
77
- "src/lint/post-synth/.gitkeep",
78
69
  ];
79
70
 
80
71
  for (const file of expectedFiles) {
@@ -498,49 +498,6 @@ export function hover(ctx: HoverContext): HoverInfo | undefined {
498
498
  `;
499
499
  }
500
500
 
501
- function generateImportParserTs(name: string): string {
502
- return `import type { TemplateParser } from "@intentius/chant/import/parser";
503
-
504
- /**
505
- * Template parser for importing external ${name} templates.
506
- *
507
- * TODO: Implement the TemplateParser interface for your format.
508
- */
509
- // export class ${name.charAt(0).toUpperCase() + name.slice(1)}Parser implements TemplateParser {
510
- // parse(data: unknown): IR { ... }
511
- // }
512
- `;
513
- }
514
-
515
- function generateImportGeneratorTs(name: string): string {
516
- return `import type { TypeScriptGenerator } from "@intentius/chant/import/generator";
517
-
518
- /**
519
- * TypeScript generator for converting imported ${name} templates.
520
- *
521
- * TODO: Implement the TypeScriptGenerator interface for your format.
522
- */
523
- // export class ${name.charAt(0).toUpperCase() + name.slice(1)}Generator implements TypeScriptGenerator {
524
- // generate(ir: IR): string { ... }
525
- // }
526
- `;
527
- }
528
-
529
- function generateCoverageTs(name: string): string {
530
- return `/**
531
- * Coverage analysis for the ${name} lexicon.
532
- *
533
- * TODO: Implement coverage analysis that checks how much of the
534
- * upstream spec is covered by the generated types.
535
- */
536
- export async function analyzeCoverage(options?: { verbose?: boolean }): Promise<void> {
537
- console.error("Coverage analysis not yet implemented");
538
- // TODO: Read generated lexicon JSON, compare against upstream spec,
539
- // and report coverage metrics.
540
- }
541
- `;
542
- }
543
-
544
501
  function generateValidateTs(name: string): string {
545
502
  return `/**
546
503
  * Validate generated lexicon-${name} artifacts.
@@ -795,41 +752,6 @@ function generateExampleInfraTs(name: string, names: ReturnType<typeof deriveNam
795
752
  `;
796
753
  }
797
754
 
798
- // ── Additional doc page generators ───────────────────────────────────
799
-
800
- function generateDocsGettingStartedMdx(name: string): string {
801
- const displayName = name.charAt(0).toUpperCase() + name.slice(1);
802
- return `---
803
- title: Getting Started
804
- description: Get started with the ${displayName} lexicon
805
- ---
806
-
807
- TODO: Document how to set up a project using the ${displayName} lexicon.
808
- `;
809
- }
810
-
811
- function generateDocsSerializationMdx(name: string): string {
812
- const displayName = name.charAt(0).toUpperCase() + name.slice(1);
813
- return `---
814
- title: Serialization
815
- description: ${displayName} output format
816
- ---
817
-
818
- TODO: Document the ${displayName} serialization format and output structure.
819
- `;
820
- }
821
-
822
- function generateDocsLintRulesMdx(name: string): string {
823
- const displayName = name.charAt(0).toUpperCase() + name.slice(1);
824
- return `---
825
- title: Lint Rules
826
- description: ${displayName} lint rules reference
827
- ---
828
-
829
- TODO: Document the lint rules provided by the ${displayName} lexicon.
830
- `;
831
- }
832
-
833
755
  // ── Docs site skeleton generators ────────────────────────────────────
834
756
 
835
757
  function generateDocsPackageJson(name: string): string {
@@ -879,9 +801,6 @@ export default defineConfig({
879
801
  title: '${displayName}',
880
802
  sidebar: [
881
803
  { label: 'Overview', slug: '' },
882
- { label: 'Getting Started', slug: 'getting-started' },
883
- { label: 'Serialization', slug: 'serialization' },
884
- { label: 'Lint Rules', slug: 'lint-rules' },
885
804
  ],
886
805
  }),
887
806
  ],
@@ -953,10 +872,6 @@ export async function initLexiconCommand(options: InitLexiconOptions): Promise<I
953
872
  "src/lint",
954
873
  "src/lint/rules",
955
874
  "src/lsp",
956
- "src/import",
957
- "src/composites",
958
- "src/actions",
959
- "src/lint/post-synth",
960
875
  "src/generated",
961
876
  "docs",
962
877
  "docs/src",
@@ -991,9 +906,6 @@ export async function initLexiconCommand(options: InitLexiconOptions): Promise<I
991
906
  "src/lsp/hover.ts": generateLspHoverTs(name),
992
907
  "src/lsp/completions.test.ts": generateCompletionsTestTs(),
993
908
  "src/lsp/hover.test.ts": generateHoverTestTs(),
994
- "src/import/parser.ts": generateImportParserTs(name),
995
- "src/import/generator.ts": generateImportGeneratorTs(name),
996
- "src/coverage.ts": generateCoverageTs(name),
997
909
  "src/plugin.test.ts": generatePluginTestTs(name, names),
998
910
  "src/serializer.test.ts": generateSerializerTestTs(name, names),
999
911
  "src/validate.ts": generateValidateTs(name),
@@ -1008,9 +920,6 @@ export async function initLexiconCommand(options: InitLexiconOptions): Promise<I
1008
920
  "docs/astro.config.mjs": generateDocsAstroConfig(name),
1009
921
  "docs/src/content.config.ts": generateDocsContentConfig(),
1010
922
  "docs/src/content/docs/index.mdx": generateDocsIndexMdx(name),
1011
- "docs/src/content/docs/getting-started.mdx": generateDocsGettingStartedMdx(name),
1012
- "docs/src/content/docs/serialization.mdx": generateDocsSerializationMdx(name),
1013
- "docs/src/content/docs/lint-rules.mdx": generateDocsLintRulesMdx(name),
1014
923
  "examples/getting-started/package.json": generateExamplePackageJson(name),
1015
924
  "examples/getting-started/src/infra.ts": generateExampleInfraTs(name, names),
1016
925
  };
@@ -1018,9 +927,6 @@ export async function initLexiconCommand(options: InitLexiconOptions): Promise<I
1018
927
  // Write .gitkeep files
1019
928
  const gitkeeps = [
1020
929
  "src/generated/.gitkeep",
1021
- "src/composites/.gitkeep",
1022
- "src/actions/.gitkeep",
1023
- "src/lint/post-synth/.gitkeep",
1024
930
  ];
1025
931
 
1026
932
  for (const gk of gitkeeps) {
@@ -3,7 +3,6 @@ import { join, resolve, dirname } from "path";
3
3
  import { fileURLToPath } from "url";
4
4
  import { homedir } from "os";
5
5
  import { createInterface } from "readline";
6
- import { z } from "zod";
7
6
  import { formatSuccess, formatWarning } from "../format";
8
7
  import { loadPlugin } from "../plugins";
9
8
 
@@ -18,17 +17,6 @@ function getChantVersion(): string {
18
17
  }
19
18
  }
20
19
 
21
- /**
22
- * Schema for validating generated package.json — catches template bugs early.
23
- */
24
- const GeneratedPackageJsonSchema = z.object({
25
- name: z.string().min(1),
26
- version: z.string(),
27
- type: z.literal("module"),
28
- scripts: z.record(z.string(), z.string()),
29
- dependencies: z.record(z.string(), z.string()),
30
- });
31
-
32
20
  /**
33
21
  * Init command options
34
22
  */
@@ -128,12 +116,6 @@ function generatePackageJson(lexicon: string, extraScripts?: Record<string, stri
128
116
  },
129
117
  };
130
118
 
131
- // Validate generated output to catch template bugs
132
- const result = GeneratedPackageJsonSchema.safeParse(pkg);
133
- if (!result.success) {
134
- throw new Error(`Bug: generated package.json is invalid: ${result.error.issues[0].message}`);
135
- }
136
-
137
119
  return JSON.stringify(pkg, null, 2);
138
120
  }
139
121
 
@@ -240,7 +222,7 @@ export interface ChantConfig {
240
222
  /**
241
223
  * Generate MCP config
242
224
  */
243
- function generateMcpConfig(_ide: "claude-code" | "cursor" | "generic", pm: "bun" | "npm"): string {
225
+ function generateMcpConfig(pm: "bun" | "npm"): string {
244
226
  const config = {
245
227
  mcpServers: {
246
228
  chant: {
@@ -454,7 +436,7 @@ export async function initCommand(options: InitOptions): Promise<InitResult> {
454
436
 
455
437
  writeIfNotExists(
456
438
  join(mcpDir, "mcp.json"),
457
- generateMcpConfig(ide, detectPackageManager(targetDir)),
439
+ generateMcpConfig(detectPackageManager(targetDir)),
458
440
  `~/.${ide === "generic" ? "config/mcp" : ide}/mcp.json`,
459
441
  createdFiles,
460
442
  warnings,
@@ -1,5 +1,5 @@
1
1
  import { buildCommand, buildCommandWatch, printErrors, printWarnings } from "../commands/build";
2
- import { formatInfo } from "../format";
2
+ import { formatError, formatInfo } from "../format";
3
3
  import type { CommandContext } from "../registry";
4
4
 
5
5
  export async function runBuild(ctx: CommandContext): Promise<number> {
@@ -10,14 +10,14 @@ export async function runBuild(ctx: CommandContext): Promise<number> {
10
10
  if (args.lexicon) {
11
11
  serializers = serializers.filter((s) => s.name === args.lexicon);
12
12
  if (serializers.length === 0) {
13
- console.error(`No serializer found for lexicon "${args.lexicon}". Available: ${ctx.serializers.map((s) => s.name).join(", ")}`);
13
+ console.error(formatError({ message: `No serializer found for lexicon "${args.lexicon}". Available: ${ctx.serializers.map((s) => s.name).join(", ")}` }));
14
14
  return 1;
15
15
  }
16
16
  }
17
17
 
18
18
  const buildFormat = (args.format || "json") as "json" | "yaml";
19
19
  if (buildFormat !== "json" && buildFormat !== "yaml") {
20
- console.error(`Invalid format for build: ${buildFormat}. Expected 'json' or 'yaml'.`);
20
+ console.error(formatError({ message: `Invalid format for build: ${buildFormat}. Expected 'json' or 'yaml'.` }));
21
21
  return 1;
22
22
  }
23
23
 
@@ -1,5 +1,5 @@
1
1
  import { lintCommand, lintCommandWatch, printLintResult } from "../commands/lint";
2
- import { formatInfo } from "../format";
2
+ import { formatError, formatInfo } from "../format";
3
3
  import type { CommandContext } from "../registry";
4
4
 
5
5
  export async function runLint(ctx: CommandContext): Promise<number> {
@@ -7,7 +7,7 @@ export async function runLint(ctx: CommandContext): Promise<number> {
7
7
 
8
8
  const lintFormat = (args.format || "stylish") as "stylish" | "json" | "sarif";
9
9
  if (lintFormat !== "stylish" && lintFormat !== "json" && lintFormat !== "sarif") {
10
- console.error(`Invalid format for lint: ${lintFormat}. Expected 'stylish', 'json', or 'sarif'.`);
10
+ console.error(formatError({ message: `Invalid format for lint: ${lintFormat}. Expected 'stylish', 'json', or 'sarif'.` }));
11
11
  return 1;
12
12
  }
13
13