@codexa/cli 9.0.29 → 9.0.31
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 +4 -7
- package/commands/knowledge.ts +1 -4
- package/commands/review.ts +3 -5
- package/commands/task.ts +37 -133
- package/commands/utils.ts +0 -1
- package/context/assembly.ts +3 -95
- package/context/generator.ts +14 -62
- package/context/index.ts +3 -6
- package/context/monitor.ts +26 -34
- package/context/sections.ts +8 -10
- package/db/schema.test.ts +21 -4
- package/db/schema.ts +10 -3
- package/package.json +1 -1
- package/simplify/prompt-builder.ts +9 -2
- package/templates/subagent-prompt-lean.md +5 -17
- package/workflow.ts +5 -7
- package/context/cache.ts +0 -85
- package/context/file-writer.ts +0 -58
package/commands/decide.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { getDb } from "../db/connection";
|
|
|
2
2
|
import { initSchema, getNextDecisionId } from "../db/schema";
|
|
3
3
|
import { resolveSpec } from "./spec-resolver";
|
|
4
4
|
import { CodexaError } from "../errors";
|
|
5
|
-
|
|
5
|
+
|
|
6
6
|
|
|
7
7
|
export interface ConflictAnalysis {
|
|
8
8
|
hasConflict: boolean;
|
|
@@ -204,9 +204,6 @@ export function decide(title: string, decision: string, options: { rationale?: s
|
|
|
204
204
|
}
|
|
205
205
|
}
|
|
206
206
|
|
|
207
|
-
// v10.0: Invalidate context cache (new decision affects context)
|
|
208
|
-
invalidateCache(spec.id);
|
|
209
|
-
|
|
210
207
|
console.log(`\nDecisao registrada: ${decisionId}`);
|
|
211
208
|
console.log(`Titulo: ${title}`);
|
|
212
209
|
console.log(`Decisao: ${decision}`);
|
|
@@ -341,10 +338,10 @@ export function supersedeDecision(
|
|
|
341
338
|
}
|
|
342
339
|
}
|
|
343
340
|
|
|
344
|
-
// Marcar antiga como superseded
|
|
341
|
+
// Marcar antiga como superseded
|
|
345
342
|
db.run(
|
|
346
|
-
"UPDATE decisions SET status = 'superseded'
|
|
347
|
-
[
|
|
343
|
+
"UPDATE decisions SET status = 'superseded' WHERE id = ?",
|
|
344
|
+
[oldDecisionId]
|
|
348
345
|
);
|
|
349
346
|
|
|
350
347
|
// Registrar como knowledge
|
package/commands/knowledge.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { getDb } from "../db/connection";
|
|
|
2
2
|
import { initSchema, getRelatedDecisions, getRelatedFiles } from "../db/schema";
|
|
3
3
|
import { resolveSpec } from "./spec-resolver";
|
|
4
4
|
import { CodexaError, ValidationError } from "../errors";
|
|
5
|
-
|
|
5
|
+
|
|
6
6
|
|
|
7
7
|
type KnowledgeCategory = "discovery" | "decision" | "blocker" | "pattern" | "constraint";
|
|
8
8
|
type KnowledgeSeverity = "info" | "warning" | "critical";
|
|
@@ -49,9 +49,6 @@ export function addKnowledge(options: AddKnowledgeOptions): void {
|
|
|
49
49
|
[spec.id, currentTask.id, options.category, options.content, severity, broadcastTo, now]
|
|
50
50
|
);
|
|
51
51
|
|
|
52
|
-
// v10.0: Invalidate context cache (new knowledge affects context)
|
|
53
|
-
invalidateCache(spec.id);
|
|
54
|
-
|
|
55
52
|
const severityIcon = {
|
|
56
53
|
info: "i",
|
|
57
54
|
warning: "!",
|
package/commands/review.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { initSchema, getArchitecturalAnalysisForSpec } from "../db/schema";
|
|
|
3
3
|
import { enforceGate } from "../gates/validator";
|
|
4
4
|
import { resolveSpec } from "./spec-resolver";
|
|
5
5
|
import { CodexaError, GateError } from "../errors";
|
|
6
|
-
|
|
6
|
+
|
|
7
7
|
import { simplifyAudit } from "./simplify";
|
|
8
8
|
|
|
9
9
|
// ═══════════════════════════════════════════════════════════════
|
|
@@ -326,8 +326,7 @@ export function reviewApprove(options?: { specId?: string; force?: boolean; forc
|
|
|
326
326
|
// Atualizar spec para completed
|
|
327
327
|
db.run("UPDATE specs SET phase = 'completed', updated_at = ? WHERE id = ?", [now, spec.id]);
|
|
328
328
|
|
|
329
|
-
|
|
330
|
-
cleanupContextFiles(spec.id);
|
|
329
|
+
|
|
331
330
|
|
|
332
331
|
// Buscar todos os dados para snapshot e relatorio
|
|
333
332
|
const tasks = db.query("SELECT * FROM tasks WHERE spec_id = ? ORDER BY number").all(spec.id) as any[];
|
|
@@ -395,8 +394,7 @@ export function reviewSkip(specId?: string): void {
|
|
|
395
394
|
// Atualizar spec para completed
|
|
396
395
|
db.run("UPDATE specs SET phase = 'completed', updated_at = ? WHERE id = ?", [now, spec.id]);
|
|
397
396
|
|
|
398
|
-
|
|
399
|
-
cleanupContextFiles(spec.id);
|
|
397
|
+
|
|
400
398
|
|
|
401
399
|
// Criar snapshot final
|
|
402
400
|
const review = db.query("SELECT * FROM review WHERE spec_id = ?").get(spec.id) as any;
|
package/commands/task.ts
CHANGED
|
@@ -11,12 +11,8 @@ import { resolveSpec, resolveSpecOrNull } from "./spec-resolver";
|
|
|
11
11
|
import { getAgentDomain, domainToScope } from "../context/domains";
|
|
12
12
|
import { resolveAgent } from "../context/agent-registry";
|
|
13
13
|
import { loadAgentExpertise, getAgentDescription } from "../context/agent-expertise";
|
|
14
|
-
import {
|
|
15
|
-
import { getFullContextFilePath } from "../context/file-writer";
|
|
16
|
-
import { getModelForTask } from "../context/model-profiles";
|
|
14
|
+
import { getModelForTask, getProfileForDomain } from "../context/model-profiles";
|
|
17
15
|
import { getContextBudget, formatContextWarning, estimateTokens } from "../context/monitor";
|
|
18
|
-
import { invalidateCache } from "../context/cache";
|
|
19
|
-
import { cleanupContextFiles } from "../context/file-writer";
|
|
20
16
|
import { isAutoSimplifyEnabled } from "./simplify";
|
|
21
17
|
import { buildSimplifyPrompt, writeSimplifyContext, type SimplifyFile } from "../simplify/prompt-builder";
|
|
22
18
|
|
|
@@ -154,7 +150,7 @@ function showStuckWarning(stuck: any[]): void {
|
|
|
154
150
|
console.log(` Use: task done <id> --force --force-reason "timeout" para liberar\n`);
|
|
155
151
|
}
|
|
156
152
|
|
|
157
|
-
export function taskStart(ids: string, json: boolean = false, minimalContext: boolean = false, specId?: string
|
|
153
|
+
export function taskStart(ids: string, json: boolean = false, minimalContext: boolean = false, specId?: string): void {
|
|
158
154
|
initSchema();
|
|
159
155
|
enforceGate("task-start");
|
|
160
156
|
|
|
@@ -235,76 +231,46 @@ export function taskStart(ids: string, json: boolean = false, minimalContext: bo
|
|
|
235
231
|
// v10.0: Model profile based on agent domain
|
|
236
232
|
const modelProfile = getModelForTask(agentDomain);
|
|
237
233
|
|
|
238
|
-
//
|
|
239
|
-
const
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
return `## KNOWLEDGE PENDENTE (OBRIGATORIO)\n\nVoce DEVE reconhecer estes items antes de completar a task:\n${critical.map((k: any) => `- [ID ${k.id}] ${k.content}`).join("\n")}\n\nUse: \`codexa knowledge ack <id>\` para cada item.`;
|
|
266
|
-
})()
|
|
267
|
-
: "";
|
|
268
|
-
|
|
269
|
-
// v10.0: Pre-built lean prompt for subagent (orchestrator just passes it)
|
|
270
|
-
const subagentPrompt = useFileContext
|
|
271
|
-
? loadTemplate("subagent-prompt-lean", {
|
|
272
|
-
taskName: task.name,
|
|
273
|
-
filesList: taskFiles.map((f: string) => ` - ${f}`).join('\n') || ' (nenhum arquivo especificado)',
|
|
274
|
-
taskDescription: task.checkpoint || task.name,
|
|
275
|
-
contextSummary: contextSummary!,
|
|
276
|
-
contextFile: contextFile || "",
|
|
277
|
-
fullContextFile,
|
|
278
|
-
pendingKnowledge: pendingKnowledgeForPrompt,
|
|
279
|
-
agentIdentity,
|
|
280
|
-
agentExpertise: agentExpertise
|
|
281
|
-
? `\n## EXPERTISE DO AGENTE\n\n${agentExpertise}\n`
|
|
282
|
-
: '',
|
|
283
|
-
})
|
|
284
|
-
: null;
|
|
285
|
-
|
|
286
|
-
// v10.0: Context budget estimation
|
|
234
|
+
// v11.0: Inline context delivery — full context in prompt, no files
|
|
235
|
+
const inlineContext = minimalContext
|
|
236
|
+
? getMinimalContextForSubagent(task.id)
|
|
237
|
+
: getContextForSubagent(task.id);
|
|
238
|
+
|
|
239
|
+
// Build pending knowledge section
|
|
240
|
+
const pendingKnowledgeForPrompt = (() => {
|
|
241
|
+
const unread = getUnreadKnowledgeForTask(spec.id, task.id);
|
|
242
|
+
const critical = unread.filter((k: any) => k.severity === 'critical' && k.task_origin !== task.id);
|
|
243
|
+
if (critical.length === 0) return "";
|
|
244
|
+
return `## KNOWLEDGE PENDENTE (OBRIGATORIO)\n\nVoce DEVE reconhecer estes items antes de completar a task:\n${critical.map((k: any) => `- [ID ${k.id}] ${k.content}`).join("\n")}\n\nUse: \`codexa knowledge ack <id>\` para cada item.`;
|
|
245
|
+
})();
|
|
246
|
+
|
|
247
|
+
// Pre-built prompt for subagent (orchestrator just passes it)
|
|
248
|
+
const subagentPrompt = loadTemplate("subagent-prompt-lean", {
|
|
249
|
+
taskName: task.name,
|
|
250
|
+
filesList: taskFiles.map((f: string) => ` - ${f}`).join('\n') || ' (nenhum arquivo especificado)',
|
|
251
|
+
taskDescription: task.checkpoint || task.name,
|
|
252
|
+
inlineContext,
|
|
253
|
+
pendingKnowledge: pendingKnowledgeForPrompt,
|
|
254
|
+
agentIdentity,
|
|
255
|
+
agentExpertise: agentExpertise
|
|
256
|
+
? `\n## EXPERTISE DO AGENTE\n\n${agentExpertise}\n`
|
|
257
|
+
: '',
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
// Context budget estimation
|
|
287
261
|
const completedTaskCount = (db.query(
|
|
288
262
|
"SELECT COUNT(*) as c FROM tasks WHERE spec_id = ? AND status = 'done'"
|
|
289
263
|
).get(spec.id) as any)?.c || 0;
|
|
290
264
|
|
|
291
|
-
const
|
|
292
|
-
const
|
|
293
|
-
? (Bun.file(contextFile).size > 0 ? String(Bun.file(contextFile).size) : "")
|
|
294
|
-
: "";
|
|
295
|
-
const contextBudget = getContextBudget(
|
|
296
|
-
promptText,
|
|
297
|
-
contextFileContent,
|
|
298
|
-
completedTaskCount,
|
|
299
|
-
contextFile || undefined,
|
|
300
|
-
);
|
|
265
|
+
const agentProfile = getProfileForDomain(agentDomain);
|
|
266
|
+
const contextBudget = getContextBudget(subagentPrompt, completedTaskCount, agentProfile);
|
|
301
267
|
|
|
302
268
|
const contextWarning = formatContextWarning(contextBudget);
|
|
303
269
|
if (contextWarning) {
|
|
304
270
|
console.error(contextWarning);
|
|
305
271
|
}
|
|
306
272
|
|
|
307
|
-
//
|
|
273
|
+
// Build output
|
|
308
274
|
const output: any = {
|
|
309
275
|
taskId: task.id,
|
|
310
276
|
number: task.number,
|
|
@@ -314,76 +280,18 @@ export function taskStart(ids: string, json: boolean = false, minimalContext: bo
|
|
|
314
280
|
files: taskFiles,
|
|
315
281
|
modelProfile,
|
|
316
282
|
contextBudget,
|
|
283
|
+
contextMode: minimalContext ? "minimal" : "inline",
|
|
284
|
+
subagentPrompt,
|
|
317
285
|
_orchestratorWarning: "NAO execute esta task diretamente. Use Task tool para delegar. Use o campo 'subagentPrompt' como prompt.",
|
|
318
286
|
};
|
|
319
287
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
output.fullContextFile = fullContextFile;
|
|
324
|
-
output.contextSummary = contextSummary!;
|
|
325
|
-
output.contextMode = "file";
|
|
326
|
-
output.subagentPrompt = subagentPrompt;
|
|
327
|
-
|
|
328
|
-
// v10.2: Fix — unreadKnowledge must be available in ALL modes
|
|
329
|
-
// Without this, file-mode subagents can't acknowledge critical knowledge,
|
|
330
|
-
// causing taskDone() to block with no way for the subagent to comply
|
|
331
|
-
const unreadKnowledge = getUnreadKnowledgeForTask(spec.id, task.id);
|
|
332
|
-
if (unreadKnowledge.length > 0) {
|
|
333
|
-
output.unreadKnowledge = unreadKnowledge.map((k: any) => ({
|
|
334
|
-
id: k.id, category: k.category, content: k.content,
|
|
335
|
-
severity: k.severity, origin_task: k.task_origin,
|
|
336
|
-
}));
|
|
337
|
-
}
|
|
338
|
-
} else if (inlineContext) {
|
|
339
|
-
// Backward compat: full inline
|
|
340
|
-
output.context = contextText;
|
|
341
|
-
output.contextMode = "inline";
|
|
342
|
-
|
|
343
|
-
const unreadKnowledge = getUnreadKnowledgeForTask(spec.id, task.id);
|
|
288
|
+
// Unread knowledge for orchestrator awareness
|
|
289
|
+
const unreadKnowledge = getUnreadKnowledgeForTask(spec.id, task.id);
|
|
290
|
+
if (unreadKnowledge.length > 0) {
|
|
344
291
|
output.unreadKnowledge = unreadKnowledge.map((k: any) => ({
|
|
345
292
|
id: k.id, category: k.category, content: k.content,
|
|
346
293
|
severity: k.severity, origin_task: k.task_origin,
|
|
347
294
|
}));
|
|
348
|
-
|
|
349
|
-
// Patterns (only in inline mode — in file mode they're in the context file)
|
|
350
|
-
let relevantPatterns: any[] = [];
|
|
351
|
-
if (taskFiles.length > 0) {
|
|
352
|
-
relevantPatterns.push(...getPatternsForFiles(taskFiles));
|
|
353
|
-
}
|
|
354
|
-
if (relevantPatterns.length === 0) {
|
|
355
|
-
relevantPatterns.push(...getPatternsByScope(agentScope));
|
|
356
|
-
}
|
|
357
|
-
const patternNames = new Set<string>();
|
|
358
|
-
output.implementationPatterns = relevantPatterns
|
|
359
|
-
.filter(p => {
|
|
360
|
-
if (patternNames.has(p.name)) return false;
|
|
361
|
-
patternNames.add(p.name);
|
|
362
|
-
return true;
|
|
363
|
-
})
|
|
364
|
-
.map(p => ({
|
|
365
|
-
name: p.name, category: p.category, applies_to: p.applies_to,
|
|
366
|
-
template: p.template,
|
|
367
|
-
structure: JSON.parse(p.structure || "{}"),
|
|
368
|
-
examples: JSON.parse(p.examples || "[]").slice(0, 3),
|
|
369
|
-
anti_patterns: JSON.parse(p.anti_patterns || "[]"),
|
|
370
|
-
confidence: p.confidence,
|
|
371
|
-
}));
|
|
372
|
-
|
|
373
|
-
output.subagentContext = loadTemplate("subagent-context", {
|
|
374
|
-
filesList: taskFiles.map((f: string) => ` - ${f}`).join('\n') || ' (nenhum arquivo especificado)',
|
|
375
|
-
agentIdentity,
|
|
376
|
-
agentExpertise: agentExpertise ? `\n## EXPERTISE DO AGENTE\n\n${agentExpertise}\n\n` : '',
|
|
377
|
-
});
|
|
378
|
-
output.subagentReturnProtocol = loadTemplate("subagent-return-protocol", {
|
|
379
|
-
patternsNote: output.implementationPatterns.length > 0
|
|
380
|
-
? `\nPATTERNS: ${output.implementationPatterns.length} patterns extraidos do projeto.\n`
|
|
381
|
-
: '',
|
|
382
|
-
});
|
|
383
|
-
} else {
|
|
384
|
-
// --minimal-context
|
|
385
|
-
output.contextSummary = contextSummary!;
|
|
386
|
-
output.contextMode = "minimal";
|
|
387
295
|
}
|
|
388
296
|
|
|
389
297
|
return output;
|
|
@@ -488,9 +396,6 @@ export function taskDone(id: string, options: { checkpoint: string; files?: stri
|
|
|
488
396
|
const processResult = processSubagentReturn(spec.id, taskId, task.number, subagentData);
|
|
489
397
|
console.log(formatProcessResult(processResult));
|
|
490
398
|
|
|
491
|
-
// v10.0: Invalidate context cache (knowledge/decisions changed)
|
|
492
|
-
invalidateCache(spec.id);
|
|
493
|
-
|
|
494
399
|
// v8.3: BLOCKING check para knowledge critico nao reconhecido (substitui warning v8.2)
|
|
495
400
|
const unackedCritical = getUnreadKnowledgeForTask(spec.id, taskId)
|
|
496
401
|
.filter((k: any) => k.severity === 'critical' && k.task_origin !== taskId);
|
|
@@ -832,7 +737,6 @@ export function taskPhaseAdvance(options: { noCompact?: boolean; json?: boolean;
|
|
|
832
737
|
if (reasoningResult.archived > 0) {
|
|
833
738
|
console.log(` Reasoning compactado: ${reasoningResult.archived} entradas removidas, ${reasoningResult.kept} mantidas`);
|
|
834
739
|
}
|
|
835
|
-
cleanupContextFiles(spec.id);
|
|
836
740
|
}
|
|
837
741
|
|
|
838
742
|
// Create phase summary as critical knowledge
|
package/commands/utils.ts
CHANGED
|
@@ -9,7 +9,6 @@ import pkg from "../package.json";
|
|
|
9
9
|
|
|
10
10
|
// Re-export context modules for backward compatibility
|
|
11
11
|
export { getContextForSubagent, getMinimalContextForSubagent } from "../context/generator";
|
|
12
|
-
export { MAX_CONTEXT_SIZE } from "../context/assembly";
|
|
13
12
|
export { AGENT_DOMAIN, getAgentDomain, domainToScope } from "../context/domains";
|
|
14
13
|
export { resolveAgent, resolveAgentName, suggestAgent, getCanonicalAgentNames } from "../context/agent-registry";
|
|
15
14
|
export { loadAgentExpertise, getAgentDescription } from "../context/agent-expertise";
|
package/context/assembly.ts
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
//
|
|
2
|
-
export const MAX_CONTEXT_SIZE = 16384;
|
|
1
|
+
// v11.0: Context assembly — no truncation, full inline delivery
|
|
3
2
|
|
|
4
3
|
export interface ContextSection {
|
|
5
4
|
name: string;
|
|
6
5
|
content: string;
|
|
7
|
-
priority: number; // Lower =
|
|
6
|
+
priority: number; // Lower = higher priority (ordering only, no truncation)
|
|
8
7
|
}
|
|
9
8
|
|
|
10
9
|
export interface ContextData {
|
|
@@ -29,7 +28,6 @@ export interface ContextData {
|
|
|
29
28
|
libContexts: any[];
|
|
30
29
|
graphDecisions: any[];
|
|
31
30
|
discoveredPatterns: any[];
|
|
32
|
-
fullMode?: boolean;
|
|
33
31
|
}
|
|
34
32
|
|
|
35
33
|
const RETURN_PROTOCOL = `
|
|
@@ -39,10 +37,7 @@ const RETURN_PROTOCOL = `
|
|
|
39
37
|
\`\`\`
|
|
40
38
|
`;
|
|
41
39
|
|
|
42
|
-
export
|
|
43
|
-
|
|
44
|
-
export function assembleSections(header: string, sections: ContextSection[], mode: AssemblyMode = "lean"): string {
|
|
45
|
-
// Sort by priority (lower = higher priority, kept during truncation)
|
|
40
|
+
export function assembleSections(header: string, sections: ContextSection[]): string {
|
|
46
41
|
const sorted = [...sections].sort((a, b) => a.priority - b.priority);
|
|
47
42
|
|
|
48
43
|
let output = header;
|
|
@@ -51,92 +46,5 @@ export function assembleSections(header: string, sections: ContextSection[], mod
|
|
|
51
46
|
}
|
|
52
47
|
|
|
53
48
|
output += RETURN_PROTOCOL;
|
|
54
|
-
|
|
55
|
-
// v10.2: Full mode — no truncation, no caps. Used for the full reference file.
|
|
56
|
-
if (mode === "full") {
|
|
57
|
-
return output;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
// v10.0: Progressive truncation — halve sections before dropping them entirely
|
|
61
|
-
if (output.length > MAX_CONTEXT_SIZE) {
|
|
62
|
-
return truncateWithBudget(header, sorted, MAX_CONTEXT_SIZE);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
49
|
return output;
|
|
66
50
|
}
|
|
67
|
-
|
|
68
|
-
function truncateWithBudget(header: string, sections: ContextSection[], maxSize: number): string {
|
|
69
|
-
const reservedSize = header.length + RETURN_PROTOCOL.length + 120;
|
|
70
|
-
const budgetForSections = maxSize - reservedSize;
|
|
71
|
-
|
|
72
|
-
// Mutable map of section content
|
|
73
|
-
const sectionContent = new Map<string, string>();
|
|
74
|
-
let totalContentSize = 0;
|
|
75
|
-
for (const s of sections) {
|
|
76
|
-
sectionContent.set(s.name, s.content);
|
|
77
|
-
totalContentSize += s.content.length;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
if (totalContentSize <= budgetForSections) {
|
|
81
|
-
let result = header;
|
|
82
|
-
for (const s of sections) result += s.content;
|
|
83
|
-
result += RETURN_PROTOCOL;
|
|
84
|
-
return result;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
// Phase 1: Halve content of lowest-priority sections iteratively
|
|
88
|
-
// Sort descending (highest priority number = least important = truncated first)
|
|
89
|
-
const byLowestPriority = [...sections].sort((a, b) => b.priority - a.priority);
|
|
90
|
-
|
|
91
|
-
for (const s of byLowestPriority) {
|
|
92
|
-
if (totalContentSize <= budgetForSections) break;
|
|
93
|
-
|
|
94
|
-
const current = sectionContent.get(s.name);
|
|
95
|
-
if (!current) continue;
|
|
96
|
-
|
|
97
|
-
const halfSize = Math.floor(current.length / 2);
|
|
98
|
-
|
|
99
|
-
if (halfSize < 80) {
|
|
100
|
-
// Too small to truncate — drop entirely
|
|
101
|
-
totalContentSize -= current.length;
|
|
102
|
-
sectionContent.delete(s.name);
|
|
103
|
-
} else {
|
|
104
|
-
const truncated = current.substring(0, halfSize) + "\n[... secao truncada]\n";
|
|
105
|
-
totalContentSize -= (current.length - truncated.length);
|
|
106
|
-
sectionContent.set(s.name, truncated);
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
// Phase 2: If still over budget, drop entire sections (fallback)
|
|
111
|
-
if (totalContentSize > budgetForSections) {
|
|
112
|
-
for (const s of byLowestPriority) {
|
|
113
|
-
if (totalContentSize <= budgetForSections) break;
|
|
114
|
-
const content = sectionContent.get(s.name);
|
|
115
|
-
if (content) {
|
|
116
|
-
totalContentSize -= content.length;
|
|
117
|
-
sectionContent.delete(s.name);
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
// Reassemble in original priority order
|
|
123
|
-
let result = header;
|
|
124
|
-
const omitted: string[] = [];
|
|
125
|
-
|
|
126
|
-
for (const s of sections) {
|
|
127
|
-
const content = sectionContent.get(s.name);
|
|
128
|
-
if (content) {
|
|
129
|
-
result += content;
|
|
130
|
-
} else {
|
|
131
|
-
omitted.push(s.name);
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
result += RETURN_PROTOCOL;
|
|
136
|
-
|
|
137
|
-
if (omitted.length > 0) {
|
|
138
|
-
result += `\n[CONTEXTO TRUNCADO: ${omitted.length} secao(oes) omitida(s) (${omitted.join(', ')}). Use: codexa context detail <secao>]`;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
return result;
|
|
142
|
-
}
|
package/context/generator.ts
CHANGED
|
@@ -2,11 +2,9 @@ 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
6
|
import { getAgentDomain, adjustSectionPriorities, domainToScope } from "./domains";
|
|
7
7
|
import { filterRelevantDecisions, filterRelevantStandards } from "./scoring";
|
|
8
|
-
import { computeContextHash, getCachedContextPath, setCachedContext } from "./cache";
|
|
9
|
-
import { writeContextFile, writeFullContextFile } from "./file-writer";
|
|
10
8
|
import {
|
|
11
9
|
buildProductSection,
|
|
12
10
|
buildArchitectureSection,
|
|
@@ -111,10 +109,10 @@ export function getMinimalContextForSubagent(taskId: number): string {
|
|
|
111
109
|
}
|
|
112
110
|
|
|
113
111
|
// ═══════════════════════════════════════════════════════════════
|
|
114
|
-
// CONTEXT BUILDER (
|
|
112
|
+
// CONTEXT BUILDER (v11.0 — inline delivery, no file system)
|
|
115
113
|
// ═══════════════════════════════════════════════════════════════
|
|
116
114
|
|
|
117
|
-
function fetchContextData(taskId: number
|
|
115
|
+
function fetchContextData(taskId: number): ContextData | null {
|
|
118
116
|
const db = getDb();
|
|
119
117
|
|
|
120
118
|
const task = db.query("SELECT * FROM tasks WHERE id = ?").get(taskId) as any;
|
|
@@ -131,12 +129,11 @@ function fetchContextData(taskId: number, fullMode: boolean = false): ContextDat
|
|
|
131
129
|
const taskFiles = task.files ? JSON.parse(task.files) : [];
|
|
132
130
|
const domain = domainToScope(getAgentDomain(task.agent));
|
|
133
131
|
|
|
134
|
-
//
|
|
135
|
-
const decisionLimit = fullMode ? 200 : 30;
|
|
132
|
+
// v11.0: Raised caps — 50 decisions (was 8), filtered by relevance
|
|
136
133
|
const allDecisions = db
|
|
137
|
-
.query(`SELECT * FROM decisions WHERE spec_id = ? AND status = 'active' ORDER BY created_at DESC LIMIT
|
|
138
|
-
.all(task.spec_id
|
|
139
|
-
const decisions =
|
|
134
|
+
.query(`SELECT * FROM decisions WHERE spec_id = ? AND status = 'active' ORDER BY created_at DESC LIMIT 100`)
|
|
135
|
+
.all(task.spec_id) as any[];
|
|
136
|
+
const decisions = filterRelevantDecisions(allDecisions, taskFiles, 50);
|
|
140
137
|
|
|
141
138
|
// Standards required + recommended que se aplicam aos arquivos
|
|
142
139
|
const standards = db
|
|
@@ -148,13 +145,13 @@ function fetchContextData(taskId: number, fullMode: boolean = false): ContextDat
|
|
|
148
145
|
.all(domain) as any[];
|
|
149
146
|
const relevantStandards = filterRelevantStandards(standards, taskFiles);
|
|
150
147
|
|
|
151
|
-
//
|
|
148
|
+
// v11.0: Raised caps — 50 critical (was 20), 30 info (was 10)
|
|
152
149
|
const allKnowledge = getKnowledgeForTask(task.spec_id, taskId);
|
|
153
150
|
const allCriticalKnowledge = allKnowledge.filter((k: any) => k.severity === 'critical' || k.severity === 'warning');
|
|
154
|
-
const criticalKnowledge =
|
|
151
|
+
const criticalKnowledge = allCriticalKnowledge.slice(0, 50);
|
|
155
152
|
const truncatedCritical = allCriticalKnowledge.length - criticalKnowledge.length;
|
|
156
153
|
const allInfoKnowledge = allKnowledge.filter((k: any) => k.severity === 'info');
|
|
157
|
-
const infoKnowledge =
|
|
154
|
+
const infoKnowledge = allInfoKnowledge.slice(0, 30);
|
|
158
155
|
const truncatedInfo = allInfoKnowledge.length - infoKnowledge.length;
|
|
159
156
|
|
|
160
157
|
const productContext = db.query("SELECT * FROM product_context WHERE id = 'default'").get() as any;
|
|
@@ -240,14 +237,10 @@ function fetchContextData(taskId: number, fullMode: boolean = false): ContextDat
|
|
|
240
237
|
|
|
241
238
|
// ── Main Entry Point ──────────────────────────────────────────
|
|
242
239
|
|
|
243
|
-
function buildContext(taskId: number
|
|
244
|
-
const
|
|
245
|
-
const data = fetchContextData(taskId, isFullMode);
|
|
240
|
+
function buildContext(taskId: number): { content: string; data: ContextData } | null {
|
|
241
|
+
const data = fetchContextData(taskId);
|
|
246
242
|
if (!data) return null;
|
|
247
243
|
|
|
248
|
-
// Pass fullMode flag so section builders remove caps
|
|
249
|
-
data.fullMode = isFullMode;
|
|
250
|
-
|
|
251
244
|
const header = `## CONTEXTO (Task #${data.task.number})
|
|
252
245
|
|
|
253
246
|
**Feature:** ${data.spec.name}
|
|
@@ -274,52 +267,11 @@ function buildContext(taskId: number, mode: AssemblyMode = "lean"): { content: s
|
|
|
274
267
|
const agentDomain = getAgentDomain(data.task.agent);
|
|
275
268
|
const sections = adjustSectionPriorities(allSections, agentDomain);
|
|
276
269
|
|
|
277
|
-
return { content: assembleSections(header, sections
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
// Backward compat wrapper
|
|
281
|
-
function buildFullContext(taskId: number): { content: string; data: ContextData } | null {
|
|
282
|
-
return buildContext(taskId, "lean");
|
|
270
|
+
return { content: assembleSections(header, sections), data };
|
|
283
271
|
}
|
|
284
272
|
|
|
285
273
|
export function getContextForSubagent(taskId: number): string {
|
|
286
274
|
initSchema();
|
|
287
|
-
const result =
|
|
275
|
+
const result = buildContext(taskId);
|
|
288
276
|
return result?.content || "ERRO: Task nao encontrada";
|
|
289
277
|
}
|
|
290
|
-
|
|
291
|
-
// ── v10.0: File-Based Context ─────────────────────────────────
|
|
292
|
-
|
|
293
|
-
export interface GeneratedContextFiles {
|
|
294
|
-
leanPath: string;
|
|
295
|
-
fullPath: string;
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
export function generateContextFile(taskId: number): string {
|
|
299
|
-
initSchema();
|
|
300
|
-
|
|
301
|
-
const db = getDb();
|
|
302
|
-
const task = db.query("SELECT spec_id FROM tasks WHERE id = ?").get(taskId) as any;
|
|
303
|
-
if (!task) return "";
|
|
304
|
-
|
|
305
|
-
// Check cache first
|
|
306
|
-
const hash = computeContextHash(task.spec_id, taskId);
|
|
307
|
-
const cached = getCachedContextPath(hash);
|
|
308
|
-
if (cached) return cached;
|
|
309
|
-
|
|
310
|
-
// Generate lean context (16KB max, truncated)
|
|
311
|
-
const leanResult = buildFullContext(taskId);
|
|
312
|
-
if (!leanResult) return "";
|
|
313
|
-
|
|
314
|
-
// v10.2: Generate full context (no truncation, no caps)
|
|
315
|
-
const fullResult = buildContext(taskId, "full");
|
|
316
|
-
if (fullResult) {
|
|
317
|
-
writeFullContextFile(taskId, fullResult.content);
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
// Write lean file and cache
|
|
321
|
-
const filePath = writeContextFile(taskId, leanResult.content);
|
|
322
|
-
setCachedContext(hash, filePath, task.spec_id);
|
|
323
|
-
|
|
324
|
-
return filePath;
|
|
325
|
-
}
|
package/context/index.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Re-exports for backward compatibility
|
|
2
|
-
export { getContextForSubagent, getMinimalContextForSubagent
|
|
3
|
-
export { assembleSections
|
|
2
|
+
export { getContextForSubagent, getMinimalContextForSubagent } from "./generator";
|
|
3
|
+
export { assembleSections } from "./assembly";
|
|
4
4
|
export { AGENT_DOMAIN, DOMAIN_PROFILES, getAgentDomain, adjustSectionPriorities, domainToScope } from "./domains";
|
|
5
5
|
export type { AgentDomain, Relevance, SectionName, DomainProfile } from "./domains";
|
|
6
6
|
export type { ContextSection, ContextData } from "./assembly";
|
|
@@ -25,10 +25,7 @@ export type { ReferenceFile } from "./references";
|
|
|
25
25
|
export { AGENT_REGISTRY, resolveAgent, resolveAgentName, suggestAgent, buildAgentDomainMap, getAllAgentNames, getCanonicalAgentNames } from "./agent-registry";
|
|
26
26
|
export type { AgentEntry } from "./agent-registry";
|
|
27
27
|
export { loadAgentExpertise, getAgentDescription, findAgentsDir, clearExpertiseCache } from "./agent-expertise";
|
|
28
|
-
// v10.0: Context engineering
|
|
29
|
-
export { computeContextHash, getCachedContextPath, setCachedContext, invalidateCache, clearCache } from "./cache";
|
|
30
|
-
export { writeContextFile, ensureContextDir, getContextFilePath, cleanupContextFiles } from "./file-writer";
|
|
31
28
|
export { getModelForTask, getProfileForDomain, DOMAIN_MODEL_MAP, MODEL_NAMES } from "./model-profiles";
|
|
32
29
|
export type { ModelProfile } from "./model-profiles";
|
|
33
|
-
export { estimateTokens, getContextBudget, formatContextWarning } from "./monitor";
|
|
30
|
+
export { estimateTokens, getContextBudget, formatContextWarning, getMaxUsableTokens } from "./monitor";
|
|
34
31
|
export type { ContextBudget, WarningLevel, ContentType } from "./monitor";
|
package/context/monitor.ts
CHANGED
|
@@ -1,33 +1,42 @@
|
|
|
1
1
|
// ═══════════════════════════════════════════════════════════════
|
|
2
|
-
// CONTEXT MONITOR (
|
|
2
|
+
// CONTEXT MONITOR (v11.0)
|
|
3
3
|
// Estimates token usage and warns when context budget is high.
|
|
4
|
-
//
|
|
4
|
+
// Model-aware: opus (1M), sonnet/haiku (200K)
|
|
5
5
|
// ═══════════════════════════════════════════════════════════════
|
|
6
6
|
|
|
7
|
+
import type { ModelProfile } from "./model-profiles";
|
|
8
|
+
|
|
7
9
|
export type WarningLevel = "ok" | "caution" | "critical";
|
|
8
10
|
export type ContentType = "code" | "markdown" | "mixed";
|
|
9
11
|
|
|
10
12
|
export interface ContextBudget {
|
|
11
13
|
promptTokens: number;
|
|
12
|
-
|
|
14
|
+
contextTokens: number;
|
|
13
15
|
orchestratorEstimate: number;
|
|
14
16
|
totalEstimated: number;
|
|
17
|
+
maxUsableTokens: number;
|
|
15
18
|
warningLevel: WarningLevel;
|
|
16
19
|
}
|
|
17
20
|
|
|
18
21
|
const CAUTION_THRESHOLD = 0.65;
|
|
19
22
|
const CRITICAL_THRESHOLD = 0.75;
|
|
20
|
-
|
|
21
|
-
|
|
23
|
+
|
|
24
|
+
// v11.0: Model-aware token limits (80% of actual window)
|
|
25
|
+
const MODEL_TOKEN_LIMITS: Record<ModelProfile, number> = {
|
|
26
|
+
quality: 800_000, // opus ~1M window
|
|
27
|
+
balanced: 160_000, // sonnet ~200K window
|
|
28
|
+
budget: 160_000, // haiku ~200K window
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export function getMaxUsableTokens(modelProfile?: ModelProfile): number {
|
|
32
|
+
return MODEL_TOKEN_LIMITS[modelProfile || "balanced"];
|
|
33
|
+
}
|
|
22
34
|
|
|
23
35
|
function detectContentType(text: string): ContentType {
|
|
24
36
|
if (text.length === 0) return "mixed";
|
|
25
37
|
|
|
26
|
-
// Count code indicators (braces, semicolons, operators)
|
|
27
38
|
const codeIndicators = (text.match(/[{}\[\];=()]/g) || []).length;
|
|
28
|
-
// Count markdown indicators (headers, lists, bold)
|
|
29
39
|
const markdownIndicators = (text.match(/^#{1,6}\s|^\s*[-*]\s|\*\*/gm) || []).length;
|
|
30
|
-
|
|
31
40
|
const codeRatio = codeIndicators / text.length;
|
|
32
41
|
|
|
33
42
|
if (codeRatio > 0.03) return "code";
|
|
@@ -40,43 +49,25 @@ export function estimateTokens(text: string, contentType?: ContentType): number
|
|
|
40
49
|
|
|
41
50
|
switch (type) {
|
|
42
51
|
case "code":
|
|
43
|
-
// Code has more tokens per character (operators, short identifiers)
|
|
44
52
|
return Math.ceil(text.length / 3);
|
|
45
53
|
case "markdown":
|
|
46
|
-
// Portuguese markdown has longer words, fewer tokens per character
|
|
47
54
|
return Math.ceil(text.length / 5);
|
|
48
55
|
default:
|
|
49
|
-
// Mixed content — conservative estimate
|
|
50
56
|
return Math.ceil(text.length / 3.5);
|
|
51
57
|
}
|
|
52
58
|
}
|
|
53
59
|
|
|
54
60
|
export function getContextBudget(
|
|
55
61
|
promptText: string,
|
|
56
|
-
contextFileText: string,
|
|
57
62
|
taskCount: number,
|
|
58
|
-
|
|
63
|
+
modelProfile?: ModelProfile,
|
|
59
64
|
): ContextBudget {
|
|
60
65
|
const promptTokens = estimateTokens(promptText);
|
|
61
|
-
|
|
62
|
-
// Use real file size if path provided
|
|
63
|
-
let contextFileTokens: number;
|
|
64
|
-
if (contextFilePath) {
|
|
65
|
-
try {
|
|
66
|
-
const realSize = Bun.file(contextFilePath).size;
|
|
67
|
-
contextFileTokens = Math.ceil(realSize / 3.5);
|
|
68
|
-
} catch {
|
|
69
|
-
contextFileTokens = estimateTokens(contextFileText);
|
|
70
|
-
}
|
|
71
|
-
} else {
|
|
72
|
-
contextFileTokens = estimateTokens(contextFileText);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// Orchestrator overhead: base + per-task accumulation (conservative)
|
|
66
|
+
const contextTokens = 0; // v11.0: context is inline in the prompt now
|
|
76
67
|
const orchestratorEstimate = 2000 + (taskCount * 1200);
|
|
77
|
-
|
|
78
|
-
const
|
|
79
|
-
const ratio = totalEstimated /
|
|
68
|
+
const totalEstimated = promptTokens + orchestratorEstimate;
|
|
69
|
+
const maxUsableTokens = getMaxUsableTokens(modelProfile);
|
|
70
|
+
const ratio = totalEstimated / maxUsableTokens;
|
|
80
71
|
|
|
81
72
|
let warningLevel: WarningLevel = "ok";
|
|
82
73
|
if (ratio >= CRITICAL_THRESHOLD) {
|
|
@@ -87,9 +78,10 @@ export function getContextBudget(
|
|
|
87
78
|
|
|
88
79
|
return {
|
|
89
80
|
promptTokens,
|
|
90
|
-
|
|
81
|
+
contextTokens,
|
|
91
82
|
orchestratorEstimate,
|
|
92
83
|
totalEstimated,
|
|
84
|
+
maxUsableTokens,
|
|
93
85
|
warningLevel,
|
|
94
86
|
};
|
|
95
87
|
}
|
|
@@ -97,10 +89,10 @@ export function getContextBudget(
|
|
|
97
89
|
export function formatContextWarning(budget: ContextBudget): string | null {
|
|
98
90
|
if (budget.warningLevel === "ok") return null;
|
|
99
91
|
|
|
100
|
-
const pct = Math.round((budget.totalEstimated /
|
|
92
|
+
const pct = Math.round((budget.totalEstimated / budget.maxUsableTokens) * 100);
|
|
101
93
|
|
|
102
94
|
if (budget.warningLevel === "critical") {
|
|
103
|
-
return `[CRITICAL] Contexto estimado em ${pct}% do limite. Considere: --minimal-context ou knowledge compact`;
|
|
95
|
+
return `[CRITICAL] Contexto estimado em ${pct}% do limite (${budget.maxUsableTokens} tokens). Considere: --minimal-context ou knowledge compact`;
|
|
104
96
|
}
|
|
105
97
|
return `[CAUTION] Contexto estimado em ${pct}% do limite. Monitore o uso.`;
|
|
106
98
|
}
|
package/context/sections.ts
CHANGED
|
@@ -114,8 +114,8 @@ export function buildStandardsSection(data: ContextData): ContextSection {
|
|
|
114
114
|
}
|
|
115
115
|
|
|
116
116
|
export function buildDecisionsSection(data: ContextData): ContextSection {
|
|
117
|
-
//
|
|
118
|
-
const decisionsToShow = data.
|
|
117
|
+
// v11.0: Show all decisions (raised cap to 50 in generator)
|
|
118
|
+
const decisionsToShow = data.allDecisions;
|
|
119
119
|
const truncatedDecisions = data.allDecisions.length - decisionsToShow.length;
|
|
120
120
|
const content = `
|
|
121
121
|
### DECISOES (${decisionsToShow.length}${truncatedDecisions > 0 ? ` [+${truncatedDecisions} mais - use: decisions list]` : ''})
|
|
@@ -170,12 +170,10 @@ ${data.patterns.map((p: any) => {
|
|
|
170
170
|
}
|
|
171
171
|
|
|
172
172
|
if (data.discoveredPatterns.length > 0) {
|
|
173
|
-
//
|
|
174
|
-
const patternsToShow = data.fullMode ? data.discoveredPatterns : data.discoveredPatterns.slice(-10);
|
|
175
|
-
const truncated = data.discoveredPatterns.length - patternsToShow.length;
|
|
173
|
+
// v11.0: Show all discovered patterns (no cap)
|
|
176
174
|
content += `
|
|
177
175
|
### PATTERNS DESCOBERTOS (${data.discoveredPatterns.length})
|
|
178
|
-
${
|
|
176
|
+
${data.discoveredPatterns.map((p: any) => `- ${p.pattern}${p.source_task ? ` (Task #${p.source_task})` : ""}`).join("\n")}
|
|
179
177
|
`;
|
|
180
178
|
}
|
|
181
179
|
|
|
@@ -191,8 +189,8 @@ export function buildUtilitiesSection(data: ContextData): ContextSection | null
|
|
|
191
189
|
}).filter(Boolean))];
|
|
192
190
|
|
|
193
191
|
const agentScope = domainToScope(getAgentDomain(data.task.agent)) || undefined;
|
|
194
|
-
//
|
|
195
|
-
const utilLimit =
|
|
192
|
+
// v11.0: Raised cap to 50 (was 15)
|
|
193
|
+
const utilLimit = 50;
|
|
196
194
|
let relevantUtilities = getUtilitiesForContext(taskDirs, undefined, utilLimit);
|
|
197
195
|
|
|
198
196
|
if (relevantUtilities.length < 5 && agentScope) {
|
|
@@ -243,8 +241,8 @@ export function buildStackSection(data: ContextData): ContextSection | null {
|
|
|
243
241
|
if (data.project) {
|
|
244
242
|
const stack = JSON.parse(data.project.stack);
|
|
245
243
|
const allStackEntries = Object.entries(stack);
|
|
246
|
-
//
|
|
247
|
-
const mainStack =
|
|
244
|
+
// v11.0: Show all stack entries (no cap)
|
|
245
|
+
const mainStack = allStackEntries;
|
|
248
246
|
const truncatedStack = allStackEntries.length - mainStack.length;
|
|
249
247
|
content += `
|
|
250
248
|
### STACK
|
package/db/schema.test.ts
CHANGED
|
@@ -494,7 +494,6 @@ describe("Migration System", () => {
|
|
|
494
494
|
content TEXT NOT NULL,
|
|
495
495
|
severity TEXT DEFAULT 'info',
|
|
496
496
|
broadcast_to TEXT DEFAULT 'all',
|
|
497
|
-
acknowledged_by TEXT,
|
|
498
497
|
created_at TEXT DEFAULT CURRENT_TIMESTAMP
|
|
499
498
|
)
|
|
500
499
|
`);
|
|
@@ -569,7 +568,27 @@ describe("Migration System", () => {
|
|
|
569
568
|
});
|
|
570
569
|
|
|
571
570
|
it("should migrate data from JSON acknowledged_by", () => {
|
|
572
|
-
|
|
571
|
+
// Recreate old schema WITH acknowledged_by column to test migration from legacy format
|
|
572
|
+
createBaseTables(db);
|
|
573
|
+
db.exec(`
|
|
574
|
+
CREATE TABLE IF NOT EXISTS knowledge (
|
|
575
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
576
|
+
spec_id TEXT NOT NULL,
|
|
577
|
+
task_origin INTEGER NOT NULL,
|
|
578
|
+
category TEXT NOT NULL,
|
|
579
|
+
content TEXT NOT NULL,
|
|
580
|
+
severity TEXT DEFAULT 'info',
|
|
581
|
+
broadcast_to TEXT DEFAULT 'all',
|
|
582
|
+
acknowledged_by TEXT,
|
|
583
|
+
created_at TEXT DEFAULT CURRENT_TIMESTAMP
|
|
584
|
+
);
|
|
585
|
+
CREATE TABLE IF NOT EXISTS knowledge_acknowledgments (
|
|
586
|
+
knowledge_id INTEGER NOT NULL REFERENCES knowledge(id) ON DELETE CASCADE,
|
|
587
|
+
task_id INTEGER NOT NULL,
|
|
588
|
+
acknowledged_at TEXT DEFAULT CURRENT_TIMESTAMP,
|
|
589
|
+
PRIMARY KEY (knowledge_id, task_id)
|
|
590
|
+
);
|
|
591
|
+
`);
|
|
573
592
|
db.run("INSERT INTO specs (id, name, phase) VALUES ('SPEC-001', 'test', 'implementing')");
|
|
574
593
|
|
|
575
594
|
// Insert with old JSON format
|
|
@@ -663,9 +682,7 @@ describe("Migration System", () => {
|
|
|
663
682
|
spec_id TEXT NOT NULL,
|
|
664
683
|
planned_vs_done TEXT,
|
|
665
684
|
deviations TEXT,
|
|
666
|
-
pattern_violations TEXT,
|
|
667
685
|
status TEXT DEFAULT 'pending',
|
|
668
|
-
resolution TEXT,
|
|
669
686
|
created_at TEXT DEFAULT CURRENT_TIMESTAMP
|
|
670
687
|
);
|
|
671
688
|
`);
|
package/db/schema.ts
CHANGED
|
@@ -73,9 +73,7 @@ export function initSchema(): void {
|
|
|
73
73
|
spec_id TEXT NOT NULL REFERENCES specs(id),
|
|
74
74
|
planned_vs_done TEXT,
|
|
75
75
|
deviations TEXT,
|
|
76
|
-
pattern_violations TEXT,
|
|
77
76
|
status TEXT DEFAULT 'pending',
|
|
78
|
-
resolution TEXT,
|
|
79
77
|
created_at TEXT DEFAULT CURRENT_TIMESTAMP
|
|
80
78
|
);
|
|
81
79
|
|
|
@@ -119,7 +117,6 @@ export function initSchema(): void {
|
|
|
119
117
|
content TEXT NOT NULL,
|
|
120
118
|
severity TEXT DEFAULT 'info',
|
|
121
119
|
broadcast_to TEXT DEFAULT 'all',
|
|
122
|
-
acknowledged_by TEXT,
|
|
123
120
|
created_at TEXT DEFAULT CURRENT_TIMESTAMP
|
|
124
121
|
);
|
|
125
122
|
|
|
@@ -503,6 +500,16 @@ const MIGRATIONS: Migration[] = [
|
|
|
503
500
|
db.exec(`ALTER TABLE project ADD COLUMN auto_simplify INTEGER DEFAULT 0`);
|
|
504
501
|
},
|
|
505
502
|
},
|
|
503
|
+
{
|
|
504
|
+
version: "10.3.0",
|
|
505
|
+
description: "Remover colunas mortas: review.pattern_violations, review.resolution, knowledge.acknowledged_by, decisions.superseded_by",
|
|
506
|
+
up: (db) => {
|
|
507
|
+
db.exec(`ALTER TABLE review DROP COLUMN pattern_violations`);
|
|
508
|
+
db.exec(`ALTER TABLE review DROP COLUMN resolution`);
|
|
509
|
+
db.exec(`ALTER TABLE knowledge DROP COLUMN acknowledged_by`);
|
|
510
|
+
db.exec(`ALTER TABLE decisions DROP COLUMN superseded_by`);
|
|
511
|
+
},
|
|
512
|
+
},
|
|
506
513
|
];
|
|
507
514
|
|
|
508
515
|
export function runMigrations(): void {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@codexa/cli",
|
|
3
|
-
"version": "9.0.
|
|
3
|
+
"version": "9.0.31",
|
|
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": {
|
|
@@ -5,9 +5,16 @@
|
|
|
5
5
|
// ═══════════════════════════════════════════════════════════════
|
|
6
6
|
|
|
7
7
|
import { getDb } from "../db/connection";
|
|
8
|
-
import { existsSync } from "fs";
|
|
8
|
+
import { existsSync, mkdirSync } from "fs";
|
|
9
9
|
import { resolve, join } from "path";
|
|
10
|
-
|
|
10
|
+
|
|
11
|
+
function ensureContextDir(): string {
|
|
12
|
+
const dir = resolve(process.cwd(), ".codexa/context");
|
|
13
|
+
if (!existsSync(dir)) {
|
|
14
|
+
mkdirSync(dir, { recursive: true });
|
|
15
|
+
}
|
|
16
|
+
return dir;
|
|
17
|
+
}
|
|
11
18
|
|
|
12
19
|
// ═══════════════════════════════════════════════════════════════
|
|
13
20
|
// INTERFACES
|
|
@@ -5,32 +5,20 @@
|
|
|
5
5
|
**ARQUIVOS**: {{filesList}}
|
|
6
6
|
**MUDANCA**: {{taskDescription}}
|
|
7
7
|
|
|
8
|
-
{{
|
|
8
|
+
{{agentExpertise}}
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
**Contexto resumido (standards, alertas, decisoes):**
|
|
13
|
-
{{contextFile}}
|
|
14
|
-
|
|
15
|
-
**Contexto COMPLETO (todos os patterns, utilities, decisions, knowledge SEM truncacao):**
|
|
16
|
-
{{fullContextFile}}
|
|
17
|
-
|
|
18
|
-
Leia o arquivo de contexto completo quando precisar de detalhes sobre patterns, utilities, stack ou discoveries que nao aparecem no resumido.
|
|
10
|
+
{{inlineContext}}
|
|
19
11
|
|
|
20
12
|
{{pendingKnowledge}}
|
|
21
13
|
|
|
22
14
|
## EXECUTE AGORA
|
|
23
15
|
|
|
24
|
-
1. Read
|
|
25
|
-
2.
|
|
26
|
-
3.
|
|
27
|
-
4. Edit/Write para criar/modificar os arquivos listados
|
|
28
|
-
5. Retorne JSON:
|
|
16
|
+
1. Read arquivos existentes que precisa modificar
|
|
17
|
+
2. Edit/Write para criar/modificar os arquivos listados
|
|
18
|
+
3. Retorne JSON:
|
|
29
19
|
|
|
30
20
|
```json
|
|
31
21
|
{"status": "completed", "summary": "...", "files_created": [], "files_modified": [], "reasoning": {"approach": "como abordou (min 20 chars)", "challenges": [], "recommendations": "para proximas tasks"}, "knowledge_to_broadcast": [], "decisions_made": []}
|
|
32
22
|
```
|
|
33
23
|
|
|
34
24
|
**REGRA**: Se retornar sem usar Edit/Write = FALHA.
|
|
35
|
-
|
|
36
|
-
{{agentExpertise}}
|
package/workflow.ts
CHANGED
|
@@ -253,11 +253,10 @@ taskCmd
|
|
|
253
253
|
.command("start <ids>")
|
|
254
254
|
.description("Inicia task(s) - pode ser multiplas separadas por virgula")
|
|
255
255
|
.option("--json", "Saida em JSON")
|
|
256
|
-
.option("--minimal-context", "Usar contexto reduzido (2KB) em vez do completo
|
|
257
|
-
.option("--inline-context", "Incluir contexto inline no JSON (modo legado, 16KB)")
|
|
256
|
+
.option("--minimal-context", "Usar contexto reduzido (~2KB) em vez do completo")
|
|
258
257
|
.option("--spec <id>", "ID do spec (padrao: mais recente)")
|
|
259
258
|
.action(wrapAction((ids: string, options) => {
|
|
260
|
-
taskStart(ids, options.json, options.minimalContext, options.spec
|
|
259
|
+
taskStart(ids, options.json, options.minimalContext, options.spec);
|
|
261
260
|
}));
|
|
262
261
|
|
|
263
262
|
taskCmd
|
|
@@ -542,15 +541,14 @@ contextCmd
|
|
|
542
541
|
"SELECT COUNT(*) as c FROM tasks WHERE spec_id = ? AND status = 'done'"
|
|
543
542
|
).get(task.spec_id) as any)?.c || 0 : 0;
|
|
544
543
|
|
|
545
|
-
const budget = getContextBudget(
|
|
544
|
+
const budget = getContextBudget(fullCtx, completedCount);
|
|
546
545
|
const warning = formatContextWarning(budget);
|
|
547
546
|
|
|
548
547
|
console.log(`\nContext Budget (Task #${taskId}):`);
|
|
549
548
|
console.log(`${"─".repeat(40)}`);
|
|
550
|
-
console.log(` Prompt (
|
|
551
|
-
console.log(` Context file: ~${budget.contextFileTokens} tokens`);
|
|
549
|
+
console.log(` Prompt (inline): ~${budget.promptTokens} tokens`);
|
|
552
550
|
console.log(` Orchestrator est.: ~${budget.orchestratorEstimate} tokens`);
|
|
553
|
-
console.log(` Total estimado: ~${budget.totalEstimated} tokens`);
|
|
551
|
+
console.log(` Total estimado: ~${budget.totalEstimated} / ${budget.maxUsableTokens} tokens`);
|
|
554
552
|
console.log(` Warning level: ${budget.warningLevel}`);
|
|
555
553
|
if (warning) console.log(`\n ${warning}`);
|
|
556
554
|
console.log();
|
package/context/cache.ts
DELETED
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
// ═══════════════════════════════════════════════════════════════
|
|
2
|
-
// CONTEXT CACHE (v10.0)
|
|
3
|
-
// Content-addressable cache to avoid regenerating identical context.
|
|
4
|
-
// Invalidated when knowledge, decisions, or task state changes.
|
|
5
|
-
// ═══════════════════════════════════════════════════════════════
|
|
6
|
-
|
|
7
|
-
import { getDb } from "../db/connection";
|
|
8
|
-
|
|
9
|
-
interface CacheEntry {
|
|
10
|
-
hash: string;
|
|
11
|
-
filePath: string;
|
|
12
|
-
specId: string;
|
|
13
|
-
createdAt: number;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
const cache = new Map<string, CacheEntry>();
|
|
17
|
-
|
|
18
|
-
export function computeContextHash(specId: string, taskId: number): string {
|
|
19
|
-
const db = getDb();
|
|
20
|
-
|
|
21
|
-
const spec = db.query("SELECT updated_at FROM specs WHERE id = ?").get(specId) as any;
|
|
22
|
-
const ctx = db.query("SELECT updated_at FROM context WHERE spec_id = ?").get(specId) as any;
|
|
23
|
-
const knowledgeCount = (db.query(
|
|
24
|
-
"SELECT COUNT(*) as c FROM knowledge WHERE spec_id = ?"
|
|
25
|
-
).get(specId) as any)?.c || 0;
|
|
26
|
-
const decisionCount = (db.query(
|
|
27
|
-
"SELECT COUNT(*) as c FROM decisions WHERE spec_id = ? AND status = 'active'"
|
|
28
|
-
).get(specId) as any)?.c || 0;
|
|
29
|
-
const task = db.query("SELECT depends_on, agent, files FROM tasks WHERE id = ?").get(taskId) as any;
|
|
30
|
-
|
|
31
|
-
// Hash key components that affect context content
|
|
32
|
-
const parts = [
|
|
33
|
-
specId,
|
|
34
|
-
String(taskId),
|
|
35
|
-
spec?.updated_at || "",
|
|
36
|
-
ctx?.updated_at || "",
|
|
37
|
-
String(knowledgeCount),
|
|
38
|
-
String(decisionCount),
|
|
39
|
-
task?.depends_on || "[]",
|
|
40
|
-
task?.agent || "",
|
|
41
|
-
task?.files || "[]",
|
|
42
|
-
];
|
|
43
|
-
|
|
44
|
-
// Simple hash using Bun's built-in hashing
|
|
45
|
-
const raw = parts.join("|");
|
|
46
|
-
const hasher = new Bun.CryptoHasher("md5");
|
|
47
|
-
hasher.update(raw);
|
|
48
|
-
return hasher.digest("hex");
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
export function getCachedContextPath(hash: string): string | null {
|
|
52
|
-
const entry = cache.get(hash);
|
|
53
|
-
if (!entry) return null;
|
|
54
|
-
|
|
55
|
-
// Check if file still exists
|
|
56
|
-
const file = Bun.file(entry.filePath);
|
|
57
|
-
if (file.size === 0) {
|
|
58
|
-
cache.delete(hash);
|
|
59
|
-
return null;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
return entry.filePath;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
export function setCachedContext(hash: string, filePath: string, specId: string): void {
|
|
66
|
-
cache.set(hash, {
|
|
67
|
-
hash,
|
|
68
|
-
filePath,
|
|
69
|
-
specId,
|
|
70
|
-
createdAt: Date.now(),
|
|
71
|
-
});
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
export function invalidateCache(specId: string): void {
|
|
75
|
-
// v10.0: Selective invalidation — only remove entries for the given spec
|
|
76
|
-
for (const [key, entry] of cache.entries()) {
|
|
77
|
-
if (entry.specId === specId) {
|
|
78
|
-
cache.delete(key);
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
export function clearCache(): void {
|
|
84
|
-
cache.clear();
|
|
85
|
-
}
|
package/context/file-writer.ts
DELETED
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
// ═══════════════════════════════════════════════════════════════
|
|
2
|
-
// CONTEXT FILE WRITER (v10.0)
|
|
3
|
-
// Writes context to .codexa/context/task-{id}.md instead of
|
|
4
|
-
// injecting 16KB inline into the subagent prompt.
|
|
5
|
-
// Subagents read the file on-demand via Read tool.
|
|
6
|
-
// ═══════════════════════════════════════════════════════════════
|
|
7
|
-
|
|
8
|
-
import { existsSync, mkdirSync } from "fs";
|
|
9
|
-
import { join, resolve } from "path";
|
|
10
|
-
|
|
11
|
-
const CONTEXT_DIR = ".codexa/context";
|
|
12
|
-
|
|
13
|
-
export function ensureContextDir(): string {
|
|
14
|
-
const dir = resolve(process.cwd(), CONTEXT_DIR);
|
|
15
|
-
if (!existsSync(dir)) {
|
|
16
|
-
mkdirSync(dir, { recursive: true });
|
|
17
|
-
}
|
|
18
|
-
return dir;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export function writeContextFile(taskId: number, content: string): string {
|
|
22
|
-
const dir = ensureContextDir();
|
|
23
|
-
const filePath = join(dir, `task-${taskId}.md`);
|
|
24
|
-
Bun.write(filePath, content);
|
|
25
|
-
return resolve(filePath);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export function writeFullContextFile(taskId: number, content: string): string {
|
|
29
|
-
const dir = ensureContextDir();
|
|
30
|
-
const filePath = join(dir, `task-${taskId}-full.md`);
|
|
31
|
-
Bun.write(filePath, content);
|
|
32
|
-
return resolve(filePath);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export function getContextFilePath(taskId: number): string {
|
|
36
|
-
return resolve(process.cwd(), CONTEXT_DIR, `task-${taskId}.md`);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export function getFullContextFilePath(taskId: number): string {
|
|
40
|
-
return resolve(process.cwd(), CONTEXT_DIR, `task-${taskId}-full.md`);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export function cleanupContextFiles(specId: string): void {
|
|
44
|
-
const dir = resolve(process.cwd(), CONTEXT_DIR);
|
|
45
|
-
if (!existsSync(dir)) return;
|
|
46
|
-
|
|
47
|
-
try {
|
|
48
|
-
const { readdirSync, unlinkSync } = require("fs");
|
|
49
|
-
const files = readdirSync(dir) as string[];
|
|
50
|
-
for (const file of files) {
|
|
51
|
-
if ((file.startsWith("task-") || file.startsWith("simplify-")) && file.endsWith(".md")) {
|
|
52
|
-
unlinkSync(join(dir, file));
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
} catch {
|
|
56
|
-
// Non-critical: don't fail if cleanup fails
|
|
57
|
-
}
|
|
58
|
-
}
|