@codexa/cli 9.0.30 → 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 +49 -47
- 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 +304 -298
- package/db/schema.ts +302 -392
- 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/db/test-helpers.ts
CHANGED
|
@@ -1,33 +1,22 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { dbRun } from "./connection";
|
|
2
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");
|
|
3
|
+
export async function cleanDb() {
|
|
12
4
|
try {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
db.run("DELETE FROM specs");
|
|
30
|
-
} finally {
|
|
31
|
-
db.run("PRAGMA foreign_keys = ON");
|
|
5
|
+
await dbRun("DELETE FROM knowledge_acknowledgments");
|
|
6
|
+
await dbRun("DELETE FROM agent_performance");
|
|
7
|
+
await dbRun("DELETE FROM reasoning_log");
|
|
8
|
+
await dbRun("DELETE FROM knowledge_graph");
|
|
9
|
+
await dbRun("DELETE FROM gate_bypasses");
|
|
10
|
+
await dbRun("DELETE FROM snapshots");
|
|
11
|
+
await dbRun("DELETE FROM review");
|
|
12
|
+
await dbRun("DELETE FROM project_utilities");
|
|
13
|
+
await dbRun("DELETE FROM artifacts");
|
|
14
|
+
await dbRun("DELETE FROM decisions");
|
|
15
|
+
await dbRun("DELETE FROM knowledge");
|
|
16
|
+
await dbRun("DELETE FROM tasks");
|
|
17
|
+
await dbRun("DELETE FROM context");
|
|
18
|
+
await dbRun("DELETE FROM specs");
|
|
19
|
+
} catch {
|
|
20
|
+
// Tables may not exist yet
|
|
32
21
|
}
|
|
33
22
|
}
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach,
|
|
2
|
-
import {
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach, spyOn } from "bun:test";
|
|
2
|
+
import { createClient } from "@libsql/client";
|
|
3
|
+
import { setClient, resetClient, dbRun } from "../db/connection";
|
|
3
4
|
import { initSchema, runMigrations } from "../db/schema";
|
|
4
5
|
import { writeFileSync, mkdirSync, rmSync, existsSync } from "fs";
|
|
5
6
|
import { join } from "path";
|
|
6
7
|
|
|
7
|
-
// Mock grepai functions before importing validator
|
|
8
8
|
import * as patterns from "../commands/patterns";
|
|
9
9
|
|
|
10
|
-
import { validateAgainstStandards
|
|
10
|
+
import { validateAgainstStandards } from "./standards-validator";
|
|
11
11
|
|
|
12
12
|
// ═══════════════════════════════════════════════════════════════
|
|
13
13
|
// HELPERS
|
|
@@ -28,7 +28,7 @@ function createTmpFile(name: string, content: string): string {
|
|
|
28
28
|
return path;
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
function insertStandard(opts: {
|
|
31
|
+
async function insertStandard(opts: {
|
|
32
32
|
category: string;
|
|
33
33
|
scope: string;
|
|
34
34
|
rule: string;
|
|
@@ -37,8 +37,7 @@ function insertStandard(opts: {
|
|
|
37
37
|
semantic_query?: string | null;
|
|
38
38
|
expect?: string;
|
|
39
39
|
}) {
|
|
40
|
-
|
|
41
|
-
db.run(
|
|
40
|
+
await dbRun(
|
|
42
41
|
`INSERT INTO standards (category, scope, rule, anti_examples, enforcement, semantic_query, expect, source, created_at)
|
|
43
42
|
VALUES (?, ?, ?, ?, ?, ?, ?, 'test', datetime('now'))`,
|
|
44
43
|
[
|
|
@@ -58,33 +57,37 @@ function insertStandard(opts: {
|
|
|
58
57
|
// ═══════════════════════════════════════════════════════════════
|
|
59
58
|
|
|
60
59
|
describe("standards-validator", () => {
|
|
61
|
-
beforeEach(() => {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
60
|
+
beforeEach(async () => {
|
|
61
|
+
const client = createClient({ url: ":memory:" });
|
|
62
|
+
setClient(client);
|
|
63
|
+
await initSchema();
|
|
64
|
+
await runMigrations();
|
|
65
|
+
await dbRun("DELETE FROM standards");
|
|
66
66
|
|
|
67
|
-
// Clean tmp files
|
|
68
67
|
if (existsSync(TMP_DIR)) {
|
|
69
68
|
rmSync(TMP_DIR, { recursive: true });
|
|
70
69
|
}
|
|
71
70
|
});
|
|
72
71
|
|
|
72
|
+
afterEach(() => {
|
|
73
|
+
resetClient();
|
|
74
|
+
});
|
|
75
|
+
|
|
73
76
|
// ─────────────────────────────────────────────────────────
|
|
74
77
|
// BASE CASES
|
|
75
78
|
// ─────────────────────────────────────────────────────────
|
|
76
79
|
|
|
77
|
-
it("returns passed when no standards exist", () => {
|
|
80
|
+
it("returns passed when no standards exist", async () => {
|
|
78
81
|
const file = createTmpFile("test.ts", "export const x = 1;");
|
|
79
|
-
const result = validateAgainstStandards([file], "all");
|
|
82
|
+
const result = await validateAgainstStandards([file], "all");
|
|
80
83
|
expect(result.passed).toBe(true);
|
|
81
84
|
expect(result.violations).toHaveLength(0);
|
|
82
85
|
expect(result.warnings).toHaveLength(0);
|
|
83
86
|
});
|
|
84
87
|
|
|
85
|
-
it("returns passed when files list is empty", () => {
|
|
86
|
-
insertStandard({ category: "code", scope: "all", rule: "No console.log" });
|
|
87
|
-
const result = validateAgainstStandards([], "all");
|
|
88
|
+
it("returns passed when files list is empty", async () => {
|
|
89
|
+
await insertStandard({ category: "code", scope: "all", rule: "No console.log" });
|
|
90
|
+
const result = await validateAgainstStandards([], "all");
|
|
88
91
|
expect(result.passed).toBe(true);
|
|
89
92
|
});
|
|
90
93
|
|
|
@@ -92,37 +95,37 @@ describe("standards-validator", () => {
|
|
|
92
95
|
// TEXTUAL VALIDATION (anti_examples)
|
|
93
96
|
// ─────────────────────────────────────────────────────────
|
|
94
97
|
|
|
95
|
-
it("detects anti_examples violation in file content", () => {
|
|
98
|
+
it("detects anti_examples violation in file content", async () => {
|
|
96
99
|
const file = createTmpFile("api.ts", 'console.log("debug"); export function handle() {}');
|
|
97
|
-
insertStandard({
|
|
100
|
+
await insertStandard({
|
|
98
101
|
category: "code",
|
|
99
102
|
scope: "all",
|
|
100
103
|
rule: "Nao usar console.log",
|
|
101
104
|
anti_examples: JSON.stringify(["console.log"]),
|
|
102
105
|
});
|
|
103
106
|
|
|
104
|
-
const result = validateAgainstStandards([file], "all");
|
|
107
|
+
const result = await validateAgainstStandards([file], "all");
|
|
105
108
|
expect(result.passed).toBe(false);
|
|
106
109
|
expect(result.violations).toHaveLength(1);
|
|
107
110
|
expect(result.violations[0].detail).toContain("console.log");
|
|
108
111
|
});
|
|
109
112
|
|
|
110
|
-
it("passes when anti_examples not found in file", () => {
|
|
113
|
+
it("passes when anti_examples not found in file", async () => {
|
|
111
114
|
const file = createTmpFile("api.ts", "export function handle() { return 'ok'; }");
|
|
112
|
-
insertStandard({
|
|
115
|
+
await insertStandard({
|
|
113
116
|
category: "code",
|
|
114
117
|
scope: "all",
|
|
115
118
|
rule: "Nao usar console.log",
|
|
116
119
|
anti_examples: JSON.stringify(["console.log"]),
|
|
117
120
|
});
|
|
118
121
|
|
|
119
|
-
const result = validateAgainstStandards([file], "all");
|
|
122
|
+
const result = await validateAgainstStandards([file], "all");
|
|
120
123
|
expect(result.passed).toBe(true);
|
|
121
124
|
});
|
|
122
125
|
|
|
123
|
-
it("treats recommended enforcement as warning not violation", () => {
|
|
126
|
+
it("treats recommended enforcement as warning not violation", async () => {
|
|
124
127
|
const file = createTmpFile("api.ts", 'console.log("test");');
|
|
125
|
-
insertStandard({
|
|
128
|
+
await insertStandard({
|
|
126
129
|
category: "code",
|
|
127
130
|
scope: "all",
|
|
128
131
|
rule: "Evitar console.log",
|
|
@@ -130,70 +133,67 @@ describe("standards-validator", () => {
|
|
|
130
133
|
enforcement: "recommended",
|
|
131
134
|
});
|
|
132
135
|
|
|
133
|
-
const result = validateAgainstStandards([file], "all");
|
|
134
|
-
expect(result.passed).toBe(true);
|
|
136
|
+
const result = await validateAgainstStandards([file], "all");
|
|
137
|
+
expect(result.passed).toBe(true);
|
|
135
138
|
expect(result.warnings).toHaveLength(1);
|
|
136
139
|
expect(result.violations).toHaveLength(0);
|
|
137
140
|
});
|
|
138
141
|
|
|
139
|
-
it("filters standards by scope", () => {
|
|
142
|
+
it("filters standards by scope", async () => {
|
|
140
143
|
const file = createTmpFile("api.ts", 'console.log("test");');
|
|
141
144
|
|
|
142
|
-
|
|
143
|
-
insertStandard({
|
|
145
|
+
await insertStandard({
|
|
144
146
|
category: "code",
|
|
145
147
|
scope: "frontend",
|
|
146
148
|
rule: "Frontend: no console.log",
|
|
147
149
|
anti_examples: JSON.stringify(["console.log"]),
|
|
148
150
|
});
|
|
149
151
|
|
|
150
|
-
|
|
151
|
-
const result = validateAgainstStandards([file], "backend");
|
|
152
|
+
const result = await validateAgainstStandards([file], "backend");
|
|
152
153
|
expect(result.passed).toBe(true);
|
|
153
154
|
});
|
|
154
155
|
|
|
155
|
-
it("matches scope 'all' for any domain", () => {
|
|
156
|
+
it("matches scope 'all' for any domain", async () => {
|
|
156
157
|
const file = createTmpFile("api.ts", 'console.log("test");');
|
|
157
|
-
insertStandard({
|
|
158
|
+
await insertStandard({
|
|
158
159
|
category: "code",
|
|
159
160
|
scope: "all",
|
|
160
161
|
rule: "No console.log",
|
|
161
162
|
anti_examples: JSON.stringify(["console.log"]),
|
|
162
163
|
});
|
|
163
164
|
|
|
164
|
-
const result = validateAgainstStandards([file], "backend");
|
|
165
|
+
const result = await validateAgainstStandards([file], "backend");
|
|
165
166
|
expect(result.passed).toBe(false);
|
|
166
167
|
});
|
|
167
168
|
|
|
168
|
-
it("skips nonexistent files gracefully", () => {
|
|
169
|
-
insertStandard({
|
|
169
|
+
it("skips nonexistent files gracefully", async () => {
|
|
170
|
+
await insertStandard({
|
|
170
171
|
category: "code",
|
|
171
172
|
scope: "all",
|
|
172
173
|
rule: "No eval",
|
|
173
174
|
anti_examples: JSON.stringify(["eval("]),
|
|
174
175
|
});
|
|
175
176
|
|
|
176
|
-
const result = validateAgainstStandards(["/nonexistent/file.ts"], "all");
|
|
177
|
+
const result = await validateAgainstStandards(["/nonexistent/file.ts"], "all");
|
|
177
178
|
expect(result.passed).toBe(true);
|
|
178
179
|
});
|
|
179
180
|
|
|
180
|
-
it("handles invalid anti_examples JSON gracefully", () => {
|
|
181
|
+
it("handles invalid anti_examples JSON gracefully", async () => {
|
|
181
182
|
const file = createTmpFile("api.ts", "export const x = 1;");
|
|
182
|
-
|
|
183
|
-
db.run(
|
|
183
|
+
await dbRun(
|
|
184
184
|
`INSERT INTO standards (category, scope, rule, anti_examples, enforcement, source, created_at)
|
|
185
185
|
VALUES ('code', 'all', 'Bad JSON', 'not-valid-json', 'required', 'test', datetime('now'))`
|
|
186
186
|
);
|
|
187
187
|
|
|
188
|
-
const result = validateAgainstStandards([file], "all");
|
|
189
|
-
expect(result.passed).toBe(true);
|
|
188
|
+
const result = await validateAgainstStandards([file], "all");
|
|
189
|
+
expect(result.passed).toBe(true);
|
|
190
190
|
});
|
|
191
191
|
|
|
192
192
|
// ─────────────────────────────────────────────────────────
|
|
193
193
|
// SEMANTIC VALIDATION (grepai)
|
|
194
194
|
// ─────────────────────────────────────────────────────────
|
|
195
195
|
|
|
196
|
-
it("detects no_match violation when grepai finds matching file", () => {
|
|
196
|
+
it("detects no_match violation when grepai finds matching file", async () => {
|
|
197
197
|
const file = createTmpFile("handler.ts", "export function handle() {}");
|
|
198
198
|
const resolvedPath = join(TMP_DIR, "handler.ts").replace(/\\/g, "/");
|
|
199
199
|
|
|
@@ -202,7 +202,7 @@ describe("standards-validator", () => {
|
|
|
202
202
|
{ path: resolvedPath, score: 0.85 },
|
|
203
203
|
]);
|
|
204
204
|
|
|
205
|
-
insertStandard({
|
|
205
|
+
await insertStandard({
|
|
206
206
|
category: "code",
|
|
207
207
|
scope: "all",
|
|
208
208
|
rule: "No .then() chains",
|
|
@@ -210,7 +210,7 @@ describe("standards-validator", () => {
|
|
|
210
210
|
expect: "no_match",
|
|
211
211
|
});
|
|
212
212
|
|
|
213
|
-
const result = validateAgainstStandards([file], "all");
|
|
213
|
+
const result = await validateAgainstStandards([file], "all");
|
|
214
214
|
expect(result.passed).toBe(false);
|
|
215
215
|
expect(result.violations).toHaveLength(1);
|
|
216
216
|
expect(result.violations[0].detail).toContain("Violacao semantica");
|
|
@@ -220,13 +220,13 @@ describe("standards-validator", () => {
|
|
|
220
220
|
searchSpy.mockRestore();
|
|
221
221
|
});
|
|
222
222
|
|
|
223
|
-
it("passes no_match when grepai finds no matches", () => {
|
|
223
|
+
it("passes no_match when grepai finds no matches", async () => {
|
|
224
224
|
const file = createTmpFile("clean.ts", "export const x = 1;");
|
|
225
225
|
|
|
226
226
|
const availSpy = spyOn(patterns, "isGrepaiAvailable").mockReturnValue(true);
|
|
227
227
|
const searchSpy = spyOn(patterns, "searchWithGrepai").mockReturnValue([]);
|
|
228
228
|
|
|
229
|
-
insertStandard({
|
|
229
|
+
await insertStandard({
|
|
230
230
|
category: "code",
|
|
231
231
|
scope: "all",
|
|
232
232
|
rule: "No .then() chains",
|
|
@@ -234,20 +234,20 @@ describe("standards-validator", () => {
|
|
|
234
234
|
expect: "no_match",
|
|
235
235
|
});
|
|
236
236
|
|
|
237
|
-
const result = validateAgainstStandards([file], "all");
|
|
237
|
+
const result = await validateAgainstStandards([file], "all");
|
|
238
238
|
expect(result.passed).toBe(true);
|
|
239
239
|
|
|
240
240
|
availSpy.mockRestore();
|
|
241
241
|
searchSpy.mockRestore();
|
|
242
242
|
});
|
|
243
243
|
|
|
244
|
-
it("detects must_match violation when grepai finds no match", () => {
|
|
244
|
+
it("detects must_match violation when grepai finds no match", async () => {
|
|
245
245
|
const file = createTmpFile("service.ts", "export function process() {}");
|
|
246
246
|
|
|
247
247
|
const availSpy = spyOn(patterns, "isGrepaiAvailable").mockReturnValue(true);
|
|
248
248
|
const searchSpy = spyOn(patterns, "searchWithGrepai").mockReturnValue([]);
|
|
249
249
|
|
|
250
|
-
insertStandard({
|
|
250
|
+
await insertStandard({
|
|
251
251
|
category: "practice",
|
|
252
252
|
scope: "all",
|
|
253
253
|
rule: "Must have error handling",
|
|
@@ -255,7 +255,7 @@ describe("standards-validator", () => {
|
|
|
255
255
|
expect: "must_match",
|
|
256
256
|
});
|
|
257
257
|
|
|
258
|
-
const result = validateAgainstStandards([file], "all");
|
|
258
|
+
const result = await validateAgainstStandards([file], "all");
|
|
259
259
|
expect(result.passed).toBe(false);
|
|
260
260
|
expect(result.violations).toHaveLength(1);
|
|
261
261
|
expect(result.violations[0].detail).toContain("Padrao obrigatorio nao encontrado");
|
|
@@ -264,7 +264,7 @@ describe("standards-validator", () => {
|
|
|
264
264
|
searchSpy.mockRestore();
|
|
265
265
|
});
|
|
266
266
|
|
|
267
|
-
it("passes must_match when grepai finds matching file", () => {
|
|
267
|
+
it("passes must_match when grepai finds matching file", async () => {
|
|
268
268
|
const file = createTmpFile("service.ts", "try { } catch (e) { }");
|
|
269
269
|
const resolvedPath = join(TMP_DIR, "service.ts").replace(/\\/g, "/");
|
|
270
270
|
|
|
@@ -273,7 +273,7 @@ describe("standards-validator", () => {
|
|
|
273
273
|
{ path: resolvedPath, score: 0.9 },
|
|
274
274
|
]);
|
|
275
275
|
|
|
276
|
-
insertStandard({
|
|
276
|
+
await insertStandard({
|
|
277
277
|
category: "practice",
|
|
278
278
|
scope: "all",
|
|
279
279
|
rule: "Must have error handling",
|
|
@@ -281,23 +281,23 @@ describe("standards-validator", () => {
|
|
|
281
281
|
expect: "must_match",
|
|
282
282
|
});
|
|
283
283
|
|
|
284
|
-
const result = validateAgainstStandards([file], "all");
|
|
284
|
+
const result = await validateAgainstStandards([file], "all");
|
|
285
285
|
expect(result.passed).toBe(true);
|
|
286
286
|
|
|
287
287
|
availSpy.mockRestore();
|
|
288
288
|
searchSpy.mockRestore();
|
|
289
289
|
});
|
|
290
290
|
|
|
291
|
-
it("ignores grepai results below score threshold", () => {
|
|
291
|
+
it("ignores grepai results below score threshold", async () => {
|
|
292
292
|
const file = createTmpFile("maybe.ts", "export const x = 1;");
|
|
293
293
|
const resolvedPath = join(TMP_DIR, "maybe.ts").replace(/\\/g, "/");
|
|
294
294
|
|
|
295
295
|
const availSpy = spyOn(patterns, "isGrepaiAvailable").mockReturnValue(true);
|
|
296
296
|
const searchSpy = spyOn(patterns, "searchWithGrepai").mockReturnValue([
|
|
297
|
-
{ path: resolvedPath, score: 0.5 },
|
|
297
|
+
{ path: resolvedPath, score: 0.5 },
|
|
298
298
|
]);
|
|
299
299
|
|
|
300
|
-
insertStandard({
|
|
300
|
+
await insertStandard({
|
|
301
301
|
category: "code",
|
|
302
302
|
scope: "all",
|
|
303
303
|
rule: "No console.log",
|
|
@@ -305,22 +305,22 @@ describe("standards-validator", () => {
|
|
|
305
305
|
expect: "no_match",
|
|
306
306
|
});
|
|
307
307
|
|
|
308
|
-
const result = validateAgainstStandards([file], "all");
|
|
309
|
-
expect(result.passed).toBe(true);
|
|
308
|
+
const result = await validateAgainstStandards([file], "all");
|
|
309
|
+
expect(result.passed).toBe(true);
|
|
310
310
|
|
|
311
311
|
availSpy.mockRestore();
|
|
312
312
|
searchSpy.mockRestore();
|
|
313
313
|
});
|
|
314
314
|
|
|
315
|
-
it("ignores grepai results for files not in validation list", () => {
|
|
315
|
+
it("ignores grepai results for files not in validation list", async () => {
|
|
316
316
|
const file = createTmpFile("target.ts", "export const x = 1;");
|
|
317
317
|
|
|
318
318
|
const availSpy = spyOn(patterns, "isGrepaiAvailable").mockReturnValue(true);
|
|
319
319
|
const searchSpy = spyOn(patterns, "searchWithGrepai").mockReturnValue([
|
|
320
|
-
{ path: "/some/other/file.ts", score: 0.95 },
|
|
320
|
+
{ path: "/some/other/file.ts", score: 0.95 },
|
|
321
321
|
]);
|
|
322
322
|
|
|
323
|
-
insertStandard({
|
|
323
|
+
await insertStandard({
|
|
324
324
|
category: "code",
|
|
325
325
|
scope: "all",
|
|
326
326
|
rule: "No eval",
|
|
@@ -328,8 +328,8 @@ describe("standards-validator", () => {
|
|
|
328
328
|
expect: "no_match",
|
|
329
329
|
});
|
|
330
330
|
|
|
331
|
-
const result = validateAgainstStandards([file], "all");
|
|
332
|
-
expect(result.passed).toBe(true);
|
|
331
|
+
const result = await validateAgainstStandards([file], "all");
|
|
332
|
+
expect(result.passed).toBe(true);
|
|
333
333
|
|
|
334
334
|
availSpy.mockRestore();
|
|
335
335
|
searchSpy.mockRestore();
|
|
@@ -339,12 +339,12 @@ describe("standards-validator", () => {
|
|
|
339
339
|
// FALLBACK (grepai unavailable)
|
|
340
340
|
// ─────────────────────────────────────────────────────────
|
|
341
341
|
|
|
342
|
-
it("falls back to anti_examples when grepai not available", () => {
|
|
342
|
+
it("falls back to anti_examples when grepai not available", async () => {
|
|
343
343
|
const file = createTmpFile("api.ts", 'eval("alert(1)");');
|
|
344
344
|
|
|
345
345
|
const availSpy = spyOn(patterns, "isGrepaiAvailable").mockReturnValue(false);
|
|
346
346
|
|
|
347
|
-
insertStandard({
|
|
347
|
+
await insertStandard({
|
|
348
348
|
category: "code",
|
|
349
349
|
scope: "all",
|
|
350
350
|
rule: "No eval",
|
|
@@ -353,19 +353,19 @@ describe("standards-validator", () => {
|
|
|
353
353
|
expect: "no_match",
|
|
354
354
|
});
|
|
355
355
|
|
|
356
|
-
const result = validateAgainstStandards([file], "all");
|
|
357
|
-
expect(result.passed).toBe(false);
|
|
356
|
+
const result = await validateAgainstStandards([file], "all");
|
|
357
|
+
expect(result.passed).toBe(false);
|
|
358
358
|
expect(result.violations).toHaveLength(1);
|
|
359
359
|
|
|
360
360
|
availSpy.mockRestore();
|
|
361
361
|
});
|
|
362
362
|
|
|
363
|
-
it("skips must_match standards when grepai not available", () => {
|
|
363
|
+
it("skips must_match standards when grepai not available", async () => {
|
|
364
364
|
const file = createTmpFile("service.ts", "export function process() {}");
|
|
365
365
|
|
|
366
366
|
const availSpy = spyOn(patterns, "isGrepaiAvailable").mockReturnValue(false);
|
|
367
367
|
|
|
368
|
-
insertStandard({
|
|
368
|
+
await insertStandard({
|
|
369
369
|
category: "practice",
|
|
370
370
|
scope: "all",
|
|
371
371
|
rule: "Must have error handling",
|
|
@@ -373,8 +373,8 @@ describe("standards-validator", () => {
|
|
|
373
373
|
expect: "must_match",
|
|
374
374
|
});
|
|
375
375
|
|
|
376
|
-
const result = validateAgainstStandards([file], "all");
|
|
377
|
-
expect(result.passed).toBe(true);
|
|
376
|
+
const result = await validateAgainstStandards([file], "all");
|
|
377
|
+
expect(result.passed).toBe(true);
|
|
378
378
|
|
|
379
379
|
availSpy.mockRestore();
|
|
380
380
|
});
|
|
@@ -383,7 +383,7 @@ describe("standards-validator", () => {
|
|
|
383
383
|
// CAP & MIXED
|
|
384
384
|
// ─────────────────────────────────────────────────────────
|
|
385
385
|
|
|
386
|
-
it("caps semantic standards at MAX_SEMANTIC_QUERIES (10)", () => {
|
|
386
|
+
it("caps semantic standards at MAX_SEMANTIC_QUERIES (10)", async () => {
|
|
387
387
|
const file = createTmpFile("test.ts", "export const x = 1;");
|
|
388
388
|
|
|
389
389
|
let callCount = 0;
|
|
@@ -393,9 +393,8 @@ describe("standards-validator", () => {
|
|
|
393
393
|
return [];
|
|
394
394
|
});
|
|
395
395
|
|
|
396
|
-
// Insert 15 semantic standards
|
|
397
396
|
for (let i = 0; i < 15; i++) {
|
|
398
|
-
insertStandard({
|
|
397
|
+
await insertStandard({
|
|
399
398
|
category: "code",
|
|
400
399
|
scope: "all",
|
|
401
400
|
rule: `Rule ${i}`,
|
|
@@ -404,14 +403,14 @@ describe("standards-validator", () => {
|
|
|
404
403
|
});
|
|
405
404
|
}
|
|
406
405
|
|
|
407
|
-
validateAgainstStandards([file], "all");
|
|
408
|
-
expect(callCount).toBe(10);
|
|
406
|
+
await validateAgainstStandards([file], "all");
|
|
407
|
+
expect(callCount).toBe(10);
|
|
409
408
|
|
|
410
409
|
availSpy.mockRestore();
|
|
411
410
|
searchSpy.mockRestore();
|
|
412
411
|
});
|
|
413
412
|
|
|
414
|
-
it("validates both semantic and textual standards in same run", () => {
|
|
413
|
+
it("validates both semantic and textual standards in same run", async () => {
|
|
415
414
|
const file = createTmpFile("mixed.ts", 'eval("code"); export const x = 1;');
|
|
416
415
|
const resolvedPath = join(TMP_DIR, "mixed.ts").replace(/\\/g, "/");
|
|
417
416
|
|
|
@@ -420,8 +419,7 @@ describe("standards-validator", () => {
|
|
|
420
419
|
{ path: resolvedPath, score: 0.9 },
|
|
421
420
|
]);
|
|
422
421
|
|
|
423
|
-
|
|
424
|
-
insertStandard({
|
|
422
|
+
await insertStandard({
|
|
425
423
|
category: "code",
|
|
426
424
|
scope: "all",
|
|
427
425
|
rule: "No .then() chains",
|
|
@@ -429,17 +427,16 @@ describe("standards-validator", () => {
|
|
|
429
427
|
expect: "no_match",
|
|
430
428
|
});
|
|
431
429
|
|
|
432
|
-
|
|
433
|
-
insertStandard({
|
|
430
|
+
await insertStandard({
|
|
434
431
|
category: "code",
|
|
435
432
|
scope: "all",
|
|
436
433
|
rule: "No eval",
|
|
437
434
|
anti_examples: JSON.stringify(["eval("]),
|
|
438
435
|
});
|
|
439
436
|
|
|
440
|
-
const result = validateAgainstStandards([file], "all");
|
|
437
|
+
const result = await validateAgainstStandards([file], "all");
|
|
441
438
|
expect(result.passed).toBe(false);
|
|
442
|
-
expect(result.violations).toHaveLength(2);
|
|
439
|
+
expect(result.violations).toHaveLength(2);
|
|
443
440
|
|
|
444
441
|
availSpy.mockRestore();
|
|
445
442
|
searchSpy.mockRestore();
|
|
@@ -1,12 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { dbAll } from "../db/connection";
|
|
2
2
|
import { existsSync, readFileSync } from "fs";
|
|
3
3
|
import { basename, resolve } from "path";
|
|
4
4
|
import { isGrepaiAvailable, searchWithGrepai, getGrepaiWorkspace, type GrepaiResult } from "../commands/patterns";
|
|
5
5
|
|
|
6
|
-
// ═══════════════════════════════════════════════════════════════
|
|
7
|
-
// INTERFACES
|
|
8
|
-
// ═══════════════════════════════════════════════════════════════
|
|
9
|
-
|
|
10
6
|
export interface Violation {
|
|
11
7
|
file: string;
|
|
12
8
|
rule: string;
|
|
@@ -34,38 +30,27 @@ interface StandardRow {
|
|
|
34
30
|
expect: string;
|
|
35
31
|
}
|
|
36
32
|
|
|
37
|
-
// ═══════════════════════════════════════════════════════════════
|
|
38
|
-
// CONSTANTES
|
|
39
|
-
// ═══════════════════════════════════════════════════════════════
|
|
40
|
-
|
|
41
33
|
const SEMANTIC_SCORE_THRESHOLD = 0.7;
|
|
42
34
|
const MAX_SEMANTIC_QUERIES = 10;
|
|
43
35
|
|
|
44
|
-
|
|
45
|
-
// VALIDACAO PRINCIPAL
|
|
46
|
-
// ═══════════════════════════════════════════════════════════════
|
|
47
|
-
|
|
48
|
-
export function validateAgainstStandards(
|
|
36
|
+
export async function validateAgainstStandards(
|
|
49
37
|
files: string[],
|
|
50
38
|
agentDomain: string
|
|
51
|
-
): ValidationResult {
|
|
52
|
-
const db = getDb();
|
|
39
|
+
): Promise<ValidationResult> {
|
|
53
40
|
const violations: Violation[] = [];
|
|
54
41
|
const warnings: Violation[] = [];
|
|
55
42
|
|
|
56
|
-
const standards =
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
.all(agentDomain) as StandardRow[];
|
|
43
|
+
const standards = await dbAll<StandardRow>(
|
|
44
|
+
`SELECT * FROM standards
|
|
45
|
+
WHERE (scope = 'all' OR scope = ?)
|
|
46
|
+
ORDER BY enforcement DESC, category`,
|
|
47
|
+
[agentDomain]
|
|
48
|
+
);
|
|
63
49
|
|
|
64
50
|
if (standards.length === 0) {
|
|
65
51
|
return { passed: true, violations: [], warnings: [] };
|
|
66
52
|
}
|
|
67
53
|
|
|
68
|
-
// Separar standards semanticos dos textuais
|
|
69
54
|
const semanticStandards = standards
|
|
70
55
|
.filter(s => s.semantic_query && s.semantic_query.trim().length > 0)
|
|
71
56
|
.slice(0, MAX_SEMANTIC_QUERIES);
|
|
@@ -73,17 +58,14 @@ export function validateAgainstStandards(
|
|
|
73
58
|
s => !s.semantic_query || s.semantic_query.trim().length === 0
|
|
74
59
|
);
|
|
75
60
|
|
|
76
|
-
// Normalizar paths dos arquivos sendo validados
|
|
77
61
|
const normalizedFiles = new Set(
|
|
78
62
|
files.filter(f => existsSync(f)).map(f => resolve(f).replace(/\\/g, "/"))
|
|
79
63
|
);
|
|
80
64
|
|
|
81
|
-
// === MODO 1: Validacao semantica via grepai ===
|
|
82
65
|
if (semanticStandards.length > 0) {
|
|
83
66
|
if (isGrepaiAvailable()) {
|
|
84
67
|
validateSemantic(semanticStandards, normalizedFiles, files, violations, warnings);
|
|
85
68
|
} else {
|
|
86
|
-
// Fallback: apenas no_match com anti_examples
|
|
87
69
|
const fallbackStandards = semanticStandards.filter(s => s.expect === "no_match");
|
|
88
70
|
const skipped = semanticStandards.length - fallbackStandards.length;
|
|
89
71
|
if (skipped > 0) {
|
|
@@ -96,7 +78,6 @@ export function validateAgainstStandards(
|
|
|
96
78
|
}
|
|
97
79
|
}
|
|
98
80
|
|
|
99
|
-
// === MODO 2: Validacao textual (anti_examples) ===
|
|
100
81
|
if (textualStandards.length > 0) {
|
|
101
82
|
validateTextual(textualStandards, files, violations, warnings);
|
|
102
83
|
}
|
|
@@ -108,10 +89,6 @@ export function validateAgainstStandards(
|
|
|
108
89
|
};
|
|
109
90
|
}
|
|
110
91
|
|
|
111
|
-
// ═══════════════════════════════════════════════════════════════
|
|
112
|
-
// VALIDACAO SEMANTICA (grepai)
|
|
113
|
-
// ═══════════════════════════════════════════════════════════════
|
|
114
|
-
|
|
115
92
|
function validateSemantic(
|
|
116
93
|
standards: StandardRow[],
|
|
117
94
|
normalizedFiles: Set<string>,
|
|
@@ -125,7 +102,6 @@ function validateSemantic(
|
|
|
125
102
|
|
|
126
103
|
const results = searchWithGrepai(std.semantic_query, 20, workspace);
|
|
127
104
|
|
|
128
|
-
// Filtrar: apenas arquivos sendo validados + score acima do threshold
|
|
129
105
|
const matchingFiles = results.filter(r => {
|
|
130
106
|
const normalized = resolve(r.path).replace(/\\/g, "/");
|
|
131
107
|
return normalizedFiles.has(normalized) && r.score >= SEMANTIC_SCORE_THRESHOLD;
|
|
@@ -150,10 +126,6 @@ function validateSemantic(
|
|
|
150
126
|
}
|
|
151
127
|
}
|
|
152
128
|
|
|
153
|
-
// ═══════════════════════════════════════════════════════════════
|
|
154
|
-
// VALIDACAO TEXTUAL (anti_examples fallback)
|
|
155
|
-
// ═══════════════════════════════════════════════════════════════
|
|
156
|
-
|
|
157
129
|
function validateTextual(
|
|
158
130
|
standards: StandardRow[],
|
|
159
131
|
files: string[],
|
|
@@ -192,10 +164,6 @@ function validateTextual(
|
|
|
192
164
|
}
|
|
193
165
|
}
|
|
194
166
|
|
|
195
|
-
// ═══════════════════════════════════════════════════════════════
|
|
196
|
-
// HELPERS
|
|
197
|
-
// ═══════════════════════════════════════════════════════════════
|
|
198
|
-
|
|
199
167
|
function addViolation(
|
|
200
168
|
std: StandardRow,
|
|
201
169
|
file: string,
|