@contextos/cli 0.1.0

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 (2) hide show
  1. package/dist/index.js +1350 -0
  2. package/package.json +42 -0
package/dist/index.js ADDED
@@ -0,0 +1,1350 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/index.ts
4
+ import { Command as Command14 } from "commander";
5
+
6
+ // src/commands/init.ts
7
+ import { Command } from "commander";
8
+ import chalk from "chalk";
9
+ import ora from "ora";
10
+ import inquirer from "inquirer";
11
+ import { existsSync, mkdirSync, writeFileSync } from "fs";
12
+ import { join } from "path";
13
+ import { stringify } from "yaml";
14
+ import {
15
+ detectProjectType
16
+ } from "@contextos/core";
17
+ var CONTEXTOS_DIR = ".contextos";
18
+ var initCommand = new Command("init").description("Initialize ContextOS in the current directory").option("-t, --template <name>", "Use a community template").option("-y, --yes", "Skip interactive prompts").option("-f, --force", "Overwrite existing configuration").action(async (options) => {
19
+ const cwd = process.cwd();
20
+ const contextosPath = join(cwd, CONTEXTOS_DIR);
21
+ if (existsSync(contextosPath) && !options.force) {
22
+ console.log(chalk.yellow("\u26A0\uFE0F ContextOS is already initialized in this directory."));
23
+ console.log(chalk.gray(" Use --force to reinitialize.\n"));
24
+ return;
25
+ }
26
+ console.log(chalk.blue.bold("\n\u{1F680} Initializing ContextOS...\n"));
27
+ const spinner = ora("Detecting project type...").start();
28
+ const detected = await detectProjectType(cwd);
29
+ spinner.succeed(`Detected: ${chalk.cyan(detected.language)}${detected.framework ? ` / ${chalk.cyan(detected.framework)}` : ""}`);
30
+ let projectName = cwd.split(/[\\/]/).pop() || "my-project";
31
+ let description = "";
32
+ if (!options.yes) {
33
+ const answers = await inquirer.prompt([
34
+ {
35
+ type: "input",
36
+ name: "projectName",
37
+ message: "Project name:",
38
+ default: projectName
39
+ },
40
+ {
41
+ type: "input",
42
+ name: "description",
43
+ message: "Project description (optional):"
44
+ },
45
+ {
46
+ type: "confirm",
47
+ name: "proceed",
48
+ message: "Create .contextos folder?",
49
+ default: true
50
+ }
51
+ ]);
52
+ if (!answers.proceed) {
53
+ console.log(chalk.yellow("\nInitialization cancelled."));
54
+ return;
55
+ }
56
+ projectName = answers.projectName;
57
+ description = answers.description;
58
+ }
59
+ const createSpinner = ora("Creating .contextos folder...").start();
60
+ try {
61
+ mkdirSync(contextosPath, { recursive: true });
62
+ mkdirSync(join(contextosPath, "db"), { recursive: true });
63
+ mkdirSync(join(contextosPath, "rules"), { recursive: true });
64
+ const contextYaml = {
65
+ version: "3.1",
66
+ project: {
67
+ name: projectName,
68
+ language: detected.language,
69
+ framework: detected.framework,
70
+ description: description || void 0
71
+ },
72
+ stack: {},
73
+ constraints: [
74
+ {
75
+ rule: "Follow project coding conventions",
76
+ severity: "warning"
77
+ }
78
+ ],
79
+ boundaries: [],
80
+ meta: {
81
+ last_indexed: (/* @__PURE__ */ new Date()).toISOString(),
82
+ index_version: "3.1"
83
+ }
84
+ };
85
+ writeFileSync(
86
+ join(contextosPath, "context.yaml"),
87
+ stringify(contextYaml, { indent: 2 }),
88
+ "utf-8"
89
+ );
90
+ const configYaml = {
91
+ indexing: {
92
+ watch_mode: true,
93
+ ignore_patterns: [
94
+ "**/*.test.ts",
95
+ "**/*.spec.ts",
96
+ "node_modules/**",
97
+ "dist/**",
98
+ ".git/**"
99
+ ],
100
+ file_size_limit: "1MB"
101
+ },
102
+ graph: {
103
+ max_depth: 2,
104
+ follow_types: ["import", "require", "export"],
105
+ include_types: true
106
+ },
107
+ embedding: {
108
+ strategy: "adaptive",
109
+ provider: "local",
110
+ model: "all-MiniLM-L6-v2",
111
+ chunk_size: 512,
112
+ overlap: 50
113
+ },
114
+ budgeting: {
115
+ strategy: "adaptive"
116
+ }
117
+ };
118
+ writeFileSync(
119
+ join(contextosPath, "config.yaml"),
120
+ stringify(configYaml, { indent: 2 }),
121
+ "utf-8"
122
+ );
123
+ const codingRules = `# Coding Rules
124
+
125
+ ## General Guidelines
126
+
127
+ - Follow the established patterns in this codebase
128
+ - Write clean, readable code
129
+ - Add comments for complex logic
130
+
131
+ ## Architecture
132
+
133
+ - Separate concerns appropriately
134
+ - Use dependency injection where applicable
135
+
136
+ ## Testing
137
+
138
+ - Write tests for new functionality
139
+ - Maintain test coverage
140
+ `;
141
+ writeFileSync(
142
+ join(contextosPath, "rules", "coding.md"),
143
+ codingRules,
144
+ "utf-8"
145
+ );
146
+ createSpinner.succeed("Created .contextos folder");
147
+ console.log(chalk.green.bold("\n\u2705 ContextOS initialized successfully!\n"));
148
+ console.log(chalk.gray("Created:"));
149
+ console.log(chalk.gray(" .contextos/context.yaml - Project configuration"));
150
+ console.log(chalk.gray(" .contextos/config.yaml - Tool settings"));
151
+ console.log(chalk.gray(" .contextos/rules/coding.md - Coding guidelines"));
152
+ console.log();
153
+ console.log(chalk.blue("Next steps:"));
154
+ console.log(chalk.white(" 1. Edit .contextos/context.yaml to customize your project"));
155
+ console.log(chalk.white(' 2. Run "ctx index" to build the initial index'));
156
+ console.log(chalk.white(' 3. Run "ctx goal <description>" to build context\n'));
157
+ } catch (error) {
158
+ createSpinner.fail("Failed to create .contextos folder");
159
+ console.error(chalk.red("Error:"), error);
160
+ process.exit(1);
161
+ }
162
+ });
163
+
164
+ // src/commands/index.ts
165
+ import { Command as Command2 } from "commander";
166
+ import chalk2 from "chalk";
167
+ import ora2 from "ora";
168
+ import { getContextBuilder } from "@contextos/core";
169
+ var indexCommand = new Command2("index").description("Index the project for context building").option("-w, --watch", "Watch mode for incremental updates").option("-f, --force", "Force full reindex").option("-s, --stats", "Show indexing statistics").action(async (options) => {
170
+ console.log(chalk2.blue.bold("\n\u{1F4CA} Indexing project...\n"));
171
+ const spinner = ora2("Initializing...").start();
172
+ try {
173
+ const builder = await getContextBuilder();
174
+ spinner.text = "Indexing files...";
175
+ const result = await builder.index(options.force);
176
+ spinner.succeed("Indexing complete");
177
+ console.log();
178
+ console.log(chalk2.gray("Results:"));
179
+ console.log(chalk2.white(` \u{1F4C1} Files indexed: ${chalk2.cyan(result.filesIndexed)}`));
180
+ console.log(chalk2.white(` \u{1F4DD} Chunks created: ${chalk2.cyan(result.chunksCreated)}`));
181
+ console.log(chalk2.white(` \u23F1\uFE0F Time: ${chalk2.cyan(result.timeMs + "ms")}`));
182
+ if (options.stats) {
183
+ const stats = builder.getStats();
184
+ if (stats) {
185
+ console.log();
186
+ console.log(chalk2.gray("Graph Statistics:"));
187
+ console.log(chalk2.white(` Nodes: ${stats.graph.nodeCount}`));
188
+ console.log(chalk2.white(` Edges: ${stats.graph.edgeCount}`));
189
+ console.log(chalk2.white(` Avg imports: ${stats.graph.avgImports}`));
190
+ console.log();
191
+ console.log(chalk2.gray("Vector Statistics:"));
192
+ console.log(chalk2.white(` Chunks: ${stats.vectors.chunkCount}`));
193
+ console.log(chalk2.white(` Files: ${stats.vectors.fileCount}`));
194
+ console.log(chalk2.white(` Embedded: ${stats.vectors.embeddedCount}`));
195
+ }
196
+ }
197
+ console.log();
198
+ console.log(chalk2.green('\u2705 Index ready. Run "ctx goal <description>" to build context.\n'));
199
+ if (options.watch) {
200
+ console.log(chalk2.blue("\u{1F440} Watching for file changes... (Ctrl+C to stop)\n"));
201
+ process.on("SIGINT", () => {
202
+ console.log(chalk2.yellow("\nWatch mode stopped."));
203
+ process.exit(0);
204
+ });
205
+ }
206
+ } catch (error) {
207
+ spinner.fail("Indexing failed");
208
+ if (error instanceof Error && error.message.includes("not initialized")) {
209
+ console.log(chalk2.yellow('\nRun "ctx init" first to initialize ContextOS.\n'));
210
+ } else {
211
+ console.error(chalk2.red("Error:"), error);
212
+ }
213
+ process.exit(1);
214
+ }
215
+ });
216
+
217
+ // src/commands/build.ts
218
+ import { Command as Command3 } from "commander";
219
+ import chalk3 from "chalk";
220
+ import ora3 from "ora";
221
+ import { getContextBuilder as getContextBuilder2 } from "@contextos/core";
222
+ var buildCommand = new Command3("build").description("Build context (infers goal from git diff)").option("-m, --max-tokens <number>", "Maximum tokens for context", "32000").option("-f, --file <path>", "Target file for context").option("--no-rules", "Exclude coding rules from context").action(async (options) => {
223
+ console.log(chalk3.blue.bold("\n\u{1F528} Building context...\n"));
224
+ const spinner = ora3("Analyzing project...").start();
225
+ try {
226
+ const builder = await getContextBuilder2();
227
+ spinner.text = "Inferring goal from git diff...";
228
+ const result = await builder.build({
229
+ maxTokens: parseInt(options.maxTokens),
230
+ targetFile: options.file,
231
+ includeRules: options.rules
232
+ });
233
+ spinner.succeed("Context built");
234
+ console.log();
235
+ console.log(chalk3.gray("Goal: ") + chalk3.white(result.goal));
236
+ console.log();
237
+ console.log(chalk3.gray("Included Files:"));
238
+ for (const file of result.files.slice(0, 10)) {
239
+ const scoreStr = (file.score.final * 100).toFixed(0) + "%";
240
+ console.log(chalk3.white(` \u{1F4C4} ${file.path} ${chalk3.gray(`(${scoreStr} - ${file.reason})`)}`));
241
+ }
242
+ if (result.files.length > 10) {
243
+ console.log(chalk3.gray(` ... and ${result.files.length - 10} more files`));
244
+ }
245
+ console.log();
246
+ console.log(chalk3.gray("Statistics:"));
247
+ console.log(chalk3.white(` \u{1F4CA} Token count: ${chalk3.cyan(result.tokenCount.toLocaleString())}`));
248
+ console.log(chalk3.white(` \u{1F4B0} Token savings: ${chalk3.green(result.savings.percentage + "%")}`));
249
+ console.log(chalk3.white(` \u23F1\uFE0F Build time: ${chalk3.cyan(result.meta.buildTime + "ms")}`));
250
+ console.log();
251
+ console.log(chalk3.green('\u2705 Context ready. Run "ctx copy" to copy to clipboard.\n'));
252
+ } catch (error) {
253
+ spinner.fail("Build failed");
254
+ if (error instanceof Error && error.message.includes("not initialized")) {
255
+ console.log(chalk3.yellow('\nRun "ctx init" first to initialize ContextOS.\n'));
256
+ } else {
257
+ console.error(chalk3.red("Error:"), error);
258
+ }
259
+ process.exit(1);
260
+ }
261
+ });
262
+
263
+ // src/commands/goal.ts
264
+ import { Command as Command4 } from "commander";
265
+ import chalk4 from "chalk";
266
+ import ora4 from "ora";
267
+ import { getContextBuilder as getContextBuilder3 } from "@contextos/core";
268
+ var goalCommand = new Command4("goal").description("Set explicit goal and build context").argument("<description>", "Goal description").option("-m, --max-tokens <number>", "Maximum tokens for context", "32000").option("-f, --file <path>", "Target file for context").option("--no-rules", "Exclude coding rules from context").option("-c, --copy", "Copy to clipboard after building").action(async (description, options) => {
269
+ console.log(chalk4.blue.bold("\n\u{1F3AF} Building context for goal...\n"));
270
+ console.log(chalk4.gray("Goal: ") + chalk4.white(description));
271
+ console.log();
272
+ const spinner = ora4("Analyzing relevance...").start();
273
+ try {
274
+ const builder = await getContextBuilder3();
275
+ spinner.text = "Ranking files...";
276
+ const result = await builder.build({
277
+ goal: description,
278
+ maxTokens: parseInt(options.maxTokens),
279
+ targetFile: options.file,
280
+ includeRules: options.rules
281
+ });
282
+ spinner.succeed("Context built");
283
+ console.log();
284
+ console.log(chalk4.gray("Top Relevant Files:"));
285
+ for (const file of result.files.slice(0, 8)) {
286
+ const scoreStr = (file.score.final * 100).toFixed(0) + "%";
287
+ console.log(chalk4.white(` \u{1F4C4} ${file.path}`));
288
+ console.log(chalk4.gray(` Score: ${scoreStr} | ${file.reason}`));
289
+ }
290
+ if (result.rules.length > 0) {
291
+ console.log();
292
+ console.log(chalk4.gray("Applied Rules:"));
293
+ for (const rule of result.rules.slice(0, 3)) {
294
+ const icon = rule.severity === "error" ? "\u{1F6AB}" : "\u26A0\uFE0F";
295
+ console.log(chalk4.white(` ${icon} ${rule.rule}`));
296
+ }
297
+ }
298
+ console.log();
299
+ console.log(chalk4.gray("Summary:"));
300
+ console.log(chalk4.white(` \u{1F4CA} Tokens: ${chalk4.cyan(result.tokenCount.toLocaleString())} / ${options.maxTokens}`));
301
+ console.log(chalk4.white(` \u{1F4C1} Files: ${chalk4.cyan(result.files.length)} included`));
302
+ console.log(chalk4.white(` \u{1F4B0} Savings: ${chalk4.green(result.savings.percentage + "%")} token reduction`));
303
+ if (options.copy) {
304
+ const { default: clipboardy } = await import("clipboardy");
305
+ const formatted = builder.formatForLLM(result);
306
+ await clipboardy.write(formatted);
307
+ console.log();
308
+ console.log(chalk4.green("\u{1F4CB} Copied to clipboard!"));
309
+ }
310
+ console.log();
311
+ if (!options.copy) {
312
+ console.log(chalk4.blue('Run "ctx copy" to copy to clipboard, or "ctx preview" for details.\n'));
313
+ }
314
+ } catch (error) {
315
+ spinner.fail("Goal building failed");
316
+ if (error instanceof Error && error.message.includes("not initialized")) {
317
+ console.log(chalk4.yellow('\nRun "ctx init" first to initialize ContextOS.\n'));
318
+ } else {
319
+ console.error(chalk4.red("Error:"), error);
320
+ }
321
+ process.exit(1);
322
+ }
323
+ });
324
+
325
+ // src/commands/preview.ts
326
+ import { Command as Command5 } from "commander";
327
+ import chalk5 from "chalk";
328
+ import ora5 from "ora";
329
+ import { getContextBuilder as getContextBuilder4 } from "@contextos/core";
330
+ var previewCommand = new Command5("preview").description("Preview context without copying").option("-g, --goal <description>", "Override goal for preview").option("-m, --max-tokens <number>", "Maximum tokens", "32000").action(async (options) => {
331
+ console.log(chalk5.blue.bold("\n\u{1F441}\uFE0F Context Preview\n"));
332
+ const spinner = ora5("Loading context...").start();
333
+ try {
334
+ const builder = await getContextBuilder4();
335
+ const result = await builder.build({
336
+ goal: options.goal,
337
+ maxTokens: parseInt(options.maxTokens)
338
+ });
339
+ spinner.stop();
340
+ console.log(chalk5.gray("\u250C" + "\u2500".repeat(68) + "\u2510"));
341
+ console.log(chalk5.gray("\u2502") + ` ${chalk5.white.bold("Context Summary")}`.padEnd(77) + chalk5.gray("\u2502"));
342
+ console.log(chalk5.gray("\u251C" + "\u2500".repeat(68) + "\u2524"));
343
+ const goalLine = ` Goal: ${result.goal}`;
344
+ console.log(chalk5.gray("\u2502") + chalk5.white(goalLine.substring(0, 68).padEnd(68)) + chalk5.gray("\u2502"));
345
+ console.log(chalk5.gray("\u251C" + "\u2500".repeat(68) + "\u2524"));
346
+ const tokenBudget = parseInt(options.maxTokens);
347
+ const usedPercent = Math.round(result.tokenCount / tokenBudget * 100);
348
+ const barLength = 40;
349
+ const filledLength = Math.round(usedPercent / 100 * barLength);
350
+ const bar = "\u2588".repeat(filledLength) + "\u2591".repeat(barLength - filledLength);
351
+ console.log(chalk5.gray("\u2502") + ` Tokens: ${result.tokenCount.toLocaleString()} / ${tokenBudget.toLocaleString()}`.padEnd(68) + chalk5.gray("\u2502"));
352
+ console.log(chalk5.gray("\u2502") + ` [${bar}] ${usedPercent}%`.padEnd(68) + chalk5.gray("\u2502"));
353
+ console.log(chalk5.gray("\u251C" + "\u2500".repeat(68) + "\u2524"));
354
+ console.log(chalk5.gray("\u2502") + chalk5.white.bold(" \u{1F4C1} Included Files (" + result.files.length + ")").padEnd(77) + chalk5.gray("\u2502"));
355
+ console.log(chalk5.gray("\u2502") + " ".repeat(68) + chalk5.gray("\u2502"));
356
+ for (const file of result.files.slice(0, 6)) {
357
+ const score = (file.score.final * 100).toFixed(0);
358
+ const line = ` ${file.path}`;
359
+ const scorePart = ` ${score}%`;
360
+ const paddedLine = line.substring(0, 58).padEnd(58) + chalk5.gray(scorePart);
361
+ console.log(chalk5.gray("\u2502") + paddedLine.padEnd(77) + chalk5.gray("\u2502"));
362
+ }
363
+ if (result.files.length > 6) {
364
+ console.log(chalk5.gray("\u2502") + chalk5.gray(` ... and ${result.files.length - 6} more files`).padEnd(68) + chalk5.gray("\u2502"));
365
+ }
366
+ console.log(chalk5.gray("\u251C" + "\u2500".repeat(68) + "\u2524"));
367
+ if (result.rules.length > 0) {
368
+ console.log(chalk5.gray("\u2502") + chalk5.white.bold(" \u{1F4CB} Active Rules (" + result.rules.length + ")").padEnd(77) + chalk5.gray("\u2502"));
369
+ for (const rule of result.rules.slice(0, 3)) {
370
+ const icon = rule.severity === "error" ? "\u{1F6AB}" : "\u26A0\uFE0F";
371
+ const line = ` ${icon} ${rule.rule}`;
372
+ console.log(chalk5.gray("\u2502") + line.substring(0, 68).padEnd(68) + chalk5.gray("\u2502"));
373
+ }
374
+ console.log(chalk5.gray("\u251C" + "\u2500".repeat(68) + "\u2524"));
375
+ }
376
+ console.log(chalk5.gray("\u2502") + chalk5.green.bold(` \u{1F4B0} Token Savings: ${result.savings.percentage}%`).padEnd(77) + chalk5.gray("\u2502"));
377
+ console.log(chalk5.gray("\u2502") + chalk5.gray(` Original: ${result.savings.original.toLocaleString()} \u2192 Optimized: ${result.savings.optimized.toLocaleString()}`).padEnd(68) + chalk5.gray("\u2502"));
378
+ console.log(chalk5.gray("\u2514" + "\u2500".repeat(68) + "\u2518"));
379
+ console.log();
380
+ console.log(chalk5.blue('Run "ctx copy" to copy this context to clipboard.\n'));
381
+ } catch (error) {
382
+ spinner.fail("Preview failed");
383
+ if (error instanceof Error && error.message.includes("not initialized")) {
384
+ console.log(chalk5.yellow('\nRun "ctx init" first to initialize ContextOS.\n'));
385
+ } else {
386
+ console.error(chalk5.red("Error:"), error);
387
+ }
388
+ process.exit(1);
389
+ }
390
+ });
391
+
392
+ // src/commands/copy.ts
393
+ import { Command as Command6 } from "commander";
394
+ import chalk6 from "chalk";
395
+ import ora6 from "ora";
396
+ import { getContextBuilder as getContextBuilder5 } from "@contextos/core";
397
+ var copyCommand = new Command6("copy").description("Copy built context to clipboard").option("-g, --goal <description>", "Goal for context").option("-m, --max-tokens <number>", "Maximum tokens", "32000").option("-f, --format <type>", "Output format: markdown, json, plain", "markdown").action(async (options) => {
398
+ const spinner = ora6("Building context...").start();
399
+ try {
400
+ const builder = await getContextBuilder5();
401
+ const result = await builder.build({
402
+ goal: options.goal,
403
+ maxTokens: parseInt(options.maxTokens)
404
+ });
405
+ spinner.text = "Formatting...";
406
+ let output;
407
+ switch (options.format) {
408
+ case "json":
409
+ output = JSON.stringify({
410
+ goal: result.goal,
411
+ files: result.files.map((f) => ({
412
+ path: f.path,
413
+ score: f.score.final,
414
+ content: f.chunks.map((c) => c.content).join("\n")
415
+ })),
416
+ rules: result.rules,
417
+ tokenCount: result.tokenCount
418
+ }, null, 2);
419
+ break;
420
+ case "plain":
421
+ output = result.files.map((f) => `// ${f.path}
422
+ ${f.chunks.map((c) => c.content).join("\n")}`).join("\n\n");
423
+ break;
424
+ default:
425
+ output = builder.formatForLLM(result);
426
+ }
427
+ const { default: clipboardy } = await import("clipboardy");
428
+ await clipboardy.write(output);
429
+ spinner.succeed("Copied to clipboard");
430
+ console.log();
431
+ console.log(chalk6.gray("Context Details:"));
432
+ console.log(chalk6.white(` \u{1F4CA} Tokens: ${chalk6.cyan(result.tokenCount.toLocaleString())}`));
433
+ console.log(chalk6.white(` \u{1F4C1} Files: ${chalk6.cyan(result.files.length)}`));
434
+ console.log(chalk6.white(` \u{1F4DD} Format: ${chalk6.cyan(options.format)}`));
435
+ console.log(chalk6.white(` \u{1F4B0} Savings: ${chalk6.green(result.savings.percentage + "%")}`));
436
+ console.log();
437
+ console.log(chalk6.green.bold("\u{1F4CB} Context copied to clipboard!\n"));
438
+ console.log(chalk6.gray("Paste into your AI assistant to get contextual help.\n"));
439
+ } catch (error) {
440
+ spinner.fail("Copy failed");
441
+ if (error instanceof Error && error.message.includes("not initialized")) {
442
+ console.log(chalk6.yellow('\nRun "ctx init" first to initialize ContextOS.\n'));
443
+ } else {
444
+ console.error(chalk6.red("Error:"), error);
445
+ }
446
+ process.exit(1);
447
+ }
448
+ });
449
+
450
+ // src/commands/doctor.ts
451
+ import { Command as Command7 } from "commander";
452
+ import chalk7 from "chalk";
453
+ import ora7 from "ora";
454
+ import { detectDrift, isGeminiAvailable, createGeminiClient } from "@contextos/core";
455
+ var doctorCommand = new Command7("doctor").description("Check for drift between context.yaml and code reality").option("--fix", "Attempt to auto-fix issues").option("--json", "Output as JSON for CI/CD").option("--ci", "Exit with code 1 if errors found").option("--explain", "Use Gemini to explain issues in detail").action(async (options) => {
456
+ if (!options.json) {
457
+ console.log(chalk7.blue.bold("\n\u{1FA7A} ContextOS Health Check\n"));
458
+ }
459
+ const spinner = options.json ? null : ora7("Analyzing project...").start();
460
+ const gemini = options.explain && isGeminiAvailable() ? createGeminiClient() : null;
461
+ try {
462
+ const report = await detectDrift();
463
+ if (spinner) spinner.stop();
464
+ if (options.json) {
465
+ console.log(JSON.stringify(report, null, 2));
466
+ if (options.ci && report.errors.length > 0) {
467
+ process.exit(1);
468
+ }
469
+ return;
470
+ }
471
+ console.log(chalk7.gray("\u2554" + "\u2550".repeat(68) + "\u2557"));
472
+ console.log(chalk7.gray("\u2551") + chalk7.white.bold(" \u{1FA7A} ContextOS Health Check").padEnd(77) + chalk7.gray("\u2551"));
473
+ console.log(chalk7.gray("\u2560" + "\u2550".repeat(68) + "\u2563"));
474
+ if (report.errors.length > 0) {
475
+ console.log(chalk7.gray("\u2551") + " ".repeat(68) + chalk7.gray("\u2551"));
476
+ console.log(chalk7.gray("\u2551") + chalk7.red.bold(` \u274C ERRORS (${report.errors.length})`).padEnd(77) + chalk7.gray("\u2551"));
477
+ console.log(chalk7.gray("\u2551") + " ".repeat(68) + chalk7.gray("\u2551"));
478
+ for (const error of report.errors) {
479
+ console.log(chalk7.gray("\u2551") + chalk7.red(` [ERROR] ${error.message}`).substring(0, 68).padEnd(68) + chalk7.gray("\u2551"));
480
+ console.log(chalk7.gray("\u2551") + chalk7.gray(` Expected: "${error.expected}"`).substring(0, 68).padEnd(68) + chalk7.gray("\u2551"));
481
+ console.log(chalk7.gray("\u2551") + chalk7.gray(` Actual: "${error.actual}"`).substring(0, 68).padEnd(68) + chalk7.gray("\u2551"));
482
+ if (error.location) {
483
+ console.log(chalk7.gray("\u2551") + chalk7.gray(` Location: ${error.location.file}${error.location.line ? ":" + error.location.line : ""}`).substring(0, 68).padEnd(68) + chalk7.gray("\u2551"));
484
+ }
485
+ console.log(chalk7.gray("\u2551") + chalk7.yellow(` \u{1F4A1} Fix: ${error.suggestion}`).substring(0, 68).padEnd(68) + chalk7.gray("\u2551"));
486
+ if (gemini) {
487
+ try {
488
+ const explanation = await gemini.explainDrift({
489
+ type: error.type,
490
+ expected: error.expected,
491
+ actual: error.actual,
492
+ location: error.location?.file
493
+ });
494
+ console.log(chalk7.gray("\u2551") + " ".repeat(68) + chalk7.gray("\u2551"));
495
+ console.log(chalk7.gray("\u2551") + chalk7.cyan(" \u{1F916} AI Explanation:").padEnd(68) + chalk7.gray("\u2551"));
496
+ const lines = explanation.split("\n").slice(0, 3);
497
+ for (const line of lines) {
498
+ console.log(chalk7.gray("\u2551") + chalk7.white(` ${line}`).substring(0, 68).padEnd(68) + chalk7.gray("\u2551"));
499
+ }
500
+ } catch {
501
+ }
502
+ }
503
+ console.log(chalk7.gray("\u2551") + " ".repeat(68) + chalk7.gray("\u2551"));
504
+ }
505
+ }
506
+ if (report.warnings.length > 0) {
507
+ console.log(chalk7.gray("\u2551") + chalk7.yellow.bold(` \u26A0\uFE0F WARNINGS (${report.warnings.length})`).padEnd(77) + chalk7.gray("\u2551"));
508
+ console.log(chalk7.gray("\u2551") + " ".repeat(68) + chalk7.gray("\u2551"));
509
+ for (const warning of report.warnings) {
510
+ console.log(chalk7.gray("\u2551") + chalk7.yellow(` [WARNING] ${warning.message}`).substring(0, 68).padEnd(68) + chalk7.gray("\u2551"));
511
+ console.log(chalk7.gray("\u2551") + chalk7.yellow(` \u{1F4A1} ${warning.suggestion}`).substring(0, 68).padEnd(68) + chalk7.gray("\u2551"));
512
+ console.log(chalk7.gray("\u2551") + " ".repeat(68) + chalk7.gray("\u2551"));
513
+ }
514
+ }
515
+ console.log(chalk7.gray("\u2560" + "\u2550".repeat(68) + "\u2563"));
516
+ const statusIcon = report.errors.length > 0 ? "\u274C" : report.warnings.length > 0 ? "\u26A0\uFE0F" : "\u2705";
517
+ const statusText = report.errors.length > 0 ? "Issues found - action required" : report.warnings.length > 0 ? "Warnings detected" : "All checks passed";
518
+ console.log(chalk7.gray("\u2551") + ` ${statusIcon} ${statusText}`.padEnd(68) + chalk7.gray("\u2551"));
519
+ console.log(chalk7.gray("\u2551") + chalk7.green(` \u2705 ${report.passed} checks passed`).padEnd(77) + chalk7.gray("\u2551"));
520
+ if (report.errors.length > 0) {
521
+ console.log(chalk7.gray("\u2551") + chalk7.red(` \u274C ${report.errors.length} errors`).padEnd(77) + chalk7.gray("\u2551"));
522
+ }
523
+ if (report.warnings.length > 0) {
524
+ console.log(chalk7.gray("\u2551") + chalk7.yellow(` \u26A0\uFE0F ${report.warnings.length} warnings`).padEnd(77) + chalk7.gray("\u2551"));
525
+ }
526
+ console.log(chalk7.gray("\u255A" + "\u2550".repeat(68) + "\u255D"));
527
+ console.log();
528
+ if (options.ci && report.errors.length > 0) {
529
+ process.exit(1);
530
+ }
531
+ } catch (error) {
532
+ if (spinner) spinner.fail("Health check failed");
533
+ if (error instanceof Error && error.message.includes("not initialized")) {
534
+ console.log(chalk7.yellow('\nRun "ctx init" first to initialize ContextOS.\n'));
535
+ } else {
536
+ console.error(chalk7.red("Error:"), error);
537
+ }
538
+ process.exit(1);
539
+ }
540
+ });
541
+
542
+ // src/commands/suggest-rules.ts
543
+ import { Command as Command8 } from "commander";
544
+ import chalk8 from "chalk";
545
+ import ora8 from "ora";
546
+ import { readFileSync, writeFileSync as writeFileSync2 } from "fs";
547
+ import { join as join2 } from "path";
548
+ import { glob } from "glob";
549
+ import { parse, stringify as stringify2 } from "yaml";
550
+ import {
551
+ isGeminiAvailable as isGeminiAvailable2,
552
+ createGeminiClient as createGeminiClient2,
553
+ loadConfig
554
+ } from "@contextos/core";
555
+ var suggestRulesCommand = new Command8("suggest-rules").description("Suggest coding constraints using Gemini AI analysis").option("-a, --apply", "Automatically apply suggested rules").option("-n, --count <number>", "Number of files to analyze", "5").action(async (options) => {
556
+ console.log(chalk8.blue.bold("\n\u{1F916} Gemini Constraint Suggester\n"));
557
+ if (!isGeminiAvailable2()) {
558
+ console.log(chalk8.yellow("\u26A0\uFE0F Gemini API key not configured."));
559
+ console.log(chalk8.gray(" Set GEMINI_API_KEY environment variable to use this feature.\n"));
560
+ console.log(chalk8.white(' Example: export GEMINI_API_KEY="your-api-key"\n'));
561
+ return;
562
+ }
563
+ const spinner = ora8("Loading project configuration...").start();
564
+ try {
565
+ const config = loadConfig();
566
+ const gemini = createGeminiClient2();
567
+ if (!gemini) {
568
+ spinner.fail("Failed to create Gemini client");
569
+ return;
570
+ }
571
+ const existingRules = config.context.constraints?.map((c) => c.rule) || [];
572
+ spinner.text = "Finding sample code files...";
573
+ const extensions = ["*.ts", "*.js", "*.tsx", "*.jsx", "*.py"];
574
+ const patterns = extensions.map((e) => `**/${e}`);
575
+ let files = await glob(patterns, {
576
+ cwd: config.rootDir,
577
+ ignore: ["**/node_modules/**", "**/dist/**", "**/.git/**", "**/*.test.*", "**/*.spec.*"],
578
+ absolute: true
579
+ });
580
+ const maxFiles = parseInt(options.count) || 5;
581
+ files = files.slice(0, maxFiles);
582
+ if (files.length === 0) {
583
+ spinner.fail("No source files found to analyze");
584
+ return;
585
+ }
586
+ spinner.text = `Analyzing ${files.length} files with Gemini...`;
587
+ let sampleCode = "";
588
+ for (const file of files) {
589
+ try {
590
+ const content = readFileSync(file, "utf-8");
591
+ const relativePath = file.replace(config.rootDir, "");
592
+ sampleCode += `
593
+ // File: ${relativePath}
594
+ ${content.slice(0, 500)}
595
+ `;
596
+ } catch {
597
+ }
598
+ }
599
+ const suggestions = await gemini.suggestConstraints(sampleCode, existingRules);
600
+ spinner.succeed("Analysis complete");
601
+ if (suggestions.length === 0) {
602
+ console.log(chalk8.yellow("\n\u{1F4CB} No new constraint suggestions at this time."));
603
+ console.log(chalk8.gray(" Your existing rules seem comprehensive!\n"));
604
+ return;
605
+ }
606
+ console.log(chalk8.white.bold("\n\u{1F4CB} Suggested Constraints:\n"));
607
+ suggestions.forEach((suggestion, index) => {
608
+ const severityIcon = suggestion.severity === "error" ? "\u{1F6AB}" : suggestion.severity === "warning" ? "\u26A0\uFE0F" : "\u2139\uFE0F";
609
+ const severityColor = suggestion.severity === "error" ? chalk8.red : suggestion.severity === "warning" ? chalk8.yellow : chalk8.blue;
610
+ console.log(`${chalk8.gray(`${index + 1}.`)} ${severityIcon} ${chalk8.white.bold(suggestion.rule)}`);
611
+ console.log(` ${chalk8.gray("Severity:")} ${severityColor(suggestion.severity)}`);
612
+ console.log(` ${chalk8.gray("Reason:")} ${suggestion.reason}`);
613
+ console.log();
614
+ });
615
+ if (options.apply) {
616
+ const applySpinner = ora8("Applying suggestions to context.yaml...").start();
617
+ try {
618
+ const contextPath = join2(config.rootDir, ".contextos", "context.yaml");
619
+ const contextContent = readFileSync(contextPath, "utf-8");
620
+ const contextYaml = parse(contextContent);
621
+ if (!contextYaml.constraints) {
622
+ contextYaml.constraints = [];
623
+ }
624
+ for (const suggestion of suggestions) {
625
+ contextYaml.constraints.push({
626
+ rule: suggestion.rule,
627
+ severity: suggestion.severity
628
+ });
629
+ }
630
+ writeFileSync2(contextPath, stringify2(contextYaml, { indent: 2 }), "utf-8");
631
+ applySpinner.succeed(`Added ${suggestions.length} constraints to context.yaml`);
632
+ } catch (error) {
633
+ applySpinner.fail("Failed to apply suggestions");
634
+ console.error(chalk8.red("Error:"), error);
635
+ }
636
+ } else {
637
+ console.log(chalk8.blue("\u{1F4A1} Run with --apply to add these rules to your context.yaml\n"));
638
+ }
639
+ } catch (error) {
640
+ spinner.fail("Suggestion failed");
641
+ if (error instanceof Error && error.message.includes("not initialized")) {
642
+ console.log(chalk8.yellow('\nRun "ctx init" first to initialize ContextOS.\n'));
643
+ } else {
644
+ console.error(chalk8.red("Error:"), error);
645
+ }
646
+ process.exit(1);
647
+ }
648
+ });
649
+
650
+ // src/commands/config.ts
651
+ import { Command as Command9 } from "commander";
652
+ import chalk9 from "chalk";
653
+ import inquirer2 from "inquirer";
654
+ import { readFileSync as readFileSync2, writeFileSync as writeFileSync3 } from "fs";
655
+ import { join as join3 } from "path";
656
+ import { parse as parse2, stringify as stringify3 } from "yaml";
657
+ import { loadConfig as loadConfig2 } from "@contextos/core";
658
+ var configCommand = new Command9("config").description("View and modify configuration settings").argument("[key]", "Configuration key to view/set (e.g., indexing.watch_mode)").argument("[value]", "Value to set").option("-l, --list", "List all configuration values").option("-r, --reset", "Reset to default configuration").option("-e, --edit", "Open configuration in interactive editor").action(async (key, value, options) => {
659
+ try {
660
+ const config = loadConfig2();
661
+ const configPath = join3(config.rootDir, ".contextos", "config.yaml");
662
+ const configContent = readFileSync2(configPath, "utf-8");
663
+ const configYaml = parse2(configContent);
664
+ if (options.list || !key && !options.reset && !options.edit) {
665
+ console.log(chalk9.blue.bold("\n\u2699\uFE0F ContextOS Configuration\n"));
666
+ console.log(chalk9.gray("Path:"), configPath);
667
+ console.log();
668
+ printConfig(configYaml);
669
+ return;
670
+ }
671
+ if (options.reset) {
672
+ const { confirm } = await inquirer2.prompt([{
673
+ type: "confirm",
674
+ name: "confirm",
675
+ message: "Reset configuration to defaults?",
676
+ default: false
677
+ }]);
678
+ if (confirm) {
679
+ const defaultConfig = {
680
+ indexing: {
681
+ watch_mode: true,
682
+ ignore_patterns: ["node_modules/**", "dist/**", ".git/**"],
683
+ file_size_limit: "1MB"
684
+ },
685
+ graph: {
686
+ max_depth: 2,
687
+ follow_types: ["import", "require", "export"],
688
+ include_types: true
689
+ },
690
+ embedding: {
691
+ strategy: "adaptive",
692
+ provider: "local",
693
+ model: "all-MiniLM-L6-v2",
694
+ chunk_size: 512,
695
+ overlap: 50
696
+ },
697
+ budgeting: {
698
+ strategy: "adaptive"
699
+ }
700
+ };
701
+ writeFileSync3(configPath, stringify3(defaultConfig, { indent: 2 }), "utf-8");
702
+ console.log(chalk9.green("\n\u2705 Configuration reset to defaults.\n"));
703
+ }
704
+ return;
705
+ }
706
+ if (options.edit) {
707
+ const answers = await inquirer2.prompt([
708
+ {
709
+ type: "confirm",
710
+ name: "watchMode",
711
+ message: "Enable watch mode (auto-indexing)?",
712
+ default: configYaml.indexing?.watch_mode ?? true
713
+ },
714
+ {
715
+ type: "number",
716
+ name: "maxDepth",
717
+ message: "Max dependency graph depth:",
718
+ default: configYaml.graph?.max_depth ?? 2
719
+ },
720
+ {
721
+ type: "list",
722
+ name: "embeddingStrategy",
723
+ message: "Embedding strategy:",
724
+ choices: ["adaptive", "full", "lazy"],
725
+ default: configYaml.embedding?.strategy ?? "adaptive"
726
+ },
727
+ {
728
+ type: "number",
729
+ name: "chunkSize",
730
+ message: "Chunk size (tokens):",
731
+ default: configYaml.embedding?.chunk_size ?? 512
732
+ },
733
+ {
734
+ type: "input",
735
+ name: "targetModel",
736
+ message: "Target LLM model:",
737
+ default: configYaml.budgeting?.target_model ?? "gpt-4-turbo"
738
+ }
739
+ ]);
740
+ configYaml.indexing = { ...configYaml.indexing, watch_mode: answers.watchMode };
741
+ configYaml.graph = { ...configYaml.graph, max_depth: answers.maxDepth };
742
+ configYaml.embedding = {
743
+ ...configYaml.embedding,
744
+ strategy: answers.embeddingStrategy,
745
+ chunk_size: answers.chunkSize
746
+ };
747
+ configYaml.budgeting = { ...configYaml.budgeting, target_model: answers.targetModel };
748
+ writeFileSync3(configPath, stringify3(configYaml, { indent: 2 }), "utf-8");
749
+ console.log(chalk9.green("\n\u2705 Configuration updated.\n"));
750
+ return;
751
+ }
752
+ if (key) {
753
+ const keys = key.split(".");
754
+ if (value !== void 0) {
755
+ setNestedValue(configYaml, keys, parseValue(value));
756
+ writeFileSync3(configPath, stringify3(configYaml, { indent: 2 }), "utf-8");
757
+ console.log(chalk9.green(`
758
+ \u2705 Set ${key} = ${value}
759
+ `));
760
+ } else {
761
+ const val = getNestedValue(configYaml, keys);
762
+ if (val !== void 0) {
763
+ console.log(`
764
+ ${chalk9.cyan(key)}: ${formatValue(val)}
765
+ `);
766
+ } else {
767
+ console.log(chalk9.yellow(`
768
+ \u26A0\uFE0F Key '${key}' not found.
769
+ `));
770
+ }
771
+ }
772
+ }
773
+ } catch (error) {
774
+ if (error instanceof Error && error.message.includes("not initialized")) {
775
+ console.log(chalk9.yellow('\nRun "ctx init" first to initialize ContextOS.\n'));
776
+ } else {
777
+ console.error(chalk9.red("Error:"), error);
778
+ }
779
+ process.exit(1);
780
+ }
781
+ });
782
+ function printConfig(obj, prefix = "") {
783
+ for (const [key, value] of Object.entries(obj)) {
784
+ const fullKey = prefix ? `${prefix}.${key}` : key;
785
+ if (value && typeof value === "object" && !Array.isArray(value)) {
786
+ console.log(chalk9.white.bold(`${fullKey}:`));
787
+ printConfig(value, fullKey);
788
+ } else {
789
+ console.log(` ${chalk9.cyan(fullKey)}: ${formatValue(value)}`);
790
+ }
791
+ }
792
+ }
793
+ function formatValue(value) {
794
+ if (Array.isArray(value)) {
795
+ return chalk9.gray(`[${value.join(", ")}]`);
796
+ }
797
+ if (typeof value === "boolean") {
798
+ return value ? chalk9.green("true") : chalk9.red("false");
799
+ }
800
+ if (typeof value === "number") {
801
+ return chalk9.yellow(String(value));
802
+ }
803
+ return chalk9.white(String(value));
804
+ }
805
+ function getNestedValue(obj, keys) {
806
+ let current = obj;
807
+ for (const key of keys) {
808
+ if (current && typeof current === "object" && key in current) {
809
+ current = current[key];
810
+ } else {
811
+ return void 0;
812
+ }
813
+ }
814
+ return current;
815
+ }
816
+ function setNestedValue(obj, keys, value) {
817
+ let current = obj;
818
+ for (let i = 0; i < keys.length - 1; i++) {
819
+ const key = keys[i];
820
+ if (!(key in current) || typeof current[key] !== "object") {
821
+ current[key] = {};
822
+ }
823
+ current = current[key];
824
+ }
825
+ current[keys[keys.length - 1]] = value;
826
+ }
827
+ function parseValue(value) {
828
+ if (value === "true") return true;
829
+ if (value === "false") return false;
830
+ if (!isNaN(Number(value))) return Number(value);
831
+ return value;
832
+ }
833
+
834
+ // src/commands/analyze.ts
835
+ import { Command as Command10 } from "commander";
836
+ import chalk10 from "chalk";
837
+ import ora9 from "ora";
838
+ import { readFileSync as readFileSync3, readdirSync } from "fs";
839
+ import { join as join4, relative } from "path";
840
+ import {
841
+ RLMEngine,
842
+ createGeminiClient as createGeminiClient3,
843
+ mergeFilesToContext
844
+ } from "@contextos/core";
845
+ function collectFiles(dir, extensions = [".ts", ".js", ".tsx", ".jsx", ".py", ".go", ".rs", ".java"], maxFiles = 100) {
846
+ const files = [];
847
+ function walk(currentDir) {
848
+ if (files.length >= maxFiles) return;
849
+ const entries = readdirSync(currentDir, { withFileTypes: true });
850
+ for (const entry of entries) {
851
+ if (files.length >= maxFiles) break;
852
+ const fullPath = join4(currentDir, entry.name);
853
+ if (entry.isDirectory()) {
854
+ if (["node_modules", ".git", "dist", "build", ".next", "__pycache__", "venv"].includes(entry.name)) {
855
+ continue;
856
+ }
857
+ walk(fullPath);
858
+ } else if (entry.isFile()) {
859
+ const ext = entry.name.substring(entry.name.lastIndexOf("."));
860
+ if (extensions.includes(ext)) {
861
+ try {
862
+ const content = readFileSync3(fullPath, "utf-8");
863
+ files.push({
864
+ path: relative(dir, fullPath),
865
+ content
866
+ });
867
+ } catch {
868
+ }
869
+ }
870
+ }
871
+ }
872
+ }
873
+ walk(dir);
874
+ return files;
875
+ }
876
+ var analyzeCommand = new Command10("analyze").description("RLM-powered deep analysis of your codebase").argument("<goal>", 'Analysis goal or question (e.g., "Find all API endpoints")').option("-d, --depth <number>", "Maximum recursion depth for RLM", "3").option("-b, --budget <tokens>", "Token budget for analysis", "50000").option("-p, --path <path>", "Path to analyze (default: current directory)", ".").option("--max-files <number>", "Maximum files to include", "100").option("--verbose", "Show detailed execution trace").action(async (goal, options) => {
877
+ console.log(chalk10.blue.bold("\n\u{1F50D} RLM Analysis Engine\n"));
878
+ console.log(chalk10.gray("Goal: ") + chalk10.white(goal));
879
+ console.log();
880
+ const spinner = ora9("Collecting codebase files...").start();
881
+ try {
882
+ const files = collectFiles(options.path, void 0, parseInt(options.maxFiles));
883
+ spinner.text = `Found ${files.length} files. Building context...`;
884
+ if (files.length === 0) {
885
+ spinner.fail("No source files found");
886
+ console.log(chalk10.yellow("\nMake sure you are in a project directory with source code.\n"));
887
+ process.exit(1);
888
+ }
889
+ const context = mergeFilesToContext(files);
890
+ const contextSizeKB = (context.length / 1024).toFixed(1);
891
+ spinner.text = `Context: ${contextSizeKB}KB. Initializing RLM engine...`;
892
+ const engine = new RLMEngine({
893
+ maxDepth: parseInt(options.depth),
894
+ maxTokenBudget: parseInt(options.budget),
895
+ backend: "gemini",
896
+ verbose: options.verbose
897
+ });
898
+ const gemini = createGeminiClient3();
899
+ if (!gemini) {
900
+ spinner.fail("Gemini API key not configured");
901
+ console.log(chalk10.yellow("\nSet GEMINI_API_KEY environment variable to use RLM analysis.\n"));
902
+ process.exit(1);
903
+ }
904
+ engine.setModelAdapter({
905
+ name: "gemini",
906
+ maxContextTokens: 2e6,
907
+ async complete(request) {
908
+ try {
909
+ const response = await gemini.request(
910
+ request.userMessage,
911
+ request.systemPrompt
912
+ );
913
+ return {
914
+ content: response,
915
+ tokensUsed: {
916
+ prompt: Math.ceil(request.userMessage.length / 4),
917
+ completion: Math.ceil(response.length / 4),
918
+ total: Math.ceil((request.userMessage.length + response.length) / 4)
919
+ },
920
+ finishReason: "stop"
921
+ };
922
+ } catch (error) {
923
+ return {
924
+ content: "",
925
+ tokensUsed: { prompt: 0, completion: 0, total: 0 },
926
+ finishReason: "error",
927
+ error: error instanceof Error ? error.message : String(error)
928
+ };
929
+ }
930
+ },
931
+ async countTokens(text) {
932
+ return Math.ceil(text.length / 4);
933
+ }
934
+ });
935
+ spinner.text = "Executing RLM analysis...";
936
+ const startTime = Date.now();
937
+ const result = await engine.execute(goal, context);
938
+ const duration = ((Date.now() - startTime) / 1e3).toFixed(1);
939
+ spinner.succeed(`Analysis complete (${duration}s)`);
940
+ console.log();
941
+ console.log(chalk10.blue.bold("\u2500".repeat(60)));
942
+ console.log(chalk10.blue.bold("\u{1F4CA} Analysis Result"));
943
+ console.log(chalk10.blue.bold("\u2500".repeat(60)));
944
+ console.log();
945
+ console.log(result.answer);
946
+ console.log();
947
+ console.log(chalk10.blue.bold("\u2500".repeat(60)));
948
+ console.log();
949
+ console.log(chalk10.gray("Statistics:"));
950
+ console.log(chalk10.white(` \u{1F522} Tokens used: ${chalk10.cyan(result.totalTokens.toLocaleString())}`));
951
+ console.log(chalk10.white(` \u{1F3AF} Confidence: ${chalk10.cyan((result.confidence * 100).toFixed(0) + "%")}`));
952
+ console.log(chalk10.white(` \u{1F4DD} Execution steps: ${chalk10.cyan(result.executionPath.length)}`));
953
+ if (result.truncated) {
954
+ console.log(chalk10.yellow(` \u26A0\uFE0F Truncated: ${result.truncationReason}`));
955
+ }
956
+ if (options.verbose && result.executionPath.length > 0) {
957
+ console.log();
958
+ console.log(chalk10.gray("Execution Trace:"));
959
+ for (const step of result.executionPath.slice(0, 10)) {
960
+ const icon = step.action === "code" ? "\u{1F4BB}" : step.action === "recurse" ? "\u{1F504}" : "\u2705";
961
+ console.log(chalk10.gray(` ${icon} ${step.action}: ${step.input.slice(0, 60)}...`));
962
+ }
963
+ if (result.executionPath.length > 10) {
964
+ console.log(chalk10.gray(` ... and ${result.executionPath.length - 10} more steps`));
965
+ }
966
+ }
967
+ console.log();
968
+ } catch (error) {
969
+ spinner.fail("Analysis failed");
970
+ console.error(chalk10.red("Error:"), error);
971
+ process.exit(1);
972
+ }
973
+ });
974
+
975
+ // src/commands/refactor.ts
976
+ import { Command as Command11 } from "commander";
977
+ import chalk11 from "chalk";
978
+ import ora10 from "ora";
979
+ import {
980
+ getContextBuilder as getContextBuilder7,
981
+ RLMEngine as RLMEngine2,
982
+ createGeminiClient as createGeminiClient4,
983
+ mergeFilesToContext as mergeFilesToContext2
984
+ } from "@contextos/core";
985
+ var refactorCommand = new Command11("refactor").description("RLM-powered safe refactoring with impact analysis").argument("<description>", 'Refactoring description (e.g., "Rename User to Account")').option("-f, --file <path>", "Target file to refactor").option("-d, --depth <number>", "Maximum analysis depth", "2").option("--dry-run", "Preview changes without applying").option("--no-backup", "Skip creating backup files").option("--force", "Apply changes without confirmation").action(async (description, options) => {
986
+ console.log(chalk11.blue.bold("\n\u{1F527} RLM Refactoring Engine\n"));
987
+ console.log(chalk11.gray("Description: ") + chalk11.white(description));
988
+ if (options.file) {
989
+ console.log(chalk11.gray("Target file: ") + chalk11.white(options.file));
990
+ }
991
+ console.log();
992
+ const spinner = ora10("Analyzing codebase...").start();
993
+ try {
994
+ const builder = await getContextBuilder7();
995
+ spinner.text = "Building context...";
996
+ const buildResult = await builder.build({
997
+ maxTokens: 5e4,
998
+ targetFile: options.file
999
+ });
1000
+ const files = buildResult.files.map((f) => ({
1001
+ path: f.path,
1002
+ content: f.content || ""
1003
+ }));
1004
+ const context = mergeFilesToContext2(files);
1005
+ spinner.text = "Initializing RLM engine...";
1006
+ const engine = new RLMEngine2({
1007
+ maxDepth: parseInt(options.depth),
1008
+ maxTokenBudget: 3e4,
1009
+ backend: "gemini"
1010
+ });
1011
+ const gemini = createGeminiClient4();
1012
+ if (!gemini) {
1013
+ spinner.fail("Gemini API key not configured");
1014
+ console.log(chalk11.yellow("\nSet GEMINI_API_KEY environment variable.\n"));
1015
+ process.exit(1);
1016
+ }
1017
+ engine.setModelAdapter({
1018
+ name: "gemini",
1019
+ maxContextTokens: 2e6,
1020
+ async complete(request) {
1021
+ try {
1022
+ const response = await gemini.request(
1023
+ request.userMessage,
1024
+ request.systemPrompt
1025
+ );
1026
+ return {
1027
+ content: response,
1028
+ tokensUsed: {
1029
+ prompt: Math.ceil(request.userMessage.length / 4),
1030
+ completion: Math.ceil(response.length / 4),
1031
+ total: Math.ceil((request.userMessage.length + response.length) / 4)
1032
+ },
1033
+ finishReason: "stop"
1034
+ };
1035
+ } catch (error) {
1036
+ return {
1037
+ content: "",
1038
+ tokensUsed: { prompt: 0, completion: 0, total: 0 },
1039
+ finishReason: "error",
1040
+ error: error instanceof Error ? error.message : String(error)
1041
+ };
1042
+ }
1043
+ },
1044
+ async countTokens(text) {
1045
+ return Math.ceil(text.length / 4);
1046
+ }
1047
+ });
1048
+ spinner.text = "Analyzing refactoring impact...";
1049
+ const analysisGoal = `
1050
+ Analyze the following refactoring request and provide a detailed impact analysis:
1051
+
1052
+ REFACTORING REQUEST: ${description}
1053
+ ${options.file ? `TARGET FILE: ${options.file}` : ""}
1054
+
1055
+ Please analyze:
1056
+ 1. Which files need to be modified
1057
+ 2. What specific changes are needed in each file
1058
+ 3. What are the potential risks
1059
+ 4. What tests should be run after the refactoring
1060
+
1061
+ Respond in JSON format:
1062
+ {
1063
+ "description": "Summary of the refactoring",
1064
+ "files": [
1065
+ {
1066
+ "path": "path/to/file.ts",
1067
+ "action": "modify|create|delete",
1068
+ "changes": "Description of changes",
1069
+ "impact": "low|medium|high"
1070
+ }
1071
+ ],
1072
+ "risks": ["Risk 1", "Risk 2"],
1073
+ "testSuggestions": ["Test 1", "Test 2"]
1074
+ }
1075
+ `.trim();
1076
+ const result = await engine.execute(analysisGoal, context);
1077
+ spinner.succeed("Impact analysis complete");
1078
+ let plan;
1079
+ try {
1080
+ const jsonMatch = result.answer.match(/\{[\s\S]*\}/);
1081
+ if (jsonMatch) {
1082
+ plan = JSON.parse(jsonMatch[0]);
1083
+ } else {
1084
+ throw new Error("No JSON found in response");
1085
+ }
1086
+ } catch {
1087
+ console.log();
1088
+ console.log(chalk11.blue.bold("Analysis Result:"));
1089
+ console.log(result.answer);
1090
+ console.log();
1091
+ process.exit(0);
1092
+ }
1093
+ console.log();
1094
+ console.log(chalk11.blue.bold("\u2500".repeat(60)));
1095
+ console.log(chalk11.blue.bold("\u{1F4CB} Refactoring Plan"));
1096
+ console.log(chalk11.blue.bold("\u2500".repeat(60)));
1097
+ console.log();
1098
+ console.log(chalk11.white(plan.description));
1099
+ console.log();
1100
+ console.log(chalk11.blue.bold("Files to modify:"));
1101
+ for (const file of plan.files) {
1102
+ const impactColor = file.impact === "high" ? chalk11.red : file.impact === "medium" ? chalk11.yellow : chalk11.green;
1103
+ const actionIcon = file.action === "create" ? "\u2795" : file.action === "delete" ? "\u{1F5D1}\uFE0F" : "\u{1F4DD}";
1104
+ console.log(` ${actionIcon} ${chalk11.white(file.path)} ${impactColor(`[${file.impact}]`)}`);
1105
+ console.log(chalk11.gray(` ${file.changes}`));
1106
+ }
1107
+ console.log();
1108
+ if (plan.risks.length > 0) {
1109
+ console.log(chalk11.yellow.bold("\u26A0\uFE0F Potential Risks:"));
1110
+ for (const risk of plan.risks) {
1111
+ console.log(chalk11.yellow(` \u2022 ${risk}`));
1112
+ }
1113
+ console.log();
1114
+ }
1115
+ if (plan.testSuggestions.length > 0) {
1116
+ console.log(chalk11.cyan.bold("\u{1F9EA} Recommended Tests:"));
1117
+ for (const test of plan.testSuggestions) {
1118
+ console.log(chalk11.cyan(` \u2022 ${test}`));
1119
+ }
1120
+ console.log();
1121
+ }
1122
+ if (options.dryRun) {
1123
+ console.log(chalk11.gray("\u2500".repeat(60)));
1124
+ console.log(chalk11.yellow("\n\u{1F4CB} Dry run mode - no changes applied.\n"));
1125
+ console.log(chalk11.gray("Remove --dry-run flag to apply changes.\n"));
1126
+ process.exit(0);
1127
+ }
1128
+ if (!options.force) {
1129
+ console.log(chalk11.gray("\u2500".repeat(60)));
1130
+ console.log(chalk11.yellow("\n\u26A0\uFE0F This is a preview. Use --force to apply changes."));
1131
+ console.log(chalk11.gray("Or use --dry-run for analysis-only mode.\n"));
1132
+ process.exit(0);
1133
+ }
1134
+ console.log(chalk11.green("\n\u2705 Refactoring plan generated successfully.\n"));
1135
+ console.log(chalk11.gray("Note: Automatic code modification coming in a future release."));
1136
+ console.log(chalk11.gray("For now, use this analysis to manually apply the changes.\n"));
1137
+ } catch (error) {
1138
+ spinner.fail("Refactoring analysis failed");
1139
+ if (error instanceof Error && error.message.includes("not initialized")) {
1140
+ console.log(chalk11.yellow('\nRun "ctx init" first to initialize ContextOS.\n'));
1141
+ } else {
1142
+ console.error(chalk11.red("Error:"), error);
1143
+ }
1144
+ process.exit(1);
1145
+ }
1146
+ });
1147
+
1148
+ // src/commands/explain.ts
1149
+ import { Command as Command12 } from "commander";
1150
+ import chalk12 from "chalk";
1151
+ import ora11 from "ora";
1152
+ import { readFileSync as readFileSync4, existsSync as existsSync2, statSync as statSync2 } from "fs";
1153
+ import { join as join5, basename } from "path";
1154
+ import {
1155
+ RLMEngine as RLMEngine3,
1156
+ createGeminiClient as createGeminiClient5,
1157
+ mergeFilesToContext as mergeFilesToContext3
1158
+ } from "@contextos/core";
1159
+ var explainCommand = new Command12("explain").description("Get AI-powered explanation of code").argument("<target>", "File path, function name, or module to explain").option("-d, --depth <number>", "Include dependencies up to depth", "1").option("-f, --format <format>", "Output format: text, markdown, json", "markdown").option("--no-examples", "Skip usage examples in explanation").option("--technical", "Include technical implementation details").action(async (target, options) => {
1160
+ console.log(chalk12.blue.bold("\n\u{1F4DA} ContextOS Code Explainer\n"));
1161
+ const spinner = ora11("Analyzing target...").start();
1162
+ try {
1163
+ let targetContent = "";
1164
+ let targetPath = "";
1165
+ let contextFiles = [];
1166
+ const possiblePath = join5(process.cwd(), target);
1167
+ if (existsSync2(possiblePath) && statSync2(possiblePath).isFile()) {
1168
+ targetPath = target;
1169
+ targetContent = readFileSync4(possiblePath, "utf-8");
1170
+ contextFiles.push({ path: target, content: targetContent });
1171
+ spinner.text = `Found file: ${target}`;
1172
+ } else if (existsSync2(target) && statSync2(target).isFile()) {
1173
+ targetPath = target;
1174
+ targetContent = readFileSync4(target, "utf-8");
1175
+ contextFiles.push({ path: basename(target), content: targetContent });
1176
+ spinner.text = `Found file: ${target}`;
1177
+ } else {
1178
+ spinner.text = `Searching for "${target}"...`;
1179
+ const searchDirs = ["src", "lib", "app", "."];
1180
+ const _found = false;
1181
+ for (const dir of searchDirs) {
1182
+ const searchPath = join5(process.cwd(), dir);
1183
+ if (!existsSync2(searchPath)) continue;
1184
+ spinner.fail(`Symbol "${target}" not found as a file.`);
1185
+ console.log(chalk12.yellow(`
1186
+ Tip: Provide a file path instead, e.g.:`));
1187
+ console.log(chalk12.gray(` ctx explain src/auth/service.ts`));
1188
+ console.log(chalk12.gray(` ctx explain ./utils/helpers.js`));
1189
+ process.exit(1);
1190
+ }
1191
+ }
1192
+ spinner.text = "Initializing RLM engine...";
1193
+ const engine = new RLMEngine3({
1194
+ maxDepth: parseInt(options.depth),
1195
+ maxTokenBudget: 2e4,
1196
+ backend: "gemini"
1197
+ });
1198
+ const gemini = createGeminiClient5();
1199
+ if (!gemini) {
1200
+ spinner.fail("Gemini API key not configured");
1201
+ console.log(chalk12.yellow("\nSet GEMINI_API_KEY environment variable.\n"));
1202
+ process.exit(1);
1203
+ }
1204
+ engine.setModelAdapter({
1205
+ name: "gemini",
1206
+ maxContextTokens: 2e6,
1207
+ async complete(request) {
1208
+ try {
1209
+ const response = await gemini.request(
1210
+ request.userMessage,
1211
+ request.systemPrompt
1212
+ );
1213
+ return {
1214
+ content: response,
1215
+ tokensUsed: {
1216
+ prompt: Math.ceil(request.userMessage.length / 4),
1217
+ completion: Math.ceil(response.length / 4),
1218
+ total: Math.ceil((request.userMessage.length + response.length) / 4)
1219
+ },
1220
+ finishReason: "stop"
1221
+ };
1222
+ } catch (error) {
1223
+ return {
1224
+ content: "",
1225
+ tokensUsed: { prompt: 0, completion: 0, total: 0 },
1226
+ finishReason: "error",
1227
+ error: error instanceof Error ? error.message : String(error)
1228
+ };
1229
+ }
1230
+ },
1231
+ async countTokens(text) {
1232
+ return Math.ceil(text.length / 4);
1233
+ }
1234
+ });
1235
+ spinner.text = "Generating explanation...";
1236
+ const technicalNote = options.technical ? "Include implementation details, algorithms used, and performance considerations." : "";
1237
+ const examplesNote = options.examples ? "Include practical usage examples." : "";
1238
+ const explainGoal = `
1239
+ Explain the following code in a clear, educational manner.
1240
+
1241
+ FILE: ${targetPath}
1242
+
1243
+ Please provide:
1244
+ 1. **Overview**: What this code does at a high level
1245
+ 2. **Key Components**: Main functions, classes, or exports
1246
+ 3. **How It Works**: Step-by-step explanation of the logic
1247
+ 4. **Dependencies**: What this code imports/relies on
1248
+ ${examplesNote ? "5. **Usage Examples**: How to use this code" : ""}
1249
+ ${technicalNote ? "6. **Technical Details**: Implementation specifics" : ""}
1250
+
1251
+ Format the response in ${options.format === "json" ? "JSON" : "Markdown"}.
1252
+ `.trim();
1253
+ const context = mergeFilesToContext3(contextFiles);
1254
+ const result = await engine.execute(explainGoal, context);
1255
+ spinner.succeed("Explanation generated");
1256
+ console.log();
1257
+ console.log(chalk12.blue.bold("\u2500".repeat(60)));
1258
+ console.log(chalk12.blue.bold(`\u{1F4D6} Explanation: ${targetPath}`));
1259
+ console.log(chalk12.blue.bold("\u2500".repeat(60)));
1260
+ console.log();
1261
+ if (options.format === "json") {
1262
+ try {
1263
+ const jsonMatch = result.answer.match(/\{[\s\S]*\}/);
1264
+ if (jsonMatch) {
1265
+ console.log(JSON.stringify(JSON.parse(jsonMatch[0]), null, 2));
1266
+ } else {
1267
+ console.log(result.answer);
1268
+ }
1269
+ } catch {
1270
+ console.log(result.answer);
1271
+ }
1272
+ } else {
1273
+ console.log(result.answer);
1274
+ }
1275
+ console.log();
1276
+ console.log(chalk12.blue.bold("\u2500".repeat(60)));
1277
+ console.log(chalk12.gray(`Tokens used: ${result.totalTokens} | Confidence: ${(result.confidence * 100).toFixed(0)}%`));
1278
+ console.log();
1279
+ } catch (error) {
1280
+ spinner.fail("Explanation failed");
1281
+ console.error(chalk12.red("Error:"), error);
1282
+ process.exit(1);
1283
+ }
1284
+ });
1285
+
1286
+ // src/commands/trace.ts
1287
+ import { Command as Command13 } from "commander";
1288
+ import chalk13 from "chalk";
1289
+ import ora12 from "ora";
1290
+ import {
1291
+ getContextBuilder as getContextBuilder8
1292
+ } from "@contextos/core";
1293
+ var traceCommand = new Command13("trace").description("Trace function call chains and dependencies").argument("<symbol>", "Function or class name to trace").option("-d, --direction <dir>", "Trace direction: callers, callees, both", "both").option("--depth <number>", "Maximum trace depth", "3").option("--json", "Output as JSON").action(async (symbol, options) => {
1294
+ console.log(chalk13.blue.bold("\n\u{1F517} Call Chain Tracer\n"));
1295
+ console.log(chalk13.gray(`Tracing: ${symbol}`));
1296
+ console.log(chalk13.gray(`Direction: ${options.direction}`));
1297
+ console.log();
1298
+ const spinner = ora12("Analyzing dependency graph...").start();
1299
+ try {
1300
+ const _builder = await getContextBuilder8();
1301
+ spinner.text = "Building call chain...";
1302
+ spinner.succeed("Trace complete");
1303
+ console.log();
1304
+ console.log(chalk13.blue.bold("\u2500".repeat(60)));
1305
+ console.log(chalk13.blue.bold(`\u{1F4CA} Call Chain for: ${symbol}`));
1306
+ console.log(chalk13.blue.bold("\u2500".repeat(60)));
1307
+ console.log();
1308
+ if (options.direction === "callers" || options.direction === "both") {
1309
+ console.log(chalk13.green.bold("\u2B06\uFE0F Called by (callers):"));
1310
+ console.log(chalk13.gray(" \u2514\u2500\u2500 Use ctx analyze to find callers"));
1311
+ console.log();
1312
+ }
1313
+ if (options.direction === "callees" || options.direction === "both") {
1314
+ console.log(chalk13.yellow.bold("\u2B07\uFE0F Calls (callees):"));
1315
+ console.log(chalk13.gray(" \u2514\u2500\u2500 Use ctx analyze to find dependencies"));
1316
+ console.log();
1317
+ }
1318
+ console.log(chalk13.blue.bold("\u2500".repeat(60)));
1319
+ console.log();
1320
+ console.log(chalk13.gray("Tip: For detailed analysis, use:"));
1321
+ console.log(chalk13.cyan(` ctx analyze "Find all functions that call ${symbol}"`));
1322
+ console.log();
1323
+ } catch (error) {
1324
+ spinner.fail("Trace failed");
1325
+ if (error instanceof Error && error.message.includes("not initialized")) {
1326
+ console.log(chalk13.yellow('\nRun "ctx init" first to initialize ContextOS.\n'));
1327
+ } else {
1328
+ console.error(chalk13.red("Error:"), error);
1329
+ }
1330
+ process.exit(1);
1331
+ }
1332
+ });
1333
+
1334
+ // src/index.ts
1335
+ var program = new Command14();
1336
+ program.name("ctx").description("ContextOS - The Context Server Protocol for AI Coding").version("0.1.0").option("-v, --verbose", "Enable verbose output");
1337
+ program.addCommand(initCommand);
1338
+ program.addCommand(indexCommand);
1339
+ program.addCommand(buildCommand);
1340
+ program.addCommand(goalCommand);
1341
+ program.addCommand(previewCommand);
1342
+ program.addCommand(copyCommand);
1343
+ program.addCommand(doctorCommand);
1344
+ program.addCommand(configCommand);
1345
+ program.addCommand(suggestRulesCommand);
1346
+ program.addCommand(analyzeCommand);
1347
+ program.addCommand(refactorCommand);
1348
+ program.addCommand(explainCommand);
1349
+ program.addCommand(traceCommand);
1350
+ program.parse();