@slope-dev/slope 1.13.0 → 1.13.1
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/dist/adapters.d.ts +12 -0
- package/dist/adapters.d.ts.map +1 -0
- package/dist/adapters.js +15 -0
- package/dist/adapters.js.map +1 -0
- package/dist/cli/commands/guard.d.ts.map +1 -1
- package/dist/cli/commands/guard.js +2 -0
- package/dist/cli/commands/guard.js.map +1 -1
- package/dist/cli/commands/hook.d.ts +4 -0
- package/dist/cli/commands/hook.d.ts.map +1 -1
- package/dist/cli/commands/hook.js +57 -83
- package/dist/cli/commands/hook.js.map +1 -1
- package/dist/cli/commands/init.d.ts +10 -3
- package/dist/cli/commands/init.d.ts.map +1 -1
- package/dist/cli/commands/init.js +78 -9
- package/dist/cli/commands/init.js.map +1 -1
- package/dist/cli/commands/roadmap.d.ts.map +1 -1
- package/dist/cli/commands/roadmap.js +94 -1
- package/dist/cli/commands/roadmap.js.map +1 -1
- package/dist/cli/guards/branch-before-commit.d.ts +7 -0
- package/dist/cli/guards/branch-before-commit.d.ts.map +1 -0
- package/dist/cli/guards/branch-before-commit.js +43 -0
- package/dist/cli/guards/branch-before-commit.js.map +1 -0
- package/dist/cli/guards/stop-check.d.ts +4 -0
- package/dist/cli/guards/stop-check.d.ts.map +1 -1
- package/dist/cli/guards/stop-check.js +50 -16
- package/dist/cli/guards/stop-check.js.map +1 -1
- package/dist/core/adapters/claude-code.d.ts +25 -0
- package/dist/core/adapters/claude-code.d.ts.map +1 -0
- package/dist/core/adapters/claude-code.js +133 -0
- package/dist/core/adapters/claude-code.js.map +1 -0
- package/dist/core/adapters/cursor.d.ts +35 -0
- package/dist/core/adapters/cursor.d.ts.map +1 -0
- package/dist/core/adapters/cursor.js +138 -0
- package/dist/core/adapters/cursor.js.map +1 -0
- package/dist/core/adapters/generic.d.ts +26 -0
- package/dist/core/adapters/generic.d.ts.map +1 -0
- package/dist/core/adapters/generic.js +137 -0
- package/dist/core/adapters/generic.js.map +1 -0
- package/dist/core/adapters/windsurf.d.ts +34 -0
- package/dist/core/adapters/windsurf.d.ts.map +1 -0
- package/dist/core/adapters/windsurf.js +165 -0
- package/dist/core/adapters/windsurf.js.map +1 -0
- package/dist/core/config.d.ts +1 -0
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js.map +1 -1
- package/dist/core/guard.d.ts +10 -3
- package/dist/core/guard.d.ts.map +1 -1
- package/dist/core/guard.js +33 -59
- package/dist/core/guard.js.map +1 -1
- package/dist/core/harness.d.ts +55 -0
- package/dist/core/harness.d.ts.map +1 -0
- package/dist/core/harness.js +88 -0
- package/dist/core/harness.js.map +1 -0
- package/dist/core/index.d.ts +7 -0
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +7 -0
- package/dist/core/index.js.map +1 -1
- package/package.json +5 -1
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
// SLOPE ClaudeCodeAdapter — adapts guard framework to Claude Code's hook system.
|
|
2
|
+
import { existsSync, writeFileSync, readFileSync, mkdirSync } from 'node:fs';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { CLAUDE_CODE_TOOLS, registerAdapter } from '../harness.js';
|
|
5
|
+
/** Claude Code adapter — formats guard output for Claude Code's hook protocol. */
|
|
6
|
+
export class ClaudeCodeAdapter {
|
|
7
|
+
id = 'claude-code';
|
|
8
|
+
displayName = 'Claude Code';
|
|
9
|
+
toolNames = CLAUDE_CODE_TOOLS;
|
|
10
|
+
formatPreToolOutput(result) {
|
|
11
|
+
return {
|
|
12
|
+
hookSpecificOutput: {
|
|
13
|
+
hookEventName: 'PreToolUse',
|
|
14
|
+
...(result.decision && { permissionDecision: result.decision }),
|
|
15
|
+
...(result.blockReason && { permissionDecisionReason: result.blockReason }),
|
|
16
|
+
...(result.context && { additionalContext: result.context }),
|
|
17
|
+
},
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
formatPostToolOutput(result) {
|
|
21
|
+
if (result.blockReason) {
|
|
22
|
+
return {
|
|
23
|
+
decision: 'block',
|
|
24
|
+
reason: result.blockReason,
|
|
25
|
+
hookSpecificOutput: {
|
|
26
|
+
hookEventName: 'PostToolUse',
|
|
27
|
+
...(result.context && { additionalContext: result.context }),
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
if (result.context) {
|
|
32
|
+
return {
|
|
33
|
+
hookSpecificOutput: {
|
|
34
|
+
hookEventName: 'PostToolUse',
|
|
35
|
+
additionalContext: result.context,
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
return {};
|
|
40
|
+
}
|
|
41
|
+
formatStopOutput(result) {
|
|
42
|
+
if (result.blockReason) {
|
|
43
|
+
return { decision: 'block', reason: result.blockReason };
|
|
44
|
+
}
|
|
45
|
+
return {};
|
|
46
|
+
}
|
|
47
|
+
generateHooksConfig(guards, guardScriptPath) {
|
|
48
|
+
const config = {};
|
|
49
|
+
// Group guards by hookEvent + matcher
|
|
50
|
+
const groups = new Map();
|
|
51
|
+
for (const g of guards) {
|
|
52
|
+
const key = `${g.hookEvent}::${g.matcher ?? ''}`;
|
|
53
|
+
const list = groups.get(key) || [];
|
|
54
|
+
list.push(g);
|
|
55
|
+
groups.set(key, list);
|
|
56
|
+
}
|
|
57
|
+
for (const [key, defs] of groups) {
|
|
58
|
+
const [hookEvent, matcher] = key.split('::');
|
|
59
|
+
if (!config[hookEvent])
|
|
60
|
+
config[hookEvent] = [];
|
|
61
|
+
const hooks = defs.map(d => ({
|
|
62
|
+
type: 'command',
|
|
63
|
+
command: `${guardScriptPath} ${d.name}`,
|
|
64
|
+
timeout: 10,
|
|
65
|
+
statusMessage: `SLOPE: ${d.description}`,
|
|
66
|
+
}));
|
|
67
|
+
const entry = { hooks };
|
|
68
|
+
if (matcher)
|
|
69
|
+
entry.matcher = matcher;
|
|
70
|
+
config[hookEvent].push(entry);
|
|
71
|
+
}
|
|
72
|
+
return config;
|
|
73
|
+
}
|
|
74
|
+
installGuards(cwd, guards) {
|
|
75
|
+
const hooksDir = join(cwd, '.claude', 'hooks');
|
|
76
|
+
mkdirSync(hooksDir, { recursive: true });
|
|
77
|
+
// Create the guard dispatcher script
|
|
78
|
+
const dispatcherPath = join(hooksDir, 'slope-guard.sh');
|
|
79
|
+
if (!existsSync(dispatcherPath)) {
|
|
80
|
+
const script = [
|
|
81
|
+
'#!/usr/bin/env bash',
|
|
82
|
+
'# SLOPE guard dispatcher — routes hook events to slope guard handlers',
|
|
83
|
+
'# Auto-generated by slope hook add --level=full',
|
|
84
|
+
'',
|
|
85
|
+
'# === SLOPE MANAGED (do not edit above this line) ===',
|
|
86
|
+
'slope guard "$@"',
|
|
87
|
+
'# === SLOPE END ===',
|
|
88
|
+
'',
|
|
89
|
+
].join('\n');
|
|
90
|
+
writeFileSync(dispatcherPath, script, { mode: 0o755 });
|
|
91
|
+
console.log(` Created ${dispatcherPath}`);
|
|
92
|
+
}
|
|
93
|
+
// Generate the hooks config for .claude/settings.json
|
|
94
|
+
const guardScript = '"$CLAUDE_PROJECT_DIR"/.claude/hooks/slope-guard.sh';
|
|
95
|
+
const hooksConfig = this.generateHooksConfig(guards, guardScript);
|
|
96
|
+
// Read and merge into .claude/settings.json
|
|
97
|
+
const settingsPath = join(cwd, '.claude', 'settings.json');
|
|
98
|
+
let settings = {};
|
|
99
|
+
if (existsSync(settingsPath)) {
|
|
100
|
+
try {
|
|
101
|
+
settings = JSON.parse(readFileSync(settingsPath, 'utf8'));
|
|
102
|
+
}
|
|
103
|
+
catch { /* start fresh */ }
|
|
104
|
+
}
|
|
105
|
+
// Merge hooks — preserve existing non-SLOPE hooks
|
|
106
|
+
const existingHooks = (settings.hooks ?? {});
|
|
107
|
+
for (const [event, entries] of Object.entries(hooksConfig)) {
|
|
108
|
+
if (!existingHooks[event]) {
|
|
109
|
+
existingHooks[event] = [];
|
|
110
|
+
}
|
|
111
|
+
for (const entry of entries) {
|
|
112
|
+
const entryWithMatcher = entry;
|
|
113
|
+
const existing = existingHooks[event];
|
|
114
|
+
const isDuplicate = existing.some(e => e.matcher === entryWithMatcher.matcher &&
|
|
115
|
+
e.hooks?.some(h => h.command?.includes('slope-guard.sh')));
|
|
116
|
+
if (!isDuplicate) {
|
|
117
|
+
existingHooks[event].push(entry);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
settings.hooks = existingHooks;
|
|
122
|
+
writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n');
|
|
123
|
+
console.log(` Updated ${settingsPath} with guard hooks`);
|
|
124
|
+
}
|
|
125
|
+
detect(cwd) {
|
|
126
|
+
return existsSync(join(cwd, '.claude'));
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
/** Singleton instance */
|
|
130
|
+
export const claudeCodeAdapter = new ClaudeCodeAdapter();
|
|
131
|
+
// Auto-register on import
|
|
132
|
+
registerAdapter(claudeCodeAdapter);
|
|
133
|
+
//# sourceMappingURL=claude-code.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"claude-code.js","sourceRoot":"","sources":["../../../src/core/adapters/claude-code.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { HarnessAdapter, ToolNameMap } from '../harness.js';
|
|
2
|
+
import type { GuardResult, AnyGuardDefinition } from '../guard.js';
|
|
3
|
+
/** Cursor hook entry in .cursor/hooks.json */
|
|
4
|
+
export interface CursorHookEntry {
|
|
5
|
+
event: string;
|
|
6
|
+
matcher?: string;
|
|
7
|
+
command: string;
|
|
8
|
+
timeout?: number;
|
|
9
|
+
description?: string;
|
|
10
|
+
}
|
|
11
|
+
/** Cursor hooks config shape */
|
|
12
|
+
export interface CursorHooksConfig {
|
|
13
|
+
hooks: CursorHookEntry[];
|
|
14
|
+
}
|
|
15
|
+
/** Cursor hook output protocol */
|
|
16
|
+
export interface CursorHookOutput {
|
|
17
|
+
decision: 'allow' | 'block';
|
|
18
|
+
reason?: string;
|
|
19
|
+
context?: string;
|
|
20
|
+
}
|
|
21
|
+
/** Cursor adapter — formats guard output for Cursor's JSON hook protocol. */
|
|
22
|
+
export declare class CursorAdapter implements HarnessAdapter {
|
|
23
|
+
readonly id: "cursor";
|
|
24
|
+
readonly displayName = "Cursor";
|
|
25
|
+
readonly toolNames: ToolNameMap;
|
|
26
|
+
formatPreToolOutput(result: GuardResult): CursorHookOutput;
|
|
27
|
+
formatPostToolOutput(result: GuardResult): CursorHookOutput;
|
|
28
|
+
formatStopOutput(result: GuardResult): CursorHookOutput;
|
|
29
|
+
generateHooksConfig(guards: AnyGuardDefinition[], guardScriptPath: string): CursorHooksConfig;
|
|
30
|
+
installGuards(cwd: string, guards: AnyGuardDefinition[]): void;
|
|
31
|
+
detect(cwd: string): boolean;
|
|
32
|
+
}
|
|
33
|
+
/** Singleton instance */
|
|
34
|
+
export declare const cursorAdapter: CursorAdapter;
|
|
35
|
+
//# sourceMappingURL=cursor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cursor.d.ts","sourceRoot":"","sources":["../../../src/core/adapters/cursor.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAEjE,OAAO,KAAK,EAAE,WAAW,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAanE,8CAA8C;AAC9C,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,gCAAgC;AAChC,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,eAAe,EAAE,CAAC;CAC1B;AAED,kCAAkC;AAClC,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,OAAO,GAAG,OAAO,CAAC;IAC5B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAYD,6EAA6E;AAC7E,qBAAa,aAAc,YAAW,cAAc;IAClD,QAAQ,CAAC,EAAE,EAAG,QAAQ,CAAU;IAChC,QAAQ,CAAC,WAAW,YAAY;IAChC,QAAQ,CAAC,SAAS,EAAE,WAAW,CAAgB;IAE/C,mBAAmB,CAAC,MAAM,EAAE,WAAW,GAAG,gBAAgB;IAe1D,oBAAoB,CAAC,MAAM,EAAE,WAAW,GAAG,gBAAgB;IAc3D,gBAAgB,CAAC,MAAM,EAAE,WAAW,GAAG,gBAAgB;IAUvD,mBAAmB,CAAC,MAAM,EAAE,kBAAkB,EAAE,EAAE,eAAe,EAAE,MAAM,GAAG,iBAAiB;IAsB7F,aAAa,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,kBAAkB,EAAE,GAAG,IAAI;IAmD9D,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO;CAG7B;AAED,yBAAyB;AACzB,eAAO,MAAM,aAAa,eAAsB,CAAC"}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
// SLOPE CursorAdapter — adapts guard framework to Cursor's hook system.
|
|
2
|
+
// Cursor (v1.7+) uses JSON stdin/stdout protocol with .cursor/hooks.json config.
|
|
3
|
+
import { existsSync, writeFileSync, readFileSync, mkdirSync } from 'node:fs';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
import { registerAdapter, resolveToolMatcher } from '../harness.js';
|
|
6
|
+
/** Cursor tool name mappings */
|
|
7
|
+
const CURSOR_TOOLS = {
|
|
8
|
+
read_file: 'read_file',
|
|
9
|
+
write_file: 'file_edit|create_file',
|
|
10
|
+
search_files: 'list_directory',
|
|
11
|
+
search_content: 'grep_search',
|
|
12
|
+
execute_command: 'run_terminal_command',
|
|
13
|
+
create_subagent: 'create_subagent',
|
|
14
|
+
exit_plan: 'exit_plan',
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Map SLOPE hook events to Cursor hook events.
|
|
18
|
+
* PreCompact is intentionally omitted — Cursor has no pre-compaction hook.
|
|
19
|
+
*/
|
|
20
|
+
const HOOK_EVENT_MAP = {
|
|
21
|
+
PreToolUse: 'pre-tool-use',
|
|
22
|
+
PostToolUse: 'post-tool-use',
|
|
23
|
+
Stop: 'on-stop',
|
|
24
|
+
};
|
|
25
|
+
/** Cursor adapter — formats guard output for Cursor's JSON hook protocol. */
|
|
26
|
+
export class CursorAdapter {
|
|
27
|
+
id = 'cursor';
|
|
28
|
+
displayName = 'Cursor';
|
|
29
|
+
toolNames = CURSOR_TOOLS;
|
|
30
|
+
formatPreToolOutput(result) {
|
|
31
|
+
if (result.decision === 'deny' || result.blockReason) {
|
|
32
|
+
return {
|
|
33
|
+
decision: 'block',
|
|
34
|
+
...(result.blockReason && { reason: result.blockReason }),
|
|
35
|
+
...(result.context && { context: result.context }),
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
// 'ask' falls through to 'allow' — Cursor has no user-confirmation prompt
|
|
39
|
+
return {
|
|
40
|
+
decision: 'allow',
|
|
41
|
+
...(result.context && { context: result.context }),
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
formatPostToolOutput(result) {
|
|
45
|
+
if (result.blockReason) {
|
|
46
|
+
return {
|
|
47
|
+
decision: 'block',
|
|
48
|
+
reason: result.blockReason,
|
|
49
|
+
...(result.context && { context: result.context }),
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
decision: 'allow',
|
|
54
|
+
...(result.context && { context: result.context }),
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
formatStopOutput(result) {
|
|
58
|
+
if (result.blockReason) {
|
|
59
|
+
return {
|
|
60
|
+
decision: 'block',
|
|
61
|
+
reason: result.blockReason,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
return { decision: 'allow' };
|
|
65
|
+
}
|
|
66
|
+
generateHooksConfig(guards, guardScriptPath) {
|
|
67
|
+
const hooks = [];
|
|
68
|
+
for (const g of guards) {
|
|
69
|
+
const cursorEvent = HOOK_EVENT_MAP[g.hookEvent];
|
|
70
|
+
if (!cursorEvent)
|
|
71
|
+
continue; // Skip unsupported hook events
|
|
72
|
+
const matcher = resolveToolMatcher(this, 'toolCategories' in g ? g.toolCategories : undefined) ?? g.matcher;
|
|
73
|
+
const entry = {
|
|
74
|
+
event: cursorEvent,
|
|
75
|
+
command: `${guardScriptPath} ${g.name}`,
|
|
76
|
+
timeout: 10000,
|
|
77
|
+
description: `SLOPE: ${g.description}`,
|
|
78
|
+
};
|
|
79
|
+
if (matcher)
|
|
80
|
+
entry.matcher = matcher;
|
|
81
|
+
hooks.push(entry);
|
|
82
|
+
}
|
|
83
|
+
return { hooks };
|
|
84
|
+
}
|
|
85
|
+
installGuards(cwd, guards) {
|
|
86
|
+
const hooksDir = join(cwd, '.cursor', 'hooks');
|
|
87
|
+
mkdirSync(hooksDir, { recursive: true });
|
|
88
|
+
// Create the guard dispatcher script
|
|
89
|
+
const dispatcherPath = join(hooksDir, 'slope-guard.sh');
|
|
90
|
+
if (!existsSync(dispatcherPath)) {
|
|
91
|
+
const script = [
|
|
92
|
+
'#!/usr/bin/env bash',
|
|
93
|
+
'# SLOPE guard dispatcher — routes hook events to slope guard handlers',
|
|
94
|
+
'# Auto-generated by slope hook add --level=full --harness=cursor',
|
|
95
|
+
'#',
|
|
96
|
+
'# Cursor passes JSON on stdin and reads JSON from stdout.',
|
|
97
|
+
'',
|
|
98
|
+
'# === SLOPE MANAGED (do not edit above this line) ===',
|
|
99
|
+
'slope guard "$@"',
|
|
100
|
+
'# === SLOPE END ===',
|
|
101
|
+
'',
|
|
102
|
+
].join('\n');
|
|
103
|
+
writeFileSync(dispatcherPath, script, { mode: 0o755 });
|
|
104
|
+
console.log(` Created ${dispatcherPath}`);
|
|
105
|
+
}
|
|
106
|
+
// Generate hooks config and merge into .cursor/hooks.json
|
|
107
|
+
// Command paths are relative to the project root (cwd), not the hooks.json file
|
|
108
|
+
const guardScript = '.cursor/hooks/slope-guard.sh';
|
|
109
|
+
const hooksConfig = this.generateHooksConfig(guards, guardScript);
|
|
110
|
+
const configPath = join(cwd, '.cursor', 'hooks.json');
|
|
111
|
+
let existing = { hooks: [] };
|
|
112
|
+
if (existsSync(configPath)) {
|
|
113
|
+
try {
|
|
114
|
+
existing = JSON.parse(readFileSync(configPath, 'utf8'));
|
|
115
|
+
if (!Array.isArray(existing.hooks))
|
|
116
|
+
existing.hooks = [];
|
|
117
|
+
}
|
|
118
|
+
catch { /* start fresh */ }
|
|
119
|
+
}
|
|
120
|
+
// Merge — avoid duplicates by checking command
|
|
121
|
+
for (const entry of hooksConfig.hooks) {
|
|
122
|
+
const isDuplicate = existing.hooks.some(e => e.command === entry.command && e.event === entry.event);
|
|
123
|
+
if (!isDuplicate) {
|
|
124
|
+
existing.hooks.push(entry);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
writeFileSync(configPath, JSON.stringify(existing, null, 2) + '\n');
|
|
128
|
+
console.log(` Updated ${configPath} with guard hooks`);
|
|
129
|
+
}
|
|
130
|
+
detect(cwd) {
|
|
131
|
+
return existsSync(join(cwd, '.cursor'));
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
/** Singleton instance */
|
|
135
|
+
export const cursorAdapter = new CursorAdapter();
|
|
136
|
+
// Auto-register on import
|
|
137
|
+
registerAdapter(cursorAdapter);
|
|
138
|
+
//# sourceMappingURL=cursor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cursor.js","sourceRoot":"","sources":["../../../src/core/adapters/cursor.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { HarnessAdapter, ToolNameMap } from '../harness.js';
|
|
2
|
+
import type { GuardResult, AnyGuardDefinition } from '../guard.js';
|
|
3
|
+
/** Guard manifest entry written to guards-manifest.json */
|
|
4
|
+
export interface GuardManifestEntry {
|
|
5
|
+
name: string;
|
|
6
|
+
description: string;
|
|
7
|
+
hookEvent: string;
|
|
8
|
+
matcher?: string;
|
|
9
|
+
level: string;
|
|
10
|
+
command: string;
|
|
11
|
+
}
|
|
12
|
+
/** Generic adapter — works with any harness via shell scripts and JSON manifest. */
|
|
13
|
+
export declare class GenericAdapter implements HarnessAdapter {
|
|
14
|
+
readonly id: "generic";
|
|
15
|
+
readonly displayName = "Generic (Shell)";
|
|
16
|
+
readonly toolNames: ToolNameMap;
|
|
17
|
+
formatPreToolOutput(result: GuardResult): unknown;
|
|
18
|
+
formatPostToolOutput(result: GuardResult): unknown;
|
|
19
|
+
formatStopOutput(result: GuardResult): unknown;
|
|
20
|
+
generateHooksConfig(guards: AnyGuardDefinition[], guardScriptPath: string): GuardManifestEntry[];
|
|
21
|
+
installGuards(cwd: string, guards: AnyGuardDefinition[]): void;
|
|
22
|
+
detect(_cwd: string): boolean;
|
|
23
|
+
}
|
|
24
|
+
/** Singleton instance */
|
|
25
|
+
export declare const genericAdapter: GenericAdapter;
|
|
26
|
+
//# sourceMappingURL=generic.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generic.d.ts","sourceRoot":"","sources":["../../../src/core/adapters/generic.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAEjE,OAAO,KAAK,EAAE,WAAW,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAanE,2DAA2D;AAC3D,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,oFAAoF;AACpF,qBAAa,cAAe,YAAW,cAAc;IACnD,QAAQ,CAAC,EAAE,EAAG,SAAS,CAAU;IACjC,QAAQ,CAAC,WAAW,qBAAqB;IACzC,QAAQ,CAAC,SAAS,EAAE,WAAW,CAAiB;IAEhD,mBAAmB,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO;IAYjD,oBAAoB,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO;IAQlD,gBAAgB,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO;IAO9C,mBAAmB,CAAC,MAAM,EAAE,kBAAkB,EAAE,EAAE,eAAe,EAAE,MAAM,GAAG,kBAAkB,EAAE;IAehG,aAAa,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,kBAAkB,EAAE,GAAG,IAAI;IAyE9D,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;CAI9B;AAED,yBAAyB;AACzB,eAAO,MAAM,cAAc,gBAAuB,CAAC"}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
// SLOPE GenericAdapter — fallback adapter for any harness without a dedicated adapter.
|
|
2
|
+
// Outputs simple JSON, generates a guards manifest, and creates shell-based dispatcher.
|
|
3
|
+
import { existsSync, writeFileSync, mkdirSync } from 'node:fs';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
import { registerAdapter, resolveToolMatcher } from '../harness.js';
|
|
6
|
+
/** Generic tool name map — uses common operation names */
|
|
7
|
+
const GENERIC_TOOLS = {
|
|
8
|
+
read_file: 'read_file',
|
|
9
|
+
write_file: 'write_file',
|
|
10
|
+
search_files: 'search_files',
|
|
11
|
+
search_content: 'search_content',
|
|
12
|
+
execute_command: 'execute_command',
|
|
13
|
+
create_subagent: 'create_subagent',
|
|
14
|
+
exit_plan: 'exit_plan',
|
|
15
|
+
};
|
|
16
|
+
/** Generic adapter — works with any harness via shell scripts and JSON manifest. */
|
|
17
|
+
export class GenericAdapter {
|
|
18
|
+
id = 'generic';
|
|
19
|
+
displayName = 'Generic (Shell)';
|
|
20
|
+
toolNames = GENERIC_TOOLS;
|
|
21
|
+
formatPreToolOutput(result) {
|
|
22
|
+
const action = result.decision === 'deny' ? 'deny'
|
|
23
|
+
: result.decision === 'ask' ? 'ask'
|
|
24
|
+
: result.context ? 'context'
|
|
25
|
+
: 'allow';
|
|
26
|
+
return {
|
|
27
|
+
action,
|
|
28
|
+
...(result.context && { message: result.context }),
|
|
29
|
+
...(result.blockReason && { reason: result.blockReason }),
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
formatPostToolOutput(result) {
|
|
33
|
+
return {
|
|
34
|
+
action: result.blockReason ? 'deny' : result.context ? 'context' : 'allow',
|
|
35
|
+
...(result.context && { message: result.context }),
|
|
36
|
+
...(result.blockReason && { reason: result.blockReason }),
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
formatStopOutput(result) {
|
|
40
|
+
return {
|
|
41
|
+
action: result.blockReason ? 'deny' : 'allow',
|
|
42
|
+
...(result.blockReason && { reason: result.blockReason }),
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
generateHooksConfig(guards, guardScriptPath) {
|
|
46
|
+
return guards.map(g => {
|
|
47
|
+
// Resolve matcher via toolCategories (harness-agnostic) with fallback to raw matcher
|
|
48
|
+
const matcher = resolveToolMatcher(this, 'toolCategories' in g ? g.toolCategories : undefined) ?? g.matcher;
|
|
49
|
+
return {
|
|
50
|
+
name: g.name,
|
|
51
|
+
description: g.description,
|
|
52
|
+
hookEvent: g.hookEvent,
|
|
53
|
+
...(matcher && { matcher }),
|
|
54
|
+
level: g.level,
|
|
55
|
+
command: `${guardScriptPath} ${g.name}`,
|
|
56
|
+
};
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
installGuards(cwd, guards) {
|
|
60
|
+
const hooksDir = join(cwd, '.slope', 'hooks');
|
|
61
|
+
mkdirSync(hooksDir, { recursive: true });
|
|
62
|
+
// Create the guard dispatcher script
|
|
63
|
+
const dispatcherPath = join(hooksDir, 'slope-guard.sh');
|
|
64
|
+
if (!existsSync(dispatcherPath)) {
|
|
65
|
+
const script = [
|
|
66
|
+
'#!/usr/bin/env bash',
|
|
67
|
+
'# SLOPE guard dispatcher — routes hook events to slope guard handlers',
|
|
68
|
+
'# Auto-generated by slope hook add --level=full --harness=generic',
|
|
69
|
+
'#',
|
|
70
|
+
'# Usage: ./slope-guard.sh <guard-name>',
|
|
71
|
+
'# Reads JSON from stdin, outputs JSON to stdout.',
|
|
72
|
+
'# See guards-manifest.json for available guards.',
|
|
73
|
+
'',
|
|
74
|
+
'# === SLOPE MANAGED (do not edit above this line) ===',
|
|
75
|
+
'slope guard "$@"',
|
|
76
|
+
'# === SLOPE END ===',
|
|
77
|
+
'',
|
|
78
|
+
].join('\n');
|
|
79
|
+
writeFileSync(dispatcherPath, script, { mode: 0o755 });
|
|
80
|
+
console.log(` Created ${dispatcherPath}`);
|
|
81
|
+
}
|
|
82
|
+
// Generate guards manifest
|
|
83
|
+
const manifestPath = join(hooksDir, 'guards-manifest.json');
|
|
84
|
+
const manifest = this.generateHooksConfig(guards, './slope-guard.sh');
|
|
85
|
+
writeFileSync(manifestPath, JSON.stringify(manifest, null, 2) + '\n');
|
|
86
|
+
console.log(` Created ${manifestPath}`);
|
|
87
|
+
// Generate README
|
|
88
|
+
const readmePath = join(hooksDir, 'README.md');
|
|
89
|
+
if (!existsSync(readmePath)) {
|
|
90
|
+
const readme = [
|
|
91
|
+
'# SLOPE Guard Hooks',
|
|
92
|
+
'',
|
|
93
|
+
'This directory contains SLOPE guard hooks for use with any AI coding harness.',
|
|
94
|
+
'',
|
|
95
|
+
'## Files',
|
|
96
|
+
'',
|
|
97
|
+
'- `slope-guard.sh` — Dispatcher script that routes guard names to `slope guard`',
|
|
98
|
+
'- `guards-manifest.json` — Machine-readable list of all installed guards',
|
|
99
|
+
'',
|
|
100
|
+
'## Integration',
|
|
101
|
+
'',
|
|
102
|
+
'To wire these guards into your harness:',
|
|
103
|
+
'',
|
|
104
|
+
'1. Configure your harness to call `slope-guard.sh <guard-name>` at the appropriate hook event',
|
|
105
|
+
'2. Pass the hook context as JSON on stdin',
|
|
106
|
+
'3. Read the JSON response from stdout',
|
|
107
|
+
'',
|
|
108
|
+
'### Hook Events',
|
|
109
|
+
'',
|
|
110
|
+
'- **PreToolUse** — Before a tool executes (can allow, deny, or inject context)',
|
|
111
|
+
'- **PostToolUse** — After a tool executes (can block or inject context)',
|
|
112
|
+
'- **Stop** — Before session ends (can block to prevent premature exit)',
|
|
113
|
+
'- **PreCompact** — Before context compaction (extract state before compression)',
|
|
114
|
+
'',
|
|
115
|
+
'### Response Format',
|
|
116
|
+
'',
|
|
117
|
+
'```json',
|
|
118
|
+
'{ "action": "allow" | "deny" | "context", "message": "...", "reason": "..." }',
|
|
119
|
+
'```',
|
|
120
|
+
'',
|
|
121
|
+
'See `guards-manifest.json` for the full list of guards and their hook events.',
|
|
122
|
+
'',
|
|
123
|
+
].join('\n');
|
|
124
|
+
writeFileSync(readmePath, readme);
|
|
125
|
+
console.log(` Created ${readmePath}`);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
detect(_cwd) {
|
|
129
|
+
// Generic is the fallback — only used when no other adapter matches
|
|
130
|
+
return true;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
/** Singleton instance */
|
|
134
|
+
export const genericAdapter = new GenericAdapter();
|
|
135
|
+
// Auto-register on import
|
|
136
|
+
registerAdapter(genericAdapter);
|
|
137
|
+
//# sourceMappingURL=generic.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generic.js","sourceRoot":"","sources":["../../../src/core/adapters/generic.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { HarnessAdapter, ToolNameMap } from '../harness.js';
|
|
2
|
+
import type { GuardResult, AnyGuardDefinition } from '../guard.js';
|
|
3
|
+
/** Windsurf hook entry in .windsurf/hooks.json */
|
|
4
|
+
export interface WindsurfHookEntry {
|
|
5
|
+
event: string;
|
|
6
|
+
matcher?: string;
|
|
7
|
+
command: string;
|
|
8
|
+
timeout?: number;
|
|
9
|
+
description?: string;
|
|
10
|
+
}
|
|
11
|
+
/** Windsurf hooks config shape */
|
|
12
|
+
export interface WindsurfHooksConfig {
|
|
13
|
+
hooks: WindsurfHookEntry[];
|
|
14
|
+
}
|
|
15
|
+
/** Windsurf hook output (JSON from adapter, translated to exit codes by dispatcher) */
|
|
16
|
+
export interface WindsurfHookOutput {
|
|
17
|
+
action: 'allow' | 'deny';
|
|
18
|
+
message?: string;
|
|
19
|
+
}
|
|
20
|
+
/** Windsurf adapter — formats guard output for Windsurf's exit-code-based hook system. */
|
|
21
|
+
export declare class WindsurfAdapter implements HarnessAdapter {
|
|
22
|
+
readonly id: "windsurf";
|
|
23
|
+
readonly displayName = "Windsurf";
|
|
24
|
+
readonly toolNames: ToolNameMap;
|
|
25
|
+
formatPreToolOutput(result: GuardResult): WindsurfHookOutput;
|
|
26
|
+
formatPostToolOutput(result: GuardResult): WindsurfHookOutput;
|
|
27
|
+
formatStopOutput(result: GuardResult): WindsurfHookOutput;
|
|
28
|
+
generateHooksConfig(guards: AnyGuardDefinition[], guardScriptPath: string): WindsurfHooksConfig;
|
|
29
|
+
installGuards(cwd: string, guards: AnyGuardDefinition[]): void;
|
|
30
|
+
detect(cwd: string): boolean;
|
|
31
|
+
}
|
|
32
|
+
/** Singleton instance */
|
|
33
|
+
export declare const windsurfAdapter: WindsurfAdapter;
|
|
34
|
+
//# sourceMappingURL=windsurf.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"windsurf.d.ts","sourceRoot":"","sources":["../../../src/core/adapters/windsurf.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAEjE,OAAO,KAAK,EAAE,WAAW,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAanE,kDAAkD;AAClD,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,kCAAkC;AAClC,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,iBAAiB,EAAE,CAAC;CAC5B;AAED,uFAAuF;AACvF,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,OAAO,GAAG,MAAM,CAAC;IACzB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAYD,0FAA0F;AAC1F,qBAAa,eAAgB,YAAW,cAAc;IACpD,QAAQ,CAAC,EAAE,EAAG,UAAU,CAAU;IAClC,QAAQ,CAAC,WAAW,cAAc;IAClC,QAAQ,CAAC,SAAS,EAAE,WAAW,CAAkB;IAEjD,mBAAmB,CAAC,MAAM,EAAE,WAAW,GAAG,kBAAkB;IAW5D,oBAAoB,CAAC,MAAM,EAAE,WAAW,GAAG,kBAAkB;IAU7D,gBAAgB,CAAC,MAAM,EAAE,WAAW,GAAG,kBAAkB;IAWzD,mBAAmB,CAAC,MAAM,EAAE,kBAAkB,EAAE,EAAE,eAAe,EAAE,MAAM,GAAG,mBAAmB;IAsB/F,aAAa,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,kBAAkB,EAAE,GAAG,IAAI;IAkF9D,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO;CAG7B;AAED,yBAAyB;AACzB,eAAO,MAAM,eAAe,iBAAwB,CAAC"}
|