mcp-castor 2026.3.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.
Files changed (42) hide show
  1. package/README.md +487 -0
  2. package/bin/castor.js +706 -0
  3. package/index.js +206 -0
  4. package/package.json +97 -0
  5. package/skills/canary-test-staging/SKILL.md +24 -0
  6. package/skills/evo-mutation-rollback/SKILL.md +29 -0
  7. package/skills/hypothesis-generation/SKILL.md +26 -0
  8. package/skills/traceback-condensing/SKILL.md +26 -0
  9. package/src/castor_runner.js +469 -0
  10. package/src/config.js +1204 -0
  11. package/src/env.js +10 -0
  12. package/src/evo_engine.js +214 -0
  13. package/src/harness/core/events.js +75 -0
  14. package/src/harness/core/kernel.js +209 -0
  15. package/src/harness/evo/evaluator.js +156 -0
  16. package/src/harness/evo/evo_operator.js +550 -0
  17. package/src/harness/evo/lineage_dag.js +383 -0
  18. package/src/harness/evo/trace_repair.js +173 -0
  19. package/src/harness/evo/watchdog.js +72 -0
  20. package/src/harness/loop_detector.js +135 -0
  21. package/src/harness/runner.js +1216 -0
  22. package/src/harness/services/ast_service.js +1813 -0
  23. package/src/harness/services/event_logger.js +275 -0
  24. package/src/harness/services/mcp_bridge.js +408 -0
  25. package/src/harness/services/provider_vllm.js +728 -0
  26. package/src/harness/services/sandbox_fs.js +1238 -0
  27. package/src/harness/services/searxng_lifecycle.js +254 -0
  28. package/src/harness/services/shell_executor.js +264 -0
  29. package/src/harness/services/shell_validator.js +506 -0
  30. package/src/harness/services/web_service.js +828 -0
  31. package/src/platform.js +344 -0
  32. package/src/repetition_detector.js +139 -0
  33. package/src/semaphore.js +373 -0
  34. package/src/server_lifecycle.js +781 -0
  35. package/src/skills.js +400 -0
  36. package/src/state_pruner.js +392 -0
  37. package/src/task_registry.js +1357 -0
  38. package/src/telemetry.js +638 -0
  39. package/src/tools.js +997 -0
  40. package/src/wsl_bridge.js +629 -0
  41. package/src/wsl_env.js +171 -0
  42. package/stream_proxy.js +453 -0
@@ -0,0 +1,135 @@
1
+ /**
2
+ * Action-Hash Loop Detector (Fingerprint-based Stagnation Tripwire).
3
+ *
4
+ * Implements sliding-window action-state fingerprinting to detect recurring,
5
+ * non-mutating tool-call cycles ("doom loops") early (e.g. turn 10-15) rather
6
+ * than waiting for an arbitrary global turn ceiling.
7
+ *
8
+ * Distinguishes:
9
+ * - Legitimate empirical exploration: changing probe scripts, testing different
10
+ * parameters, varying tools -> hashes differ -> NOT a loop.
11
+ * - True doom loop: executing the exact same tool with identical arguments and
12
+ * identical status multiple times without any intervening state mutations.
13
+ */
14
+
15
+ import crypto from "node:crypto";
16
+ import {
17
+ DEFAULT_LOOP_DETECTION_WINDOW,
18
+ DEFAULT_LOOP_DETECTION_REPETITIONS,
19
+ } from "../config.js";
20
+
21
+ export class LoopDetector {
22
+ /**
23
+ * @param {object} [options]
24
+ * @param {number} [options.windowSize] Sliding window size in turns (default: 6)
25
+ * @param {number} [options.threshold] Repetition count to trigger loop (default: 3)
26
+ * @param {number} [options.repetitionThreshold] Alias for threshold
27
+ */
28
+ constructor(options = {}) {
29
+ this.windowSize = options.windowSize || DEFAULT_LOOP_DETECTION_WINDOW || 6;
30
+ this.threshold =
31
+ options.threshold ||
32
+ options.repetitionThreshold ||
33
+ DEFAULT_LOOP_DETECTION_REPETITIONS ||
34
+ 3;
35
+ this.repetitionThreshold = this.threshold;
36
+ this.history = [];
37
+ this.mutationsSinceLastAction = 0;
38
+ }
39
+
40
+ /**
41
+ * Record that a file or workspace state mutation occurred (e.g. edit_file, write_file, apply_patch).
42
+ */
43
+ recordMutation() {
44
+ this.mutationsSinceLastAction++;
45
+ }
46
+
47
+ /**
48
+ * Compute a deterministic hash for a tool call.
49
+ * Normalizes argument object keys so formatting differences don't mask identical actions.
50
+ *
51
+ * @param {string} toolName
52
+ * @param {object|string} args
53
+ * @param {object} [result]
54
+ * @returns {string} 16-character hex signature
55
+ */
56
+ computeActionHash(toolName, args, result) {
57
+ let canonicalArgs = "";
58
+ try {
59
+ if (args && typeof args === "object") {
60
+ canonicalArgs = JSON.stringify(args, Object.keys(args).sort());
61
+ } else {
62
+ canonicalArgs = String(args ?? "");
63
+ }
64
+ } catch {
65
+ canonicalArgs = String(args ?? "");
66
+ }
67
+ const exitSig = result ? (result.exitCode ?? (result.isError ? 1 : 0)) : 0;
68
+ return crypto
69
+ .createHash("sha256")
70
+ .update(`${toolName}:${canonicalArgs}:${exitSig}`)
71
+ .digest("hex")
72
+ .slice(0, 16);
73
+ }
74
+
75
+ /**
76
+ * Record an executed action and evaluate loop status over the sliding window.
77
+ *
78
+ * @param {string} toolName
79
+ * @param {object|string} args
80
+ * @param {object} [result]
81
+ * @returns {{ isLoop: boolean, repeats: number, repetitionCount: number, fingerprint: string, signature: string, toolName: string }}
82
+ */
83
+ recordAction(toolName, args, result) {
84
+ const hash = this.computeActionHash(toolName, args, result);
85
+ const hadMutation = this.mutationsSinceLastAction > 0;
86
+ this.history.push({
87
+ toolName,
88
+ hash,
89
+ hadMutation,
90
+ timestamp: Date.now(),
91
+ });
92
+ this.mutationsSinceLastAction = 0;
93
+
94
+ while (this.history.length > this.windowSize) {
95
+ this.history.shift();
96
+ }
97
+
98
+ return this.checkLoop();
99
+ }
100
+
101
+ /**
102
+ * Check if the latest action is repeating within the sliding window without intervening mutations.
103
+ *
104
+ * @returns {{ isLoop: boolean, repeats: number, repetitionCount: number, fingerprint: string, signature: string, toolName: string }}
105
+ */
106
+ checkLoop() {
107
+ if (this.history.length === 0) {
108
+ return { isLoop: false, repeats: 0, repetitionCount: 0, fingerprint: "", signature: "", toolName: "" };
109
+ }
110
+ const latest = this.history[this.history.length - 1];
111
+ let matchCount = 0;
112
+ let mutationsInBetween = false;
113
+
114
+ for (let i = this.history.length - 1; i >= 0; i--) {
115
+ const item = this.history[i];
116
+ if (item.hash === latest.hash) {
117
+ matchCount++;
118
+ }
119
+ if (item.hadMutation) {
120
+ // Mutation occurred before/at this action; do not count actions before it
121
+ break;
122
+ }
123
+ }
124
+
125
+ const isLoop = matchCount >= this.threshold;
126
+ return {
127
+ isLoop,
128
+ repeats: matchCount,
129
+ repetitionCount: matchCount,
130
+ fingerprint: latest.hash,
131
+ signature: latest.hash,
132
+ toolName: latest.toolName,
133
+ };
134
+ }
135
+ }