@codexa/cli 8.6.0 → 8.6.9

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.
@@ -1,626 +1,626 @@
1
- import { getDb } from "../db/connection";
2
- import { initSchema } from "../db/schema";
3
- import { existsSync, readFileSync, writeFileSync } from "fs";
4
- import { join } from "path";
5
-
6
- interface ProductContext {
7
- name: string;
8
- problem: string;
9
- solution?: string;
10
- targetUsers?: string;
11
- valueProposition?: string;
12
- successMetrics?: string[];
13
- outOfScope?: string[];
14
- constraints?: string[];
15
- }
16
-
17
- interface ProductGoal {
18
- category: string;
19
- goal: string;
20
- priority: "high" | "medium" | "low";
21
- }
22
-
23
- interface ProductFeature {
24
- name: string;
25
- description?: string;
26
- priority: "high" | "medium" | "low";
27
- }
28
-
29
- // ═══════════════════════════════════════════════════════════════
30
- // PRODUCT GUIDE - Inicia o processo guiado
31
- // ═══════════════════════════════════════════════════════════════
32
-
33
- export function productGuide(json: boolean = false): void {
34
- initSchema();
35
- const db = getDb();
36
-
37
- // Verificar se ja existe
38
- const existing = db.query("SELECT * FROM product_context WHERE id = 'default'").get();
39
- if (existing) {
40
- console.log("\nContexto de produto ja definido.");
41
- console.log("Use: product show para ver detalhes");
42
- console.log("Ou: product reset para refazer\n");
43
- return;
44
- }
45
-
46
- if (json) {
47
- console.log(JSON.stringify({ status: "ready_for_guide", message: "Use AskUserQuestion para guiar o usuario" }, null, 2));
48
- return;
49
- }
50
-
51
- console.log("\n" + "═".repeat(60));
52
- console.log("PRODUCT DISCOVERY - Modo Guiado");
53
- console.log("═".repeat(60));
54
- console.log(`
55
- Este modo vai ajudar a definir o contexto do produto atraves de perguntas.
56
-
57
- O agente vai usar AskUserQuestion para coletar:
58
- 1. Nome do produto
59
- 2. Problema que resolve
60
- 3. Solucao proposta
61
- 4. Usuarios alvo
62
- 5. Proposta de valor
63
- 6. Metricas de sucesso
64
- 7. O que esta fora do escopo
65
- 8. Restricoes conhecidas
66
-
67
- Apos coletar as respostas, use:
68
- product set --name "..." --problem "..." ...
69
-
70
- Para confirmar e salvar:
71
- product confirm
72
- `);
73
- console.log("─".repeat(60) + "\n");
74
- }
75
-
76
- // ═══════════════════════════════════════════════════════════════
77
- // PRODUCT IMPORT - Importa PRD existente
78
- // ═══════════════════════════════════════════════════════════════
79
-
80
- export function productImport(options: { file?: string; content?: string }): void {
81
- initSchema();
82
- const db = getDb();
83
-
84
- // Verificar se ja existe
85
- const existing = db.query("SELECT * FROM product_context WHERE id = 'default'").get();
86
- if (existing) {
87
- console.log("\nContexto de produto ja definido.");
88
- console.log("Use: product reset para refazer\n");
89
- return;
90
- }
91
-
92
- let prdContent = "";
93
-
94
- if (options.file) {
95
- if (!existsSync(options.file)) {
96
- console.error(`\nArquivo nao encontrado: ${options.file}\n`);
97
- process.exit(1);
98
- }
99
- prdContent = readFileSync(options.file, "utf-8");
100
- } else if (options.content) {
101
- prdContent = options.content;
102
- } else {
103
- console.error("\nForneca --file ou --content\n");
104
- process.exit(1);
105
- }
106
-
107
- // Salvar como pendente para o agente processar
108
- const now = new Date().toISOString();
109
- db.run(
110
- `INSERT INTO product_context (id, name, problem, source, discovered_at, updated_at)
111
- VALUES ('pending', 'Pendente - PRD Importado', ?, 'import', ?, ?)`,
112
- [prdContent, now, now]
113
- );
114
-
115
- console.log("\n" + "═".repeat(60));
116
- console.log("PRD IMPORTADO");
117
- console.log("═".repeat(60));
118
- console.log(`
119
- Conteudo do PRD foi salvo para processamento.
120
-
121
- O agente deve agora:
122
- 1. Analisar o PRD importado
123
- 2. Extrair informacoes estruturadas
124
- 3. Usar 'product set' para definir os campos
125
- 4. Usar 'product confirm' para finalizar
126
-
127
- Comando para ver o PRD:
128
- product show --pending
129
- `);
130
- console.log("─".repeat(60) + "\n");
131
- }
132
-
133
- // ═══════════════════════════════════════════════════════════════
134
- // PRODUCT SET - Define campos do produto
135
- // ═══════════════════════════════════════════════════════════════
136
-
137
- export function productSet(options: {
138
- name?: string;
139
- problem?: string;
140
- solution?: string;
141
- targetUsers?: string;
142
- valueProposition?: string;
143
- successMetrics?: string;
144
- outOfScope?: string;
145
- constraints?: string;
146
- }): void {
147
- initSchema();
148
- const db = getDb();
149
-
150
- // Buscar pendente ou criar novo
151
- let pending = db.query("SELECT * FROM product_context WHERE id = 'pending'").get() as any;
152
- const now = new Date().toISOString();
153
-
154
- if (!pending) {
155
- // Criar novo registro pendente
156
- db.run(
157
- `INSERT INTO product_context (id, name, problem, source, discovered_at, updated_at)
158
- VALUES ('pending', 'Pendente', '', 'guide', ?, ?)`,
159
- [now, now]
160
- );
161
- pending = { name: "Pendente", problem: "" };
162
- }
163
-
164
- // Atualizar campos fornecidos
165
- const updates: string[] = [];
166
- const values: any[] = [];
167
-
168
- if (options.name) {
169
- updates.push("name = ?");
170
- values.push(options.name);
171
- }
172
- if (options.problem) {
173
- updates.push("problem = ?");
174
- values.push(options.problem);
175
- }
176
- if (options.solution) {
177
- updates.push("solution = ?");
178
- values.push(options.solution);
179
- }
180
- if (options.targetUsers) {
181
- updates.push("target_users = ?");
182
- values.push(options.targetUsers);
183
- }
184
- if (options.valueProposition) {
185
- updates.push("value_proposition = ?");
186
- values.push(options.valueProposition);
187
- }
188
- if (options.successMetrics) {
189
- updates.push("success_metrics = ?");
190
- values.push(JSON.stringify(options.successMetrics.split(",")));
191
- }
192
- if (options.outOfScope) {
193
- updates.push("out_of_scope = ?");
194
- values.push(JSON.stringify(options.outOfScope.split(",")));
195
- }
196
- if (options.constraints) {
197
- updates.push("constraints = ?");
198
- values.push(JSON.stringify(options.constraints.split(",")));
199
- }
200
-
201
- if (updates.length > 0) {
202
- updates.push("updated_at = ?");
203
- values.push(now);
204
- values.push("pending");
205
-
206
- db.run(
207
- `UPDATE product_context SET ${updates.join(", ")} WHERE id = ?`,
208
- values
209
- );
210
- }
211
-
212
- // Mostrar estado atual
213
- const current = db.query("SELECT * FROM product_context WHERE id = 'pending'").get() as any;
214
-
215
- console.log("\nContexto de produto atualizado:");
216
- console.log("─".repeat(40));
217
- if (current.name !== "Pendente") console.log(` Nome: ${current.name}`);
218
- if (current.problem) console.log(` Problema: ${current.problem.substring(0, 50)}...`);
219
- if (current.solution) console.log(` Solucao: ${current.solution.substring(0, 50)}...`);
220
- if (current.target_users) console.log(` Usuarios: ${current.target_users.substring(0, 50)}...`);
221
- if (current.value_proposition) console.log(` Proposta: ${current.value_proposition.substring(0, 50)}...`);
222
- console.log("\nPara confirmar: product confirm\n");
223
- }
224
-
225
- // ═══════════════════════════════════════════════════════════════
226
- // PRODUCT GOAL ADD - Adiciona objetivo
227
- // ═══════════════════════════════════════════════════════════════
228
-
229
- export function productGoalAdd(options: {
230
- category: string;
231
- goal: string;
232
- priority?: string;
233
- }): void {
234
- initSchema();
235
- const db = getDb();
236
-
237
- const now = new Date().toISOString();
238
- const priority = options.priority || "medium";
239
-
240
- db.run(
241
- `INSERT INTO product_goals (product_id, category, goal, priority, created_at)
242
- VALUES ('pending', ?, ?, ?, ?)`,
243
- [options.category, options.goal, priority, now]
244
- );
245
-
246
- const count = db.query("SELECT COUNT(*) as c FROM product_goals WHERE product_id = 'pending'").get() as any;
247
-
248
- console.log(`\nObjetivo adicionado (${count.c} total)`);
249
- console.log(` [${options.category}] ${options.goal} (${priority})\n`);
250
- }
251
-
252
- // ═══════════════════════════════════════════════════════════════
253
- // PRODUCT FEATURE ADD - Adiciona feature core
254
- // ═══════════════════════════════════════════════════════════════
255
-
256
- export function productFeatureAdd(options: {
257
- name: string;
258
- description?: string;
259
- priority?: string;
260
- }): void {
261
- initSchema();
262
- const db = getDb();
263
-
264
- const now = new Date().toISOString();
265
- const priority = options.priority || "medium";
266
-
267
- db.run(
268
- `INSERT INTO product_features (product_id, name, description, priority, created_at)
269
- VALUES ('pending', ?, ?, ?, ?)`,
270
- [options.name, options.description || "", priority, now]
271
- );
272
-
273
- const count = db.query("SELECT COUNT(*) as c FROM product_features WHERE product_id = 'pending'").get() as any;
274
-
275
- console.log(`\nFeature adicionada (${count.c} total)`);
276
- console.log(` ${options.name} (${priority})\n`);
277
- }
278
-
279
- // ═══════════════════════════════════════════════════════════════
280
- // PRODUCT CONFIRM - Confirma e salva
281
- // ═══════════════════════════════════════════════════════════════
282
-
283
- export function productConfirm(): void {
284
- initSchema();
285
- const db = getDb();
286
-
287
- const pending = db.query("SELECT * FROM product_context WHERE id = 'pending'").get() as any;
288
- if (!pending) {
289
- console.error("\nNenhum contexto de produto pendente.");
290
- console.error("Execute: product guide ou product import primeiro\n");
291
- process.exit(1);
292
- }
293
-
294
- // Validar campos obrigatorios
295
- if (!pending.name || pending.name === "Pendente") {
296
- console.error("\nCampo obrigatorio ausente: name");
297
- console.error("Use: product set --name \"Nome do Produto\"\n");
298
- process.exit(1);
299
- }
300
- if (!pending.problem || pending.problem === "") {
301
- console.error("\nCampo obrigatorio ausente: problem");
302
- console.error("Use: product set --problem \"Problema que resolve\"\n");
303
- process.exit(1);
304
- }
305
-
306
- const now = new Date().toISOString();
307
-
308
- // Desabilitar foreign keys temporariamente para permitir a migração
309
- db.run("PRAGMA foreign_keys = OFF");
310
-
311
- // Deletar goals e features do 'default' ANTES de deletar product_context
312
- db.run("DELETE FROM product_goals WHERE product_id = 'default'");
313
- db.run("DELETE FROM product_features WHERE product_id = 'default'");
314
- db.run("DELETE FROM product_context WHERE id = 'default'");
315
-
316
- // Mover pending para default (primeiro os filhos, depois o pai)
317
- db.run(`UPDATE product_goals SET product_id = 'default' WHERE product_id = 'pending'`);
318
- db.run(`UPDATE product_features SET product_id = 'default' WHERE product_id = 'pending'`);
319
- db.run(
320
- `UPDATE product_context SET id = 'default', updated_at = ? WHERE id = 'pending'`,
321
- [now]
322
- );
323
-
324
- // Reabilitar foreign keys
325
- db.run("PRAGMA foreign_keys = ON");
326
-
327
- // Gerar arquivo product-context.md
328
- generateProductMarkdown();
329
-
330
- const goalsCount = db.query("SELECT COUNT(*) as c FROM product_goals WHERE product_id = 'default'").get() as any;
331
- const featuresCount = db.query("SELECT COUNT(*) as c FROM product_features WHERE product_id = 'default'").get() as any;
332
-
333
- console.log("\n" + "═".repeat(60));
334
- console.log("PRODUTO CONFIGURADO");
335
- console.log("═".repeat(60));
336
- console.log(`
337
- Nome: ${pending.name}
338
- Objetivos: ${goalsCount.c}
339
- Features: ${featuresCount.c}
340
-
341
- Arquivo gerado: .codexa/product-context.md
342
-
343
- Proximo passo: /codexa:feature para iniciar uma feature
344
- `);
345
- console.log("─".repeat(60) + "\n");
346
- }
347
-
348
- // ═══════════════════════════════════════════════════════════════
349
- // PRODUCT SHOW - Mostra contexto atual
350
- // ═══════════════════════════════════════════════════════════════
351
-
352
- export function productShow(options: { json?: boolean; pending?: boolean } = {}): void {
353
- initSchema();
354
- const db = getDb();
355
-
356
- const id = options.pending ? "pending" : "default";
357
- const product = db.query(`SELECT * FROM product_context WHERE id = ?`).get(id) as any;
358
-
359
- if (!product) {
360
- if (options.pending) {
361
- console.error("\nNenhum contexto pendente.");
362
- } else {
363
- console.error("\nContexto de produto nao definido.");
364
- console.error("Execute: product guide ou product import\n");
365
- }
366
- process.exit(1);
367
- }
368
-
369
- const goals = db.query(`SELECT * FROM product_goals WHERE product_id = ? ORDER BY priority, category`).all(id) as any[];
370
- const features = db.query(`SELECT * FROM product_features WHERE product_id = ? ORDER BY priority`).all(id) as any[];
371
-
372
- if (options.json) {
373
- console.log(JSON.stringify({
374
- product,
375
- goals,
376
- features,
377
- successMetrics: product.success_metrics ? JSON.parse(product.success_metrics) : [],
378
- outOfScope: product.out_of_scope ? JSON.parse(product.out_of_scope) : [],
379
- constraints: product.constraints ? JSON.parse(product.constraints) : [],
380
- }, null, 2));
381
- return;
382
- }
383
-
384
- console.log("\n" + "═".repeat(60));
385
- console.log(`CONTEXTO DO PRODUTO${options.pending ? " (PENDENTE)" : ""}`);
386
- console.log("═".repeat(60));
387
-
388
- console.log(`\nNome: ${product.name}`);
389
- console.log(`Fonte: ${product.source}`);
390
-
391
- console.log("\n" + "─".repeat(40));
392
- console.log("PROBLEMA");
393
- console.log("─".repeat(40));
394
- console.log(product.problem);
395
-
396
- if (product.solution) {
397
- console.log("\n" + "─".repeat(40));
398
- console.log("SOLUCAO");
399
- console.log("─".repeat(40));
400
- console.log(product.solution);
401
- }
402
-
403
- if (product.target_users) {
404
- console.log("\n" + "─".repeat(40));
405
- console.log("USUARIOS ALVO");
406
- console.log("─".repeat(40));
407
- console.log(product.target_users);
408
- }
409
-
410
- if (product.value_proposition) {
411
- console.log("\n" + "─".repeat(40));
412
- console.log("PROPOSTA DE VALOR");
413
- console.log("─".repeat(40));
414
- console.log(product.value_proposition);
415
- }
416
-
417
- if (goals.length > 0) {
418
- console.log("\n" + "─".repeat(40));
419
- console.log("OBJETIVOS");
420
- console.log("─".repeat(40));
421
- for (const goal of goals) {
422
- const icon = goal.priority === "high" ? "!" : goal.priority === "low" ? "-" : "*";
423
- console.log(` ${icon} [${goal.category}] ${goal.goal}`);
424
- }
425
- }
426
-
427
- if (features.length > 0) {
428
- console.log("\n" + "─".repeat(40));
429
- console.log("FEATURES CORE");
430
- console.log("─".repeat(40));
431
- for (const feature of features) {
432
- const icon = feature.priority === "high" ? "!" : feature.priority === "low" ? "-" : "*";
433
- console.log(` ${icon} ${feature.name}${feature.description ? `: ${feature.description}` : ""}`);
434
- }
435
- }
436
-
437
- if (product.success_metrics) {
438
- const metrics = JSON.parse(product.success_metrics);
439
- if (metrics.length > 0) {
440
- console.log("\n" + "─".repeat(40));
441
- console.log("METRICAS DE SUCESSO");
442
- console.log("─".repeat(40));
443
- for (const metric of metrics) {
444
- console.log(` - ${metric}`);
445
- }
446
- }
447
- }
448
-
449
- if (product.out_of_scope) {
450
- const outOfScope = JSON.parse(product.out_of_scope);
451
- if (outOfScope.length > 0) {
452
- console.log("\n" + "─".repeat(40));
453
- console.log("FORA DO ESCOPO");
454
- console.log("─".repeat(40));
455
- for (const item of outOfScope) {
456
- console.log(` x ${item}`);
457
- }
458
- }
459
- }
460
-
461
- if (product.constraints) {
462
- const constraints = JSON.parse(product.constraints);
463
- if (constraints.length > 0) {
464
- console.log("\n" + "─".repeat(40));
465
- console.log("RESTRICOES");
466
- console.log("─".repeat(40));
467
- for (const item of constraints) {
468
- console.log(` ! ${item}`);
469
- }
470
- }
471
- }
472
-
473
- console.log("\n" + "─".repeat(60));
474
- if (!options.pending) {
475
- console.log("Arquivo: .codexa/product-context.md");
476
- }
477
- console.log("");
478
- }
479
-
480
- // ═══════════════════════════════════════════════════════════════
481
- // PRODUCT RESET - Reseta contexto de produto
482
- // ═══════════════════════════════════════════════════════════════
483
-
484
- export function productReset(): void {
485
- initSchema();
486
- const db = getDb();
487
-
488
- db.run("DELETE FROM product_features");
489
- db.run("DELETE FROM product_goals");
490
- db.run("DELETE FROM product_context");
491
-
492
- console.log("\nContexto de produto resetado.");
493
- console.log("Execute: product guide ou product import para refazer\n");
494
- }
495
-
496
- // ═══════════════════════════════════════════════════════════════
497
- // GENERATE MARKDOWN
498
- // ═══════════════════════════════════════════════════════════════
499
-
500
- function generateProductMarkdown(): void {
501
- const db = getDb();
502
- const product = db.query("SELECT * FROM product_context WHERE id = 'default'").get() as any;
503
- const goals = db.query("SELECT * FROM product_goals WHERE product_id = 'default' ORDER BY priority, category").all() as any[];
504
- const features = db.query("SELECT * FROM product_features WHERE product_id = 'default' ORDER BY priority").all() as any[];
505
-
506
- if (!product) return;
507
-
508
- let md = `# ${product.name} - Contexto de Produto
509
-
510
- > Gerado automaticamente do SQLite. Nao edite manualmente.
511
- > Para modificar, use os comandos CLI.
512
- > Gerado em: ${new Date().toISOString()}
513
- > Fonte: ${product.source}
514
-
515
- ## Problema
516
-
517
- ${product.problem}
518
-
519
- `;
520
-
521
- if (product.solution) {
522
- md += `## Solucao
523
-
524
- ${product.solution}
525
-
526
- `;
527
- }
528
-
529
- if (product.target_users) {
530
- md += `## Usuarios Alvo
531
-
532
- ${product.target_users}
533
-
534
- `;
535
- }
536
-
537
- if (product.value_proposition) {
538
- md += `## Proposta de Valor
539
-
540
- ${product.value_proposition}
541
-
542
- `;
543
- }
544
-
545
- if (goals.length > 0) {
546
- md += `## Objetivos
547
-
548
- `;
549
- // Agrupar por categoria
550
- const byCategory: Record<string, any[]> = {};
551
- for (const goal of goals) {
552
- if (!byCategory[goal.category]) byCategory[goal.category] = [];
553
- byCategory[goal.category].push(goal);
554
- }
555
-
556
- for (const [category, categoryGoals] of Object.entries(byCategory)) {
557
- md += `### ${category.charAt(0).toUpperCase() + category.slice(1)}
558
-
559
- `;
560
- for (const goal of categoryGoals) {
561
- const priority = goal.priority === "high" ? " **[ALTA]**" : goal.priority === "low" ? " [baixa]" : "";
562
- md += `- ${goal.goal}${priority}\n`;
563
- }
564
- md += "\n";
565
- }
566
- }
567
-
568
- if (features.length > 0) {
569
- md += `## Features Core
570
-
571
- | Feature | Descricao | Prioridade |
572
- |---------|-----------|------------|
573
- `;
574
- for (const feature of features) {
575
- md += `| ${feature.name} | ${feature.description || "-"} | ${feature.priority} |\n`;
576
- }
577
- md += "\n";
578
- }
579
-
580
- if (product.success_metrics) {
581
- const metrics = JSON.parse(product.success_metrics);
582
- if (metrics.length > 0) {
583
- md += `## Metricas de Sucesso
584
-
585
- `;
586
- for (const metric of metrics) {
587
- md += `- ${metric}\n`;
588
- }
589
- md += "\n";
590
- }
591
- }
592
-
593
- if (product.out_of_scope) {
594
- const outOfScope = JSON.parse(product.out_of_scope);
595
- if (outOfScope.length > 0) {
596
- md += `## Fora do Escopo
597
-
598
- > Estas funcionalidades NAO serao implementadas nesta versao.
599
-
600
- `;
601
- for (const item of outOfScope) {
602
- md += `- ~~${item}~~\n`;
603
- }
604
- md += "\n";
605
- }
606
- }
607
-
608
- if (product.constraints) {
609
- const constraints = JSON.parse(product.constraints);
610
- if (constraints.length > 0) {
611
- md += `## Restricoes
612
-
613
- > Limitacoes conhecidas que devem ser respeitadas.
614
-
615
- `;
616
- for (const item of constraints) {
617
- md += `- **${item}**\n`;
618
- }
619
- md += "\n";
620
- }
621
- }
622
-
623
- // Garantir que diretorio existe
624
- const mdPath = join(process.cwd(), ".codexa", "product-context.md");
625
- writeFileSync(mdPath, md);
626
- }
1
+ import { getDb } from "../db/connection";
2
+ import { initSchema } from "../db/schema";
3
+ import { existsSync, readFileSync, writeFileSync } from "fs";
4
+ import { join } from "path";
5
+
6
+ interface ProductContext {
7
+ name: string;
8
+ problem: string;
9
+ solution?: string;
10
+ targetUsers?: string;
11
+ valueProposition?: string;
12
+ successMetrics?: string[];
13
+ outOfScope?: string[];
14
+ constraints?: string[];
15
+ }
16
+
17
+ interface ProductGoal {
18
+ category: string;
19
+ goal: string;
20
+ priority: "high" | "medium" | "low";
21
+ }
22
+
23
+ interface ProductFeature {
24
+ name: string;
25
+ description?: string;
26
+ priority: "high" | "medium" | "low";
27
+ }
28
+
29
+ // ═══════════════════════════════════════════════════════════════
30
+ // PRODUCT GUIDE - Inicia o processo guiado
31
+ // ═══════════════════════════════════════════════════════════════
32
+
33
+ export function productGuide(json: boolean = false): void {
34
+ initSchema();
35
+ const db = getDb();
36
+
37
+ // Verificar se ja existe
38
+ const existing = db.query("SELECT * FROM product_context WHERE id = 'default'").get();
39
+ if (existing) {
40
+ console.log("\nContexto de produto ja definido.");
41
+ console.log("Use: product show para ver detalhes");
42
+ console.log("Ou: product reset para refazer\n");
43
+ return;
44
+ }
45
+
46
+ if (json) {
47
+ console.log(JSON.stringify({ status: "ready_for_guide", message: "Use AskUserQuestion para guiar o usuario" }, null, 2));
48
+ return;
49
+ }
50
+
51
+ console.log("\n" + "═".repeat(60));
52
+ console.log("PRODUCT DISCOVERY - Modo Guiado");
53
+ console.log("═".repeat(60));
54
+ console.log(`
55
+ Este modo vai ajudar a definir o contexto do produto atraves de perguntas.
56
+
57
+ O agente vai usar AskUserQuestion para coletar:
58
+ 1. Nome do produto
59
+ 2. Problema que resolve
60
+ 3. Solucao proposta
61
+ 4. Usuarios alvo
62
+ 5. Proposta de valor
63
+ 6. Metricas de sucesso
64
+ 7. O que esta fora do escopo
65
+ 8. Restricoes conhecidas
66
+
67
+ Apos coletar as respostas, use:
68
+ product set --name "..." --problem "..." ...
69
+
70
+ Para confirmar e salvar:
71
+ product confirm
72
+ `);
73
+ console.log("─".repeat(60) + "\n");
74
+ }
75
+
76
+ // ═══════════════════════════════════════════════════════════════
77
+ // PRODUCT IMPORT - Importa PRD existente
78
+ // ═══════════════════════════════════════════════════════════════
79
+
80
+ export function productImport(options: { file?: string; content?: string }): void {
81
+ initSchema();
82
+ const db = getDb();
83
+
84
+ // Verificar se ja existe
85
+ const existing = db.query("SELECT * FROM product_context WHERE id = 'default'").get();
86
+ if (existing) {
87
+ console.log("\nContexto de produto ja definido.");
88
+ console.log("Use: product reset para refazer\n");
89
+ return;
90
+ }
91
+
92
+ let prdContent = "";
93
+
94
+ if (options.file) {
95
+ if (!existsSync(options.file)) {
96
+ console.error(`\nArquivo nao encontrado: ${options.file}\n`);
97
+ process.exit(1);
98
+ }
99
+ prdContent = readFileSync(options.file, "utf-8");
100
+ } else if (options.content) {
101
+ prdContent = options.content;
102
+ } else {
103
+ console.error("\nForneca --file ou --content\n");
104
+ process.exit(1);
105
+ }
106
+
107
+ // Salvar como pendente para o agente processar
108
+ const now = new Date().toISOString();
109
+ db.run(
110
+ `INSERT INTO product_context (id, name, problem, source, discovered_at, updated_at)
111
+ VALUES ('pending', 'Pendente - PRD Importado', ?, 'import', ?, ?)`,
112
+ [prdContent, now, now]
113
+ );
114
+
115
+ console.log("\n" + "═".repeat(60));
116
+ console.log("PRD IMPORTADO");
117
+ console.log("═".repeat(60));
118
+ console.log(`
119
+ Conteudo do PRD foi salvo para processamento.
120
+
121
+ O agente deve agora:
122
+ 1. Analisar o PRD importado
123
+ 2. Extrair informacoes estruturadas
124
+ 3. Usar 'product set' para definir os campos
125
+ 4. Usar 'product confirm' para finalizar
126
+
127
+ Comando para ver o PRD:
128
+ product show --pending
129
+ `);
130
+ console.log("─".repeat(60) + "\n");
131
+ }
132
+
133
+ // ═══════════════════════════════════════════════════════════════
134
+ // PRODUCT SET - Define campos do produto
135
+ // ═══════════════════════════════════════════════════════════════
136
+
137
+ export function productSet(options: {
138
+ name?: string;
139
+ problem?: string;
140
+ solution?: string;
141
+ targetUsers?: string;
142
+ valueProposition?: string;
143
+ successMetrics?: string;
144
+ outOfScope?: string;
145
+ constraints?: string;
146
+ }): void {
147
+ initSchema();
148
+ const db = getDb();
149
+
150
+ // Buscar pendente ou criar novo
151
+ let pending = db.query("SELECT * FROM product_context WHERE id = 'pending'").get() as any;
152
+ const now = new Date().toISOString();
153
+
154
+ if (!pending) {
155
+ // Criar novo registro pendente
156
+ db.run(
157
+ `INSERT INTO product_context (id, name, problem, source, discovered_at, updated_at)
158
+ VALUES ('pending', 'Pendente', '', 'guide', ?, ?)`,
159
+ [now, now]
160
+ );
161
+ pending = { name: "Pendente", problem: "" };
162
+ }
163
+
164
+ // Atualizar campos fornecidos
165
+ const updates: string[] = [];
166
+ const values: any[] = [];
167
+
168
+ if (options.name) {
169
+ updates.push("name = ?");
170
+ values.push(options.name);
171
+ }
172
+ if (options.problem) {
173
+ updates.push("problem = ?");
174
+ values.push(options.problem);
175
+ }
176
+ if (options.solution) {
177
+ updates.push("solution = ?");
178
+ values.push(options.solution);
179
+ }
180
+ if (options.targetUsers) {
181
+ updates.push("target_users = ?");
182
+ values.push(options.targetUsers);
183
+ }
184
+ if (options.valueProposition) {
185
+ updates.push("value_proposition = ?");
186
+ values.push(options.valueProposition);
187
+ }
188
+ if (options.successMetrics) {
189
+ updates.push("success_metrics = ?");
190
+ values.push(JSON.stringify(options.successMetrics.split(",")));
191
+ }
192
+ if (options.outOfScope) {
193
+ updates.push("out_of_scope = ?");
194
+ values.push(JSON.stringify(options.outOfScope.split(",")));
195
+ }
196
+ if (options.constraints) {
197
+ updates.push("constraints = ?");
198
+ values.push(JSON.stringify(options.constraints.split(",")));
199
+ }
200
+
201
+ if (updates.length > 0) {
202
+ updates.push("updated_at = ?");
203
+ values.push(now);
204
+ values.push("pending");
205
+
206
+ db.run(
207
+ `UPDATE product_context SET ${updates.join(", ")} WHERE id = ?`,
208
+ values
209
+ );
210
+ }
211
+
212
+ // Mostrar estado atual
213
+ const current = db.query("SELECT * FROM product_context WHERE id = 'pending'").get() as any;
214
+
215
+ console.log("\nContexto de produto atualizado:");
216
+ console.log("─".repeat(40));
217
+ if (current.name !== "Pendente") console.log(` Nome: ${current.name}`);
218
+ if (current.problem) console.log(` Problema: ${current.problem.substring(0, 50)}...`);
219
+ if (current.solution) console.log(` Solucao: ${current.solution.substring(0, 50)}...`);
220
+ if (current.target_users) console.log(` Usuarios: ${current.target_users.substring(0, 50)}...`);
221
+ if (current.value_proposition) console.log(` Proposta: ${current.value_proposition.substring(0, 50)}...`);
222
+ console.log("\nPara confirmar: product confirm\n");
223
+ }
224
+
225
+ // ═══════════════════════════════════════════════════════════════
226
+ // PRODUCT GOAL ADD - Adiciona objetivo
227
+ // ═══════════════════════════════════════════════════════════════
228
+
229
+ export function productGoalAdd(options: {
230
+ category: string;
231
+ goal: string;
232
+ priority?: string;
233
+ }): void {
234
+ initSchema();
235
+ const db = getDb();
236
+
237
+ const now = new Date().toISOString();
238
+ const priority = options.priority || "medium";
239
+
240
+ db.run(
241
+ `INSERT INTO product_goals (product_id, category, goal, priority, created_at)
242
+ VALUES ('pending', ?, ?, ?, ?)`,
243
+ [options.category, options.goal, priority, now]
244
+ );
245
+
246
+ const count = db.query("SELECT COUNT(*) as c FROM product_goals WHERE product_id = 'pending'").get() as any;
247
+
248
+ console.log(`\nObjetivo adicionado (${count.c} total)`);
249
+ console.log(` [${options.category}] ${options.goal} (${priority})\n`);
250
+ }
251
+
252
+ // ═══════════════════════════════════════════════════════════════
253
+ // PRODUCT FEATURE ADD - Adiciona feature core
254
+ // ═══════════════════════════════════════════════════════════════
255
+
256
+ export function productFeatureAdd(options: {
257
+ name: string;
258
+ description?: string;
259
+ priority?: string;
260
+ }): void {
261
+ initSchema();
262
+ const db = getDb();
263
+
264
+ const now = new Date().toISOString();
265
+ const priority = options.priority || "medium";
266
+
267
+ db.run(
268
+ `INSERT INTO product_features (product_id, name, description, priority, created_at)
269
+ VALUES ('pending', ?, ?, ?, ?)`,
270
+ [options.name, options.description || "", priority, now]
271
+ );
272
+
273
+ const count = db.query("SELECT COUNT(*) as c FROM product_features WHERE product_id = 'pending'").get() as any;
274
+
275
+ console.log(`\nFeature adicionada (${count.c} total)`);
276
+ console.log(` ${options.name} (${priority})\n`);
277
+ }
278
+
279
+ // ═══════════════════════════════════════════════════════════════
280
+ // PRODUCT CONFIRM - Confirma e salva
281
+ // ═══════════════════════════════════════════════════════════════
282
+
283
+ export function productConfirm(): void {
284
+ initSchema();
285
+ const db = getDb();
286
+
287
+ const pending = db.query("SELECT * FROM product_context WHERE id = 'pending'").get() as any;
288
+ if (!pending) {
289
+ console.error("\nNenhum contexto de produto pendente.");
290
+ console.error("Execute: product guide ou product import primeiro\n");
291
+ process.exit(1);
292
+ }
293
+
294
+ // Validar campos obrigatorios
295
+ if (!pending.name || pending.name === "Pendente") {
296
+ console.error("\nCampo obrigatorio ausente: name");
297
+ console.error("Use: product set --name \"Nome do Produto\"\n");
298
+ process.exit(1);
299
+ }
300
+ if (!pending.problem || pending.problem === "") {
301
+ console.error("\nCampo obrigatorio ausente: problem");
302
+ console.error("Use: product set --problem \"Problema que resolve\"\n");
303
+ process.exit(1);
304
+ }
305
+
306
+ const now = new Date().toISOString();
307
+
308
+ // Desabilitar foreign keys temporariamente para permitir a migração
309
+ db.run("PRAGMA foreign_keys = OFF");
310
+
311
+ // Deletar goals e features do 'default' ANTES de deletar product_context
312
+ db.run("DELETE FROM product_goals WHERE product_id = 'default'");
313
+ db.run("DELETE FROM product_features WHERE product_id = 'default'");
314
+ db.run("DELETE FROM product_context WHERE id = 'default'");
315
+
316
+ // Mover pending para default (primeiro os filhos, depois o pai)
317
+ db.run(`UPDATE product_goals SET product_id = 'default' WHERE product_id = 'pending'`);
318
+ db.run(`UPDATE product_features SET product_id = 'default' WHERE product_id = 'pending'`);
319
+ db.run(
320
+ `UPDATE product_context SET id = 'default', updated_at = ? WHERE id = 'pending'`,
321
+ [now]
322
+ );
323
+
324
+ // Reabilitar foreign keys
325
+ db.run("PRAGMA foreign_keys = ON");
326
+
327
+ // Gerar arquivo product-context.md
328
+ generateProductMarkdown();
329
+
330
+ const goalsCount = db.query("SELECT COUNT(*) as c FROM product_goals WHERE product_id = 'default'").get() as any;
331
+ const featuresCount = db.query("SELECT COUNT(*) as c FROM product_features WHERE product_id = 'default'").get() as any;
332
+
333
+ console.log("\n" + "═".repeat(60));
334
+ console.log("PRODUTO CONFIGURADO");
335
+ console.log("═".repeat(60));
336
+ console.log(`
337
+ Nome: ${pending.name}
338
+ Objetivos: ${goalsCount.c}
339
+ Features: ${featuresCount.c}
340
+
341
+ Arquivo gerado: .codexa/product-context.md
342
+
343
+ Proximo passo: /codexa:feature para iniciar uma feature
344
+ `);
345
+ console.log("─".repeat(60) + "\n");
346
+ }
347
+
348
+ // ═══════════════════════════════════════════════════════════════
349
+ // PRODUCT SHOW - Mostra contexto atual
350
+ // ═══════════════════════════════════════════════════════════════
351
+
352
+ export function productShow(options: { json?: boolean; pending?: boolean } = {}): void {
353
+ initSchema();
354
+ const db = getDb();
355
+
356
+ const id = options.pending ? "pending" : "default";
357
+ const product = db.query(`SELECT * FROM product_context WHERE id = ?`).get(id) as any;
358
+
359
+ if (!product) {
360
+ if (options.pending) {
361
+ console.error("\nNenhum contexto pendente.");
362
+ } else {
363
+ console.error("\nContexto de produto nao definido.");
364
+ console.error("Execute: product guide ou product import\n");
365
+ }
366
+ process.exit(1);
367
+ }
368
+
369
+ const goals = db.query(`SELECT * FROM product_goals WHERE product_id = ? ORDER BY priority, category`).all(id) as any[];
370
+ const features = db.query(`SELECT * FROM product_features WHERE product_id = ? ORDER BY priority`).all(id) as any[];
371
+
372
+ if (options.json) {
373
+ console.log(JSON.stringify({
374
+ product,
375
+ goals,
376
+ features,
377
+ successMetrics: product.success_metrics ? JSON.parse(product.success_metrics) : [],
378
+ outOfScope: product.out_of_scope ? JSON.parse(product.out_of_scope) : [],
379
+ constraints: product.constraints ? JSON.parse(product.constraints) : [],
380
+ }, null, 2));
381
+ return;
382
+ }
383
+
384
+ console.log("\n" + "═".repeat(60));
385
+ console.log(`CONTEXTO DO PRODUTO${options.pending ? " (PENDENTE)" : ""}`);
386
+ console.log("═".repeat(60));
387
+
388
+ console.log(`\nNome: ${product.name}`);
389
+ console.log(`Fonte: ${product.source}`);
390
+
391
+ console.log("\n" + "─".repeat(40));
392
+ console.log("PROBLEMA");
393
+ console.log("─".repeat(40));
394
+ console.log(product.problem);
395
+
396
+ if (product.solution) {
397
+ console.log("\n" + "─".repeat(40));
398
+ console.log("SOLUCAO");
399
+ console.log("─".repeat(40));
400
+ console.log(product.solution);
401
+ }
402
+
403
+ if (product.target_users) {
404
+ console.log("\n" + "─".repeat(40));
405
+ console.log("USUARIOS ALVO");
406
+ console.log("─".repeat(40));
407
+ console.log(product.target_users);
408
+ }
409
+
410
+ if (product.value_proposition) {
411
+ console.log("\n" + "─".repeat(40));
412
+ console.log("PROPOSTA DE VALOR");
413
+ console.log("─".repeat(40));
414
+ console.log(product.value_proposition);
415
+ }
416
+
417
+ if (goals.length > 0) {
418
+ console.log("\n" + "─".repeat(40));
419
+ console.log("OBJETIVOS");
420
+ console.log("─".repeat(40));
421
+ for (const goal of goals) {
422
+ const icon = goal.priority === "high" ? "!" : goal.priority === "low" ? "-" : "*";
423
+ console.log(` ${icon} [${goal.category}] ${goal.goal}`);
424
+ }
425
+ }
426
+
427
+ if (features.length > 0) {
428
+ console.log("\n" + "─".repeat(40));
429
+ console.log("FEATURES CORE");
430
+ console.log("─".repeat(40));
431
+ for (const feature of features) {
432
+ const icon = feature.priority === "high" ? "!" : feature.priority === "low" ? "-" : "*";
433
+ console.log(` ${icon} ${feature.name}${feature.description ? `: ${feature.description}` : ""}`);
434
+ }
435
+ }
436
+
437
+ if (product.success_metrics) {
438
+ const metrics = JSON.parse(product.success_metrics);
439
+ if (metrics.length > 0) {
440
+ console.log("\n" + "─".repeat(40));
441
+ console.log("METRICAS DE SUCESSO");
442
+ console.log("─".repeat(40));
443
+ for (const metric of metrics) {
444
+ console.log(` - ${metric}`);
445
+ }
446
+ }
447
+ }
448
+
449
+ if (product.out_of_scope) {
450
+ const outOfScope = JSON.parse(product.out_of_scope);
451
+ if (outOfScope.length > 0) {
452
+ console.log("\n" + "─".repeat(40));
453
+ console.log("FORA DO ESCOPO");
454
+ console.log("─".repeat(40));
455
+ for (const item of outOfScope) {
456
+ console.log(` x ${item}`);
457
+ }
458
+ }
459
+ }
460
+
461
+ if (product.constraints) {
462
+ const constraints = JSON.parse(product.constraints);
463
+ if (constraints.length > 0) {
464
+ console.log("\n" + "─".repeat(40));
465
+ console.log("RESTRICOES");
466
+ console.log("─".repeat(40));
467
+ for (const item of constraints) {
468
+ console.log(` ! ${item}`);
469
+ }
470
+ }
471
+ }
472
+
473
+ console.log("\n" + "─".repeat(60));
474
+ if (!options.pending) {
475
+ console.log("Arquivo: .codexa/product-context.md");
476
+ }
477
+ console.log("");
478
+ }
479
+
480
+ // ═══════════════════════════════════════════════════════════════
481
+ // PRODUCT RESET - Reseta contexto de produto
482
+ // ═══════════════════════════════════════════════════════════════
483
+
484
+ export function productReset(): void {
485
+ initSchema();
486
+ const db = getDb();
487
+
488
+ db.run("DELETE FROM product_features");
489
+ db.run("DELETE FROM product_goals");
490
+ db.run("DELETE FROM product_context");
491
+
492
+ console.log("\nContexto de produto resetado.");
493
+ console.log("Execute: product guide ou product import para refazer\n");
494
+ }
495
+
496
+ // ═══════════════════════════════════════════════════════════════
497
+ // GENERATE MARKDOWN
498
+ // ═══════════════════════════════════════════════════════════════
499
+
500
+ function generateProductMarkdown(): void {
501
+ const db = getDb();
502
+ const product = db.query("SELECT * FROM product_context WHERE id = 'default'").get() as any;
503
+ const goals = db.query("SELECT * FROM product_goals WHERE product_id = 'default' ORDER BY priority, category").all() as any[];
504
+ const features = db.query("SELECT * FROM product_features WHERE product_id = 'default' ORDER BY priority").all() as any[];
505
+
506
+ if (!product) return;
507
+
508
+ let md = `# ${product.name} - Contexto de Produto
509
+
510
+ > Gerado automaticamente do SQLite. Nao edite manualmente.
511
+ > Para modificar, use os comandos CLI.
512
+ > Gerado em: ${new Date().toISOString()}
513
+ > Fonte: ${product.source}
514
+
515
+ ## Problema
516
+
517
+ ${product.problem}
518
+
519
+ `;
520
+
521
+ if (product.solution) {
522
+ md += `## Solucao
523
+
524
+ ${product.solution}
525
+
526
+ `;
527
+ }
528
+
529
+ if (product.target_users) {
530
+ md += `## Usuarios Alvo
531
+
532
+ ${product.target_users}
533
+
534
+ `;
535
+ }
536
+
537
+ if (product.value_proposition) {
538
+ md += `## Proposta de Valor
539
+
540
+ ${product.value_proposition}
541
+
542
+ `;
543
+ }
544
+
545
+ if (goals.length > 0) {
546
+ md += `## Objetivos
547
+
548
+ `;
549
+ // Agrupar por categoria
550
+ const byCategory: Record<string, any[]> = {};
551
+ for (const goal of goals) {
552
+ if (!byCategory[goal.category]) byCategory[goal.category] = [];
553
+ byCategory[goal.category].push(goal);
554
+ }
555
+
556
+ for (const [category, categoryGoals] of Object.entries(byCategory)) {
557
+ md += `### ${category.charAt(0).toUpperCase() + category.slice(1)}
558
+
559
+ `;
560
+ for (const goal of categoryGoals) {
561
+ const priority = goal.priority === "high" ? " **[ALTA]**" : goal.priority === "low" ? " [baixa]" : "";
562
+ md += `- ${goal.goal}${priority}\n`;
563
+ }
564
+ md += "\n";
565
+ }
566
+ }
567
+
568
+ if (features.length > 0) {
569
+ md += `## Features Core
570
+
571
+ | Feature | Descricao | Prioridade |
572
+ |---------|-----------|------------|
573
+ `;
574
+ for (const feature of features) {
575
+ md += `| ${feature.name} | ${feature.description || "-"} | ${feature.priority} |\n`;
576
+ }
577
+ md += "\n";
578
+ }
579
+
580
+ if (product.success_metrics) {
581
+ const metrics = JSON.parse(product.success_metrics);
582
+ if (metrics.length > 0) {
583
+ md += `## Metricas de Sucesso
584
+
585
+ `;
586
+ for (const metric of metrics) {
587
+ md += `- ${metric}\n`;
588
+ }
589
+ md += "\n";
590
+ }
591
+ }
592
+
593
+ if (product.out_of_scope) {
594
+ const outOfScope = JSON.parse(product.out_of_scope);
595
+ if (outOfScope.length > 0) {
596
+ md += `## Fora do Escopo
597
+
598
+ > Estas funcionalidades NAO serao implementadas nesta versao.
599
+
600
+ `;
601
+ for (const item of outOfScope) {
602
+ md += `- ~~${item}~~\n`;
603
+ }
604
+ md += "\n";
605
+ }
606
+ }
607
+
608
+ if (product.constraints) {
609
+ const constraints = JSON.parse(product.constraints);
610
+ if (constraints.length > 0) {
611
+ md += `## Restricoes
612
+
613
+ > Limitacoes conhecidas que devem ser respeitadas.
614
+
615
+ `;
616
+ for (const item of constraints) {
617
+ md += `- **${item}**\n`;
618
+ }
619
+ md += "\n";
620
+ }
621
+ }
622
+
623
+ // Garantir que diretorio existe
624
+ const mdPath = join(process.cwd(), ".codexa", "product-context.md");
625
+ writeFileSync(mdPath, md);
626
+ }