@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/decide.ts
CHANGED
|
@@ -1,249 +1,249 @@
|
|
|
1
|
-
import { getDb } from "../db/connection";
|
|
2
|
-
import { initSchema } from "../db/schema";
|
|
3
|
-
|
|
4
|
-
function getNextDecisionId(specId: string): string {
|
|
5
|
-
const db = getDb();
|
|
6
|
-
const last = db
|
|
7
|
-
.query("SELECT id FROM decisions WHERE spec_id = ? ORDER BY created_at DESC LIMIT 1")
|
|
8
|
-
.get(specId) as any;
|
|
9
|
-
|
|
10
|
-
if (!last) return "DEC-001";
|
|
11
|
-
|
|
12
|
-
const num = parseInt(last.id.replace("DEC-", "")) + 1;
|
|
13
|
-
return `DEC-${num.toString().padStart(3, "0")}`;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
interface ConflictAnalysis {
|
|
17
|
-
hasConflict: boolean;
|
|
18
|
-
conflictingDecisions: Array<{
|
|
19
|
-
id: string;
|
|
20
|
-
title: string;
|
|
21
|
-
decision: string;
|
|
22
|
-
reason: string;
|
|
23
|
-
}>;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
// Extrair keywords relevantes de um texto
|
|
27
|
-
function extractKeywords(text: string): string[] {
|
|
28
|
-
const stopWords = new Set([
|
|
29
|
-
"a", "o", "e", "de", "da", "do", "para", "com", "em", "que", "usar",
|
|
30
|
-
"the", "and", "or", "to", "for", "with", "in", "on", "is", "are", "be",
|
|
31
|
-
"will", "should", "must", "can", "use", "using", "como", "ser", "sera"
|
|
32
|
-
]);
|
|
33
|
-
|
|
34
|
-
return text
|
|
35
|
-
.toLowerCase()
|
|
36
|
-
.replace(/[^\w\s-]/g, " ")
|
|
37
|
-
.split(/\s+/)
|
|
38
|
-
.filter((word) => word.length > 2 && !stopWords.has(word));
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
// Detectar conflitos semanticos entre decisoes
|
|
42
|
-
function detectConflicts(
|
|
43
|
-
newTitle: string,
|
|
44
|
-
newDecision: string,
|
|
45
|
-
existingDecisions: any[]
|
|
46
|
-
): ConflictAnalysis {
|
|
47
|
-
const conflicts: ConflictAnalysis["conflictingDecisions"] = [];
|
|
48
|
-
|
|
49
|
-
const newKeywords = new Set([
|
|
50
|
-
...extractKeywords(newTitle),
|
|
51
|
-
...extractKeywords(newDecision),
|
|
52
|
-
]);
|
|
53
|
-
|
|
54
|
-
// Padroes de conflito conhecidos
|
|
55
|
-
const conflictPatterns: Array<{ patterns: string[][]; reason: string }> = [
|
|
56
|
-
{
|
|
57
|
-
patterns: [["rest", "api"], ["graphql"]],
|
|
58
|
-
reason: "Conflito de paradigma de API (REST vs GraphQL)",
|
|
59
|
-
},
|
|
60
|
-
{
|
|
61
|
-
patterns: [["jwt", "token"], ["session", "cookie"]],
|
|
62
|
-
reason: "Conflito de estrategia de autenticacao",
|
|
63
|
-
},
|
|
64
|
-
{
|
|
65
|
-
patterns: [["zustand"], ["redux"]],
|
|
66
|
-
reason: "Conflito de biblioteca de estado",
|
|
67
|
-
},
|
|
68
|
-
{
|
|
69
|
-
patterns: [["prisma"], ["drizzle"]],
|
|
70
|
-
reason: "Conflito de ORM",
|
|
71
|
-
},
|
|
72
|
-
{
|
|
73
|
-
patterns: [["mysql"], ["postgres", "postgresql"]],
|
|
74
|
-
reason: "Conflito de banco de dados",
|
|
75
|
-
},
|
|
76
|
-
{
|
|
77
|
-
patterns: [["server", "component"], ["client", "component"]],
|
|
78
|
-
reason: "Conflito de tipo de componente padrao",
|
|
79
|
-
},
|
|
80
|
-
{
|
|
81
|
-
patterns: [["tailwind"], ["styled", "component"]],
|
|
82
|
-
reason: "Conflito de abordagem de styling",
|
|
83
|
-
},
|
|
84
|
-
{
|
|
85
|
-
patterns: [["vitest"], ["jest"]],
|
|
86
|
-
reason: "Conflito de framework de testes",
|
|
87
|
-
},
|
|
88
|
-
];
|
|
89
|
-
|
|
90
|
-
for (const existing of existingDecisions) {
|
|
91
|
-
const existingKeywords = new Set([
|
|
92
|
-
...extractKeywords(existing.title),
|
|
93
|
-
...extractKeywords(existing.decision),
|
|
94
|
-
]);
|
|
95
|
-
|
|
96
|
-
// Verificar padroes de conflito conhecidos
|
|
97
|
-
for (const { patterns, reason } of conflictPatterns) {
|
|
98
|
-
const [patternA, patternB] = patterns;
|
|
99
|
-
|
|
100
|
-
const newHasA = patternA.some((p) => newKeywords.has(p));
|
|
101
|
-
const newHasB = patternB.some((p) => newKeywords.has(p));
|
|
102
|
-
const existingHasA = patternA.some((p) => existingKeywords.has(p));
|
|
103
|
-
const existingHasB = patternB.some((p) => existingKeywords.has(p));
|
|
104
|
-
|
|
105
|
-
// Conflito: nova decisao menciona A, existente menciona B (ou vice-versa)
|
|
106
|
-
if ((newHasA && existingHasB) || (newHasB && existingHasA)) {
|
|
107
|
-
conflicts.push({
|
|
108
|
-
id: existing.id,
|
|
109
|
-
title: existing.title,
|
|
110
|
-
decision: existing.decision,
|
|
111
|
-
reason,
|
|
112
|
-
});
|
|
113
|
-
break; // Apenas um conflito por decisao existente
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
// Verificar sobreposicao alta de keywords (mesmo topico, decisoes diferentes?)
|
|
118
|
-
if (conflicts.every((c) => c.id !== existing.id)) {
|
|
119
|
-
const intersection = [...newKeywords].filter((k) => existingKeywords.has(k));
|
|
120
|
-
const similarity = intersection.length / Math.max(newKeywords.size, existingKeywords.size);
|
|
121
|
-
|
|
122
|
-
if (similarity > 0.4 && intersection.length >= 3) {
|
|
123
|
-
conflicts.push({
|
|
124
|
-
id: existing.id,
|
|
125
|
-
title: existing.title,
|
|
126
|
-
decision: existing.decision,
|
|
127
|
-
reason: `Alta sobreposicao de topico (${Math.round(similarity * 100)}% similar) - verificar se sao decisoes conflitantes`,
|
|
128
|
-
});
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
return {
|
|
134
|
-
hasConflict: conflicts.length > 0,
|
|
135
|
-
conflictingDecisions: conflicts,
|
|
136
|
-
};
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
export function decide(title: string, decision: string, options: { rationale?: string; force?: boolean }): void {
|
|
140
|
-
initSchema();
|
|
141
|
-
|
|
142
|
-
const db = getDb();
|
|
143
|
-
const now = new Date().toISOString();
|
|
144
|
-
|
|
145
|
-
const spec = db
|
|
146
|
-
.query("SELECT * FROM specs WHERE phase NOT IN ('completed', 'cancelled') ORDER BY created_at DESC LIMIT 1")
|
|
147
|
-
.get() as any;
|
|
148
|
-
|
|
149
|
-
if (!spec) {
|
|
150
|
-
console.error("\nNenhuma feature ativa.\n");
|
|
151
|
-
process.exit(1);
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
// Verificar conflitos com decisoes existentes
|
|
155
|
-
const existingDecisions = db
|
|
156
|
-
.query("SELECT * FROM decisions WHERE spec_id = ? AND status = 'active'")
|
|
157
|
-
.all(spec.id) as any[];
|
|
158
|
-
|
|
159
|
-
if (existingDecisions.length > 0 && !options.force) {
|
|
160
|
-
const analysis = detectConflicts(title, decision, existingDecisions);
|
|
161
|
-
|
|
162
|
-
if (analysis.hasConflict) {
|
|
163
|
-
console.warn("\n⚠ POTENCIAL CONFLITO DETECTADO:\n");
|
|
164
|
-
console.warn(`Nova decisao: "${title}"`);
|
|
165
|
-
console.warn(` ${decision}\n`);
|
|
166
|
-
|
|
167
|
-
for (const conflict of analysis.conflictingDecisions) {
|
|
168
|
-
console.warn(`Pode conflitar com: ${conflict.id} - "${conflict.title}"`);
|
|
169
|
-
console.warn(` ${conflict.decision}`);
|
|
170
|
-
console.warn(` Motivo: ${conflict.reason}\n`);
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
console.warn(`${"─".repeat(50)}`);
|
|
174
|
-
console.warn(`Para registrar mesmo assim, use: decide "${title}" "${decision}" --force`);
|
|
175
|
-
console.warn(`Ou revise as decisoes existentes com: decisions\n`);
|
|
176
|
-
process.exit(1);
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
// Pegar task atual em execucao
|
|
181
|
-
const currentTask = db
|
|
182
|
-
.query("SELECT * FROM tasks WHERE spec_id = ? AND status = 'running' LIMIT 1")
|
|
183
|
-
.get(spec.id) as any;
|
|
184
|
-
|
|
185
|
-
const decisionId = getNextDecisionId(spec.id);
|
|
186
|
-
|
|
187
|
-
db.run(
|
|
188
|
-
`INSERT INTO decisions (id, spec_id, task_ref, title, decision, rationale, status, created_at)
|
|
189
|
-
VALUES (?, ?, ?, ?, ?, ?, 'active', ?)`,
|
|
190
|
-
[
|
|
191
|
-
decisionId,
|
|
192
|
-
spec.id,
|
|
193
|
-
currentTask?.number || null,
|
|
194
|
-
title,
|
|
195
|
-
decision,
|
|
196
|
-
options.rationale || null,
|
|
197
|
-
now,
|
|
198
|
-
]
|
|
199
|
-
);
|
|
200
|
-
|
|
201
|
-
console.log(`\nDecisao registrada: ${decisionId}`);
|
|
202
|
-
console.log(`Titulo: ${title}`);
|
|
203
|
-
console.log(`Decisao: ${decision}`);
|
|
204
|
-
if (options.rationale) console.log(`Racional: ${options.rationale}`);
|
|
205
|
-
if (currentTask) console.log(`Task: #${currentTask.number}`);
|
|
206
|
-
if (options.force) console.log(`[!] Registrada com --force (conflito ignorado)`);
|
|
207
|
-
console.log();
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
export function listDecisions(json: boolean = false): void {
|
|
211
|
-
initSchema();
|
|
212
|
-
|
|
213
|
-
const db = getDb();
|
|
214
|
-
|
|
215
|
-
const spec = db
|
|
216
|
-
.query("SELECT * FROM specs WHERE phase NOT IN ('completed', 'cancelled') ORDER BY created_at DESC LIMIT 1")
|
|
217
|
-
.get() as any;
|
|
218
|
-
|
|
219
|
-
if (!spec) {
|
|
220
|
-
console.error("\nNenhuma feature ativa.\n");
|
|
221
|
-
process.exit(1);
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
const decisions = db
|
|
225
|
-
.query("SELECT * FROM decisions WHERE spec_id = ? ORDER BY created_at")
|
|
226
|
-
.all(spec.id) as any[];
|
|
227
|
-
|
|
228
|
-
if (json) {
|
|
229
|
-
console.log(JSON.stringify({ decisions }));
|
|
230
|
-
return;
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
if (decisions.length === 0) {
|
|
234
|
-
console.log("\nNenhuma decisao registrada.\n");
|
|
235
|
-
return;
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
console.log(`\nDecisoes (${decisions.length}):`);
|
|
239
|
-
console.log(`${"─".repeat(60)}`);
|
|
240
|
-
|
|
241
|
-
for (const dec of decisions) {
|
|
242
|
-
const taskRef = dec.task_ref ? ` [Task #${dec.task_ref}]` : "";
|
|
243
|
-
const status = dec.status === "active" ? "" : ` (${dec.status})`;
|
|
244
|
-
console.log(`${dec.id}: ${dec.title}${taskRef}${status}`);
|
|
245
|
-
console.log(` ${dec.decision}`);
|
|
246
|
-
if (dec.rationale) console.log(` Racional: ${dec.rationale}`);
|
|
247
|
-
console.log();
|
|
248
|
-
}
|
|
249
|
-
}
|
|
1
|
+
import { getDb } from "../db/connection";
|
|
2
|
+
import { initSchema } from "../db/schema";
|
|
3
|
+
|
|
4
|
+
function getNextDecisionId(specId: string): string {
|
|
5
|
+
const db = getDb();
|
|
6
|
+
const last = db
|
|
7
|
+
.query("SELECT id FROM decisions WHERE spec_id = ? ORDER BY created_at DESC LIMIT 1")
|
|
8
|
+
.get(specId) as any;
|
|
9
|
+
|
|
10
|
+
if (!last) return "DEC-001";
|
|
11
|
+
|
|
12
|
+
const num = parseInt(last.id.replace("DEC-", "")) + 1;
|
|
13
|
+
return `DEC-${num.toString().padStart(3, "0")}`;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
interface ConflictAnalysis {
|
|
17
|
+
hasConflict: boolean;
|
|
18
|
+
conflictingDecisions: Array<{
|
|
19
|
+
id: string;
|
|
20
|
+
title: string;
|
|
21
|
+
decision: string;
|
|
22
|
+
reason: string;
|
|
23
|
+
}>;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Extrair keywords relevantes de um texto
|
|
27
|
+
function extractKeywords(text: string): string[] {
|
|
28
|
+
const stopWords = new Set([
|
|
29
|
+
"a", "o", "e", "de", "da", "do", "para", "com", "em", "que", "usar",
|
|
30
|
+
"the", "and", "or", "to", "for", "with", "in", "on", "is", "are", "be",
|
|
31
|
+
"will", "should", "must", "can", "use", "using", "como", "ser", "sera"
|
|
32
|
+
]);
|
|
33
|
+
|
|
34
|
+
return text
|
|
35
|
+
.toLowerCase()
|
|
36
|
+
.replace(/[^\w\s-]/g, " ")
|
|
37
|
+
.split(/\s+/)
|
|
38
|
+
.filter((word) => word.length > 2 && !stopWords.has(word));
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Detectar conflitos semanticos entre decisoes
|
|
42
|
+
function detectConflicts(
|
|
43
|
+
newTitle: string,
|
|
44
|
+
newDecision: string,
|
|
45
|
+
existingDecisions: any[]
|
|
46
|
+
): ConflictAnalysis {
|
|
47
|
+
const conflicts: ConflictAnalysis["conflictingDecisions"] = [];
|
|
48
|
+
|
|
49
|
+
const newKeywords = new Set([
|
|
50
|
+
...extractKeywords(newTitle),
|
|
51
|
+
...extractKeywords(newDecision),
|
|
52
|
+
]);
|
|
53
|
+
|
|
54
|
+
// Padroes de conflito conhecidos
|
|
55
|
+
const conflictPatterns: Array<{ patterns: string[][]; reason: string }> = [
|
|
56
|
+
{
|
|
57
|
+
patterns: [["rest", "api"], ["graphql"]],
|
|
58
|
+
reason: "Conflito de paradigma de API (REST vs GraphQL)",
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
patterns: [["jwt", "token"], ["session", "cookie"]],
|
|
62
|
+
reason: "Conflito de estrategia de autenticacao",
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
patterns: [["zustand"], ["redux"]],
|
|
66
|
+
reason: "Conflito de biblioteca de estado",
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
patterns: [["prisma"], ["drizzle"]],
|
|
70
|
+
reason: "Conflito de ORM",
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
patterns: [["mysql"], ["postgres", "postgresql"]],
|
|
74
|
+
reason: "Conflito de banco de dados",
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
patterns: [["server", "component"], ["client", "component"]],
|
|
78
|
+
reason: "Conflito de tipo de componente padrao",
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
patterns: [["tailwind"], ["styled", "component"]],
|
|
82
|
+
reason: "Conflito de abordagem de styling",
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
patterns: [["vitest"], ["jest"]],
|
|
86
|
+
reason: "Conflito de framework de testes",
|
|
87
|
+
},
|
|
88
|
+
];
|
|
89
|
+
|
|
90
|
+
for (const existing of existingDecisions) {
|
|
91
|
+
const existingKeywords = new Set([
|
|
92
|
+
...extractKeywords(existing.title),
|
|
93
|
+
...extractKeywords(existing.decision),
|
|
94
|
+
]);
|
|
95
|
+
|
|
96
|
+
// Verificar padroes de conflito conhecidos
|
|
97
|
+
for (const { patterns, reason } of conflictPatterns) {
|
|
98
|
+
const [patternA, patternB] = patterns;
|
|
99
|
+
|
|
100
|
+
const newHasA = patternA.some((p) => newKeywords.has(p));
|
|
101
|
+
const newHasB = patternB.some((p) => newKeywords.has(p));
|
|
102
|
+
const existingHasA = patternA.some((p) => existingKeywords.has(p));
|
|
103
|
+
const existingHasB = patternB.some((p) => existingKeywords.has(p));
|
|
104
|
+
|
|
105
|
+
// Conflito: nova decisao menciona A, existente menciona B (ou vice-versa)
|
|
106
|
+
if ((newHasA && existingHasB) || (newHasB && existingHasA)) {
|
|
107
|
+
conflicts.push({
|
|
108
|
+
id: existing.id,
|
|
109
|
+
title: existing.title,
|
|
110
|
+
decision: existing.decision,
|
|
111
|
+
reason,
|
|
112
|
+
});
|
|
113
|
+
break; // Apenas um conflito por decisao existente
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Verificar sobreposicao alta de keywords (mesmo topico, decisoes diferentes?)
|
|
118
|
+
if (conflicts.every((c) => c.id !== existing.id)) {
|
|
119
|
+
const intersection = [...newKeywords].filter((k) => existingKeywords.has(k));
|
|
120
|
+
const similarity = intersection.length / Math.max(newKeywords.size, existingKeywords.size);
|
|
121
|
+
|
|
122
|
+
if (similarity > 0.4 && intersection.length >= 3) {
|
|
123
|
+
conflicts.push({
|
|
124
|
+
id: existing.id,
|
|
125
|
+
title: existing.title,
|
|
126
|
+
decision: existing.decision,
|
|
127
|
+
reason: `Alta sobreposicao de topico (${Math.round(similarity * 100)}% similar) - verificar se sao decisoes conflitantes`,
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return {
|
|
134
|
+
hasConflict: conflicts.length > 0,
|
|
135
|
+
conflictingDecisions: conflicts,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export function decide(title: string, decision: string, options: { rationale?: string; force?: boolean }): void {
|
|
140
|
+
initSchema();
|
|
141
|
+
|
|
142
|
+
const db = getDb();
|
|
143
|
+
const now = new Date().toISOString();
|
|
144
|
+
|
|
145
|
+
const spec = db
|
|
146
|
+
.query("SELECT * FROM specs WHERE phase NOT IN ('completed', 'cancelled') ORDER BY created_at DESC LIMIT 1")
|
|
147
|
+
.get() as any;
|
|
148
|
+
|
|
149
|
+
if (!spec) {
|
|
150
|
+
console.error("\nNenhuma feature ativa.\n");
|
|
151
|
+
process.exit(1);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Verificar conflitos com decisoes existentes
|
|
155
|
+
const existingDecisions = db
|
|
156
|
+
.query("SELECT * FROM decisions WHERE spec_id = ? AND status = 'active'")
|
|
157
|
+
.all(spec.id) as any[];
|
|
158
|
+
|
|
159
|
+
if (existingDecisions.length > 0 && !options.force) {
|
|
160
|
+
const analysis = detectConflicts(title, decision, existingDecisions);
|
|
161
|
+
|
|
162
|
+
if (analysis.hasConflict) {
|
|
163
|
+
console.warn("\n⚠ POTENCIAL CONFLITO DETECTADO:\n");
|
|
164
|
+
console.warn(`Nova decisao: "${title}"`);
|
|
165
|
+
console.warn(` ${decision}\n`);
|
|
166
|
+
|
|
167
|
+
for (const conflict of analysis.conflictingDecisions) {
|
|
168
|
+
console.warn(`Pode conflitar com: ${conflict.id} - "${conflict.title}"`);
|
|
169
|
+
console.warn(` ${conflict.decision}`);
|
|
170
|
+
console.warn(` Motivo: ${conflict.reason}\n`);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
console.warn(`${"─".repeat(50)}`);
|
|
174
|
+
console.warn(`Para registrar mesmo assim, use: decide "${title}" "${decision}" --force`);
|
|
175
|
+
console.warn(`Ou revise as decisoes existentes com: decisions\n`);
|
|
176
|
+
process.exit(1);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Pegar task atual em execucao
|
|
181
|
+
const currentTask = db
|
|
182
|
+
.query("SELECT * FROM tasks WHERE spec_id = ? AND status = 'running' LIMIT 1")
|
|
183
|
+
.get(spec.id) as any;
|
|
184
|
+
|
|
185
|
+
const decisionId = getNextDecisionId(spec.id);
|
|
186
|
+
|
|
187
|
+
db.run(
|
|
188
|
+
`INSERT INTO decisions (id, spec_id, task_ref, title, decision, rationale, status, created_at)
|
|
189
|
+
VALUES (?, ?, ?, ?, ?, ?, 'active', ?)`,
|
|
190
|
+
[
|
|
191
|
+
decisionId,
|
|
192
|
+
spec.id,
|
|
193
|
+
currentTask?.number || null,
|
|
194
|
+
title,
|
|
195
|
+
decision,
|
|
196
|
+
options.rationale || null,
|
|
197
|
+
now,
|
|
198
|
+
]
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
console.log(`\nDecisao registrada: ${decisionId}`);
|
|
202
|
+
console.log(`Titulo: ${title}`);
|
|
203
|
+
console.log(`Decisao: ${decision}`);
|
|
204
|
+
if (options.rationale) console.log(`Racional: ${options.rationale}`);
|
|
205
|
+
if (currentTask) console.log(`Task: #${currentTask.number}`);
|
|
206
|
+
if (options.force) console.log(`[!] Registrada com --force (conflito ignorado)`);
|
|
207
|
+
console.log();
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
export function listDecisions(json: boolean = false): void {
|
|
211
|
+
initSchema();
|
|
212
|
+
|
|
213
|
+
const db = getDb();
|
|
214
|
+
|
|
215
|
+
const spec = db
|
|
216
|
+
.query("SELECT * FROM specs WHERE phase NOT IN ('completed', 'cancelled') ORDER BY created_at DESC LIMIT 1")
|
|
217
|
+
.get() as any;
|
|
218
|
+
|
|
219
|
+
if (!spec) {
|
|
220
|
+
console.error("\nNenhuma feature ativa.\n");
|
|
221
|
+
process.exit(1);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const decisions = db
|
|
225
|
+
.query("SELECT * FROM decisions WHERE spec_id = ? ORDER BY created_at")
|
|
226
|
+
.all(spec.id) as any[];
|
|
227
|
+
|
|
228
|
+
if (json) {
|
|
229
|
+
console.log(JSON.stringify({ decisions }));
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
if (decisions.length === 0) {
|
|
234
|
+
console.log("\nNenhuma decisao registrada.\n");
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
console.log(`\nDecisoes (${decisions.length}):`);
|
|
239
|
+
console.log(`${"─".repeat(60)}`);
|
|
240
|
+
|
|
241
|
+
for (const dec of decisions) {
|
|
242
|
+
const taskRef = dec.task_ref ? ` [Task #${dec.task_ref}]` : "";
|
|
243
|
+
const status = dec.status === "active" ? "" : ` (${dec.status})`;
|
|
244
|
+
console.log(`${dec.id}: ${dec.title}${taskRef}${status}`);
|
|
245
|
+
console.log(` ${dec.decision}`);
|
|
246
|
+
if (dec.rationale) console.log(` Racional: ${dec.rationale}`);
|
|
247
|
+
console.log();
|
|
248
|
+
}
|
|
249
|
+
}
|