@codexa/cli 9.0.7 → 9.0.8
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 +278 -0
- package/context/domains.ts +156 -0
- package/context/generator.ts +12 -13
- package/context/index.ts +3 -1
- package/context/sections.ts +2 -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
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
import { getDb } from "../db/connection";
|
|
2
2
|
import { existsSync, readFileSync } from "fs";
|
|
3
|
-
import { basename,
|
|
3
|
+
import { basename, resolve } from "path";
|
|
4
|
+
import { isGrepaiAvailable, searchWithGrepai, type GrepaiResult } from "../commands/patterns";
|
|
4
5
|
|
|
5
|
-
|
|
6
|
+
// ═══════════════════════════════════════════════════════════════
|
|
7
|
+
// INTERFACES
|
|
8
|
+
// ═══════════════════════════════════════════════════════════════
|
|
9
|
+
|
|
10
|
+
export interface Violation {
|
|
6
11
|
file: string;
|
|
7
12
|
rule: string;
|
|
8
13
|
category: string;
|
|
@@ -10,12 +15,36 @@ interface Violation {
|
|
|
10
15
|
detail?: string;
|
|
11
16
|
}
|
|
12
17
|
|
|
13
|
-
interface ValidationResult {
|
|
18
|
+
export interface ValidationResult {
|
|
14
19
|
passed: boolean;
|
|
15
20
|
violations: Violation[];
|
|
16
21
|
warnings: Violation[];
|
|
17
22
|
}
|
|
18
23
|
|
|
24
|
+
interface StandardRow {
|
|
25
|
+
id: number;
|
|
26
|
+
category: string;
|
|
27
|
+
scope: string;
|
|
28
|
+
rule: string;
|
|
29
|
+
examples: string | null;
|
|
30
|
+
anti_examples: string | null;
|
|
31
|
+
enforcement: string;
|
|
32
|
+
source: string | null;
|
|
33
|
+
semantic_query: string | null;
|
|
34
|
+
expect: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// ═══════════════════════════════════════════════════════════════
|
|
38
|
+
// CONSTANTES
|
|
39
|
+
// ═══════════════════════════════════════════════════════════════
|
|
40
|
+
|
|
41
|
+
const SEMANTIC_SCORE_THRESHOLD = 0.7;
|
|
42
|
+
const MAX_SEMANTIC_QUERIES = 10;
|
|
43
|
+
|
|
44
|
+
// ═══════════════════════════════════════════════════════════════
|
|
45
|
+
// VALIDACAO PRINCIPAL
|
|
46
|
+
// ═══════════════════════════════════════════════════════════════
|
|
47
|
+
|
|
19
48
|
export function validateAgainstStandards(
|
|
20
49
|
files: string[],
|
|
21
50
|
agentDomain: string
|
|
@@ -24,19 +53,112 @@ export function validateAgainstStandards(
|
|
|
24
53
|
const violations: Violation[] = [];
|
|
25
54
|
const warnings: Violation[] = [];
|
|
26
55
|
|
|
27
|
-
// Buscar standards aplicáveis
|
|
28
56
|
const standards = db
|
|
29
57
|
.query(
|
|
30
58
|
`SELECT * FROM standards
|
|
31
59
|
WHERE (scope = 'all' OR scope = ?)
|
|
32
60
|
ORDER BY enforcement DESC, category`
|
|
33
61
|
)
|
|
34
|
-
.all(agentDomain) as
|
|
62
|
+
.all(agentDomain) as StandardRow[];
|
|
35
63
|
|
|
36
64
|
if (standards.length === 0) {
|
|
37
65
|
return { passed: true, violations: [], warnings: [] };
|
|
38
66
|
}
|
|
39
67
|
|
|
68
|
+
// Separar standards semanticos dos textuais
|
|
69
|
+
const semanticStandards = standards
|
|
70
|
+
.filter(s => s.semantic_query && s.semantic_query.trim().length > 0)
|
|
71
|
+
.slice(0, MAX_SEMANTIC_QUERIES);
|
|
72
|
+
const textualStandards = standards.filter(
|
|
73
|
+
s => !s.semantic_query || s.semantic_query.trim().length === 0
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
// Normalizar paths dos arquivos sendo validados
|
|
77
|
+
const normalizedFiles = new Set(
|
|
78
|
+
files.filter(f => existsSync(f)).map(f => resolve(f).replace(/\\/g, "/"))
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
// === MODO 1: Validacao semantica via grepai ===
|
|
82
|
+
if (semanticStandards.length > 0) {
|
|
83
|
+
if (isGrepaiAvailable()) {
|
|
84
|
+
validateSemantic(semanticStandards, normalizedFiles, files, violations, warnings);
|
|
85
|
+
} else {
|
|
86
|
+
// Fallback: apenas no_match com anti_examples
|
|
87
|
+
const fallbackStandards = semanticStandards.filter(s => s.expect === "no_match");
|
|
88
|
+
const skipped = semanticStandards.length - fallbackStandards.length;
|
|
89
|
+
if (skipped > 0) {
|
|
90
|
+
console.warn(`[standards] grepai nao disponivel. ${skipped} standard(s) must_match ignorado(s).`);
|
|
91
|
+
}
|
|
92
|
+
if (fallbackStandards.length > 0) {
|
|
93
|
+
console.warn("[standards] grepai nao disponivel. Usando fallback textual para standards semanticos.");
|
|
94
|
+
validateTextual(fallbackStandards, files, violations, warnings);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// === MODO 2: Validacao textual (anti_examples) ===
|
|
100
|
+
if (textualStandards.length > 0) {
|
|
101
|
+
validateTextual(textualStandards, files, violations, warnings);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return {
|
|
105
|
+
passed: violations.length === 0,
|
|
106
|
+
violations,
|
|
107
|
+
warnings,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// ═══════════════════════════════════════════════════════════════
|
|
112
|
+
// VALIDACAO SEMANTICA (grepai)
|
|
113
|
+
// ═══════════════════════════════════════════════════════════════
|
|
114
|
+
|
|
115
|
+
function validateSemantic(
|
|
116
|
+
standards: StandardRow[],
|
|
117
|
+
normalizedFiles: Set<string>,
|
|
118
|
+
originalFiles: string[],
|
|
119
|
+
violations: Violation[],
|
|
120
|
+
warnings: Violation[]
|
|
121
|
+
): void {
|
|
122
|
+
for (const std of standards) {
|
|
123
|
+
if (!std.semantic_query) continue;
|
|
124
|
+
|
|
125
|
+
const results = searchWithGrepai(std.semantic_query, 20);
|
|
126
|
+
|
|
127
|
+
// Filtrar: apenas arquivos sendo validados + score acima do threshold
|
|
128
|
+
const matchingFiles = results.filter(r => {
|
|
129
|
+
const normalized = resolve(r.path).replace(/\\/g, "/");
|
|
130
|
+
return normalizedFiles.has(normalized) && r.score >= SEMANTIC_SCORE_THRESHOLD;
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
const hasMatch = matchingFiles.length > 0;
|
|
134
|
+
|
|
135
|
+
if (std.expect === "no_match" && hasMatch) {
|
|
136
|
+
for (const match of matchingFiles) {
|
|
137
|
+
addViolation(std, match.path,
|
|
138
|
+
`Violacao semantica (score: ${match.score.toFixed(2)}): "${std.semantic_query}"`,
|
|
139
|
+
violations, warnings);
|
|
140
|
+
}
|
|
141
|
+
} else if (std.expect === "must_match" && !hasMatch) {
|
|
142
|
+
for (const file of originalFiles) {
|
|
143
|
+
if (!existsSync(file)) continue;
|
|
144
|
+
addViolation(std, file,
|
|
145
|
+
`Padrao obrigatorio nao encontrado: "${std.semantic_query}"`,
|
|
146
|
+
violations, warnings);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// ═══════════════════════════════════════════════════════════════
|
|
153
|
+
// VALIDACAO TEXTUAL (anti_examples fallback)
|
|
154
|
+
// ═══════════════════════════════════════════════════════════════
|
|
155
|
+
|
|
156
|
+
function validateTextual(
|
|
157
|
+
standards: StandardRow[],
|
|
158
|
+
files: string[],
|
|
159
|
+
violations: Violation[],
|
|
160
|
+
warnings: Violation[]
|
|
161
|
+
): void {
|
|
40
162
|
for (const file of files) {
|
|
41
163
|
if (!existsSync(file)) continue;
|
|
42
164
|
|
|
@@ -47,135 +169,52 @@ export function validateAgainstStandards(
|
|
|
47
169
|
continue;
|
|
48
170
|
}
|
|
49
171
|
|
|
50
|
-
const fileName = basename(file);
|
|
51
|
-
const ext = extname(file);
|
|
52
|
-
|
|
53
172
|
for (const std of standards) {
|
|
54
|
-
|
|
55
|
-
let detail: string | undefined;
|
|
56
|
-
|
|
57
|
-
// Validações por categoria
|
|
58
|
-
switch (std.category) {
|
|
59
|
-
case "naming":
|
|
60
|
-
// Validar PascalCase para componentes React/Next
|
|
61
|
-
if (std.rule.toLowerCase().includes("pascalcase") && (ext === ".tsx" || ext === ".jsx")) {
|
|
62
|
-
const nameWithoutExt = fileName.replace(ext, "");
|
|
63
|
-
// Ignorar arquivos especiais (page, layout, loading, etc.)
|
|
64
|
-
const specialFiles = ["page", "layout", "loading", "error", "not-found", "template", "default"];
|
|
65
|
-
if (!specialFiles.includes(nameWithoutExt)) {
|
|
66
|
-
violated = !/^[A-Z][a-zA-Z0-9]*$/.test(nameWithoutExt);
|
|
67
|
-
if (violated) {
|
|
68
|
-
detail = `"${fileName}" deveria ser PascalCase (ex: ${nameWithoutExt.charAt(0).toUpperCase() + nameWithoutExt.slice(1)}.tsx)`;
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// Validar camelCase para hooks
|
|
74
|
-
if (std.rule.toLowerCase().includes("use") && std.rule.toLowerCase().includes("hook")) {
|
|
75
|
-
if (fileName.includes("use") || fileName.includes("Use")) {
|
|
76
|
-
violated = !/^use[A-Z][a-zA-Z0-9]*\.ts$/.test(fileName);
|
|
77
|
-
if (violated) {
|
|
78
|
-
detail = `Hook "${fileName}" deveria seguir padrao useXxx.ts`;
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
break;
|
|
83
|
-
|
|
84
|
-
case "code":
|
|
85
|
-
// Validar Server Components (sem "use client" desnecessário)
|
|
86
|
-
if (std.rule.toLowerCase().includes("server component")) {
|
|
87
|
-
// Se está em app/ e não é um componente interativo, não deveria ter "use client"
|
|
88
|
-
if (file.includes("/app/") && !file.includes("/components/")) {
|
|
89
|
-
const hasUseClient = content.includes('"use client"') || content.includes("'use client'");
|
|
90
|
-
const hasInteractiveCode =
|
|
91
|
-
content.includes("useState") ||
|
|
92
|
-
content.includes("useEffect") ||
|
|
93
|
-
content.includes("onClick") ||
|
|
94
|
-
content.includes("onChange");
|
|
95
|
-
|
|
96
|
-
if (hasUseClient && !hasInteractiveCode) {
|
|
97
|
-
violated = true;
|
|
98
|
-
detail = `Arquivo tem "use client" mas nao usa hooks ou eventos interativos`;
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
// Validar uso de @ alias
|
|
104
|
-
if (std.rule.toLowerCase().includes("@") && std.rule.toLowerCase().includes("alias")) {
|
|
105
|
-
// Verificar imports relativos profundos
|
|
106
|
-
const deepRelativeImport = /from ['"]\.\.\/\.\.\/\.\.\//;
|
|
107
|
-
if (deepRelativeImport.test(content)) {
|
|
108
|
-
violated = true;
|
|
109
|
-
detail = `Imports relativos profundos (../../..) deveriam usar @ alias`;
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
break;
|
|
113
|
-
|
|
114
|
-
case "structure":
|
|
115
|
-
// Validar pasta de componentes
|
|
116
|
-
if (std.rule.toLowerCase().includes("component") && (ext === ".tsx" || ext === ".jsx")) {
|
|
117
|
-
const isComponent = /^[A-Z]/.test(fileName) && !file.includes("/app/");
|
|
118
|
-
if (isComponent && !file.includes("/components/")) {
|
|
119
|
-
violated = true;
|
|
120
|
-
detail = `Componente "${fileName}" deveria estar em /components/`;
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
break;
|
|
173
|
+
if (!std.anti_examples) continue;
|
|
124
174
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
violated = true;
|
|
131
|
-
detail = `Uso de "${anti}" nao permitido. ${std.rule}`;
|
|
132
|
-
break;
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
break;
|
|
136
|
-
|
|
137
|
-
case "practice":
|
|
138
|
-
// Validar práticas obrigatórias
|
|
139
|
-
if (std.rule.toLowerCase().includes("zod") && std.rule.toLowerCase().includes("valid")) {
|
|
140
|
-
// Se arquivo define tipos/interfaces com Props, deveria usar Zod
|
|
141
|
-
const hasPropsInterface = /interface\s+\w*Props\b/.test(content);
|
|
142
|
-
const hasPropsType = /type\s+\w*Props\s*=/.test(content);
|
|
143
|
-
const usesZod = content.includes("z.") || content.includes("from 'zod'") || content.includes('from "zod"');
|
|
144
|
-
|
|
145
|
-
if ((hasPropsInterface || hasPropsType) && !usesZod && file.includes("/components/")) {
|
|
146
|
-
// Isso é mais um warning do que violation
|
|
147
|
-
if (std.enforcement === "required") {
|
|
148
|
-
violated = true;
|
|
149
|
-
detail = `Componente define Props mas nao usa Zod para validacao`;
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
break;
|
|
175
|
+
let antiExamples: string[];
|
|
176
|
+
try {
|
|
177
|
+
antiExamples = JSON.parse(std.anti_examples);
|
|
178
|
+
} catch {
|
|
179
|
+
continue;
|
|
154
180
|
}
|
|
155
181
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
file,
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
detail,
|
|
163
|
-
};
|
|
164
|
-
|
|
165
|
-
if (std.enforcement === "required") {
|
|
166
|
-
violations.push(violation);
|
|
167
|
-
} else {
|
|
168
|
-
warnings.push(violation);
|
|
182
|
+
for (const anti of antiExamples) {
|
|
183
|
+
if (content.includes(anti)) {
|
|
184
|
+
addViolation(std, file,
|
|
185
|
+
`Uso de "${anti}" viola standard: ${std.rule}`,
|
|
186
|
+
violations, warnings);
|
|
187
|
+
break;
|
|
169
188
|
}
|
|
170
189
|
}
|
|
171
190
|
}
|
|
172
191
|
}
|
|
192
|
+
}
|
|
173
193
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
194
|
+
// ═══════════════════════════════════════════════════════════════
|
|
195
|
+
// HELPERS
|
|
196
|
+
// ═══════════════════════════════════════════════════════════════
|
|
197
|
+
|
|
198
|
+
function addViolation(
|
|
199
|
+
std: StandardRow,
|
|
200
|
+
file: string,
|
|
201
|
+
detail: string,
|
|
202
|
+
violations: Violation[],
|
|
203
|
+
warnings: Violation[]
|
|
204
|
+
): void {
|
|
205
|
+
const violation: Violation = {
|
|
206
|
+
file,
|
|
207
|
+
rule: std.rule,
|
|
208
|
+
category: std.category,
|
|
209
|
+
enforcement: std.enforcement,
|
|
210
|
+
detail,
|
|
178
211
|
};
|
|
212
|
+
|
|
213
|
+
if (std.enforcement === "required") {
|
|
214
|
+
violations.push(violation);
|
|
215
|
+
} else {
|
|
216
|
+
warnings.push(violation);
|
|
217
|
+
}
|
|
179
218
|
}
|
|
180
219
|
|
|
181
220
|
export function printValidationResult(result: ValidationResult): void {
|
|
@@ -202,4 +241,4 @@ export function printValidationResult(result: ValidationResult): void {
|
|
|
202
241
|
console.warn();
|
|
203
242
|
}
|
|
204
243
|
}
|
|
205
|
-
}
|
|
244
|
+
}
|