@codexa/cli 9.0.28 → 9.0.30

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.
@@ -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
- import { invalidateCache } from "../context/cache";
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}`);
@@ -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
- import { invalidateCache } from "../context/cache";
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: "!",
@@ -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
- import { cleanupContextFiles } from "../context/file-writer";
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
- // v10.0: Clean up stale context files
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
- // v10.0: Clean up stale context files
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 { generateContextFile } from "../context/generator";
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, inlineContext: boolean = false): void {
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
- // v10.0: File-based context (default) vs inline (backward compat)
239
- const useFileContext = !inlineContext && !minimalContext;
240
- let contextFile: string | null = null;
241
- let contextText: string | null = null;
242
- let contextSummary: string;
243
-
244
- if (useFileContext) {
245
- // Write context to file, subagent reads via Read tool
246
- contextFile = generateContextFile(task.id);
247
- contextSummary = getMinimalContextForSubagent(task.id);
248
- } else if (minimalContext) {
249
- contextSummary = getMinimalContextForSubagent(task.id);
250
- } else {
251
- // --inline-context: backward compat
252
- contextText = getContextForSubagent(task.id);
253
- contextSummary = getMinimalContextForSubagent(task.id);
254
- }
255
-
256
- // v10.2: Full context file path (generated alongside lean file)
257
- const fullContextFile = useFileContext ? getFullContextFilePath(task.id) : "";
258
-
259
- // v10.2: Build pending knowledge section for template
260
- const pendingKnowledgeForPrompt = useFileContext
261
- ? (() => {
262
- const unread = getUnreadKnowledgeForTask(spec.id, task.id);
263
- const critical = unread.filter((k: any) => k.severity === 'critical' && k.task_origin !== task.id);
264
- if (critical.length === 0) return "";
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 promptText = subagentPrompt || contextText || contextSummary!;
292
- const contextFileContent = contextFile
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
- // Base output (lean by default)
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
- if (useFileContext) {
321
- // v10.0: File-based context (new default)
322
- output.contextFile = contextFile;
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";
@@ -1,10 +1,9 @@
1
- // v8.3: Limite maximo de contexto para subagents (16KB)
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 = kept during truncation
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 type AssemblyMode = "lean" | "full";
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
- }
@@ -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, type AssemblyMode } from "./assembly";
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 (v9.0 — decomposed from v8.1 monolith)
112
+ // CONTEXT BUILDER (v11.0 — inline delivery, no file system)
115
113
  // ═══════════════════════════════════════════════════════════════
116
114
 
117
- function fetchContextData(taskId: number, fullMode: boolean = false): ContextData | null {
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
- // Decisoes relevantes (v10.2: fullMode removes limits)
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, decisionLimit) as any[];
139
- const decisions = fullMode ? allDecisions : filterRelevantDecisions(allDecisions, taskFiles, 8);
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
- // Knowledge com caps e indicadores de truncamento (v10.2: fullMode removes caps)
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 = fullMode ? allCriticalKnowledge : allCriticalKnowledge.slice(0, 20);
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 = fullMode ? allInfoKnowledge : allInfoKnowledge.slice(0, 10);
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, mode: AssemblyMode = "lean"): { content: string; data: ContextData } | null {
244
- const isFullMode = mode === "full";
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, mode), data };
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 = buildFullContext(taskId);
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, generateContextFile } from "./generator";
3
- export { assembleSections, MAX_CONTEXT_SIZE } from "./assembly";
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";
@@ -1,33 +1,42 @@
1
1
  // ═══════════════════════════════════════════════════════════════
2
- // CONTEXT MONITOR (v10.0)
2
+ // CONTEXT MONITOR (v11.0)
3
3
  // Estimates token usage and warns when context budget is high.
4
- // Thresholds: 65% = caution, 75% = critical
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
- contextFileTokens: number;
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
- // Claude Code context window practical limit (~200k tokens, but 80% usable)
21
- const MAX_USABLE_TOKENS = 160_000;
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
- contextFilePath?: string,
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 totalEstimated = promptTokens + contextFileTokens + orchestratorEstimate;
79
- const ratio = totalEstimated / MAX_USABLE_TOKENS;
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
- contextFileTokens,
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 / MAX_USABLE_TOKENS) * 100);
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
  }
@@ -114,8 +114,8 @@ export function buildStandardsSection(data: ContextData): ContextSection {
114
114
  }
115
115
 
116
116
  export function buildDecisionsSection(data: ContextData): ContextSection {
117
- // v10.2: In full mode, show ALL decisions (no cap at 8)
118
- const decisionsToShow = data.fullMode ? data.allDecisions : data.decisions;
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
- // v10.2: Full mode shows ALL discovered patterns
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
- ${truncated > 0 ? `[mostrando ultimos 10 de ${data.discoveredPatterns.length}]\n` : ''}${patternsToShow.map((p: any) => `- ${p.pattern}${p.source_task ? ` (Task #${p.source_task})` : ""}`).join("\n")}
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
- // v10.2: Full mode fetches all utilities
195
- const utilLimit = data.fullMode ? 200 : 15;
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
- // v10.2: Full mode shows all stack entries
247
- const mainStack = data.fullMode ? allStackEntries : allStackEntries.slice(0, 6);
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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codexa/cli",
3
- "version": "9.0.28",
3
+ "version": "9.0.30",
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
- import { ensureContextDir } from "../context/file-writer";
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
- {{contextSummary}}
8
+ {{agentExpertise}}
9
9
 
10
- ## CONTEXTO
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 o arquivo de contexto resumido
25
- 2. Se precisar de mais detalhes, Read o arquivo de contexto completo (secoes relevantes)
26
- 3. Read arquivos existentes que precisa modificar
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 (16KB)")
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, options.inlineContext);
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(minCtx, fullCtx, completedCount);
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 (lean): ~${budget.promptTokens} tokens`);
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
- }
@@ -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
- }