@korl3one/ccode 2.3.0 → 3.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.
@@ -0,0 +1,303 @@
1
+ import * as path from 'path';
2
+ import { FileUtils } from '../utils/files.js';
3
+ import { TaskEngine } from './tasks.js';
4
+ /**
5
+ * ContextExporter: genera archivos de contexto para cada herramienta de IA.
6
+ *
7
+ * Lee la fuente de verdad (.ccode/) y produce exports específicos:
8
+ * - AGENTS.md → Estándar abierto (Linux Foundation)
9
+ * - CLAUDE.md → Claude Code
10
+ * - GEMINI.md → Gemini CLI
11
+ * - .cursorrules → Cursor
12
+ * - copilot-instructions.md → GitHub Copilot
13
+ *
14
+ * Cada export se adapta al formato y expectativas de la herramienta destino.
15
+ */
16
+ export class ContextExporter {
17
+ ccodePath;
18
+ projectRoot;
19
+ // Formatos soportados y sus rutas relativas al root del proyecto
20
+ static FORMATS = {
21
+ agents: { file: 'AGENTS.md', label: 'AGENTS.md (Open Standard)' },
22
+ claude: { file: 'CLAUDE.md', label: 'CLAUDE.md (Claude Code)' },
23
+ gemini: { file: 'GEMINI.md', label: 'GEMINI.md (Gemini CLI)' },
24
+ cursor: { file: '.cursorrules', label: '.cursorrules (Cursor)' },
25
+ copilot: { file: '.github/copilot-instructions.md', label: 'copilot-instructions.md (GitHub Copilot)' },
26
+ };
27
+ constructor(projectRoot) {
28
+ this.projectRoot = projectRoot || process.cwd();
29
+ this.ccodePath = path.join(this.projectRoot, '.ccode');
30
+ }
31
+ /**
32
+ * Lee todos los archivos de contexto de .ccode/
33
+ */
34
+ async readSources() {
35
+ const project = await FileUtils.readFileSafe(path.join(this.ccodePath, 'project.md'));
36
+ const architecture = await FileUtils.readFileSafe(path.join(this.ccodePath, 'architecture.md'));
37
+ const rules = await FileUtils.readFileSafe(path.join(this.ccodePath, 'rules.md'));
38
+ const memory = await FileUtils.readFileSafe(path.join(this.ccodePath, 'memory.md'));
39
+ const taskEngine = new TaskEngine();
40
+ await taskEngine.load();
41
+ const tasks = taskEngine.getTasks();
42
+ const stats = taskEngine.getStats();
43
+ let projectName = 'Proyecto';
44
+ try {
45
+ const ctx = await FileUtils.readJson(path.join(this.ccodePath, 'context.json'));
46
+ if (ctx.name)
47
+ projectName = ctx.name;
48
+ }
49
+ catch { /* ignore */ }
50
+ return { project, architecture, rules, memory, tasks, stats, projectName };
51
+ }
52
+ /**
53
+ * Formatea las tareas como lista legible.
54
+ */
55
+ formatTasks(tasks, style) {
56
+ if (tasks.length === 0)
57
+ return 'No hay tareas definidas.';
58
+ return tasks.map(t => {
59
+ if (style === 'checkbox') {
60
+ const check = t.status === 'completed' ? 'x' : ' ';
61
+ return `- [${check}] **${t.id}** ${t.title}`;
62
+ }
63
+ const icon = t.status === 'completed' ? 'DONE' :
64
+ t.status === 'in_progress' ? 'IN PROGRESS' :
65
+ t.status === 'failed' ? 'FAILED' : 'PENDING';
66
+ return `- [${icon}] ${t.id}: ${t.title}`;
67
+ }).join('\n');
68
+ }
69
+ /**
70
+ * Genera AGENTS.md — Estándar abierto compatible con múltiples herramientas.
71
+ * Sigue la convención de AGENTS.md (Linux Foundation / AAIF).
72
+ */
73
+ async generateAgentsMd() {
74
+ const { project, architecture, rules, tasks, stats, projectName } = await this.readSources();
75
+ return `# ${projectName}
76
+
77
+ ${project}
78
+
79
+ ## Architecture
80
+
81
+ ${architecture}
82
+
83
+ ## Development Guidelines
84
+
85
+ ${rules}
86
+
87
+ ## Current Status
88
+
89
+ Progress: ${stats.completed}/${stats.total} tasks completed
90
+
91
+ ${this.formatTasks(tasks, 'checkbox')}
92
+
93
+ ## Notes for AI Agents
94
+
95
+ - This project uses CCODE for context management
96
+ - All context files live in \`.ccode/\` — that is the source of truth
97
+ - Do not modify \`.ccode/\` files directly; use \`ccode update\` instead
98
+ - Follow the development guidelines above when making changes
99
+ - Check task status before starting new work
100
+ `;
101
+ }
102
+ /**
103
+ * Genera CLAUDE.md — Optimizado para Claude Code.
104
+ * Claude Code lee este archivo automáticamente del root del proyecto.
105
+ */
106
+ async generateClaudeMd() {
107
+ const { project, architecture, rules, tasks, stats, memory, projectName } = await this.readSources();
108
+ const activeTasks = tasks.filter(t => t.status !== 'completed');
109
+ const activeSection = activeTasks.length > 0
110
+ ? activeTasks.map(t => {
111
+ const status = t.status === 'in_progress' ? '(in progress)' : '';
112
+ return `- ${t.id}: ${t.title} ${status}\n Criteria: ${t.description}`;
113
+ }).join('\n')
114
+ : 'All tasks completed.';
115
+ return `# ${projectName}
116
+
117
+ ${project}
118
+
119
+ ## Architecture
120
+
121
+ ${architecture}
122
+
123
+ ## Rules
124
+
125
+ ${rules}
126
+
127
+ ## Progress (${stats.completed}/${stats.total})
128
+
129
+ ${this.formatTasks(tasks, 'checkbox')}
130
+
131
+ ## Pending Work
132
+
133
+ ${activeSection}
134
+
135
+ ## Decision History
136
+
137
+ ${memory}
138
+ `;
139
+ }
140
+ /**
141
+ * Genera GEMINI.md — Para Gemini CLI.
142
+ */
143
+ async generateGeminiMd() {
144
+ const { project, architecture, rules, tasks, stats, projectName } = await this.readSources();
145
+ return `# ${projectName}
146
+
147
+ ${project}
148
+
149
+ ## Architecture
150
+
151
+ ${architecture}
152
+
153
+ ## Guidelines
154
+
155
+ ${rules}
156
+
157
+ ## Task Progress (${stats.completed}/${stats.total})
158
+
159
+ ${this.formatTasks(tasks, 'checkbox')}
160
+ `;
161
+ }
162
+ /**
163
+ * Genera .cursorrules — Instrucciones para Cursor IDE.
164
+ * Cursor lee este archivo del root del proyecto automáticamente.
165
+ */
166
+ async generateCursorRules() {
167
+ const { project, architecture, rules, tasks, stats, projectName } = await this.readSources();
168
+ // Cursor rules tiende a ser más conciso y directivo
169
+ const pendingTasks = tasks.filter(t => t.status !== 'completed');
170
+ const pendingSection = pendingTasks.length > 0
171
+ ? pendingTasks.map(t => `- ${t.title}`).join('\n')
172
+ : 'All tasks completed.';
173
+ return `# ${projectName}
174
+
175
+ ${project}
176
+
177
+ ## Architecture
178
+
179
+ ${architecture}
180
+
181
+ ## Rules
182
+
183
+ ${rules}
184
+
185
+ ## Current Tasks (${stats.completed}/${stats.total} done)
186
+
187
+ ${pendingSection}
188
+ `;
189
+ }
190
+ /**
191
+ * Genera copilot-instructions.md — Para GitHub Copilot.
192
+ * Se ubica en .github/copilot-instructions.md
193
+ */
194
+ async generateCopilotInstructions() {
195
+ const { project, architecture, rules, projectName } = await this.readSources();
196
+ // Copilot instructions es más enfocado en reglas de código
197
+ return `# ${projectName}
198
+
199
+ ${project}
200
+
201
+ ## Architecture
202
+
203
+ ${architecture}
204
+
205
+ ## Coding Guidelines
206
+
207
+ ${rules}
208
+ `;
209
+ }
210
+ /**
211
+ * Genera el export universal (.ccode/context-export.md).
212
+ */
213
+ async generateUniversalExport() {
214
+ const { project, architecture, rules, memory, tasks, stats } = await this.readSources();
215
+ const taskList = tasks.map(t => {
216
+ const icon = t.status === 'completed' ? '[DONE]' :
217
+ t.status === 'in_progress' ? '[IN PROGRESS]' : '[PENDING]';
218
+ return `${icon} ${t.id} [${t.priority.toUpperCase()}] ${t.title}`;
219
+ }).join('\n');
220
+ return `# CCODE — Project Context Export
221
+
222
+ ## Project
223
+
224
+ ${project}
225
+
226
+ ## Architecture
227
+
228
+ ${architecture}
229
+
230
+ ## Development Rules
231
+
232
+ ${rules}
233
+
234
+ ## Task Progress (${stats.completed}/${stats.total} completed)
235
+
236
+ ${taskList}
237
+
238
+ ## Decision History
239
+
240
+ ${memory}
241
+ `;
242
+ }
243
+ /**
244
+ * Genera y escribe un formato específico.
245
+ */
246
+ async exportFormat(format) {
247
+ const info = ContextExporter.FORMATS[format];
248
+ if (!info)
249
+ throw new Error(`Formato desconocido: ${format}`);
250
+ let content;
251
+ switch (format) {
252
+ case 'agents':
253
+ content = await this.generateAgentsMd();
254
+ break;
255
+ case 'claude':
256
+ content = await this.generateClaudeMd();
257
+ break;
258
+ case 'gemini':
259
+ content = await this.generateGeminiMd();
260
+ break;
261
+ case 'cursor':
262
+ content = await this.generateCursorRules();
263
+ break;
264
+ case 'copilot':
265
+ content = await this.generateCopilotInstructions();
266
+ break;
267
+ default: throw new Error(`Formato desconocido: ${format}`);
268
+ }
269
+ const filePath = path.join(this.projectRoot, info.file);
270
+ // Asegurar que el directorio padre exista (para .github/)
271
+ await FileUtils.ensureDir(path.dirname(filePath));
272
+ await FileUtils.writeFile(filePath, content);
273
+ return filePath;
274
+ }
275
+ /**
276
+ * Exporta todos los formatos de una vez.
277
+ * Retorna la lista de archivos generados.
278
+ */
279
+ async exportAll() {
280
+ const results = [];
281
+ for (const [format, info] of Object.entries(ContextExporter.FORMATS)) {
282
+ await this.exportFormat(format);
283
+ results.push({ format, file: info.file, label: info.label });
284
+ }
285
+ // También generar el export universal
286
+ const universalContent = await this.generateUniversalExport();
287
+ await FileUtils.writeFile(path.join(this.ccodePath, 'context-export.md'), universalContent);
288
+ results.push({ format: 'universal', file: '.ccode/context-export.md', label: 'Export Universal' });
289
+ return results;
290
+ }
291
+ /**
292
+ * Verifica qué formatos ya existen en el proyecto.
293
+ */
294
+ async detectExisting() {
295
+ const existing = [];
296
+ for (const [format, info] of Object.entries(ContextExporter.FORMATS)) {
297
+ if (await FileUtils.exists(path.join(this.projectRoot, info.file))) {
298
+ existing.push(format);
299
+ }
300
+ }
301
+ return existing;
302
+ }
303
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@korl3one/ccode",
3
- "version": "2.3.0",
4
- "description": "CLI de contexto persistente para desarrollo asistido por IA. Genera documentacion, arquitectura, reglas y tareas verificables.",
3
+ "version": "3.1.0",
4
+ "description": "Persistent context CLI for AI-assisted development. Generates documentation, architecture, rules, and tasks — synced to Claude Code, Gemini CLI, Cursor, Copilot, and AGENTS.md.",
5
5
  "type": "module",
6
6
  "main": "dist/cli/index.js",
7
7
  "bin": {
@@ -19,6 +19,7 @@
19
19
  "ai",
20
20
  "cli",
21
21
  "context",
22
+ "context-engineering",
22
23
  "development",
23
24
  "assistant",
24
25
  "typescript",
@@ -27,9 +28,13 @@
27
28
  "architecture",
28
29
  "tasks",
29
30
  "claude",
30
- "ollama",
31
+ "gemini",
32
+ "cursor",
33
+ "copilot",
34
+ "agents-md",
31
35
  "llm",
32
- "persistent-context"
36
+ "persistent-context",
37
+ "context-sync"
33
38
  ],
34
39
  "author": "Johannes Moreno <korl3one>",
35
40
  "license": "ISC",
@@ -1,11 +0,0 @@
1
- import { IAIProvider, IAIConfig } from './provider.js';
2
- /**
3
- * Adaptador para DeepSeek.
4
- * Usa formato compatible con OpenAI.
5
- */
6
- export declare class DeepSeekAdapter implements IAIProvider {
7
- private config;
8
- constructor(config: IAIConfig);
9
- getName(): string;
10
- generate(prompt: string): Promise<string>;
11
- }
@@ -1,35 +0,0 @@
1
- import axios from 'axios';
2
- /**
3
- * Adaptador para DeepSeek.
4
- * Usa formato compatible con OpenAI.
5
- */
6
- export class DeepSeekAdapter {
7
- config;
8
- constructor(config) {
9
- this.config = {
10
- apiKey: config.apiKey || '',
11
- model: config.model || 'deepseek-chat',
12
- baseUrl: config.baseUrl || 'https://api.deepseek.com/v1/chat/completions',
13
- };
14
- }
15
- getName() {
16
- return `DeepSeek (${this.config.model})`;
17
- }
18
- async generate(prompt) {
19
- if (!this.config.apiKey) {
20
- throw new Error('API Key de DeepSeek no configurada.');
21
- }
22
- const response = await axios.post(this.config.baseUrl, {
23
- model: this.config.model,
24
- max_tokens: 8096,
25
- messages: [{ role: 'user', content: prompt }],
26
- }, {
27
- headers: {
28
- 'Authorization': `Bearer ${this.config.apiKey}`,
29
- 'Content-Type': 'application/json',
30
- },
31
- timeout: 120000,
32
- });
33
- return response.data.choices[0].message.content;
34
- }
35
- }
package/dist/ai/groq.d.ts DELETED
@@ -1,11 +0,0 @@
1
- import { IAIProvider, IAIConfig } from './provider.js';
2
- /**
3
- * Adaptador para Groq (inferencia ultra-rápida).
4
- * Usa formato compatible con OpenAI.
5
- */
6
- export declare class GroqAdapter implements IAIProvider {
7
- private config;
8
- constructor(config: IAIConfig);
9
- getName(): string;
10
- generate(prompt: string): Promise<string>;
11
- }
package/dist/ai/groq.js DELETED
@@ -1,35 +0,0 @@
1
- import axios from 'axios';
2
- /**
3
- * Adaptador para Groq (inferencia ultra-rápida).
4
- * Usa formato compatible con OpenAI.
5
- */
6
- export class GroqAdapter {
7
- config;
8
- constructor(config) {
9
- this.config = {
10
- apiKey: config.apiKey || '',
11
- model: config.model || 'llama-3.3-70b-versatile',
12
- baseUrl: config.baseUrl || 'https://api.groq.com/openai/v1/chat/completions',
13
- };
14
- }
15
- getName() {
16
- return `Groq (${this.config.model})`;
17
- }
18
- async generate(prompt) {
19
- if (!this.config.apiKey) {
20
- throw new Error('API Key de Groq no configurada.');
21
- }
22
- const response = await axios.post(this.config.baseUrl, {
23
- model: this.config.model,
24
- max_tokens: 8096,
25
- messages: [{ role: 'user', content: prompt }],
26
- }, {
27
- headers: {
28
- 'Authorization': `Bearer ${this.config.apiKey}`,
29
- 'Content-Type': 'application/json',
30
- },
31
- timeout: 60000,
32
- });
33
- return response.data.choices[0].message.content;
34
- }
35
- }
@@ -1,10 +0,0 @@
1
- import { IAIProvider, IAIConfig } from './provider.js';
2
- /**
3
- * Adaptador para Ollama (local).
4
- */
5
- export declare class OllamaAdapter implements IAIProvider {
6
- private config;
7
- constructor(config: IAIConfig);
8
- getName(): string;
9
- generate(prompt: string): Promise<string>;
10
- }
package/dist/ai/ollama.js DELETED
@@ -1,27 +0,0 @@
1
- import axios from 'axios';
2
- /**
3
- * Adaptador para Ollama (local).
4
- */
5
- export class OllamaAdapter {
6
- config;
7
- constructor(config) {
8
- this.config = {
9
- model: config.model || 'llama3',
10
- baseUrl: config.baseUrl || 'http://localhost:11434/api/generate',
11
- };
12
- }
13
- getName() {
14
- return `Ollama (${this.config.model})`;
15
- }
16
- async generate(prompt) {
17
- const response = await axios.post(this.config.baseUrl, {
18
- model: this.config.model,
19
- prompt,
20
- stream: false,
21
- }, {
22
- headers: { 'Content-Type': 'application/json' },
23
- timeout: 120000,
24
- });
25
- return response.data.response;
26
- }
27
- }
@@ -1,10 +0,0 @@
1
- import { IAIProvider, IAIConfig } from './provider.js';
2
- /**
3
- * Adaptador para OpenAI (ChatGPT).
4
- */
5
- export declare class OpenAIAdapter implements IAIProvider {
6
- private config;
7
- constructor(config: IAIConfig);
8
- getName(): string;
9
- generate(prompt: string): Promise<string>;
10
- }
package/dist/ai/openai.js DELETED
@@ -1,34 +0,0 @@
1
- import axios from 'axios';
2
- /**
3
- * Adaptador para OpenAI (ChatGPT).
4
- */
5
- export class OpenAIAdapter {
6
- config;
7
- constructor(config) {
8
- this.config = {
9
- apiKey: config.apiKey || '',
10
- model: config.model || 'gpt-4o',
11
- baseUrl: config.baseUrl || 'https://api.openai.com/v1/chat/completions',
12
- };
13
- }
14
- getName() {
15
- return `OpenAI (${this.config.model})`;
16
- }
17
- async generate(prompt) {
18
- if (!this.config.apiKey) {
19
- throw new Error('API Key de OpenAI no configurada.');
20
- }
21
- const response = await axios.post(this.config.baseUrl, {
22
- model: this.config.model,
23
- max_tokens: 8096,
24
- messages: [{ role: 'user', content: prompt }],
25
- }, {
26
- headers: {
27
- 'Authorization': `Bearer ${this.config.apiKey}`,
28
- 'Content-Type': 'application/json',
29
- },
30
- timeout: 120000,
31
- });
32
- return response.data.choices[0].message.content;
33
- }
34
- }