@codexa/cli 9.0.31 → 9.0.33
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 +209 -19
- package/db/schema.test.ts +288 -299
- package/db/schema.ts +298 -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/commands/patterns.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
import { spawnSync } from "child_process";
|
|
9
9
|
import { readFileSync, existsSync } from "fs";
|
|
10
|
-
import {
|
|
10
|
+
import { dbGet, dbAll, dbRun } from "../db/connection";
|
|
11
11
|
import { initSchema } from "../db/schema";
|
|
12
12
|
|
|
13
13
|
// ═══════════════════════════════════════════════════════════════
|
|
@@ -43,13 +43,13 @@ export function isValidWorkspaceName(name: string): boolean {
|
|
|
43
43
|
return WORKSPACE_NAME_REGEX.test(name);
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
-
export function getGrepaiWorkspace(): string | null {
|
|
46
|
+
export async function getGrepaiWorkspace(): Promise<string | null> {
|
|
47
47
|
try {
|
|
48
|
-
initSchema();
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
)
|
|
48
|
+
await initSchema();
|
|
49
|
+
const project = await dbGet<any>(
|
|
50
|
+
"SELECT grepai_workspace FROM project WHERE id = 'default'",
|
|
51
|
+
[]
|
|
52
|
+
);
|
|
53
53
|
return project?.grepai_workspace || null;
|
|
54
54
|
} catch {
|
|
55
55
|
return null;
|
|
@@ -126,13 +126,11 @@ function analyzeFile(filePath: string): FileAnalysis | null {
|
|
|
126
126
|
for (const line of lines) {
|
|
127
127
|
const trimmed = line.trim();
|
|
128
128
|
|
|
129
|
-
// Extrair imports (modulo)
|
|
130
129
|
const importMatch = trimmed.match(/^import\s+.*\s+from\s+['"]([^'"]+)['"]/);
|
|
131
130
|
if (importMatch) {
|
|
132
131
|
imports.push(importMatch[1]);
|
|
133
132
|
}
|
|
134
133
|
|
|
135
|
-
// Extrair hooks dos imports: import { useX, useY } from "..."
|
|
136
134
|
const hooksInImport = trimmed.match(/^import\s+\{([^}]+)\}\s+from/);
|
|
137
135
|
if (hooksInImport) {
|
|
138
136
|
const specifiers = hooksInImport[1].split(",").map(s => s.trim());
|
|
@@ -143,7 +141,6 @@ function analyzeFile(filePath: string): FileAnalysis | null {
|
|
|
143
141
|
}
|
|
144
142
|
}
|
|
145
143
|
|
|
146
|
-
// Extrair exports
|
|
147
144
|
if (trimmed.startsWith("export default")) {
|
|
148
145
|
hasDefaultExport = true;
|
|
149
146
|
exports.push("default");
|
|
@@ -158,7 +155,6 @@ function analyzeFile(filePath: string): FileAnalysis | null {
|
|
|
158
155
|
}
|
|
159
156
|
}
|
|
160
157
|
|
|
161
|
-
// Directives
|
|
162
158
|
if (trimmed === "'use client'" || trimmed === '"use client"') {
|
|
163
159
|
directives.push("use client");
|
|
164
160
|
conventions.push("use client");
|
|
@@ -172,12 +168,10 @@ function analyzeFile(filePath: string): FileAnalysis | null {
|
|
|
172
168
|
conventions.push("use cache");
|
|
173
169
|
}
|
|
174
170
|
|
|
175
|
-
// Async patterns
|
|
176
171
|
if (trimmed.includes("async function") || trimmed.includes("async (")) {
|
|
177
172
|
if (!conventions.includes("async")) conventions.push("async");
|
|
178
173
|
}
|
|
179
174
|
|
|
180
|
-
// React patterns (metadata complementar ao grepai)
|
|
181
175
|
if (trimmed.includes("forwardRef") && !reactPatterns.includes("forwardRef")) {
|
|
182
176
|
reactPatterns.push("forwardRef");
|
|
183
177
|
}
|
|
@@ -236,7 +230,6 @@ export function extractUtilitiesFromFile(filePath: string): ExtractedUtility[] {
|
|
|
236
230
|
for (const line of lines) {
|
|
237
231
|
const trimmed = line.trim();
|
|
238
232
|
|
|
239
|
-
// export [async] function name(args): ReturnType
|
|
240
233
|
const funcMatch = trimmed.match(
|
|
241
234
|
/^export\s+(?:async\s+)?function\s+(\w+)\s*(\([^)]*\)(?:\s*:\s*[^{]+)?)?/
|
|
242
235
|
);
|
|
@@ -249,7 +242,6 @@ export function extractUtilitiesFromFile(filePath: string): ExtractedUtility[] {
|
|
|
249
242
|
continue;
|
|
250
243
|
}
|
|
251
244
|
|
|
252
|
-
// export const name: Type = ...
|
|
253
245
|
const constMatch = trimmed.match(
|
|
254
246
|
/^export\s+const\s+(\w+)(?:\s*:\s*([^=]+))?\s*=/
|
|
255
247
|
);
|
|
@@ -262,21 +254,18 @@ export function extractUtilitiesFromFile(filePath: string): ExtractedUtility[] {
|
|
|
262
254
|
continue;
|
|
263
255
|
}
|
|
264
256
|
|
|
265
|
-
// export class Name
|
|
266
257
|
const classMatch = trimmed.match(/^export\s+class\s+(\w+)/);
|
|
267
258
|
if (classMatch) {
|
|
268
259
|
utilities.push({ name: classMatch[1], type: "class" });
|
|
269
260
|
continue;
|
|
270
261
|
}
|
|
271
262
|
|
|
272
|
-
// export interface Name
|
|
273
263
|
const ifaceMatch = trimmed.match(/^export\s+interface\s+(\w+)/);
|
|
274
264
|
if (ifaceMatch) {
|
|
275
265
|
utilities.push({ name: ifaceMatch[1], type: "interface" });
|
|
276
266
|
continue;
|
|
277
267
|
}
|
|
278
268
|
|
|
279
|
-
// export type Name
|
|
280
269
|
const typeMatch = trimmed.match(/^export\s+type\s+(\w+)/);
|
|
281
270
|
if (typeMatch) {
|
|
282
271
|
utilities.push({ name: typeMatch[1], type: "type" });
|
|
@@ -308,12 +297,11 @@ export function inferScopeFromPath(filePath: string): string {
|
|
|
308
297
|
// ATUALIZACAO INCREMENTAL DE PATTERNS (chamado por taskDone)
|
|
309
298
|
// ═══════════════════════════════════════════════════════════════
|
|
310
299
|
|
|
311
|
-
export function updatePatternsIncremental(files: string[], taskNumber: number): void {
|
|
312
|
-
initSchema();
|
|
313
|
-
const db = getDb();
|
|
300
|
+
export async function updatePatternsIncremental(files: string[], taskNumber: number): Promise<void> {
|
|
301
|
+
await initSchema();
|
|
314
302
|
const now = new Date().toISOString();
|
|
315
303
|
|
|
316
|
-
const allPatterns =
|
|
304
|
+
const allPatterns = await dbAll<any>("SELECT * FROM implementation_patterns", []);
|
|
317
305
|
if (allPatterns.length === 0) return;
|
|
318
306
|
|
|
319
307
|
for (const file of files) {
|
|
@@ -353,7 +341,7 @@ export function updatePatternsIncremental(files: string[], taskNumber: number):
|
|
|
353
341
|
const newExtractedFrom = (pattern.extracted_from || 0) + 1;
|
|
354
342
|
const newConfidence = Math.min(newExtractedFrom / 10, 1);
|
|
355
343
|
|
|
356
|
-
|
|
344
|
+
await dbRun(
|
|
357
345
|
`UPDATE implementation_patterns
|
|
358
346
|
SET examples = ?, extracted_from = ?, confidence = ?,
|
|
359
347
|
structure = ?, updated_at = ?
|
|
@@ -399,11 +387,10 @@ export interface SemanticPattern {
|
|
|
399
387
|
export function detectSemanticPatterns(workspace?: string): SemanticPattern[] {
|
|
400
388
|
if (!isGrepaiAvailable()) return [];
|
|
401
389
|
|
|
402
|
-
const ws = workspace || getGrepaiWorkspace() || undefined;
|
|
403
390
|
const detected: SemanticPattern[] = [];
|
|
404
391
|
|
|
405
392
|
for (const { query, category, scope } of SEMANTIC_PATTERN_QUERIES) {
|
|
406
|
-
const results = searchWithGrepai(query, 5,
|
|
393
|
+
const results = searchWithGrepai(query, 5, workspace);
|
|
407
394
|
if (results.length < 2) continue;
|
|
408
395
|
|
|
409
396
|
const avgScore = results.reduce((sum, r) => sum + r.score, 0) / results.length;
|
|
@@ -422,27 +409,27 @@ export function detectSemanticPatterns(workspace?: string): SemanticPattern[] {
|
|
|
422
409
|
return detected;
|
|
423
410
|
}
|
|
424
411
|
|
|
425
|
-
export function saveSemanticPatterns(patterns: SemanticPattern[]): number {
|
|
426
|
-
initSchema();
|
|
427
|
-
const db = getDb();
|
|
412
|
+
export async function saveSemanticPatterns(patterns: SemanticPattern[]): Promise<number> {
|
|
413
|
+
await initSchema();
|
|
428
414
|
const now = new Date().toISOString();
|
|
429
415
|
let saved = 0;
|
|
430
416
|
|
|
431
417
|
for (const p of patterns) {
|
|
432
418
|
try {
|
|
433
|
-
const existing =
|
|
434
|
-
"SELECT id FROM implementation_patterns WHERE name = ?"
|
|
435
|
-
|
|
419
|
+
const existing = await dbGet<any>(
|
|
420
|
+
"SELECT id FROM implementation_patterns WHERE name = ?",
|
|
421
|
+
[p.name]
|
|
422
|
+
);
|
|
436
423
|
|
|
437
424
|
if (existing) {
|
|
438
|
-
|
|
425
|
+
await dbRun(
|
|
439
426
|
`UPDATE implementation_patterns SET
|
|
440
427
|
confidence = ?, examples = ?, extracted_from = ?, updated_at = ?
|
|
441
428
|
WHERE id = ?`,
|
|
442
429
|
[p.confidence, JSON.stringify(p.files.map(f => ({ path: f, relevance: p.confidence }))), p.files.length, now, existing.id]
|
|
443
430
|
);
|
|
444
431
|
} else {
|
|
445
|
-
|
|
432
|
+
await dbRun(
|
|
446
433
|
`INSERT INTO implementation_patterns
|
|
447
434
|
(category, name, scope, applies_to, structure, template, examples, confidence, extracted_from, created_at, updated_at)
|
|
448
435
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
@@ -473,19 +460,19 @@ export interface PatternComplianceResult {
|
|
|
473
460
|
checked: number;
|
|
474
461
|
}
|
|
475
462
|
|
|
476
|
-
export function validatePatternCompliance(
|
|
463
|
+
export async function validatePatternCompliance(
|
|
477
464
|
files: string[],
|
|
478
465
|
minConfidence: number = 0.7
|
|
479
|
-
): PatternComplianceResult {
|
|
480
|
-
initSchema();
|
|
481
|
-
const db = getDb();
|
|
466
|
+
): Promise<PatternComplianceResult> {
|
|
467
|
+
await initSchema();
|
|
482
468
|
const result: PatternComplianceResult = { passed: true, violations: [], checked: 0 };
|
|
483
469
|
|
|
484
470
|
if (files.length === 0) return result;
|
|
485
471
|
|
|
486
|
-
const allPatterns =
|
|
487
|
-
"SELECT * FROM implementation_patterns WHERE confidence >= ?"
|
|
488
|
-
|
|
472
|
+
const allPatterns = await dbAll<any>(
|
|
473
|
+
"SELECT * FROM implementation_patterns WHERE confidence >= ?",
|
|
474
|
+
[minConfidence]
|
|
475
|
+
);
|
|
489
476
|
|
|
490
477
|
if (allPatterns.length === 0) return result;
|
|
491
478
|
|
|
@@ -504,10 +491,9 @@ export function validatePatternCompliance(
|
|
|
504
491
|
let structure: any;
|
|
505
492
|
try { structure = JSON.parse(pattern.structure); } catch { continue; }
|
|
506
493
|
|
|
507
|
-
// Semantic patterns (source: "semantic") — use grepai if available
|
|
508
494
|
if (structure.source === "semantic") {
|
|
509
495
|
if (isGrepaiAvailable()) {
|
|
510
|
-
const ws = getGrepaiWorkspace() || undefined;
|
|
496
|
+
const ws = await getGrepaiWorkspace() || undefined;
|
|
511
497
|
const grepaiResults = searchWithGrepai(
|
|
512
498
|
`file:${file} follows ${pattern.category} pattern`, 1, ws
|
|
513
499
|
);
|
|
@@ -522,7 +508,6 @@ export function validatePatternCompliance(
|
|
|
522
508
|
continue;
|
|
523
509
|
}
|
|
524
510
|
|
|
525
|
-
// Structural patterns — check imports, hooks, conventions
|
|
526
511
|
if (structure.requiredImports) {
|
|
527
512
|
for (const imp of structure.requiredImports) {
|
|
528
513
|
if (!analysis.imports.includes(imp)) {
|
package/commands/plan.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { dbGet, dbAll, dbRun } from "../db/connection";
|
|
2
2
|
import { initSchema, getArchitecturalAnalysisForSpec } from "../db/schema";
|
|
3
3
|
import { resolveSpec } from "./spec-resolver";
|
|
4
4
|
import { CodexaError, ValidationError } from "../errors";
|
|
@@ -154,16 +154,16 @@ export function generateSpecId(name: string): string {
|
|
|
154
154
|
|
|
155
155
|
// v8.4: Suporte a --from-analysis para import automatico de baby steps
|
|
156
156
|
// v9.9: Suporte a fases com budget calculator e auto-split
|
|
157
|
-
export function planStart(description: string, options: { fromAnalysis?: string; json?: boolean; maxTasksPerPhase?: number } = {}): void {
|
|
158
|
-
initSchema();
|
|
159
|
-
const db = getDb();
|
|
157
|
+
export async function planStart(description: string, options: { fromAnalysis?: string; json?: boolean; maxTasksPerPhase?: number } = {}): Promise<void> {
|
|
158
|
+
await initSchema();
|
|
160
159
|
|
|
161
160
|
// v8.4: Buscar analise arquitetural se --from-analysis fornecido
|
|
162
161
|
let analysis: any = null;
|
|
163
162
|
if (options.fromAnalysis) {
|
|
164
|
-
analysis =
|
|
165
|
-
"SELECT * FROM architectural_analyses WHERE id = ?"
|
|
166
|
-
|
|
163
|
+
analysis = await dbGet<any>(
|
|
164
|
+
"SELECT * FROM architectural_analyses WHERE id = ?",
|
|
165
|
+
[options.fromAnalysis]
|
|
166
|
+
);
|
|
167
167
|
|
|
168
168
|
if (!analysis) {
|
|
169
169
|
if (options.json) {
|
|
@@ -186,7 +186,7 @@ export function planStart(description: string, options: { fromAnalysis?: string;
|
|
|
186
186
|
} else {
|
|
187
187
|
// v8.4: Auto-deteccao por nome
|
|
188
188
|
// v10.0: Alertar se analise aprovada existe mas --from-analysis nao foi usado
|
|
189
|
-
const match = getArchitecturalAnalysisForSpec(description);
|
|
189
|
+
const match = await getArchitecturalAnalysisForSpec(description);
|
|
190
190
|
if (match && match.status === "approved") {
|
|
191
191
|
const msg = `Analise arquitetural aprovada encontrada: ${match.id} (${match.name}).\n`
|
|
192
192
|
+ `Use: plan start "${description}" --from-analysis ${match.id}`;
|
|
@@ -215,19 +215,19 @@ export function planStart(description: string, options: { fromAnalysis?: string;
|
|
|
215
215
|
|
|
216
216
|
// Criar spec (com analysis_id se disponivel)
|
|
217
217
|
if (analysis) {
|
|
218
|
-
|
|
218
|
+
await dbRun(
|
|
219
219
|
"INSERT INTO specs (id, name, phase, analysis_id, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?)",
|
|
220
220
|
[specId, description, "planning", analysis.id, now, now]
|
|
221
221
|
);
|
|
222
222
|
} else {
|
|
223
|
-
|
|
223
|
+
await dbRun(
|
|
224
224
|
"INSERT INTO specs (id, name, phase, created_at, updated_at) VALUES (?, ?, ?, ?, ?)",
|
|
225
225
|
[specId, description, "planning", now, now]
|
|
226
226
|
);
|
|
227
227
|
}
|
|
228
228
|
|
|
229
229
|
// Criar contexto inicial
|
|
230
|
-
|
|
230
|
+
await dbRun(
|
|
231
231
|
"INSERT INTO context (spec_id, objective, updated_at) VALUES (?, ?, ?)",
|
|
232
232
|
[specId, description, now]
|
|
233
233
|
);
|
|
@@ -266,7 +266,7 @@ export function planStart(description: string, options: { fromAnalysis?: string;
|
|
|
266
266
|
const phase = phaseMap.get(step.number) || 1;
|
|
267
267
|
|
|
268
268
|
const normalizedStepAgent = step.agent ? resolveAgentName(step.agent) : null;
|
|
269
|
-
|
|
269
|
+
await dbRun(
|
|
270
270
|
`INSERT INTO tasks (spec_id, number, name, agent, depends_on, can_parallel, files, phase)
|
|
271
271
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
272
272
|
[specId, step.number, step.name, normalizedStepAgent, dependsOn, 1, files, phase]
|
|
@@ -275,13 +275,13 @@ export function planStart(description: string, options: { fromAnalysis?: string;
|
|
|
275
275
|
}
|
|
276
276
|
|
|
277
277
|
// Atualizar total de tasks e fases no contexto
|
|
278
|
-
|
|
278
|
+
await dbRun(
|
|
279
279
|
"UPDATE context SET total_tasks = ?, total_phases = ?, current_phase = 1, updated_at = ? WHERE spec_id = ?",
|
|
280
280
|
[tasksCreated, totalPhases, now, specId]
|
|
281
281
|
);
|
|
282
282
|
|
|
283
283
|
// Marcar analise como implemented
|
|
284
|
-
|
|
284
|
+
await dbRun(
|
|
285
285
|
"UPDATE architectural_analyses SET status = 'implemented', updated_at = ? WHERE id = ?",
|
|
286
286
|
[now, analysis.id]
|
|
287
287
|
);
|
|
@@ -301,8 +301,8 @@ export function planStart(description: string, options: { fromAnalysis?: string;
|
|
|
301
301
|
console.error(`\n[ERRO] ${msg}\n`);
|
|
302
302
|
}
|
|
303
303
|
// Cleanup: remover spec vazio criado
|
|
304
|
-
|
|
305
|
-
|
|
304
|
+
await dbRun("DELETE FROM context WHERE spec_id = ?", [specId]);
|
|
305
|
+
await dbRun("DELETE FROM specs WHERE id = ?", [specId]);
|
|
306
306
|
throw new CodexaError(msg);
|
|
307
307
|
}
|
|
308
308
|
}
|
|
@@ -341,7 +341,7 @@ export function planStart(description: string, options: { fromAnalysis?: string;
|
|
|
341
341
|
console.log(`\n Plano dividido em ${totalPhases} fases:`);
|
|
342
342
|
// Count tasks per phase
|
|
343
343
|
const tasksByPhase = new Map<number, number>();
|
|
344
|
-
const allTasks =
|
|
344
|
+
const allTasks = await dbAll<any>("SELECT phase FROM tasks WHERE spec_id = ?", [specId]);
|
|
345
345
|
for (const t of allTasks) {
|
|
346
346
|
tasksByPhase.set(t.phase || 1, (tasksByPhase.get(t.phase || 1) || 0) + 1);
|
|
347
347
|
}
|
|
@@ -367,19 +367,20 @@ export function planStart(description: string, options: { fromAnalysis?: string;
|
|
|
367
367
|
}
|
|
368
368
|
}
|
|
369
369
|
|
|
370
|
-
export function planShow(json: boolean = false, specId?: string): void {
|
|
371
|
-
initSchema();
|
|
372
|
-
const db = getDb();
|
|
370
|
+
export async function planShow(json: boolean = false, specId?: string): Promise<void> {
|
|
371
|
+
await initSchema();
|
|
373
372
|
|
|
374
373
|
const spec = resolveSpec(specId);
|
|
375
374
|
|
|
376
|
-
const tasks =
|
|
377
|
-
|
|
378
|
-
|
|
375
|
+
const tasks = await dbAll<any>(
|
|
376
|
+
"SELECT * FROM tasks WHERE spec_id = ? ORDER BY number",
|
|
377
|
+
[spec.id]
|
|
378
|
+
);
|
|
379
379
|
|
|
380
|
-
const context =
|
|
381
|
-
|
|
382
|
-
|
|
380
|
+
const context = await dbGet<any>(
|
|
381
|
+
"SELECT * FROM context WHERE spec_id = ?",
|
|
382
|
+
[spec.id]
|
|
383
|
+
);
|
|
383
384
|
|
|
384
385
|
if (json) {
|
|
385
386
|
console.log(JSON.stringify({ spec, context, tasks }, null, 2));
|
|
@@ -450,23 +451,23 @@ function printTaskLine(task: any): void {
|
|
|
450
451
|
console.log(`${statusIcon} #${task.number}: ${task.name} (${task.agent || "geral"})${depsStr}${parallelStr}`);
|
|
451
452
|
}
|
|
452
453
|
|
|
453
|
-
export function planTaskAdd(options: {
|
|
454
|
+
export async function planTaskAdd(options: {
|
|
454
455
|
name: string;
|
|
455
456
|
agent?: string;
|
|
456
457
|
depends?: string;
|
|
457
458
|
files?: string;
|
|
458
459
|
sequential?: boolean;
|
|
459
460
|
specId?: string;
|
|
460
|
-
}): void {
|
|
461
|
-
initSchema();
|
|
462
|
-
const db = getDb();
|
|
461
|
+
}): Promise<void> {
|
|
462
|
+
await initSchema();
|
|
463
463
|
|
|
464
464
|
const spec = resolveSpec(options.specId, ["planning"]);
|
|
465
465
|
|
|
466
466
|
// Pegar proximo numero
|
|
467
|
-
const lastTask =
|
|
468
|
-
|
|
469
|
-
|
|
467
|
+
const lastTask = await dbGet<any>(
|
|
468
|
+
"SELECT MAX(number) as max FROM tasks WHERE spec_id = ?",
|
|
469
|
+
[spec.id]
|
|
470
|
+
);
|
|
470
471
|
const nextNumber = (lastTask?.max || 0) + 1;
|
|
471
472
|
|
|
472
473
|
// Validar dependencias
|
|
@@ -474,9 +475,10 @@ export function planTaskAdd(options: {
|
|
|
474
475
|
if (options.depends) {
|
|
475
476
|
dependsOn = options.depends.split(",").map((s) => parseInt(s.trim()));
|
|
476
477
|
for (const depId of dependsOn) {
|
|
477
|
-
const exists =
|
|
478
|
-
|
|
479
|
-
|
|
478
|
+
const exists = await dbGet(
|
|
479
|
+
"SELECT id FROM tasks WHERE spec_id = ? AND number = ?",
|
|
480
|
+
[spec.id, depId]
|
|
481
|
+
);
|
|
480
482
|
if (!exists) {
|
|
481
483
|
throw new ValidationError(`Dependencia invalida: task #${depId} nao existe.`);
|
|
482
484
|
}
|
|
@@ -484,9 +486,10 @@ export function planTaskAdd(options: {
|
|
|
484
486
|
|
|
485
487
|
// v8.1: Detectar dependencias circulares
|
|
486
488
|
// Construir grafo de dependencias e verificar se adicionar esta task cria ciclo
|
|
487
|
-
const allTasks =
|
|
488
|
-
|
|
489
|
-
|
|
489
|
+
const allTasks = await dbAll<any>(
|
|
490
|
+
"SELECT number, depends_on FROM tasks WHERE spec_id = ?",
|
|
491
|
+
[spec.id]
|
|
492
|
+
);
|
|
490
493
|
|
|
491
494
|
// Grafo: taskNumber -> [dependencias]
|
|
492
495
|
const graph = new Map<number, number[]>();
|
|
@@ -553,7 +556,7 @@ export function planTaskAdd(options: {
|
|
|
553
556
|
files = options.files.split(",").map((s) => s.trim());
|
|
554
557
|
}
|
|
555
558
|
|
|
556
|
-
|
|
559
|
+
await dbRun(
|
|
557
560
|
`INSERT INTO tasks (spec_id, number, name, agent, depends_on, can_parallel, files)
|
|
558
561
|
VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
|
559
562
|
[
|
|
@@ -568,9 +571,9 @@ export function planTaskAdd(options: {
|
|
|
568
571
|
);
|
|
569
572
|
|
|
570
573
|
// Atualizar total de tasks no contexto
|
|
571
|
-
const count =
|
|
572
|
-
|
|
573
|
-
count
|
|
574
|
+
const count = await dbGet<any>("SELECT COUNT(*) as c FROM tasks WHERE spec_id = ?", [spec.id]);
|
|
575
|
+
await dbRun("UPDATE context SET total_tasks = ?, updated_at = ? WHERE spec_id = ?", [
|
|
576
|
+
count?.c || 0,
|
|
574
577
|
new Date().toISOString(),
|
|
575
578
|
spec.id,
|
|
576
579
|
]);
|
|
@@ -582,22 +585,21 @@ export function planTaskAdd(options: {
|
|
|
582
585
|
console.log(` Paralelizavel: ${options.sequential ? "Nao" : "Sim"}\n`);
|
|
583
586
|
}
|
|
584
587
|
|
|
585
|
-
export function planCancel(specId?: string): void {
|
|
586
|
-
initSchema();
|
|
587
|
-
const db = getDb();
|
|
588
|
+
export async function planCancel(specId?: string): Promise<void> {
|
|
589
|
+
await initSchema();
|
|
588
590
|
|
|
589
591
|
const spec = resolveSpec(specId);
|
|
590
592
|
|
|
591
593
|
const now = new Date().toISOString();
|
|
592
594
|
|
|
593
595
|
// Marcar spec como cancelled
|
|
594
|
-
|
|
596
|
+
await dbRun(
|
|
595
597
|
"UPDATE specs SET phase = 'cancelled', updated_at = ? WHERE id = ?",
|
|
596
598
|
[now, spec.id]
|
|
597
599
|
);
|
|
598
600
|
|
|
599
601
|
// Marcar tasks pendentes/running como cancelled
|
|
600
|
-
const updatedTasks =
|
|
602
|
+
const updatedTasks = await dbRun(
|
|
601
603
|
"UPDATE tasks SET status = 'cancelled' WHERE spec_id = ? AND status IN ('pending', 'running')",
|
|
602
604
|
[spec.id]
|
|
603
605
|
);
|