@intentius/chant 0.0.4 → 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.
- package/README.md +10 -351
- package/bin/chant +20 -0
- package/package.json +18 -17
- package/src/bench.test.ts +3 -54
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/plugin.ts +8 -23
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/validate.ts +22 -18
- package/src/cli/commands/__snapshots__/init-lexicon.test.ts.snap +8 -23
- package/src/cli/commands/build.ts +1 -2
- package/src/cli/commands/import.test.ts +1 -1
- package/src/cli/commands/import.ts +2 -2
- package/src/cli/commands/init-lexicon.test.ts +0 -3
- package/src/cli/commands/init-lexicon.ts +31 -95
- package/src/cli/commands/init.test.ts +10 -14
- package/src/cli/commands/init.ts +16 -10
- package/src/cli/commands/lint.ts +9 -33
- package/src/cli/commands/list.ts +2 -2
- package/src/cli/commands/update.ts +5 -3
- package/src/cli/conflict-check.test.ts +0 -1
- package/src/cli/handlers/dev.ts +1 -9
- package/src/cli/main.ts +14 -4
- package/src/cli/mcp/server.test.ts +207 -4
- package/src/cli/mcp/server.ts +6 -0
- package/src/cli/mcp/tools/explain.ts +134 -0
- package/src/cli/mcp/tools/scaffold.ts +107 -0
- package/src/cli/mcp/tools/search.ts +98 -0
- package/src/codegen/docs-interpolation.test.ts +2 -2
- package/src/codegen/docs.ts +5 -4
- package/src/codegen/generate-registry.test.ts +2 -2
- package/src/codegen/generate-registry.ts +5 -6
- package/src/codegen/generate-typescript.test.ts +6 -6
- package/src/codegen/generate-typescript.ts +2 -6
- package/src/codegen/generate.ts +1 -12
- package/src/codegen/package.ts +28 -1
- package/src/codegen/typecheck.ts +6 -11
- package/src/codegen/validate.ts +16 -0
- package/src/config.ts +4 -0
- package/src/discovery/files.ts +6 -6
- package/src/discovery/import.ts +1 -1
- package/src/index.ts +1 -2
- package/src/lexicon-integrity.ts +5 -4
- package/src/lexicon.ts +2 -6
- package/src/lint/config.ts +8 -6
- package/src/lint/engine.ts +1 -5
- package/src/lint/rule.ts +0 -18
- package/src/lint/rules/evl009-composite-no-constant.test.ts +24 -8
- package/src/lint/rules/evl009-composite-no-constant.ts +50 -29
- package/src/lint/rules/index.ts +1 -22
- package/src/runtime-adapter.ts +158 -0
- package/src/serializer-walker.test.ts +0 -9
- package/src/serializer-walker.ts +1 -3
- package/src/stack-output.ts +3 -3
- package/src/barrel.test.ts +0 -157
- package/src/barrel.ts +0 -101
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/codegen/rollback.ts +0 -45
- package/src/codegen/case.test.ts +0 -30
- package/src/codegen/case.ts +0 -11
- package/src/codegen/rollback.test.ts +0 -92
- package/src/codegen/rollback.ts +0 -115
- package/src/lint/rules/barrel-import-style.test.ts +0 -80
- package/src/lint/rules/barrel-import-style.ts +0 -59
- package/src/lint/rules/enforce-barrel-import.test.ts +0 -169
- package/src/lint/rules/enforce-barrel-import.ts +0 -81
- package/src/lint/rules/enforce-barrel-ref.test.ts +0 -114
- package/src/lint/rules/enforce-barrel-ref.ts +0 -75
- package/src/lint/rules/evl006-barrel-usage.test.ts +0 -63
- package/src/lint/rules/evl006-barrel-usage.ts +0 -95
- package/src/lint/rules/evl008-unresolvable-barrel-ref.test.ts +0 -118
- package/src/lint/rules/evl008-unresolvable-barrel-ref.ts +0 -140
- package/src/lint/rules/prefer-namespace-import.test.ts +0 -102
- package/src/lint/rules/prefer-namespace-import.ts +0 -63
- package/src/lint/rules/stale-barrel-types.ts +0 -60
- package/src/project/scan.test.ts +0 -178
- package/src/project/scan.ts +0 -182
- package/src/project/sync.test.ts +0 -87
- package/src/project/sync.ts +0 -46
|
@@ -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 =
|
|
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 () => {
|
|
@@ -83,7 +83,9 @@ export const ${names.pluginVarName}: LexiconPlugin = {
|
|
|
83
83
|
|
|
84
84
|
async validate(options?: { verbose?: boolean }): Promise<void> {
|
|
85
85
|
const { validate } = await import("./validate");
|
|
86
|
-
await validate
|
|
86
|
+
const { printValidationResult } = await import("@intentius/chant/codegen/validate");
|
|
87
|
+
const result = await validate();
|
|
88
|
+
printValidationResult(result);
|
|
87
89
|
},
|
|
88
90
|
|
|
89
91
|
async coverage(options?: { verbose?: boolean; minOverall?: number }): Promise<void> {
|
|
@@ -93,32 +95,15 @@ export const ${names.pluginVarName}: LexiconPlugin = {
|
|
|
93
95
|
|
|
94
96
|
async package(options?: { verbose?: boolean; force?: boolean }): Promise<void> {
|
|
95
97
|
const { packageLexicon } = await import("./codegen/package");
|
|
96
|
-
await
|
|
97
|
-
},
|
|
98
|
-
|
|
99
|
-
async rollback(options?: { restore?: string; verbose?: boolean }): Promise<void> {
|
|
100
|
-
const { listSnapshots, restoreSnapshot } = await import("./codegen/rollback");
|
|
98
|
+
const { writeBundleSpec } = await import("@intentius/chant/codegen/package");
|
|
101
99
|
const { join, dirname } = await import("path");
|
|
102
100
|
const { fileURLToPath } = await import("url");
|
|
103
101
|
|
|
102
|
+
const { spec, stats } = await packageLexicon(options);
|
|
104
103
|
const pkgDir = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
const generatedDir = join(pkgDir, "src", "generated");
|
|
109
|
-
restoreSnapshot(String(options.restore), generatedDir);
|
|
110
|
-
console.error(\`Restored snapshot: \${options.restore}\`);
|
|
111
|
-
} else {
|
|
112
|
-
const snapshots = listSnapshots(snapshotsDir);
|
|
113
|
-
if (snapshots.length === 0) {
|
|
114
|
-
console.error("No snapshots available.");
|
|
115
|
-
} else {
|
|
116
|
-
console.error(\`Available snapshots (\${snapshots.length}):\`);
|
|
117
|
-
for (const s of snapshots) {
|
|
118
|
-
console.error(\` \${s.timestamp} \${s.resources} resources \${s.path}\`);
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
}
|
|
104
|
+
writeBundleSpec(spec, join(pkgDir, "dist"));
|
|
105
|
+
|
|
106
|
+
console.error(\`Packaged \${stats.resources} resources, \${stats.ruleCount} rules, \${stats.skillCount} skills\`);
|
|
122
107
|
},
|
|
123
108
|
|
|
124
109
|
// ── Optional extensions (uncomment and implement as needed) ───
|
|
@@ -372,55 +357,6 @@ export async function packageLexicon(options?: { verbose?: boolean; force?: bool
|
|
|
372
357
|
`;
|
|
373
358
|
}
|
|
374
359
|
|
|
375
|
-
function generateCodegenRollbackTs(): string {
|
|
376
|
-
return `import { existsSync, readdirSync, readFileSync, mkdirSync, writeFileSync, cpSync } from "fs";
|
|
377
|
-
import { join, basename } from "path";
|
|
378
|
-
|
|
379
|
-
export interface Snapshot {
|
|
380
|
-
timestamp: string;
|
|
381
|
-
resources: number;
|
|
382
|
-
path: string;
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
/**
|
|
386
|
-
* List available generation snapshots.
|
|
387
|
-
*/
|
|
388
|
-
export function listSnapshots(snapshotsDir: string): Snapshot[] {
|
|
389
|
-
if (!existsSync(snapshotsDir)) return [];
|
|
390
|
-
|
|
391
|
-
return readdirSync(snapshotsDir)
|
|
392
|
-
.filter((d) => !d.startsWith("."))
|
|
393
|
-
.sort()
|
|
394
|
-
.reverse()
|
|
395
|
-
.map((dir) => {
|
|
396
|
-
const fullPath = join(snapshotsDir, dir);
|
|
397
|
-
const metaPath = join(fullPath, "meta.json");
|
|
398
|
-
let resources = 0;
|
|
399
|
-
if (existsSync(metaPath)) {
|
|
400
|
-
try {
|
|
401
|
-
const meta = JSON.parse(readFileSync(metaPath, "utf-8"));
|
|
402
|
-
resources = meta.resources ?? 0;
|
|
403
|
-
} catch {}
|
|
404
|
-
}
|
|
405
|
-
return { timestamp: dir, resources, path: fullPath };
|
|
406
|
-
});
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
/**
|
|
410
|
-
* Restore a snapshot to the generated directory.
|
|
411
|
-
*/
|
|
412
|
-
export function restoreSnapshot(timestamp: string, generatedDir: string): void {
|
|
413
|
-
const snapshotsDir = join(generatedDir, "..", "..", ".snapshots");
|
|
414
|
-
const snapshotDir = join(snapshotsDir, timestamp);
|
|
415
|
-
if (!existsSync(snapshotDir)) {
|
|
416
|
-
throw new Error(\`Snapshot not found: \${timestamp}\`);
|
|
417
|
-
}
|
|
418
|
-
mkdirSync(generatedDir, { recursive: true });
|
|
419
|
-
cpSync(snapshotDir, generatedDir, { recursive: true });
|
|
420
|
-
}
|
|
421
|
-
`;
|
|
422
|
-
}
|
|
423
|
-
|
|
424
360
|
function generateCodegenDocsTs(name: string): string {
|
|
425
361
|
return `import { docsPipeline, writeDocsSite } from "@intentius/chant/codegen/docs";
|
|
426
362
|
|
|
@@ -612,28 +548,32 @@ export async function analyzeCoverage(options?: { verbose?: boolean }): Promise<
|
|
|
612
548
|
|
|
613
549
|
function generateValidateTs(name: string): string {
|
|
614
550
|
return `/**
|
|
615
|
-
* Validate generated
|
|
551
|
+
* Validate generated lexicon-${name} artifacts.
|
|
616
552
|
*
|
|
617
|
-
*
|
|
553
|
+
* Thin wrapper around the core validation framework
|
|
554
|
+
* with ${name}-specific configuration.
|
|
618
555
|
*/
|
|
619
|
-
export async function validate(options?: { verbose?: boolean }): Promise<void> {
|
|
620
|
-
const checks = [
|
|
621
|
-
// TODO: Add checks — e.g. verify lexicon JSON exists, types compile,
|
|
622
|
-
// registry has expected resources, etc.
|
|
623
|
-
{ name: "placeholder", ok: true, error: undefined as string | undefined },
|
|
624
|
-
];
|
|
625
556
|
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
console.error(\` [\${status}] \${check.name}\${msg}\`);
|
|
630
|
-
}
|
|
557
|
+
import { dirname } from "path";
|
|
558
|
+
import { fileURLToPath } from "url";
|
|
559
|
+
import { validateLexiconArtifacts, type ValidateResult } from "@intentius/chant/codegen/validate";
|
|
631
560
|
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
561
|
+
export type { ValidateCheck, ValidateResult } from "@intentius/chant/codegen/validate";
|
|
562
|
+
|
|
563
|
+
// TODO: Add names of required entities for your lexicon
|
|
564
|
+
const REQUIRED_NAMES: string[] = [];
|
|
565
|
+
|
|
566
|
+
/**
|
|
567
|
+
* Validate the generated lexicon-${name} artifacts.
|
|
568
|
+
*/
|
|
569
|
+
export async function validate(opts?: { basePath?: string }): Promise<ValidateResult> {
|
|
570
|
+
const basePath = opts?.basePath ?? dirname(dirname(fileURLToPath(import.meta.url)));
|
|
571
|
+
|
|
572
|
+
return validateLexiconArtifacts({
|
|
573
|
+
lexiconJsonFilename: "lexicon-${name}.json",
|
|
574
|
+
requiredNames: REQUIRED_NAMES,
|
|
575
|
+
basePath,
|
|
576
|
+
});
|
|
637
577
|
}
|
|
638
578
|
`;
|
|
639
579
|
}
|
|
@@ -721,8 +661,7 @@ package: generate validate
|
|
|
721
661
|
}
|
|
722
662
|
|
|
723
663
|
function generateGitignore(): string {
|
|
724
|
-
return
|
|
725
|
-
dist/
|
|
664
|
+
return `dist/
|
|
726
665
|
node_modules/
|
|
727
666
|
.cache/
|
|
728
667
|
`;
|
|
@@ -885,7 +824,6 @@ export async function initLexiconCommand(options: InitLexiconOptions): Promise<I
|
|
|
885
824
|
"docs/src/content",
|
|
886
825
|
"docs/src/content/docs",
|
|
887
826
|
"examples/getting-started",
|
|
888
|
-
".snapshots",
|
|
889
827
|
];
|
|
890
828
|
|
|
891
829
|
for (const dir of dirs) {
|
|
@@ -904,7 +842,6 @@ export async function initLexiconCommand(options: InitLexiconOptions): Promise<I
|
|
|
904
842
|
"src/codegen/generate-cli.ts": generateCodegenGenerateCliTs(),
|
|
905
843
|
"src/codegen/naming.ts": generateCodegenNamingTs(),
|
|
906
844
|
"src/codegen/package.ts": generateCodegenPackageTs(name),
|
|
907
|
-
"src/codegen/rollback.ts": generateCodegenRollbackTs(),
|
|
908
845
|
"src/codegen/docs.ts": generateCodegenDocsTs(name),
|
|
909
846
|
"src/spec/fetch.ts": generateSpecFetchTs(),
|
|
910
847
|
"src/spec/parse.ts": generateSpecParseTs(),
|
|
@@ -933,7 +870,6 @@ export async function initLexiconCommand(options: InitLexiconOptions): Promise<I
|
|
|
933
870
|
const gitkeeps = [
|
|
934
871
|
"src/generated/.gitkeep",
|
|
935
872
|
"examples/getting-started/.gitkeep",
|
|
936
|
-
".snapshots/.gitkeep",
|
|
937
873
|
];
|
|
938
874
|
|
|
939
875
|
for (const gk of gitkeeps) {
|
|
@@ -22,14 +22,13 @@ describe("initCommand", () => {
|
|
|
22
22
|
expect(result.createdFiles).toContain("tsconfig.json");
|
|
23
23
|
expect(result.createdFiles).toContain("chant.config.ts");
|
|
24
24
|
expect(result.createdFiles).toContain(".gitignore");
|
|
25
|
-
expect(result.createdFiles).toContain("src/_.ts");
|
|
26
25
|
expect(result.createdFiles).toContain("src/config.ts");
|
|
27
26
|
expect(result.createdFiles).toContain("src/data-bucket.ts");
|
|
28
27
|
expect(result.createdFiles).toContain("src/logs-bucket.ts");
|
|
29
28
|
});
|
|
30
29
|
});
|
|
31
30
|
|
|
32
|
-
test("aws source files use
|
|
31
|
+
test("aws source files use direct imports", async () => {
|
|
33
32
|
await withTestDir(async (testDir) => {
|
|
34
33
|
const options: InitOptions = {
|
|
35
34
|
path: testDir,
|
|
@@ -41,15 +40,15 @@ describe("initCommand", () => {
|
|
|
41
40
|
await initCommand(options);
|
|
42
41
|
|
|
43
42
|
const configContent = readFileSync(join(testDir, "src", "config.ts"), "utf-8");
|
|
44
|
-
expect(configContent).toContain('
|
|
43
|
+
expect(configContent).toContain('from "@intentius/chant-lexicon-aws"');
|
|
45
44
|
|
|
46
45
|
const dataBucketContent = readFileSync(join(testDir, "src", "data-bucket.ts"), "utf-8");
|
|
47
|
-
expect(dataBucketContent).toContain('
|
|
48
|
-
expect(dataBucketContent).toContain('
|
|
46
|
+
expect(dataBucketContent).toContain('from "@intentius/chant-lexicon-aws"');
|
|
47
|
+
expect(dataBucketContent).toContain('from "./config"');
|
|
49
48
|
|
|
50
49
|
const logsBucketContent = readFileSync(join(testDir, "src", "logs-bucket.ts"), "utf-8");
|
|
51
|
-
expect(logsBucketContent).toContain('
|
|
52
|
-
expect(logsBucketContent).toContain('
|
|
50
|
+
expect(logsBucketContent).toContain('from "@intentius/chant-lexicon-aws"');
|
|
51
|
+
expect(logsBucketContent).toContain('from "./config"');
|
|
53
52
|
});
|
|
54
53
|
});
|
|
55
54
|
|
|
@@ -177,7 +176,7 @@ describe("initCommand", () => {
|
|
|
177
176
|
});
|
|
178
177
|
});
|
|
179
178
|
|
|
180
|
-
test("
|
|
179
|
+
test("does not generate barrel file", async () => {
|
|
181
180
|
await withTestDir(async (testDir) => {
|
|
182
181
|
const options: InitOptions = {
|
|
183
182
|
path: testDir,
|
|
@@ -188,13 +187,11 @@ describe("initCommand", () => {
|
|
|
188
187
|
|
|
189
188
|
await initCommand(options);
|
|
190
189
|
|
|
190
|
+
// No _.ts barrel — direct imports are used instead
|
|
191
191
|
const barrelPath = join(testDir, "src", "_.ts");
|
|
192
|
-
expect(existsSync(barrelPath)).toBe(
|
|
192
|
+
expect(existsSync(barrelPath)).toBe(false);
|
|
193
193
|
|
|
194
|
-
|
|
195
|
-
expect(barrelContent).toContain('export * from "./config"');
|
|
196
|
-
|
|
197
|
-
// No index.ts — barrel re-exports cause duplicate entity errors during build
|
|
194
|
+
// No index.ts either
|
|
198
195
|
const indexPath = join(testDir, "src", "index.ts");
|
|
199
196
|
expect(existsSync(indexPath)).toBe(false);
|
|
200
197
|
});
|
|
@@ -218,7 +215,6 @@ describe("initCommand", () => {
|
|
|
218
215
|
expect(coreContent).toContain("Value<T>");
|
|
219
216
|
expect(coreContent).toContain("Serializer");
|
|
220
217
|
expect(coreContent).toContain("ChantConfig");
|
|
221
|
-
expect(coreContent).toContain("barrel");
|
|
222
218
|
|
|
223
219
|
const corePkg = join(testDir, ".chant", "types", "core", "package.json");
|
|
224
220
|
expect(existsSync(corePkg)).toBe(true);
|
package/src/cli/commands/init.ts
CHANGED
|
@@ -214,8 +214,6 @@ export interface ChantConfig {
|
|
|
214
214
|
};
|
|
215
215
|
}
|
|
216
216
|
|
|
217
|
-
/** Barrel proxy — lazy-loads all sibling exports */
|
|
218
|
-
export declare function barrel(dir: string): Record<string, unknown>;
|
|
219
217
|
`;
|
|
220
218
|
}
|
|
221
219
|
|
|
@@ -345,7 +343,7 @@ export async function initCommand(options: InitOptions): Promise<InitResult> {
|
|
|
345
343
|
warnings,
|
|
346
344
|
);
|
|
347
345
|
|
|
348
|
-
// Generate source files from plugin (
|
|
346
|
+
// Generate source files from plugin (if available)
|
|
349
347
|
let sourceFiles: Record<string, string> = {};
|
|
350
348
|
try {
|
|
351
349
|
const plugin = await loadPlugin(options.lexicon);
|
|
@@ -353,10 +351,7 @@ export async function initCommand(options: InitOptions): Promise<InitResult> {
|
|
|
353
351
|
sourceFiles = plugin.initTemplates();
|
|
354
352
|
}
|
|
355
353
|
} catch {
|
|
356
|
-
// Plugin not yet installed —
|
|
357
|
-
sourceFiles = {
|
|
358
|
-
"_.ts": "// Barrel — re-export shared config here\n",
|
|
359
|
-
};
|
|
354
|
+
// Plugin not yet installed — no source files to scaffold
|
|
360
355
|
}
|
|
361
356
|
for (const [filename, content] of Object.entries(sourceFiles)) {
|
|
362
357
|
writeIfNotExists(
|
|
@@ -431,18 +426,29 @@ export async function initCommand(options: InitOptions): Promise<InitResult> {
|
|
|
431
426
|
}
|
|
432
427
|
|
|
433
428
|
// Install skills from the lexicon's plugin
|
|
429
|
+
// Write to both .chant/skills/ (chant's own location) and .claude/skills/ (Claude Code discovery)
|
|
434
430
|
try {
|
|
435
431
|
const plugin = await loadPlugin(options.lexicon);
|
|
436
432
|
if (plugin.skills) {
|
|
437
433
|
const skills = plugin.skills();
|
|
438
434
|
if (skills.length > 0) {
|
|
439
|
-
|
|
440
|
-
|
|
435
|
+
// .chant/skills/ — chant's own skill storage
|
|
436
|
+
const chantSkillsDir = join(targetDir, ".chant", "skills", options.lexicon);
|
|
437
|
+
mkdirSync(chantSkillsDir, { recursive: true });
|
|
441
438
|
for (const skill of skills) {
|
|
442
|
-
const skillPath = join(
|
|
439
|
+
const skillPath = join(chantSkillsDir, `${skill.name}.md`);
|
|
443
440
|
writeFileSync(skillPath, skill.content);
|
|
444
441
|
createdFiles.push(`.chant/skills/${options.lexicon}/${skill.name}.md`);
|
|
445
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
|
+
}
|
|
446
452
|
}
|
|
447
453
|
}
|
|
448
454
|
} catch {
|
package/src/cli/commands/lint.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { resolve, join } from "path";
|
|
2
2
|
import { readFileSync, writeFileSync, readdirSync, statSync } from "fs";
|
|
3
3
|
import { runLint } from "../../lint/engine";
|
|
4
|
-
import type { LintRule, LintDiagnostic, LintFix
|
|
4
|
+
import type { LintRule, LintDiagnostic, LintFix } from "../../lint/rule";
|
|
5
5
|
import { loadPlugins, resolveProjectLexicons } from "../plugins";
|
|
6
6
|
import { formatStylish, formatJson, formatSarif } from "../reporters/stylish";
|
|
7
7
|
import { loadLocalRules } from "../../lint/rule-loader";
|
|
@@ -241,54 +241,30 @@ export async function lintCommand(options: LintOptions): Promise<LintResult> {
|
|
|
241
241
|
// Get all TypeScript files
|
|
242
242
|
const files = getTypeScriptFiles(infraPath);
|
|
243
243
|
|
|
244
|
-
// Build barrel exports context for EVL008
|
|
245
|
-
let runOptions: LintRunOptions | undefined;
|
|
246
|
-
try {
|
|
247
|
-
const { scanProject } = require("../../project/scan");
|
|
248
|
-
const scan = scanProject(infraPath);
|
|
249
|
-
const barrelExports = new Set<string>(scan.exports.map((e: { name: string }) => e.name));
|
|
250
|
-
const projectExports = new Map<string, { file: string; className: string }>();
|
|
251
|
-
for (const exp of scan.exports) {
|
|
252
|
-
projectExports.set(exp.name, { file: exp.file, className: exp.className });
|
|
253
|
-
}
|
|
254
|
-
runOptions = { barrelExports, projectExports, projectScan: scan };
|
|
255
|
-
} catch {
|
|
256
|
-
// No barrel file found — EVL008/COR016 will be no-ops
|
|
257
|
-
}
|
|
258
|
-
|
|
259
244
|
// Run lint — use per-file rules when overrides are present
|
|
260
245
|
let diagnostics: LintDiagnostic[];
|
|
261
246
|
if (options.rules) {
|
|
262
|
-
diagnostics = await runLint(files, options.rules, undefined
|
|
247
|
+
diagnostics = await runLint(files, options.rules, undefined);
|
|
263
248
|
} else if (hasOverrides) {
|
|
264
249
|
diagnostics = [];
|
|
265
250
|
for (const file of files) {
|
|
266
251
|
const relativePath = file.slice(infraPath.length + 1);
|
|
267
252
|
const { rules: fileRules, ruleOptions } = getDefaultRules(infraPath, relativePath, allRules);
|
|
268
|
-
const fileDiagnostics = await runLint([file], fileRules, ruleOptions
|
|
253
|
+
const fileDiagnostics = await runLint([file], fileRules, ruleOptions);
|
|
269
254
|
diagnostics.push(...fileDiagnostics);
|
|
270
255
|
}
|
|
271
256
|
} else {
|
|
272
257
|
const { rules, ruleOptions } = getDefaultRules(infraPath, undefined, allRules);
|
|
273
|
-
diagnostics = await runLint(files, rules, ruleOptions
|
|
258
|
+
diagnostics = await runLint(files, rules, ruleOptions);
|
|
274
259
|
}
|
|
275
260
|
|
|
276
261
|
// Apply fixes if requested
|
|
277
262
|
if (options.fix) {
|
|
278
|
-
//
|
|
279
|
-
for (const diag of diagnostics) {
|
|
280
|
-
if (diag.fix?.kind === "write-file" && diag.fix.params) {
|
|
281
|
-
const path = diag.fix.params.path as string;
|
|
282
|
-
const content = diag.fix.params.content as string;
|
|
283
|
-
writeFileSync(path, content, "utf-8");
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
// Group remaining fixes by file (exclude write-file fixes)
|
|
263
|
+
// Group fixes by file
|
|
288
264
|
const fixesByFile = new Map<string, LintFix[]>();
|
|
289
265
|
|
|
290
266
|
for (const diag of diagnostics) {
|
|
291
|
-
if (diag.fix
|
|
267
|
+
if (diag.fix) {
|
|
292
268
|
const existing = fixesByFile.get(diag.file) ?? [];
|
|
293
269
|
existing.push(diag.fix);
|
|
294
270
|
fixesByFile.set(diag.file, existing);
|
|
@@ -303,18 +279,18 @@ export async function lintCommand(options: LintOptions): Promise<LintResult> {
|
|
|
303
279
|
// Re-lint after fixes to get updated diagnostics
|
|
304
280
|
let postFixDiagnostics: LintDiagnostic[];
|
|
305
281
|
if (options.rules) {
|
|
306
|
-
postFixDiagnostics = await runLint(files, options.rules, undefined
|
|
282
|
+
postFixDiagnostics = await runLint(files, options.rules, undefined);
|
|
307
283
|
} else if (hasOverrides) {
|
|
308
284
|
postFixDiagnostics = [];
|
|
309
285
|
for (const file of files) {
|
|
310
286
|
const relativePath = file.slice(infraPath.length + 1);
|
|
311
287
|
const { rules: fileRules, ruleOptions } = getDefaultRules(infraPath, relativePath, allRules);
|
|
312
|
-
const fileDiagnostics = await runLint([file], fileRules, ruleOptions
|
|
288
|
+
const fileDiagnostics = await runLint([file], fileRules, ruleOptions);
|
|
313
289
|
postFixDiagnostics.push(...fileDiagnostics);
|
|
314
290
|
}
|
|
315
291
|
} else {
|
|
316
292
|
const { rules, ruleOptions } = getDefaultRules(infraPath, undefined, allRules);
|
|
317
|
-
postFixDiagnostics = await runLint(files, rules, ruleOptions
|
|
293
|
+
postFixDiagnostics = await runLint(files, rules, ruleOptions);
|
|
318
294
|
}
|
|
319
295
|
diagnostics.length = 0;
|
|
320
296
|
diagnostics.push(...postFixDiagnostics);
|
package/src/cli/commands/list.ts
CHANGED
|
@@ -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
|
|
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(
|
|
74
|
+
const pkg = JSON.parse(readFileSync(join(dir, "package.json"), "utf-8"));
|
|
73
75
|
if (pkg.name === packageName) return dir;
|
|
74
76
|
}
|
|
75
77
|
}
|
package/src/cli/handlers/dev.ts
CHANGED
|
@@ -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
|
|
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,
|
|
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)
|
|
@@ -161,13 +162,12 @@ const registry: CommandDef[] = [
|
|
|
161
162
|
{ name: "import", handler: runImport },
|
|
162
163
|
{ name: "init", handler: runInit },
|
|
163
164
|
{ name: "init lexicon", handler: runInitLexicon },
|
|
164
|
-
|
|
165
|
+
{ name: "update", handler: runUpdate },
|
|
165
166
|
{ name: "doctor", handler: runDoctor },
|
|
166
167
|
|
|
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({
|