Skip to content

Commit ecec9bb

Browse files
test(targets): add branch coverage for all 6 new P0-TierA targets
Adds 87 new tests covering previously untested code branches: - aider: lint.ts (hooks/permissions/mcp warnings), linter.ts (scope branches), generator edge cases, importer default scope - amazon-q: linter.ts (scope branches, glob matching) - augment-code: descriptor.ts (mergeAugmentSettings edge cases, emitScopedSettings, buildSettingsContent), lint.ts (hook event validation), global layout fall-through, global scope import - crush: config-format.ts (buildCrushConfigJson, mergeCrushConfigJson), lint.ts (lintCommands), global layout fall-through, empty hooks/permissions - qwen-code: lint.ts, linter.ts (scope branches), generator edge cases (empty body, empty allowed-tools), global layout rewriteGeneratedPath - trae: linter.ts (scope branches), generator edge cases (no root, empty), importer global scope, default scope
1 parent 8cde835 commit ecec9bb

22 files changed

Lines changed: 1440 additions & 0 deletions

tests/unit/targets/aider/generator.test.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,36 @@ describe('generateRules (aider)', () => {
110110
const results = generateRules(canonical);
111111
expect(results).toHaveLength(0);
112112
});
113+
114+
it('generates CONVENTIONS.md with only non-root rules (no root)', () => {
115+
const canonical = makeCanonical({
116+
rules: [
117+
{
118+
source: '/proj/.agentsmesh/rules/typescript.md',
119+
root: false,
120+
targets: [],
121+
description: 'TypeScript standards',
122+
globs: ['src/**/*.ts'],
123+
body: 'Use strict mode.',
124+
},
125+
{
126+
source: '/proj/.agentsmesh/rules/security.md',
127+
root: false,
128+
targets: [],
129+
description: 'Security',
130+
globs: [],
131+
body: 'No secrets.',
132+
},
133+
],
134+
});
135+
136+
const results = generateRules(canonical);
137+
138+
expect(results).toHaveLength(1);
139+
expect(results[0].path).toBe(AIDER_CONVENTIONS);
140+
expect(results[0].content).toContain('Use strict mode.');
141+
expect(results[0].content).toContain('No secrets.');
142+
});
113143
});
114144

115145
describe('generateSkills (aider)', () => {

tests/unit/targets/aider/global-layout.test.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,11 @@ describe('aider global layout', () => {
3535
expect(rewrite(skillPath, '')).toBe(expectedPath);
3636
});
3737

38+
it('rewriteGeneratedPath passes through unknown paths unchanged', () => {
39+
const rewrite = descriptor.globalSupport!.layout.rewriteGeneratedPath!;
40+
expect(rewrite('some/unknown/path.txt', '')).toBe('some/unknown/path.txt');
41+
});
42+
3843
it('globalSupport.capabilities matches project capabilities', () => {
3944
expect(descriptor.globalSupport!.capabilities).toEqual(descriptor.capabilities);
4045
});

tests/unit/targets/aider/importer.test.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,4 +78,21 @@ describe('importFromAider', () => {
7878

7979
rmSync(projectRoot, { recursive: true, force: true });
8080
});
81+
82+
it('defaults to project scope when no scope option is passed', async () => {
83+
projectRoot = setupFixture({
84+
'CONVENTIONS.md': '# Project Instructions\n\nUse TDD.',
85+
});
86+
87+
const withoutScope = await importFromAider(projectRoot);
88+
const withProjectScope = await importFromAider(projectRoot, { scope: 'project' });
89+
90+
expect(withoutScope).toHaveLength(withProjectScope.length);
91+
for (let i = 0; i < withoutScope.length; i++) {
92+
expect(withoutScope[i].toPath).toBe(withProjectScope[i].toPath);
93+
expect(withoutScope[i].feature).toBe(withProjectScope[i].feature);
94+
}
95+
96+
rmSync(projectRoot, { recursive: true, force: true });
97+
});
8198
});
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
import { describe, it, expect } from 'vitest';
2+
import type { CanonicalFiles } from '../../../../src/core/types.js';
3+
import {
4+
lintHooks,
5+
lintPermissions,
6+
lintMcp,
7+
} from '../../../../src/targets/aider/lint.js';
8+
9+
function makeCanonical(overrides: Partial<CanonicalFiles> = {}): CanonicalFiles {
10+
return {
11+
rules: [],
12+
commands: [],
13+
agents: [],
14+
skills: [],
15+
mcp: null,
16+
permissions: null,
17+
hooks: null,
18+
ignore: [],
19+
...overrides,
20+
};
21+
}
22+
23+
describe('lintHooks (aider)', () => {
24+
it('returns empty when hooks is null', () => {
25+
const canonical = makeCanonical({ hooks: null });
26+
expect(lintHooks(canonical)).toHaveLength(0);
27+
});
28+
29+
it('returns empty when hooks has no entries', () => {
30+
const canonical = makeCanonical({
31+
hooks: { PreGenerate: [], PostGenerate: [] },
32+
});
33+
expect(lintHooks(canonical)).toHaveLength(0);
34+
});
35+
36+
it('returns warning when hooks have entries', () => {
37+
const canonical = makeCanonical({
38+
hooks: {
39+
PreGenerate: [{ command: 'echo hello', pattern: '' }],
40+
},
41+
});
42+
43+
const result = lintHooks(canonical);
44+
45+
expect(result).toHaveLength(1);
46+
expect(result[0].level).toBe('warning');
47+
expect(result[0].target).toBe('aider');
48+
expect(result[0].message).toContain('hook');
49+
});
50+
});
51+
52+
describe('lintPermissions (aider)', () => {
53+
it('returns empty when permissions is null', () => {
54+
const canonical = makeCanonical({ permissions: null });
55+
expect(lintPermissions(canonical)).toHaveLength(0);
56+
});
57+
58+
it('returns empty when all permission lists are empty', () => {
59+
const canonical = makeCanonical({
60+
permissions: { allow: [], deny: [], ask: [] },
61+
});
62+
expect(lintPermissions(canonical)).toHaveLength(0);
63+
});
64+
65+
it('returns warning when allow list has entries', () => {
66+
const canonical = makeCanonical({
67+
permissions: { allow: ['Bash'], deny: [], ask: [] },
68+
});
69+
70+
const result = lintPermissions(canonical);
71+
72+
expect(result).toHaveLength(1);
73+
expect(result[0].level).toBe('warning');
74+
expect(result[0].target).toBe('aider');
75+
expect(result[0].message).toContain('permissions');
76+
});
77+
78+
it('returns warning when deny list has entries', () => {
79+
const canonical = makeCanonical({
80+
permissions: { allow: [], deny: ['WebSearch'], ask: [] },
81+
});
82+
83+
const result = lintPermissions(canonical);
84+
85+
expect(result).toHaveLength(1);
86+
expect(result[0].level).toBe('warning');
87+
});
88+
89+
it('returns warning when ask list has entries', () => {
90+
const canonical = makeCanonical({
91+
permissions: { allow: [], deny: [], ask: ['Bash'] },
92+
});
93+
94+
const result = lintPermissions(canonical);
95+
96+
expect(result).toHaveLength(1);
97+
expect(result[0].level).toBe('warning');
98+
});
99+
100+
it('returns empty when permissions has no ask property', () => {
101+
const canonical = makeCanonical({
102+
permissions: { allow: [], deny: [] },
103+
});
104+
expect(lintPermissions(canonical)).toHaveLength(0);
105+
});
106+
});
107+
108+
describe('lintMcp (aider)', () => {
109+
it('returns empty when mcp is null', () => {
110+
const canonical = makeCanonical({ mcp: null });
111+
expect(lintMcp(canonical)).toHaveLength(0);
112+
});
113+
114+
it('returns empty when mcpServers is empty', () => {
115+
const canonical = makeCanonical({ mcp: { mcpServers: {} } });
116+
expect(lintMcp(canonical)).toHaveLength(0);
117+
});
118+
119+
it('returns warning when mcp has servers', () => {
120+
const canonical = makeCanonical({
121+
mcp: {
122+
mcpServers: {
123+
context7: {
124+
command: 'npx',
125+
args: ['-y', '@upstash/context7-mcp'],
126+
},
127+
},
128+
},
129+
});
130+
131+
const result = lintMcp(canonical);
132+
133+
expect(result).toHaveLength(1);
134+
expect(result[0].level).toBe('warning');
135+
expect(result[0].target).toBe('aider');
136+
expect(result[0].message).toContain('MCP');
137+
});
138+
});
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
import { describe, it, expect } from 'vitest';
2+
import type { CanonicalFiles } from '../../../../src/core/types.js';
3+
import { lintRules } from '../../../../src/targets/aider/linter.js';
4+
5+
function makeCanonical(overrides: Partial<CanonicalFiles> = {}): CanonicalFiles {
6+
return {
7+
rules: [],
8+
commands: [],
9+
agents: [],
10+
skills: [],
11+
mcp: null,
12+
permissions: null,
13+
hooks: null,
14+
ignore: [],
15+
...overrides,
16+
};
17+
}
18+
19+
describe('lintRules (aider)', () => {
20+
it('returns empty diagnostics for empty rules', () => {
21+
const result = lintRules(makeCanonical(), '/proj', []);
22+
expect(result).toHaveLength(0);
23+
});
24+
25+
it('sets target to aider on all diagnostics', () => {
26+
const canonical = makeCanonical({
27+
rules: [
28+
{
29+
source: '/proj/.agentsmesh/rules/typescript.md',
30+
root: false,
31+
targets: [],
32+
description: '',
33+
globs: ['src/**/*.ts'],
34+
body: 'body',
35+
},
36+
],
37+
});
38+
39+
const result = lintRules(canonical, '/proj', []);
40+
for (const d of result) {
41+
expect(d.target).toBe('aider');
42+
}
43+
});
44+
45+
it('skips glob matching in global scope', () => {
46+
const canonical = makeCanonical({
47+
rules: [
48+
{
49+
source: '/proj/.agentsmesh/rules/_root.md',
50+
root: true,
51+
targets: [],
52+
description: '',
53+
globs: [],
54+
body: '# Root',
55+
},
56+
{
57+
source: '/proj/.agentsmesh/rules/unmatched.md',
58+
root: false,
59+
targets: [],
60+
description: '',
61+
globs: ['nonexistent/**/*.xyz'],
62+
body: 'body',
63+
},
64+
],
65+
});
66+
67+
const globalResult = lintRules(canonical, '/proj', [], { scope: 'global' });
68+
expect(globalResult).toHaveLength(0);
69+
});
70+
71+
it('checks glob matches in project scope', () => {
72+
const canonical = makeCanonical({
73+
rules: [
74+
{
75+
source: '/proj/.agentsmesh/rules/_root.md',
76+
root: true,
77+
targets: [],
78+
description: '',
79+
globs: [],
80+
body: '# Root',
81+
},
82+
{
83+
source: '/proj/.agentsmesh/rules/unmatched.md',
84+
root: false,
85+
targets: [],
86+
description: '',
87+
globs: ['nonexistent/**/*.xyz'],
88+
body: 'body',
89+
},
90+
],
91+
});
92+
93+
const projectResult = lintRules(canonical, '/proj', ['src/index.ts'], {
94+
scope: 'project',
95+
});
96+
expect(projectResult.length).toBeGreaterThan(0);
97+
expect(projectResult.some((d) => d.level === 'warning')).toBe(true);
98+
});
99+
});

0 commit comments

Comments
 (0)