@dotdotgod/claude-code 0.3.1 → 0.3.2
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/hooks/runtime.mjs +119 -2
- package/mcp/cli.mjs +5265 -0
- package/mcp/server.mjs +23466 -3
- package/package.json +11 -3
package/hooks/runtime.mjs
CHANGED
|
@@ -1,3 +1,120 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
// Generated by scripts/build-adapter-runtime.mjs; do not edit.
|
|
3
|
+
|
|
4
|
+
// packages/context/src/hooks.mjs
|
|
5
|
+
import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
|
|
6
|
+
import { createHash } from "node:crypto";
|
|
7
|
+
import { dirname, join, resolve } from "node:path";
|
|
8
|
+
function inputText() {
|
|
9
|
+
return readFileSync(0, "utf8");
|
|
10
|
+
}
|
|
11
|
+
function parseInput() {
|
|
12
|
+
const text = inputText().trim();
|
|
13
|
+
return text ? JSON.parse(text) : {};
|
|
14
|
+
}
|
|
15
|
+
function projectRoot(input) {
|
|
16
|
+
return resolve(input.cwd || input.project_dir || process.env.CLAUDE_PROJECT_DIR || process.cwd());
|
|
17
|
+
}
|
|
18
|
+
function sessionId(input) {
|
|
19
|
+
return String(input.session_id || input.sessionId || "default").replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
20
|
+
}
|
|
21
|
+
function statePath(input) {
|
|
22
|
+
return join(projectRoot(input), ".dotdotgod", "context", "runtime", `${sessionId(input)}.json`);
|
|
23
|
+
}
|
|
24
|
+
function readState(input) {
|
|
25
|
+
try {
|
|
26
|
+
return JSON.parse(readFileSync(statePath(input), "utf8"));
|
|
27
|
+
} catch {
|
|
28
|
+
return { loadRequired: false, pending: {} };
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
function writeState(input, state) {
|
|
32
|
+
const path = statePath(input);
|
|
33
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
34
|
+
const temp = `${path}.${process.pid}.tmp`;
|
|
35
|
+
writeFileSync(temp, `${JSON.stringify(state, null, 2)}
|
|
36
|
+
`);
|
|
37
|
+
renameSync(temp, path);
|
|
38
|
+
}
|
|
39
|
+
function fileHash(path) {
|
|
40
|
+
try {
|
|
41
|
+
return createHash("sha256").update(readFileSync(path)).digest("hex");
|
|
42
|
+
} catch {
|
|
43
|
+
return "missing";
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
function toolName(input) {
|
|
47
|
+
return String(input.tool_name || input.toolName || "");
|
|
48
|
+
}
|
|
49
|
+
function toolInput(input) {
|
|
50
|
+
return input.tool_input || input.toolInput || {};
|
|
51
|
+
}
|
|
52
|
+
function mcpTool(name, suffix) {
|
|
53
|
+
return name === suffix || name.endsWith(`__${suffix}`) || name.endsWith(`_${suffix}`);
|
|
54
|
+
}
|
|
55
|
+
function deny(reason) {
|
|
56
|
+
return { hookSpecificOutput: { hookEventName: "PreToolUse", permissionDecision: "deny", permissionDecisionReason: reason } };
|
|
57
|
+
}
|
|
58
|
+
function commandFrom(input) {
|
|
59
|
+
const value = toolInput(input);
|
|
60
|
+
return String(value.command || value.cmd || "");
|
|
61
|
+
}
|
|
62
|
+
function isSubstantive(name) {
|
|
63
|
+
return /^(Bash|Shell|local_shell|shell|shell_command|exec_command|Edit|Write|apply_patch)$/i.test(name);
|
|
64
|
+
}
|
|
65
|
+
function isImpactGate(name, input) {
|
|
66
|
+
if (!/^(Bash|Shell|local_shell|shell|shell_command|exec_command)$/i.test(name)) return false;
|
|
67
|
+
return /(^|\s)(test|build|lint|verify|commit|push|publish|deploy)(\s|:|$)|\b(git\s+(commit|push)|npm\s+(test|publish)|pnpm\s+[^\n]*(test|build|lint|verify|publish))\b/i.test(commandFrom(input));
|
|
68
|
+
}
|
|
69
|
+
function changedPath(input) {
|
|
70
|
+
const value = toolInput(input);
|
|
71
|
+
const path = value.file_path || value.path;
|
|
72
|
+
return typeof path === "string" ? resolve(projectRoot(input), path) : null;
|
|
73
|
+
}
|
|
74
|
+
function runHook(event) {
|
|
75
|
+
const input = parseInput();
|
|
76
|
+
const name = toolName(input);
|
|
77
|
+
const state = readState(input);
|
|
78
|
+
state.pending ||= {};
|
|
79
|
+
if (event === "sessionstart") {
|
|
80
|
+
state.loadRequired = true;
|
|
81
|
+
writeState(input, state);
|
|
82
|
+
return { hookSpecificOutput: { hookEventName: "SessionStart", additionalContext: "Dotdotgod project memory has not been loaded for this session. Before substantive shell or write work, call dotdotgod_project_load once with a concise task-specific focus." } };
|
|
83
|
+
}
|
|
84
|
+
if (event === "pretooluse") {
|
|
85
|
+
if (mcpTool(name, "dotdotgod_project_load") || mcpTool(name, "dotdotgod_project_impact")) return {};
|
|
86
|
+
if (state.loadRequired && isSubstantive(name)) return deny("Project memory is required first. Call dotdotgod_project_load with the current project root and a concise task-specific focus, then retry this tool.");
|
|
87
|
+
const pendingPaths = Object.keys(state.pending);
|
|
88
|
+
if (pendingPaths.length && isImpactGate(name, input)) return deny(`Graph impact is pending. Call dotdotgod_project_impact with paths=${JSON.stringify(pendingPaths)} and then retry this tool.`);
|
|
89
|
+
return {};
|
|
90
|
+
}
|
|
91
|
+
if (event === "posttooluse") {
|
|
92
|
+
if (mcpTool(name, "dotdotgod_project_load")) state.loadRequired = false;
|
|
93
|
+
else if (mcpTool(name, "dotdotgod_project_impact")) {
|
|
94
|
+
const checked = toolInput(input).paths;
|
|
95
|
+
if (Array.isArray(checked)) for (const path of checked) {
|
|
96
|
+
const absolute = resolve(projectRoot(input), path);
|
|
97
|
+
if (state.pending[absolute] === fileHash(absolute)) delete state.pending[absolute];
|
|
98
|
+
}
|
|
99
|
+
} else if (/^(Edit|Write|apply_patch)$/i.test(name)) {
|
|
100
|
+
const path = changedPath(input);
|
|
101
|
+
if (path) state.pending[path] = fileHash(path);
|
|
102
|
+
}
|
|
103
|
+
writeState(input, state);
|
|
104
|
+
return {};
|
|
105
|
+
}
|
|
106
|
+
return {};
|
|
107
|
+
}
|
|
108
|
+
function hookMain(event) {
|
|
109
|
+
try {
|
|
110
|
+
process.stdout.write(`${JSON.stringify(runHook(event))}
|
|
111
|
+
`);
|
|
112
|
+
} catch (error) {
|
|
113
|
+
process.stderr.write(`dotdotgod hook error: ${error instanceof Error ? error.message : String(error)}
|
|
114
|
+
`);
|
|
115
|
+
process.stdout.write("{}\n");
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// packages/claude-code/hooks/runtime-entry.mjs
|
|
120
|
+
hookMain(process.argv[2] || "");
|