@diff-review-system/drs 3.3.0 → 4.0.0-rc.4
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/.pi/agents/task/agents-md-updater.md +24 -0
- package/.pi/agents/task/changelog-updater.md +29 -0
- package/.pi/agents/task/review-issue-fixer.md +25 -0
- package/.pi/workflows/github-pr-describe-post.yaml +24 -0
- package/.pi/workflows/github-pr-describe.yaml +23 -0
- package/.pi/workflows/github-pr-post-comment.yaml +19 -0
- package/.pi/workflows/github-pr-review-post.yaml +32 -0
- package/.pi/workflows/github-pr-review.yaml +23 -0
- package/.pi/workflows/github-pr-show-changes.yaml +25 -0
- package/.pi/workflows/gitlab-mr-describe-post.yaml +22 -0
- package/.pi/workflows/gitlab-mr-describe.yaml +21 -0
- package/.pi/workflows/gitlab-mr-post-comment.yaml +17 -0
- package/.pi/workflows/gitlab-mr-review-code-quality.yaml +31 -0
- package/.pi/workflows/gitlab-mr-review-post-code-quality.yaml +40 -0
- package/.pi/workflows/gitlab-mr-review-post.yaml +30 -0
- package/.pi/workflows/gitlab-mr-review.yaml +21 -0
- package/.pi/workflows/gitlab-mr-show-changes.yaml +23 -0
- package/.pi/workflows/local-changelog-update.yaml +23 -0
- package/.pi/workflows/local-fix-review-issues.yaml +42 -0
- package/.pi/workflows/local-review.yaml +17 -0
- package/.pi/workflows/local-staged-review.yaml +17 -0
- package/.pi/workflows/local-update-agents-md.yaml +24 -0
- package/.pi/workflows/tag-changelog-update.yaml +26 -0
- package/README.md +215 -106
- package/dist/ci/runner.d.ts.map +1 -1
- package/dist/ci/runner.js +7 -8
- package/dist/ci/runner.js.map +1 -1
- package/dist/cli/index.js +69 -341
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/init.d.ts.map +1 -1
- package/dist/cli/init.js +25 -23
- package/dist/cli/init.js.map +1 -1
- package/dist/cli/run-agent.d.ts +24 -0
- package/dist/cli/run-agent.d.ts.map +1 -0
- package/dist/cli/run-agent.js +139 -0
- package/dist/cli/run-agent.js.map +1 -0
- package/dist/cli/run-agent.test.d.ts +2 -0
- package/dist/cli/run-agent.test.d.ts.map +1 -0
- package/dist/cli/run-agent.test.js +204 -0
- package/dist/cli/run-agent.test.js.map +1 -0
- package/dist/cli/workflow.d.ts +51 -0
- package/dist/cli/workflow.d.ts.map +1 -0
- package/dist/cli/workflow.js +1229 -0
- package/dist/cli/workflow.js.map +1 -0
- package/dist/cli/workflow.test.d.ts +2 -0
- package/dist/cli/workflow.test.d.ts.map +1 -0
- package/dist/cli/workflow.test.js +1410 -0
- package/dist/cli/workflow.test.js.map +1 -0
- package/dist/lib/agent-id.d.ts +9 -0
- package/dist/lib/agent-id.d.ts.map +1 -0
- package/dist/lib/agent-id.js +32 -0
- package/dist/lib/agent-id.js.map +1 -0
- package/dist/lib/comment-formatter.d.ts +7 -1
- package/dist/lib/comment-formatter.d.ts.map +1 -1
- package/dist/lib/comment-formatter.js +42 -1
- package/dist/lib/comment-formatter.js.map +1 -1
- package/dist/lib/comment-formatter.test.js +33 -0
- package/dist/lib/comment-formatter.test.js.map +1 -1
- package/dist/lib/comment-manager.d.ts +4 -0
- package/dist/lib/comment-manager.d.ts.map +1 -1
- package/dist/lib/comment-manager.js +7 -1
- package/dist/lib/comment-manager.js.map +1 -1
- package/dist/lib/comment-poster.d.ts +2 -2
- package/dist/lib/comment-poster.d.ts.map +1 -1
- package/dist/lib/comment-poster.js +3 -3
- package/dist/lib/comment-poster.js.map +1 -1
- package/dist/lib/comment-poster.test.js +13 -3
- package/dist/lib/comment-poster.test.js.map +1 -1
- package/dist/lib/config-model-overrides.test.d.ts +0 -10
- package/dist/lib/config-model-overrides.test.d.ts.map +1 -1
- package/dist/lib/config-model-overrides.test.js +174 -210
- package/dist/lib/config-model-overrides.test.js.map +1 -1
- package/dist/lib/config.d.ts +111 -34
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +322 -83
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/config.test.js +282 -2
- package/dist/lib/config.test.js.map +1 -1
- package/dist/lib/context-loader.d.ts +4 -4
- package/dist/lib/context-loader.d.ts.map +1 -1
- package/dist/lib/context-loader.js +10 -7
- package/dist/lib/context-loader.js.map +1 -1
- package/dist/lib/context-loader.test.js +31 -26
- package/dist/lib/context-loader.test.js.map +1 -1
- package/dist/lib/description-executor.js +1 -1
- package/dist/lib/description-executor.js.map +1 -1
- package/dist/lib/description-executor.test.js +10 -3
- package/dist/lib/description-executor.test.js.map +1 -1
- package/dist/lib/diff-lines.d.ts +9 -0
- package/dist/lib/diff-lines.d.ts.map +1 -0
- package/dist/lib/diff-lines.js +32 -0
- package/dist/lib/diff-lines.js.map +1 -0
- package/dist/lib/diff-lines.test.d.ts +2 -0
- package/dist/lib/diff-lines.test.d.ts.map +1 -0
- package/dist/lib/diff-lines.test.js +13 -0
- package/dist/lib/diff-lines.test.js.map +1 -0
- package/dist/lib/logger.d.ts +1 -1
- package/dist/lib/logger.d.ts.map +1 -1
- package/dist/lib/review-core.d.ts.map +1 -1
- package/dist/lib/review-core.js +22 -27
- package/dist/lib/review-core.js.map +1 -1
- package/dist/lib/review-core.test.js +37 -23
- package/dist/lib/review-core.test.js.map +1 -1
- package/dist/lib/review-orchestrator.d.ts.map +1 -1
- package/dist/lib/review-orchestrator.js +11 -8
- package/dist/lib/review-orchestrator.js.map +1 -1
- package/dist/lib/review-orchestrator.test.js +27 -6
- package/dist/lib/review-orchestrator.test.js.map +1 -1
- package/dist/lib/unified-review-executor.d.ts +0 -2
- package/dist/lib/unified-review-executor.d.ts.map +1 -1
- package/dist/lib/unified-review-executor.js +7 -13
- package/dist/lib/unified-review-executor.js.map +1 -1
- package/dist/lib/unified-review-executor.test.js +2 -2
- package/dist/lib/unified-review-executor.test.js.map +1 -1
- package/dist/pi/sdk.d.ts.map +1 -1
- package/dist/pi/sdk.js +37 -12
- package/dist/pi/sdk.js.map +1 -1
- package/dist/pi/sdk.test.js +83 -10
- package/dist/pi/sdk.test.js.map +1 -1
- package/dist/runtime/agent-loader.d.ts +10 -6
- package/dist/runtime/agent-loader.d.ts.map +1 -1
- package/dist/runtime/agent-loader.js +53 -27
- package/dist/runtime/agent-loader.js.map +1 -1
- package/dist/runtime/agent-loader.test.js +116 -119
- package/dist/runtime/agent-loader.test.js.map +1 -1
- package/dist/runtime/built-in-paths.d.ts +1 -0
- package/dist/runtime/built-in-paths.d.ts.map +1 -1
- package/dist/runtime/built-in-paths.js +7 -0
- package/dist/runtime/built-in-paths.js.map +1 -1
- package/dist/runtime/client.d.ts +12 -0
- package/dist/runtime/client.d.ts.map +1 -1
- package/dist/runtime/client.js +83 -58
- package/dist/runtime/client.js.map +1 -1
- package/dist/runtime/client.test.js +264 -15
- package/dist/runtime/client.test.js.map +1 -1
- package/dist/runtime/path-config.d.ts +2 -2
- package/dist/runtime/path-config.d.ts.map +1 -1
- package/dist/runtime/path-config.js +8 -8
- package/dist/runtime/path-config.js.map +1 -1
- package/dist/runtime/path-config.test.js +14 -14
- package/dist/runtime/path-config.test.js.map +1 -1
- package/package.json +3 -4
- package/.pi/agents/review/documentation.md +0 -56
- package/.pi/agents/review/performance.md +0 -53
- package/.pi/agents/review/quality.md +0 -59
- package/.pi/agents/review/security.md +0 -53
- package/.pi/agents/review/style.md +0 -132
- package/dist/cli/describe-mr.d.ts +0 -11
- package/dist/cli/describe-mr.d.ts.map +0 -1
- package/dist/cli/describe-mr.js +0 -134
- package/dist/cli/describe-mr.js.map +0 -1
- package/dist/cli/describe-pr.d.ts +0 -12
- package/dist/cli/describe-pr.d.ts.map +0 -1
- package/dist/cli/describe-pr.js +0 -135
- package/dist/cli/describe-pr.js.map +0 -1
- package/dist/cli/post-comments.d.ts +0 -20
- package/dist/cli/post-comments.d.ts.map +0 -1
- package/dist/cli/post-comments.js +0 -225
- package/dist/cli/post-comments.js.map +0 -1
- package/dist/cli/review-local.d.ts +0 -13
- package/dist/cli/review-local.d.ts.map +0 -1
- package/dist/cli/review-local.integration.test.d.ts +0 -2
- package/dist/cli/review-local.integration.test.d.ts.map +0 -1
- package/dist/cli/review-local.integration.test.js +0 -343
- package/dist/cli/review-local.integration.test.js.map +0 -1
- package/dist/cli/review-local.js +0 -90
- package/dist/cli/review-local.js.map +0 -1
- package/dist/cli/review-local.live.e2e.test.d.ts +0 -2
- package/dist/cli/review-local.live.e2e.test.d.ts.map +0 -1
- package/dist/cli/review-local.live.e2e.test.js +0 -153
- package/dist/cli/review-local.live.e2e.test.js.map +0 -1
- package/dist/cli/review-local.test.d.ts +0 -2
- package/dist/cli/review-local.test.d.ts.map +0 -1
- package/dist/cli/review-local.test.js +0 -164
- package/dist/cli/review-local.test.js.map +0 -1
- package/dist/cli/review-mr.d.ts +0 -22
- package/dist/cli/review-mr.d.ts.map +0 -1
- package/dist/cli/review-mr.js +0 -181
- package/dist/cli/review-mr.js.map +0 -1
- package/dist/cli/review-mr.test.d.ts +0 -2
- package/dist/cli/review-mr.test.d.ts.map +0 -1
- package/dist/cli/review-mr.test.js +0 -142
- package/dist/cli/review-mr.test.js.map +0 -1
- package/dist/cli/review-pr.d.ts +0 -22
- package/dist/cli/review-pr.d.ts.map +0 -1
- package/dist/cli/review-pr.js +0 -181
- package/dist/cli/review-pr.js.map +0 -1
- package/dist/cli/review-pr.test.d.ts +0 -2
- package/dist/cli/review-pr.test.d.ts.map +0 -1
- package/dist/cli/review-pr.test.js +0 -137
- package/dist/cli/review-pr.test.js.map +0 -1
- package/dist/cli/review-url.d.ts +0 -35
- package/dist/cli/review-url.d.ts.map +0 -1
- package/dist/cli/review-url.js +0 -110
- package/dist/cli/review-url.js.map +0 -1
- package/dist/cli/review-url.test.d.ts +0 -2
- package/dist/cli/review-url.test.d.ts.map +0 -1
- package/dist/cli/review-url.test.js +0 -132
- package/dist/cli/review-url.test.js.map +0 -1
- package/dist/cli/show-changes.d.ts +0 -15
- package/dist/cli/show-changes.d.ts.map +0 -1
- package/dist/cli/show-changes.js +0 -184
- package/dist/cli/show-changes.js.map +0 -1
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type { DRSConfig } from '../lib/config.js';
|
|
2
2
|
export interface AgentDefinition {
|
|
3
|
+
id: string;
|
|
4
|
+
namespace: string;
|
|
3
5
|
name: string;
|
|
4
6
|
path: string;
|
|
5
7
|
description: string;
|
|
@@ -7,24 +9,26 @@ export interface AgentDefinition {
|
|
|
7
9
|
color?: string;
|
|
8
10
|
model?: string;
|
|
9
11
|
tools?: Record<string, boolean>;
|
|
12
|
+
/** Skills declared in agent frontmatter and merged with config-level skills at runtime. */
|
|
13
|
+
skills?: string[];
|
|
10
14
|
hidden?: boolean;
|
|
11
15
|
}
|
|
12
16
|
/**
|
|
13
|
-
* Load
|
|
17
|
+
* Load agents from a project directory.
|
|
14
18
|
*
|
|
15
19
|
* Priority order:
|
|
16
|
-
* 1. Project .drs/agents/<name>/agent.md (DRS-specific overrides/custom)
|
|
20
|
+
* 1. Project .drs/agents/<namespace>/<name>/agent.md (DRS-specific overrides/custom)
|
|
17
21
|
* 2. Built-in agents shipped with DRS (.pi/agents)
|
|
18
22
|
*/
|
|
19
|
-
export declare function
|
|
23
|
+
export declare function loadAgents(projectPath: string, config?: DRSConfig): AgentDefinition[];
|
|
20
24
|
/**
|
|
21
25
|
* Get a specific agent by name
|
|
22
26
|
*/
|
|
23
|
-
export declare function getAgent(projectPath: string,
|
|
27
|
+
export declare function getAgent(projectPath: string, agentId: string, config?: DRSConfig): AgentDefinition | null;
|
|
24
28
|
/**
|
|
25
|
-
* Get all
|
|
29
|
+
* Get all agents in a namespace.
|
|
26
30
|
*/
|
|
27
|
-
export declare function
|
|
31
|
+
export declare function getAgentsByNamespace(projectPath: string, namespace: string, config?: DRSConfig): AgentDefinition[];
|
|
28
32
|
/**
|
|
29
33
|
* List all available agents
|
|
30
34
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent-loader.d.ts","sourceRoot":"","sources":["../../src/runtime/agent-loader.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"agent-loader.d.ts","sourceRoot":"","sources":["../../src/runtime/agent-loader.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAIlD,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,2FAA2F;IAC3F,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAiBD;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,WAAW,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,SAAS,GAAG,eAAe,EAAE,CAyBrF;AAoHD;;GAEG;AACH,wBAAgB,QAAQ,CACtB,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,MAAM,EACf,MAAM,CAAC,EAAE,SAAS,GACjB,eAAe,GAAG,IAAI,CAGxB;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAClC,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,MAAM,EACjB,MAAM,CAAC,EAAE,SAAS,GACjB,eAAe,EAAE,CAGnB;AAED;;GAEG;AACH,wBAAgB,UAAU,CAAC,WAAW,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,SAAS,GAAG,MAAM,EAAE,CAG5E"}
|
|
@@ -1,32 +1,45 @@
|
|
|
1
1
|
import { readFileSync, readdirSync, existsSync, statSync } from 'fs';
|
|
2
2
|
import { join, relative } from 'path';
|
|
3
3
|
import * as yaml from 'yaml';
|
|
4
|
+
import { getAgentIdValidationError, parseAgentId } from '../lib/agent-id.js';
|
|
4
5
|
import { getBuiltInAgentPaths } from './built-in-paths.js';
|
|
5
|
-
import {
|
|
6
|
+
import { resolveAgentPaths } from './path-config.js';
|
|
7
|
+
class InvalidProjectAgentPathError extends Error {
|
|
8
|
+
}
|
|
9
|
+
/** Parse a frontmatter value into a trimmed, non-empty string array. */
|
|
10
|
+
function asStringArray(value) {
|
|
11
|
+
if (!Array.isArray(value)) {
|
|
12
|
+
return undefined;
|
|
13
|
+
}
|
|
14
|
+
const parsed = value
|
|
15
|
+
.map((entry) => (typeof entry === 'string' ? entry.trim() : ''))
|
|
16
|
+
.filter((entry) => entry.length > 0);
|
|
17
|
+
return parsed.length > 0 ? parsed : undefined;
|
|
18
|
+
}
|
|
6
19
|
/**
|
|
7
|
-
* Load
|
|
20
|
+
* Load agents from a project directory.
|
|
8
21
|
*
|
|
9
22
|
* Priority order:
|
|
10
|
-
* 1. Project .drs/agents/<name>/agent.md (DRS-specific overrides/custom)
|
|
23
|
+
* 1. Project .drs/agents/<namespace>/<name>/agent.md (DRS-specific overrides/custom)
|
|
11
24
|
* 2. Built-in agents shipped with DRS (.pi/agents)
|
|
12
25
|
*/
|
|
13
|
-
export function
|
|
26
|
+
export function loadAgents(projectPath, config) {
|
|
14
27
|
const agents = [];
|
|
15
28
|
const discovered = new Set();
|
|
16
|
-
const { agentsPath } =
|
|
17
|
-
const overrideAgents =
|
|
29
|
+
const { agentsPath } = resolveAgentPaths(projectPath, config);
|
|
30
|
+
const overrideAgents = discoverProjectAgents(agentsPath, agentsPath);
|
|
18
31
|
for (const agent of overrideAgents) {
|
|
19
|
-
if (!discovered.has(agent.
|
|
32
|
+
if (!discovered.has(agent.id)) {
|
|
20
33
|
agents.push(agent);
|
|
21
|
-
discovered.add(agent.
|
|
34
|
+
discovered.add(agent.id);
|
|
22
35
|
}
|
|
23
36
|
}
|
|
24
37
|
for (const builtInPath of getBuiltInAgentPaths()) {
|
|
25
38
|
const builtInAgents = discoverAgents(builtInPath, builtInPath);
|
|
26
39
|
for (const agent of builtInAgents) {
|
|
27
|
-
if (!discovered.has(agent.
|
|
40
|
+
if (!discovered.has(agent.id)) {
|
|
28
41
|
agents.push(agent);
|
|
29
|
-
discovered.add(agent.
|
|
42
|
+
discovered.add(agent.id);
|
|
30
43
|
}
|
|
31
44
|
}
|
|
32
45
|
}
|
|
@@ -62,7 +75,7 @@ function discoverAgents(basePath, currentPath) {
|
|
|
62
75
|
/**
|
|
63
76
|
* Parse an agent markdown file and extract frontmatter
|
|
64
77
|
*/
|
|
65
|
-
function parseAgentFile(filePath, basePath, nameOverride) {
|
|
78
|
+
function parseAgentFile(filePath, basePath, nameOverride, failOnInvalidAgentId = false) {
|
|
66
79
|
try {
|
|
67
80
|
const content = readFileSync(filePath, 'utf-8');
|
|
68
81
|
// Extract YAML frontmatter
|
|
@@ -73,57 +86,70 @@ function parseAgentFile(filePath, basePath, nameOverride) {
|
|
|
73
86
|
}
|
|
74
87
|
const frontmatter = yaml.parse(frontmatterMatch[1]) ?? {};
|
|
75
88
|
// Generate agent name from relative path
|
|
76
|
-
const
|
|
89
|
+
const agentId = nameOverride ?? relative(basePath, filePath).replace(/\.md$/, '').replace(/\\/g, '/');
|
|
90
|
+
const parsedAgentId = parseAgentId(agentId);
|
|
91
|
+
if (!parsedAgentId) {
|
|
92
|
+
const guidance = !agentId.includes('/')
|
|
93
|
+
? ` Move it to .drs/agents/review/${agentId}/agent.md for a review agent.`
|
|
94
|
+
: '';
|
|
95
|
+
const error = new InvalidProjectAgentPathError(`${getAgentIdValidationError(agentId)} Project agents must be stored as .drs/agents/<namespace>/<name>/agent.md.${guidance} File: ${filePath}`);
|
|
96
|
+
throw error;
|
|
97
|
+
}
|
|
77
98
|
const prompt = content.slice(frontmatterMatch[0].length).trim();
|
|
78
99
|
return {
|
|
79
|
-
|
|
100
|
+
id: agentId,
|
|
101
|
+
namespace: parsedAgentId.namespace,
|
|
102
|
+
name: parsedAgentId.name,
|
|
80
103
|
path: filePath,
|
|
81
104
|
description: frontmatter.description || '',
|
|
82
105
|
prompt,
|
|
83
106
|
color: frontmatter.color,
|
|
84
107
|
model: frontmatter.model,
|
|
85
108
|
tools: frontmatter.tools,
|
|
109
|
+
skills: asStringArray(frontmatter.skills),
|
|
86
110
|
hidden: frontmatter.hidden || false,
|
|
87
111
|
};
|
|
88
112
|
}
|
|
89
113
|
catch (error) {
|
|
114
|
+
if (failOnInvalidAgentId && error instanceof InvalidProjectAgentPathError) {
|
|
115
|
+
throw error;
|
|
116
|
+
}
|
|
90
117
|
console.error(`Error parsing agent file ${filePath}:`, error);
|
|
91
118
|
return null;
|
|
92
119
|
}
|
|
93
120
|
}
|
|
94
121
|
/**
|
|
95
|
-
* Discover
|
|
122
|
+
* Discover project agents from .drs/agents/<namespace>/<name>/agent.md
|
|
96
123
|
*/
|
|
97
|
-
function
|
|
124
|
+
function discoverProjectAgents(basePath, currentPath) {
|
|
98
125
|
const files = traverseDirectory(basePath, currentPath, (entry) => entry === 'agent.md');
|
|
99
126
|
return files
|
|
100
127
|
.map((fullPath) => {
|
|
101
128
|
const relativePath = relative(basePath, fullPath).replace(/\\/g, '/');
|
|
102
|
-
const
|
|
103
|
-
|
|
104
|
-
return parseAgentFile(fullPath, basePath, agentName);
|
|
129
|
+
const agentId = relativePath.replace(/\/agent\.md$/, '');
|
|
130
|
+
return parseAgentFile(fullPath, basePath, agentId, true);
|
|
105
131
|
})
|
|
106
132
|
.filter((agent) => Boolean(agent));
|
|
107
133
|
}
|
|
108
134
|
/**
|
|
109
135
|
* Get a specific agent by name
|
|
110
136
|
*/
|
|
111
|
-
export function getAgent(projectPath,
|
|
112
|
-
const agents =
|
|
113
|
-
return agents.find((a) => a.
|
|
137
|
+
export function getAgent(projectPath, agentId, config) {
|
|
138
|
+
const agents = loadAgents(projectPath, config);
|
|
139
|
+
return agents.find((a) => a.id === agentId) ?? null;
|
|
114
140
|
}
|
|
115
141
|
/**
|
|
116
|
-
* Get all
|
|
142
|
+
* Get all agents in a namespace.
|
|
117
143
|
*/
|
|
118
|
-
export function
|
|
119
|
-
const agents =
|
|
120
|
-
return agents.filter((a) => a.
|
|
144
|
+
export function getAgentsByNamespace(projectPath, namespace, config) {
|
|
145
|
+
const agents = loadAgents(projectPath, config);
|
|
146
|
+
return agents.filter((a) => a.namespace === namespace);
|
|
121
147
|
}
|
|
122
148
|
/**
|
|
123
149
|
* List all available agents
|
|
124
150
|
*/
|
|
125
151
|
export function listAgents(projectPath, config) {
|
|
126
|
-
const agents =
|
|
127
|
-
return agents.map((a) => a.
|
|
152
|
+
const agents = loadAgents(projectPath, config);
|
|
153
|
+
return agents.map((a) => a.id);
|
|
128
154
|
}
|
|
129
155
|
//# sourceMappingURL=agent-loader.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent-loader.js","sourceRoot":"","sources":["../../src/runtime/agent-loader.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,IAAI,CAAC;AACrE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,MAAM,CAAC;AACtC,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"agent-loader.js","sourceRoot":"","sources":["../../src/runtime/agent-loader.ts"],"names":[],"mappings":"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"}
|
|
@@ -2,10 +2,10 @@ import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from 'fs';
|
|
|
2
2
|
import { tmpdir } from 'os';
|
|
3
3
|
import { join, resolve } from 'path';
|
|
4
4
|
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
5
|
-
import { getAgent,
|
|
5
|
+
import { getAgent, getAgentsByNamespace, listAgents, loadAgents } from './agent-loader.js';
|
|
6
6
|
function createConfig(agentsPath) {
|
|
7
7
|
return {
|
|
8
|
-
|
|
8
|
+
agents: {
|
|
9
9
|
paths: {
|
|
10
10
|
agents: agentsPath,
|
|
11
11
|
},
|
|
@@ -27,81 +27,72 @@ describe('agent-loader path resolution', () => {
|
|
|
27
27
|
it('loads override agents from configured repo-relative path', () => {
|
|
28
28
|
const projectRoot = createTempDir('drs-agent-loader-');
|
|
29
29
|
const customAgentsRoot = join(projectRoot, 'config', 'agents');
|
|
30
|
-
const customAgentPath = join(customAgentsRoot, '
|
|
31
|
-
mkdirSync(join(customAgentsRoot, '
|
|
32
|
-
writeFileSync(customAgentPath, `---\ndescription: Custom
|
|
33
|
-
const agents =
|
|
34
|
-
const
|
|
35
|
-
expect(
|
|
36
|
-
expect(
|
|
37
|
-
expect(
|
|
38
|
-
expect(agents.some((agent) => agent.
|
|
30
|
+
const customAgentPath = join(customAgentsRoot, 'review', 'unified-reviewer', 'agent.md');
|
|
31
|
+
mkdirSync(join(customAgentsRoot, 'review', 'unified-reviewer'), { recursive: true });
|
|
32
|
+
writeFileSync(customAgentPath, `---\ndescription: Custom unified override\nmodel: anthropic/custom-unified\n---\n\nCustom unified instructions\n`);
|
|
33
|
+
const agents = loadAgents(projectRoot, createConfig('config/agents'));
|
|
34
|
+
const unifiedAgent = agents.find((agent) => agent.id === 'review/unified-reviewer');
|
|
35
|
+
expect(unifiedAgent).toBeDefined();
|
|
36
|
+
expect(unifiedAgent?.description).toBe('Custom unified override');
|
|
37
|
+
expect(unifiedAgent?.path).toBe(resolve(customAgentPath));
|
|
38
|
+
expect(agents.some((agent) => agent.id === 'review/unified-reviewer')).toBe(true);
|
|
39
39
|
});
|
|
40
40
|
it('custom agent override replaces built-in prompt and model', () => {
|
|
41
41
|
const projectRoot = createTempDir('drs-agent-override-');
|
|
42
42
|
const agentsDir = join(projectRoot, '.drs', 'agents');
|
|
43
|
-
mkdirSync(join(agentsDir, '
|
|
44
|
-
writeFileSync(join(agentsDir, '
|
|
43
|
+
mkdirSync(join(agentsDir, 'review', 'unified-reviewer'), { recursive: true });
|
|
44
|
+
writeFileSync(join(agentsDir, 'review', 'unified-reviewer', 'agent.md'), [
|
|
45
45
|
'---',
|
|
46
|
-
'description: Project-specific
|
|
47
|
-
'model: openai/gpt-4o',
|
|
46
|
+
'description: Project-specific unified reviewer',
|
|
47
|
+
'model: openai/gpt-4o-mini',
|
|
48
48
|
'---',
|
|
49
49
|
'',
|
|
50
|
-
'You are a
|
|
51
|
-
'Focus on
|
|
50
|
+
'You are a unified reviewer for our Rails application.',
|
|
51
|
+
'Focus on correctness and security issues.',
|
|
52
52
|
'',
|
|
53
53
|
].join('\n'));
|
|
54
|
-
const agents =
|
|
55
|
-
const
|
|
56
|
-
expect(
|
|
57
|
-
expect(
|
|
58
|
-
expect(
|
|
59
|
-
expect(
|
|
60
|
-
expect(
|
|
54
|
+
const agents = loadAgents(projectRoot);
|
|
55
|
+
const unifiedAgent = agents.find((a) => a.id === 'review/unified-reviewer');
|
|
56
|
+
expect(unifiedAgent).toBeDefined();
|
|
57
|
+
expect(unifiedAgent?.description).toBe('Project-specific unified reviewer');
|
|
58
|
+
expect(unifiedAgent?.model).toBe('openai/gpt-4o-mini');
|
|
59
|
+
expect(unifiedAgent?.prompt).toContain('Rails application');
|
|
60
|
+
expect(unifiedAgent?.prompt).toContain('correctness and security');
|
|
61
61
|
// Must NOT contain the built-in prompt
|
|
62
|
-
expect(
|
|
63
|
-
// Other built-in agents still loaded
|
|
64
|
-
const qualityAgent = agents.find((a) => a.name === 'review/quality');
|
|
65
|
-
expect(qualityAgent).toBeDefined();
|
|
66
|
-
expect(qualityAgent?.path).toMatch(/\.pi[\\/]agents[\\/]review[\\/]quality\.md$/);
|
|
62
|
+
expect(unifiedAgent?.prompt).not.toContain('Security Vulnerability Assessment');
|
|
67
63
|
});
|
|
68
|
-
it('
|
|
64
|
+
it('supports custom review agents alongside packaged unified reviewer', () => {
|
|
69
65
|
const projectRoot = createTempDir('drs-agent-multi-override-');
|
|
70
66
|
const agentsDir = join(projectRoot, '.drs', 'agents');
|
|
71
|
-
mkdirSync(join(agentsDir, '
|
|
72
|
-
mkdirSync(join(agentsDir, '
|
|
73
|
-
writeFileSync(join(agentsDir, '
|
|
74
|
-
writeFileSync(join(agentsDir, '
|
|
75
|
-
const agents =
|
|
76
|
-
const
|
|
77
|
-
const
|
|
78
|
-
const
|
|
79
|
-
expect(
|
|
80
|
-
expect(
|
|
81
|
-
|
|
82
|
-
expect(quality?.path).toMatch(/\.pi[\\/]agents[\\/]review[\\/]quality\.md$/);
|
|
67
|
+
mkdirSync(join(agentsDir, 'review', 'sql-reviewer'), { recursive: true });
|
|
68
|
+
mkdirSync(join(agentsDir, 'review', 'api-reviewer'), { recursive: true });
|
|
69
|
+
writeFileSync(join(agentsDir, 'review', 'sql-reviewer', 'agent.md'), '---\ndescription: Custom SQL reviewer\n---\n\nCustom SQL prompt\n');
|
|
70
|
+
writeFileSync(join(agentsDir, 'review', 'api-reviewer', 'agent.md'), '---\ndescription: Custom API reviewer\n---\n\nCustom API prompt\n');
|
|
71
|
+
const agents = loadAgents(projectRoot);
|
|
72
|
+
const sqlReviewer = agents.find((a) => a.id === 'review/sql-reviewer');
|
|
73
|
+
const apiReviewer = agents.find((a) => a.id === 'review/api-reviewer');
|
|
74
|
+
const unified = agents.find((a) => a.id === 'review/unified-reviewer');
|
|
75
|
+
expect(sqlReviewer?.prompt).toBe('Custom SQL prompt');
|
|
76
|
+
expect(apiReviewer?.prompt).toBe('Custom API prompt');
|
|
77
|
+
expect(unified?.path).toMatch(/\.pi[\\/]agents[\\/]review[\\/]unified-reviewer\.md$/);
|
|
83
78
|
});
|
|
84
|
-
it('loads
|
|
85
|
-
const agents =
|
|
86
|
-
const reviewAgentNames = new Set(agents.filter((agent) => agent.
|
|
87
|
-
expect(reviewAgentNames.has('review/
|
|
88
|
-
|
|
89
|
-
expect(
|
|
90
|
-
expect(
|
|
91
|
-
expect(reviewAgentNames.has('review/documentation')).toBe(true);
|
|
92
|
-
const securityAgent = agents.find((agent) => agent.name === 'review/security');
|
|
93
|
-
expect(securityAgent?.prompt).toContain('Security Vulnerability Assessment');
|
|
94
|
-
expect(securityAgent?.path).toMatch(/\.pi[\\/]agents[\\/]review[\\/]security\.md$/);
|
|
79
|
+
it('loads packaged unified review agent', () => {
|
|
80
|
+
const agents = loadAgents(process.cwd());
|
|
81
|
+
const reviewAgentNames = new Set(agents.filter((agent) => agent.namespace === 'review').map((agent) => agent.id));
|
|
82
|
+
expect(reviewAgentNames.has('review/unified-reviewer')).toBe(true);
|
|
83
|
+
const unifiedAgent = agents.find((agent) => agent.id === 'review/unified-reviewer');
|
|
84
|
+
expect(unifiedAgent?.prompt).toContain('unified code review agent');
|
|
85
|
+
expect(unifiedAgent?.path).toMatch(/\.pi[\\/]agents[\\/]review[\\/]unified-reviewer\.md$/);
|
|
95
86
|
});
|
|
96
87
|
it('throws actionable error when configured agent path is invalid', () => {
|
|
97
88
|
const projectRoot = createTempDir('drs-agent-loader-invalid-');
|
|
98
|
-
expect(() =>
|
|
89
|
+
expect(() => loadAgents(projectRoot, createConfig('missing/agents'))).toThrow('agents.paths.agents');
|
|
99
90
|
});
|
|
100
91
|
it('override preserves frontmatter fields: color, tools, hidden', () => {
|
|
101
92
|
const projectRoot = createTempDir('drs-agent-frontmatter-');
|
|
102
93
|
const agentsDir = join(projectRoot, '.drs', 'agents');
|
|
103
|
-
mkdirSync(join(agentsDir, '
|
|
104
|
-
writeFileSync(join(agentsDir, '
|
|
94
|
+
mkdirSync(join(agentsDir, 'review', 'unified-reviewer'), { recursive: true });
|
|
95
|
+
writeFileSync(join(agentsDir, 'review', 'unified-reviewer', 'agent.md'), [
|
|
105
96
|
'---',
|
|
106
97
|
'description: Themed agent',
|
|
107
98
|
'color: "#FF5733"',
|
|
@@ -109,33 +100,45 @@ describe('agent-loader path resolution', () => {
|
|
|
109
100
|
'tools:',
|
|
110
101
|
' Read: true',
|
|
111
102
|
' Grep: false',
|
|
103
|
+
'skills:',
|
|
104
|
+
' - secure-code-review',
|
|
105
|
+
' - dependency-audit',
|
|
112
106
|
'---',
|
|
113
107
|
'',
|
|
114
108
|
'Custom prompt with all frontmatter fields.',
|
|
115
109
|
'',
|
|
116
110
|
].join('\n'));
|
|
117
|
-
const agents =
|
|
118
|
-
const
|
|
119
|
-
expect(
|
|
120
|
-
expect(
|
|
121
|
-
expect(
|
|
122
|
-
expect(
|
|
111
|
+
const agents = loadAgents(projectRoot);
|
|
112
|
+
const unified = agents.find((a) => a.id === 'review/unified-reviewer');
|
|
113
|
+
expect(unified?.color).toBe('#FF5733');
|
|
114
|
+
expect(unified?.hidden).toBe(true);
|
|
115
|
+
expect(unified?.tools).toEqual({ Read: true, Grep: false });
|
|
116
|
+
expect(unified?.skills).toEqual(['secure-code-review', 'dependency-audit']);
|
|
117
|
+
expect(unified?.prompt).toBe('Custom prompt with all frontmatter fields.');
|
|
123
118
|
});
|
|
124
119
|
it('agent.md without frontmatter is skipped with warning', () => {
|
|
125
120
|
const projectRoot = createTempDir('drs-agent-no-frontmatter-');
|
|
126
121
|
const agentsDir = join(projectRoot, '.drs', 'agents');
|
|
127
|
-
mkdirSync(join(agentsDir, '
|
|
128
|
-
writeFileSync(join(agentsDir, '
|
|
122
|
+
mkdirSync(join(agentsDir, 'review', 'unified-reviewer'), { recursive: true });
|
|
123
|
+
writeFileSync(join(agentsDir, 'review', 'unified-reviewer', 'agent.md'), 'Just plain text, no frontmatter at all.\n');
|
|
129
124
|
const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => { });
|
|
130
|
-
const agents =
|
|
131
|
-
// Override skipped — built-in
|
|
132
|
-
const
|
|
133
|
-
expect(
|
|
125
|
+
const agents = loadAgents(projectRoot);
|
|
126
|
+
// Override skipped — built-in unified reviewer still loaded
|
|
127
|
+
const unified = agents.find((a) => a.id === 'review/unified-reviewer');
|
|
128
|
+
expect(unified?.path).toMatch(/\.pi[\\/]agents[\\/]review[\\/]unified-reviewer\.md$/);
|
|
134
129
|
expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining('No frontmatter'));
|
|
135
130
|
warnSpy.mockRestore();
|
|
136
131
|
});
|
|
132
|
+
it('fails flat project agents with migration guidance', () => {
|
|
133
|
+
const projectRoot = createTempDir('drs-agent-flat-');
|
|
134
|
+
const agentsDir = join(projectRoot, '.drs', 'agents');
|
|
135
|
+
mkdirSync(join(agentsDir, 'unified-reviewer'), { recursive: true });
|
|
136
|
+
writeFileSync(join(agentsDir, 'unified-reviewer', 'agent.md'), '---\ndescription: Flat security\n---\n\nFlat prompt\n');
|
|
137
|
+
expect(() => loadAgents(projectRoot)).toThrow('.drs/agents/<namespace>/<name>/agent.md');
|
|
138
|
+
expect(() => loadAgents(projectRoot)).toThrow('.drs/agents/review/unified-reviewer/agent.md');
|
|
139
|
+
});
|
|
137
140
|
});
|
|
138
|
-
describe('getAgent /
|
|
141
|
+
describe('getAgent / getAgentsByNamespace / listAgents', () => {
|
|
139
142
|
const tempDirs = [];
|
|
140
143
|
function createTempDir(prefix) {
|
|
141
144
|
const dir = mkdtempSync(join(tmpdir(), prefix));
|
|
@@ -148,10 +151,10 @@ describe('getAgent / getReviewAgents / listAgents', () => {
|
|
|
148
151
|
}
|
|
149
152
|
});
|
|
150
153
|
it('getAgent returns a specific agent by full name', () => {
|
|
151
|
-
const agent = getAgent(process.cwd(), 'review/
|
|
154
|
+
const agent = getAgent(process.cwd(), 'review/unified-reviewer');
|
|
152
155
|
expect(agent).not.toBeNull();
|
|
153
|
-
expect(agent?.
|
|
154
|
-
expect(agent?.prompt).toContain('
|
|
156
|
+
expect(agent?.id).toBe('review/unified-reviewer');
|
|
157
|
+
expect(agent?.prompt).toContain('unified code review agent');
|
|
155
158
|
});
|
|
156
159
|
it('getAgent returns null for non-existent agent', () => {
|
|
157
160
|
const agent = getAgent(process.cwd(), 'review/nonexistent');
|
|
@@ -160,42 +163,38 @@ describe('getAgent / getReviewAgents / listAgents', () => {
|
|
|
160
163
|
it('getAgent returns override when present', () => {
|
|
161
164
|
const projectRoot = createTempDir('drs-get-agent-');
|
|
162
165
|
const agentsDir = join(projectRoot, '.drs', 'agents');
|
|
163
|
-
mkdirSync(join(agentsDir, '
|
|
164
|
-
writeFileSync(join(agentsDir, '
|
|
165
|
-
const agent = getAgent(projectRoot, 'review/
|
|
166
|
+
mkdirSync(join(agentsDir, 'review', 'unified-reviewer'), { recursive: true });
|
|
167
|
+
writeFileSync(join(agentsDir, 'review', 'unified-reviewer', 'agent.md'), '---\ndescription: Override via getAgent\n---\n\nOverride prompt\n');
|
|
168
|
+
const agent = getAgent(projectRoot, 'review/unified-reviewer');
|
|
166
169
|
expect(agent?.description).toBe('Override via getAgent');
|
|
167
170
|
expect(agent?.prompt).toBe('Override prompt');
|
|
168
171
|
});
|
|
169
|
-
it('
|
|
170
|
-
const agents =
|
|
171
|
-
expect(agents.length).toBeGreaterThanOrEqual(
|
|
172
|
-
expect(agents.every((a) => a.
|
|
172
|
+
it('getAgentsByNamespace returns only review agents', () => {
|
|
173
|
+
const agents = getAgentsByNamespace(process.cwd(), 'review');
|
|
174
|
+
expect(agents.length).toBeGreaterThanOrEqual(1);
|
|
175
|
+
expect(agents.every((a) => a.namespace === 'review')).toBe(true);
|
|
173
176
|
});
|
|
174
|
-
it('
|
|
177
|
+
it('getAgentsByNamespace includes overrides', () => {
|
|
175
178
|
const projectRoot = createTempDir('drs-get-review-agents-');
|
|
176
179
|
const agentsDir = join(projectRoot, '.drs', 'agents');
|
|
177
|
-
mkdirSync(join(agentsDir, '
|
|
178
|
-
writeFileSync(join(agentsDir, '
|
|
179
|
-
const agents =
|
|
180
|
-
const
|
|
181
|
-
expect(
|
|
180
|
+
mkdirSync(join(agentsDir, 'review', 'unified-reviewer'), { recursive: true });
|
|
181
|
+
writeFileSync(join(agentsDir, 'review', 'unified-reviewer', 'agent.md'), '---\ndescription: Custom unified\n---\n\nCustom unified prompt\n');
|
|
182
|
+
const agents = getAgentsByNamespace(projectRoot, 'review');
|
|
183
|
+
const unified = agents.find((a) => a.id === 'review/unified-reviewer');
|
|
184
|
+
expect(unified?.description).toBe('Custom unified');
|
|
182
185
|
});
|
|
183
186
|
it('listAgents returns all agent names as strings', () => {
|
|
184
187
|
const names = listAgents(process.cwd());
|
|
185
|
-
expect(names).toContain('review/
|
|
186
|
-
expect(names).toContain('review/quality');
|
|
187
|
-
expect(names).toContain('review/style');
|
|
188
|
-
expect(names).toContain('review/performance');
|
|
189
|
-
expect(names).toContain('review/documentation');
|
|
188
|
+
expect(names).toContain('review/unified-reviewer');
|
|
190
189
|
});
|
|
191
190
|
it('listAgents reflects overrides without duplication', () => {
|
|
192
191
|
const projectRoot = createTempDir('drs-list-agents-');
|
|
193
192
|
const agentsDir = join(projectRoot, '.drs', 'agents');
|
|
194
|
-
mkdirSync(join(agentsDir, '
|
|
195
|
-
writeFileSync(join(agentsDir, '
|
|
193
|
+
mkdirSync(join(agentsDir, 'review', 'unified-reviewer'), { recursive: true });
|
|
194
|
+
writeFileSync(join(agentsDir, 'review', 'unified-reviewer', 'agent.md'), '---\ndescription: Override\n---\n\nOverride prompt\n');
|
|
196
195
|
const names = listAgents(projectRoot);
|
|
197
|
-
const
|
|
198
|
-
expect(
|
|
196
|
+
const unifiedCount = names.filter((n) => n === 'review/unified-reviewer').length;
|
|
197
|
+
expect(unifiedCount).toBe(1);
|
|
199
198
|
});
|
|
200
199
|
});
|
|
201
200
|
describe('new custom agent (not overriding built-in)', () => {
|
|
@@ -213,8 +212,8 @@ describe('new custom agent (not overriding built-in)', () => {
|
|
|
213
212
|
it('discovers a brand new agent alongside built-ins', () => {
|
|
214
213
|
const projectRoot = createTempDir('drs-new-agent-');
|
|
215
214
|
const agentsDir = join(projectRoot, '.drs', 'agents');
|
|
216
|
-
mkdirSync(join(agentsDir, 'rails-reviewer'), { recursive: true });
|
|
217
|
-
writeFileSync(join(agentsDir, 'rails-reviewer', 'agent.md'), [
|
|
215
|
+
mkdirSync(join(agentsDir, 'review', 'rails-reviewer'), { recursive: true });
|
|
216
|
+
writeFileSync(join(agentsDir, 'review', 'rails-reviewer', 'agent.md'), [
|
|
218
217
|
'---',
|
|
219
218
|
'description: Rails-specific code reviewer',
|
|
220
219
|
'model: anthropic/claude-sonnet-4-5-20250929',
|
|
@@ -225,56 +224,54 @@ describe('new custom agent (not overriding built-in)', () => {
|
|
|
225
224
|
'Focus on mass assignment, strong params, and N+1 queries.',
|
|
226
225
|
'',
|
|
227
226
|
].join('\n'));
|
|
228
|
-
const agents =
|
|
227
|
+
const agents = loadAgents(projectRoot);
|
|
229
228
|
// New agent discovered
|
|
230
|
-
const rails = agents.find((a) => a.
|
|
229
|
+
const rails = agents.find((a) => a.id === 'review/rails-reviewer');
|
|
231
230
|
expect(rails).toBeDefined();
|
|
232
231
|
expect(rails?.description).toBe('Rails-specific code reviewer');
|
|
233
232
|
expect(rails?.model).toBe('anthropic/claude-sonnet-4-5-20250929');
|
|
234
233
|
expect(rails?.color).toBe('#CC0000');
|
|
235
234
|
expect(rails?.prompt).toContain('mass assignment');
|
|
236
235
|
expect(rails?.prompt).toContain('N+1 queries');
|
|
237
|
-
//
|
|
238
|
-
expect(agents.some((a) => a.
|
|
239
|
-
expect(agents.some((a) => a.name === 'review/quality')).toBe(true);
|
|
236
|
+
// Packaged unified reviewer still present
|
|
237
|
+
expect(agents.some((a) => a.id === 'review/unified-reviewer')).toBe(true);
|
|
240
238
|
});
|
|
241
239
|
it('new agent is accessible via getAgent', () => {
|
|
242
240
|
const projectRoot = createTempDir('drs-new-get-agent-');
|
|
243
241
|
const agentsDir = join(projectRoot, '.drs', 'agents');
|
|
244
|
-
mkdirSync(join(agentsDir, 'api-reviewer'), { recursive: true });
|
|
245
|
-
writeFileSync(join(agentsDir, 'api-reviewer', 'agent.md'), '---\ndescription: API contract reviewer\n---\n\nReview REST API contracts.\n');
|
|
242
|
+
mkdirSync(join(agentsDir, 'review', 'api-reviewer'), { recursive: true });
|
|
243
|
+
writeFileSync(join(agentsDir, 'review', 'api-reviewer', 'agent.md'), '---\ndescription: API contract reviewer\n---\n\nReview REST API contracts.\n');
|
|
246
244
|
const agent = getAgent(projectRoot, 'review/api-reviewer');
|
|
247
245
|
expect(agent).not.toBeNull();
|
|
248
246
|
expect(agent?.description).toBe('API contract reviewer');
|
|
249
247
|
expect(agent?.prompt).toBe('Review REST API contracts.');
|
|
250
248
|
});
|
|
251
|
-
it('new agent appears in
|
|
249
|
+
it('new agent appears in getAgentsByNamespace and listAgents', () => {
|
|
252
250
|
const projectRoot = createTempDir('drs-new-list-');
|
|
253
251
|
const agentsDir = join(projectRoot, '.drs', 'agents');
|
|
254
|
-
mkdirSync(join(agentsDir, 'accessibility'), { recursive: true });
|
|
255
|
-
writeFileSync(join(agentsDir, 'accessibility', 'agent.md'), '---\ndescription: Accessibility reviewer\n---\n\nCheck WCAG compliance.\n');
|
|
256
|
-
const reviewAgents =
|
|
257
|
-
expect(reviewAgents.some((a) => a.
|
|
252
|
+
mkdirSync(join(agentsDir, 'review', 'accessibility'), { recursive: true });
|
|
253
|
+
writeFileSync(join(agentsDir, 'review', 'accessibility', 'agent.md'), '---\ndescription: Accessibility reviewer\n---\n\nCheck WCAG compliance.\n');
|
|
254
|
+
const reviewAgents = getAgentsByNamespace(projectRoot, 'review');
|
|
255
|
+
expect(reviewAgents.some((a) => a.id === 'review/accessibility')).toBe(true);
|
|
258
256
|
const names = listAgents(projectRoot);
|
|
259
257
|
expect(names).toContain('review/accessibility');
|
|
260
258
|
});
|
|
261
259
|
it('new agent coexists with overrides of built-ins', () => {
|
|
262
260
|
const projectRoot = createTempDir('drs-new-plus-override-');
|
|
263
261
|
const agentsDir = join(projectRoot, '.drs', 'agents');
|
|
264
|
-
mkdirSync(join(agentsDir, '
|
|
265
|
-
mkdirSync(join(agentsDir, 'rails-reviewer'), { recursive: true });
|
|
266
|
-
writeFileSync(join(agentsDir, '
|
|
267
|
-
writeFileSync(join(agentsDir, 'rails-reviewer', 'agent.md'), '---\ndescription: Rails reviewer\n---\n\nRails review prompt\n');
|
|
268
|
-
const agents =
|
|
262
|
+
mkdirSync(join(agentsDir, 'review', 'unified-reviewer'), { recursive: true });
|
|
263
|
+
mkdirSync(join(agentsDir, 'review', 'rails-reviewer'), { recursive: true });
|
|
264
|
+
writeFileSync(join(agentsDir, 'review', 'unified-reviewer', 'agent.md'), '---\ndescription: Custom unified\n---\n\nCustom unified prompt\n');
|
|
265
|
+
writeFileSync(join(agentsDir, 'review', 'rails-reviewer', 'agent.md'), '---\ndescription: Rails reviewer\n---\n\nRails review prompt\n');
|
|
266
|
+
const agents = loadAgents(projectRoot);
|
|
269
267
|
// Override replaces built-in
|
|
270
|
-
const
|
|
271
|
-
expect(
|
|
268
|
+
const unified = agents.find((a) => a.id === 'review/unified-reviewer');
|
|
269
|
+
expect(unified?.prompt).toBe('Custom unified prompt');
|
|
272
270
|
// New agent added
|
|
273
|
-
const rails = agents.find((a) => a.
|
|
271
|
+
const rails = agents.find((a) => a.id === 'review/rails-reviewer');
|
|
274
272
|
expect(rails?.prompt).toBe('Rails review prompt');
|
|
275
|
-
//
|
|
276
|
-
expect(agents.some((a) => a.
|
|
277
|
-
expect(agents.some((a) => a.name === 'review/style')).toBe(true);
|
|
273
|
+
// Packaged unified reviewer can be overridden while custom agents coexist
|
|
274
|
+
expect(agents.some((a) => a.id === 'review/unified-reviewer')).toBe(true);
|
|
278
275
|
});
|
|
279
276
|
});
|
|
280
277
|
//# sourceMappingURL=agent-loader.test.js.map
|