@mossbear/projections 0.1.0-alpha.26
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +7 -0
- package/dist/agents-dir.d.ts +13 -0
- package/dist/agents-dir.d.ts.map +1 -0
- package/dist/agents-dir.js +36 -0
- package/dist/agents-dir.js.map +1 -0
- package/dist/agents-dir.test.d.ts +2 -0
- package/dist/agents-dir.test.d.ts.map +1 -0
- package/dist/agents-dir.test.js +30 -0
- package/dist/agents-dir.test.js.map +1 -0
- package/dist/agents-md.d.ts +12 -0
- package/dist/agents-md.d.ts.map +1 -0
- package/dist/agents-md.js +47 -0
- package/dist/agents-md.js.map +1 -0
- package/dist/agents-md.test.d.ts +2 -0
- package/dist/agents-md.test.d.ts.map +1 -0
- package/dist/agents-md.test.js +44 -0
- package/dist/agents-md.test.js.map +1 -0
- package/dist/claude-md.d.ts +17 -0
- package/dist/claude-md.d.ts.map +1 -0
- package/dist/claude-md.js +43 -0
- package/dist/claude-md.js.map +1 -0
- package/dist/claude-md.test.d.ts +2 -0
- package/dist/claude-md.test.d.ts.map +1 -0
- package/dist/claude-md.test.js +61 -0
- package/dist/claude-md.test.js.map +1 -0
- package/dist/claude-rules.d.ts +17 -0
- package/dist/claude-rules.d.ts.map +1 -0
- package/dist/claude-rules.js +43 -0
- package/dist/claude-rules.js.map +1 -0
- package/dist/claude-rules.test.d.ts +2 -0
- package/dist/claude-rules.test.d.ts.map +1 -0
- package/dist/claude-rules.test.js +34 -0
- package/dist/claude-rules.test.js.map +1 -0
- package/dist/compatibility-attribution.d.ts +7 -0
- package/dist/compatibility-attribution.d.ts.map +1 -0
- package/dist/compatibility-attribution.js +11 -0
- package/dist/compatibility-attribution.js.map +1 -0
- package/dist/cursor-rules.d.ts +17 -0
- package/dist/cursor-rules.d.ts.map +1 -0
- package/dist/cursor-rules.js +36 -0
- package/dist/cursor-rules.js.map +1 -0
- package/dist/cursor-rules.test.d.ts +2 -0
- package/dist/cursor-rules.test.d.ts.map +1 -0
- package/dist/cursor-rules.test.js +58 -0
- package/dist/cursor-rules.test.js.map +1 -0
- package/dist/detect-format.d.ts +15 -0
- package/dist/detect-format.d.ts.map +1 -0
- package/dist/detect-format.js +88 -0
- package/dist/detect-format.js.map +1 -0
- package/dist/detect-format.test.d.ts +2 -0
- package/dist/detect-format.test.d.ts.map +1 -0
- package/dist/detect-format.test.js +104 -0
- package/dist/detect-format.test.js.map +1 -0
- package/dist/eval-path.d.ts +91 -0
- package/dist/eval-path.d.ts.map +1 -0
- package/dist/eval-path.js +110 -0
- package/dist/eval-path.js.map +1 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +17 -0
- package/dist/index.js.map +1 -0
- package/dist/path-rules.d.ts +45 -0
- package/dist/path-rules.d.ts.map +1 -0
- package/dist/path-rules.js +65 -0
- package/dist/path-rules.js.map +1 -0
- package/dist/skill-member-path.d.ts +51 -0
- package/dist/skill-member-path.d.ts.map +1 -0
- package/dist/skill-member-path.js +76 -0
- package/dist/skill-member-path.js.map +1 -0
- package/dist/skill-member-path.test.d.ts +2 -0
- package/dist/skill-member-path.test.d.ts.map +1 -0
- package/dist/skill-member-path.test.js +0 -0
- package/dist/skill-member-path.test.js.map +1 -0
- package/package.json +47 -0
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { renderCursorRules } from './cursor-rules.js';
|
|
3
|
+
const agent = {
|
|
4
|
+
id: 'reviewer',
|
|
5
|
+
name: 'Code Reviewer',
|
|
6
|
+
description: 'Reviews code for quality issues.',
|
|
7
|
+
capabilities: ['no trailing whitespace', 'prefer const over let'],
|
|
8
|
+
};
|
|
9
|
+
describe('renderCursorRules', () => {
|
|
10
|
+
it('renders capabilities as prefixed rule lines', () => {
|
|
11
|
+
const output = renderCursorRules([agent]);
|
|
12
|
+
expect(output).toContain('[Code Reviewer] no trailing whitespace');
|
|
13
|
+
expect(output).toContain('[Code Reviewer] prefer const over let');
|
|
14
|
+
});
|
|
15
|
+
it('includes the auto-generated header', () => {
|
|
16
|
+
const output = renderCursorRules([agent]);
|
|
17
|
+
expect(output).toContain('Auto-generated by mossbear');
|
|
18
|
+
});
|
|
19
|
+
it('does not include attribution by default', () => {
|
|
20
|
+
const output = renderCursorRules([agent]);
|
|
21
|
+
expect(output).not.toContain('Guides managed by Mossbear');
|
|
22
|
+
});
|
|
23
|
+
it('appends attribution when enabled', () => {
|
|
24
|
+
const output = renderCursorRules([agent], { includeAttribution: true });
|
|
25
|
+
expect(output).toContain('# Guides managed by Mossbear (mossbear.io)');
|
|
26
|
+
});
|
|
27
|
+
it('uses a custom attribution string when provided', () => {
|
|
28
|
+
const output = renderCursorRules([agent], {
|
|
29
|
+
includeAttribution: true,
|
|
30
|
+
attribution: 'Managed by Mossbear — 94% alignment score',
|
|
31
|
+
});
|
|
32
|
+
expect(output).toContain('# Managed by Mossbear — 94% alignment score');
|
|
33
|
+
});
|
|
34
|
+
it('returns a valid string for an empty agent list', () => {
|
|
35
|
+
const output = renderCursorRules([]);
|
|
36
|
+
expect(typeof output).toBe('string');
|
|
37
|
+
});
|
|
38
|
+
it('skips agents with no capabilities', () => {
|
|
39
|
+
const noCapAgent = { ...agent, capabilities: [] };
|
|
40
|
+
const output = renderCursorRules([noCapAgent]);
|
|
41
|
+
expect(output).not.toContain('# Code Reviewer');
|
|
42
|
+
expect(output).not.toContain('[Code Reviewer]');
|
|
43
|
+
});
|
|
44
|
+
it('renders multiple agents in order', () => {
|
|
45
|
+
const second = {
|
|
46
|
+
id: 'formatter',
|
|
47
|
+
name: 'Formatter',
|
|
48
|
+
description: '',
|
|
49
|
+
capabilities: ['use 2-space indentation'],
|
|
50
|
+
};
|
|
51
|
+
const output = renderCursorRules([agent, second]);
|
|
52
|
+
const reviewerPos = output.indexOf('# Code Reviewer');
|
|
53
|
+
const formatterPos = output.indexOf('# Formatter');
|
|
54
|
+
expect(reviewerPos).toBeLessThan(formatterPos);
|
|
55
|
+
expect(output).toContain('[Formatter] use 2-space indentation');
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
//# sourceMappingURL=cursor-rules.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cursor-rules.test.js","sourceRoot":"","sources":["../src/cursor-rules.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { GuideFileFormat } from '@mossbear/protocol';
|
|
2
|
+
export declare function detectFormat(path: string): GuideFileFormat | null;
|
|
3
|
+
export declare function isSupportedGuidePath(path: string): boolean;
|
|
4
|
+
/**
|
|
5
|
+
* True for a path inside a `skills/<name>/` tree that is NOT itself the
|
|
6
|
+
* SKILL.md — i.e. skill *member* content (scripts, references, assets).
|
|
7
|
+
* `detectFormat` deliberately returns `null` for these, so a consumer that
|
|
8
|
+
* resolves a member edit to its owning skill uses this to recognise them
|
|
9
|
+
* rather than dropping them as unknown. No caller in this repo today: the
|
|
10
|
+
* incremental re-sync that used it (`rules watch`) was deleted, and the full
|
|
11
|
+
* discovery scan groups members by walking the tree instead. Kept as part of
|
|
12
|
+
* this module's path-classification surface, alongside `isSupportedGuidePath`.
|
|
13
|
+
*/
|
|
14
|
+
export declare function isSkillMemberPath(path: string): boolean;
|
|
15
|
+
//# sourceMappingURL=detect-format.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"detect-format.d.ts","sourceRoot":"","sources":["../src/detect-format.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAA;AA0BzD,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,eAAe,GAAG,IAAI,CA+DjE;AAED,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,WAEhD;AAED;;;;;;;;;GASG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,WAG7C"}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
function normalizeGuidePath(path) {
|
|
2
|
+
return path.replaceAll('\\', '/').replace(/\/+/g, '/').toLowerCase();
|
|
3
|
+
}
|
|
4
|
+
function isUnderSkillsDir(normalizedPath) {
|
|
5
|
+
return (normalizedPath.startsWith('.claude/skills/') ||
|
|
6
|
+
normalizedPath.includes('/.claude/skills/') ||
|
|
7
|
+
normalizedPath.startsWith('.agents/skills/') ||
|
|
8
|
+
normalizedPath.includes('/.agents/skills/'));
|
|
9
|
+
}
|
|
10
|
+
function basenameOf(normalizedPath) {
|
|
11
|
+
return normalizedPath.slice(normalizedPath.lastIndexOf('/') + 1);
|
|
12
|
+
}
|
|
13
|
+
function isUnderCursorRulesDir(normalizedPath) {
|
|
14
|
+
return (normalizedPath.startsWith('.cursor/rules/') ||
|
|
15
|
+
normalizedPath.includes('/.cursor/rules/'));
|
|
16
|
+
}
|
|
17
|
+
export function detectFormat(path) {
|
|
18
|
+
const normalizedPath = normalizeGuidePath(path);
|
|
19
|
+
// Checked ahead of the generic `.agents/` rule below: a SKILL.md nested
|
|
20
|
+
// under a `skills/` directory is a distinct artifact (npx-skills / Claude
|
|
21
|
+
// Code skill installs), not a freeform `.agents/` guide entry. Every other
|
|
22
|
+
// file in a skills/ tree (scripts, references, assets) is skill *member*
|
|
23
|
+
// content — imported alongside its SKILL.md by discovery, never as a
|
|
24
|
+
// standalone guide — so the whole subtree is opaque to the rules below
|
|
25
|
+
// (previously `.agents/skills/<skill>/scripts/…` leaked through the
|
|
26
|
+
// `.agents/` catch-all as its own agents-dir guide).
|
|
27
|
+
if (isUnderSkillsDir(normalizedPath)) {
|
|
28
|
+
return normalizedPath.endsWith('/skill.md') ? 'skill-md' : null;
|
|
29
|
+
}
|
|
30
|
+
if (normalizedPath === 'agents.md' || normalizedPath.endsWith('/agents.md')) {
|
|
31
|
+
return 'agents-md';
|
|
32
|
+
}
|
|
33
|
+
if (normalizedPath === 'claude.md' || normalizedPath.endsWith('/claude.md')) {
|
|
34
|
+
return 'claude-md';
|
|
35
|
+
}
|
|
36
|
+
if (normalizedPath === '.cursorrules' || normalizedPath.endsWith('/.cursorrules')) {
|
|
37
|
+
return 'cursorrules';
|
|
38
|
+
}
|
|
39
|
+
// Dot-files inside a recognised location are tooling scaffolding, not
|
|
40
|
+
// guidance: `mossbear init` writes `.agents/.gitkeep` so git tracks the
|
|
41
|
+
// otherwise-empty directory (`commands/init.ts`), and editors and VCS leave
|
|
42
|
+
// `.DS_Store`/`.gitignore`/`.gitattributes` beside real guides. Importing
|
|
43
|
+
// them files a guide the user never wrote — an empty card on the guides
|
|
44
|
+
// screen, an extra head on the run summary's guide count, and a body-less
|
|
45
|
+
// guide handed to the grader.
|
|
46
|
+
//
|
|
47
|
+
// Ordering is load-bearing: this sits *below* the `.cursorrules` branch,
|
|
48
|
+
// the one dot-file that is a genuine guide and is matched by exact name. A
|
|
49
|
+
// future dot-named format must be matched above this line too. Same shape
|
|
50
|
+
// as the skills branch at the top — a recognised location whose contents
|
|
51
|
+
// still are not, individually, guides.
|
|
52
|
+
if (basenameOf(normalizedPath).startsWith('.'))
|
|
53
|
+
return null;
|
|
54
|
+
// Modern Cursor "Project Rules": a directory of `.mdc` files under
|
|
55
|
+
// `.cursor/rules/` (project or `~/.cursor/rules/` global), distinct from the
|
|
56
|
+
// legacy single-file `.cursorrules` above. Cursor only reads `.mdc` files
|
|
57
|
+
// here, so other files (README.md, assets) fall through to `null`. Nested
|
|
58
|
+
// rule directories (`.cursor/rules/frontend/*.mdc`) are matched too.
|
|
59
|
+
if (isUnderCursorRulesDir(normalizedPath) && normalizedPath.endsWith('.mdc')) {
|
|
60
|
+
return 'cursor-rules';
|
|
61
|
+
}
|
|
62
|
+
if (normalizedPath.startsWith('.agents/') || normalizedPath.includes('/.agents/')) {
|
|
63
|
+
return 'agents-dir';
|
|
64
|
+
}
|
|
65
|
+
if (normalizedPath.startsWith('.claude/rules/') ||
|
|
66
|
+
normalizedPath.includes('/.claude/rules/')) {
|
|
67
|
+
return 'claude-rules';
|
|
68
|
+
}
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
export function isSupportedGuidePath(path) {
|
|
72
|
+
return detectFormat(path) !== null;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* True for a path inside a `skills/<name>/` tree that is NOT itself the
|
|
76
|
+
* SKILL.md — i.e. skill *member* content (scripts, references, assets).
|
|
77
|
+
* `detectFormat` deliberately returns `null` for these, so a consumer that
|
|
78
|
+
* resolves a member edit to its owning skill uses this to recognise them
|
|
79
|
+
* rather than dropping them as unknown. No caller in this repo today: the
|
|
80
|
+
* incremental re-sync that used it (`rules watch`) was deleted, and the full
|
|
81
|
+
* discovery scan groups members by walking the tree instead. Kept as part of
|
|
82
|
+
* this module's path-classification surface, alongside `isSupportedGuidePath`.
|
|
83
|
+
*/
|
|
84
|
+
export function isSkillMemberPath(path) {
|
|
85
|
+
const normalizedPath = normalizeGuidePath(path);
|
|
86
|
+
return isUnderSkillsDir(normalizedPath) && !normalizedPath.endsWith('/skill.md');
|
|
87
|
+
}
|
|
88
|
+
//# sourceMappingURL=detect-format.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"detect-format.js","sourceRoot":"","sources":["../src/detect-format.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"detect-format.test.d.ts","sourceRoot":"","sources":["../src/detect-format.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { detectFormat, isSkillMemberPath, isSupportedGuidePath } from './detect-format.js';
|
|
3
|
+
describe('detectFormat', () => {
|
|
4
|
+
it('detects canonical single-file formats', () => {
|
|
5
|
+
expect(detectFormat('AGENTS.md')).toBe('agents-md');
|
|
6
|
+
expect(detectFormat('/repo/CLAUDE.md')).toBe('claude-md');
|
|
7
|
+
expect(detectFormat('.cursorrules')).toBe('cursorrules');
|
|
8
|
+
});
|
|
9
|
+
it('detects directory-backed formats by path only', () => {
|
|
10
|
+
expect(detectFormat('.agents/reviewer/SKILL.md')).toBe('agents-dir');
|
|
11
|
+
expect(detectFormat('/repo/.claude/rules/backend.md')).toBe('claude-rules');
|
|
12
|
+
});
|
|
13
|
+
it('detects a SKILL.md nested under a skills/ directory as skill-md', () => {
|
|
14
|
+
expect(detectFormat('.claude/skills/reviewer/SKILL.md')).toBe('skill-md');
|
|
15
|
+
expect(detectFormat('/repo/.claude/skills/reviewer/SKILL.md')).toBe('skill-md');
|
|
16
|
+
expect(detectFormat('.agents/skills/reviewer/SKILL.md')).toBe('skill-md');
|
|
17
|
+
expect(detectFormat('/repo/.agents/skills/reviewer/SKILL.md')).toBe('skill-md');
|
|
18
|
+
});
|
|
19
|
+
it('ignores non-SKILL.md files inside a skills/ directory', () => {
|
|
20
|
+
expect(detectFormat('.claude/skills/reviewer/script.py')).toBeNull();
|
|
21
|
+
expect(detectFormat('.claude/skills/reviewer/README.md')).toBeNull();
|
|
22
|
+
// The whole skills/ subtree is opaque: under .agents/skills/ member files
|
|
23
|
+
// must not leak through the generic .agents/ catch-all as agents-dir
|
|
24
|
+
// guides — they import with their skill, as members.
|
|
25
|
+
expect(detectFormat('.agents/skills/reviewer/scripts/lint.py')).toBeNull();
|
|
26
|
+
expect(detectFormat('.agents/skills/reviewer/references/api.md')).toBeNull();
|
|
27
|
+
// Even an AGENTS.md nested in a skill directory is skill member content.
|
|
28
|
+
expect(detectFormat('.claude/skills/reviewer/references/AGENTS.md')).toBeNull();
|
|
29
|
+
});
|
|
30
|
+
it('ignores dot-files in every recognised guide location', () => {
|
|
31
|
+
// `mossbear init` writes .agents/.gitkeep so git tracks the directory; it is
|
|
32
|
+
// scaffolding, not guidance, and importing it produced an empty guide.
|
|
33
|
+
expect(detectFormat('.agents/.gitkeep')).toBeNull();
|
|
34
|
+
expect(detectFormat('/repo/.agents/.gitkeep')).toBeNull();
|
|
35
|
+
expect(detectFormat('C:\\repo\\.agents\\.gitkeep')).toBeNull();
|
|
36
|
+
expect(detectFormat('.agents/team/.DS_Store')).toBeNull();
|
|
37
|
+
expect(detectFormat('.claude/rules/.gitkeep')).toBeNull();
|
|
38
|
+
expect(detectFormat('.cursor/rules/.gitkeep')).toBeNull();
|
|
39
|
+
// Even a dot-file carrying a rules-directory extension stays out.
|
|
40
|
+
expect(detectFormat('.cursor/rules/.hidden.mdc')).toBeNull();
|
|
41
|
+
});
|
|
42
|
+
it('still detects .cursorrules, the one dot-file that is a guide', () => {
|
|
43
|
+
// Guards the ordering: the dot-file rejection must sit below this branch.
|
|
44
|
+
expect(detectFormat('.cursorrules')).toBe('cursorrules');
|
|
45
|
+
expect(detectFormat('/repo/.cursorrules')).toBe('cursorrules');
|
|
46
|
+
expect(detectFormat('C:\\repo\\.cursorrules')).toBe('cursorrules');
|
|
47
|
+
expect(isSupportedGuidePath('.cursorrules')).toBe(true);
|
|
48
|
+
});
|
|
49
|
+
it('detects .mdc files under .cursor/rules/ as cursor-rules', () => {
|
|
50
|
+
expect(detectFormat('.cursor/rules/backend.mdc')).toBe('cursor-rules');
|
|
51
|
+
expect(detectFormat('/repo/.cursor/rules/backend.mdc')).toBe('cursor-rules');
|
|
52
|
+
// Nested rule directories are supported.
|
|
53
|
+
expect(detectFormat('.cursor/rules/frontend/api.mdc')).toBe('cursor-rules');
|
|
54
|
+
// Global (~/.cursor/rules/) paths resolve to absolute home paths.
|
|
55
|
+
expect(detectFormat('/home/user/.cursor/rules/style.mdc')).toBe('cursor-rules');
|
|
56
|
+
});
|
|
57
|
+
it('ignores non-.mdc files under .cursor/rules/ and the legacy single file', () => {
|
|
58
|
+
expect(detectFormat('.cursor/rules/README.md')).toBeNull();
|
|
59
|
+
expect(detectFormat('.cursor/rules/assets/logo.png')).toBeNull();
|
|
60
|
+
// The legacy single-file format stays distinct from the directory format.
|
|
61
|
+
expect(detectFormat('.cursorrules')).toBe('cursorrules');
|
|
62
|
+
});
|
|
63
|
+
it('normalizes windows-style paths', () => {
|
|
64
|
+
expect(detectFormat('C:\\repo\\AGENTS.md')).toBe('agents-md');
|
|
65
|
+
expect(detectFormat('C:\\repo\\CLAUDE.md')).toBe('claude-md');
|
|
66
|
+
expect(detectFormat('C:\\repo\\.agents\\reviewer\\SKILL.md')).toBe('agents-dir');
|
|
67
|
+
expect(detectFormat('C:\\repo\\.claude\\rules\\backend.md')).toBe('claude-rules');
|
|
68
|
+
expect(detectFormat('C:\\repo\\.claude\\skills\\reviewer\\SKILL.md')).toBe('skill-md');
|
|
69
|
+
expect(detectFormat('C:\\repo\\.cursor\\rules\\backend.mdc')).toBe('cursor-rules');
|
|
70
|
+
});
|
|
71
|
+
it('returns null for unsupported paths', () => {
|
|
72
|
+
expect(detectFormat('README.md')).toBeNull();
|
|
73
|
+
expect(detectFormat('.agents')).toBeNull();
|
|
74
|
+
expect(detectFormat('.claude/settings.json')).toBeNull();
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
describe('isSupportedGuidePath', () => {
|
|
78
|
+
it('tracks detectFormat support', () => {
|
|
79
|
+
expect(isSupportedGuidePath('AGENTS.md')).toBe(true);
|
|
80
|
+
expect(isSupportedGuidePath('docs/guide.md')).toBe(false);
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
describe('isSkillMemberPath', () => {
|
|
84
|
+
it('is true for member content inside a skills/ tree', () => {
|
|
85
|
+
expect(isSkillMemberPath('.claude/skills/reviewer/script.py')).toBe(true);
|
|
86
|
+
expect(isSkillMemberPath('.claude/skills/reviewer/references/api.md')).toBe(true);
|
|
87
|
+
expect(isSkillMemberPath('/repo/.claude/skills/reviewer/assets/logo.png')).toBe(true);
|
|
88
|
+
expect(isSkillMemberPath('.agents/skills/reviewer/scripts/lint.py')).toBe(true);
|
|
89
|
+
// Even a nested AGENTS.md is member content, matching detectFormat.
|
|
90
|
+
expect(isSkillMemberPath('.claude/skills/reviewer/references/AGENTS.md')).toBe(true);
|
|
91
|
+
});
|
|
92
|
+
it('is false for the SKILL.md itself', () => {
|
|
93
|
+
expect(isSkillMemberPath('.claude/skills/reviewer/SKILL.md')).toBe(false);
|
|
94
|
+
expect(isSkillMemberPath('/repo/.agents/skills/reviewer/SKILL.md')).toBe(false);
|
|
95
|
+
expect(isSkillMemberPath('C:\\repo\\.claude\\skills\\reviewer\\SKILL.md')).toBe(false);
|
|
96
|
+
});
|
|
97
|
+
it('is false for paths outside a skills/ tree', () => {
|
|
98
|
+
expect(isSkillMemberPath('AGENTS.md')).toBe(false);
|
|
99
|
+
expect(isSkillMemberPath('.claude/rules/backend.md')).toBe(false);
|
|
100
|
+
expect(isSkillMemberPath('.cursor/rules/backend.mdc')).toBe(false);
|
|
101
|
+
expect(isSkillMemberPath('docs/guide.md')).toBe(false);
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
//# sourceMappingURL=detect-format.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"detect-format.test.js","sourceRoot":"","sources":["../src/detect-format.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where a **plain guide's** evaluation criteria live on disk: the eval shadow
|
|
3
|
+
* tree at `<scopeRoot>/.mossbear/evals/<the guide's scope-relative path>`
|
|
4
|
+
* (docs/plans/eval-criteria-on-disk-plan.md → Approach).
|
|
5
|
+
*
|
|
6
|
+
* One rule — *the eval file lives with its guide* — expressed two ways, because
|
|
7
|
+
* the two guide shapes have different structures:
|
|
8
|
+
*
|
|
9
|
+
* - a **skill** is a directory, so its criteria sit inside it as a positional
|
|
10
|
+
* `EVAL.md` (already shipped: `skillMemberContentType`,
|
|
11
|
+
* `apps/dashboard/src/server/rules/skillMembers.ts`). Nothing here touches
|
|
12
|
+
* that half;
|
|
13
|
+
* - a **plain guide** is a bare file with nowhere to put a sibling that would
|
|
14
|
+
* not clutter — and land inside — a discovery root, so its criteria go in
|
|
15
|
+
* the shadow tree, which mirrors the guide's own path.
|
|
16
|
+
*
|
|
17
|
+
* Mirroring the path is what removes the mapping table: two guides cannot share
|
|
18
|
+
* a path, so their shadow entries cannot collide, and the scope root carries the
|
|
19
|
+
* rest — a `global` guide's criteria land in `~/.mossbear/evals/.claude/CLAUDE.md`
|
|
20
|
+
* by the same rule that puts a `project` guide's in the repo.
|
|
21
|
+
*
|
|
22
|
+
* **The shadow tree is never guidance.** `.mossbear/` is not a discovery root
|
|
23
|
+
* (`discover.ts`), so nothing under it is imported as a guide — that is the
|
|
24
|
+
* property `rules-discover.test.ts` and `rules-manifest.test.ts` pin down. But
|
|
25
|
+
* "not discovered" is not the same as "not classifiable", and `detectFormat` is
|
|
26
|
+
* path-only: hand it `.mossbear/evals/AGENTS.md` and it answers `agents-md`,
|
|
27
|
+
* because the path *ends* in `agents.md`. Any future call site that classifies a
|
|
28
|
+
* local file it did not get from discovery — the reconciler this item unblocks —
|
|
29
|
+
* must therefore ask `classifyLocalItemPath` rather than `detectFormat`, which
|
|
30
|
+
* is why the shadow-tree check lives inside it and cannot be forgotten.
|
|
31
|
+
*
|
|
32
|
+
* **Why this lives in `projections` rather than the CLI or the protocol.**
|
|
33
|
+
* The server needs it too: a guide pull has to tell each client where that
|
|
34
|
+
* guide's criteria belong, and deriving the path on each client instead would
|
|
35
|
+
* be the per-clone divergence `unified-item-sync` exists to remove. Of the
|
|
36
|
+
* three homes that could serve both sides, this is the cheapest — the rule is
|
|
37
|
+
* *about* format and path semantics, which is this package's subject, and it
|
|
38
|
+
* needs `detectFormat`, which is already here. Putting it in
|
|
39
|
+
* `@mossbear/protocol` would drag this package into the dependency graph of a
|
|
40
|
+
* published, deliberately valibot-only contract package; re-expressing the
|
|
41
|
+
* skill check without `detectFormat` would duplicate the rule this module was
|
|
42
|
+
* written to keep in one place (Planning Principle 14).
|
|
43
|
+
*/
|
|
44
|
+
import type { GuideFileFormat } from '@mossbear/protocol';
|
|
45
|
+
/** Scope-root-relative POSIX directory holding the eval shadow tree. */
|
|
46
|
+
export declare const EVAL_SHADOW_DIR = ".mossbear/evals";
|
|
47
|
+
/**
|
|
48
|
+
* The shadow-tree path for a **plain** guide, relative to the same scope root
|
|
49
|
+
* the guide's own path is relative to. `null` when the guide path could never
|
|
50
|
+
* have been discovered as a guide in the first place (absolute, escaping,
|
|
51
|
+
* control characters, over-long, or already inside `.mossbear/`) — and `null` for a
|
|
52
|
+
* skill, which keeps its criteria positionally.
|
|
53
|
+
*/
|
|
54
|
+
export declare function evalShadowPath(guidePath: string): string | null;
|
|
55
|
+
/**
|
|
56
|
+
* The inverse: the guide whose criteria a shadow path holds, or `null` when the
|
|
57
|
+
* path is not in the shadow tree (or names the directory itself).
|
|
58
|
+
*
|
|
59
|
+
* A true inverse over the paths `evalShadowPath` accepts, and deliberately no
|
|
60
|
+
* wider: a remainder this function would recover but that function would never
|
|
61
|
+
* emit — `.mossbear/evals/.mossbear/evals/AGENTS.md`, `.mossbear/evals/.claude/skills/review/SKILL.md` —
|
|
62
|
+
* names no guide whose criteria belong here, so it is refused rather than
|
|
63
|
+
* reported as valid eval content. Without that, the shadow tree could hold an
|
|
64
|
+
* entry the derivation says is unreachable.
|
|
65
|
+
*/
|
|
66
|
+
export declare function guidePathFromEvalShadow(shadowPath: string): string | null;
|
|
67
|
+
/**
|
|
68
|
+
* What a scope-relative path on disk *is*, for a caller holding a path rather
|
|
69
|
+
* than a discovery result.
|
|
70
|
+
*
|
|
71
|
+
* `eval` is checked before anything else so no path under `.mossbear/` can come
|
|
72
|
+
* back as a guide — see this module's header for why `detectFormat` alone gets
|
|
73
|
+
* that wrong.
|
|
74
|
+
*/
|
|
75
|
+
export type LocalItemClassification =
|
|
76
|
+
/** Criteria for the guide at `guidePath`, held in the shadow tree. */
|
|
77
|
+
{
|
|
78
|
+
kind: 'eval';
|
|
79
|
+
guidePath: string;
|
|
80
|
+
}
|
|
81
|
+
/** A guide file, in the format `detectFormat` recognised. */
|
|
82
|
+
| {
|
|
83
|
+
kind: 'guide';
|
|
84
|
+
format: GuideFileFormat;
|
|
85
|
+
}
|
|
86
|
+
/** Neither: Mossbear's own state, a skill member, an unrelated file. */
|
|
87
|
+
| {
|
|
88
|
+
kind: 'other';
|
|
89
|
+
};
|
|
90
|
+
export declare function classifyLocalItemPath(path: string): LocalItemClassification;
|
|
91
|
+
//# sourceMappingURL=eval-path.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"eval-path.d.ts","sourceRoot":"","sources":["../src/eval-path.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AAWH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAA;AAEzD,wEAAwE;AACxE,eAAO,MAAM,eAAe,oBAAoB,CAAA;AAIhD;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAY/D;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,uBAAuB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAYzE;AAED;;;;;;;GAOG;AACH,MAAM,MAAM,uBAAuB;AACjC,sEAAsE;AACpE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE;AACrC,6DAA6D;GAC3D;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,eAAe,CAAA;CAAE;AAC5C,wEAAwE;GACtE;IAAE,IAAI,EAAE,OAAO,CAAA;CAAE,CAAA;AAErB,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG,uBAAuB,CAU3E"}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where a **plain guide's** evaluation criteria live on disk: the eval shadow
|
|
3
|
+
* tree at `<scopeRoot>/.mossbear/evals/<the guide's scope-relative path>`
|
|
4
|
+
* (docs/plans/eval-criteria-on-disk-plan.md → Approach).
|
|
5
|
+
*
|
|
6
|
+
* One rule — *the eval file lives with its guide* — expressed two ways, because
|
|
7
|
+
* the two guide shapes have different structures:
|
|
8
|
+
*
|
|
9
|
+
* - a **skill** is a directory, so its criteria sit inside it as a positional
|
|
10
|
+
* `EVAL.md` (already shipped: `skillMemberContentType`,
|
|
11
|
+
* `apps/dashboard/src/server/rules/skillMembers.ts`). Nothing here touches
|
|
12
|
+
* that half;
|
|
13
|
+
* - a **plain guide** is a bare file with nowhere to put a sibling that would
|
|
14
|
+
* not clutter — and land inside — a discovery root, so its criteria go in
|
|
15
|
+
* the shadow tree, which mirrors the guide's own path.
|
|
16
|
+
*
|
|
17
|
+
* Mirroring the path is what removes the mapping table: two guides cannot share
|
|
18
|
+
* a path, so their shadow entries cannot collide, and the scope root carries the
|
|
19
|
+
* rest — a `global` guide's criteria land in `~/.mossbear/evals/.claude/CLAUDE.md`
|
|
20
|
+
* by the same rule that puts a `project` guide's in the repo.
|
|
21
|
+
*
|
|
22
|
+
* **The shadow tree is never guidance.** `.mossbear/` is not a discovery root
|
|
23
|
+
* (`discover.ts`), so nothing under it is imported as a guide — that is the
|
|
24
|
+
* property `rules-discover.test.ts` and `rules-manifest.test.ts` pin down. But
|
|
25
|
+
* "not discovered" is not the same as "not classifiable", and `detectFormat` is
|
|
26
|
+
* path-only: hand it `.mossbear/evals/AGENTS.md` and it answers `agents-md`,
|
|
27
|
+
* because the path *ends* in `agents.md`. Any future call site that classifies a
|
|
28
|
+
* local file it did not get from discovery — the reconciler this item unblocks —
|
|
29
|
+
* must therefore ask `classifyLocalItemPath` rather than `detectFormat`, which
|
|
30
|
+
* is why the shadow-tree check lives inside it and cannot be forgotten.
|
|
31
|
+
*
|
|
32
|
+
* **Why this lives in `projections` rather than the CLI or the protocol.**
|
|
33
|
+
* The server needs it too: a guide pull has to tell each client where that
|
|
34
|
+
* guide's criteria belong, and deriving the path on each client instead would
|
|
35
|
+
* be the per-clone divergence `unified-item-sync` exists to remove. Of the
|
|
36
|
+
* three homes that could serve both sides, this is the cheapest — the rule is
|
|
37
|
+
* *about* format and path semantics, which is this package's subject, and it
|
|
38
|
+
* needs `detectFormat`, which is already here. Putting it in
|
|
39
|
+
* `@mossbear/protocol` would drag this package into the dependency graph of a
|
|
40
|
+
* published, deliberately valibot-only contract package; re-expressing the
|
|
41
|
+
* skill check without `detectFormat` would duplicate the rule this module was
|
|
42
|
+
* written to keep in one place (Planning Principle 14).
|
|
43
|
+
*/
|
|
44
|
+
import { detectFormat } from './detect-format.js';
|
|
45
|
+
import { isSafeRelativePath, isSkillGuidePath, isUnderMossbearDir, segmentsOf, toPosix, } from './path-rules.js';
|
|
46
|
+
/** Scope-root-relative POSIX directory holding the eval shadow tree. */
|
|
47
|
+
export const EVAL_SHADOW_DIR = '.mossbear/evals';
|
|
48
|
+
const MOSSBEAR_DIR_SEGMENT = '.mossbear';
|
|
49
|
+
/**
|
|
50
|
+
* The shadow-tree path for a **plain** guide, relative to the same scope root
|
|
51
|
+
* the guide's own path is relative to. `null` when the guide path could never
|
|
52
|
+
* have been discovered as a guide in the first place (absolute, escaping,
|
|
53
|
+
* control characters, over-long, or already inside `.mossbear/`) — and `null` for a
|
|
54
|
+
* skill, which keeps its criteria positionally.
|
|
55
|
+
*/
|
|
56
|
+
export function evalShadowPath(guidePath) {
|
|
57
|
+
const normalized = toPosix(guidePath);
|
|
58
|
+
if (!isSafeRelativePath(normalized))
|
|
59
|
+
return null;
|
|
60
|
+
if (isUnderMossbearDir(normalized))
|
|
61
|
+
return null;
|
|
62
|
+
if (isSkillGuidePath(normalized))
|
|
63
|
+
return null;
|
|
64
|
+
const shadowPath = `${EVAL_SHADOW_DIR}/${normalized}`;
|
|
65
|
+
// The shadow path is itself carried in payloads and sidecars, so a guide path
|
|
66
|
+
// that is safe on its own but pushes the shadow path past the 512-char bound
|
|
67
|
+
// has no representable home — say so rather than emitting a path that fails
|
|
68
|
+
// validation one layer later.
|
|
69
|
+
return isSafeRelativePath(shadowPath) ? shadowPath : null;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* The inverse: the guide whose criteria a shadow path holds, or `null` when the
|
|
73
|
+
* path is not in the shadow tree (or names the directory itself).
|
|
74
|
+
*
|
|
75
|
+
* A true inverse over the paths `evalShadowPath` accepts, and deliberately no
|
|
76
|
+
* wider: a remainder this function would recover but that function would never
|
|
77
|
+
* emit — `.mossbear/evals/.mossbear/evals/AGENTS.md`, `.mossbear/evals/.claude/skills/review/SKILL.md` —
|
|
78
|
+
* names no guide whose criteria belong here, so it is refused rather than
|
|
79
|
+
* reported as valid eval content. Without that, the shadow tree could hold an
|
|
80
|
+
* entry the derivation says is unreachable.
|
|
81
|
+
*/
|
|
82
|
+
export function guidePathFromEvalShadow(shadowPath) {
|
|
83
|
+
const normalized = toPosix(shadowPath);
|
|
84
|
+
const segments = segmentsOf(normalized);
|
|
85
|
+
if (segments[0] !== MOSSBEAR_DIR_SEGMENT || segments[1] !== 'evals')
|
|
86
|
+
return null;
|
|
87
|
+
// Slice the original (not the lower-cased copy) so the guide path keeps the
|
|
88
|
+
// casing it was written with — `AGENTS.md` must not come back as `agents.md`.
|
|
89
|
+
const remainder = normalized.split('/').slice(2).join('/');
|
|
90
|
+
if (remainder.length === 0)
|
|
91
|
+
return null;
|
|
92
|
+
if (!isSafeRelativePath(remainder))
|
|
93
|
+
return null;
|
|
94
|
+
if (isUnderMossbearDir(remainder) || isSkillGuidePath(remainder))
|
|
95
|
+
return null;
|
|
96
|
+
return remainder;
|
|
97
|
+
}
|
|
98
|
+
export function classifyLocalItemPath(path) {
|
|
99
|
+
const guidePath = guidePathFromEvalShadow(path);
|
|
100
|
+
if (guidePath !== null)
|
|
101
|
+
return { kind: 'eval', guidePath };
|
|
102
|
+
// Everything else under `.mossbear/` is Mossbear's own state (`docs.json`, the repo
|
|
103
|
+
// config, pulled `context/`) — and an unusable shadow path (`.mossbear/evals/..`)
|
|
104
|
+
// lands here too. Never guidance either way.
|
|
105
|
+
if (isUnderMossbearDir(path))
|
|
106
|
+
return { kind: 'other' };
|
|
107
|
+
const format = detectFormat(toPosix(path));
|
|
108
|
+
return format === null ? { kind: 'other' } : { kind: 'guide', format };
|
|
109
|
+
}
|
|
110
|
+
//# sourceMappingURL=eval-path.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"eval-path.js","sourceRoot":"","sources":["../src/eval-path.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AACjD,OAAO,EACL,kBAAkB,EAClB,gBAAgB,EAChB,kBAAkB,EAClB,UAAU,EACV,OAAO,GACR,MAAM,iBAAiB,CAAA;AAIxB,wEAAwE;AACxE,MAAM,CAAC,MAAM,eAAe,GAAG,iBAAiB,CAAA;AAEhD,MAAM,oBAAoB,GAAG,WAAW,CAAA;AAExC;;;;;;GAMG;AACH,MAAM,UAAU,cAAc,CAAC,SAAiB;IAC9C,MAAM,UAAU,GAAG,OAAO,CAAC,SAAS,CAAC,CAAA;IACrC,IAAI,CAAC,kBAAkB,CAAC,UAAU,CAAC;QAAE,OAAO,IAAI,CAAA;IAChD,IAAI,kBAAkB,CAAC,UAAU,CAAC;QAAE,OAAO,IAAI,CAAA;IAC/C,IAAI,gBAAgB,CAAC,UAAU,CAAC;QAAE,OAAO,IAAI,CAAA;IAE7C,MAAM,UAAU,GAAG,GAAG,eAAe,IAAI,UAAU,EAAE,CAAA;IACrD,8EAA8E;IAC9E,6EAA6E;IAC7E,4EAA4E;IAC5E,8BAA8B;IAC9B,OAAO,kBAAkB,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,CAAA;AAC3D,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,uBAAuB,CAAC,UAAkB;IACxD,MAAM,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC,CAAA;IACtC,MAAM,QAAQ,GAAG,UAAU,CAAC,UAAU,CAAC,CAAA;IACvC,IAAI,QAAQ,CAAC,CAAC,CAAC,KAAK,oBAAoB,IAAI,QAAQ,CAAC,CAAC,CAAC,KAAK,OAAO;QAAE,OAAO,IAAI,CAAA;IAEhF,4EAA4E;IAC5E,8EAA8E;IAC9E,MAAM,SAAS,GAAG,UAAU,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;IAC1D,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAA;IACvC,IAAI,CAAC,kBAAkB,CAAC,SAAS,CAAC;QAAE,OAAO,IAAI,CAAA;IAC/C,IAAI,kBAAkB,CAAC,SAAS,CAAC,IAAI,gBAAgB,CAAC,SAAS,CAAC;QAAE,OAAO,IAAI,CAAA;IAC7E,OAAO,SAAS,CAAA;AAClB,CAAC;AAkBD,MAAM,UAAU,qBAAqB,CAAC,IAAY;IAChD,MAAM,SAAS,GAAG,uBAAuB,CAAC,IAAI,CAAC,CAAA;IAC/C,IAAI,SAAS,KAAK,IAAI;QAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,CAAA;IAC1D,oFAAoF;IACpF,kFAAkF;IAClF,6CAA6C;IAC7C,IAAI,kBAAkB,CAAC,IAAI,CAAC;QAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,CAAA;IAEtD,MAAM,MAAM,GAAG,YAAY,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAA;IAC1C,OAAO,MAAM,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,CAAA;AACxE,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @mossbear/projections
|
|
3
|
+
*
|
|
4
|
+
* Pure projection logic for canonical AGENTS.md and .agents/ outputs,
|
|
5
|
+
* plus compatibility exporters for platform-native formats.
|
|
6
|
+
* This package must remain importable outside the CLI package.
|
|
7
|
+
*/
|
|
8
|
+
export * from './agents-md.js';
|
|
9
|
+
export * from './eval-path.js';
|
|
10
|
+
export * from './agents-dir.js';
|
|
11
|
+
export * from './claude-md.js';
|
|
12
|
+
export * from './claude-rules.js';
|
|
13
|
+
export * from './compatibility-attribution.js';
|
|
14
|
+
export * from './cursor-rules.js';
|
|
15
|
+
export * from './detect-format.js';
|
|
16
|
+
export * from './skill-member-path.js';
|
|
17
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,cAAc,gBAAgB,CAAA;AAC9B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,mBAAmB,CAAA;AACjC,cAAc,gCAAgC,CAAA;AAC9C,cAAc,mBAAmB,CAAA;AACjC,cAAc,oBAAoB,CAAA;AAClC,cAAc,wBAAwB,CAAA"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @mossbear/projections
|
|
3
|
+
*
|
|
4
|
+
* Pure projection logic for canonical AGENTS.md and .agents/ outputs,
|
|
5
|
+
* plus compatibility exporters for platform-native formats.
|
|
6
|
+
* This package must remain importable outside the CLI package.
|
|
7
|
+
*/
|
|
8
|
+
export * from './agents-md.js';
|
|
9
|
+
export * from './eval-path.js';
|
|
10
|
+
export * from './agents-dir.js';
|
|
11
|
+
export * from './claude-md.js';
|
|
12
|
+
export * from './claude-rules.js';
|
|
13
|
+
export * from './compatibility-attribution.js';
|
|
14
|
+
export * from './cursor-rules.js';
|
|
15
|
+
export * from './detect-format.js';
|
|
16
|
+
export * from './skill-member-path.js';
|
|
17
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,cAAc,gBAAgB,CAAA;AAC9B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,mBAAmB,CAAA;AACjC,cAAc,gCAAgC,CAAA;AAC9C,cAAc,mBAAmB,CAAA;AACjC,cAAc,oBAAoB,CAAA;AAClC,cAAc,wBAAwB,CAAA"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The scope-relative path predicates that both attached-file derivations share.
|
|
3
|
+
*
|
|
4
|
+
* A guide's attached files land in two different places depending on the guide's
|
|
5
|
+
* shape — a plain guide's criteria go to the eval shadow tree (`eval-path.ts`), a
|
|
6
|
+
* skill's members go inside its own directory (`skill-member-path.ts`) — but both
|
|
7
|
+
* derivations answer the same safety questions first, and a second copy of those
|
|
8
|
+
* answers is a second thing to get wrong. They live here so the two derivations
|
|
9
|
+
* cannot drift on what counts as a safe path or as a skill.
|
|
10
|
+
*/
|
|
11
|
+
export declare function toPosix(path: string): string;
|
|
12
|
+
export declare function isSafeRelativePath(path: string): boolean;
|
|
13
|
+
/**
|
|
14
|
+
* Split a scope-relative path into its lower-cased segments.
|
|
15
|
+
*
|
|
16
|
+
* Lower-cased because recognition and derivation pull in opposite directions:
|
|
17
|
+
* a shadow path is *written* as exactly `.mossbear/evals/…`, but on a
|
|
18
|
+
* case-insensitive filesystem `.Mossbear/Evals/AGENTS.md` names the same file, and
|
|
19
|
+
* the two ways of being wrong about it are not symmetric — classifying eval
|
|
20
|
+
* criteria as guidance hands a rubric to an agent as instructions, while
|
|
21
|
+
* classifying a stray capitalised path as eval content merely declines to treat
|
|
22
|
+
* it as a guide. Recognition therefore leans the safe way.
|
|
23
|
+
*/
|
|
24
|
+
export declare function segmentsOf(path: string): string[];
|
|
25
|
+
/** True when `path` is inside `.mossbear/`, whatever it holds. */
|
|
26
|
+
export declare function isUnderMossbearDir(path: string): boolean;
|
|
27
|
+
/**
|
|
28
|
+
* True when a path is a skill's own `SKILL.md`.
|
|
29
|
+
*
|
|
30
|
+
* The rule is *the attached file lives with its guide*, and a skill's guide is a
|
|
31
|
+
* directory — so its criteria are the positional `EVAL.md` inside it and its
|
|
32
|
+
* members are the rest of that directory
|
|
33
|
+
* (`skillMemberContentType`, `apps/dashboard/src/server/rules/skillMembers.ts:60-66`).
|
|
34
|
+
* Shadowing a skill as well would put one skill's rubric in two places, split it
|
|
35
|
+
* out of the directory that makes a skill portable, and hand the next reconciler
|
|
36
|
+
* two rows to disagree over — the "two shapes of one fact" CLAUDE.md → Simplicity
|
|
37
|
+
* refuses. `detectFormat` is the same test discovery uses, so what counts as a
|
|
38
|
+
* skill here cannot drift from what counts as one there.
|
|
39
|
+
*
|
|
40
|
+
* This one predicate partitions the two derivations: `evalShadowPath` returns
|
|
41
|
+
* null exactly where this is true, and `skillMemberPath` returns null exactly
|
|
42
|
+
* where it is false.
|
|
43
|
+
*/
|
|
44
|
+
export declare function isSkillGuidePath(path: string): boolean;
|
|
45
|
+
//# sourceMappingURL=path-rules.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"path-rules.d.ts","sourceRoot":"","sources":["../src/path-rules.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAeH,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE5C;AAED,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAExD;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAEjD;AAED,kEAAkE;AAClE,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAExD;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEtD"}
|