wogiflow 2.31.1 → 2.31.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wogiflow",
3
- "version": "2.31.1",
3
+ "version": "2.31.2",
4
4
  "description": "AI-powered development workflow management system with multi-model support",
5
5
  "main": "lib/index.js",
6
6
  "bin": {
@@ -886,6 +886,33 @@ const CONFIG_DEFAULTS = {
886
886
  }
887
887
  },
888
888
 
889
+ // --- Deferral Gate (wf-f9912af6 + wf-b8839d99) ---
890
+ // wf-740f47e4 (DOCS-DRIFT): explicit defaults so config-schema.md isn't lying.
891
+ // The gate worked via inline fallbacks before, but config consumers couldn't
892
+ // discover the keys through the defaults loader.
893
+ deferralGate: {
894
+ enabled: true,
895
+ authTtlSeconds: 600,
896
+ classifyUserPrompts: true,
897
+ minClassifierConfidence: 75
898
+ },
899
+
900
+ // --- Self-Adversary Gate (wf-e399bd8d) ---
901
+ selfAdversaryGate: {
902
+ enabled: true,
903
+ targetConfidence: 95,
904
+ maxIterations: 8,
905
+ generatorModel: 'anthropic:claude-sonnet-4-6',
906
+ adversaryModel: 'anthropic:claude-3-5-haiku-latest'
907
+ },
908
+
909
+ // --- Research-Required Gate (wf-5cd71b1f) ---
910
+ researchRequiredGate: {
911
+ enabled: true,
912
+ requiredEvidence: 2,
913
+ maxAttempts: 3
914
+ },
915
+
889
916
  // --- Long Input Gate ---
890
917
  longInputGate: {
891
918
  enabled: true,
@@ -359,17 +359,22 @@ async function runSelfAdversaryLoop(opts = {}) {
359
359
  verdict
360
360
  });
361
361
 
362
- // Termination checks
362
+ // Termination checks. wf-740f47e4 (L-1-RESIDUAL): adversary VERDICT is
363
+ // authoritative — confidence threshold alone cannot override 'revise'.
364
+ // Previously a second unconditional `if (adjustedConfidence >= target)`
365
+ // bypassed the verdict, accepting decisions the adversary explicitly
366
+ // wanted refined. The S-3 confidence-cap (+10 ceiling) limited damage
367
+ // but the verdict contract was still violated.
363
368
  if (verdict === 'needs-user') {
364
369
  return buildEscalate(candidate, iterationMemory, targetConfidence, 'adversary-says-needs-user');
365
370
  }
366
371
  if (verdict === 'accept' && adjustedConfidence >= targetConfidence) {
367
372
  return buildSuccess({ ...candidate, confidence: adjustedConfidence }, iterationMemory, targetConfidence);
368
373
  }
369
- if (adjustedConfidence >= targetConfidence) {
370
- return buildSuccess({ ...candidate, confidence: adjustedConfidence }, iterationMemory, targetConfidence);
371
- }
372
- // Otherwise loop again with the critique in memory
374
+ // verdict === 'revise' or any other value → continue iterating, even if
375
+ // adjustedConfidence is high. The adversary explicitly said "not yet";
376
+ // honor it. Only the loop-exhausted path (below) can ship a 'revise'
377
+ // decision and even then it surfaces via buildEscalate, not Success.
373
378
  }
374
379
 
375
380
  // Max iterations exhausted without reaching threshold
@@ -331,7 +331,9 @@ function stripQuotedContent(cmd) {
331
331
  // heredoc; longer than that, the gate fails open (no strip) which is safer than
332
332
  // ReDoS. Single unified terminator regex covers both EOL-anchored and word-
333
333
  // boundary cases; tolerates optional trailing whitespace/punctuation.
334
- stripped = stripped.replace(/<<-?\s*['"]?(\w+)['"]?[\s\S]{0,8000}?\n\1(?:\s*[;)]?\s*$|\b)/gm, ' <<HEREDOC>> ');
334
+ // wf-740f47e4 (CRLF): accept both \n and \r\n terminators so Windows-style
335
+ // line endings in fixtures or test inputs don't bypass the strip.
336
+ stripped = stripped.replace(/<<-?\s*['"]?(\w+)['"]?[\s\S]{0,8000}?\r?\n\1(?:\s*[;)]?\s*$|\b)/gm, ' <<HEREDOC>> ');
335
337
  // Single-quoted strings
336
338
  stripped = stripped.replace(/'[^']*'/g, "''");
337
339
  // Backtick command substitution
@@ -46,6 +46,22 @@ const REMEDIATION_LABELS = Object.freeze({
46
46
  'workspace-overdue': 'workspace-overdue (a worker dispatch is past its deadline)'
47
47
  });
48
48
 
49
+ /**
50
+ * Internal: rank gate IDs by REMEDIATION_PRIORITY. Unknown gates sort to
51
+ * the bottom. Returns the IDs in priority order. wf-740f47e4 (DUAL API):
52
+ * extracted from pickTopRemediation + pickStopHookGate so the priority
53
+ * source-of-truth is single.
54
+ */
55
+ function _rankByPriority(gateIds) {
56
+ return [...gateIds].sort((a, b) => {
57
+ const ia = REMEDIATION_PRIORITY.indexOf(a);
58
+ const ib = REMEDIATION_PRIORITY.indexOf(b);
59
+ const na = ia === -1 ? Number.POSITIVE_INFINITY : ia;
60
+ const nb = ib === -1 ? Number.POSITIVE_INFINITY : ib;
61
+ return na - nb;
62
+ });
63
+ }
64
+
49
65
  /**
50
66
  * Pick the top-priority active remediation from a set of (gateId, message) pairs.
51
67
  *
@@ -58,16 +74,13 @@ function pickTopRemediation(active) {
58
74
  if (!Array.isArray(active) || active.length === 0) {
59
75
  return { top: null, queued: [] };
60
76
  }
61
- // Filter to valid entries and sort by priority index.
62
77
  const valid = active.filter(g => g && typeof g.id === 'string' && typeof g.message === 'string' && g.message.trim().length > 0);
63
78
  if (valid.length === 0) return { top: null, queued: [] };
64
79
 
65
- const indexed = valid.map(g => ({ ...g, idx: REMEDIATION_PRIORITY.indexOf(g.id) }))
66
- .map(g => ({ ...g, idx: g.idx === -1 ? Number.POSITIVE_INFINITY : g.idx }));
67
- indexed.sort((a, b) => a.idx - b.idx);
68
-
69
- const top = { id: indexed[0].id, message: indexed[0].message };
70
- const queued = indexed.slice(1).map(g => g.id);
80
+ const byId = new Map(valid.map(g => [g.id, g.message]));
81
+ const ranked = _rankByPriority(valid.map(g => g.id));
82
+ const top = { id: ranked[0], message: byId.get(ranked[0]) };
83
+ const queued = ranked.slice(1);
71
84
  return { top, queued };
72
85
  }
73
86
 
@@ -111,12 +124,12 @@ function selectAndRender(gateMap) {
111
124
  */
112
125
  function pickStopHookGate(activeFlags) {
113
126
  if (!activeFlags || typeof activeFlags !== 'object') return { topGateId: null, queued: [] };
114
- const active = REMEDIATION_PRIORITY.filter(id => activeFlags[id] === true);
115
- if (active.length === 0) return { topGateId: null, queued: [] };
116
- return {
117
- topGateId: active[0],
118
- queued: active.slice(1)
119
- };
127
+ // wf-740f47e4 (DUAL API): use the shared _rankByPriority helper so this
128
+ // and pickTopRemediation can never disagree on priority order.
129
+ const activeIds = Object.keys(activeFlags).filter(id => activeFlags[id] === true);
130
+ if (activeIds.length === 0) return { topGateId: null, queued: [] };
131
+ const ranked = _rankByPriority(activeIds);
132
+ return { topGateId: ranked[0], queued: ranked.slice(1) };
120
133
  }
121
134
 
122
135
  module.exports = {
@@ -42,10 +42,13 @@ async function orchestrateStop({ parsedInput }) {
42
42
  let orchestratorTopGate = null;
43
43
  try {
44
44
  const { pickStopHookGate } = require('./gate-orchestrator');
45
- const { topGateId } = pickStopHookGate({
45
+ // wf-740f47e4 (NULL-CHECK): guard against malformed return shape.
46
+ const result = pickStopHookGate({
46
47
  'long-input-pending': activeGates['long-input-pending'] === true
47
48
  });
48
- orchestratorTopGate = topGateId;
49
+ orchestratorTopGate = (result && typeof result === 'object' && typeof result.topGateId === 'string')
50
+ ? result.topGateId
51
+ : null;
49
52
  } catch (_err) { /* fail-open */ }
50
53
  const longInputActive = orchestratorTopGate === 'long-input-pending';
51
54