Skip to content
Merged
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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,7 @@ codebase.

- [rUv](https://github.com/ruvnet) — [ruflo/claude-flow](https://github.com/ruvnet/ruflo); upstream issues filed from this kit's verification work: [#2219](https://github.com/ruvnet/ruflo/issues/2219), [#2222](https://github.com/ruvnet/ruflo/issues/2222), [#2239](https://github.com/ruvnet/ruflo/issues/2239), [#2360](https://github.com/ruvnet/ruflo/issues/2360), [#2549](https://github.com/ruvnet/ruflo/issues/2549), [#2670](https://github.com/ruvnet/ruflo/issues/2670)
- [Dragan Spiridonov](https://github.com/proffesor-for-testing) — [agentic-qe](https://github.com/proffesor-for-testing/agentic-qe)
- [Stuart Kerr](https://github.com/stuinfla) — [RuvNet Brain](https://github.com/stuinfla/ruvnet-brain), the source-grounded knowledge base over the rUv stack that `ak setup` installs and `ak sync` keeps current
- [Ciprian Melian](https://github.com/ciprianmelian) — prior art: [repair gist](https://gist.github.com/ciprianmelian/eb7e8ff7d24018141ca34bb8a7e216a6)

### Contributors
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@pacphi/agentic-kit",
"version": "4.0.0-alpha.30",
"description": "Machine-level setup, healing, and verification kit for your AI agent stack (ruflo/claude-flow, agentic-qe, Claude Code glue) — cross-platform, zero-dependency",
"description": "Setup, healing, verification, and multi-host execution routing (Claude, Codex, OpenCode) for your AI agent stack (ruflo/claude-flow, agentic-qe, RuvNet Brain) — cross-platform, zero-dependency",
"type": "module",
"bin": {
"agentic-kit": "bin/agentic-kit.mjs",
Expand Down
19 changes: 15 additions & 4 deletions src/lib/natives.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -177,8 +177,19 @@ export function dbPathPinStatus({ settingsLocalFile, projectRoot }) {
return { warn: false, pinned };
}

export const aidefencePresent = () =>
fs.existsSync(path.join(rufloNodeModules(), '@claude-flow', 'aidefence', 'package.json'));
// npm only hoists a @claude-flow/* package to the top of ruflo's node_modules when
// every dependent can share one version; a conflicting sibling (e.g. @claude-flow/cli
// pinning a different range) leaves it nested under that dependent instead. A
// top-level-only check then false-negatives on a package that IS installed — seen
// with @claude-flow/security landing under cli/node_modules instead of hoisting.
// Check the top level plus the known @claude-flow/* dependents (mirrors
// rufloMemoryContexts' consumer list) rather than a full recursive search.
function claudeFlowPackagePresent(name) {
const nm = rufloNodeModules();
const roots = [nm, path.join(nm, '@claude-flow', 'cli', 'node_modules')];
return roots.some((root) => fs.existsSync(path.join(root, '@claude-flow', name, 'package.json')));
}

export const aidefencePresent = () => claudeFlowPackagePresent('aidefence');

export const securityPresent = () =>
fs.existsSync(path.join(rufloNodeModules(), '@claude-flow', 'security', 'package.json'));
export const securityPresent = () => claudeFlowPackagePresent('security');
62 changes: 61 additions & 1 deletion tests/kit/natives.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ import assert from 'node:assert/strict';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { bsq3Root, bsq3IsNative, selfSpecConflicts } from '../../src/lib/natives.mjs';
import { bsq3Root, bsq3IsNative, selfSpecConflicts, aidefencePresent, securityPresent } from '../../src/lib/natives.mjs';
import { _setGlobalRootForTest } from '../../src/lib/paths.mjs';

function makeFixture({ withBinding }) {
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'ak-natives-'));
Expand Down Expand Up @@ -111,3 +112,62 @@ test('selfSpecConflicts ignores non-plain-semver declarations (workspace:/link:/
assert.deepEqual(selfSpecConflicts(dir, '^12.10.0'), []);
fs.rmSync(dir, { recursive: true, force: true });
});

function makeGlobalRootFixture() {
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'ak-natives-cfpkg-'));
fs.mkdirSync(path.join(root, 'ruflo', 'node_modules'), { recursive: true });
return root;
}

test('securityPresent is true when the package is hoisted to the top of ruflo/node_modules', () => {
const root = makeGlobalRootFixture();
writePkgJson(path.join(root, 'ruflo', 'node_modules', '@claude-flow', 'security'), { name: '@claude-flow/security' });
_setGlobalRootForTest(root);
try {
assert.equal(securityPresent(), true);
} finally {
_setGlobalRootForTest(null);
fs.rmSync(root, { recursive: true, force: true });
}
});

test('securityPresent is true when npm nests the package under @claude-flow/cli instead of hoisting it (regression: false WARN when a version conflict prevents hoisting)', () => {
const root = makeGlobalRootFixture();
writePkgJson(
path.join(root, 'ruflo', 'node_modules', '@claude-flow', 'cli', 'node_modules', '@claude-flow', 'security'),
{ name: '@claude-flow/security' },
);
_setGlobalRootForTest(root);
try {
assert.equal(securityPresent(), true);
} finally {
_setGlobalRootForTest(null);
fs.rmSync(root, { recursive: true, force: true });
}
});

test('securityPresent is false when the package is genuinely absent from both locations', () => {
const root = makeGlobalRootFixture();
_setGlobalRootForTest(root);
try {
assert.equal(securityPresent(), false);
} finally {
_setGlobalRootForTest(null);
fs.rmSync(root, { recursive: true, force: true });
}
});

test('aidefencePresent follows the same top-level-or-nested-under-cli resolution', () => {
const root = makeGlobalRootFixture();
writePkgJson(
path.join(root, 'ruflo', 'node_modules', '@claude-flow', 'cli', 'node_modules', '@claude-flow', 'aidefence'),
{ name: '@claude-flow/aidefence' },
);
_setGlobalRootForTest(root);
try {
assert.equal(aidefencePresent(), true);
} finally {
_setGlobalRootForTest(null);
fs.rmSync(root, { recursive: true, force: true });
}
});
Loading