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
4 changes: 4 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ pnpm pack:check

This performs a dry-run package build so the final tarball contents can be reviewed before publication.

## Dependency maintenance

Scheduled Dependabot version-update pull requests are disabled. Review dependency updates manually when preparing a maintenance release. Check the Node.js 20 and 22 CI matrix before updating build or test tooling; a package that requires Node.js 22 cannot replace a tool used by the Node.js 20 jobs. Run `pnpm install` to update the lockfile, then `pnpm verify` and `pnpm release:check` before merging. Security advisories should still be reviewed as they arise.

## Add a new SkillBench rule

1. Choose the category under `src/rules/` and add a focused rule module. A rule exports a typed `Rule` with `id`, `name`, `description`, `category`, `defaultSeverity`, `weight`, and `check()`.
Expand Down
9 changes: 9 additions & 0 deletions scripts/measure-scale-performance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,15 @@ async function main(): Promise<void> {
`Performance fixture discovery mismatch: expected ${fileCount} files, received ${report.files.length}.`,
);
}
const countedIssues = report.files.reduce(
(total, file) => total + file.issueCount,
0,
);
if (countedIssues !== report.issues.length) {
throw new Error(
`Per-file finding counts disagree with the report: ${countedIssues} versus ${report.issues.length}.`,
);
}

const result: PerformanceResult = {
version: 1,
Expand Down
7 changes: 5 additions & 2 deletions src/core/analyze.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@ export function analyzeDocuments(
critical: 0,
};
for (const entry of issues) summary[entry.severity] += 1;
const issueCounts = new Map<string, number>();
for (const issue of issues) {
issueCounts.set(issue.path, (issueCounts.get(issue.path) ?? 0) + 1);
}

return {
schemaVersion: SCHEMA_VERSION,
Expand All @@ -66,8 +70,7 @@ export function analyzeDocuments(
path: context.document.relativePath,
kind: context.document.kind,
tokens: context.tokens,
issueCount: issues.filter((entry) => entry.path === context.document.relativePath)
.length,
issueCount: issueCounts.get(context.document.relativePath) ?? 0,
})),
};
}
Expand Down
16 changes: 15 additions & 1 deletion src/parser/discovery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ const defaultIgnores = [
];

const maxDocumentBytes = 2 * 1024 * 1024;
const maxConcurrentReads = 16;

export class DiscoveryError extends Error {
override readonly name = 'DiscoveryError';
Expand Down Expand Up @@ -57,7 +58,20 @@ export async function discoverDocuments(
`No supported agent instruction files found under ${target}`,
);
}
return Promise.all(paths.map((filePath) => readDocument(filePath, absoluteTarget)));
// Keep result order stable while limiting open files on large repositories.
const documents = new Array<ParsedDocument>(paths.length);
let nextIndex = 0;
await Promise.all(
Array.from({ length: Math.min(maxConcurrentReads, paths.length) }, async () => {
while (nextIndex < paths.length) {
const index = nextIndex++;
const filePath = paths[index];
if (filePath === undefined) continue;
documents[index] = await readDocument(filePath, absoluteTarget);
}
}),
);
return documents;
}

async function walk(
Expand Down
43 changes: 0 additions & 43 deletions tests/unit/workflow-security.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { readFileSync, readdirSync } from 'node:fs';
import path from 'node:path';

import YAML from 'yaml';
import { describe, expect, it } from 'vitest';

const root = path.resolve(import.meta.dirname, '../..');
Expand Down Expand Up @@ -57,46 +56,4 @@ describe('workflow supply-chain policy', () => {
).toBe(checkoutCount);
}
});

it('configures weekly dependency updates and preserves Node 20 majors', () => {
const config = YAML.parse(read('.github/dependabot.yml')) as {
version: number;
updates: {
'package-ecosystem': string;
schedule: { interval: string };
ignore?: {
'dependency-name': string;
'update-types': string[];
}[];
}[];
};

expect(config.version).toBe(2);
expect(config.updates).toEqual(
expect.arrayContaining([
expect.objectContaining({
'package-ecosystem': 'npm',
schedule: { interval: 'weekly' },
}),
expect.objectContaining({
'package-ecosystem': 'github-actions',
schedule: { interval: 'weekly' },
}),
]),
);

const npmUpdate = config.updates.find(
(update) => update['package-ecosystem'] === 'npm',
);
expect(npmUpdate?.ignore).toEqual([
{
'dependency-name': 'chalk',
'update-types': ['version-update:semver-major'],
},
{
'dependency-name': 'commander',
'update-types': ['version-update:semver-major'],
},
]);
});
});
Loading