@intutic/sync-daemon 1.6.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 (147) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +73 -0
  3. package/dist/brainIndexer.d.ts +51 -0
  4. package/dist/brainIndexer.d.ts.map +1 -0
  5. package/dist/brainIndexer.js +148 -0
  6. package/dist/brainIndexer.js.map +1 -0
  7. package/dist/configReader.d.ts +46 -0
  8. package/dist/configReader.d.ts.map +1 -0
  9. package/dist/configReader.js +133 -0
  10. package/dist/configReader.js.map +1 -0
  11. package/dist/configWriter.d.ts +53 -0
  12. package/dist/configWriter.d.ts.map +1 -0
  13. package/dist/configWriter.js +503 -0
  14. package/dist/configWriter.js.map +1 -0
  15. package/dist/harness/aiderConfigMerger.d.ts +27 -0
  16. package/dist/harness/aiderConfigMerger.d.ts.map +1 -0
  17. package/dist/harness/aiderConfigMerger.js +139 -0
  18. package/dist/harness/aiderConfigMerger.js.map +1 -0
  19. package/dist/harness/antigravityHooks.d.ts +43 -0
  20. package/dist/harness/antigravityHooks.d.ts.map +1 -0
  21. package/dist/harness/antigravityHooks.js +193 -0
  22. package/dist/harness/antigravityHooks.js.map +1 -0
  23. package/dist/harness/claudeCodeHooks.d.ts +59 -0
  24. package/dist/harness/claudeCodeHooks.d.ts.map +1 -0
  25. package/dist/harness/claudeCodeHooks.js +414 -0
  26. package/dist/harness/claudeCodeHooks.js.map +1 -0
  27. package/dist/harness/claudeDesktopHooks.d.ts +30 -0
  28. package/dist/harness/claudeDesktopHooks.d.ts.map +1 -0
  29. package/dist/harness/claudeDesktopHooks.js +267 -0
  30. package/dist/harness/claudeDesktopHooks.js.map +1 -0
  31. package/dist/harness/clineHooks.d.ts +23 -0
  32. package/dist/harness/clineHooks.d.ts.map +1 -0
  33. package/dist/harness/clineHooks.js +169 -0
  34. package/dist/harness/clineHooks.js.map +1 -0
  35. package/dist/harness/continueHooks.d.ts +33 -0
  36. package/dist/harness/continueHooks.d.ts.map +1 -0
  37. package/dist/harness/continueHooks.js +124 -0
  38. package/dist/harness/continueHooks.js.map +1 -0
  39. package/dist/harness/cursorHooks.d.ts +27 -0
  40. package/dist/harness/cursorHooks.d.ts.map +1 -0
  41. package/dist/harness/cursorHooks.js +221 -0
  42. package/dist/harness/cursorHooks.js.map +1 -0
  43. package/dist/harness/cursorHooksJson.d.ts +29 -0
  44. package/dist/harness/cursorHooksJson.d.ts.map +1 -0
  45. package/dist/harness/cursorHooksJson.js +130 -0
  46. package/dist/harness/cursorHooksJson.js.map +1 -0
  47. package/dist/harness/gooseHardener.d.ts +39 -0
  48. package/dist/harness/gooseHardener.d.ts.map +1 -0
  49. package/dist/harness/gooseHardener.js +152 -0
  50. package/dist/harness/gooseHardener.js.map +1 -0
  51. package/dist/harness/gooseHooks.d.ts +24 -0
  52. package/dist/harness/gooseHooks.d.ts.map +1 -0
  53. package/dist/harness/gooseHooks.js +175 -0
  54. package/dist/harness/gooseHooks.js.map +1 -0
  55. package/dist/harness/gooseHooksWriter.d.ts +27 -0
  56. package/dist/harness/gooseHooksWriter.d.ts.map +1 -0
  57. package/dist/harness/gooseHooksWriter.js +147 -0
  58. package/dist/harness/gooseHooksWriter.js.map +1 -0
  59. package/dist/harness/hermesHooks.d.ts +31 -0
  60. package/dist/harness/hermesHooks.d.ts.map +1 -0
  61. package/dist/harness/hermesHooks.js +245 -0
  62. package/dist/harness/hermesHooks.js.map +1 -0
  63. package/dist/harness/mcpAutoWrite.d.ts +35 -0
  64. package/dist/harness/mcpAutoWrite.d.ts.map +1 -0
  65. package/dist/harness/mcpAutoWrite.js +334 -0
  66. package/dist/harness/mcpAutoWrite.js.map +1 -0
  67. package/dist/harness/n8nHooks.d.ts +28 -0
  68. package/dist/harness/n8nHooks.d.ts.map +1 -0
  69. package/dist/harness/n8nHooks.js +205 -0
  70. package/dist/harness/n8nHooks.js.map +1 -0
  71. package/dist/harness/openWebuiHooks.d.ts +31 -0
  72. package/dist/harness/openWebuiHooks.d.ts.map +1 -0
  73. package/dist/harness/openWebuiHooks.js +166 -0
  74. package/dist/harness/openWebuiHooks.js.map +1 -0
  75. package/dist/harness/openclawHooks.d.ts +34 -0
  76. package/dist/harness/openclawHooks.d.ts.map +1 -0
  77. package/dist/harness/openclawHooks.js +296 -0
  78. package/dist/harness/openclawHooks.js.map +1 -0
  79. package/dist/harness/openhandsHooks.d.ts +24 -0
  80. package/dist/harness/openhandsHooks.d.ts.map +1 -0
  81. package/dist/harness/openhandsHooks.js +178 -0
  82. package/dist/harness/openhandsHooks.js.map +1 -0
  83. package/dist/harness/openhandsHooksWriter.d.ts +23 -0
  84. package/dist/harness/openhandsHooksWriter.d.ts.map +1 -0
  85. package/dist/harness/openhandsHooksWriter.js +146 -0
  86. package/dist/harness/openhandsHooksWriter.js.map +1 -0
  87. package/dist/harness/piHooks.d.ts +32 -0
  88. package/dist/harness/piHooks.d.ts.map +1 -0
  89. package/dist/harness/piHooks.js +257 -0
  90. package/dist/harness/piHooks.js.map +1 -0
  91. package/dist/harness/rooCodeHooks.d.ts +31 -0
  92. package/dist/harness/rooCodeHooks.d.ts.map +1 -0
  93. package/dist/harness/rooCodeHooks.js +300 -0
  94. package/dist/harness/rooCodeHooks.js.map +1 -0
  95. package/dist/harness/windsurfHooks.d.ts +27 -0
  96. package/dist/harness/windsurfHooks.d.ts.map +1 -0
  97. package/dist/harness/windsurfHooks.js +202 -0
  98. package/dist/harness/windsurfHooks.js.map +1 -0
  99. package/dist/hashReporter.d.ts +33 -0
  100. package/dist/hashReporter.d.ts.map +1 -0
  101. package/dist/hashReporter.js +69 -0
  102. package/dist/hashReporter.js.map +1 -0
  103. package/dist/index.d.ts +43 -0
  104. package/dist/index.d.ts.map +1 -0
  105. package/dist/index.js +43 -0
  106. package/dist/index.js.map +1 -0
  107. package/dist/integrityStore.d.ts +43 -0
  108. package/dist/integrityStore.d.ts.map +1 -0
  109. package/dist/integrityStore.js +94 -0
  110. package/dist/integrityStore.js.map +1 -0
  111. package/dist/lib/complianceProbes.d.ts +39 -0
  112. package/dist/lib/complianceProbes.d.ts.map +1 -0
  113. package/dist/lib/complianceProbes.js +115 -0
  114. package/dist/lib/complianceProbes.js.map +1 -0
  115. package/dist/lib/processPoller.d.ts +2 -0
  116. package/dist/lib/processPoller.d.ts.map +1 -0
  117. package/dist/lib/processPoller.js +87 -0
  118. package/dist/lib/processPoller.js.map +1 -0
  119. package/dist/lib/runtimeEnv.d.ts +49 -0
  120. package/dist/lib/runtimeEnv.d.ts.map +1 -0
  121. package/dist/lib/runtimeEnv.js +70 -0
  122. package/dist/lib/runtimeEnv.js.map +1 -0
  123. package/dist/skillWriter.d.ts +24 -0
  124. package/dist/skillWriter.d.ts.map +1 -0
  125. package/dist/skillWriter.js +112 -0
  126. package/dist/skillWriter.js.map +1 -0
  127. package/dist/syncLoop.d.ts +70 -0
  128. package/dist/syncLoop.d.ts.map +1 -0
  129. package/dist/syncLoop.js +546 -0
  130. package/dist/syncLoop.js.map +1 -0
  131. package/dist/trajectoryMonitor.d.ts +126 -0
  132. package/dist/trajectoryMonitor.d.ts.map +1 -0
  133. package/dist/trajectoryMonitor.js +291 -0
  134. package/dist/trajectoryMonitor.js.map +1 -0
  135. package/dist/watcher/driftWatcher.d.ts +25 -0
  136. package/dist/watcher/driftWatcher.d.ts.map +1 -0
  137. package/dist/watcher/driftWatcher.js +72 -0
  138. package/dist/watcher/driftWatcher.js.map +1 -0
  139. package/dist/watcher/settingsGuard.d.ts +32 -0
  140. package/dist/watcher/settingsGuard.d.ts.map +1 -0
  141. package/dist/watcher/settingsGuard.js +250 -0
  142. package/dist/watcher/settingsGuard.js.map +1 -0
  143. package/dist/wsClient.d.ts +49 -0
  144. package/dist/wsClient.d.ts.map +1 -0
  145. package/dist/wsClient.js +149 -0
  146. package/dist/wsClient.js.map +1 -0
  147. package/package.json +96 -0
@@ -0,0 +1,126 @@
1
+ /**
2
+ * Trajectory Monitor — Daemon-side session watcher (Worker Thread).
3
+ *
4
+ * Runs inside the sync-daemon as a Node.js Worker thread. Subscribes to
5
+ * Valkey pub/sub `trace:live:{sessionId}` channels, buffers events in a
6
+ * sliding window, and submits trajectory summaries to the control plane
7
+ * for analysis.
8
+ *
9
+ * Key guarantees:
10
+ * - Non-blocking: Worker thread isolation, fire-and-forget analysis
11
+ * - No LLM calls on developer machine (all judgment is server-side)
12
+ * - Cooldown rate limiter: 1 analysis request per minute per session
13
+ *
14
+ * LLD #52 §4.1 — Daemon-side trajectory buffer
15
+ *
16
+ * @module
17
+ */
18
+ /** Individual trace event from the proxy pub/sub stream. */
19
+ export interface TraceEvent {
20
+ sessionId: string;
21
+ workspaceId: string;
22
+ toolName: string;
23
+ model: string;
24
+ inputTokens: number;
25
+ outputTokens: number;
26
+ reasoningTokens?: number;
27
+ status: 'success' | 'error';
28
+ timestamp: string;
29
+ }
30
+ /** Configuration for the trajectory monitor. */
31
+ export interface TrajectoryMonitorConfig {
32
+ /** Valkey connection URL. */
33
+ valkeyUrl: string;
34
+ /** Control plane base URL for submitting summaries. */
35
+ controlPlaneUrl: string;
36
+ /** API key for control plane authentication. */
37
+ apiKey: string;
38
+ /** Sliding window size in milliseconds. Default: 300_000 (5 minutes). */
39
+ windowMs?: number;
40
+ /** Maximum events per buffer. Default: 500. */
41
+ maxBufferSize?: number;
42
+ /** Summary submission interval in ms. Default: 60_000 (1 minute). */
43
+ submitIntervalMs?: number;
44
+ /** Session IDs to monitor. Empty = monitor all. */
45
+ sessionFilter?: string[];
46
+ }
47
+ /**
48
+ * Per-session sliding window buffer for trace events.
49
+ */
50
+ export declare class TrajectoryBuffer {
51
+ readonly sessionId: string;
52
+ readonly workspaceId: string;
53
+ private events;
54
+ private readonly maxSize;
55
+ private readonly windowMs;
56
+ private lastSubmitTime;
57
+ constructor(sessionId: string, workspaceId: string, maxSize?: number, windowMs?: number);
58
+ /** Add an event to the buffer. Trims old events and enforces size cap. */
59
+ push(event: TraceEvent): void;
60
+ /** Check if cooldown has elapsed since last submission. */
61
+ canSubmit(cooldownMs: number): boolean;
62
+ /** Mark that a submission was made. */
63
+ markSubmitted(): void;
64
+ /** Get the current buffer size. */
65
+ get size(): number;
66
+ /**
67
+ * Compute a trajectory summary from the current buffer.
68
+ */
69
+ toSummary(activeSopIds: string[], budgetUtilization: number): TrajectorySummaryPayload;
70
+ }
71
+ /** Payload sent to the control plane for analysis. */
72
+ export interface TrajectorySummaryPayload {
73
+ sessionId: string;
74
+ workspaceId: string;
75
+ windowStartedAt: string;
76
+ windowEndedAt: string;
77
+ toolCallCount: number;
78
+ uniqueTools: string[];
79
+ totalTokens: number;
80
+ modelSwitches: number;
81
+ errorCount: number;
82
+ tokenVelocity: number;
83
+ toolCallVelocity: number;
84
+ maxConsecutiveIdenticalCalls: number;
85
+ activeSopIds: string[];
86
+ budgetUtilization: number;
87
+ }
88
+ /**
89
+ * Trajectory Monitor controller.
90
+ *
91
+ * Manages per-session buffers, subscribes to trace events via Valkey pub/sub,
92
+ * and periodically submits summaries to the control plane for analysis.
93
+ */
94
+ export declare class TrajectoryMonitor {
95
+ private readonly config;
96
+ private buffers;
97
+ private submitTimer;
98
+ private running;
99
+ constructor(config: TrajectoryMonitorConfig);
100
+ /**
101
+ * Start the monitor — subscribe to Valkey and begin periodic submission.
102
+ */
103
+ start(): Promise<void>;
104
+ /**
105
+ * Stop the monitor and clean up resources.
106
+ */
107
+ stop(): void;
108
+ /**
109
+ * Process an incoming trace event from the Valkey pub/sub stream.
110
+ * Called by the sync-daemon when a trace:live event is received.
111
+ */
112
+ handleTraceEvent(event: TraceEvent): void;
113
+ /**
114
+ * Submit summaries for all active session buffers.
115
+ */
116
+ private submitAllSummaries;
117
+ /**
118
+ * Submit a single session's trajectory summary to the control plane.
119
+ */
120
+ private submitSummary;
121
+ private saveOffline;
122
+ private drainOfflineTrajectories;
123
+ /** Get the number of active session buffers. */
124
+ get activeSessionCount(): number;
125
+ }
126
+ //# sourceMappingURL=trajectoryMonitor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"trajectoryMonitor.d.ts","sourceRoot":"","sources":["../src/trajectoryMonitor.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,291 @@
1
+ /**
2
+ * Trajectory Monitor — Daemon-side session watcher (Worker Thread).
3
+ *
4
+ * Runs inside the sync-daemon as a Node.js Worker thread. Subscribes to
5
+ * Valkey pub/sub `trace:live:{sessionId}` channels, buffers events in a
6
+ * sliding window, and submits trajectory summaries to the control plane
7
+ * for analysis.
8
+ *
9
+ * Key guarantees:
10
+ * - Non-blocking: Worker thread isolation, fire-and-forget analysis
11
+ * - No LLM calls on developer machine (all judgment is server-side)
12
+ * - Cooldown rate limiter: 1 analysis request per minute per session
13
+ *
14
+ * LLD #52 §4.1 — Daemon-side trajectory buffer
15
+ *
16
+ * @module
17
+ */
18
+ import { createLogger } from '@intutic/logger';
19
+ import * as fs from 'node:fs';
20
+ import * as path from 'node:path';
21
+ import * as os from 'node:os';
22
+ const log = createLogger('trajectory-monitor');
23
+ // ─── Sliding Window Buffer ──────────────────────────────────────────
24
+ /**
25
+ * Per-session sliding window buffer for trace events.
26
+ */
27
+ export class TrajectoryBuffer {
28
+ sessionId;
29
+ workspaceId;
30
+ events = [];
31
+ maxSize;
32
+ windowMs;
33
+ lastSubmitTime = 0;
34
+ constructor(sessionId, workspaceId, maxSize = 500, windowMs = 300_000) {
35
+ this.sessionId = sessionId;
36
+ this.workspaceId = workspaceId;
37
+ this.maxSize = maxSize;
38
+ this.windowMs = windowMs;
39
+ }
40
+ /** Add an event to the buffer. Trims old events and enforces size cap. */
41
+ push(event) {
42
+ this.events.push(event);
43
+ // Enforce size cap
44
+ if (this.events.length > this.maxSize) {
45
+ this.events = this.events.slice(-this.maxSize);
46
+ }
47
+ // Trim events outside the sliding window
48
+ const cutoff = Date.now() - this.windowMs;
49
+ this.events = this.events.filter((e) => new Date(e.timestamp).getTime() >= cutoff);
50
+ }
51
+ /** Check if cooldown has elapsed since last submission. */
52
+ canSubmit(cooldownMs) {
53
+ return Date.now() - this.lastSubmitTime >= cooldownMs;
54
+ }
55
+ /** Mark that a submission was made. */
56
+ markSubmitted() {
57
+ this.lastSubmitTime = Date.now();
58
+ }
59
+ /** Get the current buffer size. */
60
+ get size() {
61
+ return this.events.length;
62
+ }
63
+ /**
64
+ * Compute a trajectory summary from the current buffer.
65
+ */
66
+ toSummary(activeSopIds, budgetUtilization) {
67
+ const tools = this.events.map((e) => e.toolName);
68
+ const uniqueTools = [...new Set(tools)];
69
+ const totalTokens = this.events.reduce((sum, e) => sum + e.inputTokens + e.outputTokens + (e.reasoningTokens ?? 0), 0);
70
+ const errorCount = this.events.filter((e) => e.status === 'error').length;
71
+ // Calculate velocities
72
+ const windowStartMs = this.events.length > 0
73
+ ? new Date(this.events[0].timestamp).getTime()
74
+ : Date.now();
75
+ const windowEndMs = this.events.length > 0
76
+ ? new Date(this.events[this.events.length - 1].timestamp).getTime()
77
+ : Date.now();
78
+ const windowMinutes = Math.max(1, (windowEndMs - windowStartMs) / 60_000);
79
+ const tokenVelocity = totalTokens / windowMinutes;
80
+ const toolCallVelocity = this.events.length / windowMinutes;
81
+ // Detect consecutive identical calls
82
+ let maxConsecutive = 0;
83
+ let currentConsecutive = 1;
84
+ for (let i = 1; i < tools.length; i++) {
85
+ if (tools[i] === tools[i - 1]) {
86
+ currentConsecutive++;
87
+ maxConsecutive = Math.max(maxConsecutive, currentConsecutive);
88
+ }
89
+ else {
90
+ currentConsecutive = 1;
91
+ }
92
+ }
93
+ // Detect model switches
94
+ const models = this.events.map((e) => e.model);
95
+ let modelSwitches = 0;
96
+ for (let i = 1; i < models.length; i++) {
97
+ if (models[i] !== models[i - 1])
98
+ modelSwitches++;
99
+ }
100
+ return {
101
+ sessionId: this.sessionId,
102
+ workspaceId: this.workspaceId,
103
+ windowStartedAt: this.events[0]?.timestamp ?? new Date().toISOString(),
104
+ windowEndedAt: this.events[this.events.length - 1]?.timestamp ?? new Date().toISOString(),
105
+ toolCallCount: this.events.length,
106
+ uniqueTools,
107
+ totalTokens,
108
+ modelSwitches,
109
+ errorCount,
110
+ tokenVelocity,
111
+ toolCallVelocity,
112
+ maxConsecutiveIdenticalCalls: maxConsecutive,
113
+ activeSopIds,
114
+ budgetUtilization,
115
+ };
116
+ }
117
+ }
118
+ // ─── Monitor Controller ─────────────────────────────────────────────
119
+ /**
120
+ * Trajectory Monitor controller.
121
+ *
122
+ * Manages per-session buffers, subscribes to trace events via Valkey pub/sub,
123
+ * and periodically submits summaries to the control plane for analysis.
124
+ */
125
+ export class TrajectoryMonitor {
126
+ config;
127
+ buffers = new Map();
128
+ submitTimer = null;
129
+ running = false;
130
+ constructor(config) {
131
+ this.config = config;
132
+ }
133
+ /**
134
+ * Start the monitor — subscribe to Valkey and begin periodic submission.
135
+ */
136
+ async start() {
137
+ if (this.running) {
138
+ log.warn('trajectory_monitor already running');
139
+ return;
140
+ }
141
+ this.running = true;
142
+ const submitIntervalMs = this.config.submitIntervalMs ?? 60_000;
143
+ log.info({ submitIntervalMs, windowMs: this.config.windowMs ?? 300_000 }, 'Starting trajectory monitor');
144
+ // Start periodic summary submission
145
+ this.submitTimer = setInterval(() => {
146
+ void this.submitAllSummaries();
147
+ }, submitIntervalMs);
148
+ // Note: Valkey subscription is handled by the sync-daemon's existing
149
+ // Redis subscriber. Events are forwarded to this worker via postMessage.
150
+ // See LLD #52 §4.1 for the worker thread lifecycle.
151
+ }
152
+ /**
153
+ * Stop the monitor and clean up resources.
154
+ */
155
+ stop() {
156
+ if (this.submitTimer) {
157
+ clearInterval(this.submitTimer);
158
+ this.submitTimer = null;
159
+ }
160
+ this.buffers.clear();
161
+ this.running = false;
162
+ log.info('Trajectory monitor stopped');
163
+ }
164
+ /**
165
+ * Process an incoming trace event from the Valkey pub/sub stream.
166
+ * Called by the sync-daemon when a trace:live event is received.
167
+ */
168
+ handleTraceEvent(event) {
169
+ // Apply session filter if configured
170
+ if (this.config.sessionFilter?.length &&
171
+ !this.config.sessionFilter.includes(event.sessionId)) {
172
+ return;
173
+ }
174
+ let buffer = this.buffers.get(event.sessionId);
175
+ if (!buffer) {
176
+ buffer = new TrajectoryBuffer(event.sessionId, event.workspaceId, this.config.maxBufferSize ?? 500, this.config.windowMs ?? 300_000);
177
+ this.buffers.set(event.sessionId, buffer);
178
+ }
179
+ buffer.push(event);
180
+ // Immediate submission for critical signals
181
+ if (event.status === 'error' && buffer.size > 3) {
182
+ void this.submitSummary(buffer);
183
+ }
184
+ }
185
+ /**
186
+ * Submit summaries for all active session buffers.
187
+ */
188
+ async submitAllSummaries() {
189
+ const cooldownMs = this.config.submitIntervalMs ?? 60_000;
190
+ // Drain any cached offline trajectories first
191
+ await this.drainOfflineTrajectories();
192
+ for (const buffer of this.buffers.values()) {
193
+ if (buffer.size === 0 || !buffer.canSubmit(cooldownMs))
194
+ continue;
195
+ await this.submitSummary(buffer);
196
+ }
197
+ // Clean up empty/stale buffers
198
+ for (const [sessionId, buffer] of this.buffers.entries()) {
199
+ if (buffer.size === 0) {
200
+ this.buffers.delete(sessionId);
201
+ }
202
+ }
203
+ }
204
+ /**
205
+ * Submit a single session's trajectory summary to the control plane.
206
+ */
207
+ async submitSummary(buffer) {
208
+ const summary = buffer.toSummary([], 0); // SOPs and budget filled by CP
209
+ const offlineFile = path.join(os.homedir(), '.intutic', 'events', 'offline-trajectories.jsonl');
210
+ try {
211
+ const res = await fetch(`${this.config.controlPlaneUrl}/api/v1/trajectory/analyze`, {
212
+ method: 'POST',
213
+ headers: {
214
+ 'Content-Type': 'application/json',
215
+ Authorization: `Bearer ${this.config.apiKey}`,
216
+ },
217
+ body: JSON.stringify(summary),
218
+ signal: AbortSignal.timeout(10_000), // 10s timeout
219
+ });
220
+ buffer.markSubmitted();
221
+ if (!res.ok) {
222
+ log.warn({ sessionId: buffer.sessionId, status: res.status }, `Trajectory submission failed: ${res.status}, buffering offline`);
223
+ this.saveOffline(summary, offlineFile);
224
+ return;
225
+ }
226
+ const verdict = (await res.json());
227
+ log.debug({ sessionId: buffer.sessionId, state: verdict.state, verdict: verdict.verdict }, `Trajectory verdict: ${verdict.verdict}`);
228
+ }
229
+ catch (err) {
230
+ log.debug({ err, sessionId: buffer.sessionId }, 'Trajectory submission error (buffering offline)');
231
+ buffer.markSubmitted();
232
+ this.saveOffline(summary, offlineFile);
233
+ }
234
+ }
235
+ saveOffline(summary, filePath) {
236
+ try {
237
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
238
+ fs.appendFileSync(filePath, JSON.stringify(summary) + '\n', 'utf-8');
239
+ }
240
+ catch (err) {
241
+ log.error({ err }, 'Failed to write offline trajectory');
242
+ }
243
+ }
244
+ async drainOfflineTrajectories() {
245
+ const offlineFile = path.join(os.homedir(), '.intutic', 'events', 'offline-trajectories.jsonl');
246
+ if (!fs.existsSync(offlineFile))
247
+ return;
248
+ try {
249
+ const raw = fs.readFileSync(offlineFile, 'utf-8');
250
+ const lines = raw.split('\n').map(l => l.trim()).filter(Boolean);
251
+ if (lines.length === 0)
252
+ return;
253
+ log.info({ count: lines.length }, 'Attempting to drain offline trajectories');
254
+ const remaining = [];
255
+ for (const line of lines) {
256
+ try {
257
+ const summary = JSON.parse(line);
258
+ const res = await fetch(`${this.config.controlPlaneUrl}/api/v1/trajectory/analyze`, {
259
+ method: 'POST',
260
+ headers: {
261
+ 'Content-Type': 'application/json',
262
+ Authorization: `Bearer ${this.config.apiKey}`,
263
+ },
264
+ body: JSON.stringify(summary),
265
+ signal: AbortSignal.timeout(5000),
266
+ });
267
+ if (!res.ok) {
268
+ remaining.push(line);
269
+ }
270
+ }
271
+ catch {
272
+ remaining.push(line);
273
+ }
274
+ }
275
+ if (remaining.length > 0) {
276
+ fs.writeFileSync(offlineFile, remaining.join('\n') + '\n', 'utf-8');
277
+ }
278
+ else {
279
+ fs.unlinkSync(offlineFile);
280
+ }
281
+ }
282
+ catch (err) {
283
+ log.error({ err }, 'Failed to drain offline trajectories');
284
+ }
285
+ }
286
+ /** Get the number of active session buffers. */
287
+ get activeSessionCount() {
288
+ return this.buffers.size;
289
+ }
290
+ }
291
+ //# sourceMappingURL=trajectoryMonitor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"trajectoryMonitor.js","sourceRoot":"","sources":["../src/trajectoryMonitor.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * driftWatcher.ts — Filesystem watcher for detecting config file drift.
3
+ *
4
+ * Uses chokidar to monitor active harness configuration files in real time.
5
+ * When changes are detected, it notifies the daemon via a callback to trigger
6
+ * validation against the integrity baseline.
7
+ *
8
+ * LLD #14 — driftWatcher.ts
9
+ * HLD §3.14 — Real-Time State Mirroring (Filesystem integrity checks)
10
+ *
11
+ * @module
12
+ */
13
+ import type { HarnessType } from '@intutic/shared-types';
14
+ /**
15
+ * Initializes file watches on harness configurations.
16
+ *
17
+ * @param workspaceRoot - Absolute path to the workspace root.
18
+ * @param harnesses - Harness types to watch.
19
+ * @param onChange - Callback triggered when a watched file is modified or deleted.
20
+ * @returns An object with a `stop` function to close the watcher.
21
+ */
22
+ export declare function startWatcher(workspaceRoot: string, harnesses: HarnessType[], onChange: (filePath: string) => Promise<void>): {
23
+ stop: () => void;
24
+ };
25
+ //# sourceMappingURL=driftWatcher.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"driftWatcher.d.ts","sourceRoot":"","sources":["../../src/watcher/driftWatcher.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAMH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAA;AAMxD;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAC1B,aAAa,EAAE,MAAM,EACrB,SAAS,EAAE,WAAW,EAAE,EACxB,QAAQ,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,GAC5C;IAAE,IAAI,EAAE,MAAM,IAAI,CAAA;CAAE,CAmDtB"}
@@ -0,0 +1,72 @@
1
+ /**
2
+ * driftWatcher.ts — Filesystem watcher for detecting config file drift.
3
+ *
4
+ * Uses chokidar to monitor active harness configuration files in real time.
5
+ * When changes are detected, it notifies the daemon via a callback to trigger
6
+ * validation against the integrity baseline.
7
+ *
8
+ * LLD #14 — driftWatcher.ts
9
+ * HLD §3.14 — Real-Time State Mirroring (Filesystem integrity checks)
10
+ *
11
+ * @module
12
+ */
13
+ import chokidar from 'chokidar';
14
+ import * as node_path from 'node:path';
15
+ import { HARNESS_FILES } from '../configWriter.js';
16
+ import { createLogger } from '@intutic/logger';
17
+ import { buildProtectedPaths } from './settingsGuard.js';
18
+ const log = createLogger('sync-drift-watcher');
19
+ /**
20
+ * Initializes file watches on harness configurations.
21
+ *
22
+ * @param workspaceRoot - Absolute path to the workspace root.
23
+ * @param harnesses - Harness types to watch.
24
+ * @param onChange - Callback triggered when a watched file is modified or deleted.
25
+ * @returns An object with a `stop` function to close the watcher.
26
+ */
27
+ export function startWatcher(workspaceRoot, harnesses, onChange) {
28
+ const filePaths = harnesses
29
+ .map((h) => HARNESS_FILES[h])
30
+ .filter((f) => !!f)
31
+ .map((f) => node_path.join(workspaceRoot, f));
32
+ // Also watch git-context.json for real-time Git metadata updates
33
+ filePaths.push(node_path.join(workspaceRoot, '.intutic', 'git-context.json'));
34
+ // Also watch session-context.json for real-time active sops/judging updates
35
+ filePaths.push(node_path.join(workspaceRoot, '.intutic', 'session-context.json'));
36
+ // Watch .intutic/sops directory itself (and child directories/files) for real-time rules updates
37
+ filePaths.push(node_path.join(workspaceRoot, '.intutic', 'sops'));
38
+ // Watch ALL governance config paths across all 18 supported harnesses.
39
+ // Any change (edit or delete) triggers the settingsGuard pipeline which
40
+ // validates and restores the file within one poll cycle.
41
+ const protectedPaths = buildProtectedPaths(workspaceRoot);
42
+ for (const p of protectedPaths) {
43
+ if (!filePaths.includes(p))
44
+ filePaths.push(p);
45
+ }
46
+ log.info({ action: 'start_watcher', fileCount: filePaths.length }, `Starting filesystem watcher for ${filePaths.length} governed paths across all harnesses`);
47
+ const watcher = chokidar.watch(filePaths, {
48
+ persistent: true,
49
+ ignoreInitial: true, // Only care about modifications post-start
50
+ awaitWriteFinish: {
51
+ stabilityThreshold: 200,
52
+ pollInterval: 100,
53
+ },
54
+ });
55
+ watcher.on('all', (event, path) => {
56
+ if (event === 'change' || event === 'unlink') {
57
+ log.debug({ action: 'watcher_change_detected', event, path }, 'Governed file change detected');
58
+ onChange(path).catch((err) => {
59
+ log.error({ action: 'watcher_on_change_error', err, path }, 'Error in watcher onChange handler');
60
+ });
61
+ }
62
+ });
63
+ return {
64
+ stop: () => {
65
+ log.info({ action: 'stop_watcher' }, 'Stopping filesystem watcher');
66
+ watcher.close().catch((err) => {
67
+ log.warn({ action: 'watcher_close_error', err }, 'Error closing filesystem watcher');
68
+ });
69
+ },
70
+ };
71
+ }
72
+ //# sourceMappingURL=driftWatcher.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"driftWatcher.js","sourceRoot":"","sources":["../../src/watcher/driftWatcher.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,32 @@
1
+ /**
2
+ * settingsGuard.ts — Multi-harness governance config tamper detection & restoration.
3
+ *
4
+ * Expanded from Claude Code-only to cover all 18 Intutic-supported harnesses.
5
+ * Called by the drift watcher whenever any protected governance config file changes.
6
+ *
7
+ * Behaviour per path type:
8
+ * - JSON hook files (Cursor, Cline, OpenHands, Goose): validate marker presence, restore.
9
+ * - Immutable files (Goose plugin): log governance_override_attempt incident instead of restoring.
10
+ * - Text rules (.roorules, .clinerules): restore from last-written hash.
11
+ * - Settings files (VS Code, Aider, claude_desktop_config.json): detect drift, restore.
12
+ *
13
+ * LLD #14 — settingsGuard.ts
14
+ * HLD §3.14 — Three-Tier Defense Cascade (Tier 1 Native Gating)
15
+ *
16
+ * @module
17
+ */
18
+ import type { SyncSopEntry } from '@intutic/shared-types';
19
+ /** Returns the full list of protected paths to watch. */
20
+ export declare function buildProtectedPaths(workspaceRoot: string): string[];
21
+ /**
22
+ * Inspect any protected governance config file and restore if tampered.
23
+ *
24
+ * @param changedPath - Absolute path of the file that changed.
25
+ * @param workspaceRoot - Workspace root for restoration helpers.
26
+ * @param sops - Current SOP list.
27
+ * @param proxyUrl - Current proxy URL.
28
+ * @param settings - Optional passthrough settings.
29
+ * @returns true if tampering was detected.
30
+ */
31
+ export declare function guardSettingsFile(changedPath: string, workspaceRoot: string, sops: SyncSopEntry[], proxyUrl?: string, settings?: Record<string, unknown>): Promise<boolean>;
32
+ //# sourceMappingURL=settingsGuard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"settingsGuard.d.ts","sourceRoot":"","sources":["../../src/watcher/settingsGuard.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAOH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAA;AAkBzD,yDAAyD;AACzD,wBAAgB,mBAAmB,CAAC,aAAa,EAAE,MAAM,GAAG,MAAM,EAAE,CA+BnE;AAID;;;;;;;;;GASG;AACH,wBAAsB,iBAAiB,CACrC,WAAW,EAAE,MAAM,EACnB,aAAa,EAAE,MAAM,EACrB,IAAI,EAAE,YAAY,EAAE,EACpB,QAAQ,SAAK,EACb,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GACjC,OAAO,CAAC,OAAO,CAAC,CA2FlB"}