@codexa/cli 8.5.0 → 8.6.0
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 +760 -896
- package/commands/check.ts +131 -131
- package/commands/clear.ts +170 -174
- package/commands/decide.ts +249 -249
- package/commands/discover.ts +82 -10
- package/commands/knowledge.ts +361 -361
- package/commands/patterns.ts +621 -621
- package/commands/plan.ts +376 -376
- package/commands/product.ts +626 -628
- package/commands/research.ts +754 -754
- package/commands/review.ts +463 -463
- package/commands/standards.ts +200 -223
- package/commands/task.ts +2 -2
- package/commands/utils.ts +1021 -1021
- package/db/connection.ts +32 -32
- package/db/schema.ts +719 -788
- package/detectors/loader.ts +0 -12
- package/gates/standards-validator.ts +204 -204
- package/gates/validator.ts +441 -441
- package/package.json +43 -43
- package/protocol/process-return.ts +450 -450
- package/protocol/subagent-protocol.ts +401 -411
- package/workflow.ts +0 -18
package/detectors/loader.ts
CHANGED
|
@@ -26,18 +26,6 @@ export {
|
|
|
26
26
|
type DetectorResult,
|
|
27
27
|
} from "./index";
|
|
28
28
|
|
|
29
|
-
// Re-export utilities for custom detectors
|
|
30
|
-
export {
|
|
31
|
-
registerDetector,
|
|
32
|
-
fileExists,
|
|
33
|
-
dirExists,
|
|
34
|
-
findFiles,
|
|
35
|
-
readJson,
|
|
36
|
-
readText,
|
|
37
|
-
parseToml,
|
|
38
|
-
parseYaml,
|
|
39
|
-
} from "./index";
|
|
40
|
-
|
|
41
29
|
/**
|
|
42
30
|
* Convenience function for CLI display
|
|
43
31
|
*/
|
|
@@ -1,205 +1,205 @@
|
|
|
1
|
-
import { getDb } from "../db/connection";
|
|
2
|
-
import { existsSync, readFileSync } from "fs";
|
|
3
|
-
import { basename, extname } from "path";
|
|
4
|
-
|
|
5
|
-
interface Violation {
|
|
6
|
-
file: string;
|
|
7
|
-
rule: string;
|
|
8
|
-
category: string;
|
|
9
|
-
enforcement: string;
|
|
10
|
-
detail?: string;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
interface ValidationResult {
|
|
14
|
-
passed: boolean;
|
|
15
|
-
violations: Violation[];
|
|
16
|
-
warnings: Violation[];
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export function validateAgainstStandards(
|
|
20
|
-
files: string[],
|
|
21
|
-
agentDomain: string
|
|
22
|
-
): ValidationResult {
|
|
23
|
-
const db = getDb();
|
|
24
|
-
const violations: Violation[] = [];
|
|
25
|
-
const warnings: Violation[] = [];
|
|
26
|
-
|
|
27
|
-
// Buscar standards aplicáveis
|
|
28
|
-
const standards = db
|
|
29
|
-
.query(
|
|
30
|
-
`SELECT * FROM standards
|
|
31
|
-
WHERE (scope = 'all' OR scope = ?)
|
|
32
|
-
ORDER BY enforcement DESC, category`
|
|
33
|
-
)
|
|
34
|
-
.all(agentDomain) as any[];
|
|
35
|
-
|
|
36
|
-
if (standards.length === 0) {
|
|
37
|
-
return { passed: true, violations: [], warnings: [] };
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
for (const file of files) {
|
|
41
|
-
if (!existsSync(file)) continue;
|
|
42
|
-
|
|
43
|
-
let content: string;
|
|
44
|
-
try {
|
|
45
|
-
content = readFileSync(file, "utf-8");
|
|
46
|
-
} catch {
|
|
47
|
-
continue;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
const fileName = basename(file);
|
|
51
|
-
const ext = extname(file);
|
|
52
|
-
|
|
53
|
-
for (const std of standards) {
|
|
54
|
-
let violated = false;
|
|
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;
|
|
124
|
-
|
|
125
|
-
case "library":
|
|
126
|
-
// Validar anti-exemplos (bibliotecas proibidas)
|
|
127
|
-
const antiExamples = std.anti_examples ? JSON.parse(std.anti_examples) : [];
|
|
128
|
-
for (const anti of antiExamples) {
|
|
129
|
-
if (content.includes(anti)) {
|
|
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;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
if (violated) {
|
|
157
|
-
const violation: Violation = {
|
|
158
|
-
file,
|
|
159
|
-
rule: std.rule,
|
|
160
|
-
category: std.category,
|
|
161
|
-
enforcement: std.enforcement,
|
|
162
|
-
detail,
|
|
163
|
-
};
|
|
164
|
-
|
|
165
|
-
if (std.enforcement === "required") {
|
|
166
|
-
violations.push(violation);
|
|
167
|
-
} else {
|
|
168
|
-
warnings.push(violation);
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
return {
|
|
175
|
-
passed: violations.length === 0,
|
|
176
|
-
violations,
|
|
177
|
-
warnings,
|
|
178
|
-
};
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
export function printValidationResult(result: ValidationResult): void {
|
|
182
|
-
if (result.violations.length > 0) {
|
|
183
|
-
console.error("\n⛔ VIOLACOES DE STANDARDS (required):\n");
|
|
184
|
-
for (const v of result.violations) {
|
|
185
|
-
console.error(` [${v.category}] ${basename(v.file)}`);
|
|
186
|
-
console.error(` Regra: ${v.rule}`);
|
|
187
|
-
if (v.detail) {
|
|
188
|
-
console.error(` Detalhe: ${v.detail}`);
|
|
189
|
-
}
|
|
190
|
-
console.error();
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
if (result.warnings.length > 0) {
|
|
195
|
-
console.warn("\n⚠ AVISOS (recommended):\n");
|
|
196
|
-
for (const w of result.warnings) {
|
|
197
|
-
console.warn(` [${w.category}] ${basename(w.file)}`);
|
|
198
|
-
console.warn(` Regra: ${w.rule}`);
|
|
199
|
-
if (w.detail) {
|
|
200
|
-
console.warn(` Detalhe: ${w.detail}`);
|
|
201
|
-
}
|
|
202
|
-
console.warn();
|
|
203
|
-
}
|
|
204
|
-
}
|
|
1
|
+
import { getDb } from "../db/connection";
|
|
2
|
+
import { existsSync, readFileSync } from "fs";
|
|
3
|
+
import { basename, extname } from "path";
|
|
4
|
+
|
|
5
|
+
interface Violation {
|
|
6
|
+
file: string;
|
|
7
|
+
rule: string;
|
|
8
|
+
category: string;
|
|
9
|
+
enforcement: string;
|
|
10
|
+
detail?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
interface ValidationResult {
|
|
14
|
+
passed: boolean;
|
|
15
|
+
violations: Violation[];
|
|
16
|
+
warnings: Violation[];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function validateAgainstStandards(
|
|
20
|
+
files: string[],
|
|
21
|
+
agentDomain: string
|
|
22
|
+
): ValidationResult {
|
|
23
|
+
const db = getDb();
|
|
24
|
+
const violations: Violation[] = [];
|
|
25
|
+
const warnings: Violation[] = [];
|
|
26
|
+
|
|
27
|
+
// Buscar standards aplicáveis
|
|
28
|
+
const standards = db
|
|
29
|
+
.query(
|
|
30
|
+
`SELECT * FROM standards
|
|
31
|
+
WHERE (scope = 'all' OR scope = ?)
|
|
32
|
+
ORDER BY enforcement DESC, category`
|
|
33
|
+
)
|
|
34
|
+
.all(agentDomain) as any[];
|
|
35
|
+
|
|
36
|
+
if (standards.length === 0) {
|
|
37
|
+
return { passed: true, violations: [], warnings: [] };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
for (const file of files) {
|
|
41
|
+
if (!existsSync(file)) continue;
|
|
42
|
+
|
|
43
|
+
let content: string;
|
|
44
|
+
try {
|
|
45
|
+
content = readFileSync(file, "utf-8");
|
|
46
|
+
} catch {
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const fileName = basename(file);
|
|
51
|
+
const ext = extname(file);
|
|
52
|
+
|
|
53
|
+
for (const std of standards) {
|
|
54
|
+
let violated = false;
|
|
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;
|
|
124
|
+
|
|
125
|
+
case "library":
|
|
126
|
+
// Validar anti-exemplos (bibliotecas proibidas)
|
|
127
|
+
const antiExamples = std.anti_examples ? JSON.parse(std.anti_examples) : [];
|
|
128
|
+
for (const anti of antiExamples) {
|
|
129
|
+
if (content.includes(anti)) {
|
|
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;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (violated) {
|
|
157
|
+
const violation: Violation = {
|
|
158
|
+
file,
|
|
159
|
+
rule: std.rule,
|
|
160
|
+
category: std.category,
|
|
161
|
+
enforcement: std.enforcement,
|
|
162
|
+
detail,
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
if (std.enforcement === "required") {
|
|
166
|
+
violations.push(violation);
|
|
167
|
+
} else {
|
|
168
|
+
warnings.push(violation);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return {
|
|
175
|
+
passed: violations.length === 0,
|
|
176
|
+
violations,
|
|
177
|
+
warnings,
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export function printValidationResult(result: ValidationResult): void {
|
|
182
|
+
if (result.violations.length > 0) {
|
|
183
|
+
console.error("\n⛔ VIOLACOES DE STANDARDS (required):\n");
|
|
184
|
+
for (const v of result.violations) {
|
|
185
|
+
console.error(` [${v.category}] ${basename(v.file)}`);
|
|
186
|
+
console.error(` Regra: ${v.rule}`);
|
|
187
|
+
if (v.detail) {
|
|
188
|
+
console.error(` Detalhe: ${v.detail}`);
|
|
189
|
+
}
|
|
190
|
+
console.error();
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (result.warnings.length > 0) {
|
|
195
|
+
console.warn("\n⚠ AVISOS (recommended):\n");
|
|
196
|
+
for (const w of result.warnings) {
|
|
197
|
+
console.warn(` [${w.category}] ${basename(w.file)}`);
|
|
198
|
+
console.warn(` Regra: ${w.rule}`);
|
|
199
|
+
if (w.detail) {
|
|
200
|
+
console.warn(` Detalhe: ${w.detail}`);
|
|
201
|
+
}
|
|
202
|
+
console.warn();
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
205
|
}
|