Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions .aiox-core/core/ideation/ideation-engine.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ const { execSync } = require('child_process');
// Import dependencies with fallbacks
let GotchasMemory;
try {
GotchasMemory = require('../memory/gotchas-memory');
({ GotchasMemory } = require('../memory/gotchas-memory'));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Instantiate GotchasMemory against the configured project root.

Now that the named export makes this path reachable again, the fallback constructor still uses process.cwd() instead of this.rootPath. When IdeationEngine is run against another workspace via config.rootPath, known-gotcha filtering will read the wrong .aiox store.

Suggested fix
-    this.gotchasMemory = config.gotchasMemory || (GotchasMemory ? new GotchasMemory() : null);
+    this.gotchasMemory =
+      config.gotchasMemory || (GotchasMemory ? new GotchasMemory(this.rootPath) : null);
As per coding guidelines, ".aiox-core/core/**": "Ensure backwards compatibility — core modules are consumed by all agents."

Also applies to: 30-30

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.aiox-core/core/ideation/ideation-engine.js at line 16, The GotchasMemory
instance is being created against process.cwd() instead of the configured
project root; update the IdeationEngine initialization to instantiate
GotchasMemory with this.rootPath (e.g., replace any new GotchasMemory() or calls
that default to process.cwd() with new GotchasMemory(this.rootPath)) so
known-gotcha filtering reads the correct .aiox store; apply the same change for
the other occurrence referenced (around the GotchasMemory usage at lines ~30).

} catch {
GotchasMemory = null;
}
Expand Down Expand Up @@ -76,10 +76,10 @@ class IdeationEngine {
let filtered = suggestions;
if (this.gotchasMemory) {
try {
const knownIssues = await this.gotchasMemory.getAll();
const knownIssues = this.gotchasMemory.listGotchas();
filtered = suggestions.filter((s) => !this.isKnownGotcha(s, knownIssues));
} catch {
// Ignore
} catch (error) {
console.warn('[IdeationEngine] Failed to load known gotchas for filtering:', error.message);
}
}

Expand Down
7 changes: 6 additions & 1 deletion .aiox-core/core/ids/circuit-breaker.js
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,12 @@ class CircuitBreaker {
*/
recordFailure() {
this._failureCount++;
this._lastFailureTime = Date.now();

// Only update lastFailureTime when not already OPEN
// to preserve the original recovery timeout
if (this._state !== STATE_OPEN) {
this._lastFailureTime = Date.now();
}

if (this._state === STATE_HALF_OPEN) {
// Any failure in half-open re-opens the circuit
Expand Down
28 changes: 26 additions & 2 deletions .aiox-core/core/memory/gotchas-memory.js
Original file line number Diff line number Diff line change
Expand Up @@ -253,15 +253,39 @@ class GotchasMemory extends EventEmitter {
count: 0,
firstSeen: now,
lastSeen: now,
timestamps: [],
samples: [],
errorPattern: errorData.message,
category: this._detectCategory(errorData.message + ' ' + (errorData.stack || '')),
};
}

// Update tracking
tracking.count++;
// Reset tracking if outside error window (stale errors should not count)
if (now - tracking.lastSeen > this.options.errorWindowMs) {
tracking.count = 0;
tracking.firstSeen = now;
tracking.samples = [];
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

// Ensure timestamps array exists for entries loaded from disk before this fix
if (!Array.isArray(tracking.timestamps)) {
tracking.timestamps = [];
}

// Add current timestamp and prune occurrences outside the sliding error window.
// This ensures errorWindowMs truly bounds the counted occurrences rather than
// relying on inactivity gaps — the previous gap-based reset allowed stale errors
// (e.g., at 0h, 20h, 40h) to keep accumulating as long as each repeat landed
// just inside the gap boundary.
const windowStart = now - this.options.errorWindowMs;
tracking.timestamps.push(now);
tracking.timestamps = tracking.timestamps.filter((ts) => ts >= windowStart);

// Recompute derived fields from the pruned sliding window
tracking.count = tracking.timestamps.length;
tracking.firstSeen = tracking.timestamps.length > 0 ? tracking.timestamps[0] : now;
Comment on lines +270 to +286

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Backfill timestamps for pre-upgrade tracking entries.

When a persisted entry created before this change is loaded, tracking.timestamps is initialized to [], so the next occurrence recomputes tracking.count as 1 even if the stored count/lastSeen are still inside the active window. That breaks pending auto-captures across upgrade boundaries. Seed the window from persisted sample timestamps or preserved count metadata before recomputing.
As per coding guidelines, ".aiox-core/core/**": "Ensure backwards compatibility — core modules are consumed by all agents."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.aiox-core/core/memory/gotchas-memory.js around lines 270 - 286, Persisted
tracking entries that lack tracking.timestamps should be backfilled from
preserved metadata before pruning: if tracking.timestamps is not an array,
initialize it by using existing tracking.count and tracking.lastSeen (or
tracking.firstSeen if lastSeen missing) to synthesize a timestamps array (e.g.,
create tracking.count timestamps ending at tracking.lastSeen spaced backward by
small deltas or evenly within this.options.errorWindowMs), then proceed to
push(now), filter by windowStart, and recompute tracking.count/firstSeen as you
already do; update the block operating on tracking.timestamps, tracking.count,
tracking.lastSeen, and tracking.firstSeen to perform this seeding step when
timestamps are absent.

tracking.lastSeen = now;

if (tracking.samples.length < 5) {
tracking.samples.push({
timestamp: new Date(now).toISOString(),
Expand Down
74 changes: 72 additions & 2 deletions .aiox-core/core/orchestration/condition-evaluator.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,13 @@ class ConditionEvaluator {
// Context for QA approval tracking (updated externally)
this._qaApproved = false;
this._phaseOutputs = {};

// Runtime overrides for conditions that cannot be inferred from TechStackProfile.
// Workflow engines populate this map at runtime to drive human-intent conditions
// (e.g., user_wants_ai_generation, stories_remaining). When a condition is absent
// from the map the evaluator falls back to a permissive default so that optional
// workflow steps are not silently skipped.
this._runtimeOverrides = {};
}

/**
Expand All @@ -54,6 +61,29 @@ class ConditionEvaluator {
this._phaseOutputs = outputs;
}

/**
* Set a runtime condition value that cannot be derived from TechStackProfile.
* Use this to drive human-intent conditions such as loop-termination signals
* (stories_remaining, epic_complete) and optional step flags
* (user_wants_ai_generation, etc.) from the workflow engine.
*
* @param {string} name - Condition name (e.g., 'stories_remaining')
* @param {boolean} value - Resolved value for this execution cycle
*/
setRuntimeCondition(name, value) {
this._runtimeOverrides[name] = Boolean(value);
}

/**
* Set multiple runtime conditions at once.
* @param {Object.<string, boolean>} overrides - Map of condition name to value
*/
setRuntimeConditions(overrides) {
for (const [name, value] of Object.entries(overrides)) {
this._runtimeOverrides[name] = Boolean(value);
}
}
Comment on lines +73 to +85

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Add input validation on public API methods.

Per coding guidelines for core modules, public APIs should validate inputs. Currently:

  • setRuntimeCondition doesn't validate that name is a non-empty string.
  • setRuntimeConditions will throw if overrides is null/undefined.
🛡️ Proposed defensive checks
  setRuntimeCondition(name, value) {
+   if (typeof name !== 'string' || !name.trim()) {
+     console.warn('[ConditionEvaluator] setRuntimeCondition called with invalid name:', name);
+     return;
+   }
    this._runtimeOverrides[name] = Boolean(value);
  }

  setRuntimeConditions(overrides) {
+   if (!overrides || typeof overrides !== 'object') {
+     console.warn('[ConditionEvaluator] setRuntimeConditions called with invalid overrides:', overrides);
+     return;
+   }
    for (const [name, value] of Object.entries(overrides)) {
      this._runtimeOverrides[name] = Boolean(value);
    }
  }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
setRuntimeCondition(name, value) {
this._runtimeOverrides[name] = Boolean(value);
}
/**
* Set multiple runtime conditions at once.
* @param {Object.<string, boolean>} overrides - Map of condition name to value
*/
setRuntimeConditions(overrides) {
for (const [name, value] of Object.entries(overrides)) {
this._runtimeOverrides[name] = Boolean(value);
}
}
setRuntimeCondition(name, value) {
if (typeof name !== 'string' || !name.trim()) {
console.warn('[ConditionEvaluator] setRuntimeCondition called with invalid name:', name);
return;
}
this._runtimeOverrides[name] = Boolean(value);
}
/**
* Set multiple runtime conditions at once.
* `@param` {Object.<string, boolean>} overrides - Map of condition name to value
*/
setRuntimeConditions(overrides) {
if (!overrides || typeof overrides !== 'object') {
console.warn('[ConditionEvaluator] setRuntimeConditions called with invalid overrides:', overrides);
return;
}
for (const [name, value] of Object.entries(overrides)) {
this._runtimeOverrides[name] = Boolean(value);
}
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.aiox-core/core/orchestration/condition-evaluator.js around lines 73 - 85,
Validate inputs for the public API: in setRuntimeCondition(name, value) ensure
name is a non-empty string (throw a TypeError if not) before assigning
this._runtimeOverrides[name] = Boolean(value); in
setRuntimeConditions(overrides) ensure overrides is a non-null plain object
(throw a TypeError if it's null/undefined or not an object), then iterate
Object.entries(overrides) and validate each key is a non-empty string before
setting this._runtimeOverrides[name] = Boolean(value); keep using the same
_runtimeOverrides storage and preserve current Boolean coercion for values.


/**
* Evaluate a condition string
* @param {string} condition - Condition like 'project_has_database'
Expand Down Expand Up @@ -97,6 +127,39 @@ class ConditionEvaluator {
// Composite conditions
has_any_data_to_analyze: () =>
this.profile.hasDatabase || this.profile.hasFrontend || this.profile.hasBackend,

// Workflow guidance conditions — human-driven, cannot be inferred from the
// tech stack profile. The workflow engine resolves these at runtime via
// setRuntimeCondition() / setRuntimeConditions(). When no override has been
// registered the evaluator returns true (permissive default) so that optional
// and iterative workflow steps are not silently skipped. Loop-termination
// conditions (stories_remaining, epic_complete) MUST be set to false by the
// orchestrator when the loop should exit; leaving them unset keeps the loop
// running, which is the safe default for unmanaged executions.
user_wants_ai_generation: () =>
this._runtimeOverrides['user_wants_ai_generation'] !== undefined
? this._runtimeOverrides['user_wants_ai_generation']
: true, // Optional AI UI generation step
stories_remaining: () =>
this._runtimeOverrides['stories_remaining'] !== undefined
? this._runtimeOverrides['stories_remaining']
: true, // Repeat story cycle; orchestrator sets false when backlog is empty
Comment on lines +143 to +146

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Search for orchestrators calling setRuntimeConditions or setRuntimeCondition
# Expected: At least one call setting stories_remaining to false

echo "=== Searching for setRuntimeCondition(s) calls ==="
rg -n 'setRuntimeCondition' --type js -C3

echo ""
echo "=== Searching for stories_remaining assignments ==="
rg -n 'stories_remaining' --type js --type yaml -C2

Repository: SynkraAI/aiox-core

Length of output: 165


🏁 Script executed:

#!/bin/bash
# Check if the files exist and examine ConditionEvaluator usage

echo "=== Checking for condition-evaluator.js ==="
find . -name "condition-evaluator.js" -type f

echo ""
echo "=== Checking for workflow-orchestrator.js ==="
find . -name "workflow-orchestrator.js" -type f

echo ""
echo "=== Searching for ConditionEvaluator instantiation ==="
rg -n "new ConditionEvaluator\|ConditionEvaluator" --type js -C 2

echo ""
echo "=== Checking for any loop conditions or stories_remaining in workflows ==="
rg -n "while\|for\|loop" --type js -B2 -A2 | head -50

echo ""
echo "=== Checking for TODO/FIXME related to conditions ==="
rg -n "TODO|FIXME" --type js -C 2 | grep -i "condition\|loop\|orchestrat" -C 2

Repository: SynkraAI/aiox-core

Length of output: 884


🏁 Script executed:

#!/bin/bash
# Read the specific files referenced in the review

echo "=== Reading condition-evaluator.js (lines 130-160) ==="
sed -n '130,160p' ./.aiox-core/core/orchestration/condition-evaluator.js

echo ""
echo "=== Reading workflow-orchestrator.js (lines 355-375) ==="
sed -n '355,375p' ./.aiox-core/core/orchestration/workflow-orchestrator.js

echo ""
echo "=== Checking full condition-evaluator.js file length ==="
wc -l ./.aiox-core/core/orchestration/condition-evaluator.js

echo ""
echo "=== Checking full workflow-orchestrator.js file length ==="
wc -l ./.aiox-core/core/orchestration/workflow-orchestrator.js

echo ""
echo "=== Searching for imports of ConditionEvaluator across codebase ==="
rg -n "import.*ConditionEvaluator\|require.*condition-evaluator" --type js

Repository: SynkraAI/aiox-core

Length of output: 3365


🏁 Script executed:

#!/bin/bash
# Check for setRuntimeConditions method and its usage

echo "=== Searching for setRuntimeConditions method definition ==="
rg -n "setRuntimeConditions\|setRuntimeCondition" --type js -A 5

echo ""
echo "=== Searching for conditionEvaluator method calls (evaluate, etc.) ==="
rg -n "conditionEvaluator\." --type js -B 1 -A 1

echo ""
echo "=== Searching for stories_remaining usage in workflow logic ==="
rg -n "stories_remaining" --type js -B 2 -A 2

echo ""
echo "=== Checking workflow execution loops and conditionals ==="
rg -n "while.*condition|if.*condition" --type js -C 2 | head -60

Repository: SynkraAI/aiox-core

Length of output: 1785


🏁 Script executed:

#!/bin/bash
# Read the entire condition-evaluator.js file to understand the class structure

echo "=== Reading condition-evaluator.js class definition (first 100 lines) ==="
head -100 ./.aiox-core/core/orchestration/condition-evaluator.js

echo ""
echo "=== Reading for public methods and setRuntimeConditions ==="
rg -n "^\s+(set|get|evaluate)" ./.aiox-core/core/orchestration/condition-evaluator.js

echo ""
echo "=== Checking export statements ==="
tail -50 ./.aiox-core/core/orchestration/condition-evaluator.js

Repository: SynkraAI/aiox-core

Length of output: 5249


🏁 Script executed:

#!/bin/bash
# Search for actual usage of conditionEvaluator and evaluate method calls

echo "=== Searching for evaluate method calls ==="
rg -n "\.evaluate\(" --type js -B 2 -A 2

echo ""
echo "=== Searching for condition evaluation in orchestrator ==="
grep -n "should.*Execute\|evaluate.*Phase\|stories_remaining" ./.aiox-core/core/orchestration/workflow-orchestrator.js -C 3

echo ""
echo "=== Checking if stories_remaining is referenced in phase configs ==="
rg -n "stories_remaining\|loop.*condition" --type js --type json --type yaml -C 2

Repository: SynkraAI/aiox-core

Length of output: 7274


🏁 Script executed:

#!/bin/bash
# Check phase definitions and configurations

echo "=== Searching for phase definitions with condition properties ==="
rg -n "phase.*condition\|condition.*stories\|condition.*epic_complete" --type js --type json -B 2 -A 2

echo ""
echo "=== Checking orchestrator for phase configuration loading ==="
grep -n "phases\|getPhases\|phaseConfig" ./.aiox-core/core/orchestration/workflow-orchestrator.js -C 2 | head -40

echo ""
echo "=== Looking for any YAML/JSON phase definitions ==="
find ./.aiox-core -name "*.yaml" -o -name "*.yml" -o -name "*.json" | xargs grep -l "phase\|condition" 2>/dev/null | head -10

echo ""
echo "=== Checking if setRuntimeConditions is ever called (comprehensive search) ==="
git grep -n "setRuntimeCondition" 2>/dev/null || rg -r "setRuntimeCondition" . --type js --type json --type yaml

Repository: SynkraAI/aiox-core

Length of output: 2806


🏁 Script executed:

#!/bin/bash
# Check actual workflow structure

echo "=== Reading a sample workflow definition ==="
head -100 ./.aiox-core/development/workflows/brownfield-discovery.yaml

echo ""
echo "=== Checking for condition properties in workflows ==="
grep -n "condition:" ./.aiox-core/development/workflows/*.yaml ./.aiox-core/development/templates/squad/workflow-template.yaml

echo ""
echo "=== Checking bob surface criteria for phase conditions ==="
head -80 ./.aiox-core/core/orchestration/bob-surface-criteria.yaml

Repository: SynkraAI/aiox-core

Length of output: 12452


Integrate runtime condition setting into orchestrator to enable loop termination.

The stories_remaining condition is actively used across multiple workflows (brownfield-fullstack.yaml:226, greenfield-fullstack.yaml:246, etc.) as a loop guard. However, setRuntimeConditions() is never called anywhere in the codebase, meaning stories_remaining will always evaluate to its default value of true, preventing loop termination even when the orchestrator detects an empty backlog.

To fix this, the orchestrator must call setRuntimeConditions({stories_remaining: false}) when the story backlog is exhausted. The same applies to other loop-termination conditions (epic_complete) and user-driven conditions that are documented in condition-evaluator.js but are currently non-functional due to missing orchestrator integration.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.aiox-core/core/orchestration/condition-evaluator.js around lines 143 - 146,
The orchestrator currently never invokes setRuntimeConditions, so the condition
function stories_remaining in condition-evaluator.js always returns the default
true; update the orchestrator (where it detects the story backlog exhaustion) to
call setRuntimeConditions({ stories_remaining: false }) so loop guards can
terminate; likewise set other documented termination flags (e.g., epic_complete)
via setRuntimeConditions when their corresponding orchestrator state changes so
condition-evaluator's runtime overrides are honored.

epic_complete: () =>
this._runtimeOverrides['epic_complete'] !== undefined
? this._runtimeOverrides['epic_complete']
: true, // Optional epic retrospective step
architecture_suggests_prd_changes: () =>
this._runtimeOverrides['architecture_suggests_prd_changes'] !== undefined
? this._runtimeOverrides['architecture_suggests_prd_changes']
: true, // Optional PRD update after architecture review
po_checklist_issues: () =>
this._runtimeOverrides['po_checklist_issues'] !== undefined
? this._runtimeOverrides['po_checklist_issues']
: true, // Optional fix delegation when PO finds issues
user_has_generated_ui: () =>
this._runtimeOverrides['user_has_generated_ui'] !== undefined
? this._runtimeOverrides['user_has_generated_ui']
: true, // Optional project-setup guidance for generated UI
};

// Check for built-in evaluator
Expand Down Expand Up @@ -145,9 +208,9 @@ class ConditionEvaluator {
return this._evaluateDotNotation(condition);
}

// Unknown condition - default to true (permissive)
// Unknown condition - default to false (fail-safe)
console.warn(`[ConditionEvaluator] Unknown condition: ${condition}`);
return true;
return false;
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}

/**
Expand Down Expand Up @@ -336,6 +399,13 @@ class ConditionEvaluator {
project_has_backend: 'No backend framework detected',
supabase_configured: 'Supabase not configured or missing environment variables',
qa_review_approved: 'QA review not yet approved',
// Runtime-resolved conditions
user_wants_ai_generation: 'Orchestrator signalled: user declined AI UI generation',
stories_remaining: 'Orchestrator signalled: story backlog is empty',
epic_complete: 'Orchestrator signalled: epic is not yet complete',
architecture_suggests_prd_changes: 'Orchestrator signalled: no PRD changes suggested by architecture review',
po_checklist_issues: 'Orchestrator signalled: PO checklist passed with no issues',
user_has_generated_ui: 'Orchestrator signalled: no generated UI present in project',
};

const reasons = failed.map((c) => explanations[c] || `Condition not met: ${c}`);
Expand Down
Loading
Loading