@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
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
import { describe, it, expect } from "bun:test";
|
|
2
|
+
import {
|
|
3
|
+
getAgentDomain,
|
|
4
|
+
domainToScope,
|
|
5
|
+
adjustSectionPriorities,
|
|
6
|
+
AGENT_DOMAIN,
|
|
7
|
+
DOMAIN_PROFILES,
|
|
8
|
+
} from "./domains";
|
|
9
|
+
import type { ContextSection } from "./assembly";
|
|
10
|
+
|
|
11
|
+
function makeSections(): ContextSection[] {
|
|
12
|
+
return [
|
|
13
|
+
{ name: "STANDARDS", content: "...", priority: 1 },
|
|
14
|
+
{ name: "ALERTAS", content: "...", priority: 2 },
|
|
15
|
+
{ name: "ARQUITETURA", content: "...", priority: 3 },
|
|
16
|
+
{ name: "DECISOES", content: "...", priority: 4 },
|
|
17
|
+
{ name: "CONTEXTO DE TASKS ANTERIORES", content: "...", priority: 5 },
|
|
18
|
+
{ name: "GRAPH", content: "...", priority: 6 },
|
|
19
|
+
{ name: "PRODUTO", content: "...", priority: 7 },
|
|
20
|
+
{ name: "DISCOVERIES", content: "...", priority: 8 },
|
|
21
|
+
{ name: "UTILITIES", content: "...", priority: 8 },
|
|
22
|
+
{ name: "PATTERNS", content: "...", priority: 9 },
|
|
23
|
+
{ name: "HINTS", content: "...", priority: 10 },
|
|
24
|
+
{ name: "STACK", content: "...", priority: 11 },
|
|
25
|
+
];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// ── getAgentDomain ───────────────────────────────────────────
|
|
29
|
+
|
|
30
|
+
describe("getAgentDomain", () => {
|
|
31
|
+
it("maps backend agents correctly", () => {
|
|
32
|
+
expect(getAgentDomain("backend-javascript")).toBe("backend");
|
|
33
|
+
expect(getAgentDomain("expert-csharp-developer")).toBe("backend");
|
|
34
|
+
expect(getAgentDomain("golang-pro")).toBe("backend");
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("maps frontend agents correctly", () => {
|
|
38
|
+
expect(getAgentDomain("expert-nextjs-developer")).toBe("frontend");
|
|
39
|
+
expect(getAgentDomain("frontend-flutter")).toBe("frontend");
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it("maps database agent correctly", () => {
|
|
43
|
+
expect(getAgentDomain("expert-postgres-developer")).toBe("database");
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it("maps testing agent correctly", () => {
|
|
47
|
+
expect(getAgentDomain("testing-unit")).toBe("testing");
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("maps review agent correctly", () => {
|
|
51
|
+
expect(getAgentDomain("expert-code-reviewer")).toBe("review");
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it("maps security agent correctly", () => {
|
|
55
|
+
expect(getAgentDomain("security-specialist")).toBe("security");
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("maps explore agent correctly", () => {
|
|
59
|
+
expect(getAgentDomain("deep-explore")).toBe("explore");
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it("maps architecture agent correctly", () => {
|
|
63
|
+
expect(getAgentDomain("architect")).toBe("architecture");
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it("returns null for unknown agents", () => {
|
|
67
|
+
expect(getAgentDomain("unknown-agent")).toBeNull();
|
|
68
|
+
expect(getAgentDomain("general-purpose")).toBeNull();
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it("returns null for null/undefined/empty input", () => {
|
|
72
|
+
expect(getAgentDomain(null)).toBeNull();
|
|
73
|
+
expect(getAgentDomain(undefined)).toBeNull();
|
|
74
|
+
expect(getAgentDomain("")).toBeNull();
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
// ── domainToScope ────────────────────────────────────────────
|
|
79
|
+
|
|
80
|
+
describe("domainToScope", () => {
|
|
81
|
+
it("maps code domains to their scope names", () => {
|
|
82
|
+
expect(domainToScope("backend")).toBe("backend");
|
|
83
|
+
expect(domainToScope("frontend")).toBe("frontend");
|
|
84
|
+
expect(domainToScope("database")).toBe("database");
|
|
85
|
+
expect(domainToScope("testing")).toBe("testing");
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it("maps cross-cutting domains to 'all'", () => {
|
|
89
|
+
expect(domainToScope("review")).toBe("all");
|
|
90
|
+
expect(domainToScope("security")).toBe("all");
|
|
91
|
+
expect(domainToScope("explore")).toBe("all");
|
|
92
|
+
expect(domainToScope("architecture")).toBe("all");
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it("returns 'all' for null domain", () => {
|
|
96
|
+
expect(domainToScope(null)).toBe("all");
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
// Regression: verifies the split("-")[0] bug is fixed
|
|
100
|
+
it("expert-postgres-developer resolves to 'database' scope (not 'expert')", () => {
|
|
101
|
+
const domain = getAgentDomain("expert-postgres-developer");
|
|
102
|
+
expect(domainToScope(domain)).toBe("database");
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it("expert-nextjs-developer resolves to 'frontend' scope (not 'expert')", () => {
|
|
106
|
+
const domain = getAgentDomain("expert-nextjs-developer");
|
|
107
|
+
expect(domainToScope(domain)).toBe("frontend");
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it("golang-pro resolves to 'backend' scope (not 'golang')", () => {
|
|
111
|
+
const domain = getAgentDomain("golang-pro");
|
|
112
|
+
expect(domainToScope(domain)).toBe("backend");
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
// ── adjustSectionPriorities ──────────────────────────────────
|
|
117
|
+
|
|
118
|
+
describe("adjustSectionPriorities", () => {
|
|
119
|
+
it("null domain passes all sections through unchanged", () => {
|
|
120
|
+
const sections = makeSections();
|
|
121
|
+
const result = adjustSectionPriorities(sections, null);
|
|
122
|
+
expect(result).toHaveLength(12);
|
|
123
|
+
expect(result).toEqual(sections);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it("explore domain excludes relevance-0 sections", () => {
|
|
127
|
+
const result = adjustSectionPriorities(makeSections(), "explore");
|
|
128
|
+
const names = result.map(s => s.name);
|
|
129
|
+
|
|
130
|
+
// explore profile: 7 sections have relevance 0
|
|
131
|
+
expect(names).not.toContain("STANDARDS");
|
|
132
|
+
expect(names).not.toContain("CONTEXTO DE TASKS ANTERIORES");
|
|
133
|
+
expect(names).not.toContain("GRAPH");
|
|
134
|
+
expect(names).not.toContain("PRODUTO");
|
|
135
|
+
expect(names).not.toContain("UTILITIES");
|
|
136
|
+
expect(names).not.toContain("PATTERNS");
|
|
137
|
+
expect(names).not.toContain("HINTS");
|
|
138
|
+
|
|
139
|
+
// Should contain the 5 non-zero sections
|
|
140
|
+
expect(names).toContain("ALERTAS");
|
|
141
|
+
expect(names).toContain("ARQUITETURA");
|
|
142
|
+
expect(names).toContain("DECISOES");
|
|
143
|
+
expect(names).toContain("DISCOVERIES");
|
|
144
|
+
expect(names).toContain("STACK");
|
|
145
|
+
expect(result).toHaveLength(5);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it("essential sections (relevance=3) get priority boosted by -2", () => {
|
|
149
|
+
const result = adjustSectionPriorities(makeSections(), "backend");
|
|
150
|
+
|
|
151
|
+
// backend: DECISOES=3 (base 4 + offset -2 = 2)
|
|
152
|
+
const decisoes = result.find(s => s.name === "DECISOES");
|
|
153
|
+
expect(decisoes?.priority).toBe(2);
|
|
154
|
+
|
|
155
|
+
// backend: UTILITIES=3 (base 8 + offset -2 = 6)
|
|
156
|
+
const utilities = result.find(s => s.name === "UTILITIES");
|
|
157
|
+
expect(utilities?.priority).toBe(6);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it("priority floor is 1 (never goes below)", () => {
|
|
161
|
+
const result = adjustSectionPriorities(makeSections(), "backend");
|
|
162
|
+
|
|
163
|
+
// backend: STANDARDS=3 (base 1 + offset -2 = -1 → floor 1)
|
|
164
|
+
const standards = result.find(s => s.name === "STANDARDS");
|
|
165
|
+
expect(standards?.priority).toBe(1);
|
|
166
|
+
|
|
167
|
+
// backend: ALERTAS=3 (base 2 + offset -2 = 0 → floor 1)
|
|
168
|
+
const alertas = result.find(s => s.name === "ALERTAS");
|
|
169
|
+
expect(alertas?.priority).toBe(1);
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
it("nice-to-have sections (relevance=1) get deprioritized by +4", () => {
|
|
173
|
+
const result = adjustSectionPriorities(makeSections(), "backend");
|
|
174
|
+
|
|
175
|
+
// backend: PRODUTO=1 (base 7 + offset +4 = 11)
|
|
176
|
+
const produto = result.find(s => s.name === "PRODUTO");
|
|
177
|
+
expect(produto?.priority).toBe(11);
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it("useful sections (relevance=2) keep base priority", () => {
|
|
181
|
+
const result = adjustSectionPriorities(makeSections(), "backend");
|
|
182
|
+
|
|
183
|
+
// backend: ARQUITETURA=2 (base 3 + offset 0 = 3)
|
|
184
|
+
const arq = result.find(s => s.name === "ARQUITETURA");
|
|
185
|
+
expect(arq?.priority).toBe(3);
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
it("testing domain excludes only PRODUTO (relevance=0)", () => {
|
|
189
|
+
const result = adjustSectionPriorities(makeSections(), "testing");
|
|
190
|
+
const names = result.map(s => s.name);
|
|
191
|
+
expect(names).not.toContain("PRODUTO");
|
|
192
|
+
expect(result).toHaveLength(11);
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
it("testing domain boosts CONTEXTO DE TASKS ANTERIORES (relevance=3)", () => {
|
|
196
|
+
const result = adjustSectionPriorities(makeSections(), "testing");
|
|
197
|
+
const ctx = result.find(s => s.name === "CONTEXTO DE TASKS ANTERIORES");
|
|
198
|
+
// base 5 + offset -2 = 3
|
|
199
|
+
expect(ctx?.priority).toBe(3);
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
it("database domain excludes PRODUTO (relevance=0)", () => {
|
|
203
|
+
const result = adjustSectionPriorities(makeSections(), "database");
|
|
204
|
+
const names = result.map(s => s.name);
|
|
205
|
+
expect(names).not.toContain("PRODUTO");
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
it("frontend domain keeps PRODUTO with boosted priority", () => {
|
|
209
|
+
const result = adjustSectionPriorities(makeSections(), "frontend");
|
|
210
|
+
const produto = result.find(s => s.name === "PRODUTO");
|
|
211
|
+
// frontend: PRODUTO=3 (base 7 + offset -2 = 5)
|
|
212
|
+
expect(produto?.priority).toBe(5);
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
it("security domain excludes PRODUTO (relevance=0)", () => {
|
|
216
|
+
const result = adjustSectionPriorities(makeSections(), "security");
|
|
217
|
+
const names = result.map(s => s.name);
|
|
218
|
+
expect(names).not.toContain("PRODUTO");
|
|
219
|
+
expect(result).toHaveLength(11);
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
it("handles unknown section names gracefully (forward compatibility)", () => {
|
|
223
|
+
const sections: ContextSection[] = [
|
|
224
|
+
{ name: "FUTURE_SECTION", content: "...", priority: 5 },
|
|
225
|
+
{ name: "STANDARDS", content: "...", priority: 1 },
|
|
226
|
+
];
|
|
227
|
+
const result = adjustSectionPriorities(sections, "backend");
|
|
228
|
+
expect(result).toHaveLength(2);
|
|
229
|
+
const future = result.find(s => s.name === "FUTURE_SECTION");
|
|
230
|
+
expect(future?.priority).toBe(5); // unchanged
|
|
231
|
+
});
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
// ── DOMAIN_PROFILES completeness ─────────────────────────────
|
|
235
|
+
|
|
236
|
+
describe("DOMAIN_PROFILES completeness", () => {
|
|
237
|
+
const allSectionNames = [
|
|
238
|
+
"STANDARDS", "ALERTAS", "ARQUITETURA", "DECISOES",
|
|
239
|
+
"CONTEXTO DE TASKS ANTERIORES", "GRAPH", "PRODUTO",
|
|
240
|
+
"DISCOVERIES", "UTILITIES", "PATTERNS", "HINTS", "STACK",
|
|
241
|
+
];
|
|
242
|
+
|
|
243
|
+
it("every domain profile covers all 12 sections", () => {
|
|
244
|
+
for (const [domain, profile] of Object.entries(DOMAIN_PROFILES)) {
|
|
245
|
+
for (const section of allSectionNames) {
|
|
246
|
+
expect(profile[section as keyof typeof profile]).toBeDefined();
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
it("all relevance values are 0, 1, 2, or 3", () => {
|
|
252
|
+
for (const [domain, profile] of Object.entries(DOMAIN_PROFILES)) {
|
|
253
|
+
for (const [section, relevance] of Object.entries(profile)) {
|
|
254
|
+
expect([0, 1, 2, 3]).toContain(relevance);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
});
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
// ── AGENT_DOMAIN completeness ────────────────────────────────
|
|
261
|
+
|
|
262
|
+
describe("AGENT_DOMAIN completeness", () => {
|
|
263
|
+
it("maps all 11 known agents", () => {
|
|
264
|
+
const knownAgents = [
|
|
265
|
+
"backend-javascript", "expert-csharp-developer", "golang-pro",
|
|
266
|
+
"expert-nextjs-developer", "frontend-flutter",
|
|
267
|
+
"expert-postgres-developer",
|
|
268
|
+
"testing-unit",
|
|
269
|
+
"expert-code-reviewer",
|
|
270
|
+
"security-specialist",
|
|
271
|
+
"deep-explore",
|
|
272
|
+
"architect",
|
|
273
|
+
];
|
|
274
|
+
for (const agent of knownAgents) {
|
|
275
|
+
expect(AGENT_DOMAIN[agent]).toBeDefined();
|
|
276
|
+
}
|
|
277
|
+
});
|
|
278
|
+
});
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
// ═══════════════════════════════════════════════════════════════
|
|
2
|
+
// DOMAIN-BASED CONTEXT PRIORITY (v9.4)
|
|
3
|
+
// Replaces binary AGENT_SECTIONS with relevance-based filtering.
|
|
4
|
+
// ═══════════════════════════════════════════════════════════════
|
|
5
|
+
|
|
6
|
+
import type { ContextSection } from "./assembly";
|
|
7
|
+
|
|
8
|
+
export type AgentDomain =
|
|
9
|
+
| "backend"
|
|
10
|
+
| "frontend"
|
|
11
|
+
| "database"
|
|
12
|
+
| "testing"
|
|
13
|
+
| "review"
|
|
14
|
+
| "security"
|
|
15
|
+
| "explore"
|
|
16
|
+
| "architecture";
|
|
17
|
+
|
|
18
|
+
export type Relevance = 0 | 1 | 2 | 3;
|
|
19
|
+
|
|
20
|
+
export type SectionName =
|
|
21
|
+
| "STANDARDS"
|
|
22
|
+
| "ALERTAS"
|
|
23
|
+
| "ARQUITETURA"
|
|
24
|
+
| "DECISOES"
|
|
25
|
+
| "CONTEXTO DE TASKS ANTERIORES"
|
|
26
|
+
| "GRAPH"
|
|
27
|
+
| "PRODUTO"
|
|
28
|
+
| "DISCOVERIES"
|
|
29
|
+
| "UTILITIES"
|
|
30
|
+
| "PATTERNS"
|
|
31
|
+
| "HINTS"
|
|
32
|
+
| "STACK";
|
|
33
|
+
|
|
34
|
+
export type DomainProfile = Record<SectionName, Relevance>;
|
|
35
|
+
|
|
36
|
+
// ── Agent → Domain mapping ───────────────────────────────────
|
|
37
|
+
|
|
38
|
+
export const AGENT_DOMAIN: Record<string, AgentDomain> = {
|
|
39
|
+
"backend-javascript": "backend",
|
|
40
|
+
"expert-csharp-developer": "backend",
|
|
41
|
+
"golang-pro": "backend",
|
|
42
|
+
"expert-nextjs-developer": "frontend",
|
|
43
|
+
"frontend-flutter": "frontend",
|
|
44
|
+
"expert-postgres-developer": "database",
|
|
45
|
+
"testing-unit": "testing",
|
|
46
|
+
"expert-code-reviewer": "review",
|
|
47
|
+
"security-specialist": "security",
|
|
48
|
+
"deep-explore": "explore",
|
|
49
|
+
"architect": "architecture",
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
// ── Domain → Section relevance profiles ──────────────────────
|
|
53
|
+
//
|
|
54
|
+
// 3 = essential (priority offset -2, survives truncation)
|
|
55
|
+
// 2 = useful (priority offset 0, base priority kept)
|
|
56
|
+
// 1 = nice-to-have (priority offset +4, dropped first on truncation)
|
|
57
|
+
// 0 = noise (excluded entirely)
|
|
58
|
+
|
|
59
|
+
export const DOMAIN_PROFILES: Record<AgentDomain, DomainProfile> = {
|
|
60
|
+
backend: {
|
|
61
|
+
"STANDARDS": 3, "ALERTAS": 3, "ARQUITETURA": 2, "DECISOES": 3,
|
|
62
|
+
"CONTEXTO DE TASKS ANTERIORES": 2, "GRAPH": 2, "PRODUTO": 1,
|
|
63
|
+
"DISCOVERIES": 2, "UTILITIES": 3, "PATTERNS": 2, "HINTS": 2, "STACK": 2,
|
|
64
|
+
},
|
|
65
|
+
frontend: {
|
|
66
|
+
"STANDARDS": 3, "ALERTAS": 3, "ARQUITETURA": 2, "DECISOES": 3,
|
|
67
|
+
"CONTEXTO DE TASKS ANTERIORES": 2, "GRAPH": 2, "PRODUTO": 3,
|
|
68
|
+
"DISCOVERIES": 2, "UTILITIES": 3, "PATTERNS": 3, "HINTS": 2, "STACK": 2,
|
|
69
|
+
},
|
|
70
|
+
database: {
|
|
71
|
+
"STANDARDS": 3, "ALERTAS": 3, "ARQUITETURA": 3, "DECISOES": 3,
|
|
72
|
+
"CONTEXTO DE TASKS ANTERIORES": 2, "GRAPH": 2, "PRODUTO": 0,
|
|
73
|
+
"DISCOVERIES": 2, "UTILITIES": 1, "PATTERNS": 2, "HINTS": 2, "STACK": 3,
|
|
74
|
+
},
|
|
75
|
+
testing: {
|
|
76
|
+
"STANDARDS": 3, "ALERTAS": 3, "ARQUITETURA": 1, "DECISOES": 2,
|
|
77
|
+
"CONTEXTO DE TASKS ANTERIORES": 3, "GRAPH": 1, "PRODUTO": 0,
|
|
78
|
+
"DISCOVERIES": 2, "UTILITIES": 3, "PATTERNS": 3, "HINTS": 2, "STACK": 1,
|
|
79
|
+
},
|
|
80
|
+
review: {
|
|
81
|
+
"STANDARDS": 3, "ALERTAS": 3, "ARQUITETURA": 3, "DECISOES": 3,
|
|
82
|
+
"CONTEXTO DE TASKS ANTERIORES": 2, "GRAPH": 2, "PRODUTO": 1,
|
|
83
|
+
"DISCOVERIES": 2, "UTILITIES": 3, "PATTERNS": 2, "HINTS": 3, "STACK": 2,
|
|
84
|
+
},
|
|
85
|
+
security: {
|
|
86
|
+
"STANDARDS": 3, "ALERTAS": 3, "ARQUITETURA": 2, "DECISOES": 2,
|
|
87
|
+
"CONTEXTO DE TASKS ANTERIORES": 1, "GRAPH": 1, "PRODUTO": 0,
|
|
88
|
+
"DISCOVERIES": 2, "UTILITIES": 1, "PATTERNS": 1, "HINTS": 2, "STACK": 3,
|
|
89
|
+
},
|
|
90
|
+
explore: {
|
|
91
|
+
"STANDARDS": 0, "ALERTAS": 1, "ARQUITETURA": 3, "DECISOES": 1,
|
|
92
|
+
"CONTEXTO DE TASKS ANTERIORES": 0, "GRAPH": 0, "PRODUTO": 0,
|
|
93
|
+
"DISCOVERIES": 1, "UTILITIES": 0, "PATTERNS": 0, "HINTS": 0, "STACK": 3,
|
|
94
|
+
},
|
|
95
|
+
architecture: {
|
|
96
|
+
"STANDARDS": 2, "ALERTAS": 2, "ARQUITETURA": 3, "DECISOES": 3,
|
|
97
|
+
"CONTEXTO DE TASKS ANTERIORES": 2, "GRAPH": 2, "PRODUTO": 3,
|
|
98
|
+
"DISCOVERIES": 2, "UTILITIES": 1, "PATTERNS": 2, "HINTS": 1, "STACK": 3,
|
|
99
|
+
},
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
// ── Priority offset per relevance level ──────────────────────
|
|
103
|
+
|
|
104
|
+
const RELEVANCE_OFFSET: Record<Relevance, number> = {
|
|
105
|
+
3: -2, // essential: boost
|
|
106
|
+
2: 0, // useful: keep as-is
|
|
107
|
+
1: 4, // nice-to-have: deprioritize
|
|
108
|
+
0: 0, // noise: excluded before this applies
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
// ── Functions ────────────────────────────────────────────────
|
|
112
|
+
|
|
113
|
+
export function getAgentDomain(agentName: string | null | undefined): AgentDomain | null {
|
|
114
|
+
if (!agentName) return null;
|
|
115
|
+
return AGENT_DOMAIN[agentName] ?? null;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export function domainToScope(domain: AgentDomain | null): string {
|
|
119
|
+
if (!domain) return "all";
|
|
120
|
+
|
|
121
|
+
const scopeMap: Record<AgentDomain, string> = {
|
|
122
|
+
backend: "backend",
|
|
123
|
+
frontend: "frontend",
|
|
124
|
+
database: "database",
|
|
125
|
+
testing: "testing",
|
|
126
|
+
review: "all",
|
|
127
|
+
security: "all",
|
|
128
|
+
explore: "all",
|
|
129
|
+
architecture: "all",
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
return scopeMap[domain];
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export function adjustSectionPriorities(
|
|
136
|
+
sections: ContextSection[],
|
|
137
|
+
domain: AgentDomain | null,
|
|
138
|
+
): ContextSection[] {
|
|
139
|
+
if (!domain) return sections;
|
|
140
|
+
|
|
141
|
+
const profile = DOMAIN_PROFILES[domain];
|
|
142
|
+
|
|
143
|
+
return sections
|
|
144
|
+
.filter(s => {
|
|
145
|
+
const relevance = profile[s.name as SectionName];
|
|
146
|
+
// Unknown section names pass through (forward compatibility)
|
|
147
|
+
return relevance === undefined || relevance > 0;
|
|
148
|
+
})
|
|
149
|
+
.map(s => {
|
|
150
|
+
const relevance = profile[s.name as SectionName];
|
|
151
|
+
if (relevance === undefined) return s;
|
|
152
|
+
|
|
153
|
+
const offset = RELEVANCE_OFFSET[relevance];
|
|
154
|
+
return { ...s, priority: Math.max(1, s.priority + offset) };
|
|
155
|
+
});
|
|
156
|
+
}
|
package/context/generator.ts
CHANGED
|
@@ -2,7 +2,8 @@ import { getDb } from "../db/connection";
|
|
|
2
2
|
import { initSchema, getPatternsForFiles, getRelatedDecisions, getArchitecturalAnalysisForSpec } from "../db/schema";
|
|
3
3
|
import { getKnowledgeForTask } from "../commands/knowledge";
|
|
4
4
|
import type { ContextSection, ContextData } from "./assembly";
|
|
5
|
-
import { assembleSections
|
|
5
|
+
import { assembleSections } from "./assembly";
|
|
6
|
+
import { getAgentDomain, adjustSectionPriorities, domainToScope } from "./domains";
|
|
6
7
|
import { filterRelevantDecisions, filterRelevantStandards } from "./scoring";
|
|
7
8
|
import {
|
|
8
9
|
buildProductSection,
|
|
@@ -33,7 +34,7 @@ export function getMinimalContextForSubagent(taskId: number): string {
|
|
|
33
34
|
const context = db.query("SELECT * FROM context WHERE spec_id = ?").get(task.spec_id) as any;
|
|
34
35
|
|
|
35
36
|
const taskFiles = task.files ? JSON.parse(task.files) : [];
|
|
36
|
-
const domain = task.agent
|
|
37
|
+
const domain = domainToScope(getAgentDomain(task.agent));
|
|
37
38
|
|
|
38
39
|
// 1. Standards REQUIRED apenas (nao recommended)
|
|
39
40
|
const requiredStandards = db.query(
|
|
@@ -42,11 +43,12 @@ export function getMinimalContextForSubagent(taskId: number): string {
|
|
|
42
43
|
LIMIT 10`
|
|
43
44
|
).all(domain) as any[];
|
|
44
45
|
|
|
45
|
-
// 2. Blockers CRITICAL
|
|
46
|
+
// 2. Blockers CRITICAL (spec atual + cross-feature)
|
|
46
47
|
const criticalBlockers = db.query(
|
|
47
|
-
`SELECT content FROM knowledge
|
|
48
|
-
WHERE
|
|
49
|
-
ORDER BY created_at DESC
|
|
48
|
+
`SELECT DISTINCT content FROM knowledge
|
|
49
|
+
WHERE severity = 'critical'
|
|
50
|
+
ORDER BY CASE WHEN spec_id = ? THEN 0 ELSE 1 END, created_at DESC
|
|
51
|
+
LIMIT 5`
|
|
50
52
|
).all(task.spec_id) as any[];
|
|
51
53
|
|
|
52
54
|
// 3. Decisoes da task anterior (dependency direta)
|
|
@@ -124,7 +126,7 @@ function fetchContextData(taskId: number): ContextData | null {
|
|
|
124
126
|
|
|
125
127
|
// Arquivos da task para filtrar contexto relevante
|
|
126
128
|
const taskFiles = task.files ? JSON.parse(task.files) : [];
|
|
127
|
-
const domain = task.agent
|
|
129
|
+
const domain = domainToScope(getAgentDomain(task.agent));
|
|
128
130
|
|
|
129
131
|
// Decisoes relevantes (max 8, priorizando as que mencionam arquivos da task)
|
|
130
132
|
const allDecisions = db
|
|
@@ -262,12 +264,9 @@ export function getContextForSubagent(taskId: number): string {
|
|
|
262
264
|
buildHintsSection(data),
|
|
263
265
|
].filter((s): s is ContextSection => s !== null);
|
|
264
266
|
|
|
265
|
-
// v9.
|
|
266
|
-
const
|
|
267
|
-
const
|
|
268
|
-
const sections = allowedSections
|
|
269
|
-
? allSections.filter(s => allowedSections.includes(s.name))
|
|
270
|
-
: allSections;
|
|
267
|
+
// v9.4: Domain-based priority adjustment (replaces binary AGENT_SECTIONS)
|
|
268
|
+
const agentDomain = getAgentDomain(data.task.agent);
|
|
269
|
+
const sections = adjustSectionPriorities(allSections, agentDomain);
|
|
271
270
|
|
|
272
271
|
return assembleSections(header, sections);
|
|
273
272
|
}
|
package/context/index.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
// Re-exports for backward compatibility
|
|
2
2
|
export { getContextForSubagent, getMinimalContextForSubagent } from "./generator";
|
|
3
|
-
export { assembleSections, MAX_CONTEXT_SIZE
|
|
3
|
+
export { assembleSections, MAX_CONTEXT_SIZE } from "./assembly";
|
|
4
|
+
export { AGENT_DOMAIN, DOMAIN_PROFILES, getAgentDomain, adjustSectionPriorities, domainToScope } from "./domains";
|
|
5
|
+
export type { AgentDomain, Relevance, SectionName, DomainProfile } from "./domains";
|
|
4
6
|
export type { ContextSection, ContextData } from "./assembly";
|
|
5
7
|
export { filterRelevantDecisions, filterRelevantStandards } from "./scoring";
|
|
6
8
|
export {
|
package/context/sections.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { getUtilitiesForContext, getAgentHints } from "../db/schema";
|
|
2
2
|
import type { ContextSection, ContextData } from "./assembly";
|
|
3
|
+
import { getAgentDomain, domainToScope } from "./domains";
|
|
3
4
|
|
|
4
5
|
// ── Section Builders ──────────────────────────────────────────
|
|
5
6
|
|
|
@@ -162,7 +163,7 @@ export function buildUtilitiesSection(data: ContextData): ContextSection | null
|
|
|
162
163
|
return parts.slice(0, -1).join("/");
|
|
163
164
|
}).filter(Boolean))];
|
|
164
165
|
|
|
165
|
-
const agentScope = data.task.agent
|
|
166
|
+
const agentScope = domainToScope(getAgentDomain(data.task.agent)) || undefined;
|
|
166
167
|
let relevantUtilities = getUtilitiesForContext(taskDirs, undefined, 15);
|
|
167
168
|
|
|
168
169
|
if (relevantUtilities.length < 5 && agentScope) {
|
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
|
+
}
|