@codexa/cli 9.0.7 → 9.0.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/decide.ts +120 -3
- package/commands/discover.ts +18 -9
- package/commands/integration.test.ts +754 -0
- package/commands/knowledge.test.ts +2 -6
- package/commands/knowledge.ts +20 -4
- package/commands/patterns.ts +8 -644
- package/commands/product.ts +41 -104
- package/commands/spec-resolver.test.ts +2 -13
- package/commands/standards.ts +33 -3
- package/commands/task.ts +21 -4
- package/commands/utils.test.ts +25 -87
- package/commands/utils.ts +20 -82
- package/context/assembly.ts +11 -12
- package/context/domains.test.ts +300 -0
- package/context/domains.ts +157 -0
- package/context/generator.ts +14 -13
- package/context/index.ts +6 -1
- package/context/references.test.ts +159 -0
- package/context/references.ts +159 -0
- package/context/sections.ts +18 -1
- package/db/schema.ts +40 -5
- package/db/test-helpers.ts +33 -0
- package/gates/standards-validator.test.ts +447 -0
- package/gates/standards-validator.ts +164 -125
- package/gates/typecheck-validator.ts +296 -92
- package/gates/validator.ts +93 -8
- package/package.json +1 -1
- package/protocol/process-return.ts +39 -4
- package/workflow.ts +54 -84
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { describe, it, expect, beforeAll, afterAll } from "bun:test";
|
|
2
|
+
import { join } from "path";
|
|
3
|
+
import { mkdirSync, rmSync, writeFileSync } from "fs";
|
|
4
|
+
import { findReferenceFiles } from "./references";
|
|
5
|
+
|
|
6
|
+
// ── Test fixtures ────────────────────────────────────────────
|
|
7
|
+
|
|
8
|
+
const TMP_DIR = join(import.meta.dir, "__test_refs__");
|
|
9
|
+
|
|
10
|
+
function ensureDir(dir: string) {
|
|
11
|
+
mkdirSync(dir, { recursive: true });
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function writeTmpFile(relativePath: string, content: string): string {
|
|
15
|
+
const full = join(TMP_DIR, relativePath);
|
|
16
|
+
ensureDir(join(full, ".."));
|
|
17
|
+
writeFileSync(full, content);
|
|
18
|
+
return relativePath;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
beforeAll(() => {
|
|
22
|
+
ensureDir(TMP_DIR);
|
|
23
|
+
|
|
24
|
+
// src/routes/ — 3 route files of different sizes
|
|
25
|
+
writeTmpFile("src/routes/users.ts", "export function getUsers() { return []; }\nexport function createUser() { return {}; }\n// more code here to make it larger\n".repeat(5));
|
|
26
|
+
writeTmpFile("src/routes/products.ts", "export function getProducts() { return []; }\n");
|
|
27
|
+
writeTmpFile("src/routes/orders.ts", "export function getOrders() { return []; }\nexport function createOrder() { return {}; }\n");
|
|
28
|
+
|
|
29
|
+
// src/services/ — 2 service files
|
|
30
|
+
writeTmpFile("src/services/auth.service.ts", "export class AuthService { login() {} logout() {} verify() {} }\n".repeat(3));
|
|
31
|
+
writeTmpFile("src/services/user.service.ts", "export class UserService { getById() {} }\n");
|
|
32
|
+
|
|
33
|
+
// src/components/ — React components
|
|
34
|
+
writeTmpFile("src/components/Button.tsx", "export function Button() { return <button/>; }\n");
|
|
35
|
+
writeTmpFile("src/components/Card.tsx", "export function Card() { return <div/>; }\nexport function CardHeader() { return <div/>; }\n".repeat(3));
|
|
36
|
+
|
|
37
|
+
// src/modules/users/ and src/modules/payments/ — parallel structure
|
|
38
|
+
writeTmpFile("src/modules/users/route.ts", "export function usersRoute() { return {}; }\n".repeat(4));
|
|
39
|
+
writeTmpFile("src/modules/users/service.ts", "export function usersService() {}\n");
|
|
40
|
+
writeTmpFile("src/modules/payments/service.ts", "export function paymentsService() {}\n");
|
|
41
|
+
|
|
42
|
+
// tests/ — test files
|
|
43
|
+
writeTmpFile("tests/routes/users.test.ts", "describe('users', () => { it('works', () => {}); });\n".repeat(2));
|
|
44
|
+
|
|
45
|
+
// node_modules/ — should be ignored
|
|
46
|
+
writeTmpFile("node_modules/pkg/index.ts", "export const x = 1;\n");
|
|
47
|
+
|
|
48
|
+
// Non-code file
|
|
49
|
+
writeTmpFile("docs/readme.md", "# Readme\n");
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
afterAll(() => {
|
|
53
|
+
rmSync(TMP_DIR, { recursive: true, force: true });
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
// ── Tests ────────────────────────────────────────────────────
|
|
57
|
+
|
|
58
|
+
describe("findReferenceFiles", () => {
|
|
59
|
+
it("finds siblings in the same directory", () => {
|
|
60
|
+
// Target: new file in src/routes/ — should find existing routes
|
|
61
|
+
const refs = findReferenceFiles(["src/routes/payments.ts"], TMP_DIR);
|
|
62
|
+
|
|
63
|
+
expect(refs.length).toBeGreaterThanOrEqual(1);
|
|
64
|
+
expect(refs[0].reason).toBe("mesmo diretorio");
|
|
65
|
+
// Should pick the largest sibling (users.ts has the most content)
|
|
66
|
+
expect(refs[0].path).toBe("src/routes/users.ts");
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it("prioritizes largest file as reference", () => {
|
|
70
|
+
const refs = findReferenceFiles(["src/components/Modal.tsx"], TMP_DIR);
|
|
71
|
+
|
|
72
|
+
expect(refs.length).toBe(1);
|
|
73
|
+
// Card.tsx is larger than Button.tsx
|
|
74
|
+
expect(refs[0].path).toBe("src/components/Card.tsx");
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it("finds namesakes when no siblings exist", () => {
|
|
78
|
+
// Target: src/modules/payments/route.ts — no siblings, but src/modules/users/route.ts exists
|
|
79
|
+
const refs = findReferenceFiles(["src/modules/payments/route.ts"], TMP_DIR);
|
|
80
|
+
|
|
81
|
+
expect(refs.length).toBeGreaterThanOrEqual(1);
|
|
82
|
+
const paths = refs.map(r => r.path);
|
|
83
|
+
// Should find the namesake route.ts or a sibling service.ts
|
|
84
|
+
expect(
|
|
85
|
+
paths.some(p => p.includes("route.ts") || p.includes("service.ts"))
|
|
86
|
+
).toBe(true);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it("returns empty for non-code files", () => {
|
|
90
|
+
const refs = findReferenceFiles(["docs/notes.md"], TMP_DIR);
|
|
91
|
+
expect(refs).toEqual([]);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it("returns empty for empty task files", () => {
|
|
95
|
+
expect(findReferenceFiles([], TMP_DIR)).toEqual([]);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it("returns empty for null/undefined", () => {
|
|
99
|
+
expect(findReferenceFiles(null as any, TMP_DIR)).toEqual([]);
|
|
100
|
+
expect(findReferenceFiles(undefined as any, TMP_DIR)).toEqual([]);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it("ignores node_modules", () => {
|
|
104
|
+
const refs = findReferenceFiles(["node_modules/pkg/new.ts"], TMP_DIR);
|
|
105
|
+
// Even if node_modules has .ts files, targets in ignored dirs get no refs
|
|
106
|
+
// (the target itself is filtered by extension check, not by ignored dir)
|
|
107
|
+
// But results should never point INTO node_modules
|
|
108
|
+
for (const ref of refs) {
|
|
109
|
+
expect(ref.path).not.toContain("node_modules");
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it("limits to 3 references max", () => {
|
|
114
|
+
const refs = findReferenceFiles([
|
|
115
|
+
"src/routes/payments.ts",
|
|
116
|
+
"src/services/payment.service.ts",
|
|
117
|
+
"src/components/Modal.tsx",
|
|
118
|
+
"tests/routes/products.test.ts",
|
|
119
|
+
], TMP_DIR);
|
|
120
|
+
|
|
121
|
+
expect(refs.length).toBeLessThanOrEqual(3);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it("deduplicates references", () => {
|
|
125
|
+
const refs = findReferenceFiles([
|
|
126
|
+
"src/routes/payments.ts",
|
|
127
|
+
"src/routes/invoices.ts",
|
|
128
|
+
], TMP_DIR);
|
|
129
|
+
|
|
130
|
+
const paths = refs.map(r => r.path);
|
|
131
|
+
const unique = new Set(paths);
|
|
132
|
+
expect(paths.length).toBe(unique.size);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it("handles Windows-style backslash paths", () => {
|
|
136
|
+
const refs = findReferenceFiles(["src\\routes\\payments.ts"], TMP_DIR);
|
|
137
|
+
expect(refs.length).toBeGreaterThanOrEqual(1);
|
|
138
|
+
// All returned paths should use forward slashes
|
|
139
|
+
for (const ref of refs) {
|
|
140
|
+
expect(ref.path).not.toContain("\\");
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it("finds reference for test files", () => {
|
|
145
|
+
const refs = findReferenceFiles(["tests/routes/products.test.ts"], TMP_DIR);
|
|
146
|
+
expect(refs.length).toBeGreaterThanOrEqual(1);
|
|
147
|
+
expect(refs[0].path).toContain("users.test.ts");
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it("uses parent directory as fallback", () => {
|
|
151
|
+
// Target in a new subdirectory with no siblings or namesakes
|
|
152
|
+
writeTmpFile("src/features/billing/handler.ts", "export function handle() {}\n");
|
|
153
|
+
const refs = findReferenceFiles(["src/features/billing/processor.ts"], TMP_DIR);
|
|
154
|
+
// Should find handler.ts as sibling
|
|
155
|
+
if (refs.length > 0) {
|
|
156
|
+
expect(refs[0].path).toContain("billing");
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
});
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { join, dirname, basename, extname, resolve, relative } from "path";
|
|
2
|
+
import { globSync } from "fs";
|
|
3
|
+
|
|
4
|
+
// ═══════════════════════════════════════════════════════════════
|
|
5
|
+
// REFERENCE FILES — Pattern by Example (v9.5)
|
|
6
|
+
//
|
|
7
|
+
// Finds existing files similar to task targets so subagents can
|
|
8
|
+
// read them and replicate the coding style exactly.
|
|
9
|
+
// ═══════════════════════════════════════════════════════════════
|
|
10
|
+
|
|
11
|
+
export interface ReferenceFile {
|
|
12
|
+
path: string;
|
|
13
|
+
reason: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const IGNORED_DIRS = new Set([
|
|
17
|
+
"node_modules", ".git", "dist", "build", ".next", ".codexa",
|
|
18
|
+
".nuxt", ".output", "coverage", "__pycache__", ".venv", "vendor",
|
|
19
|
+
"target", "bin", "obj",
|
|
20
|
+
]);
|
|
21
|
+
|
|
22
|
+
const CODE_EXTENSIONS = new Set([
|
|
23
|
+
".ts", ".tsx", ".js", ".jsx", ".vue", ".svelte",
|
|
24
|
+
".go", ".rs", ".py", ".cs", ".kt", ".java",
|
|
25
|
+
".dart", ".php", ".rb", ".swift",
|
|
26
|
+
]);
|
|
27
|
+
|
|
28
|
+
function normalizePath(p: string): string {
|
|
29
|
+
return p.replace(/\\/g, "/");
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function isIgnored(filePath: string): boolean {
|
|
33
|
+
const parts = normalizePath(filePath).split("/");
|
|
34
|
+
return parts.some(part => IGNORED_DIRS.has(part));
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function getFileSize(filePath: string): number {
|
|
38
|
+
try {
|
|
39
|
+
return Bun.file(filePath).size;
|
|
40
|
+
} catch {
|
|
41
|
+
return 0;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function fileExists(filePath: string): boolean {
|
|
46
|
+
try {
|
|
47
|
+
return Bun.file(filePath).size > 0;
|
|
48
|
+
} catch {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function globFiles(pattern: string, cwd: string): string[] {
|
|
54
|
+
try {
|
|
55
|
+
const glob = new Bun.Glob(pattern);
|
|
56
|
+
const results: string[] = [];
|
|
57
|
+
for (const match of glob.scanSync({ cwd, dot: false })) {
|
|
58
|
+
const full = join(cwd, match);
|
|
59
|
+
if (!isIgnored(full)) {
|
|
60
|
+
results.push(normalizePath(full));
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return results;
|
|
64
|
+
} catch {
|
|
65
|
+
return [];
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function pickLargest(files: string[]): string | null {
|
|
70
|
+
if (files.length === 0) return null;
|
|
71
|
+
|
|
72
|
+
let best = files[0];
|
|
73
|
+
let bestSize = getFileSize(files[0]);
|
|
74
|
+
|
|
75
|
+
for (let i = 1; i < files.length; i++) {
|
|
76
|
+
const size = getFileSize(files[i]);
|
|
77
|
+
if (size > bestSize) {
|
|
78
|
+
best = files[i];
|
|
79
|
+
bestSize = size;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return bestSize > 0 ? best : null;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Find reference files for a set of task target files.
|
|
88
|
+
*
|
|
89
|
+
* Strategy per target file:
|
|
90
|
+
* 1. Siblings — other files in the same directory with same extension
|
|
91
|
+
* 2. Namesakes — files with same basename in other directories
|
|
92
|
+
* 3. Parent — files with same extension in parent directory tree
|
|
93
|
+
*
|
|
94
|
+
* Returns max 3 deduplicated reference file paths.
|
|
95
|
+
*/
|
|
96
|
+
export function findReferenceFiles(
|
|
97
|
+
taskFiles: string[],
|
|
98
|
+
projectRoot: string,
|
|
99
|
+
): ReferenceFile[] {
|
|
100
|
+
if (!taskFiles || taskFiles.length === 0) return [];
|
|
101
|
+
|
|
102
|
+
const seen = new Set<string>();
|
|
103
|
+
const results: ReferenceFile[] = [];
|
|
104
|
+
const normalizedRoot = normalizePath(resolve(projectRoot));
|
|
105
|
+
|
|
106
|
+
for (const rawTarget of taskFiles) {
|
|
107
|
+
if (results.length >= 3) break;
|
|
108
|
+
|
|
109
|
+
const target = normalizePath(resolve(projectRoot, rawTarget));
|
|
110
|
+
const ext = extname(target);
|
|
111
|
+
const base = basename(target);
|
|
112
|
+
const dir = dirname(target);
|
|
113
|
+
|
|
114
|
+
if (!ext || !CODE_EXTENSIONS.has(ext)) continue;
|
|
115
|
+
|
|
116
|
+
// Strategy 1: Siblings (same directory, same extension)
|
|
117
|
+
const siblings = globFiles(`*${ext}`, dir)
|
|
118
|
+
.filter(f => f !== target && !seen.has(f) && fileExists(f));
|
|
119
|
+
|
|
120
|
+
const bestSibling = pickLargest(siblings);
|
|
121
|
+
if (bestSibling) {
|
|
122
|
+
const rel = normalizePath(relative(normalizedRoot, bestSibling));
|
|
123
|
+
seen.add(bestSibling);
|
|
124
|
+
results.push({ path: rel, reason: "mesmo diretorio" });
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Strategy 2: Namesakes (same filename in other directories)
|
|
129
|
+
const namesakes = globFiles(`**/${base}`, normalizedRoot)
|
|
130
|
+
.filter(f => f !== target && !seen.has(f) && fileExists(f));
|
|
131
|
+
|
|
132
|
+
if (namesakes.length > 0) {
|
|
133
|
+
// Pick the one closest by path depth similarity
|
|
134
|
+
const bestNamesake = pickLargest(namesakes);
|
|
135
|
+
if (bestNamesake) {
|
|
136
|
+
const rel = normalizePath(relative(normalizedRoot, bestNamesake));
|
|
137
|
+
seen.add(bestNamesake);
|
|
138
|
+
results.push({ path: rel, reason: "mesmo tipo de arquivo" });
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Strategy 3: Parent directory search (same extension, one level up)
|
|
144
|
+
const parentDir = dirname(dir);
|
|
145
|
+
if (parentDir !== dir) {
|
|
146
|
+
const parentFiles = globFiles(`**/*${ext}`, parentDir)
|
|
147
|
+
.filter(f => f !== target && !seen.has(f) && fileExists(f));
|
|
148
|
+
|
|
149
|
+
const bestParent = pickLargest(parentFiles);
|
|
150
|
+
if (bestParent) {
|
|
151
|
+
const rel = normalizePath(relative(normalizedRoot, bestParent));
|
|
152
|
+
seen.add(bestParent);
|
|
153
|
+
results.push({ path: rel, reason: "arquivo similar proximo" });
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return results;
|
|
159
|
+
}
|
package/context/sections.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { getUtilitiesForContext, getAgentHints } from "../db/schema";
|
|
2
2
|
import type { ContextSection, ContextData } from "./assembly";
|
|
3
|
+
import { getAgentDomain, domainToScope } from "./domains";
|
|
4
|
+
import { findReferenceFiles } from "./references";
|
|
3
5
|
|
|
4
6
|
// ── Section Builders ──────────────────────────────────────────
|
|
5
7
|
|
|
@@ -162,7 +164,7 @@ export function buildUtilitiesSection(data: ContextData): ContextSection | null
|
|
|
162
164
|
return parts.slice(0, -1).join("/");
|
|
163
165
|
}).filter(Boolean))];
|
|
164
166
|
|
|
165
|
-
const agentScope = data.task.agent
|
|
167
|
+
const agentScope = domainToScope(getAgentDomain(data.task.agent)) || undefined;
|
|
166
168
|
let relevantUtilities = getUtilitiesForContext(taskDirs, undefined, 15);
|
|
167
169
|
|
|
168
170
|
if (relevantUtilities.length < 5 && agentScope) {
|
|
@@ -244,3 +246,18 @@ ${hints.map(h => `- ${h}`).join("\n")}
|
|
|
244
246
|
`;
|
|
245
247
|
return { name: "HINTS", content, priority: 10 };
|
|
246
248
|
}
|
|
249
|
+
|
|
250
|
+
export function buildReferencesSection(data: ContextData): ContextSection | null {
|
|
251
|
+
if (data.taskFiles.length === 0) return null;
|
|
252
|
+
|
|
253
|
+
const projectRoot = process.cwd();
|
|
254
|
+
const refs = findReferenceFiles(data.taskFiles, projectRoot);
|
|
255
|
+
if (refs.length === 0) return null;
|
|
256
|
+
|
|
257
|
+
const content = `
|
|
258
|
+
### REFERENCIA DE ESTILO (${refs.length})
|
|
259
|
+
**ANTES de criar/modificar arquivos, leia estas referencias e replique o mesmo estilo** (nomenclatura, estrutura, imports, error handling):
|
|
260
|
+
${refs.map(r => `- \`${r.path}\` (${r.reason})`).join("\n")}
|
|
261
|
+
`;
|
|
262
|
+
return { name: "REFERENCIAS", content, priority: 2 };
|
|
263
|
+
}
|
package/db/schema.ts
CHANGED
|
@@ -237,7 +237,6 @@ export function initSchema(): void {
|
|
|
237
237
|
relation TEXT NOT NULL, -- uses, implements, depends_on, modifies, contradicts, extracted_from
|
|
238
238
|
|
|
239
239
|
-- Metadados
|
|
240
|
-
strength REAL DEFAULT 1.0, -- 0-1, peso da relacao
|
|
241
240
|
metadata TEXT, -- JSON com informacoes adicionais
|
|
242
241
|
|
|
243
242
|
created_at TEXT DEFAULT CURRENT_TIMESTAMP
|
|
@@ -459,6 +458,28 @@ const MIGRATIONS: Migration[] = [
|
|
|
459
458
|
}
|
|
460
459
|
},
|
|
461
460
|
},
|
|
461
|
+
{
|
|
462
|
+
version: "9.5.0",
|
|
463
|
+
description: "Adicionar superseded_by na tabela decisions",
|
|
464
|
+
up: (db) => {
|
|
465
|
+
db.exec(`ALTER TABLE decisions ADD COLUMN superseded_by TEXT`);
|
|
466
|
+
},
|
|
467
|
+
},
|
|
468
|
+
{
|
|
469
|
+
version: "9.6.0",
|
|
470
|
+
description: "Adicionar semantic_query e expect na tabela standards para validacao semantica stack-agnostica",
|
|
471
|
+
up: (db) => {
|
|
472
|
+
db.exec(`ALTER TABLE standards ADD COLUMN semantic_query TEXT`);
|
|
473
|
+
db.exec(`ALTER TABLE standards ADD COLUMN expect TEXT DEFAULT 'no_match'`);
|
|
474
|
+
},
|
|
475
|
+
},
|
|
476
|
+
{
|
|
477
|
+
version: "9.7.0",
|
|
478
|
+
description: "Adicionar typecheck_command na tabela project para typecheck agnostico de stack",
|
|
479
|
+
up: (db) => {
|
|
480
|
+
db.exec(`ALTER TABLE project ADD COLUMN typecheck_command TEXT`);
|
|
481
|
+
},
|
|
482
|
+
},
|
|
462
483
|
];
|
|
463
484
|
|
|
464
485
|
export function runMigrations(): void {
|
|
@@ -521,6 +542,22 @@ export function claimTask(taskId: number): boolean {
|
|
|
521
542
|
return result.changes > 0;
|
|
522
543
|
}
|
|
523
544
|
|
|
545
|
+
const STUCK_THRESHOLD_MS = 30 * 60 * 1000; // 30 minutos
|
|
546
|
+
|
|
547
|
+
export function detectStuckTasks(specId: string): any[] {
|
|
548
|
+
const db = getDb();
|
|
549
|
+
const running = db.query(
|
|
550
|
+
"SELECT * FROM tasks WHERE spec_id = ? AND status = 'running'"
|
|
551
|
+
).all(specId) as any[];
|
|
552
|
+
|
|
553
|
+
const now = Date.now();
|
|
554
|
+
return running.filter((task: any) => {
|
|
555
|
+
if (!task.started_at) return true;
|
|
556
|
+
const elapsed = now - new Date(task.started_at).getTime();
|
|
557
|
+
return elapsed > STUCK_THRESHOLD_MS;
|
|
558
|
+
});
|
|
559
|
+
}
|
|
560
|
+
|
|
524
561
|
export function getPatternsByScope(scope: string): any[] {
|
|
525
562
|
const db = getDb();
|
|
526
563
|
return db.query(
|
|
@@ -582,7 +619,6 @@ export interface GraphRelation {
|
|
|
582
619
|
targetType: string;
|
|
583
620
|
targetId: string;
|
|
584
621
|
relation: string;
|
|
585
|
-
strength?: number;
|
|
586
622
|
metadata?: Record<string, any>;
|
|
587
623
|
}
|
|
588
624
|
|
|
@@ -591,8 +627,8 @@ export function addGraphRelation(specId: string | null, relation: GraphRelation)
|
|
|
591
627
|
const now = new Date().toISOString();
|
|
592
628
|
|
|
593
629
|
db.run(
|
|
594
|
-
`INSERT INTO knowledge_graph (spec_id, source_type, source_id, target_type, target_id, relation,
|
|
595
|
-
VALUES (?, ?, ?, ?, ?, ?, ?,
|
|
630
|
+
`INSERT INTO knowledge_graph (spec_id, source_type, source_id, target_type, target_id, relation, metadata, created_at)
|
|
631
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
596
632
|
[
|
|
597
633
|
specId,
|
|
598
634
|
relation.sourceType,
|
|
@@ -600,7 +636,6 @@ export function addGraphRelation(specId: string | null, relation: GraphRelation)
|
|
|
600
636
|
relation.targetType,
|
|
601
637
|
relation.targetId,
|
|
602
638
|
relation.relation,
|
|
603
|
-
relation.strength || 1.0,
|
|
604
639
|
relation.metadata ? JSON.stringify(relation.metadata) : null,
|
|
605
640
|
now,
|
|
606
641
|
]
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { getDb } from "./connection";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Limpa todas as tabelas do DB respeitando ordem de FK constraints.
|
|
5
|
+
* Usa PRAGMA foreign_keys = OFF como safety net.
|
|
6
|
+
*
|
|
7
|
+
* Uso: beforeEach(() => { initSchema(); cleanDb(); });
|
|
8
|
+
*/
|
|
9
|
+
export function cleanDb() {
|
|
10
|
+
const db = getDb();
|
|
11
|
+
db.run("PRAGMA foreign_keys = OFF");
|
|
12
|
+
try {
|
|
13
|
+
// Folhas (sem dependentes) primeiro
|
|
14
|
+
db.run("DELETE FROM knowledge_acknowledgments");
|
|
15
|
+
db.run("DELETE FROM agent_performance");
|
|
16
|
+
db.run("DELETE FROM reasoning_log");
|
|
17
|
+
db.run("DELETE FROM knowledge_graph");
|
|
18
|
+
db.run("DELETE FROM gate_bypasses");
|
|
19
|
+
db.run("DELETE FROM snapshots");
|
|
20
|
+
db.run("DELETE FROM review");
|
|
21
|
+
db.run("DELETE FROM project_utilities");
|
|
22
|
+
db.run("DELETE FROM artifacts");
|
|
23
|
+
db.run("DELETE FROM decisions");
|
|
24
|
+
// Dependentes de knowledge (antes de knowledge)
|
|
25
|
+
db.run("DELETE FROM knowledge");
|
|
26
|
+
// Dependentes de tasks/specs
|
|
27
|
+
db.run("DELETE FROM tasks");
|
|
28
|
+
db.run("DELETE FROM context");
|
|
29
|
+
db.run("DELETE FROM specs");
|
|
30
|
+
} finally {
|
|
31
|
+
db.run("PRAGMA foreign_keys = ON");
|
|
32
|
+
}
|
|
33
|
+
}
|