Skip to content

Commit 44b9dad

Browse files
authored
fix(release): enforce CI and idle assignment safety (#3037)
Closes #3030 and #3031.
2 parents 45e65b5 + 693385e commit 44b9dad

15 files changed

Lines changed: 362 additions & 26 deletions

File tree

.claude/settings.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -174,15 +174,15 @@
174174
"taskListEnabled": true,
175175
"mailboxEnabled": true,
176176
"coordination": {
177-
"autoAssignOnIdle": true,
177+
"autoAssignOnIdle": false,
178178
"trainPatternsOnComplete": true,
179179
"notifyLeadOnComplete": true,
180180
"sharedMemoryNamespace": "agent-teams"
181181
},
182182
"hooks": {
183183
"teammateIdle": {
184184
"enabled": true,
185-
"autoAssign": true,
185+
"autoAssign": false,
186186
"checkTaskList": true
187187
},
188188
"taskCompleted": {

.github/workflows/ci.yml

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -73,20 +73,18 @@ jobs:
7373
uses: ./.github/actions/npm-ci-retry
7474

7575
- name: Run all tests
76-
run: npm test || echo "⚠️ Some tests failed (Jest teardown issues - non-blocking)"
77-
continue-on-error: true
78-
79-
- name: Generate coverage report
80-
if: matrix.os == 'ubuntu-latest'
81-
run: npm run test:coverage || echo "⚠️ Coverage generation failed (non-blocking)"
82-
continue-on-error: true
76+
# #3030: the historical suite has known debt, but new failing files
77+
# are release-blocking. The committed file-level baseline may only
78+
# shrink; replacing an old failure with a new one still fails.
79+
run: node scripts/ci-test-ratchet.mjs
8380

8481
- name: Upload test results
8582
if: always()
8683
uses: actions/upload-artifact@v4
8784
with:
8885
name: test-results-${{ matrix.os }}
89-
path: coverage/
86+
path: .test-results/vitest.json
87+
if-no-files-found: error
9088

9189
# Documentation generation
9290
docs:
@@ -314,4 +312,4 @@ jobs:
314312
echo "✅ CI Pipeline completed"
315313
echo "Security: ${{ needs.security.result }}"
316314
echo "Test: ${{ needs.test.result }}"
317-
echo "Build: ${{ needs.build.result }}"
315+
echo "Build: ${{ needs.build.result }}"

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "claude-flow",
3-
"version": "3.38.11",
3+
"version": "3.38.12",
44
"workspaces": [
55
"v3/@claude-flow/codex",
66
"v3/@claude-flow/plugin-agent-federation",

ruflo/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "ruflo",
3-
"version": "3.38.11",
3+
"version": "3.38.12",
44
"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",
55
"main": "bin/ruflo.js",
66
"type": "module",
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import { describe, expect, it } from 'vitest';
2+
import { evaluateTestReport } from '../ci-test-ratchet.mjs';
3+
4+
const root = '/repo';
5+
6+
describe('CI test failure ratchet (#3030)', () => {
7+
it('permits only failures already named by the baseline', () => {
8+
const result = evaluateTestReport({
9+
success: false,
10+
testResults: [
11+
{ name: '/repo/tests/known.test.ts', status: 'failed' },
12+
{ name: '/repo/tests/green.test.ts', status: 'passed' },
13+
],
14+
}, ['tests/known.test.ts'], root);
15+
16+
expect(result.ok).toBe(true);
17+
expect(result.unexpected).toEqual([]);
18+
});
19+
20+
it('fails when a new file starts failing even if the total count is unchanged', () => {
21+
const result = evaluateTestReport({
22+
success: false,
23+
testResults: [{ name: '/repo/tests/new-regression.test.ts', status: 'failed' }],
24+
}, ['tests/old-debt.test.ts'], root);
25+
26+
expect(result.ok).toBe(false);
27+
expect(result.unexpected).toEqual(['tests/new-regression.test.ts']);
28+
expect(result.fixed).toEqual(['tests/old-debt.test.ts']);
29+
});
30+
31+
it('fails closed when Vitest fails without a file-level result', () => {
32+
const result = evaluateTestReport({ success: false, testResults: [] }, [], root);
33+
34+
expect(result.ok).toBe(false);
35+
expect(result.error).toMatch(/without identifying/);
36+
});
37+
38+
it('fails closed for an empty or malformed report', () => {
39+
expect(evaluateTestReport({ success: true, testResults: [] }, [], root).ok).toBe(false);
40+
expect(evaluateTestReport({}, [], root).ok).toBe(false);
41+
});
42+
});

scripts/ci-test-baseline.txt

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
# Monotone-decreasing file-level failure baseline for .github/workflows/ci.yml.
2+
# Source: origin/main 45e65b5dae5d2c312e70cd5ba90df0701ea05c28
3+
# Evidence: GitHub Actions run 31859043593 (116 failing test files).
4+
# Remove entries when fixed. Never add entries to make a regression green.
5+
plugins/ruflo-adr/scripts/__tests__/adr-create-schema-2651.test.mjs
6+
plugins/ruflo-adr/scripts/__tests__/index-idempotency-2660.test.mjs
7+
plugins/ruflo-adr/scripts/__tests__/parser-bullets-2659.test.mjs
8+
plugins/ruflo-adr/scripts/__tests__/skip-brain-dir-2911.test.mjs
9+
plugins/ruflo-business-pods/scripts/pod-tick.test.mjs
10+
plugins/ruflo-core/scripts/mcp-launch.test.cjs
11+
plugins/ruflo-workflows/scripts/gaia-audit.test.mjs
12+
ruflo/src/ruvocal/src/lib/components/chat/MarkdownRenderer.svelte.test.ts
13+
ruflo/src/ruvocal/src/lib/migrations/migrations.spec.ts
14+
ruflo/src/ruvocal/src/lib/migrations/routines/09-delete-empty-conversations.spec.ts
15+
ruflo/src/ruvocal/src/lib/server/__tests__/conversation-stop-generating.spec.ts
16+
ruflo/src/ruvocal/src/lib/server/api/__tests__/conversations-id.spec.ts
17+
ruflo/src/ruvocal/src/lib/server/api/__tests__/conversations-message.spec.ts
18+
ruflo/src/ruvocal/src/lib/server/api/__tests__/conversations.spec.ts
19+
ruflo/src/ruvocal/src/lib/server/api/__tests__/misc.spec.ts
20+
ruflo/src/ruvocal/src/lib/server/api/__tests__/user-reports.spec.ts
21+
ruflo/src/ruvocal/src/lib/server/api/__tests__/user.spec.ts
22+
ruflo/src/ruvocal/src/lib/server/database/__tests__/rvf.spec.ts
23+
ruflo/src/ruvocal/src/lib/server/isURLLocal.spec.ts
24+
ruflo/src/ruvocal/src/lib/server/mcp/clientPool.spec.ts
25+
ruflo/src/ruvocal/src/lib/server/textGeneration/mcp/wasmTools.test.ts
26+
ruflo/src/ruvocal/src/lib/utils/generationState.spec.ts
27+
ruflo/src/ruvocal/src/lib/utils/marked.spec.ts
28+
ruflo/src/ruvocal/src/lib/utils/messageUpdates.spec.ts
29+
ruflo/src/ruvocal/src/lib/utils/template.spec.ts
30+
ruflo/src/ruvocal/src/lib/utils/toolProgress.spec.ts
31+
ruflo/src/ruvocal/src/lib/utils/tree/addChildren.spec.ts
32+
ruflo/src/ruvocal/src/lib/utils/tree/addSibling.spec.ts
33+
ruflo/src/ruvocal/src/lib/utils/tree/buildSubtree.spec.ts
34+
ruflo/src/ruvocal/src/lib/utils/tree/convertLegacyConversation.spec.ts
35+
ruflo/src/ruvocal/src/lib/utils/tree/isMessageId.spec.ts
36+
ruflo/src/ruvocal/src/lib/utils/tree/treeHelpers.spec.ts
37+
ruflo/src/ruvocal/src/lib/wasm/tests/wasm-capabilities.test.ts
38+
ruflo/src/ruvocal/src/routes/login/callback/updateUser.spec.ts
39+
scripts/__tests__/audit-supply-chain.test.mjs
40+
scripts/__tests__/stage-internal-runtime-bundles.test.mjs
41+
tests/context-persistence-hook.test.mjs
42+
tests/hook-handler-runwithtimeout.test.cjs
43+
tests/rvf-backend.test.ts
44+
tests/rvf-capability-verify.test.ts
45+
tests/rvf-embeddings.test.ts
46+
tests/rvf-event-log.test.ts
47+
tests/rvf-integration.test.ts
48+
tests/rvf-learning-store.test.ts
49+
tests/rvf-migration.test.ts
50+
v3/@claude-flow/cli/__tests__/adr-323-memory-provenance.test.ts
51+
v3/@claude-flow/cli/__tests__/agenticow-tools.test.ts
52+
v3/@claude-flow/cli/__tests__/agntcy/agntcy-commands.test.ts
53+
v3/@claude-flow/cli/__tests__/cli.test.ts
54+
v3/@claude-flow/cli/__tests__/config-loading.test.ts
55+
v3/@claude-flow/cli/__tests__/flywheel-envelope-proposer-contract.test.ts
56+
v3/@claude-flow/cli/__tests__/guidance-brain.test.ts
57+
v3/@claude-flow/cli/__tests__/hooks-intelligence-train-2940.test.ts
58+
v3/@claude-flow/cli/__tests__/hooks-metrics-swarm-backup-2797-2798-2799.test.ts
59+
v3/@claude-flow/cli/__tests__/hooks-post-task-graph-edge-2961.test.ts
60+
v3/@claude-flow/cli/__tests__/init-kebab-flags-2952.test.ts
61+
v3/@claude-flow/cli/__tests__/integration-docker.test.ts
62+
v3/@claude-flow/cli/__tests__/issue-2733-statusline-model-name.test.ts
63+
v3/@claude-flow/cli/__tests__/mcp-client-guardrail.test.ts
64+
v3/@claude-flow/cli/__tests__/mcp-client.test.ts
65+
v3/@claude-flow/cli/__tests__/mcp-http-foreground-2984.test.ts
66+
v3/@claude-flow/cli/__tests__/mcp-http-protocol-tools-2990.test.ts
67+
v3/@claude-flow/cli/__tests__/mcp-tool-filter-2726.test.ts
68+
v3/@claude-flow/cli/__tests__/memory-bridge-failure-diagnostics.test.ts
69+
v3/@claude-flow/cli/__tests__/memory-concurrent-write-loss-2878.test.ts
70+
v3/@claude-flow/cli/__tests__/memory-search-2790.test.ts
71+
v3/@claude-flow/cli/__tests__/memory-search-recall-2558.test.ts
72+
v3/@claude-flow/cli/__tests__/memory-store-upsert-default-2594.test.ts
73+
v3/@claude-flow/cli/__tests__/neural-router.test.ts
74+
v3/@claude-flow/cli/__tests__/output.test.ts
75+
v3/@claude-flow/cli/__tests__/planflip-mempoison-2752.test.ts
76+
v3/@claude-flow/cli/__tests__/plugins-transfer-deep.test.ts
77+
v3/@claude-flow/cli/__tests__/policy-runtime.test.ts
78+
v3/@claude-flow/cli/__tests__/pq-validation.test.ts
79+
v3/@claude-flow/cli/__tests__/proxy-extract-archive.test.ts
80+
v3/@claude-flow/cli/__tests__/security-scan-enum-validation.test.ts
81+
v3/@claude-flow/cli/__tests__/security-scan-persistence.test.ts
82+
v3/@claude-flow/cli/__tests__/security-scan-secret-regex-2931.test.ts
83+
v3/@claude-flow/cli/__tests__/validate-input-path-2352.test.ts
84+
v3/@claude-flow/cli/__tests__/version-anv.test.ts
85+
v3/@claude-flow/embeddings/__tests__/minimal.test.mjs
86+
v3/@claude-flow/embeddings/__tests__/simple.test.mjs
87+
v3/@claude-flow/guidance/tests/hooks.test.ts
88+
v3/@claude-flow/guidance/tests/integration.test.ts
89+
v3/@claude-flow/hooks/__tests__/channel-guard-worker.test.ts
90+
v3/@claude-flow/hooks/__tests__/workers.test.ts
91+
v3/@claude-flow/hooks/src/__tests__/guidance-provider.test.ts
92+
v3/@claude-flow/hooks/src/__tests__/reasoningbank.test.ts
93+
v3/@claude-flow/memory/src/agentdb-backend.test.ts
94+
v3/@claude-flow/memory/src/agentdb-retrieval-guard.test.ts
95+
v3/@claude-flow/memory/src/consolidator.test.ts
96+
v3/@claude-flow/memory/src/controller-registry.test.ts
97+
v3/@claude-flow/memory/src/database-provider.test.ts
98+
v3/@claude-flow/memory/src/graceful-retrieval.test.ts
99+
v3/@claude-flow/memory/src/hnsw-persistence.test.ts
100+
v3/@claude-flow/memory/src/hybrid-backend.test.ts
101+
v3/@claude-flow/memory/src/index.test.ts
102+
v3/@claude-flow/plugin-iot-cognitum/__tests__/integration/sdk-client.test.ts
103+
v3/@claude-flow/plugins/examples/ruvector-plugins/ruvector-plugins.test.ts
104+
v3/@claude-flow/swarm/__tests__/consensus.test.ts
105+
v3/@claude-flow/swarm/__tests__/topology.test.ts
106+
v3/__tests__/appliance/gguf-engine.test.ts
107+
v3/__tests__/appliance/rvfa-builder.test.ts
108+
v3/__tests__/appliance/rvfa-distribution.test.ts
109+
v3/__tests__/appliance/rvfa-format.test.ts
110+
v3/__tests__/appliance/rvfa-signing.test.ts
111+
v3/__tests__/integration/swarm-integration.test.ts
112+
v3/plugins/code-intelligence/tests/mcp-tools.test.ts
113+
v3/plugins/code-intelligence/tests/types.test.ts
114+
v3/plugins/cognitive-kernel/tests/bridges.test.ts
115+
v3/plugins/cognitive-kernel/tests/mcp-tools.test.ts
116+
v3/plugins/financial-risk/tests/mcp-tools.test.ts
117+
v3/plugins/healthcare-clinical/tests/types.test.ts
118+
v3/plugins/legal-contracts/tests/mcp-tools.test.ts
119+
v3/plugins/legal-contracts/tests/types.test.ts
120+
v3/plugins/quantum-optimizer/tests/mcp-tools.test.ts
121+

scripts/ci-test-ratchet.mjs

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
#!/usr/bin/env node
2+
3+
import { spawnSync } from 'node:child_process';
4+
import { existsSync, mkdirSync, readFileSync, rmSync } from 'node:fs';
5+
import { dirname, isAbsolute, relative, resolve, sep } from 'node:path';
6+
import { fileURLToPath, pathToFileURL } from 'node:url';
7+
8+
const REPO_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..');
9+
10+
function normalizeTestPath(name, repoRoot) {
11+
const normalized = isAbsolute(name) ? relative(repoRoot, name) : name;
12+
return normalized.split(sep).join('/').replace(/^\.\//, '');
13+
}
14+
15+
export function evaluateTestReport(report, baselineEntries, repoRoot = REPO_ROOT) {
16+
if (!report || !Array.isArray(report.testResults)) {
17+
return { ok: false, error: 'Vitest JSON report is missing testResults[]' };
18+
}
19+
if (report.testResults.length === 0 && report.success !== false) {
20+
return { ok: false, error: 'Vitest JSON report contains zero test files' };
21+
}
22+
23+
const baseline = new Set(
24+
baselineEntries.map((entry) => entry.trim()).filter((entry) => entry && !entry.startsWith('#')),
25+
);
26+
const failed = new Set(
27+
report.testResults
28+
.filter((result) => result?.status === 'failed' && typeof result.name === 'string')
29+
.map((result) => normalizeTestPath(result.name, repoRoot)),
30+
);
31+
const unexpected = [...failed].filter((name) => !baseline.has(name)).sort();
32+
const fixed = [...baseline].filter((name) => !failed.has(name)).sort();
33+
34+
if (report.success === false && failed.size === 0) {
35+
return {
36+
ok: false,
37+
error: 'Vitest failed without identifying a failing test file',
38+
failed: [],
39+
unexpected: [],
40+
fixed,
41+
};
42+
}
43+
44+
return {
45+
ok: unexpected.length === 0,
46+
failed: [...failed].sort(),
47+
unexpected,
48+
fixed,
49+
baselineCount: baseline.size,
50+
};
51+
}
52+
53+
function parseArgs(argv) {
54+
const args = { report: '', baseline: resolve(REPO_ROOT, 'scripts/ci-test-baseline.txt'), run: true };
55+
for (let i = 0; i < argv.length; i += 1) {
56+
if (argv[i] === '--report' && argv[i + 1]) {
57+
args.report = resolve(argv[++i]);
58+
args.run = false;
59+
} else if (argv[i] === '--baseline' && argv[i + 1]) {
60+
args.baseline = resolve(argv[++i]);
61+
} else {
62+
throw new Error(`Unknown or incomplete argument: ${argv[i]}`);
63+
}
64+
}
65+
return args;
66+
}
67+
68+
function main() {
69+
const args = parseArgs(process.argv.slice(2));
70+
const reportPath = args.report || resolve(REPO_ROOT, '.test-results/vitest.json');
71+
const vitestBin = resolve(REPO_ROOT, 'node_modules/vitest/vitest.mjs');
72+
73+
if (args.run) {
74+
mkdirSync(dirname(reportPath), { recursive: true });
75+
// A killed runner must not accidentally reuse a prior green-enough report.
76+
rmSync(reportPath, { force: true });
77+
const run = spawnSync(process.execPath, [
78+
vitestBin,
79+
'run',
80+
'--reporter=json',
81+
`--outputFile=${reportPath}`,
82+
], {
83+
cwd: REPO_ROOT,
84+
env: { ...process.env, CI: '1' },
85+
stdio: 'inherit',
86+
});
87+
if (run.error) throw run.error;
88+
if (run.signal || (run.status !== 0 && run.status !== 1)) {
89+
console.error(`CI test ratchet: Vitest terminated abnormally (${run.signal ?? run.status})`);
90+
process.exit(1);
91+
}
92+
}
93+
94+
if (!existsSync(reportPath)) {
95+
console.error(`CI test ratchet: report was not produced: ${reportPath}`);
96+
process.exit(1);
97+
}
98+
if (!existsSync(args.baseline)) {
99+
console.error(`CI test ratchet: baseline is missing: ${args.baseline}`);
100+
process.exit(1);
101+
}
102+
103+
const report = JSON.parse(readFileSync(reportPath, 'utf8'));
104+
const baselineEntries = readFileSync(args.baseline, 'utf8').split(/\r?\n/);
105+
const result = evaluateTestReport(report, baselineEntries);
106+
107+
if (!result.ok) {
108+
console.error(`CI test ratchet: FAILED — ${result.unexpected?.length ?? 0} unexpected failing file(s)`);
109+
for (const name of result.unexpected ?? []) console.error(` + ${name}`);
110+
if (result.error) console.error(` ${result.error}`);
111+
process.exit(1);
112+
}
113+
114+
console.log(
115+
`CI test ratchet: PASS — ${result.failed.length}/${result.baselineCount} known failing files remain; `
116+
+ `${result.fixed.length} baseline file(s) are now green`,
117+
);
118+
}
119+
120+
if (process.argv[1] && import.meta.url === pathToFileURL(resolve(process.argv[1])).href) {
121+
main();
122+
}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
3.32.29
1+
3.38.12
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
{
22
"manifest": {
3-
"version": "3.38.11",
3+
"version": "3.38.12",
44
"files": {
55
"auto-memory-hook.mjs": "85fe05c757421c52137c0bc8545a0896bab6b4714538c2a11d1d0835bfcc8c1c",
66
"hook-handler.cjs": "dae295fb9ae2626b89899c19a20cc911541af82b52d2eeb9b214d618b96e9a86",
77
"intelligence.cjs": "30e42ed7ec4ca5a94ac54fdb1330d2d47ac5f3fdeeef207753574723a9e77b5c",
88
"statusline.cjs": "0457fe53f8cd2c56458ff178392536a5868efd1a573665fa43bc01d2d95ca677"
99
}
1010
},
11-
"signature": "rNkivgIb+R5mwQcChrNF+m4lsfPPlKBn4oizP9Q7nuuIi8en/J3NJYf5l6mPRfEc4vdlqnnf0+Oid/007092Dw==",
11+
"signature": "8JINtA8uubiBqNQbsz8OL2fcc4xmKGk6gKPGoa1ib7/21nYDaYWiWrZB/4xli8AGUdmQuMS9GT6ANbpPjqAcCw==",
1212
"algorithm": "ed25519"
1313
}

0 commit comments

Comments
 (0)