@clonst/clonst 1.0.0

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.
@@ -0,0 +1,141 @@
1
+ import { appendFileSync, existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
2
+ import path from "node:path";
3
+ import { logsDir } from "./paths.js";
4
+ /**
5
+ * Human-readable server log: stderr only (stdout is reserved for the MCP stdio protocol).
6
+ */
7
+ export function logStderr(message) {
8
+ process.stderr.write(`[clonst] ${message}\n`);
9
+ }
10
+ const SAFE_NAME = /^[A-Za-z0-9._-]+$/;
11
+ /**
12
+ * Neutralizes any path traversal in a file/directory name: characters outside
13
+ * the whitelist become "_", "." and ".." are forbidden.
14
+ * Sanitization (not rejection): an ugly name beats a lost LLM response.
15
+ */
16
+ export function sanitizeName(value) {
17
+ const cleaned = value.replace(/[^A-Za-z0-9._-]/g, "_");
18
+ if (cleaned === "" || /^\.+$/.test(cleaned))
19
+ return "_";
20
+ return cleaned;
21
+ }
22
+ function isValidSessionId(value) {
23
+ return SAFE_NAME.test(value) && !/^\.+$/.test(value);
24
+ }
25
+ /**
26
+ * Logger for one review session: one JSONL line per event, plus systematic
27
+ * saving of the complete raw CLI responses (project rule: never lose a paid
28
+ * LLM response, even if parsing fails afterwards).
29
+ *
30
+ * Layout:
31
+ * ~/.clonst/logs/<sessionId>.jsonl
32
+ * ~/.clonst/logs/raw/<sessionId>/<name>.txt
33
+ *
34
+ * A full ping-pong lives under ONE sessionId: round 1 starts under a
35
+ * provisional identifier then is migrated (migrateTo) to the reviewer's
36
+ * thread_id once known; later rounds open directly under that thread_id.
37
+ */
38
+ export class SessionLogger {
39
+ _sessionId;
40
+ _jsonlPath;
41
+ _rawDir;
42
+ constructor(sessionId) {
43
+ // The sessionId is generated by Clonst: a non-conforming name is an internal bug, throw early.
44
+ if (!isValidSessionId(sessionId)) {
45
+ throw new Error(`Invalid sessionId: "${sessionId}"`);
46
+ }
47
+ this._sessionId = sessionId;
48
+ const dir = logsDir();
49
+ this._jsonlPath = path.join(dir, `${sessionId}.jsonl`);
50
+ this._rawDir = path.join(dir, "raw", sessionId);
51
+ mkdirSync(dir, { recursive: true });
52
+ }
53
+ get sessionId() {
54
+ return this._sessionId;
55
+ }
56
+ /** Path of the session's JSONL file (exposed for caller-side diagnostics). */
57
+ get jsonlPath() {
58
+ return this._jsonlPath;
59
+ }
60
+ /**
61
+ * Renames the session (JSONL file + raw directory) to a new identifier,
62
+ * typically the reviewer's thread_id once known at round 1. Never throws:
63
+ * on failure (existing target, lock...) the session keeps its provisional
64
+ * identifier and the failure is reported. Returns true if migration happened.
65
+ */
66
+ migrateTo(newSessionId) {
67
+ if (!isValidSessionId(newSessionId)) {
68
+ logStderr(`session migration refused: invalid identifier "${newSessionId}"`);
69
+ return false;
70
+ }
71
+ if (newSessionId === this._sessionId)
72
+ return true;
73
+ const dir = logsDir();
74
+ const newJsonlPath = path.join(dir, `${newSessionId}.jsonl`);
75
+ const newRawDir = path.join(dir, "raw", newSessionId);
76
+ try {
77
+ if (existsSync(newJsonlPath) || existsSync(newRawDir)) {
78
+ logStderr(`session migration refused: "${newSessionId}" already exists, logs kept under "${this._sessionId}"`);
79
+ return false;
80
+ }
81
+ if (existsSync(this._jsonlPath))
82
+ renameSync(this._jsonlPath, newJsonlPath);
83
+ if (existsSync(this._rawDir))
84
+ renameSync(this._rawDir, newRawDir);
85
+ const previous = this._sessionId;
86
+ this._sessionId = newSessionId;
87
+ this._jsonlPath = newJsonlPath;
88
+ this._rawDir = newRawDir;
89
+ this.log({ event: "session_migrated", from: previous, to: newSessionId });
90
+ return true;
91
+ }
92
+ catch (err) {
93
+ logStderr(`session migration failed (${err instanceof Error ? err.message : String(err)}), logs kept under "${this._sessionId}"`);
94
+ return false;
95
+ }
96
+ }
97
+ /** Appends a timestamped JSONL line. Never throws: a logging failure must not break a review. */
98
+ log(entry) {
99
+ try {
100
+ const line = JSON.stringify({ timestamp: new Date().toISOString(), ...entry });
101
+ appendFileSync(this._jsonlPath, line + "\n", "utf-8");
102
+ }
103
+ catch (err) {
104
+ logStderr(`failed to write JSONL log: ${err instanceof Error ? err.message : String(err)}`);
105
+ }
106
+ }
107
+ /**
108
+ * Path of a file inside the session's raw directory (name sanitized,
109
+ * directory created). Used for files written directly by the CLIs
110
+ * (--output-last-message).
111
+ */
112
+ rawPath(name) {
113
+ mkdirSync(this._rawDir, { recursive: true });
114
+ return path.join(this._rawDir, sanitizeName(name));
115
+ }
116
+ /** Re-reads a file from the session's raw directory. null if absent or unreadable. */
117
+ readRaw(name) {
118
+ try {
119
+ return readFileSync(this.rawPath(name), "utf-8");
120
+ }
121
+ catch {
122
+ return null;
123
+ }
124
+ }
125
+ /**
126
+ * Saves a complete raw response (CLI JSONL stdout, last message, etc.).
127
+ * Call it BEFORE any parsing attempt. Returns the written path, or null on failure.
128
+ */
129
+ saveRaw(name, content) {
130
+ try {
131
+ const filePath = this.rawPath(name);
132
+ writeFileSync(filePath, content, "utf-8");
133
+ return filePath;
134
+ }
135
+ catch (err) {
136
+ logStderr(`failed to save raw response ${name}: ${err instanceof Error ? err.message : String(err)}`);
137
+ return null;
138
+ }
139
+ }
140
+ }
141
+ //# sourceMappingURL=logger.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logger.js","sourceRoot":"","sources":["../../src/utils/logger.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ import os from "node:os";
2
+ import path from "node:path";
3
+ /**
4
+ * Root of Clonst's persistent data (config, logs, raw responses).
5
+ * Overridable through CLONST_HOME (used by tests to isolate themselves).
6
+ */
7
+ export function clonstHome() {
8
+ return process.env.CLONST_HOME || path.join(os.homedir(), ".clonst");
9
+ }
10
+ export function logsDir() {
11
+ return path.join(clonstHome(), "logs");
12
+ }
13
+ export function configPath() {
14
+ return path.join(clonstHome(), "config.json");
15
+ }
16
+ //# sourceMappingURL=paths.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"paths.js","sourceRoot":"","sources":["../../src/utils/paths.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B;;;GAGG;AACH,MAAM,UAAU,UAAU;IACxB,OAAO,OAAO,CAAC,GAAG,CAAC,WAAW,IAAI,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,SAAS,CAAC,CAAC;AACvE,CAAC;AAED,MAAM,UAAU,OAAO;IACrB,OAAO,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,EAAE,MAAM,CAAC,CAAC;AACzC,CAAC;AAED,MAAM,UAAU,UAAU;IACxB,OAAO,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,EAAE,aAAa,CAAC,CAAC;AAChD,CAAC"}
@@ -0,0 +1,186 @@
1
+ import { spawn } from "node:child_process";
2
+ import { logStderr } from "./logger.js";
3
+ export const DEFAULT_TIMEOUT_MS = 600_000;
4
+ /** After a timeout kill, max delay before giving up even if 'close' never fires. */
5
+ const KILL_GRACE_MS = 10_000;
6
+ // Variables inherited from the parent Claude Code session (Clonst is spawned by
7
+ // it). We do not propagate them to LLM CLIs: they can trigger nested-session
8
+ // behaviors.
9
+ const STRIPPED_ENV_PREFIXES = ["CLAUDECODE", "CLAUDE_CODE_"];
10
+ export function cleanEnv(base = process.env) {
11
+ const env = {};
12
+ for (const [key, value] of Object.entries(base)) {
13
+ if (STRIPPED_ENV_PREFIXES.some((prefix) => key.startsWith(prefix)))
14
+ continue;
15
+ env[key] = value;
16
+ }
17
+ return env;
18
+ }
19
+ /**
20
+ * Guard for dynamic identifiers (e.g. the thread_id returned by Codex).
21
+ * Accepts only [A-Za-z0-9._-], enough for UUIDs and model names.
22
+ * Do NOT use this for file paths: quoteArg handles those.
23
+ */
24
+ export function assertSafeArg(arg, label) {
25
+ if (!/^[A-Za-z0-9._-]+$/.test(arg)) {
26
+ throw new Error(`Unsafe dynamic argument for ${label}: "${arg}"`);
27
+ }
28
+ return arg;
29
+ }
30
+ // Characters that need no quoting (simple Windows/POSIX paths included).
31
+ const WIN_SAFE_ARG = /^[A-Za-z0-9_\-.:\\/=]+$/;
32
+ const POSIX_SAFE_ARG = /^[A-Za-z0-9_\-.:/=]+$/;
33
+ /**
34
+ * Centralized argument quoting for shell:true (required on Windows for the npm
35
+ * .cmd/.ps1 shims). Callers pass RAW arguments, never pre-quoted ones.
36
+ *
37
+ * Windows (cmd.exe + msvcrt parsing): the characters `"`, `%` and newlines have
38
+ * no legitimate use in Clonst (flags, UUIDs, paths) and their cmd escaping rules
39
+ * are too fragile: explicit rejection. Other arguments are wrapped in double
40
+ * quotes, with trailing backslashes doubled (msvcrt rule: `C:\dir\` -> `"C:\dir\\"`).
41
+ *
42
+ * POSIX (/bin/sh): single quotes, with `'` escaped as `'\''`.
43
+ */
44
+ export function quoteArg(arg) {
45
+ if (arg.length === 0) {
46
+ return process.platform === "win32" ? '""' : "''";
47
+ }
48
+ if (process.platform === "win32") {
49
+ if (WIN_SAFE_ARG.test(arg))
50
+ return arg;
51
+ if (/["%\r\n]/.test(arg)) {
52
+ throw new Error(`Argument cannot be safely represented under cmd.exe: "${arg}"`);
53
+ }
54
+ const withDoubledTrailingBackslashes = arg.replace(/(\\+)$/, "$1$1");
55
+ return `"${withDoubledTrailingBackslashes}"`;
56
+ }
57
+ if (POSIX_SAFE_ARG.test(arg))
58
+ return arg;
59
+ return `'${arg.replaceAll("'", `'\\''`)}'`;
60
+ }
61
+ function killProcessTree(pid) {
62
+ if (process.platform === "win32") {
63
+ // proc.kill() would only kill the intermediate shell (shell:true): the child
64
+ // codex.exe would survive and keep consuming quota. taskkill /T kills the tree.
65
+ const killer = spawn("taskkill", ["/pid", String(pid), "/T", "/F"], {
66
+ shell: false,
67
+ stdio: "ignore",
68
+ });
69
+ killer.on("error", (err) => {
70
+ logStderr(`taskkill unreachable for pid ${pid}: ${err.message}`);
71
+ });
72
+ killer.on("exit", (code) => {
73
+ if (code !== 0) {
74
+ logStderr(`taskkill failed for pid ${pid} (exit ${code}): orphan process possible`);
75
+ }
76
+ });
77
+ }
78
+ else {
79
+ // The process was spawned with detached:true, so it leads its own group:
80
+ // kill(-pid) kills the whole group (shell + child CLI).
81
+ try {
82
+ process.kill(-pid, "SIGKILL");
83
+ }
84
+ catch {
85
+ try {
86
+ process.kill(pid, "SIGKILL");
87
+ }
88
+ catch {
89
+ // already gone
90
+ }
91
+ }
92
+ }
93
+ }
94
+ // A simple executable name: letters/digits/._-, no path separator, no space.
95
+ const SIMPLE_COMMAND = /^[A-Za-z0-9._-]+$/;
96
+ /**
97
+ * Spawns a CLI and captures stdout/stderr as UTF-8.
98
+ *
99
+ * Contract:
100
+ * - `command` is a SIMPLE EXECUTABLE NAME resolved via PATH ("codex", "node", ...).
101
+ * Never a path (absolute or relative), never dynamic content: with shell:true,
102
+ * the command is not quoted by Node and a path with spaces would break
103
+ * (C:\Program Files\...). Validated here, fails early otherwise.
104
+ * - `args` is passed RAW (no caller-side pre-quoting), quoting is centralized
105
+ * here through quoteArg. This covers dynamic paths passed as arguments, e.g.
106
+ * `--output-last-message <file under ~/.clonst/>` (tested, spaces included).
107
+ * - Large variable content (prompts) goes through options.stdin.
108
+ */
109
+ export function spawnCLI(command, args, options = {}) {
110
+ if (!SIMPLE_COMMAND.test(command)) {
111
+ throw new Error(`spawnCLI expects a simple executable name resolved via PATH, got: "${command}"`);
112
+ }
113
+ const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
114
+ const start = Date.now();
115
+ const quotedArgs = args.map(quoteArg);
116
+ return new Promise((resolve, reject) => {
117
+ const proc = spawn(command, quotedArgs, {
118
+ cwd: options.cwd,
119
+ env: cleanEnv(),
120
+ shell: true,
121
+ windowsHide: true,
122
+ // POSIX: process group leader, so kill(-pid) kills the whole tree.
123
+ detached: process.platform !== "win32",
124
+ });
125
+ let stdout = "";
126
+ let stderr = "";
127
+ let timedOut = false;
128
+ let settled = false;
129
+ let graceTimer;
130
+ const settle = (result) => {
131
+ if (settled)
132
+ return;
133
+ settled = true;
134
+ clearTimeout(timer);
135
+ if (graceTimer)
136
+ clearTimeout(graceTimer);
137
+ resolve(result);
138
+ };
139
+ const timer = setTimeout(() => {
140
+ timedOut = true;
141
+ if (proc.pid !== undefined)
142
+ killProcessTree(proc.pid);
143
+ // If even the kill does not trigger 'close' (taskkill failure), return what
144
+ // we have instead of blocking the caller forever.
145
+ graceTimer = setTimeout(() => {
146
+ logStderr(`process ${command} still alive ${KILL_GRACE_MS} ms after the kill, giving up`);
147
+ settle({ stdout, stderr, exitCode: null, durationMs: Date.now() - start, timedOut: true });
148
+ }, KILL_GRACE_MS);
149
+ }, timeoutMs);
150
+ proc.stdout.setEncoding("utf-8");
151
+ proc.stderr.setEncoding("utf-8");
152
+ proc.stdout.on("data", (chunk) => {
153
+ stdout += chunk;
154
+ });
155
+ proc.stderr.on("data", (chunk) => {
156
+ stderr += chunk;
157
+ });
158
+ // EPIPE is possible if the process dies before the pipe closes, with or
159
+ // without provided stdin (end() is called in every case): an error event
160
+ // without a listener would crash the whole MCP server.
161
+ proc.stdin.on("error", () => { });
162
+ if (options.stdin !== undefined) {
163
+ proc.stdin.write(options.stdin, "utf-8");
164
+ }
165
+ proc.stdin.end();
166
+ proc.on("error", (err) => {
167
+ if (settled)
168
+ return;
169
+ settled = true;
170
+ clearTimeout(timer);
171
+ if (graceTimer)
172
+ clearTimeout(graceTimer);
173
+ reject(err);
174
+ });
175
+ proc.on("close", (code) => {
176
+ settle({
177
+ stdout,
178
+ stderr,
179
+ exitCode: code,
180
+ durationMs: Date.now() - start,
181
+ timedOut,
182
+ });
183
+ });
184
+ });
185
+ }
186
+ //# sourceMappingURL=process.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"process.js","sourceRoot":"","sources":["../../src/utils/process.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,63 @@
1
+ {
2
+ "name": "@clonst/clonst",
3
+ "version": "1.0.0",
4
+ "publishConfig": {
5
+ "access": "public"
6
+ },
7
+ "description": "MCP server that lets Claude Code get its plans and code adversarially reviewed by Codex, in a ping-pong loop until consensus",
8
+ "type": "module",
9
+ "main": "dist/index.js",
10
+ "bin": {
11
+ "clonst": "dist/index.js"
12
+ },
13
+ "files": [
14
+ "dist",
15
+ "!dist/tests"
16
+ ],
17
+ "scripts": {
18
+ "build": "tsc",
19
+ "prepublishOnly": "npm test && npm run smoke",
20
+ "dev": "tsc --watch",
21
+ "start": "node dist/index.js",
22
+ "smoke": "node scripts/smoke-mcp.mjs",
23
+ "test": "npm run build && node --test \"dist/tests/**/*.test.js\""
24
+ },
25
+ "engines": {
26
+ "node": ">=22"
27
+ },
28
+ "keywords": [
29
+ "mcp",
30
+ "mcp-server",
31
+ "model-context-protocol",
32
+ "claude",
33
+ "claude-code",
34
+ "codex",
35
+ "openai",
36
+ "anthropic",
37
+ "chatgpt",
38
+ "code-review",
39
+ "ai-code-review",
40
+ "adversarial-review",
41
+ "second-opinion",
42
+ "llm",
43
+ "developer-tools"
44
+ ],
45
+ "author": "Christopher <christopher@capritora.com>",
46
+ "license": "MIT",
47
+ "repository": {
48
+ "type": "git",
49
+ "url": "git+https://github.com/capritora/clonst.git"
50
+ },
51
+ "homepage": "https://github.com/capritora/clonst#readme",
52
+ "bugs": {
53
+ "url": "https://github.com/capritora/clonst/issues"
54
+ },
55
+ "dependencies": {
56
+ "@modelcontextprotocol/sdk": "^1.29.0",
57
+ "zod": "^4.4.3"
58
+ },
59
+ "devDependencies": {
60
+ "@types/node": "^26.1.0",
61
+ "typescript": "^6.0.3"
62
+ }
63
+ }