@studio-foundation/runner 0.3.0-beta.1 → 0.3.0-beta.6
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/package.json +6 -3
- package/ARCHITECTURE.md +0 -53
- package/configs/agents/analyst.agent.yaml +0 -31
- package/configs/agents/code-generator.agent.yaml +0 -31
- package/configs/agents/generic.agent.yaml +0 -23
- package/src/__tests__/script-executor.test.ts +0 -180
- package/src/index.ts +0 -68
- package/src/integrations/integration-loader.test.ts +0 -88
- package/src/integrations/integration-loader.ts +0 -68
- package/src/middleware/anonymization.ts +0 -38
- package/src/plugins/index.ts +0 -4
- package/src/plugins/mcp-client.test.ts +0 -148
- package/src/plugins/mcp-client.ts +0 -128
- package/src/plugins/oauth-provider.test.ts +0 -167
- package/src/plugins/oauth-provider.ts +0 -175
- package/src/plugins/plugin-loader.test.ts +0 -114
- package/src/plugins/plugin-loader.ts +0 -90
- package/src/prompt-builder.test.ts +0 -167
- package/src/prompt-builder.ts +0 -332
- package/src/providers/anthropic.test.ts +0 -101
- package/src/providers/anthropic.ts +0 -135
- package/src/providers/mock.ts +0 -57
- package/src/providers/ollama.test.ts +0 -166
- package/src/providers/ollama.ts +0 -152
- package/src/providers/openai-responses.ts +0 -212
- package/src/providers/openai.test.ts +0 -67
- package/src/providers/openai.ts +0 -139
- package/src/providers/provider.ts +0 -54
- package/src/providers/registry.ts +0 -77
- package/src/runner.test.ts +0 -343
- package/src/runner.ts +0 -396
- package/src/script-executor.ts +0 -107
- package/src/tools/builtin/git.ts +0 -311
- package/src/tools/builtin/patch.ts +0 -257
- package/src/tools/builtin/repo-manager.ts +0 -142
- package/src/tools/builtin/search.ts +0 -108
- package/src/tools/builtin/shell.ts +0 -82
- package/src/tools/builtin/studio-run.ts +0 -73
- package/src/tools/builtin/web-search.test.ts +0 -122
- package/src/tools/builtin/web-search.ts +0 -101
- package/src/tools/errors.test.ts +0 -12
- package/src/tools/errors.ts +0 -6
- package/src/tools/plugin-loader.test.ts +0 -130
- package/src/tools/plugin-loader.ts +0 -203
- package/src/tools/skills/README.md +0 -49
- package/src/tools/skills/skill-loader.test.ts +0 -106
- package/src/tools/skills/skill-loader.ts +0 -62
- package/src/tools/tool-executor.test.ts +0 -88
- package/src/tools/tool-executor.ts +0 -84
- package/src/tools/tool-registry.ts +0 -130
- package/src/tools/yaml-executor.ts +0 -120
- package/src/utils/race-signal.test.ts +0 -50
- package/src/utils/race-signal.ts +0 -17
- package/templates/integrations/linear.integration.yaml +0 -35
- package/templates/integrations/slack.integration.yaml +0 -22
- package/templates/integrations/webhook.integration.yaml +0 -17
- package/templates/tools/git.tool.yaml +0 -80
- package/templates/tools/repo-manager.tool.yaml +0 -64
- package/templates/tools/search.tool.yaml +0 -22
- package/templates/tools/shell.tool.yaml +0 -19
- package/templates/tools/web-search.tool.yaml +0 -24
- package/tests/anonymization-middleware.test.ts +0 -61
- package/tests/anthropic.test.ts +0 -87
- package/tests/apply-patch.test.ts +0 -355
- package/tests/fixtures/tools/test-builtin.tool.yaml +0 -14
- package/tests/fixtures/tools/test-shell.tool.yaml +0 -19
- package/tests/mock-provider.test.ts +0 -104
- package/tests/openai.test.ts +0 -72
- package/tests/plugin-loader.test.ts +0 -54
- package/tests/prompt-builder.test.ts +0 -468
- package/tests/runner-anonymization.test.ts +0 -89
- package/tests/runner.test.ts +0 -885
- package/tests/studio-run.test.ts +0 -94
- package/tests/tool-executor.test.ts +0 -115
- package/tests/tool-registry.test.ts +0 -84
- package/tests/yaml-executor.test.ts +0 -76
- package/tsconfig.json +0 -20
- package/vitest.config.ts +0 -7
|
@@ -1,130 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tool registry - manages tool definitions and execution
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { ToolDefinition } from '@studio-foundation/contracts';
|
|
6
|
-
|
|
7
|
-
/** Normalize tool name: dots → hyphens so both conventions work */
|
|
8
|
-
export function normalizeToolName(name: string): string {
|
|
9
|
-
return name.replace(/\./g, '-');
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export interface ToolResult {
|
|
13
|
-
success: boolean;
|
|
14
|
-
output: unknown;
|
|
15
|
-
error?: string;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export interface Tool {
|
|
19
|
-
name: string;
|
|
20
|
-
description: string;
|
|
21
|
-
parameters: Record<string, unknown>;
|
|
22
|
-
execute(args: Record<string, unknown>): Promise<ToolResult>;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export class ToolRegistry {
|
|
26
|
-
private tools: Map<string, Tool> = new Map();
|
|
27
|
-
private toolToPlugin: Map<string, string> = new Map(); // normalized name → plugin name
|
|
28
|
-
private pluginSnippets: Map<string, string> = new Map(); // plugin name → snippet
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Register a single tool (no plugin metadata).
|
|
32
|
-
*/
|
|
33
|
-
register(tool: Tool): void {
|
|
34
|
-
this.tools.set(tool.name, tool);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Register all tools belonging to a plugin.
|
|
39
|
-
* If promptSnippet is provided it will be returned by getActiveSnippets()
|
|
40
|
-
* whenever any tool from this plugin is in the registry.
|
|
41
|
-
*/
|
|
42
|
-
registerPlugin(pluginName: string, tools: Tool[], promptSnippet?: string): void {
|
|
43
|
-
for (const tool of tools) {
|
|
44
|
-
this.register(tool);
|
|
45
|
-
this.toolToPlugin.set(normalizeToolName(tool.name), pluginName);
|
|
46
|
-
}
|
|
47
|
-
if (promptSnippet) {
|
|
48
|
-
this.pluginSnippets.set(pluginName, promptSnippet);
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Get tool by name
|
|
54
|
-
*/
|
|
55
|
-
get(name: string): Tool | undefined {
|
|
56
|
-
return this.tools.get(name);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* Check if tool exists
|
|
61
|
-
*/
|
|
62
|
-
has(name: string): boolean {
|
|
63
|
-
return this.tools.has(name);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* List all tools
|
|
68
|
-
*/
|
|
69
|
-
list(): Tool[] {
|
|
70
|
-
return Array.from(this.tools.values());
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Convert tools to LLM tool definitions format
|
|
75
|
-
*/
|
|
76
|
-
toToolDefinitions(): ToolDefinition[] {
|
|
77
|
-
return this.list().map(tool => ({
|
|
78
|
-
name: tool.name,
|
|
79
|
-
description: tool.description,
|
|
80
|
-
parameters: tool.parameters
|
|
81
|
-
}));
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* Return prompt snippets for all plugins that have at least one tool
|
|
86
|
-
* currently in this registry.
|
|
87
|
-
*/
|
|
88
|
-
getActiveSnippets(): string[] {
|
|
89
|
-
const activePlugins = new Set<string>();
|
|
90
|
-
for (const toolName of this.tools.keys()) {
|
|
91
|
-
const plugin = this.toolToPlugin.get(normalizeToolName(toolName));
|
|
92
|
-
if (plugin) activePlugins.add(plugin);
|
|
93
|
-
}
|
|
94
|
-
return Array.from(activePlugins)
|
|
95
|
-
.map(p => this.pluginSnippets.get(p))
|
|
96
|
-
.filter((s): s is string => s !== undefined);
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* Create a new registry filtered to specific tool names.
|
|
101
|
-
* Normalizes dots to hyphens so both "repo_manager.write_file"
|
|
102
|
-
* and "repo_manager-write_file" match the registered name.
|
|
103
|
-
* Plugin snippet metadata is carried over for included tools.
|
|
104
|
-
*/
|
|
105
|
-
filter(allowedTools: string[]): ToolRegistry {
|
|
106
|
-
const filtered = new ToolRegistry();
|
|
107
|
-
for (const toolName of allowedTools) {
|
|
108
|
-
const tool = this.tools.get(toolName)
|
|
109
|
-
?? this.tools.get(normalizeToolName(toolName));
|
|
110
|
-
if (tool) {
|
|
111
|
-
filtered.register(tool);
|
|
112
|
-
// Carry over plugin metadata so getActiveSnippets() works on filtered registry
|
|
113
|
-
const pluginName = this.toolToPlugin.get(normalizeToolName(tool.name));
|
|
114
|
-
if (pluginName) {
|
|
115
|
-
filtered.toolToPlugin.set(normalizeToolName(tool.name), pluginName);
|
|
116
|
-
const snippet = this.pluginSnippets.get(pluginName);
|
|
117
|
-
if (snippet) filtered.pluginSnippets.set(pluginName, snippet);
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
return filtered;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
/**
|
|
125
|
-
* Create a full copy of this registry (all tools + plugin metadata).
|
|
126
|
-
*/
|
|
127
|
-
clone(): ToolRegistry {
|
|
128
|
-
return this.filter(Array.from(this.tools.keys()));
|
|
129
|
-
}
|
|
130
|
-
}
|
|
@@ -1,120 +0,0 @@
|
|
|
1
|
-
// runner/src/tools/yaml-executor.ts
|
|
2
|
-
import { execFile } from 'node:child_process';
|
|
3
|
-
import { promisify } from 'node:util';
|
|
4
|
-
import type { ParseOutputFormat } from '@studio-foundation/contracts';
|
|
5
|
-
|
|
6
|
-
const execFileAsync = promisify(execFile);
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Render a shell command template with parameter substitution.
|
|
10
|
-
*
|
|
11
|
-
* Supports:
|
|
12
|
-
* {{param}} → stringify value (empty string if undefined)
|
|
13
|
-
* {{#if param}}...{{/if}} → include block only when param is truthy
|
|
14
|
-
* {{#if param}}...{{else}}...{{/if}} → if/else block
|
|
15
|
-
* {{param | join 'sep'}} → join array with separator
|
|
16
|
-
* {{param | json}} → JSON.stringify(value)
|
|
17
|
-
*/
|
|
18
|
-
export function renderTemplate(
|
|
19
|
-
template: string,
|
|
20
|
-
params: Record<string, unknown>
|
|
21
|
-
): string {
|
|
22
|
-
let result = template;
|
|
23
|
-
|
|
24
|
-
// {{#if param}}...{{else}}...{{/if}} blocks (with else)
|
|
25
|
-
result = result.replace(
|
|
26
|
-
/\{\{#if (\w+)\}\}([\s\S]*?)\{\{else\}\}([\s\S]*?)\{\{\/if\}\}/g,
|
|
27
|
-
(_, key: string, trueBranch: string, falseBranch: string) =>
|
|
28
|
-
params[key] ? trueBranch : falseBranch
|
|
29
|
-
);
|
|
30
|
-
|
|
31
|
-
// {{#if param}}...{{/if}} blocks (without else)
|
|
32
|
-
result = result.replace(
|
|
33
|
-
/\{\{#if (\w+)\}\}([\s\S]*?)\{\{\/if\}\}/g,
|
|
34
|
-
(_, key: string, inner: string) => (params[key] ? inner : '')
|
|
35
|
-
);
|
|
36
|
-
|
|
37
|
-
// {{param | join 'sep'}} filter
|
|
38
|
-
result = result.replace(
|
|
39
|
-
/\{\{(\w+)\s*\|\s*join\s+'([^']*)'\}\}/g,
|
|
40
|
-
(_, key: string, sep: string) => {
|
|
41
|
-
const value = params[key];
|
|
42
|
-
return Array.isArray(value) ? value.join(sep) : String(value ?? '');
|
|
43
|
-
}
|
|
44
|
-
);
|
|
45
|
-
|
|
46
|
-
// {{param | join "sep"}} filter (double quotes variant)
|
|
47
|
-
result = result.replace(
|
|
48
|
-
/\{\{(\w+)\s*\|\s*join\s+"([^"]*)"\}\}/g,
|
|
49
|
-
(_, key: string, sep: string) => {
|
|
50
|
-
const value = params[key];
|
|
51
|
-
return Array.isArray(value) ? value.join(sep) : String(value ?? '');
|
|
52
|
-
}
|
|
53
|
-
);
|
|
54
|
-
|
|
55
|
-
// {{param | json}} filter
|
|
56
|
-
result = result.replace(
|
|
57
|
-
/\{\{(\w+)\s*\|\s*json\}\}/g,
|
|
58
|
-
(_, key: string) => JSON.stringify(params[key] ?? null)
|
|
59
|
-
);
|
|
60
|
-
|
|
61
|
-
// Plain {{param}} substitution
|
|
62
|
-
result = result.replace(
|
|
63
|
-
/\{\{(\w+)\}\}/g,
|
|
64
|
-
(_, key: string) => (params[key] === undefined ? '' : String(params[key]))
|
|
65
|
-
);
|
|
66
|
-
|
|
67
|
-
return result;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
export interface ShellResult {
|
|
71
|
-
success: boolean;
|
|
72
|
-
output: unknown;
|
|
73
|
-
error?: string;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* Execute a rendered shell command and parse the output.
|
|
78
|
-
*/
|
|
79
|
-
export async function executeShellCommand(
|
|
80
|
-
command: string,
|
|
81
|
-
parseOutput: ParseOutputFormat = 'text',
|
|
82
|
-
workingDir: string,
|
|
83
|
-
timeoutMs: number = 30_000,
|
|
84
|
-
env?: Record<string, string>
|
|
85
|
-
): Promise<ShellResult> {
|
|
86
|
-
try {
|
|
87
|
-
const { stdout } = await execFileAsync('sh', ['-c', command], {
|
|
88
|
-
cwd: workingDir,
|
|
89
|
-
timeout: timeoutMs,
|
|
90
|
-
maxBuffer: 10 * 1024 * 1024,
|
|
91
|
-
...(env ? { env: { ...process.env, ...env } } : {}),
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
const raw = stdout.trim();
|
|
95
|
-
|
|
96
|
-
if (parseOutput === 'json') {
|
|
97
|
-
try {
|
|
98
|
-
return { success: true, output: JSON.parse(raw) };
|
|
99
|
-
} catch {
|
|
100
|
-
return {
|
|
101
|
-
success: false,
|
|
102
|
-
output: undefined,
|
|
103
|
-
error: `Failed to parse JSON output: ${raw.slice(0, 200)}`,
|
|
104
|
-
};
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
return { success: true, output: raw };
|
|
109
|
-
} catch (err: unknown) {
|
|
110
|
-
const e = err as { message?: string; stderr?: string; stdout?: string };
|
|
111
|
-
// When claude -p exits non-zero, the JSON result is still in stdout.
|
|
112
|
-
// Try to parse it so the runner gets structured error info instead of "Command failed".
|
|
113
|
-
if (parseOutput === 'json' && e.stdout?.trim()) {
|
|
114
|
-
try {
|
|
115
|
-
return { success: false, output: JSON.parse(e.stdout.trim()), error: e.stderr?.trim() || undefined };
|
|
116
|
-
} catch { /* fall through to default error */ }
|
|
117
|
-
}
|
|
118
|
-
return { success: false, output: undefined, error: e.stderr?.trim() || e.message || 'Command failed' };
|
|
119
|
-
}
|
|
120
|
-
}
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import { raceSignal } from './race-signal.js';
|
|
3
|
-
|
|
4
|
-
describe('raceSignal', () => {
|
|
5
|
-
it('resolves normally when no signal provided', async () => {
|
|
6
|
-
const result = await raceSignal(Promise.resolve(42));
|
|
7
|
-
expect(result).toBe(42);
|
|
8
|
-
});
|
|
9
|
-
|
|
10
|
-
it('resolves normally when signal is not aborted', async () => {
|
|
11
|
-
const controller = new AbortController();
|
|
12
|
-
const result = await raceSignal(Promise.resolve('hello'), controller.signal);
|
|
13
|
-
expect(result).toBe('hello');
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
it('rejects immediately if signal is already aborted', async () => {
|
|
17
|
-
const controller = new AbortController();
|
|
18
|
-
controller.abort();
|
|
19
|
-
await expect(raceSignal(new Promise(() => {}), controller.signal))
|
|
20
|
-
.rejects.toThrow('Aborted');
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
it('rejects when signal fires after creation', async () => {
|
|
24
|
-
const controller = new AbortController();
|
|
25
|
-
// A promise that never resolves on its own
|
|
26
|
-
const hanging = new Promise<never>(() => {});
|
|
27
|
-
const raced = raceSignal(hanging, controller.signal);
|
|
28
|
-
controller.abort();
|
|
29
|
-
await expect(raced).rejects.toThrow('Aborted');
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
it('resolves if promise resolves before signal fires', async () => {
|
|
33
|
-
const controller = new AbortController();
|
|
34
|
-
const result = await raceSignal(Promise.resolve(99), controller.signal);
|
|
35
|
-
// Abort after the fact — should not cause rejection
|
|
36
|
-
controller.abort();
|
|
37
|
-
expect(result).toBe(99);
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
it('thrown error name is AbortError', async () => {
|
|
41
|
-
const controller = new AbortController();
|
|
42
|
-
controller.abort();
|
|
43
|
-
try {
|
|
44
|
-
await raceSignal(new Promise(() => {}), controller.signal);
|
|
45
|
-
} catch (e) {
|
|
46
|
-
expect(e).toBeInstanceOf(DOMException);
|
|
47
|
-
expect((e as DOMException).name).toBe('AbortError');
|
|
48
|
-
}
|
|
49
|
-
});
|
|
50
|
-
});
|
package/src/utils/race-signal.ts
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Race a promise against an AbortSignal.
|
|
3
|
-
* Rejects with DOMException('Aborted', 'AbortError') if the signal fires first.
|
|
4
|
-
*/
|
|
5
|
-
export function raceSignal<T>(promise: Promise<T>, signal?: AbortSignal): Promise<T> {
|
|
6
|
-
if (!signal) return promise;
|
|
7
|
-
if (signal.aborted) return Promise.reject(new DOMException('Aborted', 'AbortError'));
|
|
8
|
-
|
|
9
|
-
return new Promise((resolve, reject) => {
|
|
10
|
-
const onAbort = () => reject(new DOMException('Aborted', 'AbortError'));
|
|
11
|
-
signal.addEventListener('abort', onAbort, { once: true });
|
|
12
|
-
|
|
13
|
-
promise
|
|
14
|
-
.then((v) => { signal.removeEventListener('abort', onAbort); resolve(v); })
|
|
15
|
-
.catch((e) => { signal.removeEventListener('abort', onAbort); reject(e); });
|
|
16
|
-
});
|
|
17
|
-
}
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
name: linear
|
|
2
|
-
version: 1
|
|
3
|
-
description: "Linear webhook trigger + issue status sync"
|
|
4
|
-
|
|
5
|
-
config:
|
|
6
|
-
required:
|
|
7
|
-
- LINEAR_API_KEY
|
|
8
|
-
- LINEAR_WEBHOOK_SECRET
|
|
9
|
-
optional:
|
|
10
|
-
autoTrigger: false
|
|
11
|
-
|
|
12
|
-
webhook:
|
|
13
|
-
hmac:
|
|
14
|
-
header: linear-signature
|
|
15
|
-
secret_env: LINEAR_WEBHOOK_SECRET
|
|
16
|
-
handler: linear-webhook
|
|
17
|
-
|
|
18
|
-
on_failure:
|
|
19
|
-
handler: linear-failure
|
|
20
|
-
|
|
21
|
-
events:
|
|
22
|
-
consumes:
|
|
23
|
-
- linear.issue.in_progress
|
|
24
|
-
emits:
|
|
25
|
-
- pipeline.complete
|
|
26
|
-
- pipeline.failed
|
|
27
|
-
|
|
28
|
-
test:
|
|
29
|
-
type: http
|
|
30
|
-
endpoint: https://api.linear.app/graphql
|
|
31
|
-
method: POST
|
|
32
|
-
auth: bearer:${LINEAR_API_KEY}
|
|
33
|
-
body: '{"query":"{ viewer { id name } }"}'
|
|
34
|
-
expect:
|
|
35
|
-
status: 200
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
name: slack
|
|
2
|
-
version: 1
|
|
3
|
-
description: "Slack notifications for pipeline events"
|
|
4
|
-
|
|
5
|
-
config:
|
|
6
|
-
required:
|
|
7
|
-
- SLACK_BOT_TOKEN
|
|
8
|
-
optional:
|
|
9
|
-
channel: "#studio-runs"
|
|
10
|
-
|
|
11
|
-
events:
|
|
12
|
-
emits:
|
|
13
|
-
- pipeline.complete
|
|
14
|
-
- pipeline.failed
|
|
15
|
-
|
|
16
|
-
test:
|
|
17
|
-
type: http
|
|
18
|
-
endpoint: https://slack.com/api/auth.test
|
|
19
|
-
method: POST
|
|
20
|
-
auth: bearer:${SLACK_BOT_TOKEN}
|
|
21
|
-
expect:
|
|
22
|
-
status: 200
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
name: webhook
|
|
2
|
-
version: 1
|
|
3
|
-
description: "Generic HTTP webhook notifications for pipeline events"
|
|
4
|
-
|
|
5
|
-
config:
|
|
6
|
-
optional:
|
|
7
|
-
url: ""
|
|
8
|
-
events: "pipeline.complete,pipeline.failed"
|
|
9
|
-
secret: ""
|
|
10
|
-
|
|
11
|
-
events:
|
|
12
|
-
emits:
|
|
13
|
-
- pipeline.complete
|
|
14
|
-
- pipeline.failed
|
|
15
|
-
- pipeline.start
|
|
16
|
-
- stage.complete
|
|
17
|
-
- stage.failed
|
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
name: git
|
|
2
|
-
description: Git version control operations
|
|
3
|
-
version: 1
|
|
4
|
-
|
|
5
|
-
commands:
|
|
6
|
-
- name: git-status
|
|
7
|
-
description: Show working tree status
|
|
8
|
-
parameters: {}
|
|
9
|
-
execute:
|
|
10
|
-
type: shell
|
|
11
|
-
command: git status --porcelain
|
|
12
|
-
parse_output: text
|
|
13
|
-
|
|
14
|
-
- name: git-diff
|
|
15
|
-
description: Show changes in the working tree
|
|
16
|
-
parameters:
|
|
17
|
-
staged:
|
|
18
|
-
type: boolean
|
|
19
|
-
required: false
|
|
20
|
-
description: Show staged changes instead of unstaged
|
|
21
|
-
file:
|
|
22
|
-
type: string
|
|
23
|
-
required: false
|
|
24
|
-
description: Restrict diff to this file path
|
|
25
|
-
execute:
|
|
26
|
-
type: shell
|
|
27
|
-
command: |
|
|
28
|
-
git diff {{#if staged}}--cached{{/if}} {{#if file}}{{file}}{{/if}}
|
|
29
|
-
parse_output: text
|
|
30
|
-
|
|
31
|
-
- name: git-checkout
|
|
32
|
-
description: Checkout an existing branch or create a new one
|
|
33
|
-
parameters:
|
|
34
|
-
branch:
|
|
35
|
-
type: string
|
|
36
|
-
required: true
|
|
37
|
-
description: Branch name to checkout or create
|
|
38
|
-
create:
|
|
39
|
-
type: boolean
|
|
40
|
-
required: false
|
|
41
|
-
description: Create the branch if it does not exist
|
|
42
|
-
execute:
|
|
43
|
-
type: shell
|
|
44
|
-
command: |
|
|
45
|
-
git checkout {{#if create}}-b{{/if}} {{branch}}
|
|
46
|
-
parse_output: text
|
|
47
|
-
|
|
48
|
-
- name: git-commit
|
|
49
|
-
description: Stage all changes and commit with a message
|
|
50
|
-
parameters:
|
|
51
|
-
message:
|
|
52
|
-
type: string
|
|
53
|
-
required: true
|
|
54
|
-
description: Commit message (use conventional commits format)
|
|
55
|
-
execute:
|
|
56
|
-
type: shell
|
|
57
|
-
command: |
|
|
58
|
-
git add -A && git commit -m "{{message}}"
|
|
59
|
-
parse_output: text
|
|
60
|
-
|
|
61
|
-
- name: git-push
|
|
62
|
-
description: Push the current branch to origin
|
|
63
|
-
parameters:
|
|
64
|
-
set_upstream:
|
|
65
|
-
type: boolean
|
|
66
|
-
required: false
|
|
67
|
-
description: Set upstream tracking reference (-u flag)
|
|
68
|
-
execute:
|
|
69
|
-
type: shell
|
|
70
|
-
command: |
|
|
71
|
-
git push {{#if set_upstream}}-u{{/if}} origin HEAD
|
|
72
|
-
parse_output: text
|
|
73
|
-
|
|
74
|
-
prompt_snippet: |
|
|
75
|
-
You have access to git tools. Always create a feature branch before making changes.
|
|
76
|
-
Never commit directly to main or master.
|
|
77
|
-
Use conventional commit messages: <type>(<scope>): <description>
|
|
78
|
-
|
|
79
|
-
constraints:
|
|
80
|
-
requires_binaries: [git]
|
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
name: repo_manager
|
|
2
|
-
description: Read and write files in the workspace
|
|
3
|
-
version: 1
|
|
4
|
-
|
|
5
|
-
commands:
|
|
6
|
-
- name: repo_manager-read_file
|
|
7
|
-
description: Read a file from the workspace
|
|
8
|
-
parameters:
|
|
9
|
-
path:
|
|
10
|
-
type: string
|
|
11
|
-
required: true
|
|
12
|
-
description: File path relative to the workspace root
|
|
13
|
-
execute:
|
|
14
|
-
type: builtin
|
|
15
|
-
parse_output: json
|
|
16
|
-
|
|
17
|
-
- name: repo_manager-write_file
|
|
18
|
-
description: Write or create a file in the workspace
|
|
19
|
-
parameters:
|
|
20
|
-
path:
|
|
21
|
-
type: string
|
|
22
|
-
required: true
|
|
23
|
-
description: File path relative to the workspace root
|
|
24
|
-
content:
|
|
25
|
-
type: string
|
|
26
|
-
required: true
|
|
27
|
-
description: Full content to write to the file
|
|
28
|
-
execute:
|
|
29
|
-
type: builtin
|
|
30
|
-
parse_output: json
|
|
31
|
-
|
|
32
|
-
- name: repo_manager-list_files
|
|
33
|
-
description: List files in the workspace
|
|
34
|
-
parameters:
|
|
35
|
-
path:
|
|
36
|
-
type: string
|
|
37
|
-
required: false
|
|
38
|
-
description: Directory to list (default is workspace root)
|
|
39
|
-
recursive:
|
|
40
|
-
type: boolean
|
|
41
|
-
required: false
|
|
42
|
-
description: Whether to list recursively
|
|
43
|
-
execute:
|
|
44
|
-
type: builtin
|
|
45
|
-
parse_output: json
|
|
46
|
-
|
|
47
|
-
- name: repo_manager-apply_patch
|
|
48
|
-
description: Apply a unified diff patch to a file
|
|
49
|
-
parameters:
|
|
50
|
-
path:
|
|
51
|
-
type: string
|
|
52
|
-
required: true
|
|
53
|
-
description: File path to patch
|
|
54
|
-
patch:
|
|
55
|
-
type: string
|
|
56
|
-
required: true
|
|
57
|
-
description: Unified diff patch content
|
|
58
|
-
execute:
|
|
59
|
-
type: builtin
|
|
60
|
-
parse_output: json
|
|
61
|
-
|
|
62
|
-
prompt_snippet: |
|
|
63
|
-
You have access to file management tools. Read files before modifying them.
|
|
64
|
-
Use repo_manager-write_file to create or update files — provide the full file content.
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
name: search
|
|
2
|
-
description: Search the codebase by content or file pattern
|
|
3
|
-
version: 1
|
|
4
|
-
|
|
5
|
-
commands:
|
|
6
|
-
- name: search-search_codebase
|
|
7
|
-
description: Search files by content pattern (uses ripgrep)
|
|
8
|
-
parameters:
|
|
9
|
-
pattern:
|
|
10
|
-
type: string
|
|
11
|
-
required: true
|
|
12
|
-
description: Regex or literal pattern to search for
|
|
13
|
-
file_pattern:
|
|
14
|
-
type: string
|
|
15
|
-
required: false
|
|
16
|
-
description: Glob pattern to restrict which files are searched (e.g. "*.ts")
|
|
17
|
-
execute:
|
|
18
|
-
type: builtin
|
|
19
|
-
parse_output: json
|
|
20
|
-
|
|
21
|
-
prompt_snippet: |
|
|
22
|
-
You have access to a codebase search tool. Use it to find relevant code before making changes.
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
name: shell
|
|
2
|
-
description: Execute shell commands in the workspace
|
|
3
|
-
version: 1
|
|
4
|
-
|
|
5
|
-
commands:
|
|
6
|
-
- name: shell-run_command
|
|
7
|
-
description: Run a shell command in the workspace directory
|
|
8
|
-
parameters:
|
|
9
|
-
command:
|
|
10
|
-
type: string
|
|
11
|
-
required: true
|
|
12
|
-
description: Shell command to execute
|
|
13
|
-
execute:
|
|
14
|
-
type: builtin
|
|
15
|
-
parse_output: text
|
|
16
|
-
|
|
17
|
-
prompt_snippet: |
|
|
18
|
-
You have access to a shell tool. Use it to run build, test, or inspection commands.
|
|
19
|
-
Avoid destructive commands. Prefer targeted commands over broad ones.
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
name: web-search
|
|
2
|
-
description: Search the web using Tavily (requires TAVILY_API_KEY)
|
|
3
|
-
version: 1
|
|
4
|
-
|
|
5
|
-
commands:
|
|
6
|
-
- name: web_search-search
|
|
7
|
-
description: Search the web and return relevant results
|
|
8
|
-
parameters:
|
|
9
|
-
query:
|
|
10
|
-
type: string
|
|
11
|
-
required: true
|
|
12
|
-
description: The search query
|
|
13
|
-
max_results:
|
|
14
|
-
type: number
|
|
15
|
-
required: false
|
|
16
|
-
description: "Maximum number of results to return (default: 5)"
|
|
17
|
-
execute:
|
|
18
|
-
type: builtin
|
|
19
|
-
parse_output: json
|
|
20
|
-
|
|
21
|
-
prompt_snippet: |
|
|
22
|
-
You have access to a web search tool. Use it to find up-to-date information from the web.
|
|
23
|
-
Search in the language most relevant to the query (English for technical docs, French for local content).
|
|
24
|
-
Prefer specific, targeted queries over broad ones for better results.
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import { AnonymizationMiddleware } from '../src/middleware/anonymization.js';
|
|
3
|
-
|
|
4
|
-
describe('AnonymizationMiddleware', () => {
|
|
5
|
-
it('anonymizes text containing PII', () => {
|
|
6
|
-
const mw = new AnonymizationMiddleware();
|
|
7
|
-
const result = mw.anonymize('Contact mc@acme.com');
|
|
8
|
-
expect(result).not.toContain('mc@acme.com');
|
|
9
|
-
expect(result).toContain('EMAIL_1');
|
|
10
|
-
});
|
|
11
|
-
|
|
12
|
-
it('deanonymizes using accumulated keymap', () => {
|
|
13
|
-
const mw = new AnonymizationMiddleware();
|
|
14
|
-
const anon = mw.anonymize('Contact mc@acme.com');
|
|
15
|
-
const restored = mw.deanonymize(anon);
|
|
16
|
-
expect(restored).toBe('Contact mc@acme.com');
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
it('is consistent across multiple calls (same email = same token)', () => {
|
|
20
|
-
const mw = new AnonymizationMiddleware();
|
|
21
|
-
const first = mw.anonymize('Email mc@acme.com here');
|
|
22
|
-
const second = mw.anonymize('Also mc@acme.com again');
|
|
23
|
-
expect(first).toContain('EMAIL_1');
|
|
24
|
-
expect(second).toContain('EMAIL_1');
|
|
25
|
-
// Verify deanonymize restores both
|
|
26
|
-
expect(mw.deanonymize(first)).toBe('Email mc@acme.com here');
|
|
27
|
-
expect(mw.deanonymize(second)).toBe('Also mc@acme.com again');
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
it('accumulates keymap across calls', () => {
|
|
31
|
-
const mw = new AnonymizationMiddleware();
|
|
32
|
-
mw.anonymize('Email mc@acme.com');
|
|
33
|
-
mw.anonymize('Phone 514-555-1234');
|
|
34
|
-
const keymap = mw.getKeymap();
|
|
35
|
-
expect(keymap['EMAIL_1']).toBe('mc@acme.com');
|
|
36
|
-
expect(keymap['PHONE_1']).toBe('514-555-1234');
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
it('handles JSON round-trip', () => {
|
|
40
|
-
const mw = new AnonymizationMiddleware();
|
|
41
|
-
const obj = { email: 'mc@acme.com', message: 'hello' };
|
|
42
|
-
const anonStr = mw.anonymize(JSON.stringify(obj));
|
|
43
|
-
const restored = JSON.parse(mw.deanonymize(anonStr));
|
|
44
|
-
expect(restored.email).toBe('mc@acme.com');
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
it('passes through text with no PII unchanged', () => {
|
|
48
|
-
const mw = new AnonymizationMiddleware();
|
|
49
|
-
const text = 'Calculate 2 + 2 = 4';
|
|
50
|
-
expect(mw.anonymize(text)).toBe(text);
|
|
51
|
-
expect(mw.deanonymize(text)).toBe(text);
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
it('new email in second call gets EMAIL_2 (not EMAIL_1 again)', () => {
|
|
55
|
-
const mw = new AnonymizationMiddleware();
|
|
56
|
-
mw.anonymize('Email mc@acme.com');
|
|
57
|
-
const second = mw.anonymize('Other other@example.com');
|
|
58
|
-
expect(second).toContain('EMAIL_2');
|
|
59
|
-
expect(mw.getKeymap()['EMAIL_2']).toBe('other@example.com');
|
|
60
|
-
});
|
|
61
|
-
});
|