@codexa/cli 9.0.31 → 9.0.32
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 +52 -87
- package/commands/check.ts +22 -23
- package/commands/clear.ts +42 -48
- package/commands/decide.ts +46 -44
- package/commands/discover.ts +81 -94
- package/commands/integration.test.ts +262 -313
- package/commands/knowledge.test.ts +56 -61
- package/commands/knowledge.ts +126 -131
- package/commands/patterns.ts +28 -43
- package/commands/plan.ts +50 -48
- package/commands/product.ts +57 -59
- package/commands/research.ts +64 -77
- package/commands/review.ts +100 -86
- package/commands/simplify.ts +24 -35
- package/commands/spec-resolver.test.ts +52 -48
- package/commands/spec-resolver.ts +21 -23
- package/commands/standards.ts +20 -27
- package/commands/sync.ts +2 -8
- package/commands/task.ts +106 -97
- package/commands/team.test.ts +22 -83
- package/commands/team.ts +62 -50
- package/commands/utils.ts +83 -81
- package/context/assembly.ts +0 -1
- package/context/generator.ts +66 -79
- package/context/sections.ts +8 -14
- package/db/connection.ts +195 -19
- package/db/schema.test.ts +288 -299
- package/db/schema.ts +297 -394
- package/db/test-helpers.ts +18 -29
- package/gates/standards-validator.test.ts +83 -86
- package/gates/standards-validator.ts +9 -41
- package/gates/validator.test.ts +13 -22
- package/gates/validator.ts +69 -107
- package/package.json +2 -1
- package/protocol/process-return.ts +41 -57
- package/simplify/prompt-builder.test.ts +44 -42
- package/simplify/prompt-builder.ts +12 -14
- package/workflow.ts +159 -174
package/context/generator.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { dbGet, dbAll } from "../db/connection";
|
|
2
2
|
import { initSchema, getPatternsForFiles, getRelatedDecisions, getArchitecturalAnalysisForSpec } from "../db/schema";
|
|
3
3
|
import { getKnowledgeForTask } from "../commands/knowledge";
|
|
4
4
|
import type { ContextSection, ContextData } from "./assembly";
|
|
@@ -21,57 +21,55 @@ import {
|
|
|
21
21
|
buildReferencesSection,
|
|
22
22
|
} from "./sections";
|
|
23
23
|
|
|
24
|
-
// v9.0: Contexto minimo para subagent (max ~2KB)
|
|
25
24
|
const MAX_MINIMAL_CONTEXT = 2048;
|
|
26
25
|
|
|
27
|
-
export function getMinimalContextForSubagent(taskId: number): string {
|
|
28
|
-
initSchema();
|
|
29
|
-
const db = getDb();
|
|
26
|
+
export async function getMinimalContextForSubagent(taskId: number): Promise<string> {
|
|
27
|
+
await initSchema();
|
|
30
28
|
|
|
31
|
-
const task =
|
|
29
|
+
const task = await dbGet<any>("SELECT * FROM tasks WHERE id = ?", [taskId]);
|
|
32
30
|
if (!task) return "ERRO: Task nao encontrada";
|
|
33
31
|
|
|
34
|
-
const spec =
|
|
35
|
-
const context =
|
|
32
|
+
const spec = await dbGet<any>("SELECT * FROM specs WHERE id = ?", [task.spec_id]);
|
|
33
|
+
const context = await dbGet<any>("SELECT * FROM context WHERE spec_id = ?", [task.spec_id]);
|
|
36
34
|
|
|
37
35
|
const taskFiles = task.files ? JSON.parse(task.files) : [];
|
|
38
36
|
const domain = domainToScope(getAgentDomain(task.agent));
|
|
39
37
|
|
|
40
|
-
|
|
41
|
-
const requiredStandards = db.query(
|
|
38
|
+
const requiredStandards = await dbAll<any>(
|
|
42
39
|
`SELECT rule FROM standards
|
|
43
40
|
WHERE enforcement = 'required' AND (scope = 'all' OR scope = ?)
|
|
44
|
-
LIMIT 10
|
|
45
|
-
|
|
41
|
+
LIMIT 10`,
|
|
42
|
+
[domain]
|
|
43
|
+
);
|
|
46
44
|
|
|
47
|
-
|
|
48
|
-
const criticalBlockers = db.query(
|
|
45
|
+
const criticalBlockers = await dbAll<any>(
|
|
49
46
|
`SELECT DISTINCT content FROM knowledge
|
|
50
47
|
WHERE severity = 'critical'
|
|
51
48
|
ORDER BY CASE WHEN spec_id = ? THEN 0 ELSE 1 END, created_at DESC
|
|
52
|
-
LIMIT 5
|
|
53
|
-
|
|
49
|
+
LIMIT 5`,
|
|
50
|
+
[task.spec_id]
|
|
51
|
+
);
|
|
54
52
|
|
|
55
|
-
// 3. Decisoes da task anterior (dependency direta)
|
|
56
53
|
const dependsOn = task.depends_on ? JSON.parse(task.depends_on) : [];
|
|
57
54
|
let depDecisions: any[] = [];
|
|
58
55
|
if (dependsOn.length > 0) {
|
|
59
56
|
const placeholders = dependsOn.map(() => '?').join(',');
|
|
60
|
-
const depTasks =
|
|
61
|
-
`SELECT id FROM tasks WHERE spec_id = ? AND number IN (${placeholders})
|
|
62
|
-
|
|
57
|
+
const depTasks = await dbAll<any>(
|
|
58
|
+
`SELECT id FROM tasks WHERE spec_id = ? AND number IN (${placeholders})`,
|
|
59
|
+
[task.spec_id, ...dependsOn]
|
|
60
|
+
);
|
|
63
61
|
|
|
64
62
|
for (const dt of depTasks) {
|
|
65
|
-
const reasoning =
|
|
63
|
+
const reasoning = await dbAll<any>(
|
|
66
64
|
`SELECT thought FROM reasoning_log
|
|
67
65
|
WHERE spec_id = ? AND task_id = ? AND category = 'recommendation'
|
|
68
|
-
ORDER BY created_at DESC LIMIT 2
|
|
69
|
-
|
|
66
|
+
ORDER BY created_at DESC LIMIT 2`,
|
|
67
|
+
[task.spec_id, dt.id]
|
|
68
|
+
);
|
|
70
69
|
depDecisions.push(...reasoning);
|
|
71
70
|
}
|
|
72
71
|
}
|
|
73
72
|
|
|
74
|
-
// Montar output minimo
|
|
75
73
|
let output = `## CONTEXTO MINIMO (Task #${task.number})
|
|
76
74
|
|
|
77
75
|
**Feature:** ${spec.name}
|
|
@@ -100,7 +98,6 @@ export function getMinimalContextForSubagent(taskId: number): string {
|
|
|
100
98
|
codexa context detail architecture # Analise arquitetural
|
|
101
99
|
`;
|
|
102
100
|
|
|
103
|
-
// Truncar se exceder limite
|
|
104
101
|
if (output.length > MAX_MINIMAL_CONTEXT) {
|
|
105
102
|
output = output.substring(0, MAX_MINIMAL_CONTEXT - 100) + "\n\n[CONTEXTO TRUNCADO - use: codexa context detail <secao>]\n";
|
|
106
103
|
}
|
|
@@ -108,44 +105,33 @@ export function getMinimalContextForSubagent(taskId: number): string {
|
|
|
108
105
|
return output;
|
|
109
106
|
}
|
|
110
107
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
// ═══════════════════════════════════════════════════════════════
|
|
114
|
-
|
|
115
|
-
function fetchContextData(taskId: number): ContextData | null {
|
|
116
|
-
const db = getDb();
|
|
117
|
-
|
|
118
|
-
const task = db.query("SELECT * FROM tasks WHERE id = ?").get(taskId) as any;
|
|
108
|
+
async function fetchContextData(taskId: number): Promise<ContextData | null> {
|
|
109
|
+
const task = await dbGet<any>("SELECT * FROM tasks WHERE id = ?", [taskId]);
|
|
119
110
|
if (!task) return null;
|
|
120
111
|
|
|
121
|
-
const spec =
|
|
122
|
-
const context =
|
|
123
|
-
const project =
|
|
112
|
+
const spec = await dbGet<any>("SELECT * FROM specs WHERE id = ?", [task.spec_id]);
|
|
113
|
+
const context = await dbGet<any>("SELECT * FROM context WHERE spec_id = ?", [task.spec_id]);
|
|
114
|
+
const project = await dbGet<any>("SELECT * FROM project WHERE id = 'default'");
|
|
124
115
|
|
|
125
|
-
|
|
126
|
-
const archAnalysis = getArchitecturalAnalysisForSpec(spec.name, task.spec_id);
|
|
116
|
+
const archAnalysis = await getArchitecturalAnalysisForSpec(spec.name, task.spec_id);
|
|
127
117
|
|
|
128
|
-
// Arquivos da task para filtrar contexto relevante
|
|
129
118
|
const taskFiles = task.files ? JSON.parse(task.files) : [];
|
|
130
119
|
const domain = domainToScope(getAgentDomain(task.agent));
|
|
131
120
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
.
|
|
135
|
-
|
|
121
|
+
const allDecisions = await dbAll<any>(
|
|
122
|
+
`SELECT * FROM decisions WHERE spec_id = ? AND status = 'active' ORDER BY created_at DESC LIMIT 100`,
|
|
123
|
+
[task.spec_id]
|
|
124
|
+
);
|
|
136
125
|
const decisions = filterRelevantDecisions(allDecisions, taskFiles, 50);
|
|
137
126
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
.query(
|
|
141
|
-
`SELECT * FROM standards
|
|
127
|
+
const standards = await dbAll<any>(
|
|
128
|
+
`SELECT * FROM standards
|
|
142
129
|
WHERE (scope = 'all' OR scope = ?)
|
|
143
|
-
ORDER BY enforcement DESC, category
|
|
144
|
-
|
|
145
|
-
|
|
130
|
+
ORDER BY enforcement DESC, category`,
|
|
131
|
+
[domain]
|
|
132
|
+
);
|
|
146
133
|
const relevantStandards = filterRelevantStandards(standards, taskFiles);
|
|
147
134
|
|
|
148
|
-
// v11.0: Raised caps — 50 critical (was 20), 30 info (was 10)
|
|
149
135
|
const allKnowledge = getKnowledgeForTask(task.spec_id, taskId);
|
|
150
136
|
const allCriticalKnowledge = allKnowledge.filter((k: any) => k.severity === 'critical' || k.severity === 'warning');
|
|
151
137
|
const criticalKnowledge = allCriticalKnowledge.slice(0, 50);
|
|
@@ -154,46 +140,48 @@ function fetchContextData(taskId: number): ContextData | null {
|
|
|
154
140
|
const infoKnowledge = allInfoKnowledge.slice(0, 30);
|
|
155
141
|
const truncatedInfo = allInfoKnowledge.length - infoKnowledge.length;
|
|
156
142
|
|
|
157
|
-
const productContext =
|
|
158
|
-
const patterns = getPatternsForFiles(taskFiles);
|
|
143
|
+
const productContext = await dbGet<any>("SELECT * FROM product_context WHERE id = 'default'");
|
|
144
|
+
const patterns = await getPatternsForFiles(taskFiles);
|
|
159
145
|
|
|
160
|
-
// v8.2: Reasoning de tasks dependentes + todas tasks completas recentes
|
|
161
146
|
const dependsOn = task.depends_on ? JSON.parse(task.depends_on) : [];
|
|
162
147
|
const depReasoning: any[] = [];
|
|
163
148
|
const seenTaskIds = new Set<number>();
|
|
164
149
|
|
|
165
150
|
if (dependsOn.length > 0) {
|
|
166
151
|
const placeholders = dependsOn.map(() => '?').join(',');
|
|
167
|
-
const depTasks =
|
|
168
|
-
`SELECT id, number FROM tasks WHERE spec_id = ? AND number IN (${placeholders})
|
|
169
|
-
|
|
152
|
+
const depTasks = await dbAll<any>(
|
|
153
|
+
`SELECT id, number FROM tasks WHERE spec_id = ? AND number IN (${placeholders})`,
|
|
154
|
+
[task.spec_id, ...dependsOn]
|
|
155
|
+
);
|
|
170
156
|
|
|
171
157
|
for (const depTask of depTasks) {
|
|
172
158
|
seenTaskIds.add(depTask.id);
|
|
173
|
-
const reasoning =
|
|
159
|
+
const reasoning = await dbAll<any>(
|
|
174
160
|
`SELECT category, thought FROM reasoning_log
|
|
175
161
|
WHERE spec_id = ? AND task_id = ?
|
|
176
162
|
AND category IN ('recommendation', 'decision', 'challenge')
|
|
177
163
|
ORDER BY
|
|
178
164
|
CASE importance WHEN 'critical' THEN 1 WHEN 'high' THEN 2 ELSE 3 END,
|
|
179
165
|
created_at DESC
|
|
180
|
-
LIMIT 3
|
|
181
|
-
|
|
166
|
+
LIMIT 3`,
|
|
167
|
+
[task.spec_id, depTask.id]
|
|
168
|
+
);
|
|
182
169
|
for (const r of reasoning) {
|
|
183
170
|
depReasoning.push({ ...r, fromTask: depTask.number });
|
|
184
171
|
}
|
|
185
172
|
}
|
|
186
173
|
}
|
|
187
174
|
|
|
188
|
-
const completedTasks =
|
|
175
|
+
const completedTasks = await dbAll<any>(
|
|
189
176
|
`SELECT t.id, t.number FROM tasks t
|
|
190
177
|
WHERE t.spec_id = ? AND t.status = 'done' AND t.id != ?
|
|
191
|
-
ORDER BY t.completed_at DESC LIMIT 10
|
|
192
|
-
|
|
178
|
+
ORDER BY t.completed_at DESC LIMIT 10`,
|
|
179
|
+
[task.spec_id, taskId]
|
|
180
|
+
);
|
|
193
181
|
|
|
194
182
|
for (const ct of completedTasks) {
|
|
195
183
|
if (seenTaskIds.has(ct.id)) continue;
|
|
196
|
-
const reasoning =
|
|
184
|
+
const reasoning = await dbAll<any>(
|
|
197
185
|
`SELECT category, thought FROM reasoning_log
|
|
198
186
|
WHERE spec_id = ? AND task_id = ?
|
|
199
187
|
AND category IN ('recommendation', 'challenge')
|
|
@@ -201,21 +189,22 @@ function fetchContextData(taskId: number): ContextData | null {
|
|
|
201
189
|
ORDER BY
|
|
202
190
|
CASE importance WHEN 'critical' THEN 1 ELSE 2 END,
|
|
203
191
|
created_at DESC
|
|
204
|
-
LIMIT 2
|
|
205
|
-
|
|
192
|
+
LIMIT 2`,
|
|
193
|
+
[task.spec_id, ct.id]
|
|
194
|
+
);
|
|
206
195
|
for (const r of reasoning) {
|
|
207
196
|
depReasoning.push({ ...r, fromTask: ct.number });
|
|
208
197
|
}
|
|
209
198
|
}
|
|
210
199
|
|
|
211
|
-
const libContexts =
|
|
200
|
+
const libContexts = await dbAll<any>(
|
|
212
201
|
"SELECT lib_name, version FROM lib_contexts ORDER BY lib_name LIMIT 10"
|
|
213
|
-
)
|
|
202
|
+
);
|
|
214
203
|
|
|
215
204
|
const graphDecisions: any[] = [];
|
|
216
205
|
for (const file of taskFiles) {
|
|
217
206
|
try {
|
|
218
|
-
const related = getRelatedDecisions(file, "file");
|
|
207
|
+
const related = await getRelatedDecisions(file, "file");
|
|
219
208
|
for (const d of related) {
|
|
220
209
|
if (!graphDecisions.find((gd: any) => gd.id === d.id)) {
|
|
221
210
|
graphDecisions.push(d);
|
|
@@ -227,7 +216,7 @@ function fetchContextData(taskId: number): ContextData | null {
|
|
|
227
216
|
const discoveredPatterns = context?.patterns ? JSON.parse(context.patterns) : [];
|
|
228
217
|
|
|
229
218
|
return {
|
|
230
|
-
|
|
219
|
+
task, spec, context, project, taskFiles, domain, archAnalysis,
|
|
231
220
|
decisions, allDecisions, relevantStandards,
|
|
232
221
|
criticalKnowledge, truncatedCritical, infoKnowledge, truncatedInfo,
|
|
233
222
|
productContext, patterns, depReasoning, libContexts,
|
|
@@ -235,10 +224,8 @@ function fetchContextData(taskId: number): ContextData | null {
|
|
|
235
224
|
};
|
|
236
225
|
}
|
|
237
226
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
function buildContext(taskId: number): { content: string; data: ContextData } | null {
|
|
241
|
-
const data = fetchContextData(taskId);
|
|
227
|
+
async function buildContext(taskId: number): Promise<{ content: string; data: ContextData } | null> {
|
|
228
|
+
const data = await fetchContextData(taskId);
|
|
242
229
|
if (!data) return null;
|
|
243
230
|
|
|
244
231
|
const header = `## CONTEXTO (Task #${data.task.number})
|
|
@@ -258,10 +245,10 @@ function buildContext(taskId: number): { content: string; data: ContextData } |
|
|
|
258
245
|
buildAlertsSection(data),
|
|
259
246
|
buildDiscoveriesSection(data),
|
|
260
247
|
buildPatternsSection(data),
|
|
261
|
-
buildUtilitiesSection(data),
|
|
248
|
+
await buildUtilitiesSection(data),
|
|
262
249
|
buildGraphSection(data),
|
|
263
250
|
buildStackSection(data),
|
|
264
|
-
buildHintsSection(data),
|
|
251
|
+
await buildHintsSection(data),
|
|
265
252
|
].filter((s): s is ContextSection => s !== null);
|
|
266
253
|
|
|
267
254
|
const agentDomain = getAgentDomain(data.task.agent);
|
|
@@ -270,8 +257,8 @@ function buildContext(taskId: number): { content: string; data: ContextData } |
|
|
|
270
257
|
return { content: assembleSections(header, sections), data };
|
|
271
258
|
}
|
|
272
259
|
|
|
273
|
-
export function getContextForSubagent(taskId: number): string {
|
|
274
|
-
initSchema();
|
|
275
|
-
const result = buildContext(taskId);
|
|
260
|
+
export async function getContextForSubagent(taskId: number): Promise<string> {
|
|
261
|
+
await initSchema();
|
|
262
|
+
const result = await buildContext(taskId);
|
|
276
263
|
return result?.content || "ERRO: Task nao encontrada";
|
|
277
264
|
}
|
package/context/sections.ts
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
|
+
import { dbGet } from "../db/connection";
|
|
1
2
|
import { getUtilitiesForContext, getAgentHints } from "../db/schema";
|
|
2
3
|
import type { ContextSection, ContextData } from "./assembly";
|
|
3
4
|
import { getAgentDomain, domainToScope } from "./domains";
|
|
4
5
|
import { findReferenceFiles } from "./references";
|
|
5
6
|
|
|
6
|
-
// ── Section Builders ──────────────────────────────────────────
|
|
7
|
-
|
|
8
7
|
export function buildProductSection(data: ContextData): ContextSection | null {
|
|
9
8
|
if (!data.productContext) return null;
|
|
10
9
|
|
|
@@ -42,8 +41,6 @@ export function buildProductSection(data: ContextData): ContextSection | null {
|
|
|
42
41
|
|
|
43
42
|
content += "\n";
|
|
44
43
|
|
|
45
|
-
// v10.2: Product context is small (~500-2000 bytes) but high-impact.
|
|
46
|
-
// Priority 1 ensures it is NEVER truncated.
|
|
47
44
|
return { name: "PRODUTO", content, priority: 1 };
|
|
48
45
|
}
|
|
49
46
|
|
|
@@ -114,7 +111,6 @@ export function buildStandardsSection(data: ContextData): ContextSection {
|
|
|
114
111
|
}
|
|
115
112
|
|
|
116
113
|
export function buildDecisionsSection(data: ContextData): ContextSection {
|
|
117
|
-
// v11.0: Show all decisions (raised cap to 50 in generator)
|
|
118
114
|
const decisionsToShow = data.allDecisions;
|
|
119
115
|
const truncatedDecisions = data.allDecisions.length - decisionsToShow.length;
|
|
120
116
|
const content = `
|
|
@@ -170,7 +166,6 @@ ${data.patterns.map((p: any) => {
|
|
|
170
166
|
}
|
|
171
167
|
|
|
172
168
|
if (data.discoveredPatterns.length > 0) {
|
|
173
|
-
// v11.0: Show all discovered patterns (no cap)
|
|
174
169
|
content += `
|
|
175
170
|
### PATTERNS DESCOBERTOS (${data.discoveredPatterns.length})
|
|
176
171
|
${data.discoveredPatterns.map((p: any) => `- ${p.pattern}${p.source_task ? ` (Task #${p.source_task})` : ""}`).join("\n")}
|
|
@@ -181,7 +176,7 @@ ${data.discoveredPatterns.map((p: any) => `- ${p.pattern}${p.source_task ? ` (Ta
|
|
|
181
176
|
return { name: "PATTERNS", content, priority: 9 };
|
|
182
177
|
}
|
|
183
178
|
|
|
184
|
-
export function buildUtilitiesSection(data: ContextData): ContextSection | null {
|
|
179
|
+
export async function buildUtilitiesSection(data: ContextData): Promise<ContextSection | null> {
|
|
185
180
|
try {
|
|
186
181
|
const taskDirs = [...new Set(data.taskFiles.map((f: string) => {
|
|
187
182
|
const parts = f.replace(/\\/g, "/").split("/");
|
|
@@ -189,12 +184,11 @@ export function buildUtilitiesSection(data: ContextData): ContextSection | null
|
|
|
189
184
|
}).filter(Boolean))];
|
|
190
185
|
|
|
191
186
|
const agentScope = domainToScope(getAgentDomain(data.task.agent)) || undefined;
|
|
192
|
-
// v11.0: Raised cap to 50 (was 15)
|
|
193
187
|
const utilLimit = 50;
|
|
194
|
-
let relevantUtilities = getUtilitiesForContext(taskDirs, undefined, utilLimit);
|
|
188
|
+
let relevantUtilities = await getUtilitiesForContext(taskDirs, undefined, utilLimit);
|
|
195
189
|
|
|
196
190
|
if (relevantUtilities.length < 5 && agentScope) {
|
|
197
|
-
const scopeUtils = getUtilitiesForContext([], agentScope, 15);
|
|
191
|
+
const scopeUtils = await getUtilitiesForContext([], agentScope, 15);
|
|
198
192
|
const existingKeys = new Set(relevantUtilities.map((u: any) => `${u.file_path}:${u.utility_name}`));
|
|
199
193
|
for (const u of scopeUtils) {
|
|
200
194
|
if (!existingKeys.has(`${u.file_path}:${u.utility_name}`)) {
|
|
@@ -206,7 +200,8 @@ export function buildUtilitiesSection(data: ContextData): ContextSection | null
|
|
|
206
200
|
|
|
207
201
|
if (relevantUtilities.length === 0) return null;
|
|
208
202
|
|
|
209
|
-
const
|
|
203
|
+
const totalCountRow = await dbGet<any>("SELECT COUNT(*) as c FROM project_utilities");
|
|
204
|
+
const totalCount = totalCountRow?.c || 0;
|
|
210
205
|
const truncated = totalCount - relevantUtilities.length;
|
|
211
206
|
const content = `
|
|
212
207
|
### UTILITIES EXISTENTES (${relevantUtilities.length}${truncated > 0 ? ` [+${truncated} mais]` : ''})
|
|
@@ -241,7 +236,6 @@ export function buildStackSection(data: ContextData): ContextSection | null {
|
|
|
241
236
|
if (data.project) {
|
|
242
237
|
const stack = JSON.parse(data.project.stack);
|
|
243
238
|
const allStackEntries = Object.entries(stack);
|
|
244
|
-
// v11.0: Show all stack entries (no cap)
|
|
245
239
|
const mainStack = allStackEntries;
|
|
246
240
|
const truncatedStack = allStackEntries.length - mainStack.length;
|
|
247
241
|
content += `
|
|
@@ -261,11 +255,11 @@ ${data.libContexts.map((l: any) => `- ${l.lib_name}${l.version ? ` v${l.version}
|
|
|
261
255
|
return { name: "STACK", content, priority: 11 };
|
|
262
256
|
}
|
|
263
257
|
|
|
264
|
-
export function buildHintsSection(data: ContextData): ContextSection | null {
|
|
258
|
+
export async function buildHintsSection(data: ContextData): Promise<ContextSection | null> {
|
|
265
259
|
const agentType = data.task.agent;
|
|
266
260
|
if (!agentType) return null;
|
|
267
261
|
|
|
268
|
-
const hints = getAgentHints(agentType);
|
|
262
|
+
const hints = await getAgentHints(agentType);
|
|
269
263
|
if (hints.length === 0) return null;
|
|
270
264
|
|
|
271
265
|
const content = `
|
package/db/connection.ts
CHANGED
|
@@ -1,32 +1,208 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { createClient, type Client, type InValue } from "@libsql/client";
|
|
2
|
+
import { execSync } from "child_process";
|
|
3
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
|
|
2
4
|
import { join, dirname } from "path";
|
|
3
|
-
import { mkdirSync, existsSync } from "fs";
|
|
4
5
|
|
|
5
|
-
|
|
6
|
+
let client: Client | null = null;
|
|
6
7
|
|
|
7
|
-
|
|
8
|
+
// ═══════════════════════════════════════════════════════════════
|
|
9
|
+
// Resolucao de DB URL: env → config local → derivacao do git
|
|
10
|
+
// ═══════════════════════════════════════════════════════════════
|
|
8
11
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
function resolveDbUrl(): string {
|
|
13
|
+
// 1. Override explicito via env var
|
|
14
|
+
if (process.env.CODEXA_DB_URL) return process.env.CODEXA_DB_URL;
|
|
15
|
+
|
|
16
|
+
// 2. Cache local (.codexa/config.json)
|
|
17
|
+
const configPath = join(process.cwd(), ".codexa", "config.json");
|
|
18
|
+
if (existsSync(configPath)) {
|
|
19
|
+
try {
|
|
20
|
+
const config = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
21
|
+
if (config.database) return config.database;
|
|
22
|
+
} catch { /* config invalido, continuar */ }
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// 3. Derivar do git remote
|
|
26
|
+
const org = process.env.TURSO_ORG;
|
|
27
|
+
if (!org) {
|
|
28
|
+
throw new Error(
|
|
29
|
+
"Banco de dados nao configurado.\n\n" +
|
|
30
|
+
"Opcao 1 — Configurar automaticamente:\n" +
|
|
31
|
+
" export TURSO_ORG=sua-org\n" +
|
|
32
|
+
" export TURSO_AUTH_TOKEN=eyJ...\n" +
|
|
33
|
+
" codexa init\n\n" +
|
|
34
|
+
"Opcao 2 — Configurar manualmente:\n" +
|
|
35
|
+
" export CODEXA_DB_URL=libsql://seu-db.turso.io\n" +
|
|
36
|
+
" export TURSO_AUTH_TOKEN=eyJ..."
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const slug = deriveSlugFromGit();
|
|
41
|
+
const url = `libsql://codexa-${slug}-${org}.turso.io`;
|
|
42
|
+
|
|
43
|
+
// Salvar no cache para proximas execucoes
|
|
44
|
+
saveConfig(configPath, url);
|
|
45
|
+
|
|
46
|
+
return url;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function deriveSlugFromGit(): string {
|
|
50
|
+
try {
|
|
51
|
+
const remote = execSync("git remote get-url origin", { encoding: "utf-8" }).trim();
|
|
52
|
+
// github.com/leandro/meu-saas.git → leandro--meu-saas
|
|
53
|
+
// git@github.com:leandro/meu-saas.git → leandro--meu-saas
|
|
54
|
+
const match = remote.match(/[/:]([^/]+)\/([^/]+?)(?:\.git)?$/);
|
|
55
|
+
if (match) {
|
|
56
|
+
return `${match[1]}--${match[2]}`.toLowerCase().replace(/[^a-z0-9-]/g, "-");
|
|
57
|
+
}
|
|
58
|
+
} catch { /* git nao disponivel */ }
|
|
59
|
+
|
|
60
|
+
// Fallback: nome da pasta atual
|
|
61
|
+
const dirName = process.cwd().split(/[/\\]/).pop() || "unknown";
|
|
62
|
+
return dirName.toLowerCase().replace(/[^a-z0-9-]/g, "-");
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function saveConfig(configPath: string, url: string): void {
|
|
66
|
+
try {
|
|
67
|
+
const dir = dirname(configPath);
|
|
68
|
+
if (!existsSync(dir)) {
|
|
69
|
+
mkdirSync(dir, { recursive: true });
|
|
70
|
+
}
|
|
71
|
+
writeFileSync(configPath, JSON.stringify({ database: url }, null, 2) + "\n");
|
|
72
|
+
} catch { /* falha ao salvar cache, nao e critico */ }
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// ═══════════════════════════════════════════════════════════════
|
|
76
|
+
// Provisioning: cria o DB no Turso se nao existir
|
|
77
|
+
// ═══════════════════════════════════════════════════════════════
|
|
78
|
+
|
|
79
|
+
export async function ensureDatabase(): Promise<void> {
|
|
80
|
+
const url = resolveDbUrl();
|
|
81
|
+
|
|
82
|
+
// Somente provisionar para URLs Turso (nao para file: ou :memory:)
|
|
83
|
+
if (!url.startsWith("libsql://")) return;
|
|
84
|
+
|
|
85
|
+
const authToken = process.env.TURSO_AUTH_TOKEN;
|
|
86
|
+
const org = process.env.TURSO_ORG;
|
|
87
|
+
if (!authToken || !org) return;
|
|
88
|
+
|
|
89
|
+
// Extrair nome do DB da URL: libsql://codexa-slug-org.turso.io → codexa-slug
|
|
90
|
+
const hostMatch = url.match(/^libsql:\/\/(.+)-[^-]+\.turso\.io$/);
|
|
91
|
+
if (!hostMatch) return;
|
|
92
|
+
const dbName = hostMatch[1];
|
|
93
|
+
|
|
94
|
+
// Tentar conectar primeiro — se o DB ja existe, nao precisa criar
|
|
95
|
+
try {
|
|
96
|
+
const testClient = createClient({ url, authToken });
|
|
97
|
+
await testClient.execute("SELECT 1");
|
|
98
|
+
testClient.close();
|
|
99
|
+
return;
|
|
100
|
+
} catch { /* DB nao existe, criar */ }
|
|
101
|
+
|
|
102
|
+
// Criar via Turso Platform API
|
|
103
|
+
const group = process.env.TURSO_GROUP || "default";
|
|
104
|
+
try {
|
|
105
|
+
const response = await fetch(
|
|
106
|
+
`https://api.turso.tech/v1/organizations/${org}/databases`,
|
|
107
|
+
{
|
|
108
|
+
method: "POST",
|
|
109
|
+
headers: {
|
|
110
|
+
"Authorization": `Bearer ${authToken}`,
|
|
111
|
+
"Content-Type": "application/json",
|
|
112
|
+
},
|
|
113
|
+
body: JSON.stringify({ name: dbName, group }),
|
|
114
|
+
}
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
if (response.ok) {
|
|
118
|
+
console.log(`[codexa] Banco criado no Turso: ${dbName}`);
|
|
119
|
+
} else if (response.status === 409) {
|
|
120
|
+
// Ja existe — tudo certo
|
|
121
|
+
} else {
|
|
122
|
+
const body = await response.text();
|
|
123
|
+
console.error(`[codexa] Falha ao criar banco: ${response.status} ${body}`);
|
|
15
124
|
}
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
db.exec("PRAGMA foreign_keys = ON");
|
|
125
|
+
} catch (e: any) {
|
|
126
|
+
console.error(`[codexa] Erro ao provisionar banco: ${e.message}`);
|
|
19
127
|
}
|
|
20
|
-
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// ═══════════════════════════════════════════════════════════════
|
|
131
|
+
// Client management
|
|
132
|
+
// ═══════════════════════════════════════════════════════════════
|
|
133
|
+
|
|
134
|
+
export function getDb(): Client {
|
|
135
|
+
if (!client) {
|
|
136
|
+
const url = resolveDbUrl();
|
|
137
|
+
const authToken = process.env.TURSO_AUTH_TOKEN;
|
|
138
|
+
|
|
139
|
+
client = createClient({
|
|
140
|
+
url,
|
|
141
|
+
authToken: authToken || undefined,
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
return client;
|
|
21
145
|
}
|
|
22
146
|
|
|
23
147
|
export function closeDb(): void {
|
|
24
|
-
if (
|
|
25
|
-
|
|
26
|
-
|
|
148
|
+
if (client) {
|
|
149
|
+
client.close();
|
|
150
|
+
client = null;
|
|
27
151
|
}
|
|
28
152
|
}
|
|
29
153
|
|
|
30
|
-
export function
|
|
31
|
-
|
|
154
|
+
export function resetClient(): void {
|
|
155
|
+
client = null;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export function setClient(c: Client): void {
|
|
159
|
+
client = c;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export function getResolvedDbUrl(): string {
|
|
163
|
+
return resolveDbUrl();
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// ═══════════════════════════════════════════════════════════════
|
|
167
|
+
// Helpers: drop-in async replacements for bun:sqlite patterns
|
|
168
|
+
// ═══════════════════════════════════════════════════════════════
|
|
169
|
+
|
|
170
|
+
type Args = InValue[];
|
|
171
|
+
|
|
172
|
+
export async function dbGet<T = any>(sql: string, args: Args = []): Promise<T | null> {
|
|
173
|
+
const db = getDb();
|
|
174
|
+
const result = await db.execute({ sql, args });
|
|
175
|
+
return (result.rows[0] as T) ?? null;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export async function dbAll<T = any>(sql: string, args: Args = []): Promise<T[]> {
|
|
179
|
+
const db = getDb();
|
|
180
|
+
const result = await db.execute({ sql, args });
|
|
181
|
+
return result.rows as T[];
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export async function dbRun(sql: string, args: Args = []): Promise<{ changes: number; lastInsertRowid: bigint | undefined }> {
|
|
185
|
+
const db = getDb();
|
|
186
|
+
const result = await db.execute({ sql, args });
|
|
187
|
+
return { changes: result.rowsAffected, lastInsertRowid: result.lastInsertRowid };
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export async function dbExec(sql: string): Promise<void> {
|
|
191
|
+
const db = getDb();
|
|
192
|
+
const statements = sql
|
|
193
|
+
.split(";")
|
|
194
|
+
.map((s) => s.trim())
|
|
195
|
+
.filter((s) => s.length > 0 && !s.startsWith("--"));
|
|
196
|
+
|
|
197
|
+
if (statements.length === 0) return;
|
|
198
|
+
|
|
199
|
+
if (statements.length === 1) {
|
|
200
|
+
await db.execute(statements[0]);
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
await db.batch(
|
|
205
|
+
statements.map((s) => ({ sql: s, args: [] })),
|
|
206
|
+
"write"
|
|
207
|
+
);
|
|
32
208
|
}
|