@cybedefend/vibedefend 1.1.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/LICENSE +77 -0
- package/README.md +120 -0
- package/bin/vibedefend.js +19 -0
- package/dist/auth/auth-store.js +170 -0
- package/dist/auth/auth-store.js.map +1 -0
- package/dist/auth/auth.js +125 -0
- package/dist/auth/auth.js.map +1 -0
- package/dist/auth/callback-server.js +216 -0
- package/dist/auth/callback-server.js.map +1 -0
- package/dist/auth/pkce.js +31 -0
- package/dist/auth/pkce.js.map +1 -0
- package/dist/auth/token-exchange.js +83 -0
- package/dist/auth/token-exchange.js.map +1 -0
- package/dist/clients/claude-code.js +170 -0
- package/dist/clients/claude-code.js.map +1 -0
- package/dist/clients/codex.js +378 -0
- package/dist/clients/codex.js.map +1 -0
- package/dist/clients/cursor-guards-rules.js +94 -0
- package/dist/clients/cursor-guards-rules.js.map +1 -0
- package/dist/clients/cursor.js +172 -0
- package/dist/clients/cursor.js.map +1 -0
- package/dist/clients/detect.js +86 -0
- package/dist/clients/detect.js.map +1 -0
- package/dist/clients/registry.js +41 -0
- package/dist/clients/registry.js.map +1 -0
- package/dist/clients/types.js +2 -0
- package/dist/clients/types.js.map +1 -0
- package/dist/clients/vscode.js +187 -0
- package/dist/clients/vscode.js.map +1 -0
- package/dist/clients/windsurf.js +151 -0
- package/dist/clients/windsurf.js.map +1 -0
- package/dist/config.js +32 -0
- package/dist/config.js.map +1 -0
- package/dist/custom-regions.js +112 -0
- package/dist/custom-regions.js.map +1 -0
- package/dist/diagnostics.js +122 -0
- package/dist/diagnostics.js.map +1 -0
- package/dist/doctor.js +125 -0
- package/dist/doctor.js.map +1 -0
- package/dist/guards-evaluator/bucketing.js +83 -0
- package/dist/guards-evaluator/bucketing.js.map +1 -0
- package/dist/guards-evaluator/evaluate.js +272 -0
- package/dist/guards-evaluator/evaluate.js.map +1 -0
- package/dist/guards-evaluator/glob.js +148 -0
- package/dist/guards-evaluator/glob.js.map +1 -0
- package/dist/guards-evaluator/index.js +9 -0
- package/dist/guards-evaluator/index.js.map +1 -0
- package/dist/guards-evaluator/preprocess.js +174 -0
- package/dist/guards-evaluator/preprocess.js.map +1 -0
- package/dist/guards-evaluator/redact.js +111 -0
- package/dist/guards-evaluator/redact.js.map +1 -0
- package/dist/guards-evaluator/regex.js +125 -0
- package/dist/guards-evaluator/regex.js.map +1 -0
- package/dist/guards-evaluator/types.js +2 -0
- package/dist/guards-evaluator/types.js.map +1 -0
- package/dist/guards-evaluator/validation.js +115 -0
- package/dist/guards-evaluator/validation.js.map +1 -0
- package/dist/hook-runner.js +6680 -0
- package/dist/hooks/install.js +169 -0
- package/dist/hooks/install.js.map +1 -0
- package/dist/hooks/runtime/api.js +167 -0
- package/dist/hooks/runtime/api.js.map +1 -0
- package/dist/hooks/runtime/config.js +60 -0
- package/dist/hooks/runtime/config.js.map +1 -0
- package/dist/hooks/runtime/emit.js +45 -0
- package/dist/hooks/runtime/emit.js.map +1 -0
- package/dist/hooks/runtime/fetch-rules.js +154 -0
- package/dist/hooks/runtime/fetch-rules.js.map +1 -0
- package/dist/hooks/runtime/guard-rules-cache.js +217 -0
- package/dist/hooks/runtime/guard-rules-cache.js.map +1 -0
- package/dist/hooks/runtime/guard-violations-buffer.js +105 -0
- package/dist/hooks/runtime/guard-violations-buffer.js.map +1 -0
- package/dist/hooks/runtime/pre-compact.js +41 -0
- package/dist/hooks/runtime/pre-compact.js.map +1 -0
- package/dist/hooks/runtime/resolve.js +206 -0
- package/dist/hooks/runtime/resolve.js.map +1 -0
- package/dist/hooks/runtime/session-review.js +198 -0
- package/dist/hooks/runtime/session-review.js.map +1 -0
- package/dist/hooks/runtime/session-start.js +101 -0
- package/dist/hooks/runtime/session-start.js.map +1 -0
- package/dist/hooks/runtime/sniff.js +112 -0
- package/dist/hooks/runtime/sniff.js.map +1 -0
- package/dist/hooks/runtime/types.js +22 -0
- package/dist/hooks/runtime/types.js.map +1 -0
- package/dist/hooks/runtime/user-prompt-submit.js +154 -0
- package/dist/hooks/runtime/user-prompt-submit.js.map +1 -0
- package/dist/index.js +129 -0
- package/dist/index.js.map +1 -0
- package/dist/install.js +183 -0
- package/dist/install.js.map +1 -0
- package/dist/login.js +335 -0
- package/dist/login.js.map +1 -0
- package/dist/prompts.js +134 -0
- package/dist/prompts.js.map +1 -0
- package/dist/self-update.js +177 -0
- package/dist/self-update.js.map +1 -0
- package/dist/status.js +58 -0
- package/dist/status.js.map +1 -0
- package/dist/utils.js +84 -0
- package/dist/utils.js.map +1 -0
- package/dist/version.js +23 -0
- package/dist/version.js.map +1 -0
- package/package.json +73 -0
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Install-side helpers for the Node-based hook runtime.
|
|
3
|
+
*
|
|
4
|
+
* Replaces the previous `writeHookFiles` (which rendered four bash
|
|
5
|
+
* scripts) with a simpler "copy + write config" pair:
|
|
6
|
+
*
|
|
7
|
+
* 1. Copy the bundled `dist/hook-runner.js` (built by esbuild) to
|
|
8
|
+
* `~/.cybedefend/hook-runner.js`. ONE script for all 5 agents.
|
|
9
|
+
* 2. Write `~/.cybedefend/runtime-config.json` with the user's region
|
|
10
|
+
* + hook tunables β the runner reads it on every invocation.
|
|
11
|
+
*
|
|
12
|
+
* Adapters then reference `node <path>/hook-runner.js <subcommand>` from
|
|
13
|
+
* their respective settings files. No bash, no jq, no curl β runs
|
|
14
|
+
* natively on Linux, macOS, AND Windows (the previous design needed
|
|
15
|
+
* Git Bash on Windows).
|
|
16
|
+
*/
|
|
17
|
+
import { copyFileSync, existsSync, rmSync } from 'node:fs';
|
|
18
|
+
import { dirname, join } from 'node:path';
|
|
19
|
+
import { fileURLToPath } from 'node:url';
|
|
20
|
+
import { ensureDirFor, home, log, writeJson } from '../utils.js';
|
|
21
|
+
/** Top-level directory we own. */
|
|
22
|
+
export const VIBEDEFEND_DIR = home('.cybedefend');
|
|
23
|
+
/** Where the bundled runner lives on the user's machine after install. */
|
|
24
|
+
export const HOOK_RUNNER_PATH = home('.cybedefend', 'hook-runner.js');
|
|
25
|
+
/** Persisted config the runner reads at hook-invocation time. */
|
|
26
|
+
export const RUNTIME_CONFIG_PATH = home('.cybedefend', 'runtime-config.json');
|
|
27
|
+
/** Legacy bash hooks dir from V1; deleted on install if found. */
|
|
28
|
+
const LEGACY_HOOKS_DIR = home('.cybedefend', 'hooks');
|
|
29
|
+
/**
|
|
30
|
+
* Resolve the path of the bundled `hook-runner.js` shipped in our npm
|
|
31
|
+
* package. Works for:
|
|
32
|
+
* - production: <pkg>/dist/install.js β <pkg>/dist/hook-runner.js
|
|
33
|
+
* - dev (tsx): <pkg>/src/hooks/install.ts β <pkg>/dist/hook-runner.js
|
|
34
|
+
*
|
|
35
|
+
* Returns null if the bundled file isn't found (build hasn't run yet).
|
|
36
|
+
*/
|
|
37
|
+
function locateBundledRunner() {
|
|
38
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
39
|
+
// Production: hook-runner.js is in the same dist/ as install.js.
|
|
40
|
+
const candidates = [
|
|
41
|
+
join(here, 'hook-runner.js'),
|
|
42
|
+
join(here, '..', 'dist', 'hook-runner.js'),
|
|
43
|
+
join(here, '..', '..', 'dist', 'hook-runner.js'),
|
|
44
|
+
];
|
|
45
|
+
for (const c of candidates) {
|
|
46
|
+
if (existsSync(c))
|
|
47
|
+
return c;
|
|
48
|
+
}
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Drop any legacy `~/.cybedefend/hooks/` directory (V1 bash scripts).
|
|
53
|
+
* Idempotent β silent no-op if the directory doesn't exist.
|
|
54
|
+
*/
|
|
55
|
+
function removeLegacyHooks() {
|
|
56
|
+
if (existsSync(LEGACY_HOOKS_DIR)) {
|
|
57
|
+
try {
|
|
58
|
+
rmSync(LEGACY_HOOKS_DIR, { recursive: true, force: true });
|
|
59
|
+
log.hint(`Removed legacy bash hooks dir: ${LEGACY_HOOKS_DIR}`);
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
// Best-effort cleanup; not fatal.
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Install the hook runtime on the user's machine.
|
|
68
|
+
*
|
|
69
|
+
* Steps:
|
|
70
|
+
* 1. Sweep legacy `~/.cybedefend/hooks/` if present.
|
|
71
|
+
* 2. Copy `dist/hook-runner.js` β `~/.cybedefend/hook-runner.js`.
|
|
72
|
+
* 3. Write `~/.cybedefend/runtime-config.json` with the user's
|
|
73
|
+
* region + hook settings.
|
|
74
|
+
*
|
|
75
|
+
* Returns the destination paths so the caller can `log.hint(...)` them.
|
|
76
|
+
*/
|
|
77
|
+
export function installHookRuntime(opts) {
|
|
78
|
+
removeLegacyHooks();
|
|
79
|
+
const source = locateBundledRunner();
|
|
80
|
+
if (source === null) {
|
|
81
|
+
throw new Error('Could not locate the bundled hook-runner.js. Did `pnpm run build` finish? ' +
|
|
82
|
+
'Expected to find it under <package>/dist/.');
|
|
83
|
+
}
|
|
84
|
+
ensureDirFor(HOOK_RUNNER_PATH);
|
|
85
|
+
copyFileSync(source, HOOK_RUNNER_PATH);
|
|
86
|
+
const runtimeConfig = {
|
|
87
|
+
region: {
|
|
88
|
+
id: opts.region.id,
|
|
89
|
+
label: opts.region.label,
|
|
90
|
+
mcpName: opts.region.mcpName,
|
|
91
|
+
mcpUrl: opts.region.mcpUrl,
|
|
92
|
+
apiBase: opts.region.apiBase,
|
|
93
|
+
},
|
|
94
|
+
hooks: {
|
|
95
|
+
enableSessionReview: opts.hooks.enableSessionReview,
|
|
96
|
+
reviewThreshold: opts.hooks.reviewThreshold,
|
|
97
|
+
autoProposeMode: opts.hooks.autoProposeMode,
|
|
98
|
+
},
|
|
99
|
+
installedVersion: opts.installedVersion,
|
|
100
|
+
};
|
|
101
|
+
writeJson(RUNTIME_CONFIG_PATH, runtimeConfig);
|
|
102
|
+
return { runnerPath: HOOK_RUNNER_PATH, configPath: RUNTIME_CONFIG_PATH };
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Compose the `command` field for a settings.json hook entry, targeting
|
|
106
|
+
* a specific subcommand of the runner. Pure β adapters call this without
|
|
107
|
+
* touching the filesystem.
|
|
108
|
+
*/
|
|
109
|
+
export function hookCommand(subcommand) {
|
|
110
|
+
return `node ${HOOK_RUNNER_PATH} ${subcommand}`;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Marker used by every adapter's `dropOurs` to identify legacy AND new
|
|
114
|
+
* entries it owns. Matches any command referencing a path under our
|
|
115
|
+
* managed dir β covers the V1 bash hooks (`~/.cybedefend/hooks/*.sh`),
|
|
116
|
+
* the new runner (`~/.cybedefend/hook-runner.js`), and any future file
|
|
117
|
+
* we put under `~/.cybedefend/`.
|
|
118
|
+
*
|
|
119
|
+
* `inspect()` uses this CONSTANT directly because that detection is
|
|
120
|
+
* legitimately scoped to whether the CURRENT install wired its hooks;
|
|
121
|
+
* legacy V0 entries left over from a previous install must NOT report
|
|
122
|
+
* `hooksWired: true`.
|
|
123
|
+
*
|
|
124
|
+
* For idempotent cleanup on re-install (`dropOurs`), use
|
|
125
|
+
* `isVibedefendOwnedHookCommand()` below β it broadens the match to
|
|
126
|
+
* cover the V0 repo-embedded path so a fresh install scrubs them too.
|
|
127
|
+
*/
|
|
128
|
+
export const VIBEDEFEND_PATH_MARKER = VIBEDEFEND_DIR;
|
|
129
|
+
/**
|
|
130
|
+
* Substrings identifying hook commands that an OLDER version of
|
|
131
|
+
* vibedefend (or its V0 manual-template predecessor) installed and
|
|
132
|
+
* that a fresh install must scrub on top of the V2 path.
|
|
133
|
+
*
|
|
134
|
+
* V0 lived inside the api-microservices repo at
|
|
135
|
+
* `tools/cybedefend-claude-hooks/hook-*.sh` β users wired absolute paths
|
|
136
|
+
* to those scripts into `~/.claude/settings.json` by hand, predating the
|
|
137
|
+
* vibedefend-cli installer. Today those entries still sit alongside the
|
|
138
|
+
* V2 Node runner unless we drop them: SessionStart, PreToolUse, Stop and
|
|
139
|
+
* PreCompact each fired twice on Claude Code, duplicating context
|
|
140
|
+
* injection and (observed in the wild) confusing the agent into ignoring
|
|
141
|
+
* the workflow doctrine.
|
|
142
|
+
*
|
|
143
|
+
* Matching by directory NAME (`cybedefend-claude-hooks/`) rather than
|
|
144
|
+
* absolute path makes it location-independent β the user's repo can
|
|
145
|
+
* live anywhere on disk.
|
|
146
|
+
*/
|
|
147
|
+
const LEGACY_HOOK_MARKERS = [
|
|
148
|
+
'cybedefend-claude-hooks',
|
|
149
|
+
];
|
|
150
|
+
/**
|
|
151
|
+
* Predicate every adapter's `dropOurs` uses to decide whether a hook
|
|
152
|
+
* entry belongs to vibedefend. Returns `true` for the current V2 path
|
|
153
|
+
* AND for any historical vibedefend-managed path (`LEGACY_HOOK_MARKERS`).
|
|
154
|
+
*
|
|
155
|
+
* Third-party hooks (different command, different path) MUST be left
|
|
156
|
+
* alone β only commands that match one of these markers get scrubbed.
|
|
157
|
+
*/
|
|
158
|
+
export function isVibedefendOwnedHookCommand(command) {
|
|
159
|
+
if (typeof command !== 'string' || command.length === 0)
|
|
160
|
+
return false;
|
|
161
|
+
if (command.includes(VIBEDEFEND_PATH_MARKER))
|
|
162
|
+
return true;
|
|
163
|
+
for (const marker of LEGACY_HOOK_MARKERS) {
|
|
164
|
+
if (command.includes(marker))
|
|
165
|
+
return true;
|
|
166
|
+
}
|
|
167
|
+
return false;
|
|
168
|
+
}
|
|
169
|
+
//# sourceMappingURL=install.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"install.js","sourceRoot":"","sources":["../../src/hooks/install.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
const DEFAULT_TIMEOUT_MS = 10_000;
|
|
2
|
+
/**
|
|
3
|
+
* POST /project/<id>/business-rules/fetch
|
|
4
|
+
*
|
|
5
|
+
* Returns the rendered markdown body + total rule count, or `null` on
|
|
6
|
+
* any error (network, 4xx/5xx, malformed JSON). The hook treats `null`
|
|
7
|
+
* as "no rules to inject, proceed".
|
|
8
|
+
*/
|
|
9
|
+
export async function fetchBusinessRules(opts) {
|
|
10
|
+
const f = opts.fetchImpl ?? globalThis.fetch;
|
|
11
|
+
const url = `${opts.apiBase}/project/${encodeURIComponent(opts.projectId)}/business-rules/fetch`;
|
|
12
|
+
let res;
|
|
13
|
+
try {
|
|
14
|
+
res = await f(url, {
|
|
15
|
+
method: 'POST',
|
|
16
|
+
headers: {
|
|
17
|
+
Authorization: `Bearer ${opts.token}`,
|
|
18
|
+
'Content-Type': 'application/json',
|
|
19
|
+
},
|
|
20
|
+
body: JSON.stringify({
|
|
21
|
+
files: opts.files,
|
|
22
|
+
intent: opts.intent,
|
|
23
|
+
format: 'md',
|
|
24
|
+
topK: opts.topK ?? 5,
|
|
25
|
+
}),
|
|
26
|
+
signal: AbortSignal.timeout(opts.timeoutMs ?? DEFAULT_TIMEOUT_MS),
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
if (!res.ok)
|
|
33
|
+
return null;
|
|
34
|
+
let body;
|
|
35
|
+
try {
|
|
36
|
+
body = await res.json();
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
// Tolerate both `{data: {markdown, total}}` (gateway-wrapped) and the
|
|
42
|
+
// flat shape `{markdown, total}` (older ingestion-service replies).
|
|
43
|
+
const root = body;
|
|
44
|
+
const data = root.data ?? root;
|
|
45
|
+
const markdown = typeof data.markdown === 'string'
|
|
46
|
+
? data.markdown
|
|
47
|
+
: typeof data.markdown_body === 'string'
|
|
48
|
+
? (data.markdown_body)
|
|
49
|
+
: '';
|
|
50
|
+
const total = typeof data.total === 'number' ? data.total : 0;
|
|
51
|
+
return { markdown, total, raw: body };
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* POST /project/<id>/security-rules/fetch
|
|
55
|
+
*
|
|
56
|
+
* Identical contract to {@link fetchBusinessRules} β same input, same
|
|
57
|
+
* tolerant JSON unwrapping, same `null`-on-any-error guarantee. Only the
|
|
58
|
+
* URL path differs (`security-rules/fetch` instead of `business-rules/fetch`).
|
|
59
|
+
*/
|
|
60
|
+
export async function fetchSecurityRules(opts) {
|
|
61
|
+
const f = opts.fetchImpl ?? globalThis.fetch;
|
|
62
|
+
const url = `${opts.apiBase}/project/${encodeURIComponent(opts.projectId)}/security-rules/fetch`;
|
|
63
|
+
let res;
|
|
64
|
+
try {
|
|
65
|
+
res = await f(url, {
|
|
66
|
+
method: 'POST',
|
|
67
|
+
headers: {
|
|
68
|
+
Authorization: `Bearer ${opts.token}`,
|
|
69
|
+
'Content-Type': 'application/json',
|
|
70
|
+
},
|
|
71
|
+
body: JSON.stringify({
|
|
72
|
+
files: opts.files,
|
|
73
|
+
intent: opts.intent,
|
|
74
|
+
format: 'md',
|
|
75
|
+
topK: opts.topK ?? 5,
|
|
76
|
+
}),
|
|
77
|
+
signal: AbortSignal.timeout(opts.timeoutMs ?? DEFAULT_TIMEOUT_MS),
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
if (!res.ok)
|
|
84
|
+
return null;
|
|
85
|
+
let body;
|
|
86
|
+
try {
|
|
87
|
+
body = await res.json();
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
// Tolerate both `{data: {markdown, total}}` (gateway-wrapped) and the
|
|
93
|
+
// flat shape `{markdown, total}` (older ingestion-service replies).
|
|
94
|
+
const root = body;
|
|
95
|
+
const data = root.data ?? root;
|
|
96
|
+
const markdown = typeof data.markdown === 'string'
|
|
97
|
+
? data.markdown
|
|
98
|
+
: typeof data.markdown_body === 'string'
|
|
99
|
+
? (data.markdown_body)
|
|
100
|
+
: '';
|
|
101
|
+
const total = typeof data.total === 'number' ? data.total : 0;
|
|
102
|
+
return { markdown, total, raw: body };
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* GET /project/<id>/business-rules/proposals?status=proposed
|
|
106
|
+
* Used by the SessionStart hook to show the inbox count to the user.
|
|
107
|
+
*/
|
|
108
|
+
export async function fetchProposalsCount(opts) {
|
|
109
|
+
const f = opts.fetchImpl ?? globalThis.fetch;
|
|
110
|
+
const url = `${opts.apiBase}/project/${encodeURIComponent(opts.projectId)}/business-rules/proposals?status=proposed`;
|
|
111
|
+
let res;
|
|
112
|
+
try {
|
|
113
|
+
res = await f(url, {
|
|
114
|
+
method: 'GET',
|
|
115
|
+
headers: { Authorization: `Bearer ${opts.token}` },
|
|
116
|
+
signal: AbortSignal.timeout(opts.timeoutMs ?? 5_000),
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
if (!res.ok)
|
|
123
|
+
return null;
|
|
124
|
+
let body;
|
|
125
|
+
try {
|
|
126
|
+
body = await res.json();
|
|
127
|
+
}
|
|
128
|
+
catch {
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
const root = body;
|
|
132
|
+
const data = root.data ?? root;
|
|
133
|
+
const total = typeof data.total === 'number'
|
|
134
|
+
? data.total
|
|
135
|
+
: Array.isArray(data.proposals)
|
|
136
|
+
? data.proposals.length
|
|
137
|
+
: 0;
|
|
138
|
+
return { total, raw: body };
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Authenticated liveness probe for `vibedefend status` / `doctor`.
|
|
142
|
+
* Hits a cheap, auth-gated, project-scoped GET and classifies the result:
|
|
143
|
+
* reachable + authorized β `ok`, 401/403 β `token_invalid`, network failure
|
|
144
|
+
* β `unreachable`, any other non-2xx β `error`.
|
|
145
|
+
*/
|
|
146
|
+
export async function pingGateway(opts) {
|
|
147
|
+
const f = opts.fetchImpl ?? globalThis.fetch;
|
|
148
|
+
const url = `${opts.apiBase}/project/${encodeURIComponent(opts.projectId)}/business-rules/proposals?status=proposed`;
|
|
149
|
+
let res;
|
|
150
|
+
try {
|
|
151
|
+
res = await f(url, {
|
|
152
|
+
method: 'GET',
|
|
153
|
+
headers: { Authorization: `Bearer ${opts.token}` },
|
|
154
|
+
signal: AbortSignal.timeout(opts.timeoutMs ?? 5_000),
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
catch {
|
|
158
|
+
return { status: 'unreachable' };
|
|
159
|
+
}
|
|
160
|
+
if (res.status === 401 || res.status === 403) {
|
|
161
|
+
return { status: 'token_invalid' };
|
|
162
|
+
}
|
|
163
|
+
if (res.ok)
|
|
164
|
+
return { status: 'ok' };
|
|
165
|
+
return { status: 'error', detail: `HTTP ${res.status}` };
|
|
166
|
+
}
|
|
167
|
+
//# sourceMappingURL=api.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api.js","sourceRoot":"","sources":["../../../src/hooks/runtime/api.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Load the runtime config written by the installer.
|
|
3
|
+
*
|
|
4
|
+
* One file per user: `~/.cybedefend/runtime-config.json`. Holds the
|
|
5
|
+
* region (so the hook knows which `apiBase` / `mcpName` to use) and
|
|
6
|
+
* tunables (threshold, auto-propose). Re-written every time the user
|
|
7
|
+
* runs `vibedefend install` or `vibedefend update`.
|
|
8
|
+
*
|
|
9
|
+
* Why a config file rather than env vars or CLI args?
|
|
10
|
+
* - Settings.json `command` strings can't easily carry structured data
|
|
11
|
+
* across the OS shell escape boundary (try threading a JSON object
|
|
12
|
+
* through a Windows cmd.exe command line and you'll see).
|
|
13
|
+
* - Env vars work but require listing every key in every adapter's
|
|
14
|
+
* settings file. Brittle.
|
|
15
|
+
* - One file = one source of truth, easy for users to inspect, easy
|
|
16
|
+
* for the installer to atomically update.
|
|
17
|
+
*/
|
|
18
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
19
|
+
import { homedir } from 'node:os';
|
|
20
|
+
import { join } from 'node:path';
|
|
21
|
+
export function runtimeConfigPath() {
|
|
22
|
+
return join(homedir(), '.cybedefend', 'runtime-config.json');
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Returns the config or `null` if absent / corrupt. Hooks treat `null`
|
|
26
|
+
* as "not installed yet, silently no-op" rather than crashing the
|
|
27
|
+
* agent's edit.
|
|
28
|
+
*/
|
|
29
|
+
export function loadRuntimeConfig(path) {
|
|
30
|
+
const p = path ?? runtimeConfigPath();
|
|
31
|
+
if (!existsSync(p))
|
|
32
|
+
return null;
|
|
33
|
+
try {
|
|
34
|
+
const raw = readFileSync(p, 'utf8');
|
|
35
|
+
return JSON.parse(raw);
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
/** Read JSON from stdin (the hook event). Returns `{}` on parse failure. */
|
|
42
|
+
export async function readStdinJson() {
|
|
43
|
+
const chunks = [];
|
|
44
|
+
for await (const chunk of process.stdin) {
|
|
45
|
+
chunks.push(typeof chunk === 'string' ? Buffer.from(chunk) : chunk);
|
|
46
|
+
}
|
|
47
|
+
const raw = Buffer.concat(chunks).toString('utf8');
|
|
48
|
+
if (!raw.trim())
|
|
49
|
+
return {};
|
|
50
|
+
try {
|
|
51
|
+
const parsed = JSON.parse(raw);
|
|
52
|
+
return typeof parsed === 'object' && parsed !== null
|
|
53
|
+
? parsed
|
|
54
|
+
: {};
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
return {};
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../../src/hooks/runtime/config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAIjC,MAAM,UAAU,iBAAiB;IAC/B,OAAO,IAAI,CAAC,OAAO,EAAE,EAAE,aAAa,EAAE,qBAAqB,CAAC,CAAC;AAC/D,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,iBAAiB,CAAC,IAAa;IAC7C,MAAM,CAAC,GAAG,IAAI,IAAI,iBAAiB,EAAE,CAAC;IACtC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC;QAAE,OAAO,IAAI,CAAC;IAChC,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,YAAY,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC;QACpC,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAkB,CAAC;IAC1C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,4EAA4E;AAC5E,MAAM,CAAC,KAAK,UAAU,aAAa;IACjC,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,IAAI,KAAK,EAAE,MAAM,KAAK,IAAI,OAAO,CAAC,KAAK,EAAE,CAAC;QACxC,MAAM,CAAC,IAAI,CAAC,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;IACtE,CAAC;IACD,MAAM,GAAG,GAAG,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;IACnD,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE;QAAE,OAAO,EAAE,CAAC;IAC3B,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAC/B,OAAO,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAK,IAAI;YAClD,CAAC,CAAE,MAAkC;YACrC,CAAC,CAAC,EAAE,CAAC;IACT,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
export function emit(body, client) {
|
|
2
|
+
if (!body)
|
|
3
|
+
return;
|
|
4
|
+
if (client === 'cursor') {
|
|
5
|
+
const payload = JSON.stringify({
|
|
6
|
+
permission: 'allow',
|
|
7
|
+
agent_message: body,
|
|
8
|
+
});
|
|
9
|
+
process.stdout.write(payload);
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
// Default: plain markdown to stdout.
|
|
13
|
+
process.stdout.write(body);
|
|
14
|
+
if (!body.endsWith('\n'))
|
|
15
|
+
process.stdout.write('\n');
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* SessionStart-context emission.
|
|
19
|
+
*
|
|
20
|
+
* For Codex: wraps in the documented JSON envelope so the body lands in
|
|
21
|
+
* `additionalContext` and Codex injects it as developer context for the
|
|
22
|
+
* upcoming conversation. (The "plain text on stdout" fallback Codex's
|
|
23
|
+
* docs mention does not appear to be reliably honored in practice.)
|
|
24
|
+
*
|
|
25
|
+
* For every other client: delegates to `emit`, which preserves Cursor's
|
|
26
|
+
* permission-allow envelope and Claude Code / VS Code / Windsurf's
|
|
27
|
+
* plain-markdown shape β all of which already work for their respective
|
|
28
|
+
* SessionStart equivalents.
|
|
29
|
+
*/
|
|
30
|
+
export function emitContext(body, client) {
|
|
31
|
+
if (!body)
|
|
32
|
+
return;
|
|
33
|
+
if (client === 'codex') {
|
|
34
|
+
const payload = JSON.stringify({
|
|
35
|
+
hookSpecificOutput: {
|
|
36
|
+
hookEventName: 'SessionStart',
|
|
37
|
+
additionalContext: body,
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
process.stdout.write(payload);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
emit(body, client);
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=emit.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"emit.js","sourceRoot":"","sources":["../../../src/hooks/runtime/emit.ts"],"names":[],"mappings":"AAgCA,MAAM,UAAU,IAAI,CAAC,IAAY,EAAE,MAAgB;IACjD,IAAI,CAAC,IAAI;QAAE,OAAO;IAElB,IAAI,MAAM,KAAK,QAAQ,EAAE,CAAC;QACxB,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC;YAC7B,UAAU,EAAE,OAAO;YACnB,aAAa,EAAE,IAAI;SACpB,CAAC,CAAC;QACH,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QAC9B,OAAO;IACT,CAAC;IAED,qCAAqC;IACrC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAC3B,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC;QAAE,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;AACvD,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,WAAW,CAAC,IAAY,EAAE,MAAgB;IACxD,IAAI,CAAC,IAAI;QAAE,OAAO;IAElB,IAAI,MAAM,KAAK,OAAO,EAAE,CAAC;QACvB,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC;YAC7B,kBAAkB,EAAE;gBAClB,aAAa,EAAE,cAAc;gBAC7B,iBAAiB,EAAE,IAAI;aACxB;SACF,CAAC,CAAC;QACH,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QAC9B,OAAO;IACT,CAAC;IAED,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;AACrB,CAAC"}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PreToolUse hook β fetch business rules before an Edit/Write.
|
|
3
|
+
*
|
|
4
|
+
* Triggered by every agent on every tool call (Cursor and Claude Code
|
|
5
|
+
* honour matchers and only fire for Edit/Write/MultiEdit; VS Code
|
|
6
|
+
* ignores matchers, so we self-filter; Windsurf has its own
|
|
7
|
+
* pre_write_code event; Codex uses apply_patch). We early-exit unless
|
|
8
|
+
* the tool name is in our interested set.
|
|
9
|
+
*/
|
|
10
|
+
import { sniff } from './sniff.js';
|
|
11
|
+
import { resolveProjectId, resolveToken } from './resolve.js';
|
|
12
|
+
import { fetchBusinessRules, fetchSecurityRules } from './api.js';
|
|
13
|
+
import { emit } from './emit.js';
|
|
14
|
+
import { loadRuntimeConfig, readStdinJson } from './config.js';
|
|
15
|
+
const INTERESTED_TOOLS = new Set([
|
|
16
|
+
'Edit',
|
|
17
|
+
'Write',
|
|
18
|
+
'MultiEdit',
|
|
19
|
+
'apply_patch',
|
|
20
|
+
]);
|
|
21
|
+
export async function fetchRulesHook(deps = {}) {
|
|
22
|
+
const readStdin = deps.readStdin ?? readStdinJson;
|
|
23
|
+
const loadConfig = deps.loadConfig ?? loadRuntimeConfig;
|
|
24
|
+
const resolveProject = deps.resolveProject ?? resolveProjectId;
|
|
25
|
+
const resolveTok = deps.resolveTokenFn ?? resolveToken;
|
|
26
|
+
const fetchRules = deps.fetchRulesFn ?? fetchBusinessRules;
|
|
27
|
+
const fetchSecRules = deps.fetchSecurityRulesFn ?? fetchSecurityRules;
|
|
28
|
+
const emitFn = deps.emitFn ?? emit;
|
|
29
|
+
const stdin = await readStdin();
|
|
30
|
+
const event = sniff(stdin);
|
|
31
|
+
// Self-filter: ignore tool calls we don't care about. Crucial for
|
|
32
|
+
// VS Code Copilot which ignores matchers and fires PreToolUse on
|
|
33
|
+
// every tool call.
|
|
34
|
+
if (!INTERESTED_TOOLS.has(event.toolName))
|
|
35
|
+
return;
|
|
36
|
+
if (!event.filePath)
|
|
37
|
+
return;
|
|
38
|
+
const config = loadConfig();
|
|
39
|
+
if (!config)
|
|
40
|
+
return;
|
|
41
|
+
const { projectId, apiBase: configApiBase } = resolveProject();
|
|
42
|
+
if (!projectId)
|
|
43
|
+
return;
|
|
44
|
+
const apiBase = process.env.CYBEDEFEND_API_BASE ?? configApiBase ?? config.region.apiBase;
|
|
45
|
+
const token = resolveTok(config.region.mcpName);
|
|
46
|
+
if (!token)
|
|
47
|
+
return;
|
|
48
|
+
const intent = buildIntent(event);
|
|
49
|
+
const fetchOpts = {
|
|
50
|
+
apiBase,
|
|
51
|
+
projectId,
|
|
52
|
+
token,
|
|
53
|
+
files: [event.filePath],
|
|
54
|
+
intent,
|
|
55
|
+
topK: 5,
|
|
56
|
+
};
|
|
57
|
+
// Fetch both corpora in parallel. They are independent β either one
|
|
58
|
+
// failing (returning null) must NEVER suppress the other's block.
|
|
59
|
+
const [businessResult, securityResult] = await Promise.all([
|
|
60
|
+
fetchRules(fetchOpts),
|
|
61
|
+
fetchSecRules(fetchOpts),
|
|
62
|
+
]);
|
|
63
|
+
if (businessResult) {
|
|
64
|
+
const body = composeBody(businessResult.markdown, businessResult.total, event.filePath);
|
|
65
|
+
if (body)
|
|
66
|
+
emitFn(body, event.client);
|
|
67
|
+
}
|
|
68
|
+
if (securityResult &&
|
|
69
|
+
securityResult.total > 0 &&
|
|
70
|
+
securityResult.markdown &&
|
|
71
|
+
securityResult.markdown.length > 0) {
|
|
72
|
+
emitFn(composeSecurityBody(securityResult.markdown, securityResult.total, event.filePath), event.client);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
/** Build the retrieval `intent` string from the event content. */
|
|
76
|
+
function buildIntent(event) {
|
|
77
|
+
const limit = parseInt(process.env.CYBEDEFEND_INTENT_BYTES ?? '800', 10);
|
|
78
|
+
const snippet = event.newContent
|
|
79
|
+
.slice(0, Number.isFinite(limit) ? limit : 800)
|
|
80
|
+
.replace(/\s+/g, ' ')
|
|
81
|
+
.trim();
|
|
82
|
+
return `Working on ${event.filePath} (tool=${event.toolName}, client=${event.client}). Code being written: ${snippet}`;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Imperative + ready-to-paste template that the agent prepends to its reply.
|
|
86
|
+
*
|
|
87
|
+
* Why a concrete template instead of "consider emitting": Claude Code's TUI
|
|
88
|
+
* collapses MCP tool calls into a one-liner ("utilisΓ© 2 outils"), so the
|
|
89
|
+
* user has no visibility into WHICH rules were fetched or HOW the agent
|
|
90
|
+
* applied them. The previous "MANDATORY β emit a status block" language
|
|
91
|
+
* read as guidance and the agent silently applied rules without surfacing
|
|
92
|
+
* them. Concrete templates the agent paste-and-fills are followed far
|
|
93
|
+
* more reliably than abstract instructions; this block is what the user
|
|
94
|
+
* sees in chat as proof CybeDefend is influencing the change.
|
|
95
|
+
*
|
|
96
|
+
* Format chosen: compact blockquote (3-4 lines per file edit), one line
|
|
97
|
+
* per rule, with [severity Β· category] for skimmability and a one-sentence
|
|
98
|
+
* applies/N-A reason.
|
|
99
|
+
*/
|
|
100
|
+
function visibleCalloutTemplate(kind, total, filePath) {
|
|
101
|
+
const subject = kind === 'Business' ? 'rules' : 'security rules';
|
|
102
|
+
return [
|
|
103
|
+
'---',
|
|
104
|
+
'',
|
|
105
|
+
`**π‘οΈ REQUIRED USER-VISIBLE MARKER** β you MUST include this exact block as the FIRST paragraph of your next assistant reply, BEFORE the Edit/Write tool call. Do not paraphrase, do not skip β without it, the user cannot see CybeDefend is influencing the change.`,
|
|
106
|
+
'',
|
|
107
|
+
`> π‘οΈ **CybeDefend ${kind} Rules** β ${total} rule${total === 1 ? '' : 's'} for \`${filePath}\``,
|
|
108
|
+
`> - **<rule-title>** [<severity> Β· <category>] β applies because <one-sentence reason>`,
|
|
109
|
+
`> - **<rule-title>** [<severity> Β· <category>] β N/A because <one-sentence reason>`,
|
|
110
|
+
'',
|
|
111
|
+
`(Fill the bracketed values from the ${subject} listed above. Keep one line per rule. If a rule does not apply to this change, still emit the line with an N/A reason β the user needs to see CybeDefend was consulted.)`,
|
|
112
|
+
'',
|
|
113
|
+
].join('\n');
|
|
114
|
+
}
|
|
115
|
+
/** Compose the markdown body the agent will see. */
|
|
116
|
+
function composeBody(markdown, total, filePath) {
|
|
117
|
+
if (markdown && markdown.length > 0) {
|
|
118
|
+
return [
|
|
119
|
+
`## CybeDefend Business Rules (${total} β fetched for ${filePath})`,
|
|
120
|
+
markdown,
|
|
121
|
+
'',
|
|
122
|
+
visibleCalloutTemplate('Business', total, filePath),
|
|
123
|
+
].join('\n');
|
|
124
|
+
}
|
|
125
|
+
if (total === 0) {
|
|
126
|
+
// Even with 0 matches, the agent must still surface a visible line so
|
|
127
|
+
// the user can see CybeDefend ran on this file rather than being
|
|
128
|
+
// silent (silence is indistinguishable from "CybeDefend wasn't
|
|
129
|
+
// consulted at all").
|
|
130
|
+
return [
|
|
131
|
+
`## CybeDefend Business Rules (0 β fetched for ${filePath})`,
|
|
132
|
+
'',
|
|
133
|
+
'(no matching business rules for this change)',
|
|
134
|
+
'',
|
|
135
|
+
'---',
|
|
136
|
+
'',
|
|
137
|
+
`**π‘οΈ REQUIRED USER-VISIBLE MARKER** β include this exact line as the FIRST paragraph of your next assistant reply, BEFORE the Edit/Write tool call:`,
|
|
138
|
+
'',
|
|
139
|
+
`> π‘οΈ **CybeDefend** β no matching business rules for \`${filePath}\`. Proceeding.`,
|
|
140
|
+
'',
|
|
141
|
+
].join('\n');
|
|
142
|
+
}
|
|
143
|
+
return '';
|
|
144
|
+
}
|
|
145
|
+
/** Compose the security-rules markdown block. Caller omits it when empty. */
|
|
146
|
+
function composeSecurityBody(markdown, total, filePath) {
|
|
147
|
+
return [
|
|
148
|
+
`## CybeDefend Security Rules (${total} β fetched for ${filePath})`,
|
|
149
|
+
markdown,
|
|
150
|
+
'',
|
|
151
|
+
visibleCalloutTemplate('Security', total, filePath),
|
|
152
|
+
].join('\n');
|
|
153
|
+
}
|
|
154
|
+
//# sourceMappingURL=fetch-rules.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fetch-rules.js","sourceRoot":"","sources":["../../../src/hooks/runtime/fetch-rules.ts"],"names":[],"mappings":"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"}
|