mirror of
https://github.com/ruvnet/ruflo.git
synced 2026-09-14 14:01:28 +08:00
fix(routing): stale route cache + --explore false (3.10.8) (#2229)
* feat(workflows): add native Workflow JS orchestration surface (ADR-0002) ruflo-workflows now documents both workflow surfaces: the existing 10 workflow_* MCP tools and the native Claude Code Workflow JS capability (.claude/workflows/*.js — agent/parallel/pipeline/phase fan-out). - ADR-0002 (Accepted): adopts native orchestration alongside the MCP surface - Reference workflow .claude/workflows/plugin-contract-audit.js (fans smoke contracts across all plugins, diagnoses failures in parallel) - README: native orchestration section + four-hook API + surface decision table - workflow-create/workflow-run skills, workflow-specialist agent, /workflow command made surface-aware - plugin.json 0.3.0 -> 0.4.0; native-workflow keywords + component block - smoke.sh reconciled (stale version + ADR-0001 status) and extended 11 -> 15 checks; smoke-gaia.sh version assertion bumped to 0.4.0 Co-Authored-By: RuFlo <ruv@ruv.net> * fix(routing): #bugB stale route cache + #bugC --explore false (3.10.8) Two routing-learning correctness bugs from the intelligence audit's remaining punch-list (docs/reviews/intelligence-system-audit-2026-05-29.md §Remediation). - Bug B (stale route cache): QLearningRouter.update() only invalidated the whole route cache every 50 updates, so a freshly-learned Q-update stayed hidden behind a stale cached decision — feedback appeared to have no effect on routing in-process until 50 updates accumulated. Now update() invalidates the updated state's cache entry immediately (new invalidateCacheEntry). Verified: learned route flips coder→researcher within 10 updates (was 50+). - Bug C (--explore false ignored): boolean flags dropped an explicit space-form value, forcing a default-true boolean (explore) to true even with , so exploitation could never be forced. parser.ts now consumes a true/false literal for boolean flags (--explore false / -e false), while --explore=false and --no-explore keep working. Verified deterministic. +4 regression tests (15/15 bug-cluster pass); 52/52 parser tests pass; cli build clean. Audit doc updated with full remediation status (3.10.7 + 3.10.8 shipped; SONA-default/MicroLoRA/EWC-Fisher/per-task-bandit deferred with honest rationale — the latter two need an ADR/upstream fix, not a patch). Co-Authored-By: RuFlo <ruv@ruv.net>
This commit is contained in:
@@ -0,0 +1,91 @@
|
||||
export const meta = {
|
||||
name: 'plugin-contract-audit',
|
||||
description: 'Run every ruflo plugin smoke contract, fan diagnosis agents out over the failures, and report a punch list',
|
||||
phases: [
|
||||
{ title: 'Sweep', detail: 'run all plugins/*/scripts/smoke.sh, collect pass/fail' },
|
||||
{ title: 'Diagnose', detail: 'one agent per failing plugin — root cause + minimal fix' },
|
||||
{ title: 'Report', detail: 'assemble the audit summary' },
|
||||
],
|
||||
}
|
||||
|
||||
// args (all optional):
|
||||
// string → only audit plugins whose name contains this substring
|
||||
// { filter?: string, → same substring filter
|
||||
// diagnose?: boolean } → set false to skip the Diagnose phase (sweep only)
|
||||
const opts = typeof args === 'string' ? { filter: args } : (args || {})
|
||||
const FILTER = opts.filter || ''
|
||||
const DIAGNOSE = opts.diagnose !== false
|
||||
|
||||
const SWEEP_SCHEMA = {
|
||||
type: 'object', additionalProperties: false,
|
||||
required: ['results'],
|
||||
properties: {
|
||||
results: {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'object', additionalProperties: false,
|
||||
required: ['plugin', 'passed', 'failed'],
|
||||
properties: {
|
||||
plugin: { type: 'string' },
|
||||
passed: { type: 'integer' },
|
||||
failed: { type: 'integer' },
|
||||
exitCode: { type: 'integer' },
|
||||
failingChecks: { type: 'array', items: { type: 'string' } },
|
||||
},
|
||||
},
|
||||
},
|
||||
notes: { type: 'string' },
|
||||
},
|
||||
}
|
||||
|
||||
const DIAGNOSIS_SCHEMA = {
|
||||
type: 'object', additionalProperties: false,
|
||||
required: ['plugin', 'rootCause', 'proposedFix', 'confident'],
|
||||
properties: {
|
||||
plugin: { type: 'string' },
|
||||
rootCause: { type: 'string' },
|
||||
proposedFix: { type: 'string' },
|
||||
files: { type: 'array', items: { type: 'string' } },
|
||||
confident: { type: 'boolean' },
|
||||
},
|
||||
}
|
||||
|
||||
phase('Sweep')
|
||||
const filterClause = FILTER
|
||||
? `Only audit plugins whose directory name contains "${FILTER}". `
|
||||
: ''
|
||||
const sweep = await agent(
|
||||
`From the repo root, audit every ruflo plugin's smoke contract. ${filterClause}For each script matching the glob plugins/*/scripts/smoke.sh, run it with bash and capture its output and exit code. Each smoke script prints a trailing "N passed, M failed" line.
|
||||
For every plugin report: plugin (the directory name under plugins/), passed (integer), failed (integer), exitCode (integer), and failingChecks (the "→ ..." lines that printed FAIL, verbatim, empty array if none).
|
||||
Do NOT modify any files — this is read/run only. Return every audited plugin via the schema, not just the failing ones.`,
|
||||
{ label: 'sweep:all-smokes', phase: 'Sweep', schema: SWEEP_SCHEMA, agentType: 'tester' }
|
||||
)
|
||||
|
||||
const results = (sweep?.results || []).filter((r) => !FILTER || r.plugin.includes(FILTER))
|
||||
const failures = results.filter((r) => r.failed > 0 || (r.exitCode && r.exitCode !== 0))
|
||||
log(`Sweep: ${results.length} plugins audited, ${failures.length} failing`)
|
||||
|
||||
let diagnoses = []
|
||||
if (DIAGNOSE && failures.length) {
|
||||
phase('Diagnose')
|
||||
diagnoses = (await parallel(
|
||||
failures.map((f) => () =>
|
||||
agent(
|
||||
`Plugin "${f.plugin}" fails its smoke contract (plugins/${f.plugin}/scripts/smoke.sh): ${f.failed} check(s) failed. Failing checks:\n${(f.failingChecks || []).join('\n') || '(not captured — re-run the smoke script to see them)'}\n\nRead plugins/${f.plugin}/scripts/smoke.sh and the plugin files it inspects (plugin.json, README.md, skills, agents, commands, docs/adrs). Determine the ROOT CAUSE of each failing check and propose a MINIMAL fix. Distinguish a stale assertion in smoke.sh (the contract drifted from reality) from a genuine plugin defect. Do NOT edit anything — report only, via the schema, with confident=true only if the root cause is unambiguous.`,
|
||||
{ label: `diagnose:${f.plugin}`, phase: 'Diagnose', schema: DIAGNOSIS_SCHEMA, agentType: 'code-analyzer' }
|
||||
)
|
||||
)
|
||||
)).filter(Boolean)
|
||||
log(`Diagnose: ${diagnoses.length}/${failures.length} diagnosed`)
|
||||
}
|
||||
|
||||
phase('Report')
|
||||
const summary = {
|
||||
audited: results.length,
|
||||
passing: results.length - failures.length,
|
||||
failing: failures.length,
|
||||
failingPlugins: failures.map((f) => ({ plugin: f.plugin, failed: f.failed })),
|
||||
diagnoses,
|
||||
}
|
||||
log(`Report: ${summary.passing}/${summary.audited} plugins pass their contract`)
|
||||
return summary
|
||||
@@ -87,3 +87,26 @@ What does **not** hold up is the **performance-multiplier marketing**: the HNSW
|
||||
5. Wire or remove the inert pieces (#4–#7) so named capabilities are either real or not advertised.
|
||||
|
||||
*Per-subsystem raw evidence is preserved in the audit run; load-bearing file:line references are inline above.*
|
||||
|
||||
---
|
||||
|
||||
## Remediation status (updated 2026-05-29)
|
||||
|
||||
### Shipped in v3.10.7
|
||||
- ✅ **#1 negative-reward inversion** — fixed in `parser.ts` (negative numeric literals accepted as flag values). Verified in the published artifact.
|
||||
- ✅ **#2 Flash Attention fabrication** — randomized telemetry removed from both `attention-coordinator` copies (unmeasured sentinel + "unverified" labels).
|
||||
- ✅ **#3 embedding observability** — `generateEmbedding` returns `backend: onnx|mock`, surfaced in `memory_bridge_status`/`import`.
|
||||
- ✅ **#4/#5 MCP learning** — `trajectory-end` no longer feeds EWC a synthetic gradient; `hooks_intelligence_learn` runs a real cycle.
|
||||
- ✅ **HNSW optimization** — root-caused the silent brute-force fallback (no `storagePath` → native DB lock → silent `catch{}`); fixed with unique storagePath + `hnswConfig {m:32, efC:200}` + a visible fallback warning. Measured 0.92×→3.2–4.7× at N=5k, 0.95×→1.89× at N=20k.
|
||||
- ✅ Perf docs rewritten to measured values; `scripts/benchmark-intelligence.mjs` added.
|
||||
|
||||
### Shipped in v3.10.8
|
||||
- ✅ **#10 Bug B (stale route cache)** — `update()` now invalidates the updated state's cache entry immediately (was: whole cache only every 50 updates, hiding learning in-process). Verified: learned route changes within 10 updates.
|
||||
- ✅ **#10 Bug C (`--explore false` ignored)** — the parser now consumes an explicit `true`/`false` value for boolean flags in the space form, so a default-true boolean can be disabled. Verified deterministic exploitation with `explore=false`.
|
||||
|
||||
### Deferred — with honest rationale (NOT fixed)
|
||||
- **SONA "default-path adapt is a stub"** — re-examined: the default intelligence path's pattern-confidence learning runs through `LocalSonaCoordinator`, which IS real and was confirmed working end-to-end (confidence 0.906→1.0). The inert piece is the *supplementary* `@ruvector/ruvllm` `SonaCoordinator` forward, which is not load-bearing for the confirmed learning. The audit slightly overstated this as a default-path gap; wiring the WASM SONA into the default path is an *enhancement*, not a bug fix, and is left for a dedicated change.
|
||||
- **WASM MicroLoRA `apply()` inert** — lives in the `@ruvector/ruvllm`/`-wasm` **published dependency**, not ruflo source; cannot be fixed by editing a node_module. Requires an upstream fix or a deliberate route-around (use the real JS `LoraAdapter` path). Tracked, not shipped here.
|
||||
- **EWC++ "Fisher information" is a proxy** (`|w|`/`embedding²`, not gradient curvature) — functional regularizer; relabeling vs. real gradient-Fisher is a design decision, deferred.
|
||||
- **Bandit priors global-per-model, not per-task** — making them per-task changes the **persisted state schema** (`priors` → per-task-bucket map), so it needs an ADR + migration, not a patch. Deferred to a dedicated change.
|
||||
- **Embedding ONNX broken without native `sharp`** — now *observable* (3.10.7 `backend:mock`); a sharp-free transformers path / bundled binary is the real fix, deferred.
|
||||
|
||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "claude-flow",
|
||||
"version": "3.10.7",
|
||||
"version": "3.10.8",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "claude-flow",
|
||||
"version": "3.10.7",
|
||||
"version": "3.10.8",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@claude-flow/cli-core": "^3.7.0-alpha.5",
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "claude-flow",
|
||||
"version": "3.10.7",
|
||||
"version": "3.10.8",
|
||||
"description": "Ruflo - Enterprise AI agent orchestration for Claude Code. Deploy 60+ specialized agents in coordinated swarms with self-learning, fault-tolerant consensus, vector memory, and MCP integration",
|
||||
"main": "dist/index.js",
|
||||
"type": "module",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "ruflo-workflows",
|
||||
"description": "Workflow automation with templates, orchestration, and lifecycle management — wraps 10 workflow_* MCP tools (create/run/execute/status/list/pause/resume/cancel/delete/template) with full state-machine lifecycle (created → running ↔ paused → completed/cancelled). Includes GAIA benchmark component for Princeton HAL leaderboard submissions.",
|
||||
"version": "0.3.0",
|
||||
"description": "Workflow automation across two surfaces: the 10 workflow_* MCP tools (create/run/execute/status/list/pause/resume/cancel/delete/template) with full state-machine lifecycle (created → running ↔ paused → completed/cancelled), and native Claude Code Workflow JS orchestration (.claude/workflows/*.js — agent/parallel/pipeline/phase fan-out). Includes GAIA benchmark component for Princeton HAL leaderboard submissions.",
|
||||
"version": "0.4.0",
|
||||
"author": {
|
||||
"name": "ruvnet",
|
||||
"url": "https://github.com/ruvnet"
|
||||
@@ -18,6 +18,10 @@
|
||||
"workflow-templates",
|
||||
"pause-resume",
|
||||
"lifecycle",
|
||||
"native-workflow",
|
||||
"agent-fanout",
|
||||
"pipeline",
|
||||
"parallel",
|
||||
"gaia",
|
||||
"benchmark",
|
||||
"hal-leaderboard",
|
||||
@@ -53,6 +57,14 @@
|
||||
"gaia-patterns",
|
||||
"gaia-debug-patterns"
|
||||
]
|
||||
},
|
||||
"native_workflows": {
|
||||
"description": "Native Claude Code Workflow JS orchestration — deterministic subagent fan-out via agent/parallel/pipeline/phase, authored as .claude/workflows/*.js named workflows",
|
||||
"location": ".claude/workflows/*.js",
|
||||
"api": ["agent", "parallel", "pipeline", "phase", "log"],
|
||||
"invocation": "Workflow tool — Workflow({ name }) | Workflow({ scriptPath }) | Workflow({ resumeFromRunId })",
|
||||
"reference_workflow": ".claude/workflows/plugin-contract-audit.js",
|
||||
"adrs": ["ADR-0002"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
# ruflo-workflows
|
||||
|
||||
Workflow automation with templates, orchestration, and full state-machine lifecycle management.
|
||||
Workflow automation across **two complementary surfaces**:
|
||||
|
||||
1. **MCP `workflow_*` tools** — declarative, persisted workflow definitions with a full state-machine lifecycle (create → run ↔ pause → complete/cancel). Best for long-lived, resumable, human-gated pipelines.
|
||||
2. **Native Claude Code `Workflow` JS** — imperative orchestration scripts (`.claude/workflows/*.js`) that fan subagents out deterministically via `agent` / `parallel` / `pipeline` / `phase`. Best for comprehensive fan-out: review, audit, migration, research.
|
||||
|
||||
Neither subsumes the other — see [Choosing a surface](#choosing-a-surface).
|
||||
|
||||
## Install
|
||||
|
||||
@@ -11,19 +16,19 @@ Workflow automation with templates, orchestration, and full state-machine lifecy
|
||||
|
||||
## Features
|
||||
|
||||
- **Workflow creation**: Define multi-step processes with conditions and parallel execution
|
||||
- **Templates**: Reusable workflow patterns for common operations
|
||||
- **Lifecycle management**: Execute, pause, resume, cancel running workflows
|
||||
- **Approval gates**: Manual pause points for human review
|
||||
- **MCP workflow definitions**: multi-step processes with conditions, parallel steps, and templates
|
||||
- **Lifecycle management**: execute, pause, resume, cancel running workflows
|
||||
- **Approval gates**: manual pause points for human review
|
||||
- **Native orchestration**: author `.claude/workflows/*.js` fan-out/pipeline scripts and run them with the `Workflow` tool
|
||||
|
||||
## Commands
|
||||
|
||||
- `/workflow` -- List workflows, check status, view templates
|
||||
- `/workflow` -- List MCP workflows + templates **and** native `.claude/workflows/*.js` scripts
|
||||
|
||||
## Skills
|
||||
|
||||
- `workflow-create` -- Create reusable workflow templates
|
||||
- `workflow-run` -- Execute and manage running workflows
|
||||
- `workflow-create` -- Author MCP workflow templates **or** native `.claude/workflows/*.js` orchestration scripts
|
||||
- `workflow-run` -- Execute/manage MCP workflows **or** invoke + resume native workflows
|
||||
|
||||
## Compatibility
|
||||
|
||||
@@ -68,6 +73,56 @@ created ──run──→ running ──pause──→ paused ──resume─
|
||||
|
||||
`workflow_execute` is the **stateless** path — fire-and-forget, no persisted state machine.
|
||||
|
||||
## Native Workflow Orchestration (Claude Code `Workflow` tool)
|
||||
|
||||
The native surface runs a JavaScript orchestration script that fans subagents out deterministically. Scripts live in **`.claude/workflows/*.js`** and each begins with a **pure-literal** `export const meta` block — the `meta.name` makes it an invocable **named workflow**.
|
||||
|
||||
```js
|
||||
export const meta = {
|
||||
name: 'plugin-contract-audit',
|
||||
description: 'Run every plugin smoke contract, diagnose failures, report',
|
||||
phases: [{ title: 'Sweep' }, { title: 'Diagnose' }, { title: 'Report' }],
|
||||
}
|
||||
// body runs inside an async wrapper — top-level await + return are legal
|
||||
const sweep = await agent('run all plugins/*/scripts/smoke.sh ...', { schema: SWEEP_SCHEMA })
|
||||
const failures = (sweep?.results || []).filter((r) => r.failed > 0)
|
||||
const diagnoses = await parallel(failures.map((f) => () =>
|
||||
agent(`diagnose ${f.plugin}`, { schema: DIAGNOSIS_SCHEMA })))
|
||||
return { audited: sweep.results.length, failures, diagnoses }
|
||||
```
|
||||
|
||||
### The four-hook API
|
||||
|
||||
| Hook | Purpose |
|
||||
|------|---------|
|
||||
| `agent(prompt, opts)` | Spawn one subagent; with `opts.schema` returns validated structured output |
|
||||
| `parallel(thunks)` | Run thunks concurrently with a **barrier** (await all) |
|
||||
| `pipeline(items, ...stages)` | Run each item through all stages independently — **no barrier** |
|
||||
| `phase(title)` / `log(msg)` | Progress grouping and narration |
|
||||
|
||||
`meta` MUST be a pure literal (no variables, calls, or interpolation). Use `parallel` only when you genuinely need every result together; otherwise prefer `pipeline`.
|
||||
|
||||
### Invocation
|
||||
|
||||
```js
|
||||
Workflow({ name: 'plugin-contract-audit' }) // run a named .claude/workflows/*.js
|
||||
Workflow({ scriptPath: '.claude/workflows/foo.js' }) // run a script by path
|
||||
Workflow({ name: 'plugin-contract-audit', args: 'ruflo-agentdb' }) // pass args (the script's `args` global)
|
||||
Workflow({ scriptPath, resumeFromRunId: 'wf_…' }) // resume — unchanged agent() calls return cached
|
||||
```
|
||||
|
||||
The repo ships a reference workflow at `.claude/workflows/plugin-contract-audit.js` and a worked example at `.claude/workflows/intelligence-system-hardening.js`.
|
||||
|
||||
## Choosing a surface
|
||||
|
||||
| If you need… | Use |
|
||||
|--------------|-----|
|
||||
| A persisted definition that pauses for human approval and resumes across sessions | **MCP `workflow_*`** |
|
||||
| A stateful lifecycle (`created → running ↔ paused → completed/cancelled`) the engine schedules | **MCP `workflow_*`** |
|
||||
| Deterministic fan-out of many subagents (review N dimensions, audit N plugins, migrate N files) | **Native `Workflow` JS** |
|
||||
| Structured, schema-validated results aggregated in code | **Native `Workflow` JS** |
|
||||
| A one-shot stateless run | MCP `workflow_execute` **or** native `Workflow` |
|
||||
|
||||
## Namespace coordination
|
||||
|
||||
This plugin owns the `workflows-state` AgentDB namespace (kebab-case, follows the convention from [ruflo-agentdb ADR-0001 §"Namespace convention"](../ruflo-agentdb/docs/adrs/0001-agentdb-optimization.md)). Reserved namespaces (`pattern`, `claude-memories`, `default`) MUST NOT be shadowed.
|
||||
@@ -78,12 +133,13 @@ This plugin owns the `workflows-state` AgentDB namespace (kebab-case, follows th
|
||||
|
||||
```bash
|
||||
bash plugins/ruflo-workflows/scripts/smoke.sh
|
||||
# Expected: "11 passed, 0 failed"
|
||||
# Expected: "15 passed, 0 failed"
|
||||
```
|
||||
|
||||
## Architecture Decisions
|
||||
|
||||
- [`ADR-0001` — ruflo-workflows plugin contract (10-tool MCP surface, lifecycle state machine, smoke as contract)](./docs/adrs/0001-workflows-contract.md)
|
||||
- [`ADR-0002` — native Claude Code Workflow orchestration (`.claude/workflows/*.js` fan-out) alongside the MCP surface](./docs/adrs/0002-native-workflow-orchestration.md)
|
||||
|
||||
## Related Plugins
|
||||
|
||||
|
||||
@@ -4,7 +4,11 @@ description: Workflow automation specialist for creating, executing, and managin
|
||||
model: sonnet
|
||||
---
|
||||
|
||||
You are a workflow automation specialist for Ruflo's workflow engine. Your responsibilities:
|
||||
You are a workflow automation specialist for Ruflo. You work across **two surfaces** and pick the right one for each job.
|
||||
|
||||
## Surface 1 — MCP `workflow_*` (persisted, lifecycle)
|
||||
|
||||
For long-lived, resumable, human-gated pipelines with a state machine (created → running ↔ paused → completed/cancelled).
|
||||
|
||||
1. **Design workflows** with sequential, parallel, and conditional steps
|
||||
2. **Execute workflows** and monitor step-by-step progress
|
||||
@@ -21,6 +25,18 @@ Use these MCP tools:
|
||||
|
||||
Design workflows with clear failure paths and approval gates for critical steps.
|
||||
|
||||
## Surface 2 — Native `.claude/workflows/*.js` (deterministic fan-out)
|
||||
|
||||
For comprehensive subagent fan-out (review N dimensions, audit N targets, migrate N files, multi-source research) where results are aggregated in code.
|
||||
|
||||
- Author a `.js` file under `.claude/workflows/` starting with a **pure-literal** `export const meta = { name, description, phases }`. The body runs in an async wrapper with `agent` / `parallel` / `pipeline` / `phase` / `log` injected; pass `schema` to `agent()` for validated structured output. Default to `pipeline` over `parallel`. Never call `Date.now()`/`Math.random()` (they throw).
|
||||
- Invoke with the `Workflow` tool: `Workflow({ name })`, `Workflow({ scriptPath })`, `Workflow({ name, args })`, or `Workflow({ scriptPath, resumeFromRunId })`.
|
||||
- Reference implementation: `.claude/workflows/plugin-contract-audit.js`. Contract: [ADR-0002](../docs/adrs/0002-native-workflow-orchestration.md).
|
||||
|
||||
## Choosing a surface
|
||||
|
||||
Persisted definition that pauses for human approval and resumes across sessions → **MCP**. Deterministic parallel/pipeline subagent fan-out with code-side aggregation → **native JS**. One-shot stateless run → either.
|
||||
|
||||
### Memory Learning
|
||||
|
||||
Store successful workflow templates and execution patterns:
|
||||
|
||||
@@ -1,10 +1,20 @@
|
||||
---
|
||||
name: workflow
|
||||
description: Workflow management -- list workflows, check status, view templates
|
||||
description: Workflow management -- list MCP workflows + templates and native .claude/workflows/*.js scripts
|
||||
---
|
||||
|
||||
Manage workflows:
|
||||
Manage workflows across both surfaces:
|
||||
|
||||
## MCP workflows (persisted, lifecycle)
|
||||
|
||||
1. Call `mcp__claude-flow__workflow_list` to show all defined workflows
|
||||
2. Call `mcp__claude-flow__workflow_template` to show available templates
|
||||
3. Show workflow IDs, status (running/paused/completed), and step progress
|
||||
|
||||
## Native workflows (`.claude/workflows/*.js`)
|
||||
|
||||
4. List the native orchestration scripts: `ls .claude/workflows/*.js` (each file's `meta.name` is its invocable name)
|
||||
5. For each, read the `export const meta` block and show `name` + `description` + phase titles
|
||||
6. Run one with the `Workflow` tool — `Workflow({ name })` — or author a new one via the `workflow-create` skill
|
||||
|
||||
See ADR-0002 for when to use which surface.
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
---
|
||||
id: ADR-0002
|
||||
title: ruflo-workflows adopts native Claude Code Workflow orchestration (.claude/workflows/*.js) alongside the MCP workflow_* surface
|
||||
status: Accepted
|
||||
date: 2026-05-29
|
||||
authors:
|
||||
- coder (Claude Code)
|
||||
tags: [plugin, workflows, orchestration, native-workflow, claude-code, agent-fanout]
|
||||
---
|
||||
|
||||
## Context
|
||||
|
||||
[ADR-0001](./0001-workflows-contract.md) established `ruflo-workflows` as the canonical wrapper for the **10 `workflow_*` MCP tools** (`v3/@claude-flow/cli/src/mcp-tools/workflow-tools.ts`). That surface is *declarative and persisted*: a workflow definition is created, then run/paused/resumed/cancelled through a state machine, with state indexed in the `workflows-state` AgentDB namespace.
|
||||
|
||||
Claude Code has since shipped a second, complementary capability — the **native `Workflow` tool**. It executes a JavaScript orchestration script that fans subagents out deterministically via four hooks:
|
||||
|
||||
| Hook | Purpose |
|
||||
|------|---------|
|
||||
| `agent(prompt, opts)` | Spawn one subagent; with `opts.schema` it returns validated structured output |
|
||||
| `parallel(thunks)` | Run thunks concurrently with a barrier (await all) |
|
||||
| `pipeline(items, ...stages)` | Run each item through all stages independently, no barrier |
|
||||
| `phase(title)` / `log(msg)` | Progress grouping and narration |
|
||||
|
||||
These scripts live in `.claude/workflows/*.js`. Each begins with a pure-literal `export const meta = { name, description, phases }` block; the file's `meta.name` makes it an invocable **named workflow** (`Workflow({ name })`) that also surfaces in the skill/workflow list. The repo already contains one such script — `.claude/workflows/intelligence-system-hardening.js`.
|
||||
|
||||
The two surfaces solve different problems and neither subsumes the other:
|
||||
|
||||
| Dimension | MCP `workflow_*` (ADR-0001) | Native `Workflow` JS (this ADR) |
|
||||
|-----------|------------------------------|----------------------------------|
|
||||
| Form | Declarative definition + lifecycle state machine | Imperative JS orchestration script |
|
||||
| Unit of work | Persisted workflow steps | Subagents (`agent()`) |
|
||||
| Persistence | Stateful, resumable across sessions (`workflows-state`) | Per-run journal; resume via `resumeFromRunId` |
|
||||
| Concurrency | Engine-scheduled steps | `parallel()` barrier / `pipeline()` streaming |
|
||||
| Best for | Long-lived, pausable, human-gated pipelines | Comprehensive fan-out: review, audit, migration, research |
|
||||
| Location | AgentDB definitions | `.claude/workflows/*.js` |
|
||||
|
||||
Before this ADR the plugin documented only the MCP surface, so users had no in-plugin guidance for authoring or running the native scripts that the project is already accumulating.
|
||||
|
||||
## Decision
|
||||
|
||||
1. Add this ADR (Accepted). ADR-0001 remains Accepted and unchanged; this ADR **amends** it by adding a second surface, it does not supersede it.
|
||||
2. The plugin documents **both** surfaces. README gains a "Native Workflow Orchestration" section: the four-hook API, the pure-literal `meta` requirement, the `.claude/workflows/*.js` location/discovery rule, invocation (`Workflow({ name })` / `{ scriptPath }` / `{ resumeFromRunId }`), and a decision table for choosing MCP-vs-native.
|
||||
3. Skills extend to cover authoring and running native scripts:
|
||||
- `workflow-create` — how to author a `.claude/workflows/*.js` (meta block, hook API, schema-validated agents, `parallel` vs `pipeline`).
|
||||
- `workflow-run` — how to invoke a named native workflow and resume it, in addition to MCP run/pause/resume/cancel.
|
||||
4. The `workflow-specialist` agent and `/workflow` command become surface-aware: the command also lists `.claude/workflows/*.js`; the agent knows when to reach for native fan-out vs the MCP lifecycle engine.
|
||||
5. Ship a reference native workflow — `.claude/workflows/plugin-contract-audit.js` — that runs every `plugins/*/scripts/smoke.sh`, fans diagnosis agents out over the failures, and reports. It is the executable example of the new capability and is directly useful for the repo's "smoke as contract" discipline.
|
||||
6. Bump `0.3.0 → 0.4.0` (minor: additive capability). Keywords add `native-workflow`, `agent-fanout`, `pipeline`, `parallel`.
|
||||
7. `scripts/smoke.sh` is reconciled with current reality (version, ADR-0001 now Accepted) and extended with native-surface checks: ADR-0002 present + Accepted, README native section + four-hook API documented, `.claude/workflows/` location referenced.
|
||||
|
||||
## Consequences
|
||||
|
||||
**Positive:** the plugin now reflects the full workflow capability of the platform, not just the MCP slice. Authors get a documented, validated path to write fan-out/pipeline orchestrations, with a working reference script. The audit workflow turns the project's 32 plugin smoke contracts into a one-call parallel sweep.
|
||||
|
||||
**Negative:** two surfaces means contributors must pick the right one; the README decision table mitigates this. The native scripts are project-level (`.claude/workflows/`), not shipped inside the plugin package, so the plugin documents and exemplifies them rather than bundling them.
|
||||
|
||||
**Neutral:** the `workflows-state` namespace claim is unchanged and applies to the MCP surface only; native scripts persist via the per-run journal, not AgentDB.
|
||||
|
||||
## Verification
|
||||
|
||||
```bash
|
||||
# Plugin contract (documents both surfaces; stays inside the plugin boundary):
|
||||
bash plugins/ruflo-workflows/scripts/smoke.sh
|
||||
# Expected: "15 passed, 0 failed"
|
||||
```
|
||||
|
||||
The reference native workflow is project-level (`.claude/workflows/`), not part of the plugin
|
||||
package, so its syntax is validated separately rather than from the plugin smoke. A native
|
||||
workflow body runs inside an async wrapper (top-level `await`/`return` are legal), so it is
|
||||
checked as an async-wrapped ES module with `meta` kept as a module export:
|
||||
|
||||
```bash
|
||||
node -e 'const fs=require("fs");let s=fs.readFileSync(".claude/workflows/plugin-contract-audit.js","utf8").replace(/^export\s+const\s+meta/m,"const meta");fs.writeFileSync("/tmp/wf.mjs","let agent,parallel,pipeline,phase,log,args,budget,workflow;async function __wf(){\n"+s+"\n}")' \
|
||||
&& node --check /tmp/wf.mjs && echo OK
|
||||
```
|
||||
|
||||
## Related
|
||||
|
||||
- [`0001-workflows-contract.md`](./0001-workflows-contract.md) — the MCP `workflow_*` contract this ADR amends
|
||||
- `.claude/workflows/intelligence-system-hardening.js` — first native workflow in the repo
|
||||
- `.claude/workflows/plugin-contract-audit.js` — reference native workflow shipped with this ADR
|
||||
- `plugins/ruflo-loop-workers/docs/adrs/0001-loop-workers-contract.md` — sibling automation surface (recurring loops)
|
||||
- `plugins/ruflo-sparc/docs/adrs/0001-sparc-contract.md` — SPARC phase transitions as workflows
|
||||
|
||||
## Implementation status
|
||||
|
||||
ADR-0002 accepted. Native-workflow documentation added to README + both skills + agent + command; plugin bumped to v0.4.0; reference workflow `.claude/workflows/plugin-contract-audit.js` authored and syntax-validated; smoke gate extended to 15 checks covering both surfaces.
|
||||
@@ -11,9 +11,9 @@ bad() { printf "FAIL: %s\n" "$1"; FAIL=$((FAIL+1)); }
|
||||
|
||||
# ── plugin.json ──────────────────────────────────────────────────────────────
|
||||
|
||||
step "1. plugin.json bumped to 0.3.0 with gaia keywords"
|
||||
step "1. plugin.json at 0.4.0 with gaia keywords"
|
||||
v=$(grep -E '"version"' "$ROOT/.claude-plugin/plugin.json" | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1)
|
||||
if [[ "$v" != "0.3.0" ]]; then bad "expected 0.3.0, got '$v'"; else
|
||||
if [[ "$v" != "0.4.0" ]]; then bad "expected 0.4.0, got '$v'"; else
|
||||
miss=""
|
||||
for k in gaia benchmark hal-leaderboard evaluation; do
|
||||
grep -q "\"$k\"" "$ROOT/.claude-plugin/plugin.json" || miss="$miss $k"
|
||||
@@ -118,7 +118,7 @@ grep -qE 'SWE-bench|WebArena|HumanEval|extensib' "$ROOT/skills/gaia-submission/S
|
||||
|
||||
# ── original smoke test still passes ─────────────────────────────────────────
|
||||
|
||||
step "13. original plugin smoke (0.3.0 is not 0.2.0 but other checks still valid)"
|
||||
step "13. core plugin artifacts intact (skills + agent + command)"
|
||||
# Re-run a subset: skills + agent + command for pre-existing artifacts
|
||||
miss=""
|
||||
for s in workflow-create workflow-run; do
|
||||
|
||||
@@ -7,9 +7,9 @@ step() { printf "→ %s ... " "$1"; }
|
||||
ok() { printf "PASS\n"; PASS=$((PASS+1)); }
|
||||
bad() { printf "FAIL: %s\n" "$1"; FAIL=$((FAIL+1)); }
|
||||
|
||||
step "1. plugin.json declares 0.2.0 with new keywords"
|
||||
step "1. plugin.json declares 0.4.0 with lifecycle keywords"
|
||||
v=$(grep -E '"version"' "$ROOT/.claude-plugin/plugin.json" | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1)
|
||||
if [[ "$v" != "0.2.0" ]]; then bad "expected 0.2.0, got '$v'"; else
|
||||
if [[ "$v" != "0.4.0" ]]; then bad "expected 0.4.0, got '$v'"; else
|
||||
miss=""
|
||||
for k in mcp workflow-templates pause-resume lifecycle; do
|
||||
grep -q "\"$k\"" "$ROOT/.claude-plugin/plugin.json" || miss="$miss $k"
|
||||
@@ -72,10 +72,10 @@ grep -q "workflow_execute" "$F" \
|
||||
&& grep -qE "stateless|one-shot|fire-and-forget" "$F" \
|
||||
&& ok || bad "stateless path not documented"
|
||||
|
||||
step "10. ADR-0001 exists with status Proposed"
|
||||
step "10. ADR-0001 exists with status Accepted"
|
||||
ADR="$ROOT/docs/adrs/0001-workflows-contract.md"
|
||||
[[ -f "$ADR" ]] && grep -qE "^status:[[:space:]]*Proposed" "$ADR" \
|
||||
&& ok || bad "ADR missing or status != Proposed"
|
||||
[[ -f "$ADR" ]] && grep -qE "^status:[[:space:]]*Accepted" "$ADR" \
|
||||
&& ok || bad "ADR-0001 missing or status != Accepted"
|
||||
|
||||
step "11. no wildcard tool grants in skills"
|
||||
bad_skills=""
|
||||
@@ -84,5 +84,32 @@ for f in "$ROOT"/skills/*/SKILL.md; do
|
||||
done
|
||||
[[ -z "$bad_skills" ]] && ok || bad "wildcard:$bad_skills"
|
||||
|
||||
step "12. ADR-0002 exists with status Accepted"
|
||||
ADR2="$ROOT/docs/adrs/0002-native-workflow-orchestration.md"
|
||||
[[ -f "$ADR2" ]] && grep -qE "^status:[[:space:]]*Accepted" "$ADR2" \
|
||||
&& ok || bad "ADR-0002 missing or status != Accepted"
|
||||
|
||||
step "13. README documents native workflow orchestration"
|
||||
F="$ROOT/README.md"
|
||||
grep -q "Native Workflow Orchestration" "$F" \
|
||||
&& grep -qF ".claude/workflows" "$F" \
|
||||
&& grep -qF "Workflow({" "$F" \
|
||||
&& ok || bad "native orchestration section incomplete"
|
||||
|
||||
step "14. four-hook API documented (agent/parallel/pipeline/phase)"
|
||||
F="$ROOT/README.md"
|
||||
miss=""
|
||||
for hook in 'agent(' 'parallel(' 'pipeline(' 'phase('; do
|
||||
grep -qF "$hook" "$F" || miss="$miss $hook"
|
||||
done
|
||||
[[ -z "$miss" ]] && ok || bad "missing hooks:$miss"
|
||||
|
||||
step "15. plugin.json declares native-workflow keywords"
|
||||
miss=""
|
||||
for k in native-workflow agent-fanout pipeline parallel; do
|
||||
grep -q "\"$k\"" "$ROOT/.claude-plugin/plugin.json" || miss="$miss $k"
|
||||
done
|
||||
[[ -z "$miss" ]] && ok || bad "missing native keywords:$miss"
|
||||
|
||||
printf "\n%s passed, %s failed\n" "$PASS" "$FAIL"
|
||||
[[ $FAIL -eq 0 ]] || exit 1
|
||||
|
||||
@@ -1,29 +1,60 @@
|
||||
---
|
||||
name: workflow-create
|
||||
description: Create reusable workflow templates with steps, conditions, and parallel execution
|
||||
argument-hint: "<name> [--steps N]"
|
||||
allowed-tools: mcp__claude-flow__workflow_create mcp__claude-flow__workflow_template mcp__claude-flow__workflow_list mcp__claude-flow__workflow_status mcp__claude-flow__workflow_delete Bash
|
||||
description: Author a workflow — either an MCP workflow template (persisted, lifecycle) or a native .claude/workflows/*.js orchestration script (agent/parallel/pipeline fan-out)
|
||||
argument-hint: "<name> [--native] [--steps N]"
|
||||
allowed-tools: mcp__claude-flow__workflow_create mcp__claude-flow__workflow_template mcp__claude-flow__workflow_list mcp__claude-flow__workflow_status mcp__claude-flow__workflow_delete Write Read Edit Bash
|
||||
---
|
||||
|
||||
# Workflow Create
|
||||
|
||||
Create reusable workflow templates for automated task execution.
|
||||
Author a workflow on whichever surface fits the job.
|
||||
|
||||
## When to use
|
||||
## Pick a surface
|
||||
|
||||
When you have a repeatable multi-step process (CI/CD, onboarding, release, review) that should be codified as a workflow.
|
||||
- **MCP workflow template** — a persisted definition with a pause/resume lifecycle. Use for long-lived, human-gated, resumable pipelines.
|
||||
- **Native `.claude/workflows/*.js`** — an imperative orchestration script that fans subagents out. Use for comprehensive fan-out (review, audit, migration, research) where you aggregate structured results in code.
|
||||
|
||||
## Steps
|
||||
## A — MCP workflow template
|
||||
|
||||
1. **List templates** — call `mcp__claude-flow__workflow_template` to see available workflow templates
|
||||
1. **List templates** — call `mcp__claude-flow__workflow_template` to see available templates
|
||||
2. **Create workflow** — call `mcp__claude-flow__workflow_create` with steps, conditions, and execution order
|
||||
3. **List workflows** — call `mcp__claude-flow__workflow_list` to see all defined workflows
|
||||
4. **Check status** — call `mcp__claude-flow__workflow_status` to monitor a workflow
|
||||
5. **Clean up** — call `mcp__claude-flow__workflow_delete` to remove unused workflows
|
||||
|
||||
## Workflow features
|
||||
Features: sequential/parallel steps, conditional branching, template inheritance, pause/resume approval gates.
|
||||
|
||||
- Sequential and parallel step execution
|
||||
- Conditional branching based on step outcomes
|
||||
- Template inheritance for common patterns
|
||||
- Pause/resume for manual approval gates
|
||||
## B — Native `.claude/workflows/*.js`
|
||||
|
||||
Write a `.js` file under `.claude/workflows/`. It MUST begin with a **pure-literal** `export const meta` block; the body runs inside an async wrapper (top-level `await`/`return` are legal) with these hooks injected:
|
||||
|
||||
| Hook | Purpose |
|
||||
|------|---------|
|
||||
| `agent(prompt, opts)` | Spawn one subagent; pass `opts.schema` (JSON Schema) to get validated structured output back |
|
||||
| `parallel(thunks)` | Run thunks concurrently with a **barrier** — `.filter(Boolean)` the results |
|
||||
| `pipeline(items, ...stages)` | Stream each item through stages independently — **prefer this** over a barrier |
|
||||
| `phase(title)` / `log(msg)` | Progress grouping / narration |
|
||||
|
||||
```js
|
||||
export const meta = {
|
||||
name: 'my-workflow', // becomes the invocable name — must be a pure literal
|
||||
description: 'one line',
|
||||
phases: [{ title: 'Find' }, { title: 'Verify' }],
|
||||
}
|
||||
const SCHEMA = { type: 'object', required: ['ok'], properties: { ok: { type: 'boolean' } }, additionalProperties: false }
|
||||
phase('Find')
|
||||
const found = await agent('find the things', { schema: SCHEMA, agentType: 'tester' })
|
||||
phase('Verify')
|
||||
const checked = await parallel((found.items || []).map((it) => () =>
|
||||
agent(`verify ${it}`, { schema: SCHEMA })))
|
||||
return { found, checked: checked.filter(Boolean) }
|
||||
```
|
||||
|
||||
Rules: `meta` is a pure literal (no variables/calls/interpolation); default to `pipeline` over `parallel`; never use `Date.now()`/`Math.random()` (they throw — vary by index instead). Validate syntax (the body is ESM-in-async-wrapper, not a bare module):
|
||||
|
||||
```bash
|
||||
node -e 'const fs=require("fs");let s=fs.readFileSync(".claude/workflows/my-workflow.js","utf8").replace(/^export\s+const\s+meta/m,"const meta");fs.writeFileSync("/tmp/wf.mjs","let agent,parallel,pipeline,phase,log,args,budget,workflow;async function __wf(){\n"+s+"\n}")' \
|
||||
&& node --check /tmp/wf.mjs && echo OK
|
||||
```
|
||||
|
||||
Run it with the `workflow-run` skill or `Workflow({ name: 'my-workflow' })`. Reference: `.claude/workflows/plugin-contract-audit.js`. See [ADR-0002](../../docs/adrs/0002-native-workflow-orchestration.md).
|
||||
|
||||
@@ -1,29 +1,40 @@
|
||||
---
|
||||
name: workflow-run
|
||||
description: Execute, pause, resume, and cancel running workflows
|
||||
argument-hint: "<workflow-id>"
|
||||
allowed-tools: mcp__claude-flow__workflow_execute mcp__claude-flow__workflow_run mcp__claude-flow__workflow_pause mcp__claude-flow__workflow_resume mcp__claude-flow__workflow_cancel mcp__claude-flow__workflow_status Bash
|
||||
description: Run a workflow — drive an MCP workflow lifecycle (execute/pause/resume/cancel) or invoke + resume a native .claude/workflows/*.js orchestration via the Workflow tool
|
||||
argument-hint: "<workflow-id-or-name>"
|
||||
allowed-tools: mcp__claude-flow__workflow_execute mcp__claude-flow__workflow_run mcp__claude-flow__workflow_pause mcp__claude-flow__workflow_resume mcp__claude-flow__workflow_cancel mcp__claude-flow__workflow_status Workflow Read Bash
|
||||
---
|
||||
|
||||
# Workflow Run
|
||||
|
||||
Execute and manage running workflows.
|
||||
Run and manage a workflow on either surface.
|
||||
|
||||
## When to use
|
||||
## A — MCP workflow lifecycle
|
||||
|
||||
When you need to run a defined workflow, monitor its progress, or control its execution (pause, resume, cancel).
|
||||
|
||||
## Steps
|
||||
When you need to run a persisted definition and control its lifecycle (pause/resume/cancel):
|
||||
|
||||
1. **Execute** — call `mcp__claude-flow__workflow_execute` or `mcp__claude-flow__workflow_run` with the workflow ID
|
||||
2. **Monitor** — call `mcp__claude-flow__workflow_status` to check progress and step outcomes
|
||||
3. **Pause** — call `mcp__claude-flow__workflow_pause` to halt execution at the current step
|
||||
3. **Pause** — call `mcp__claude-flow__workflow_pause` to halt at the current step
|
||||
4. **Resume** — call `mcp__claude-flow__workflow_resume` to continue from where paused
|
||||
5. **Cancel** — call `mcp__claude-flow__workflow_cancel` to abort the workflow
|
||||
|
||||
## Execution modes
|
||||
Execution modes: **sequential**, **parallel** (independent steps), **conditional** (branch on outcome), **manual gate** (pause for human approval).
|
||||
|
||||
- **Sequential** — steps run one after another
|
||||
- **Parallel** — independent steps run concurrently
|
||||
- **Conditional** — steps execute based on previous step outcomes
|
||||
- **Manual gate** — pause for human approval before continuing
|
||||
## B — Native `.claude/workflows/*.js`
|
||||
|
||||
When you need a deterministic subagent fan-out, run a named native workflow with the `Workflow` tool. The named workflows are the `meta.name` of each `.claude/workflows/*.js` file (list them with `/workflow` or `ls .claude/workflows/`).
|
||||
|
||||
```js
|
||||
Workflow({ name: 'plugin-contract-audit' }) // run a named workflow
|
||||
Workflow({ name: 'plugin-contract-audit', args: 'ruflo-agentdb' }) // pass args → the script's `args` global
|
||||
Workflow({ scriptPath: '.claude/workflows/foo.js' }) // run a script by path
|
||||
Workflow({ scriptPath, resumeFromRunId: 'wf_…' }) // resume — unchanged agent() calls return cached
|
||||
```
|
||||
|
||||
Notes:
|
||||
- A native workflow runs in the background; you are notified on completion (don't poll). Watch live progress with `/workflows`.
|
||||
- Pause/resume here is **journal-based** (`resumeFromRunId`), not the MCP state machine. Stop a run first, then resume from its `runId`.
|
||||
- To author a new native workflow, use the `workflow-create` skill.
|
||||
|
||||
See [ADR-0002](../../docs/adrs/0002-native-workflow-orchestration.md).
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "ruflo",
|
||||
"version": "3.10.7",
|
||||
"version": "3.10.8",
|
||||
"description": "Ruflo - Enterprise AI agent orchestration platform. Deploy 60+ specialized agents in coordinated swarms with self-learning, fault-tolerant consensus, vector memory, and MCP integration",
|
||||
"main": "bin/ruflo.js",
|
||||
"type": "module",
|
||||
|
||||
@@ -167,3 +167,49 @@ describe('#2226 — pattern store and search share a backend', () => {
|
||||
expect(hit).toBeDefined();
|
||||
}, 60_000);
|
||||
});
|
||||
|
||||
/**
|
||||
* 3.10.8 routing-learning fixes (follow-ups to the intelligence audit):
|
||||
* Bug B — Q-router cached a stale route decision and only invalidated the
|
||||
* whole cache every 50 updates, so a freshly-learned Q-update was
|
||||
* hidden in-process until 50 updates accumulated. Now the updated
|
||||
* state's cache entry is invalidated immediately.
|
||||
* Bug C — boolean flags ignored an explicit space-form value, so
|
||||
* `route task --explore false` still explored (could not disable a
|
||||
* default-true boolean). The parser now consumes `true`/`false`.
|
||||
*/
|
||||
describe('3.10.8 #bugB — Q-router reflects a learned update immediately (no 50-update cache lag)', () => {
|
||||
it('changes the exploited route within a handful of updates', async () => {
|
||||
const dir = mkdtempSync(path.join(tmpdir(), 'ruflo-qcache-'));
|
||||
try {
|
||||
const r = createQLearningRouter({ modelPath: path.join(dir, 'q.json') });
|
||||
await r.initialize();
|
||||
const task = 'deep research and investigation task';
|
||||
r.route(task, false); // prime the cache with the cold (all-zero) decision
|
||||
for (let i = 0; i < 5; i++) r.update(task, 'researcher', 1.0);
|
||||
for (let i = 0; i < 5; i++) r.update(task, 'architect', -1.0);
|
||||
// Only 10 updates — well under the old 50-update invalidation threshold.
|
||||
expect(r.route(task, false).route).toBe('researcher');
|
||||
} finally {
|
||||
rmSync(dir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('3.10.8 #bugC — boolean flags accept an explicit space-form value', () => {
|
||||
const parse = (argv: string[]) =>
|
||||
new CommandParser({ booleanFlags: ['explore'], allowUnknownFlags: true })
|
||||
.parse(['route', 'task', 'x', ...argv]).flags.explore;
|
||||
|
||||
it('parses --explore false as false (was forced true)', () => {
|
||||
expect(parse(['--explore', 'false'])).toBe(false);
|
||||
});
|
||||
it('parses --explore true as true', () => {
|
||||
expect(parse(['--explore', 'true'])).toBe(true);
|
||||
});
|
||||
it('still honors --explore=false and bare --explore', () => {
|
||||
expect(parse(['--explore=false'])).toBe(false);
|
||||
expect(parse(['--explore'])).toBe(true);
|
||||
expect(parse(['--no-explore'])).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@claude-flow/cli",
|
||||
"version": "3.10.7",
|
||||
"version": "3.10.8",
|
||||
"type": "module",
|
||||
"description": "Ruflo CLI - Enterprise AI agent orchestration with 60+ specialized agents, swarm coordination, MCP server, self-learning hooks, and vector memory for Claude Code",
|
||||
"main": "dist/src/index.js",
|
||||
|
||||
@@ -297,7 +297,16 @@ export class CommandParser {
|
||||
const normalizedKey = this.normalizeKey(key);
|
||||
|
||||
if (booleanFlags.has(normalizedKey)) {
|
||||
flags[normalizedKey] = true;
|
||||
// #explore-flag: allow an explicit boolean value (`--explore false`,
|
||||
// `--explore true`). Without this, a default-true boolean could never
|
||||
// be disabled via the space form — the value was dropped and the flag
|
||||
// forced to true. The `=` form already worked via parseValue.
|
||||
if (nextIndex < args.length && this.isBooleanLiteral(args[nextIndex])) {
|
||||
flags[normalizedKey] = args[nextIndex].toLowerCase() === 'true';
|
||||
nextIndex++;
|
||||
} else {
|
||||
flags[normalizedKey] = true;
|
||||
}
|
||||
} else if (nextIndex < args.length && this.isFlagValue(args[nextIndex])) {
|
||||
flags[normalizedKey] = this.parseValue(args[nextIndex]);
|
||||
nextIndex++;
|
||||
@@ -315,7 +324,14 @@ export class CommandParser {
|
||||
const normalizedKey = this.normalizeKey(key);
|
||||
|
||||
if (booleanFlags.has(normalizedKey)) {
|
||||
flags[normalizedKey] = true;
|
||||
// #explore-flag: short boolean flags also accept an explicit value
|
||||
// (`-e false`) so a default-true boolean can be turned off.
|
||||
if (nextIndex < args.length && this.isBooleanLiteral(args[nextIndex])) {
|
||||
flags[normalizedKey] = args[nextIndex].toLowerCase() === 'true';
|
||||
nextIndex++;
|
||||
} else {
|
||||
flags[normalizedKey] = true;
|
||||
}
|
||||
} else if (nextIndex < args.length && this.isFlagValue(args[nextIndex])) {
|
||||
flags[normalizedKey] = this.parseValue(args[nextIndex]);
|
||||
nextIndex++;
|
||||
@@ -355,6 +371,14 @@ export class CommandParser {
|
||||
return /^-\d*\.?\d+(?:[eE][+-]?\d+)?$/.test(arg);
|
||||
}
|
||||
|
||||
/** True for the literal tokens `true`/`false` (case-insensitive). Used so a
|
||||
* boolean flag can take an explicit value in the space form, e.g.
|
||||
* `--explore false` / `-e true`. */
|
||||
private isBooleanLiteral(arg: string): boolean {
|
||||
const a = arg.toLowerCase();
|
||||
return a === 'true' || a === 'false';
|
||||
}
|
||||
|
||||
private parseValue(value: string): string | number | boolean {
|
||||
// Boolean
|
||||
if (value.toLowerCase() === 'true') return true;
|
||||
|
||||
@@ -423,6 +423,14 @@ export class QLearningRouter {
|
||||
this.cacheOrder = [];
|
||||
}
|
||||
|
||||
/** Invalidate a single state's cached route (called after its Q-values change
|
||||
* so the next route() reflects the update immediately). */
|
||||
private invalidateCacheEntry(stateKey: string): void {
|
||||
if (this.routeCache.delete(stateKey)) {
|
||||
this.cacheOrder = this.cacheOrder.filter(k => k !== stateKey);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update Q-values based on feedback
|
||||
* Includes experience replay for stable learning
|
||||
@@ -453,6 +461,12 @@ export class QLearningRouter {
|
||||
// Perform direct update
|
||||
const tdError = this.updateQValue(stateKey, actionIdx, reward, nextStateKey);
|
||||
|
||||
// #cache-staleness: invalidate THIS state's cached route immediately. The
|
||||
// periodic full invalidation (every 50 updates) otherwise left a freshly
|
||||
// learned Q-update hidden behind a stale cached decision, so feedback
|
||||
// appeared to have no effect on routing until 50 updates accumulated.
|
||||
this.invalidateCacheEntry(stateKey);
|
||||
|
||||
// Perform experience replay
|
||||
if (this.config.enableReplay && this.replayBuffer.length >= this.config.replayBatchSize) {
|
||||
this.experienceReplay();
|
||||
|
||||
Reference in New Issue
Block a user