@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.
- package/commands/architect.ts +760 -760
- package/commands/check.ts +131 -131
- package/commands/clear.ts +170 -170
- package/commands/decide.ts +249 -249
- package/commands/discover.ts +1071 -1071
- package/commands/knowledge.ts +361 -361
- package/commands/patterns.ts +621 -621
- package/commands/plan.ts +376 -376
- package/commands/product.ts +626 -626
- package/commands/research.ts +754 -754
- package/commands/review.ts +463 -463
- package/commands/standards.ts +200 -200
- package/commands/task.ts +623 -623
- package/commands/utils.ts +1021 -1021
- package/db/connection.ts +32 -32
- package/db/schema.ts +719 -719
- package/detectors/README.md +109 -109
- package/detectors/dotnet.ts +357 -357
- package/detectors/flutter.ts +350 -350
- package/detectors/go.ts +324 -324
- package/detectors/index.ts +387 -387
- package/detectors/jvm.ts +433 -433
- package/detectors/loader.ts +128 -128
- package/detectors/node.ts +493 -493
- package/detectors/python.ts +423 -423
- package/detectors/rust.ts +348 -348
- package/gates/standards-validator.ts +204 -204
- package/gates/validator.ts +441 -441
- package/package.json +44 -43
- package/protocol/process-return.ts +450 -450
- package/protocol/subagent-protocol.ts +401 -401
- package/workflow.ts +783 -782
package/commands/product.ts
CHANGED
|
@@ -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
|
+
}
|