@intentius/chant 0.0.1

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 (271) hide show
  1. package/README.md +365 -0
  2. package/package.json +22 -0
  3. package/src/attrref.test.ts +148 -0
  4. package/src/attrref.ts +50 -0
  5. package/src/barrel.test.ts +157 -0
  6. package/src/barrel.ts +101 -0
  7. package/src/bench.test.ts +227 -0
  8. package/src/build.test.ts +437 -0
  9. package/src/build.ts +425 -0
  10. package/src/builder.test.ts +312 -0
  11. package/src/builder.ts +56 -0
  12. package/src/child-project.ts +44 -0
  13. package/src/cli/commands/__fixtures__/init-lexicon-output/README.md +26 -0
  14. package/src/cli/commands/__fixtures__/init-lexicon-output/docs/astro.config.mjs +14 -0
  15. package/src/cli/commands/__fixtures__/init-lexicon-output/docs/package.json +16 -0
  16. package/src/cli/commands/__fixtures__/init-lexicon-output/docs/src/content/docs/index.mdx +8 -0
  17. package/src/cli/commands/__fixtures__/init-lexicon-output/docs/src/content.config.ts +7 -0
  18. package/src/cli/commands/__fixtures__/init-lexicon-output/docs/tsconfig.json +10 -0
  19. package/src/cli/commands/__fixtures__/init-lexicon-output/examples/getting-started/.gitkeep +0 -0
  20. package/src/cli/commands/__fixtures__/init-lexicon-output/justfile +26 -0
  21. package/src/cli/commands/__fixtures__/init-lexicon-output/package.json +29 -0
  22. package/src/cli/commands/__fixtures__/init-lexicon-output/src/codegen/docs.ts +25 -0
  23. package/src/cli/commands/__fixtures__/init-lexicon-output/src/codegen/generate-cli.ts +8 -0
  24. package/src/cli/commands/__fixtures__/init-lexicon-output/src/codegen/generate.ts +74 -0
  25. package/src/cli/commands/__fixtures__/init-lexicon-output/src/codegen/naming.ts +33 -0
  26. package/src/cli/commands/__fixtures__/init-lexicon-output/src/codegen/package.ts +25 -0
  27. package/src/cli/commands/__fixtures__/init-lexicon-output/src/codegen/rollback.ts +45 -0
  28. package/src/cli/commands/__fixtures__/init-lexicon-output/src/coverage.ts +11 -0
  29. package/src/cli/commands/__fixtures__/init-lexicon-output/src/generated/.gitkeep +0 -0
  30. package/src/cli/commands/__fixtures__/init-lexicon-output/src/import/generator.ts +10 -0
  31. package/src/cli/commands/__fixtures__/init-lexicon-output/src/import/parser.ts +10 -0
  32. package/src/cli/commands/__fixtures__/init-lexicon-output/src/index.ts +9 -0
  33. package/src/cli/commands/__fixtures__/init-lexicon-output/src/lint/rules/index.ts +1 -0
  34. package/src/cli/commands/__fixtures__/init-lexicon-output/src/lint/rules/sample.ts +18 -0
  35. package/src/cli/commands/__fixtures__/init-lexicon-output/src/lsp/completions.ts +14 -0
  36. package/src/cli/commands/__fixtures__/init-lexicon-output/src/lsp/hover.ts +14 -0
  37. package/src/cli/commands/__fixtures__/init-lexicon-output/src/plugin.ts +110 -0
  38. package/src/cli/commands/__fixtures__/init-lexicon-output/src/serializer.ts +24 -0
  39. package/src/cli/commands/__fixtures__/init-lexicon-output/src/spec/fetch.ts +21 -0
  40. package/src/cli/commands/__fixtures__/init-lexicon-output/src/spec/parse.ts +25 -0
  41. package/src/cli/commands/__fixtures__/init-lexicon-output/src/validate-cli.ts +4 -0
  42. package/src/cli/commands/__fixtures__/init-lexicon-output/src/validate.ts +24 -0
  43. package/src/cli/commands/__fixtures__/init-lexicon-output/tsconfig.json +10 -0
  44. package/src/cli/commands/__fixtures__/sample-rule.ts +11 -0
  45. package/src/cli/commands/__snapshots__/init-lexicon.test.ts.snap +222 -0
  46. package/src/cli/commands/build.test.ts +149 -0
  47. package/src/cli/commands/build.ts +344 -0
  48. package/src/cli/commands/diff.test.ts +148 -0
  49. package/src/cli/commands/diff.ts +221 -0
  50. package/src/cli/commands/doctor.test.ts +239 -0
  51. package/src/cli/commands/doctor.ts +224 -0
  52. package/src/cli/commands/import.test.ts +379 -0
  53. package/src/cli/commands/import.ts +335 -0
  54. package/src/cli/commands/init-lexicon.test.ts +297 -0
  55. package/src/cli/commands/init-lexicon.ts +993 -0
  56. package/src/cli/commands/init.test.ts +317 -0
  57. package/src/cli/commands/init.ts +505 -0
  58. package/src/cli/commands/licenses.ts +165 -0
  59. package/src/cli/commands/lint.test.ts +332 -0
  60. package/src/cli/commands/lint.ts +408 -0
  61. package/src/cli/commands/list.test.ts +100 -0
  62. package/src/cli/commands/list.ts +108 -0
  63. package/src/cli/commands/update.test.ts +38 -0
  64. package/src/cli/commands/update.ts +207 -0
  65. package/src/cli/conflict-check.test.ts +255 -0
  66. package/src/cli/conflict-check.ts +89 -0
  67. package/src/cli/debug.ts +8 -0
  68. package/src/cli/format.test.ts +140 -0
  69. package/src/cli/format.ts +133 -0
  70. package/src/cli/handlers/build.ts +58 -0
  71. package/src/cli/handlers/dev.ts +38 -0
  72. package/src/cli/handlers/init.ts +46 -0
  73. package/src/cli/handlers/lint.ts +36 -0
  74. package/src/cli/handlers/misc.ts +57 -0
  75. package/src/cli/handlers/serve.ts +26 -0
  76. package/src/cli/index.ts +3 -0
  77. package/src/cli/lsp/capabilities.ts +46 -0
  78. package/src/cli/lsp/diagnostics.ts +52 -0
  79. package/src/cli/lsp/server.test.ts +618 -0
  80. package/src/cli/lsp/server.ts +393 -0
  81. package/src/cli/main.test.ts +257 -0
  82. package/src/cli/main.ts +224 -0
  83. package/src/cli/mcp/resources/context.ts +59 -0
  84. package/src/cli/mcp/server.test.ts +747 -0
  85. package/src/cli/mcp/server.ts +402 -0
  86. package/src/cli/mcp/tools/build.ts +117 -0
  87. package/src/cli/mcp/tools/import.ts +48 -0
  88. package/src/cli/mcp/tools/lint.ts +45 -0
  89. package/src/cli/plugins.test.ts +31 -0
  90. package/src/cli/plugins.ts +94 -0
  91. package/src/cli/registry.ts +73 -0
  92. package/src/cli/reporters/stylish.test.ts +282 -0
  93. package/src/cli/reporters/stylish.ts +186 -0
  94. package/src/cli/watch.test.ts +81 -0
  95. package/src/cli/watch.ts +101 -0
  96. package/src/codegen/case.test.ts +30 -0
  97. package/src/codegen/case.ts +11 -0
  98. package/src/codegen/coverage.ts +167 -0
  99. package/src/codegen/docs.ts +634 -0
  100. package/src/codegen/fetch.test.ts +119 -0
  101. package/src/codegen/fetch.ts +261 -0
  102. package/src/codegen/generate-registry.test.ts +118 -0
  103. package/src/codegen/generate-registry.ts +107 -0
  104. package/src/codegen/generate-runtime-index.test.ts +81 -0
  105. package/src/codegen/generate-runtime-index.ts +99 -0
  106. package/src/codegen/generate-typescript.test.ts +146 -0
  107. package/src/codegen/generate-typescript.ts +161 -0
  108. package/src/codegen/generate.ts +206 -0
  109. package/src/codegen/json-patch.test.ts +113 -0
  110. package/src/codegen/json-patch.ts +151 -0
  111. package/src/codegen/json-schema.test.ts +196 -0
  112. package/src/codegen/json-schema.ts +209 -0
  113. package/src/codegen/naming.ts +201 -0
  114. package/src/codegen/package.ts +161 -0
  115. package/src/codegen/rollback.test.ts +92 -0
  116. package/src/codegen/rollback.ts +115 -0
  117. package/src/codegen/topo-sort.test.ts +69 -0
  118. package/src/codegen/topo-sort.ts +46 -0
  119. package/src/codegen/typecheck.test.ts +37 -0
  120. package/src/codegen/typecheck.ts +74 -0
  121. package/src/codegen/validate.test.ts +86 -0
  122. package/src/codegen/validate.ts +143 -0
  123. package/src/composite.test.ts +426 -0
  124. package/src/composite.ts +243 -0
  125. package/src/config.test.ts +91 -0
  126. package/src/config.ts +87 -0
  127. package/src/declarable.test.ts +160 -0
  128. package/src/declarable.ts +47 -0
  129. package/src/detectLexicon.test.ts +236 -0
  130. package/src/detectLexicon.ts +37 -0
  131. package/src/discovery/cache.test.ts +78 -0
  132. package/src/discovery/cache.ts +86 -0
  133. package/src/discovery/collect.test.ts +269 -0
  134. package/src/discovery/collect.ts +51 -0
  135. package/src/discovery/cycles.test.ts +238 -0
  136. package/src/discovery/cycles.ts +107 -0
  137. package/src/discovery/files.test.ts +154 -0
  138. package/src/discovery/files.ts +61 -0
  139. package/src/discovery/graph.test.ts +476 -0
  140. package/src/discovery/graph.ts +150 -0
  141. package/src/discovery/import.test.ts +199 -0
  142. package/src/discovery/import.ts +20 -0
  143. package/src/discovery/index.test.ts +272 -0
  144. package/src/discovery/index.ts +132 -0
  145. package/src/discovery/resolve.test.ts +267 -0
  146. package/src/discovery/resolve.ts +54 -0
  147. package/src/errors.test.ts +138 -0
  148. package/src/errors.ts +86 -0
  149. package/src/import/base-parser.test.ts +67 -0
  150. package/src/import/base-parser.ts +48 -0
  151. package/src/import/generator.ts +21 -0
  152. package/src/import/ir-utils.test.ts +103 -0
  153. package/src/import/ir-utils.ts +87 -0
  154. package/src/import/parser.ts +41 -0
  155. package/src/index.ts +60 -0
  156. package/src/intrinsic-interpolation.test.ts +91 -0
  157. package/src/intrinsic-interpolation.ts +89 -0
  158. package/src/intrinsic.test.ts +69 -0
  159. package/src/intrinsic.ts +43 -0
  160. package/src/lexicon-integrity.test.ts +94 -0
  161. package/src/lexicon-integrity.ts +69 -0
  162. package/src/lexicon-manifest.test.ts +101 -0
  163. package/src/lexicon-manifest.ts +71 -0
  164. package/src/lexicon-output.test.ts +182 -0
  165. package/src/lexicon-output.ts +82 -0
  166. package/src/lexicon-schema.test.ts +239 -0
  167. package/src/lexicon-schema.ts +144 -0
  168. package/src/lexicon.ts +212 -0
  169. package/src/lint/config-overrides.test.ts +254 -0
  170. package/src/lint/config.test.ts +644 -0
  171. package/src/lint/config.ts +375 -0
  172. package/src/lint/declarative.test.ts +256 -0
  173. package/src/lint/declarative.ts +187 -0
  174. package/src/lint/engine.test.ts +465 -0
  175. package/src/lint/engine.ts +172 -0
  176. package/src/lint/named-checks.test.ts +37 -0
  177. package/src/lint/named-checks.ts +33 -0
  178. package/src/lint/parser.test.ts +129 -0
  179. package/src/lint/parser.ts +42 -0
  180. package/src/lint/post-synth.test.ts +113 -0
  181. package/src/lint/post-synth.ts +76 -0
  182. package/src/lint/presets/relaxed.json +19 -0
  183. package/src/lint/presets/strict.json +19 -0
  184. package/src/lint/rule-loader.test.ts +67 -0
  185. package/src/lint/rule-loader.ts +67 -0
  186. package/src/lint/rule-options.test.ts +141 -0
  187. package/src/lint/rule.test.ts +196 -0
  188. package/src/lint/rule.ts +98 -0
  189. package/src/lint/rules/barrel-import-style.test.ts +80 -0
  190. package/src/lint/rules/barrel-import-style.ts +59 -0
  191. package/src/lint/rules/composite-scope.ts +55 -0
  192. package/src/lint/rules/cor017-composite-name-match.test.ts +107 -0
  193. package/src/lint/rules/cor017-composite-name-match.ts +108 -0
  194. package/src/lint/rules/cor018-composite-prefer-lexicon-type.test.ts +172 -0
  195. package/src/lint/rules/cor018-composite-prefer-lexicon-type.ts +167 -0
  196. package/src/lint/rules/declarable-naming-convention.test.ts +69 -0
  197. package/src/lint/rules/declarable-naming-convention.ts +70 -0
  198. package/src/lint/rules/enforce-barrel-import.test.ts +169 -0
  199. package/src/lint/rules/enforce-barrel-import.ts +81 -0
  200. package/src/lint/rules/enforce-barrel-ref.test.ts +114 -0
  201. package/src/lint/rules/enforce-barrel-ref.ts +75 -0
  202. package/src/lint/rules/evl001-non-literal-expression.test.ts +158 -0
  203. package/src/lint/rules/evl001-non-literal-expression.ts +149 -0
  204. package/src/lint/rules/evl002-control-flow-resource.test.ts +110 -0
  205. package/src/lint/rules/evl002-control-flow-resource.ts +61 -0
  206. package/src/lint/rules/evl003-dynamic-property-access.test.ts +63 -0
  207. package/src/lint/rules/evl003-dynamic-property-access.ts +41 -0
  208. package/src/lint/rules/evl004-spread-non-const.test.ts +130 -0
  209. package/src/lint/rules/evl004-spread-non-const.ts +111 -0
  210. package/src/lint/rules/evl005-resource-block-body.test.ts +59 -0
  211. package/src/lint/rules/evl005-resource-block-body.ts +49 -0
  212. package/src/lint/rules/evl006-barrel-usage.test.ts +63 -0
  213. package/src/lint/rules/evl006-barrel-usage.ts +95 -0
  214. package/src/lint/rules/evl007-invalid-siblings.test.ts +87 -0
  215. package/src/lint/rules/evl007-invalid-siblings.ts +139 -0
  216. package/src/lint/rules/evl008-unresolvable-barrel-ref.test.ts +118 -0
  217. package/src/lint/rules/evl008-unresolvable-barrel-ref.ts +140 -0
  218. package/src/lint/rules/evl009-composite-no-constant.test.ts +162 -0
  219. package/src/lint/rules/evl009-composite-no-constant.ts +171 -0
  220. package/src/lint/rules/evl010-composite-no-transform.test.ts +121 -0
  221. package/src/lint/rules/evl010-composite-no-transform.ts +69 -0
  222. package/src/lint/rules/export-required.test.ts +213 -0
  223. package/src/lint/rules/export-required.ts +158 -0
  224. package/src/lint/rules/file-declarable-limit.test.ts +148 -0
  225. package/src/lint/rules/file-declarable-limit.ts +96 -0
  226. package/src/lint/rules/flat-declarations.test.ts +210 -0
  227. package/src/lint/rules/flat-declarations.ts +70 -0
  228. package/src/lint/rules/index.ts +99 -0
  229. package/src/lint/rules/no-cyclic-declarable-ref.test.ts +135 -0
  230. package/src/lint/rules/no-cyclic-declarable-ref.ts +178 -0
  231. package/src/lint/rules/no-redundant-type-import.test.ts +129 -0
  232. package/src/lint/rules/no-redundant-type-import.ts +85 -0
  233. package/src/lint/rules/no-redundant-value-cast.test.ts +51 -0
  234. package/src/lint/rules/no-redundant-value-cast.ts +46 -0
  235. package/src/lint/rules/no-string-ref.test.ts +100 -0
  236. package/src/lint/rules/no-string-ref.ts +66 -0
  237. package/src/lint/rules/no-unused-declarable-import.test.ts +74 -0
  238. package/src/lint/rules/no-unused-declarable-import.ts +103 -0
  239. package/src/lint/rules/no-unused-declarable.test.ts +134 -0
  240. package/src/lint/rules/no-unused-declarable.ts +118 -0
  241. package/src/lint/rules/prefer-namespace-import.test.ts +102 -0
  242. package/src/lint/rules/prefer-namespace-import.ts +63 -0
  243. package/src/lint/rules/single-concern-file.test.ts +156 -0
  244. package/src/lint/rules/single-concern-file.ts +98 -0
  245. package/src/lint/rules/stale-barrel-types.ts +60 -0
  246. package/src/lint/selectors.test.ts +113 -0
  247. package/src/lint/selectors.ts +188 -0
  248. package/src/lsp/lexicon-providers.ts +191 -0
  249. package/src/lsp/types.ts +79 -0
  250. package/src/mcp/types.ts +22 -0
  251. package/src/project/scan.test.ts +178 -0
  252. package/src/project/scan.ts +182 -0
  253. package/src/project/sync.test.ts +87 -0
  254. package/src/project/sync.ts +46 -0
  255. package/src/project-validation.test.ts +64 -0
  256. package/src/project-validation.ts +79 -0
  257. package/src/pseudo-parameter.test.ts +39 -0
  258. package/src/pseudo-parameter.ts +47 -0
  259. package/src/runtime.ts +68 -0
  260. package/src/serializer-walker.test.ts +124 -0
  261. package/src/serializer-walker.ts +83 -0
  262. package/src/serializer.ts +42 -0
  263. package/src/sort.test.ts +290 -0
  264. package/src/sort.ts +58 -0
  265. package/src/stack-output.ts +82 -0
  266. package/src/types.test.ts +307 -0
  267. package/src/types.ts +46 -0
  268. package/src/utils.test.ts +195 -0
  269. package/src/utils.ts +46 -0
  270. package/src/validation.test.ts +308 -0
  271. package/src/validation.ts +50 -0
@@ -0,0 +1,505 @@
1
+ import { existsSync, mkdirSync, writeFileSync, readdirSync, readFileSync } from "fs";
2
+ import { join, resolve } from "path";
3
+ import { homedir } from "os";
4
+ import { createInterface } from "readline";
5
+ import { z } from "zod";
6
+ import { formatSuccess, formatWarning } from "../format";
7
+ import { loadPlugin } from "../plugins";
8
+
9
+ /**
10
+ * Schema for validating generated package.json — catches template bugs early.
11
+ */
12
+ const GeneratedPackageJsonSchema = z.object({
13
+ name: z.string().min(1),
14
+ version: z.string(),
15
+ type: z.literal("module"),
16
+ scripts: z.record(z.string(), z.string()),
17
+ dependencies: z.record(z.string(), z.string()),
18
+ });
19
+
20
+ /**
21
+ * Init command options
22
+ */
23
+ export interface InitOptions {
24
+ /** Target directory (defaults to cwd) */
25
+ path?: string;
26
+ /** Lexicon to use */
27
+ lexicon: string;
28
+ /** Force init even in non-empty directory */
29
+ force?: boolean;
30
+ /** Skip MCP config generation */
31
+ skipMcp?: boolean;
32
+ /** Skip interactive install prompt */
33
+ skipInstall?: boolean;
34
+ }
35
+
36
+ /**
37
+ * Init command result
38
+ */
39
+ export interface InitResult {
40
+ /** Whether init succeeded */
41
+ success: boolean;
42
+ /** Created files */
43
+ createdFiles: string[];
44
+ /** Warning messages */
45
+ warnings: string[];
46
+ /** Error message if failed */
47
+ error?: string;
48
+ }
49
+
50
+ /**
51
+ * Detect the IDE environment for MCP config
52
+ */
53
+ function detectIdeEnvironment(): "claude-code" | "cursor" | "generic" {
54
+ // Check for Claude Code
55
+ if (existsSync(join(homedir(), ".claude"))) {
56
+ return "claude-code";
57
+ }
58
+
59
+ // Check for Cursor
60
+ if (existsSync(join(homedir(), ".cursor"))) {
61
+ return "cursor";
62
+ }
63
+
64
+ return "generic";
65
+ }
66
+
67
+ /**
68
+ * Get MCP config directory based on IDE
69
+ */
70
+ function getMcpConfigDir(ide: "claude-code" | "cursor" | "generic"): string {
71
+ switch (ide) {
72
+ case "claude-code":
73
+ return join(homedir(), ".claude");
74
+ case "cursor":
75
+ return join(homedir(), ".cursor");
76
+ default:
77
+ return join(homedir(), ".config", "mcp");
78
+ }
79
+ }
80
+
81
+ /**
82
+ * Generate package.json content
83
+ */
84
+ function generatePackageJson(lexicon: string): string {
85
+ const dependencies: Record<string, string> = {
86
+ "@intentius/chant": "^0.1.0",
87
+ [`@intentius/chant-lexicon-${lexicon}`]: "^0.1.0",
88
+ };
89
+
90
+ const pkg = {
91
+ name: "chant-project",
92
+ version: "0.1.0",
93
+ type: "module" as const,
94
+ scripts: {
95
+ build: `chant build src --lexicon ${lexicon}`,
96
+ lint: "chant lint src",
97
+ dev: `chant build src --lexicon ${lexicon} --watch`,
98
+ },
99
+ dependencies,
100
+ devDependencies: {
101
+ typescript: "^5.0.0",
102
+ },
103
+ };
104
+
105
+ // Validate generated output to catch template bugs
106
+ const result = GeneratedPackageJsonSchema.safeParse(pkg);
107
+ if (!result.success) {
108
+ throw new Error(`Bug: generated package.json is invalid: ${result.error.issues[0].message}`);
109
+ }
110
+
111
+ return JSON.stringify(pkg, null, 2);
112
+ }
113
+
114
+ /**
115
+ * Generate tsconfig.json content with path mappings for .chant/ types
116
+ */
117
+ function generateTsConfig(lexicon: string): string {
118
+ const config = {
119
+ compilerOptions: {
120
+ target: "ES2022",
121
+ module: "NodeNext",
122
+ moduleResolution: "NodeNext",
123
+ strict: true,
124
+ esModuleInterop: true,
125
+ skipLibCheck: true,
126
+ declaration: true,
127
+ outDir: "./dist",
128
+ rootDir: "./src",
129
+ paths: {
130
+ "@intentius/chant": ["./.chant/types/core"],
131
+ "@intentius/chant/*": ["./.chant/types/core/*"],
132
+ [`@intentius/chant-lexicon-${lexicon}`]: [`./.chant/types/lexicon-${lexicon}`],
133
+ [`@intentius/chant-lexicon-${lexicon}/*`]: [`./.chant/types/lexicon-${lexicon}/*`],
134
+ },
135
+ },
136
+ include: ["src"],
137
+ exclude: ["node_modules", "dist"],
138
+ };
139
+
140
+ return JSON.stringify(config, null, 2);
141
+ }
142
+
143
+ /**
144
+ * Generate chant.config.ts content
145
+ */
146
+ function generateChantConfig(lexicon: string): string {
147
+ return `import type { ChantConfig } from "@intentius/chant";
148
+
149
+ export default {
150
+ lexicons: ["${lexicon}"],
151
+ } satisfies ChantConfig;
152
+ `;
153
+ }
154
+
155
+ /**
156
+ * Generate .gitignore content
157
+ */
158
+ function generateGitignore(): string {
159
+ return `dist/
160
+ node_modules/
161
+ .chant/types/
162
+ .chant/meta/
163
+ .chant/rules/
164
+ .chant/skills/
165
+ `;
166
+ }
167
+
168
+
169
+ /**
170
+ * Generate embedded core type definitions for .chant/types/core/
171
+ */
172
+ function generateCoreTypeDefs(): string {
173
+ return `// @intentius/chant — core type definitions
174
+ // Run "chant update" to sync the latest types
175
+
176
+ /** Wraps a value that may be a literal or an intrinsic expression */
177
+ export type Value<T> = T | Intrinsic;
178
+
179
+ /** Marker interface for intrinsic functions (Ref, Sub, etc.) */
180
+ export interface Intrinsic {
181
+ toJSON(): unknown;
182
+ }
183
+
184
+ /** Serializer interface for chant specifications */
185
+ export interface Serializer {
186
+ name: string;
187
+ rulePrefix: string;
188
+ serialize(entities: Map<string, Declarable>, outputs?: LexiconOutput[]): string;
189
+ serializeCrossRef?(output: LexiconOutput): unknown;
190
+ }
191
+
192
+ /** Base interface for all declarable entities */
193
+ export interface Declarable {
194
+ readonly lexicon: string;
195
+ readonly entityType: string;
196
+ readonly kind?: "resource" | "property";
197
+ }
198
+
199
+ /** Cross-lexicon output reference */
200
+ export interface LexiconOutput {
201
+ readonly outputName: string;
202
+ readonly sourceEntity: string;
203
+ readonly sourceAttribute: string;
204
+ readonly lexicon: string;
205
+ }
206
+
207
+ /** Top-level project configuration */
208
+ export interface ChantConfig {
209
+ lexicons?: string[];
210
+ lint?: {
211
+ rules?: Record<string, string | [string, Record<string, unknown>]>;
212
+ extends?: string[];
213
+ plugins?: string[];
214
+ };
215
+ }
216
+
217
+ /** Barrel proxy — lazy-loads all sibling exports */
218
+ export declare function barrel(dir: string): Record<string, unknown>;
219
+ `;
220
+ }
221
+
222
+ /**
223
+ * Generate MCP config
224
+ */
225
+ function generateMcpConfig(_ide: "claude-code" | "cursor" | "generic"): string {
226
+ const config = {
227
+ mcpServers: {
228
+ chant: {
229
+ command: "npx",
230
+ args: ["chant", "serve", "mcp"],
231
+ },
232
+ },
233
+ };
234
+
235
+ return JSON.stringify(config, null, 2);
236
+ }
237
+
238
+ /**
239
+ * Prompt user for install
240
+ */
241
+ async function promptInstall(): Promise<boolean> {
242
+ const rl = createInterface({
243
+ input: process.stdin,
244
+ output: process.stdout,
245
+ });
246
+
247
+ return new Promise((resolve) => {
248
+ rl.question("Install dependencies? (Y/n) ", (answer) => {
249
+ rl.close();
250
+ const trimmed = answer.trim().toLowerCase();
251
+ resolve(trimmed === "" || trimmed === "y" || trimmed === "yes");
252
+ });
253
+ });
254
+ }
255
+
256
+ /**
257
+ * Write a file if it doesn't exist, tracking created files and warnings
258
+ */
259
+ function writeIfNotExists(
260
+ filePath: string,
261
+ content: string,
262
+ relativePath: string,
263
+ createdFiles: string[],
264
+ warnings: string[],
265
+ ): void {
266
+ if (!existsSync(filePath)) {
267
+ writeFileSync(filePath, content);
268
+ createdFiles.push(relativePath);
269
+ } else {
270
+ warnings.push(`${relativePath} already exists, skipping`);
271
+ }
272
+ }
273
+
274
+ /**
275
+ * Execute the init command
276
+ */
277
+ export async function initCommand(options: InitOptions): Promise<InitResult> {
278
+ const targetDir = resolve(options.path ?? ".");
279
+ const createdFiles: string[] = [];
280
+ const warnings: string[] = [];
281
+
282
+ // Check if directory is non-empty
283
+ if (existsSync(targetDir)) {
284
+ const contents = readdirSync(targetDir);
285
+ const nonHiddenFiles = contents.filter((f) => !f.startsWith("."));
286
+
287
+ if (nonHiddenFiles.length > 0 && !options.force) {
288
+ return {
289
+ success: false,
290
+ createdFiles: [],
291
+ warnings: [],
292
+ error: `Directory is not empty. Use --force to initialize anyway.`,
293
+ };
294
+ }
295
+
296
+ if (nonHiddenFiles.length > 0) {
297
+ warnings.push("Initializing in non-empty directory");
298
+ }
299
+ }
300
+
301
+ // Create target directory if it doesn't exist
302
+ if (!existsSync(targetDir)) {
303
+ mkdirSync(targetDir, { recursive: true });
304
+ }
305
+
306
+ // Create src directory
307
+ const srcDir = join(targetDir, "src");
308
+ if (!existsSync(srcDir)) {
309
+ mkdirSync(srcDir, { recursive: true });
310
+ }
311
+
312
+ // Generate package.json
313
+ writeIfNotExists(
314
+ join(targetDir, "package.json"),
315
+ generatePackageJson(options.lexicon),
316
+ "package.json",
317
+ createdFiles,
318
+ warnings,
319
+ );
320
+
321
+ // Generate tsconfig.json
322
+ writeIfNotExists(
323
+ join(targetDir, "tsconfig.json"),
324
+ generateTsConfig(options.lexicon),
325
+ "tsconfig.json",
326
+ createdFiles,
327
+ warnings,
328
+ );
329
+
330
+ // Generate chant.config.ts
331
+ writeIfNotExists(
332
+ join(targetDir, "chant.config.ts"),
333
+ generateChantConfig(options.lexicon),
334
+ "chant.config.ts",
335
+ createdFiles,
336
+ warnings,
337
+ );
338
+
339
+ // Generate .gitignore
340
+ writeIfNotExists(
341
+ join(targetDir, ".gitignore"),
342
+ generateGitignore(),
343
+ ".gitignore",
344
+ createdFiles,
345
+ warnings,
346
+ );
347
+
348
+ // Generate source files from plugin (or fallback to a minimal barrel)
349
+ let sourceFiles: Record<string, string> = {};
350
+ try {
351
+ const plugin = await loadPlugin(options.lexicon);
352
+ if (plugin.initTemplates) {
353
+ sourceFiles = plugin.initTemplates();
354
+ }
355
+ } catch {
356
+ // Plugin not yet installed — write a minimal barrel stub
357
+ sourceFiles = {
358
+ "_.ts": "// Barrel — re-export shared config here\n",
359
+ };
360
+ }
361
+ for (const [filename, content] of Object.entries(sourceFiles)) {
362
+ writeIfNotExists(
363
+ join(srcDir, filename),
364
+ content,
365
+ `src/${filename}`,
366
+ createdFiles,
367
+ warnings,
368
+ );
369
+ }
370
+
371
+ // Scaffold .chant/types/core/ with embedded type definitions
372
+ const coreTypesDir = join(targetDir, ".chant", "types", "core");
373
+ mkdirSync(coreTypesDir, { recursive: true });
374
+
375
+ writeIfNotExists(
376
+ join(coreTypesDir, "package.json"),
377
+ JSON.stringify({ name: "@intentius/chant", version: "0.0.0", types: "./index.d.ts" }, null, 2),
378
+ ".chant/types/core/package.json",
379
+ createdFiles,
380
+ warnings,
381
+ );
382
+
383
+ writeIfNotExists(
384
+ join(coreTypesDir, "index.d.ts"),
385
+ generateCoreTypeDefs(),
386
+ ".chant/types/core/index.d.ts",
387
+ createdFiles,
388
+ warnings,
389
+ );
390
+
391
+ // Scaffold .chant/types/lexicon-{lexicon}/ stub
392
+ const lexiconTypesDir = join(targetDir, ".chant", "types", `lexicon-${options.lexicon}`);
393
+ mkdirSync(lexiconTypesDir, { recursive: true });
394
+
395
+ writeIfNotExists(
396
+ join(lexiconTypesDir, "package.json"),
397
+ JSON.stringify(
398
+ { name: `@intentius/chant-lexicon-${options.lexicon}`, version: "0.0.0", types: "./index.d.ts" },
399
+ null,
400
+ 2,
401
+ ),
402
+ `.chant/types/lexicon-${options.lexicon}/package.json`,
403
+ createdFiles,
404
+ warnings,
405
+ );
406
+
407
+ writeIfNotExists(
408
+ join(lexiconTypesDir, "index.d.ts"),
409
+ `// Lexicon type stubs — run "chant update" to sync full types\nexport {};\n`,
410
+ `.chant/types/lexicon-${options.lexicon}/index.d.ts`,
411
+ createdFiles,
412
+ warnings,
413
+ );
414
+
415
+ // Generate MCP config
416
+ if (!options.skipMcp) {
417
+ const ide = detectIdeEnvironment();
418
+ const mcpDir = getMcpConfigDir(ide);
419
+
420
+ if (!existsSync(mcpDir)) {
421
+ mkdirSync(mcpDir, { recursive: true });
422
+ }
423
+
424
+ writeIfNotExists(
425
+ join(mcpDir, "mcp.json"),
426
+ generateMcpConfig(ide),
427
+ `~/.${ide === "generic" ? "config/mcp" : ide}/mcp.json`,
428
+ createdFiles,
429
+ warnings,
430
+ );
431
+ }
432
+
433
+ // Install skills from the lexicon's plugin
434
+ try {
435
+ const plugin = await loadPlugin(options.lexicon);
436
+ if (plugin.skills) {
437
+ const skills = plugin.skills();
438
+ if (skills.length > 0) {
439
+ const skillsDir = join(targetDir, ".chant", "skills", options.lexicon);
440
+ mkdirSync(skillsDir, { recursive: true });
441
+ for (const skill of skills) {
442
+ const skillPath = join(skillsDir, `${skill.name}.md`);
443
+ writeFileSync(skillPath, skill.content);
444
+ createdFiles.push(`.chant/skills/${options.lexicon}/${skill.name}.md`);
445
+ }
446
+ }
447
+ }
448
+ } catch {
449
+ // Skills are optional — don't fail init if plugin isn't installed yet
450
+ }
451
+
452
+ return {
453
+ success: true,
454
+ createdFiles,
455
+ warnings,
456
+ };
457
+ }
458
+
459
+ /**
460
+ * Print init result and prompt for install
461
+ */
462
+ export async function printInitResult(
463
+ result: InitResult,
464
+ options?: { skipInstall?: boolean; cwd?: string },
465
+ ): Promise<void> {
466
+ if (!result.success) {
467
+ console.error(result.error);
468
+ return;
469
+ }
470
+
471
+ for (const warning of result.warnings) {
472
+ console.error(formatWarning({ message: warning }));
473
+ }
474
+
475
+ if (result.createdFiles.length > 0) {
476
+ console.log(formatSuccess("Created:"));
477
+ for (const file of result.createdFiles) {
478
+ console.log(` ${file}`);
479
+ }
480
+ }
481
+
482
+ console.log("");
483
+
484
+ // Interactive install prompt
485
+ if (!options?.skipInstall) {
486
+ const shouldInstall = await promptInstall();
487
+ if (shouldInstall) {
488
+ const { execSync } = await import("child_process");
489
+ const cwd = options?.cwd ?? ".";
490
+ console.log("Installing dependencies...");
491
+ try {
492
+ execSync("npm install", { cwd, stdio: "inherit" });
493
+ } catch {
494
+ console.error(formatWarning({ message: "Install failed. Run 'npm install' manually." }));
495
+ }
496
+ }
497
+ }
498
+
499
+ console.log("");
500
+ console.log("Next steps:");
501
+ console.log(" 1. Edit src/config.ts");
502
+ console.log(" 2. Add resources in src/");
503
+ console.log(" 3. npm run build");
504
+ }
505
+
@@ -0,0 +1,165 @@
1
+ import { resolve, join } from "node:path";
2
+ import { readFileSync, readdirSync, existsSync, statSync } from "node:fs";
3
+
4
+ export interface LicenseEntry {
5
+ name: string;
6
+ version: string;
7
+ license: string;
8
+ licenseText?: string;
9
+ }
10
+
11
+ export interface LicensesResult {
12
+ success: boolean;
13
+ entries: LicenseEntry[];
14
+ output: string;
15
+ }
16
+
17
+ /**
18
+ * Read license info from a package's directory
19
+ */
20
+ function readPackageLicense(pkgDir: string): LicenseEntry | null {
21
+ const pkgJsonPath = join(pkgDir, "package.json");
22
+ if (!existsSync(pkgJsonPath)) return null;
23
+
24
+ try {
25
+ const pkgJson = JSON.parse(readFileSync(pkgJsonPath, "utf-8"));
26
+ if (!pkgJson.name) return null;
27
+
28
+ const entry: LicenseEntry = {
29
+ name: pkgJson.name,
30
+ version: pkgJson.version || "unknown",
31
+ license: pkgJson.license || "UNKNOWN",
32
+ };
33
+
34
+ // Try to read LICENSE file
35
+ const licenseNames = ["LICENSE", "LICENSE.md", "LICENSE.txt", "LICENCE", "LICENCE.md"];
36
+ for (const name of licenseNames) {
37
+ const licensePath = join(pkgDir, name);
38
+ if (existsSync(licensePath)) {
39
+ entry.licenseText = readFileSync(licensePath, "utf-8");
40
+ break;
41
+ }
42
+ }
43
+
44
+ return entry;
45
+ } catch {
46
+ return null;
47
+ }
48
+ }
49
+
50
+ /**
51
+ * Scan node_modules for packages
52
+ */
53
+ function scanNodeModules(nodeModulesDir: string): LicenseEntry[] {
54
+ if (!existsSync(nodeModulesDir)) return [];
55
+
56
+ const entries: LicenseEntry[] = [];
57
+ const dirs = readdirSync(nodeModulesDir);
58
+
59
+ for (const dir of dirs) {
60
+ if (dir.startsWith(".")) continue;
61
+
62
+ const fullPath = join(nodeModulesDir, dir);
63
+ if (!statSync(fullPath).isDirectory()) continue;
64
+
65
+ if (dir.startsWith("@")) {
66
+ // Scoped packages
67
+ const scopedDirs = readdirSync(fullPath);
68
+ for (const scopedDir of scopedDirs) {
69
+ const scopedPath = join(fullPath, scopedDir);
70
+ if (statSync(scopedPath).isDirectory()) {
71
+ const entry = readPackageLicense(scopedPath);
72
+ if (entry) entries.push(entry);
73
+ }
74
+ }
75
+ } else {
76
+ const entry = readPackageLicense(fullPath);
77
+ if (entry) entries.push(entry);
78
+ }
79
+ }
80
+
81
+ return entries.sort((a, b) => a.name.localeCompare(b.name));
82
+ }
83
+
84
+ /**
85
+ * Format license entries as a text table
86
+ */
87
+ function formatText(entries: LicenseEntry[]): string {
88
+ if (entries.length === 0) return "No third-party packages found.";
89
+
90
+ // Group by license type
91
+ const groups = new Map<string, LicenseEntry[]>();
92
+ for (const entry of entries) {
93
+ const existing = groups.get(entry.license) ?? [];
94
+ existing.push(entry);
95
+ groups.set(entry.license, existing);
96
+ }
97
+
98
+ const lines: string[] = [];
99
+ lines.push(`Third-party licenses (${entries.length} packages):`);
100
+ lines.push("");
101
+
102
+ // Find max widths for alignment
103
+ const maxName = Math.max(...entries.map((e) => e.name.length), 7);
104
+ const maxVersion = Math.max(...entries.map((e) => e.version.length), 7);
105
+
106
+ lines.push(
107
+ `${"Package".padEnd(maxName)} ${"Version".padEnd(maxVersion)} License`,
108
+ );
109
+ lines.push(`${"─".repeat(maxName)} ${"─".repeat(maxVersion)} ${"─".repeat(15)}`);
110
+
111
+ for (const entry of entries) {
112
+ lines.push(
113
+ `${entry.name.padEnd(maxName)} ${entry.version.padEnd(maxVersion)} ${entry.license}`,
114
+ );
115
+ }
116
+
117
+ lines.push("");
118
+ lines.push("License summary:");
119
+ const sortedGroups = [...groups.entries()].sort((a, b) => b[1].length - a[1].length);
120
+ for (const [license, pkgs] of sortedGroups) {
121
+ lines.push(` ${license}: ${pkgs.length}`);
122
+ }
123
+
124
+ return lines.join("\n");
125
+ }
126
+
127
+ /**
128
+ * Format license entries as JSON
129
+ */
130
+ function formatJson(entries: LicenseEntry[]): string {
131
+ return JSON.stringify(
132
+ entries.map(({ name, version, license }) => ({ name, version, license })),
133
+ null,
134
+ 2,
135
+ );
136
+ }
137
+
138
+ /**
139
+ * Execute the licenses command
140
+ */
141
+ export async function licensesCommand(options: {
142
+ path: string;
143
+ format: "text" | "json";
144
+ }): Promise<LicensesResult> {
145
+ const projectPath = resolve(options.path);
146
+ const nodeModulesDir = join(projectPath, "node_modules");
147
+
148
+ const entries = scanNodeModules(nodeModulesDir);
149
+
150
+ const output =
151
+ options.format === "json" ? formatJson(entries) : formatText(entries);
152
+
153
+ return {
154
+ success: true,
155
+ entries,
156
+ output,
157
+ };
158
+ }
159
+
160
+ /**
161
+ * Print licenses result to console
162
+ */
163
+ export function printLicensesResult(result: LicensesResult): void {
164
+ console.log(result.output);
165
+ }