@codexa/cli 9.0.12 → 9.0.13
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/plan.ts +21 -3
- package/commands/task.ts +19 -0
- package/commands/utils.test.ts +6 -2
- package/commands/utils.ts +2 -0
- package/context/agent-expertise.test.ts +101 -0
- package/context/agent-expertise.ts +134 -0
- package/context/agent-registry.test.ts +195 -0
- package/context/agent-registry.ts +207 -0
- package/context/domains.test.ts +33 -0
- package/context/domains.ts +9 -16
- package/context/index.ts +3 -0
- package/package.json +1 -1
- package/templates/subagent-context.md +3 -1
package/commands/plan.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { getDb } from "../db/connection";
|
|
|
2
2
|
import { initSchema, getArchitecturalAnalysisForSpec } from "../db/schema";
|
|
3
3
|
import { resolveSpec } from "./spec-resolver";
|
|
4
4
|
import { CodexaError, ValidationError } from "../errors";
|
|
5
|
+
import { resolveAgent, resolveAgentName, suggestAgent, getCanonicalAgentNames } from "../context/agent-registry";
|
|
5
6
|
|
|
6
7
|
export function generateSpecId(name: string): string {
|
|
7
8
|
const date = new Date().toISOString().split("T")[0];
|
|
@@ -87,10 +88,11 @@ export function planStart(description: string, options: { fromAnalysis?: string;
|
|
|
87
88
|
const files = step.files && step.files.length > 0 ? JSON.stringify(step.files) : null;
|
|
88
89
|
const dependsOn = step.dependsOn && step.dependsOn.length > 0 ? JSON.stringify(step.dependsOn) : null;
|
|
89
90
|
|
|
91
|
+
const normalizedStepAgent = step.agent ? resolveAgentName(step.agent) : null;
|
|
90
92
|
db.run(
|
|
91
93
|
`INSERT INTO tasks (spec_id, number, name, agent, depends_on, can_parallel, files)
|
|
92
94
|
VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
|
93
|
-
[specId, step.number, step.name,
|
|
95
|
+
[specId, step.number, step.name, normalizedStepAgent, dependsOn, 1, files]
|
|
94
96
|
);
|
|
95
97
|
tasksCreated++;
|
|
96
98
|
}
|
|
@@ -266,6 +268,22 @@ export function planTaskAdd(options: {
|
|
|
266
268
|
}
|
|
267
269
|
}
|
|
268
270
|
|
|
271
|
+
// Validar e normalizar agent name (R3)
|
|
272
|
+
let normalizedAgent: string | null = options.agent || null;
|
|
273
|
+
if (normalizedAgent) {
|
|
274
|
+
const entry = resolveAgent(normalizedAgent);
|
|
275
|
+
if (!entry) {
|
|
276
|
+
const suggestion = suggestAgent(normalizedAgent);
|
|
277
|
+
const hint = suggestion
|
|
278
|
+
? `\nVoce quis dizer: "${suggestion}"?`
|
|
279
|
+
: `\nAgentes validos: ${getCanonicalAgentNames().join(", ")}`;
|
|
280
|
+
throw new ValidationError(
|
|
281
|
+
`Agente desconhecido: "${normalizedAgent}".${hint}`
|
|
282
|
+
);
|
|
283
|
+
}
|
|
284
|
+
normalizedAgent = entry.canonical;
|
|
285
|
+
}
|
|
286
|
+
|
|
269
287
|
// Parsear arquivos
|
|
270
288
|
let files: string[] = [];
|
|
271
289
|
if (options.files) {
|
|
@@ -279,7 +297,7 @@ export function planTaskAdd(options: {
|
|
|
279
297
|
spec.id,
|
|
280
298
|
nextNumber,
|
|
281
299
|
options.name,
|
|
282
|
-
|
|
300
|
+
normalizedAgent,
|
|
283
301
|
dependsOn.length > 0 ? JSON.stringify(dependsOn) : null,
|
|
284
302
|
options.sequential ? 0 : 1,
|
|
285
303
|
files.length > 0 ? JSON.stringify(files) : null,
|
|
@@ -295,7 +313,7 @@ export function planTaskAdd(options: {
|
|
|
295
313
|
]);
|
|
296
314
|
|
|
297
315
|
console.log(`\nTask #${nextNumber} adicionada: ${options.name}`);
|
|
298
|
-
if (
|
|
316
|
+
if (normalizedAgent) console.log(` Agente: ${normalizedAgent}${normalizedAgent !== options.agent ? ` (normalizado de "${options.agent}")` : ""}`);
|
|
299
317
|
if (dependsOn.length > 0) console.log(` Depende de: ${dependsOn.join(", ")}`);
|
|
300
318
|
if (files.length > 0) console.log(` Arquivos: ${files.join(", ")}`);
|
|
301
319
|
console.log(` Paralelizavel: ${options.sequential ? "Nao" : "Sim"}\n`);
|
package/commands/task.ts
CHANGED
|
@@ -9,6 +9,8 @@ import { loadTemplate } from "../templates/loader";
|
|
|
9
9
|
import { TaskStateError, ValidationError, KnowledgeBlockError } from "../errors";
|
|
10
10
|
import { resolveSpec, resolveSpecOrNull } from "./spec-resolver";
|
|
11
11
|
import { getAgentDomain, domainToScope } from "../context/domains";
|
|
12
|
+
import { resolveAgent } from "../context/agent-registry";
|
|
13
|
+
import { loadAgentExpertise, getAgentDescription } from "../context/agent-expertise";
|
|
12
14
|
|
|
13
15
|
export function taskNext(json: boolean = false, specId?: string): void {
|
|
14
16
|
initSchema();
|
|
@@ -224,11 +226,24 @@ export function taskStart(ids: string, json: boolean = false, fullContext: boole
|
|
|
224
226
|
confidence: p.confidence,
|
|
225
227
|
}));
|
|
226
228
|
|
|
229
|
+
// R1: Load condensed expertise from agent .md
|
|
230
|
+
const agentExpertise = task.agent ? loadAgentExpertise(task.agent) : "";
|
|
231
|
+
|
|
232
|
+
// R4: Build agent identity string
|
|
233
|
+
const agentEntry = task.agent ? resolveAgent(task.agent) : null;
|
|
234
|
+
const agentIdentity = agentEntry
|
|
235
|
+
? `## IDENTIDADE DO AGENTE\n\nVoce e um ${agentEntry.description}.\nSiga os principios e padroes descritos abaixo.\n`
|
|
236
|
+
: "";
|
|
237
|
+
|
|
238
|
+
// R5: Get description for orchestrator use
|
|
239
|
+
const agentDescription = task.agent ? getAgentDescription(task.agent) : null;
|
|
240
|
+
|
|
227
241
|
return {
|
|
228
242
|
taskId: task.id,
|
|
229
243
|
number: task.number,
|
|
230
244
|
name: task.name,
|
|
231
245
|
agent: task.agent,
|
|
246
|
+
agentDescription,
|
|
232
247
|
files: taskFiles,
|
|
233
248
|
// AVISO PARA O ORQUESTRADOR - NAO EXECUTE, DELEGUE
|
|
234
249
|
_orchestratorWarning: "NAO execute esta task diretamente. Use Task tool com subagent_type='general-purpose' para delegar. O campo 'subagentContext' abaixo e o prompt para o SUBAGENT.",
|
|
@@ -248,6 +263,10 @@ export function taskStart(ids: string, json: boolean = false, fullContext: boole
|
|
|
248
263
|
// Contexto para o SUBAGENT (o orquestrador deve passar isso via Task tool)
|
|
249
264
|
subagentContext: loadTemplate("subagent-context", {
|
|
250
265
|
filesList: taskFiles.map(f => ` - ${f}`).join('\n') || ' (nenhum arquivo especificado - analise o contexto)',
|
|
266
|
+
agentIdentity,
|
|
267
|
+
agentExpertise: agentExpertise
|
|
268
|
+
? `\n## EXPERTISE DO AGENTE\n\n${agentExpertise}\n\n`
|
|
269
|
+
: '',
|
|
251
270
|
}),
|
|
252
271
|
// Instrucoes de retorno para o SUBAGENT
|
|
253
272
|
subagentReturnProtocol: loadTemplate("subagent-return-protocol", {
|
package/commands/utils.test.ts
CHANGED
|
@@ -29,10 +29,14 @@ describe("Domain exports from utils (backward compatibility)", () => {
|
|
|
29
29
|
expect(domainToScope(domain)).toBe("backend");
|
|
30
30
|
});
|
|
31
31
|
|
|
32
|
+
it("aliases now resolve via registry", () => {
|
|
33
|
+
expect(getAgentDomain("frontend-next")).toBe("frontend");
|
|
34
|
+
expect(getAgentDomain("database-postgres")).toBe("database");
|
|
35
|
+
});
|
|
36
|
+
|
|
32
37
|
it("unknown agents return null domain and 'all' scope", () => {
|
|
33
|
-
expect(getAgentDomain("frontend-next")).toBeNull();
|
|
34
|
-
expect(getAgentDomain("database-postgres")).toBeNull();
|
|
35
38
|
expect(getAgentDomain("general-purpose")).toBeNull();
|
|
39
|
+
expect(getAgentDomain("some-unknown-agent")).toBeNull();
|
|
36
40
|
expect(domainToScope(getAgentDomain("general-purpose"))).toBe("all");
|
|
37
41
|
});
|
|
38
42
|
});
|
package/commands/utils.ts
CHANGED
|
@@ -11,6 +11,8 @@ import pkg from "../package.json";
|
|
|
11
11
|
export { getContextForSubagent, getMinimalContextForSubagent } from "../context/generator";
|
|
12
12
|
export { MAX_CONTEXT_SIZE } from "../context/assembly";
|
|
13
13
|
export { AGENT_DOMAIN, getAgentDomain, domainToScope } from "../context/domains";
|
|
14
|
+
export { resolveAgent, resolveAgentName, suggestAgent, getCanonicalAgentNames } from "../context/agent-registry";
|
|
15
|
+
export { loadAgentExpertise, getAgentDescription } from "../context/agent-expertise";
|
|
14
16
|
|
|
15
17
|
export function status(json: boolean = false, specId?: string): void {
|
|
16
18
|
initSchema();
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach } from "bun:test";
|
|
2
|
+
import { loadAgentExpertise, getAgentDescription, clearExpertiseCache } from "./agent-expertise";
|
|
3
|
+
|
|
4
|
+
beforeEach(() => {
|
|
5
|
+
clearExpertiseCache();
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
// ── loadAgentExpertise ───────────────────────────────────────
|
|
9
|
+
|
|
10
|
+
describe("loadAgentExpertise", () => {
|
|
11
|
+
it("returns non-empty string for known agents", () => {
|
|
12
|
+
const expertise = loadAgentExpertise("golang-pro");
|
|
13
|
+
expect(expertise.length).toBeGreaterThan(0);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it("resolves aliases before loading", () => {
|
|
17
|
+
const byCanonical = loadAgentExpertise("golang-pro");
|
|
18
|
+
clearExpertiseCache();
|
|
19
|
+
const byAlias = loadAgentExpertise("backend-go");
|
|
20
|
+
expect(byCanonical).toBe(byAlias);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it("loads different expertise for different agents", () => {
|
|
24
|
+
const goExpertise = loadAgentExpertise("golang-pro");
|
|
25
|
+
clearExpertiseCache();
|
|
26
|
+
const flutterExpertise = loadAgentExpertise("frontend-flutter");
|
|
27
|
+
expect(goExpertise).not.toBe(flutterExpertise);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it("returns empty for unknown agents", () => {
|
|
31
|
+
expect(loadAgentExpertise("nonexistent-agent")).toBe("");
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("returns empty for empty input", () => {
|
|
35
|
+
expect(loadAgentExpertise("")).toBe("");
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it("respects 2.5KB budget", () => {
|
|
39
|
+
// Check all known agents
|
|
40
|
+
const agents = ["golang-pro", "expert-csharp-developer", "backend-javascript",
|
|
41
|
+
"expert-nextjs-developer", "frontend-flutter", "expert-postgres-developer",
|
|
42
|
+
"testing-unit", "security-specialist"];
|
|
43
|
+
|
|
44
|
+
for (const agent of agents) {
|
|
45
|
+
clearExpertiseCache();
|
|
46
|
+
const expertise = loadAgentExpertise(agent);
|
|
47
|
+
// Allow small margin for truncation message
|
|
48
|
+
expect(expertise.length).toBeLessThanOrEqual(2560 + 100);
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it("strips YAML frontmatter", () => {
|
|
53
|
+
const expertise = loadAgentExpertise("golang-pro");
|
|
54
|
+
expect(expertise).not.toContain("---\nname:");
|
|
55
|
+
expect(expertise).not.toContain("invocation:");
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("strips redundant Retorno section", () => {
|
|
59
|
+
const expertise = loadAgentExpertise("golang-pro");
|
|
60
|
+
expect(expertise).not.toContain("**Retorno**: Siga");
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it("contains agent-specific content", () => {
|
|
64
|
+
const goExpertise = loadAgentExpertise("golang-pro");
|
|
65
|
+
expect(goExpertise).toContain("Go");
|
|
66
|
+
|
|
67
|
+
clearExpertiseCache();
|
|
68
|
+
const flutterExpertise = loadAgentExpertise("frontend-flutter");
|
|
69
|
+
expect(flutterExpertise).toContain("Flutter");
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it("caches results across calls", () => {
|
|
73
|
+
const first = loadAgentExpertise("golang-pro");
|
|
74
|
+
const second = loadAgentExpertise("golang-pro");
|
|
75
|
+
expect(first).toBe(second);
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
// ── getAgentDescription ──────────────────────────────────────
|
|
80
|
+
|
|
81
|
+
describe("getAgentDescription", () => {
|
|
82
|
+
it("returns description for known agent by canonical name", () => {
|
|
83
|
+
const desc = getAgentDescription("golang-pro");
|
|
84
|
+
expect(desc).not.toBeNull();
|
|
85
|
+
expect(desc).toContain("Go");
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it("returns description for known agent by alias", () => {
|
|
89
|
+
const desc = getAgentDescription("backend-go");
|
|
90
|
+
expect(desc).not.toBeNull();
|
|
91
|
+
expect(desc).toContain("Go");
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it("returns null for unknown agent", () => {
|
|
95
|
+
expect(getAgentDescription("nonexistent")).toBeNull();
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it("returns null for empty input", () => {
|
|
99
|
+
expect(getAgentDescription("")).toBeNull();
|
|
100
|
+
});
|
|
101
|
+
});
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
// ═══════════════════════════════════════════════════════════════
|
|
2
|
+
// AGENT EXPERTISE LOADER (v9.5)
|
|
3
|
+
// Loads and condenses agent .md expertise for injection into
|
|
4
|
+
// the subagent prompt. Budget: max 2.5KB per agent.
|
|
5
|
+
// ═══════════════════════════════════════════════════════════════
|
|
6
|
+
|
|
7
|
+
import { readFileSync, existsSync } from "fs";
|
|
8
|
+
import { join } from "path";
|
|
9
|
+
import { execSync } from "child_process";
|
|
10
|
+
import { resolveAgent } from "./agent-registry";
|
|
11
|
+
|
|
12
|
+
const MAX_EXPERTISE_SIZE = 2560; // 2.5KB budget
|
|
13
|
+
|
|
14
|
+
const expertiseCache = new Map<string, string>();
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Find the agents directory.
|
|
18
|
+
* Checks: 1) .claude/agents/ in cwd 2) plugins dir via git root
|
|
19
|
+
*/
|
|
20
|
+
export function findAgentsDir(): string | null {
|
|
21
|
+
// 1. Check synced agents in project
|
|
22
|
+
const projectAgents = join(process.cwd(), ".claude", "agents");
|
|
23
|
+
if (existsSync(projectAgents)) return projectAgents;
|
|
24
|
+
|
|
25
|
+
// 2. Dev repo: git root + plugins/codexa-workflow/agents/
|
|
26
|
+
try {
|
|
27
|
+
const gitRoot = execSync("git rev-parse --show-toplevel", { encoding: "utf-8" }).trim();
|
|
28
|
+
const pluginDir = join(gitRoot, "plugins", "codexa-workflow", "agents");
|
|
29
|
+
if (existsSync(pluginDir)) return pluginDir;
|
|
30
|
+
} catch {
|
|
31
|
+
// Not in git repo
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Load and condense expertise from an agent .md file.
|
|
39
|
+
* Strips frontmatter, condenses code blocks, truncates to budget.
|
|
40
|
+
* Returns empty string if agent not found or has no file.
|
|
41
|
+
*/
|
|
42
|
+
export function loadAgentExpertise(agentName: string): string {
|
|
43
|
+
if (!agentName) return "";
|
|
44
|
+
|
|
45
|
+
const cached = expertiseCache.get(agentName);
|
|
46
|
+
if (cached !== undefined) return cached;
|
|
47
|
+
|
|
48
|
+
const entry = resolveAgent(agentName);
|
|
49
|
+
if (!entry) {
|
|
50
|
+
expertiseCache.set(agentName, "");
|
|
51
|
+
return "";
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const agentsDir = findAgentsDir();
|
|
55
|
+
if (!agentsDir) {
|
|
56
|
+
expertiseCache.set(agentName, "");
|
|
57
|
+
return "";
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const filePath = join(agentsDir, `${entry.filename}.md`);
|
|
61
|
+
if (!existsSync(filePath)) {
|
|
62
|
+
expertiseCache.set(agentName, "");
|
|
63
|
+
return "";
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
try {
|
|
67
|
+
const raw = readFileSync(filePath, "utf-8");
|
|
68
|
+
const expertise = extractAndCondense(raw);
|
|
69
|
+
expertiseCache.set(agentName, expertise);
|
|
70
|
+
return expertise;
|
|
71
|
+
} catch {
|
|
72
|
+
expertiseCache.set(agentName, "");
|
|
73
|
+
return "";
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Get agent description from registry (R5).
|
|
79
|
+
*/
|
|
80
|
+
export function getAgentDescription(agentName: string): string | null {
|
|
81
|
+
const entry = resolveAgent(agentName);
|
|
82
|
+
return entry?.description ?? null;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Clear the expertise cache (for tests).
|
|
87
|
+
*/
|
|
88
|
+
export function clearExpertiseCache(): void {
|
|
89
|
+
expertiseCache.clear();
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// ── Internal helpers ─────────────────────────────────────────
|
|
93
|
+
|
|
94
|
+
function extractAndCondense(raw: string): string {
|
|
95
|
+
// Strip YAML frontmatter (--- ... ---)
|
|
96
|
+
const match = raw.match(/^---\n[\s\S]*?\n---\n([\s\S]*)$/);
|
|
97
|
+
if (!match) return "";
|
|
98
|
+
let body = match[1].trim();
|
|
99
|
+
|
|
100
|
+
// Remove redundant "Retorno" footer (already in return protocol template)
|
|
101
|
+
body = body.replace(/---\s*\n+\*\*Retorno\*\*:.*$/s, "").trim();
|
|
102
|
+
|
|
103
|
+
// Condense code blocks: keep ≤8 lines as-is, >8 keeps first 6
|
|
104
|
+
body = condenseCodeBlocks(body);
|
|
105
|
+
|
|
106
|
+
// Truncate to budget at section boundary
|
|
107
|
+
if (body.length > MAX_EXPERTISE_SIZE) {
|
|
108
|
+
const truncPoint = findSectionBoundary(body, MAX_EXPERTISE_SIZE - 80);
|
|
109
|
+
body = body.substring(0, truncPoint).trim() +
|
|
110
|
+
"\n\n[Expertise truncada — use agent .md completo se necessario]";
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return body;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function condenseCodeBlocks(text: string): string {
|
|
117
|
+
return text.replace(/```(\w*)\n([\s\S]*?)```/g, (_match, lang: string, code: string) => {
|
|
118
|
+
const lines = code.split("\n");
|
|
119
|
+
if (lines.length <= 8) return _match;
|
|
120
|
+
const kept = lines.slice(0, 6).join("\n");
|
|
121
|
+
return `\`\`\`${lang}\n${kept}\n// ... (${lines.length - 6} linhas omitidas)\n\`\`\``;
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function findSectionBoundary(text: string, targetLen: number): number {
|
|
126
|
+
let i = Math.min(targetLen, text.length);
|
|
127
|
+
const floor = Math.max(0, targetLen - 500);
|
|
128
|
+
while (i > floor) {
|
|
129
|
+
if (text[i] === "\n" && text.substring(i + 1, i + 4) === "## ") return i;
|
|
130
|
+
if (text[i] === "\n" && text.substring(i + 1, i + 5) === "### ") return i;
|
|
131
|
+
i--;
|
|
132
|
+
}
|
|
133
|
+
return targetLen;
|
|
134
|
+
}
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import { describe, it, expect } from "bun:test";
|
|
2
|
+
import {
|
|
3
|
+
AGENT_REGISTRY,
|
|
4
|
+
resolveAgent,
|
|
5
|
+
resolveAgentName,
|
|
6
|
+
suggestAgent,
|
|
7
|
+
buildAgentDomainMap,
|
|
8
|
+
getAllAgentNames,
|
|
9
|
+
getCanonicalAgentNames,
|
|
10
|
+
} from "./agent-registry";
|
|
11
|
+
|
|
12
|
+
// ── AGENT_REGISTRY structure ─────────────────────────────────
|
|
13
|
+
|
|
14
|
+
describe("AGENT_REGISTRY", () => {
|
|
15
|
+
it("contains 11 agents", () => {
|
|
16
|
+
expect(AGENT_REGISTRY).toHaveLength(11);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it("every entry has required fields", () => {
|
|
20
|
+
for (const entry of AGENT_REGISTRY) {
|
|
21
|
+
expect(entry.canonical).toBeTruthy();
|
|
22
|
+
expect(entry.filename).toBeTruthy();
|
|
23
|
+
expect(entry.domain).toBeTruthy();
|
|
24
|
+
expect(entry.description).toBeTruthy();
|
|
25
|
+
expect(Array.isArray(entry.aliases)).toBe(true);
|
|
26
|
+
expect(entry.aliases.length).toBeGreaterThan(0);
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it("filename is always present as alias", () => {
|
|
31
|
+
for (const entry of AGENT_REGISTRY) {
|
|
32
|
+
// filename should be resolvable (either as alias or as canonical)
|
|
33
|
+
const resolved = resolveAgent(entry.filename);
|
|
34
|
+
expect(resolved).not.toBeNull();
|
|
35
|
+
expect(resolved!.canonical).toBe(entry.canonical);
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it("canonical names are unique", () => {
|
|
40
|
+
const canonicals = AGENT_REGISTRY.map(e => e.canonical);
|
|
41
|
+
expect(new Set(canonicals).size).toBe(canonicals.length);
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
// ── resolveAgent ─────────────────────────────────────────────
|
|
46
|
+
|
|
47
|
+
describe("resolveAgent", () => {
|
|
48
|
+
it("resolves by canonical name", () => {
|
|
49
|
+
expect(resolveAgent("golang-pro")?.canonical).toBe("golang-pro");
|
|
50
|
+
expect(resolveAgent("architect")?.canonical).toBe("architect");
|
|
51
|
+
expect(resolveAgent("backend-javascript")?.canonical).toBe("backend-javascript");
|
|
52
|
+
expect(resolveAgent("frontend-flutter")?.canonical).toBe("frontend-flutter");
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("resolves by filename (alias)", () => {
|
|
56
|
+
expect(resolveAgent("backend-go")?.canonical).toBe("golang-pro");
|
|
57
|
+
expect(resolveAgent("backend-csharp")?.canonical).toBe("expert-csharp-developer");
|
|
58
|
+
expect(resolveAgent("frontend-next")?.canonical).toBe("expert-nextjs-developer");
|
|
59
|
+
expect(resolveAgent("database-postgres")?.canonical).toBe("expert-postgres-developer");
|
|
60
|
+
expect(resolveAgent("codexa-architect")?.canonical).toBe("architect");
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it("resolves by short aliases", () => {
|
|
64
|
+
expect(resolveAgent("go")?.canonical).toBe("golang-pro");
|
|
65
|
+
expect(resolveAgent("golang")?.canonical).toBe("golang-pro");
|
|
66
|
+
expect(resolveAgent("csharp")?.canonical).toBe("expert-csharp-developer");
|
|
67
|
+
expect(resolveAgent("dotnet")?.canonical).toBe("expert-csharp-developer");
|
|
68
|
+
expect(resolveAgent("nextjs")?.canonical).toBe("expert-nextjs-developer");
|
|
69
|
+
expect(resolveAgent("next")?.canonical).toBe("expert-nextjs-developer");
|
|
70
|
+
expect(resolveAgent("flutter")?.canonical).toBe("frontend-flutter");
|
|
71
|
+
expect(resolveAgent("dart")?.canonical).toBe("frontend-flutter");
|
|
72
|
+
expect(resolveAgent("postgres")?.canonical).toBe("expert-postgres-developer");
|
|
73
|
+
expect(resolveAgent("supabase")?.canonical).toBe("expert-postgres-developer");
|
|
74
|
+
expect(resolveAgent("security")?.canonical).toBe("security-specialist");
|
|
75
|
+
expect(resolveAgent("explore")?.canonical).toBe("deep-explore");
|
|
76
|
+
expect(resolveAgent("arch")?.canonical).toBe("architect");
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it("is case-insensitive", () => {
|
|
80
|
+
expect(resolveAgent("GOLANG-PRO")?.canonical).toBe("golang-pro");
|
|
81
|
+
expect(resolveAgent("Backend-Go")?.canonical).toBe("golang-pro");
|
|
82
|
+
expect(resolveAgent("FLUTTER")?.canonical).toBe("frontend-flutter");
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it("returns null for unknown agents", () => {
|
|
86
|
+
expect(resolveAgent("unknown-agent")).toBeNull();
|
|
87
|
+
expect(resolveAgent("general-purpose")).toBeNull();
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it("returns null for empty/null-like input", () => {
|
|
91
|
+
expect(resolveAgent("")).toBeNull();
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
// ── resolveAgentName ─────────────────────────────────────────
|
|
96
|
+
|
|
97
|
+
describe("resolveAgentName", () => {
|
|
98
|
+
it("normalizes aliases to canonical", () => {
|
|
99
|
+
expect(resolveAgentName("backend-go")).toBe("golang-pro");
|
|
100
|
+
expect(resolveAgentName("nextjs")).toBe("expert-nextjs-developer");
|
|
101
|
+
expect(resolveAgentName("postgres")).toBe("expert-postgres-developer");
|
|
102
|
+
expect(resolveAgentName("codexa-architect")).toBe("architect");
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it("keeps canonical names unchanged", () => {
|
|
106
|
+
expect(resolveAgentName("golang-pro")).toBe("golang-pro");
|
|
107
|
+
expect(resolveAgentName("architect")).toBe("architect");
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it("returns input unchanged if not found (backward compat)", () => {
|
|
111
|
+
expect(resolveAgentName("custom-agent")).toBe("custom-agent");
|
|
112
|
+
expect(resolveAgentName("my-special-agent")).toBe("my-special-agent");
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
// ── suggestAgent ─────────────────────────────────────────────
|
|
117
|
+
|
|
118
|
+
describe("suggestAgent", () => {
|
|
119
|
+
it("suggests for close typos", () => {
|
|
120
|
+
expect(suggestAgent("golng-pro")).toBe("golang-pro");
|
|
121
|
+
expect(suggestAgent("backend-goo")).toBe("golang-pro");
|
|
122
|
+
expect(suggestAgent("testing-unti")).toBe("testing-unit");
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it("suggests for minor misspellings of aliases", () => {
|
|
126
|
+
expect(suggestAgent("fluter")).toBe("frontend-flutter");
|
|
127
|
+
expect(suggestAgent("postgre")).toBe("expert-postgres-developer");
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it("returns null for very different strings", () => {
|
|
131
|
+
expect(suggestAgent("xyzxyzxyzxyz")).toBeNull();
|
|
132
|
+
expect(suggestAgent("completely-unrelated-name")).toBeNull();
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
// ── buildAgentDomainMap ──────────────────────────────────────
|
|
137
|
+
|
|
138
|
+
describe("buildAgentDomainMap", () => {
|
|
139
|
+
const derived = buildAgentDomainMap();
|
|
140
|
+
|
|
141
|
+
it("contains all 11 canonical agents", () => {
|
|
142
|
+
expect(Object.keys(derived)).toHaveLength(11);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it("maps backend agents correctly", () => {
|
|
146
|
+
expect(derived["golang-pro"]).toBe("backend");
|
|
147
|
+
expect(derived["expert-csharp-developer"]).toBe("backend");
|
|
148
|
+
expect(derived["backend-javascript"]).toBe("backend");
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it("maps frontend agents correctly", () => {
|
|
152
|
+
expect(derived["expert-nextjs-developer"]).toBe("frontend");
|
|
153
|
+
expect(derived["frontend-flutter"]).toBe("frontend");
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it("maps other domains correctly", () => {
|
|
157
|
+
expect(derived["expert-postgres-developer"]).toBe("database");
|
|
158
|
+
expect(derived["testing-unit"]).toBe("testing");
|
|
159
|
+
expect(derived["expert-code-reviewer"]).toBe("review");
|
|
160
|
+
expect(derived["security-specialist"]).toBe("security");
|
|
161
|
+
expect(derived["deep-explore"]).toBe("explore");
|
|
162
|
+
expect(derived["architect"]).toBe("architecture");
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
// ── getAllAgentNames / getCanonicalAgentNames ─────────────────
|
|
167
|
+
|
|
168
|
+
describe("getAllAgentNames", () => {
|
|
169
|
+
it("includes canonical names", () => {
|
|
170
|
+
const all = getAllAgentNames();
|
|
171
|
+
expect(all).toContain("golang-pro");
|
|
172
|
+
expect(all).toContain("architect");
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
it("includes aliases", () => {
|
|
176
|
+
const all = getAllAgentNames();
|
|
177
|
+
expect(all).toContain("backend-go");
|
|
178
|
+
expect(all).toContain("go");
|
|
179
|
+
expect(all).toContain("nextjs");
|
|
180
|
+
expect(all).toContain("flutter");
|
|
181
|
+
});
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
describe("getCanonicalAgentNames", () => {
|
|
185
|
+
it("returns exactly 11 names", () => {
|
|
186
|
+
expect(getCanonicalAgentNames()).toHaveLength(11);
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
it("contains only canonical names", () => {
|
|
190
|
+
const canonical = getCanonicalAgentNames();
|
|
191
|
+
expect(canonical).toContain("golang-pro");
|
|
192
|
+
expect(canonical).not.toContain("backend-go"); // alias, not canonical
|
|
193
|
+
expect(canonical).not.toContain("go"); // alias, not canonical
|
|
194
|
+
});
|
|
195
|
+
});
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
// ═══════════════════════════════════════════════════════════════
|
|
2
|
+
// AGENT REGISTRY (v9.5)
|
|
3
|
+
// Single source of truth for all agent metadata.
|
|
4
|
+
// Replaces hardcoded AGENT_DOMAIN in domains.ts.
|
|
5
|
+
// ═══════════════════════════════════════════════════════════════
|
|
6
|
+
|
|
7
|
+
import type { AgentDomain } from "./domains";
|
|
8
|
+
|
|
9
|
+
export interface AgentEntry {
|
|
10
|
+
/** Canonical name (= YAML `name` field in agent .md) */
|
|
11
|
+
canonical: string;
|
|
12
|
+
/** Filename without .md extension */
|
|
13
|
+
filename: string;
|
|
14
|
+
/** Domain for context priority */
|
|
15
|
+
domain: AgentDomain;
|
|
16
|
+
/** Human-readable description from YAML frontmatter */
|
|
17
|
+
description: string;
|
|
18
|
+
/** Aliases: filename-without-ext, abbreviations */
|
|
19
|
+
aliases: string[];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export const AGENT_REGISTRY: AgentEntry[] = [
|
|
23
|
+
{
|
|
24
|
+
canonical: "golang-pro",
|
|
25
|
+
filename: "backend-go",
|
|
26
|
+
domain: "backend",
|
|
27
|
+
description: "Go senior developer — goroutines, channels, interfaces, net/http 1.22+, error handling with wrapping",
|
|
28
|
+
aliases: ["backend-go", "go", "golang"],
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
canonical: "expert-csharp-developer",
|
|
32
|
+
filename: "backend-csharp",
|
|
33
|
+
domain: "backend",
|
|
34
|
+
description: "C#/.NET senior developer — ASP.NET Core 9+, Entity Framework Core, Minimal APIs, DI, modern C# 12+",
|
|
35
|
+
aliases: ["backend-csharp", "csharp", "dotnet", "c#"],
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
canonical: "backend-javascript",
|
|
39
|
+
filename: "backend-javascript",
|
|
40
|
+
domain: "backend",
|
|
41
|
+
description: "Backend JavaScript/TypeScript developer — Node.js (Express/Fastify) e Bun (Hono/Elysia), APIs REST, middleware",
|
|
42
|
+
aliases: ["backend-js", "nodejs", "bun-backend"],
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
canonical: "expert-nextjs-developer",
|
|
46
|
+
filename: "frontend-next",
|
|
47
|
+
domain: "frontend",
|
|
48
|
+
description: "Next.js 16 developer — App Router, Server Components, Cache Components, Turbopack, React 19.2",
|
|
49
|
+
aliases: ["frontend-next", "nextjs", "next"],
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
canonical: "frontend-flutter",
|
|
53
|
+
filename: "frontend-flutter",
|
|
54
|
+
domain: "frontend",
|
|
55
|
+
description: "Flutter/Dart senior developer — Riverpod, GoRouter, widget composition, platform-specific code",
|
|
56
|
+
aliases: ["flutter", "dart"],
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
canonical: "expert-postgres-developer",
|
|
60
|
+
filename: "database-postgres",
|
|
61
|
+
domain: "database",
|
|
62
|
+
description: "PostgreSQL/Supabase developer — schema design, query optimization, RLS policies, Drizzle ORM",
|
|
63
|
+
aliases: ["database-postgres", "postgres", "postgresql", "supabase"],
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
canonical: "testing-unit",
|
|
67
|
+
filename: "testing-unit",
|
|
68
|
+
domain: "testing",
|
|
69
|
+
description: "Unit testing specialist — Vitest/Jest, Testing Library, mocks, coverage",
|
|
70
|
+
aliases: ["testing", "unit-test", "vitest", "jest"],
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
canonical: "expert-code-reviewer",
|
|
74
|
+
filename: "expert-code-reviewer",
|
|
75
|
+
domain: "review",
|
|
76
|
+
description: "Code reviewer — quality assurance, security analysis, standards compliance, architectural validation",
|
|
77
|
+
aliases: ["reviewer", "code-reviewer", "review"],
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
canonical: "security-specialist",
|
|
81
|
+
filename: "security-specialist",
|
|
82
|
+
domain: "security",
|
|
83
|
+
description: "Security auditor — OWASP Top 10, secrets detection, dependency vulnerabilities, auth/authz",
|
|
84
|
+
aliases: ["security", "owasp"],
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
canonical: "deep-explore",
|
|
88
|
+
filename: "deep-explore",
|
|
89
|
+
domain: "explore",
|
|
90
|
+
description: "Codebase explorer — grepai semantic search, call graph tracing, architecture understanding",
|
|
91
|
+
aliases: ["explore", "grepai", "search"],
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
canonical: "architect",
|
|
95
|
+
filename: "codexa-architect",
|
|
96
|
+
domain: "architecture",
|
|
97
|
+
description: "Software architect — analise, design, decomposicao em baby steps, diagramas Mermaid, ADRs",
|
|
98
|
+
aliases: ["codexa-architect", "arch"],
|
|
99
|
+
},
|
|
100
|
+
];
|
|
101
|
+
|
|
102
|
+
// ── Lookup indexes (built once, O(1) lookups) ────────────────
|
|
103
|
+
|
|
104
|
+
const byCanonical = new Map<string, AgentEntry>();
|
|
105
|
+
const byAlias = new Map<string, AgentEntry>();
|
|
106
|
+
|
|
107
|
+
for (const entry of AGENT_REGISTRY) {
|
|
108
|
+
byCanonical.set(entry.canonical, entry);
|
|
109
|
+
byAlias.set(entry.canonical, entry);
|
|
110
|
+
byAlias.set(entry.filename, entry);
|
|
111
|
+
for (const alias of entry.aliases) {
|
|
112
|
+
byAlias.set(alias.toLowerCase(), entry);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Resolve any agent name variant (canonical, filename, alias) to registry entry.
|
|
118
|
+
* Case-insensitive. Returns null if no match found.
|
|
119
|
+
*/
|
|
120
|
+
export function resolveAgent(name: string): AgentEntry | null {
|
|
121
|
+
if (!name) return null;
|
|
122
|
+
const lower = name.toLowerCase();
|
|
123
|
+
return byCanonical.get(lower) ?? byAlias.get(lower) ?? null;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Resolve agent name to canonical name.
|
|
128
|
+
* Returns the input unchanged if not found (backward compat).
|
|
129
|
+
*/
|
|
130
|
+
export function resolveAgentName(name: string): string {
|
|
131
|
+
const entry = resolveAgent(name);
|
|
132
|
+
return entry ? entry.canonical : name;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Get all valid agent names (canonical + aliases) for validation/suggestions.
|
|
137
|
+
*/
|
|
138
|
+
export function getAllAgentNames(): string[] {
|
|
139
|
+
return Array.from(byAlias.keys());
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Get all canonical agent names.
|
|
144
|
+
*/
|
|
145
|
+
export function getCanonicalAgentNames(): string[] {
|
|
146
|
+
return AGENT_REGISTRY.map(e => e.canonical);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Did-you-mean suggestion using Levenshtein distance.
|
|
151
|
+
* Returns the closest canonical name if distance <= 4, else null.
|
|
152
|
+
*/
|
|
153
|
+
export function suggestAgent(input: string): string | null {
|
|
154
|
+
const lower = input.toLowerCase();
|
|
155
|
+
let bestMatch: string | null = null;
|
|
156
|
+
let bestDistance = Infinity;
|
|
157
|
+
|
|
158
|
+
for (const name of getAllAgentNames()) {
|
|
159
|
+
const d = levenshtein(lower, name);
|
|
160
|
+
if (d < bestDistance) {
|
|
161
|
+
bestDistance = d;
|
|
162
|
+
bestMatch = name;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (bestDistance <= 4 && bestMatch) {
|
|
167
|
+
const entry = resolveAgent(bestMatch);
|
|
168
|
+
return entry?.canonical ?? bestMatch;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return null;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Derive AGENT_DOMAIN map from registry (backward compat with domains.ts).
|
|
176
|
+
*/
|
|
177
|
+
export function buildAgentDomainMap(): Record<string, AgentDomain> {
|
|
178
|
+
const map: Record<string, AgentDomain> = {};
|
|
179
|
+
for (const entry of AGENT_REGISTRY) {
|
|
180
|
+
map[entry.canonical] = entry.domain;
|
|
181
|
+
}
|
|
182
|
+
return map;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// ── Levenshtein (minimal, no deps) ──────────────────────────
|
|
186
|
+
|
|
187
|
+
function levenshtein(a: string, b: string): number {
|
|
188
|
+
const m = a.length;
|
|
189
|
+
const n = b.length;
|
|
190
|
+
const dp: number[][] = Array.from({ length: m + 1 }, () => Array(n + 1).fill(0));
|
|
191
|
+
|
|
192
|
+
for (let i = 0; i <= m; i++) dp[i][0] = i;
|
|
193
|
+
for (let j = 0; j <= n; j++) dp[0][j] = j;
|
|
194
|
+
|
|
195
|
+
for (let i = 1; i <= m; i++) {
|
|
196
|
+
for (let j = 1; j <= n; j++) {
|
|
197
|
+
const cost = a[i - 1] === b[j - 1] ? 0 : 1;
|
|
198
|
+
dp[i][j] = Math.min(
|
|
199
|
+
dp[i - 1][j] + 1,
|
|
200
|
+
dp[i][j - 1] + 1,
|
|
201
|
+
dp[i - 1][j - 1] + cost,
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return dp[m][n];
|
|
207
|
+
}
|
package/context/domains.test.ts
CHANGED
|
@@ -298,3 +298,36 @@ describe("AGENT_DOMAIN completeness", () => {
|
|
|
298
298
|
}
|
|
299
299
|
});
|
|
300
300
|
});
|
|
301
|
+
|
|
302
|
+
// ── getAgentDomain with aliases (via registry) ───────────────
|
|
303
|
+
|
|
304
|
+
describe("getAgentDomain with aliases", () => {
|
|
305
|
+
it("resolves filename aliases to correct domain", () => {
|
|
306
|
+
expect(getAgentDomain("backend-go")).toBe("backend");
|
|
307
|
+
expect(getAgentDomain("backend-csharp")).toBe("backend");
|
|
308
|
+
expect(getAgentDomain("frontend-next")).toBe("frontend");
|
|
309
|
+
expect(getAgentDomain("database-postgres")).toBe("database");
|
|
310
|
+
expect(getAgentDomain("codexa-architect")).toBe("architecture");
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
it("resolves short aliases to correct domain", () => {
|
|
314
|
+
expect(getAgentDomain("go")).toBe("backend");
|
|
315
|
+
expect(getAgentDomain("golang")).toBe("backend");
|
|
316
|
+
expect(getAgentDomain("csharp")).toBe("backend");
|
|
317
|
+
expect(getAgentDomain("dotnet")).toBe("backend");
|
|
318
|
+
expect(getAgentDomain("nextjs")).toBe("frontend");
|
|
319
|
+
expect(getAgentDomain("next")).toBe("frontend");
|
|
320
|
+
expect(getAgentDomain("flutter")).toBe("frontend");
|
|
321
|
+
expect(getAgentDomain("dart")).toBe("frontend");
|
|
322
|
+
expect(getAgentDomain("postgres")).toBe("database");
|
|
323
|
+
expect(getAgentDomain("supabase")).toBe("database");
|
|
324
|
+
expect(getAgentDomain("security")).toBe("security");
|
|
325
|
+
expect(getAgentDomain("explore")).toBe("explore");
|
|
326
|
+
expect(getAgentDomain("arch")).toBe("architecture");
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
it("still returns null for truly unknown names", () => {
|
|
330
|
+
expect(getAgentDomain("xyz-unknown")).toBeNull();
|
|
331
|
+
expect(getAgentDomain("general-purpose")).toBeNull();
|
|
332
|
+
});
|
|
333
|
+
});
|
package/context/domains.ts
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
// ═══════════════════════════════════════════════════════════════
|
|
5
5
|
|
|
6
6
|
import type { ContextSection } from "./assembly";
|
|
7
|
+
import { buildAgentDomainMap, resolveAgent } from "./agent-registry";
|
|
7
8
|
|
|
8
9
|
export type AgentDomain =
|
|
9
10
|
| "backend"
|
|
@@ -34,21 +35,9 @@ export type SectionName =
|
|
|
34
35
|
|
|
35
36
|
export type DomainProfile = Record<SectionName, Relevance>;
|
|
36
37
|
|
|
37
|
-
// ── Agent → Domain mapping
|
|
38
|
-
|
|
39
|
-
export const AGENT_DOMAIN: Record<string, AgentDomain> =
|
|
40
|
-
"backend-javascript": "backend",
|
|
41
|
-
"expert-csharp-developer": "backend",
|
|
42
|
-
"golang-pro": "backend",
|
|
43
|
-
"expert-nextjs-developer": "frontend",
|
|
44
|
-
"frontend-flutter": "frontend",
|
|
45
|
-
"expert-postgres-developer": "database",
|
|
46
|
-
"testing-unit": "testing",
|
|
47
|
-
"expert-code-reviewer": "review",
|
|
48
|
-
"security-specialist": "security",
|
|
49
|
-
"deep-explore": "explore",
|
|
50
|
-
"architect": "architecture",
|
|
51
|
-
};
|
|
38
|
+
// ── Agent → Domain mapping (derived from AGENT_REGISTRY) ─────
|
|
39
|
+
|
|
40
|
+
export const AGENT_DOMAIN: Record<string, AgentDomain> = buildAgentDomainMap();
|
|
52
41
|
|
|
53
42
|
// ── Domain → Section relevance profiles ──────────────────────
|
|
54
43
|
//
|
|
@@ -113,7 +102,11 @@ const RELEVANCE_OFFSET: Record<Relevance, number> = {
|
|
|
113
102
|
|
|
114
103
|
export function getAgentDomain(agentName: string | null | undefined): AgentDomain | null {
|
|
115
104
|
if (!agentName) return null;
|
|
116
|
-
|
|
105
|
+
// Fast path: direct canonical lookup
|
|
106
|
+
if (AGENT_DOMAIN[agentName]) return AGENT_DOMAIN[agentName];
|
|
107
|
+
// Resolve alias via registry
|
|
108
|
+
const entry = resolveAgent(agentName);
|
|
109
|
+
return entry?.domain ?? null;
|
|
117
110
|
}
|
|
118
111
|
|
|
119
112
|
export function domainToScope(domain: AgentDomain | null): string {
|
package/context/index.ts
CHANGED
|
@@ -22,3 +22,6 @@ export {
|
|
|
22
22
|
} from "./sections";
|
|
23
23
|
export { findReferenceFiles } from "./references";
|
|
24
24
|
export type { ReferenceFile } from "./references";
|
|
25
|
+
export { AGENT_REGISTRY, resolveAgent, resolveAgentName, suggestAgent, buildAgentDomainMap, getAllAgentNames, getCanonicalAgentNames } from "./agent-registry";
|
|
26
|
+
export type { AgentEntry } from "./agent-registry";
|
|
27
|
+
export { loadAgentExpertise, getAgentDescription, findAgentsDir, clearExpertiseCache } from "./agent-expertise";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@codexa/cli",
|
|
3
|
-
"version": "9.0.
|
|
3
|
+
"version": "9.0.13",
|
|
4
4
|
"description": "Orchestrated workflow system for Claude Code - manages feature development through parallel subagents with structured phases, gates, and quality enforcement.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
{{agentIdentity}}
|
|
2
|
+
|
|
1
3
|
╔══════════════════════════════════════════════════════════════════════════════╗
|
|
2
4
|
║ DIRETIVA CRITICA: USE Write/Edit PARA CRIAR OS ARQUIVOS ║
|
|
3
5
|
║ NAO descreva. NAO planeje. NAO simule. EXECUTE AGORA. ║
|
|
4
6
|
║ Se retornar sem usar Write/Edit, a task FALHA. ║
|
|
5
7
|
╚══════════════════════════════════════════════════════════════════════════════╝
|
|
6
|
-
|
|
8
|
+
{{agentExpertise}}
|
|
7
9
|
ARQUIVOS QUE VOCE DEVE CRIAR (use Write para cada um):
|
|
8
10
|
{{filesList}}
|
|
9
11
|
|