@intentius/chant 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.
Files changed (38) hide show
  1. package/bin/chant +20 -0
  2. package/package.json +18 -17
  3. package/src/cli/commands/__fixtures__/init-lexicon-output/src/plugin.ts +0 -25
  4. package/src/cli/commands/__snapshots__/init-lexicon.test.ts.snap +0 -25
  5. package/src/cli/commands/build.ts +1 -2
  6. package/src/cli/commands/import.ts +2 -2
  7. package/src/cli/commands/init-lexicon.test.ts +0 -3
  8. package/src/cli/commands/init-lexicon.ts +1 -79
  9. package/src/cli/commands/init.ts +14 -3
  10. package/src/cli/commands/list.ts +2 -2
  11. package/src/cli/commands/update.ts +5 -3
  12. package/src/cli/conflict-check.test.ts +0 -1
  13. package/src/cli/handlers/dev.ts +1 -9
  14. package/src/cli/main.ts +13 -3
  15. package/src/cli/mcp/server.test.ts +207 -4
  16. package/src/cli/mcp/server.ts +6 -0
  17. package/src/cli/mcp/tools/explain.ts +134 -0
  18. package/src/cli/mcp/tools/scaffold.ts +107 -0
  19. package/src/cli/mcp/tools/search.ts +98 -0
  20. package/src/codegen/generate-registry.test.ts +1 -1
  21. package/src/codegen/generate-registry.ts +2 -3
  22. package/src/codegen/generate-typescript.test.ts +6 -6
  23. package/src/codegen/generate-typescript.ts +2 -6
  24. package/src/codegen/generate.ts +1 -12
  25. package/src/codegen/typecheck.ts +6 -11
  26. package/src/config.ts +4 -0
  27. package/src/index.ts +1 -1
  28. package/src/lexicon-integrity.ts +5 -4
  29. package/src/lexicon.ts +2 -6
  30. package/src/lint/config.ts +8 -6
  31. package/src/runtime-adapter.ts +158 -0
  32. package/src/serializer-walker.test.ts +0 -9
  33. package/src/serializer-walker.ts +1 -3
  34. package/src/cli/commands/__fixtures__/init-lexicon-output/src/codegen/rollback.ts +0 -45
  35. package/src/codegen/case.test.ts +0 -30
  36. package/src/codegen/case.ts +0 -11
  37. package/src/codegen/rollback.test.ts +0 -92
  38. package/src/codegen/rollback.ts +0 -115
package/bin/chant ADDED
@@ -0,0 +1,20 @@
1
+ #!/bin/sh
2
+ # Cross-runtime CLI wrapper. Tries bun first, falls back to npx tsx.
3
+ set -e
4
+
5
+ # Resolve symlinks to find the real script location
6
+ SELF="$0"
7
+ while [ -L "$SELF" ]; do
8
+ DIR="$(cd "$(dirname "$SELF")" && pwd)"
9
+ SELF="$(readlink "$SELF")"
10
+ # Handle relative symlink targets
11
+ case "$SELF" in /*) ;; *) SELF="$DIR/$SELF" ;; esac
12
+ done
13
+ SCRIPT_DIR="$(cd "$(dirname "$SELF")" && pwd)"
14
+ MAIN_TS="$SCRIPT_DIR/../src/cli/main.ts"
15
+
16
+ if command -v bun >/dev/null 2>&1; then
17
+ exec bun "$MAIN_TS" "$@"
18
+ else
19
+ exec npx tsx "$MAIN_TS" "$@"
20
+ fi
package/package.json CHANGED
@@ -1,23 +1,24 @@
1
1
  {
2
2
  "name": "@intentius/chant",
3
- "version": "0.0.5",
3
+ "version": "0.0.8",
4
4
  "license": "Apache-2.0",
5
5
  "type": "module",
6
- "files": ["src/"],
6
+ "files": ["src/", "bin/"],
7
7
  "publishConfig": {
8
- "access": "public"
9
- },
10
- "bin": {
11
- "chant": "./src/cli/main.ts"
12
- },
13
- "exports": {
14
- ".": "./src/index.ts",
15
- "./cli": "./src/cli/index.ts",
16
- "./cli/*": "./src/cli/*",
17
- "./*": "./src/*"
18
- },
19
- "dependencies": {
20
- "fflate": "^0.8.2",
21
- "zod": "^4.3.6"
22
- }
8
+ "access": "public"
9
+ },
10
+ "bin": {
11
+ "chant": "./bin/chant"
12
+ },
13
+ "exports": {
14
+ ".": "./src/index.ts",
15
+ "./cli": "./src/cli/index.ts",
16
+ "./cli/*": "./src/cli/*",
17
+ "./*": "./src/*"
18
+ },
19
+ "dependencies": {
20
+ "fflate": "^0.8.2",
21
+ "picomatch": "^4.0.3",
22
+ "zod": "^4.3.6"
23
+ }
23
24
  }
@@ -42,31 +42,6 @@ export const fixturePlugin: LexiconPlugin = {
42
42
  console.error(`Packaged ${stats.resources} resources, ${stats.ruleCount} rules, ${stats.skillCount} skills`);
43
43
  },
44
44
 
45
- async rollback(options?: { restore?: string; verbose?: boolean }): Promise<void> {
46
- const { listSnapshots, restoreSnapshot } = await import("./codegen/rollback");
47
- const { join, dirname } = await import("path");
48
- const { fileURLToPath } = await import("url");
49
-
50
- const pkgDir = dirname(dirname(fileURLToPath(import.meta.url)));
51
- const snapshotsDir = join(pkgDir, ".snapshots");
52
-
53
- if (options?.restore) {
54
- const generatedDir = join(pkgDir, "src", "generated");
55
- restoreSnapshot(String(options.restore), generatedDir);
56
- console.error(`Restored snapshot: ${options.restore}`);
57
- } else {
58
- const snapshots = listSnapshots(snapshotsDir);
59
- if (snapshots.length === 0) {
60
- console.error("No snapshots available.");
61
- } else {
62
- console.error(`Available snapshots (${snapshots.length}):`);
63
- for (const s of snapshots) {
64
- console.error(` ${s.timestamp} ${s.resources} resources ${s.path}`);
65
- }
66
- }
67
- }
68
- },
69
-
70
45
  // ── Optional extensions (uncomment and implement as needed) ───
71
46
 
72
47
  // lintRules(): LintRule[] {
@@ -152,31 +152,6 @@ export const fixturePlugin: LexiconPlugin = {
152
152
  console.error(\`Packaged \${stats.resources} resources, \${stats.ruleCount} rules, \${stats.skillCount} skills\`);
153
153
  },
154
154
 
155
- async rollback(options?: { restore?: string; verbose?: boolean }): Promise<void> {
156
- const { listSnapshots, restoreSnapshot } = await import("./codegen/rollback");
157
- const { join, dirname } = await import("path");
158
- const { fileURLToPath } = await import("url");
159
-
160
- const pkgDir = dirname(dirname(fileURLToPath(import.meta.url)));
161
- const snapshotsDir = join(pkgDir, ".snapshots");
162
-
163
- if (options?.restore) {
164
- const generatedDir = join(pkgDir, "src", "generated");
165
- restoreSnapshot(String(options.restore), generatedDir);
166
- console.error(\`Restored snapshot: \${options.restore}\`);
167
- } else {
168
- const snapshots = listSnapshots(snapshotsDir);
169
- if (snapshots.length === 0) {
170
- console.error("No snapshots available.");
171
- } else {
172
- console.error(\`Available snapshots (\${snapshots.length}):\`);
173
- for (const s of snapshots) {
174
- console.error(\` \${s.timestamp} \${s.resources} resources \${s.path}\`);
175
- }
176
- }
177
- }
178
- },
179
-
180
155
  // ── Optional extensions (uncomment and implement as needed) ───
181
156
 
182
157
  // lintRules(): LintRule[] {
@@ -5,7 +5,7 @@ import { runPostSynthChecks } from "../../lint/post-synth";
5
5
  import type { PostSynthCheck } from "../../lint/post-synth";
6
6
  import { formatError, formatWarning, formatSuccess, formatBold, formatInfo } from "../format";
7
7
  import { writeFileSync } from "fs";
8
- import { resolve } from "path";
8
+ import { resolve, dirname, join } from "path";
9
9
  import { watchDirectory, formatTimestamp, formatChangedFiles } from "../watch";
10
10
 
11
11
  /**
@@ -172,7 +172,6 @@ export async function buildCommand(options: BuildOptions): Promise<BuildResult>
172
172
 
173
173
  // Write additional files (e.g. nested stack templates) alongside the primary output
174
174
  if (additionalFiles.size > 0) {
175
- const { dirname, join } = require("path");
176
175
  const outputDir = dirname(outputPath);
177
176
  for (const [filename, content] of additionalFiles) {
178
177
  let fileContent = content;
@@ -1,4 +1,4 @@
1
- import { existsSync, mkdirSync, writeFileSync, readFileSync } from "fs";
1
+ import { existsSync, mkdirSync, writeFileSync, readFileSync, readdirSync } from "fs";
2
2
  import { join, resolve, basename } from "path";
3
3
  import { formatSuccess, formatWarning, formatError } from "../format";
4
4
  import type { TemplateIR, ResourceIR, TemplateParser } from "../../import/parser";
@@ -266,7 +266,7 @@ export async function importCommand(options: ImportOptions): Promise<ImportResul
266
266
 
267
267
  // Check output directory
268
268
  if (existsSync(outputDir) && !options.force) {
269
- const files = require("fs").readdirSync(outputDir);
269
+ const files = readdirSync(outputDir);
270
270
  if (files.length > 0) {
271
271
  warnings.push(`Output directory ${outputDir} is not empty. Use --force to overwrite.`);
272
272
  }
@@ -40,7 +40,6 @@ describe("initLexiconCommand", () => {
40
40
  "src/codegen/generate-cli.ts",
41
41
  "src/codegen/naming.ts",
42
42
  "src/codegen/package.ts",
43
- "src/codegen/rollback.ts",
44
43
  "src/codegen/docs.ts",
45
44
  "src/spec/fetch.ts",
46
45
  "src/spec/parse.ts",
@@ -65,7 +64,6 @@ describe("initLexiconCommand", () => {
65
64
  "docs/src/content/docs/index.mdx",
66
65
  "src/generated/.gitkeep",
67
66
  "examples/getting-started/.gitkeep",
68
- ".snapshots/.gitkeep",
69
67
  ];
70
68
 
71
69
  for (const file of expectedFiles) {
@@ -83,7 +81,6 @@ describe("initLexiconCommand", () => {
83
81
  expect(pluginContent).toContain("async validate(");
84
82
  expect(pluginContent).toContain("async coverage(");
85
83
  expect(pluginContent).toContain("async package(");
86
- expect(pluginContent).toContain("async rollback(");
87
84
  });
88
85
 
89
86
  test("package name uses the provided lexicon name", async () => {
@@ -106,31 +106,6 @@ export const ${names.pluginVarName}: LexiconPlugin = {
106
106
  console.error(\`Packaged \${stats.resources} resources, \${stats.ruleCount} rules, \${stats.skillCount} skills\`);
107
107
  },
108
108
 
109
- async rollback(options?: { restore?: string; verbose?: boolean }): Promise<void> {
110
- const { listSnapshots, restoreSnapshot } = await import("./codegen/rollback");
111
- const { join, dirname } = await import("path");
112
- const { fileURLToPath } = await import("url");
113
-
114
- const pkgDir = dirname(dirname(fileURLToPath(import.meta.url)));
115
- const snapshotsDir = join(pkgDir, ".snapshots");
116
-
117
- if (options?.restore) {
118
- const generatedDir = join(pkgDir, "src", "generated");
119
- restoreSnapshot(String(options.restore), generatedDir);
120
- console.error(\`Restored snapshot: \${options.restore}\`);
121
- } else {
122
- const snapshots = listSnapshots(snapshotsDir);
123
- if (snapshots.length === 0) {
124
- console.error("No snapshots available.");
125
- } else {
126
- console.error(\`Available snapshots (\${snapshots.length}):\`);
127
- for (const s of snapshots) {
128
- console.error(\` \${s.timestamp} \${s.resources} resources \${s.path}\`);
129
- }
130
- }
131
- }
132
- },
133
-
134
109
  // ── Optional extensions (uncomment and implement as needed) ───
135
110
 
136
111
  // lintRules(): LintRule[] {
@@ -382,55 +357,6 @@ export async function packageLexicon(options?: { verbose?: boolean; force?: bool
382
357
  `;
383
358
  }
384
359
 
385
- function generateCodegenRollbackTs(): string {
386
- return `import { existsSync, readdirSync, readFileSync, mkdirSync, writeFileSync, cpSync } from "fs";
387
- import { join, basename } from "path";
388
-
389
- export interface Snapshot {
390
- timestamp: string;
391
- resources: number;
392
- path: string;
393
- }
394
-
395
- /**
396
- * List available generation snapshots.
397
- */
398
- export function listSnapshots(snapshotsDir: string): Snapshot[] {
399
- if (!existsSync(snapshotsDir)) return [];
400
-
401
- return readdirSync(snapshotsDir)
402
- .filter((d) => !d.startsWith("."))
403
- .sort()
404
- .reverse()
405
- .map((dir) => {
406
- const fullPath = join(snapshotsDir, dir);
407
- const metaPath = join(fullPath, "meta.json");
408
- let resources = 0;
409
- if (existsSync(metaPath)) {
410
- try {
411
- const meta = JSON.parse(readFileSync(metaPath, "utf-8"));
412
- resources = meta.resources ?? 0;
413
- } catch {}
414
- }
415
- return { timestamp: dir, resources, path: fullPath };
416
- });
417
- }
418
-
419
- /**
420
- * Restore a snapshot to the generated directory.
421
- */
422
- export function restoreSnapshot(timestamp: string, generatedDir: string): void {
423
- const snapshotsDir = join(generatedDir, "..", "..", ".snapshots");
424
- const snapshotDir = join(snapshotsDir, timestamp);
425
- if (!existsSync(snapshotDir)) {
426
- throw new Error(\`Snapshot not found: \${timestamp}\`);
427
- }
428
- mkdirSync(generatedDir, { recursive: true });
429
- cpSync(snapshotDir, generatedDir, { recursive: true });
430
- }
431
- `;
432
- }
433
-
434
360
  function generateCodegenDocsTs(name: string): string {
435
361
  return `import { docsPipeline, writeDocsSite } from "@intentius/chant/codegen/docs";
436
362
 
@@ -735,8 +661,7 @@ package: generate validate
735
661
  }
736
662
 
737
663
  function generateGitignore(): string {
738
- return `.snapshots/
739
- dist/
664
+ return `dist/
740
665
  node_modules/
741
666
  .cache/
742
667
  `;
@@ -899,7 +824,6 @@ export async function initLexiconCommand(options: InitLexiconOptions): Promise<I
899
824
  "docs/src/content",
900
825
  "docs/src/content/docs",
901
826
  "examples/getting-started",
902
- ".snapshots",
903
827
  ];
904
828
 
905
829
  for (const dir of dirs) {
@@ -918,7 +842,6 @@ export async function initLexiconCommand(options: InitLexiconOptions): Promise<I
918
842
  "src/codegen/generate-cli.ts": generateCodegenGenerateCliTs(),
919
843
  "src/codegen/naming.ts": generateCodegenNamingTs(),
920
844
  "src/codegen/package.ts": generateCodegenPackageTs(name),
921
- "src/codegen/rollback.ts": generateCodegenRollbackTs(),
922
845
  "src/codegen/docs.ts": generateCodegenDocsTs(name),
923
846
  "src/spec/fetch.ts": generateSpecFetchTs(),
924
847
  "src/spec/parse.ts": generateSpecParseTs(),
@@ -947,7 +870,6 @@ export async function initLexiconCommand(options: InitLexiconOptions): Promise<I
947
870
  const gitkeeps = [
948
871
  "src/generated/.gitkeep",
949
872
  "examples/getting-started/.gitkeep",
950
- ".snapshots/.gitkeep",
951
873
  ];
952
874
 
953
875
  for (const gk of gitkeeps) {
@@ -426,18 +426,29 @@ export async function initCommand(options: InitOptions): Promise<InitResult> {
426
426
  }
427
427
 
428
428
  // Install skills from the lexicon's plugin
429
+ // Write to both .chant/skills/ (chant's own location) and .claude/skills/ (Claude Code discovery)
429
430
  try {
430
431
  const plugin = await loadPlugin(options.lexicon);
431
432
  if (plugin.skills) {
432
433
  const skills = plugin.skills();
433
434
  if (skills.length > 0) {
434
- const skillsDir = join(targetDir, ".chant", "skills", options.lexicon);
435
- mkdirSync(skillsDir, { recursive: true });
435
+ // .chant/skills/ chant's own skill storage
436
+ const chantSkillsDir = join(targetDir, ".chant", "skills", options.lexicon);
437
+ mkdirSync(chantSkillsDir, { recursive: true });
436
438
  for (const skill of skills) {
437
- const skillPath = join(skillsDir, `${skill.name}.md`);
439
+ const skillPath = join(chantSkillsDir, `${skill.name}.md`);
438
440
  writeFileSync(skillPath, skill.content);
439
441
  createdFiles.push(`.chant/skills/${options.lexicon}/${skill.name}.md`);
440
442
  }
443
+
444
+ // .claude/skills/ — Claude Code skill discovery format
445
+ for (const skill of skills) {
446
+ const claudeSkillDir = join(targetDir, ".claude", "skills", skill.name);
447
+ mkdirSync(claudeSkillDir, { recursive: true });
448
+ const claudeSkillPath = join(claudeSkillDir, "SKILL.md");
449
+ writeFileSync(claudeSkillPath, skill.content);
450
+ createdFiles.push(`.claude/skills/${skill.name}/SKILL.md`);
451
+ }
441
452
  }
442
453
  }
443
454
  } catch {
@@ -51,8 +51,8 @@ export async function listCommand(options: ListOptions): Promise<ListResult> {
51
51
  for (const [name, decl] of result.entities) {
52
52
  entities.push({
53
53
  name,
54
- lexicon: decl.lexicon,
55
- entityType: decl.entityType,
54
+ lexicon: decl.lexicon ?? "",
55
+ entityType: decl.entityType ?? "",
56
56
  kind: decl.kind ?? "resource",
57
57
  });
58
58
  }
@@ -1,5 +1,6 @@
1
- import { existsSync, mkdirSync, writeFileSync, cpSync, readdirSync, statSync } from "fs";
1
+ import { existsSync, mkdirSync, writeFileSync, cpSync, readdirSync, statSync, readFileSync } from "fs";
2
2
  import { join, resolve } from "path";
3
+ import { createRequire } from "module";
3
4
  import { formatSuccess, formatWarning, formatError } from "../format";
4
5
  import { loadChantConfig } from "../../config";
5
6
  import { loadPlugins } from "../plugins";
@@ -63,13 +64,14 @@ function copyTypeFiles(src: string, dest: string): number {
63
64
  function resolvePackagePath(packageName: string, projectDir: string): string | undefined {
64
65
  // Try resolve from project dir
65
66
  try {
66
- const entryPoint = require.resolve(packageName, { paths: [projectDir] });
67
+ const _require = createRequire(join(projectDir, "package.json"));
68
+ const entryPoint = _require.resolve(packageName);
67
69
  // Walk up from entry point to find package root
68
70
  let dir = entryPoint;
69
71
  while (dir !== "/") {
70
72
  dir = join(dir, "..");
71
73
  if (existsSync(join(dir, "package.json"))) {
72
- const pkg = JSON.parse(require("fs").readFileSync(join(dir, "package.json"), "utf-8"));
74
+ const pkg = JSON.parse(readFileSync(join(dir, "package.json"), "utf-8"));
73
75
  if (pkg.name === packageName) return dir;
74
76
  }
75
77
  }
@@ -22,7 +22,6 @@ function makePlugin(
22
22
  validate: noopAsync,
23
23
  coverage: noopAsync,
24
24
  package: noopAsync,
25
- rollback: noopAsync,
26
25
  };
27
26
 
28
27
  if (opts.rules) {
@@ -21,18 +21,10 @@ export async function runDevPublish(ctx: CommandContext): Promise<number> {
21
21
  return 0;
22
22
  }
23
23
 
24
- export async function runDevRollback(ctx: CommandContext): Promise<number> {
25
- for (const plugin of ctx.plugins) {
26
- await plugin.rollback({ verbose: ctx.args.verbose });
27
- console.error(formatSuccess(`${plugin.name}: rollback complete`));
28
- }
29
- return 0;
30
- }
31
-
32
24
  export async function runDevUnknown(ctx: CommandContext): Promise<number> {
33
25
  console.error(formatError({
34
26
  message: `Unknown dev subcommand: ${ctx.args.path}`,
35
- hint: "Available: chant dev generate, chant dev publish, chant dev rollback",
27
+ hint: "Available: chant dev generate, chant dev publish",
36
28
  }));
37
29
  return 1;
38
30
  }
package/src/cli/main.ts CHANGED
@@ -4,9 +4,11 @@ import { resolve } from "node:path";
4
4
  import { formatSuccess, formatError } from "./format";
5
5
  import { loadPlugins, resolveProjectLexicons } from "./plugins";
6
6
  import { resolveCommand, type CommandDef, type ParsedArgs } from "./registry";
7
+ import { loadChantConfig } from "../config";
8
+ import { initRuntime } from "../runtime-adapter";
7
9
  import { runBuild } from "./handlers/build";
8
10
  import { runLint } from "./handlers/lint";
9
- import { runDevGenerate, runDevPublish, runDevRollback, runDevUnknown } from "./handlers/dev";
11
+ import { runDevGenerate, runDevPublish, runDevUnknown } from "./handlers/dev";
10
12
  import { runServeLsp, runServeMcp, runServeUnknown } from "./handlers/serve";
11
13
  import { runInit, runInitLexicon } from "./handlers/init";
12
14
  import { runList, runImport, runUpdate, runDoctor } from "./handlers/misc";
@@ -89,7 +91,6 @@ Commands:
89
91
  Lexicon development:
90
92
  dev generate Generate lexicon artifacts (+ validate + coverage)
91
93
  dev publish Package lexicon for distribution
92
- dev rollback List or restore generation snapshots
93
94
 
94
95
  Servers:
95
96
  serve lsp Start the LSP server (stdio)
@@ -167,7 +168,6 @@ const registry: CommandDef[] = [
167
168
  // Dev subcommands
168
169
  { name: "dev generate", requiresPlugins: true, handler: runDevGenerate },
169
170
  { name: "dev publish", requiresPlugins: true, handler: runDevPublish },
170
- { name: "dev rollback", requiresPlugins: true, handler: runDevRollback },
171
171
 
172
172
  // Serve subcommands
173
173
  { name: "serve lsp", requiresPlugins: true, handler: runServeLsp },
@@ -189,6 +189,16 @@ async function main(): Promise<void> {
189
189
  process.exit(args.help ? 0 : 1);
190
190
  }
191
191
 
192
+ // Initialize runtime adapter early — before plugins or commands run
193
+ const projectPath0 = resolve(args.path === "." ? "." : args.path);
194
+ try {
195
+ const { config } = await loadChantConfig(projectPath0);
196
+ initRuntime(config.runtime);
197
+ } catch {
198
+ // Config may not exist yet (e.g. `chant init`); auto-detect runtime
199
+ initRuntime();
200
+ }
201
+
192
202
  const match = resolveCommand(args, registry);
193
203
  if (!match) {
194
204
  console.error(formatError({