@codexa/cli 9.0.21 → 9.0.22

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.
@@ -107,10 +107,20 @@ export function detectConflicts(
107
107
 
108
108
  // Verificar sobreposicao alta de keywords (mesmo topico, decisoes diferentes?)
109
109
  if (conflicts.every((c) => c.id !== existing.id)) {
110
+ // v10.1: Skip exact duplicates (same title+decision = not a conflict)
111
+ if (
112
+ newTitle.trim().toLowerCase() === existing.title?.trim().toLowerCase() &&
113
+ newDecision.trim().toLowerCase() === existing.decision?.trim().toLowerCase()
114
+ ) {
115
+ continue;
116
+ }
117
+
110
118
  const intersection = [...newKeywords].filter((k) => existingKeywords.has(k));
111
119
  const similarity = intersection.length / Math.max(newKeywords.size, existingKeywords.size);
112
120
 
113
- if (similarity > 0.4 && intersection.length >= 3) {
121
+ // v10.1: Raised threshold from 0.4 to 0.7 to reduce false positives
122
+ // Complementary decisions (e.g. "Use REST" + "Use JWT") were falsely flagged
123
+ if (similarity > 0.7 && intersection.length >= 3) {
114
124
  conflicts.push({
115
125
  id: existing.id,
116
126
  title: existing.title,
@@ -493,9 +493,17 @@ function executeCheck(check: string, context: any): { passed: boolean; details?:
493
493
  .all(dcTask.spec_id) as any[];
494
494
  if (existingDecisions.length === 0) return { passed: true };
495
495
 
496
+ // v10.1: Filter out decisions from the CURRENT task.
497
+ // processSubagentReturn() already saved them to DB before this gate runs,
498
+ // so without filtering, the new decisions would match against themselves.
499
+ const priorDecisions = existingDecisions.filter(
500
+ (d: any) => d.task_ref !== dcTask.number
501
+ );
502
+ if (priorDecisions.length === 0) return { passed: true };
503
+
496
504
  const allConflicts: string[] = [];
497
505
  for (const dec of context.subagentData.decisions_made) {
498
- const analysis = detectConflicts(dec.title, dec.decision, existingDecisions);
506
+ const analysis = detectConflicts(dec.title, dec.decision, priorDecisions);
499
507
  if (analysis.hasConflict) {
500
508
  for (const conflict of analysis.conflictingDecisions) {
501
509
  allConflicts.push(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codexa/cli",
3
- "version": "9.0.21",
3
+ "version": "9.0.22",
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": {
@@ -316,20 +316,35 @@ export function parseSubagentReturn(input: string): ParseResult {
316
316
  }
317
317
  }
318
318
 
319
- // v9.0: Validacao de reasoning obrigatorio para status "completed" (Gate 4.4)
319
+ // v9.0: Validacao de reasoning para status "completed" (Gate 4.4)
320
+ // v10.1: Auto-derive reasoning from summary when missing (graceful degradation)
320
321
  if (parsed.status === "completed") {
321
322
  if (!parsed.reasoning || typeof parsed.reasoning !== "object") {
322
- errors.push(
323
- "Status 'completed' requer campo 'reasoning' com pelo menos 'approach'. " +
324
- "Isso preserva contexto entre tasks. Exemplo: {\"reasoning\": {\"approach\": \"Descreva como abordou o problema\"}}"
325
- );
323
+ // Auto-derive reasoning from summary instead of hard-failing
324
+ const summary = typeof parsed.summary === "string" ? parsed.summary : "";
325
+ if (summary.length >= 20) {
326
+ parsed.reasoning = { approach: summary };
327
+ console.warn("[protocol] reasoning ausente — auto-derivado do summary");
328
+ } else {
329
+ errors.push(
330
+ "Status 'completed' requer campo 'reasoning' com pelo menos 'approach'. " +
331
+ "Isso preserva contexto entre tasks. Exemplo: {\"reasoning\": {\"approach\": \"Descreva como abordou o problema\"}}"
332
+ );
333
+ }
326
334
  } else {
327
335
  const r = parsed.reasoning as Record<string, unknown>;
328
336
  if (!r.approach || typeof r.approach !== "string" || (r.approach as string).length < 20) {
329
- errors.push(
330
- "Campo 'reasoning.approach' obrigatorio com minimo 20 caracteres para status 'completed'. " +
331
- "Descreva COMO voce abordou o problema, nao apenas O QUE fez."
332
- );
337
+ // Auto-derive approach from summary if approach is too short
338
+ const summary = typeof parsed.summary === "string" ? parsed.summary : "";
339
+ if (summary.length >= 20) {
340
+ (parsed.reasoning as Record<string, unknown>).approach = summary;
341
+ console.warn("[protocol] reasoning.approach insuficiente — auto-derivado do summary");
342
+ } else {
343
+ errors.push(
344
+ "Campo 'reasoning.approach' obrigatorio com minimo 20 caracteres para status 'completed'. " +
345
+ "Descreva COMO voce abordou o problema, nao apenas O QUE fez."
346
+ );
347
+ }
333
348
  }
334
349
  }
335
350
  }