log-llm-config 1.2.8 → 1.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/dist/apply_deferred_vscdb.js +8 -0
- package/dist/cli/bash_script_generator.js +23 -11
- package/dist/compliance_check_runner.js +31 -0
- package/dist/compliance_prompt_gate.js +64 -8
- package/dist/log_config_files/readers/vscdb_config_builder.js +6 -2
- package/dist/log_config_files/readers/vscdb_reader.js +102 -1
- package/dist/log_config_files/runtime/compliance_check.js +147 -59
- package/dist/log_config_files/runtime/hardware_uuid.js +9 -0
- package/dist/log_config_files/runtime/hook_logger.js +38 -5
- package/dist/log_config_files/runtime/main_runner.js +2 -0
- package/dist/log_config_files/runtime/management_storage.js +31 -0
- package/dist/log_config_files/runtime/remediation_sync.js +709 -19
- package/package.json +7 -5
|
@@ -9,23 +9,32 @@
|
|
|
9
9
|
* Background: compliance_check_runner runs syncRemediations (network) then the same local check.
|
|
10
10
|
*/
|
|
11
11
|
import { existsSync, readFileSync } from 'node:fs';
|
|
12
|
+
import { readVscdbItemTableJson } from '../readers/vscdb_reader.js';
|
|
12
13
|
import { readRemediationInstructionsFile, writeRemediationInstructionsFile } from './management_storage.js';
|
|
13
|
-
import { hookRunLog } from './hook_logger.js';
|
|
14
|
+
import { complianceRunnerDiag, hookRunLog } from './hook_logger.js';
|
|
14
15
|
import { loadEndpointBase } from '../sender/endpoint_config.js';
|
|
15
|
-
import { resolveHardwareUuid } from './hardware_uuid.js';
|
|
16
|
-
import { enforceRemediation, fetchSync, remediationFixSpec, reportAutofixApplied, syncRemediations, } from './remediation_sync.js';
|
|
16
|
+
import { resolveHardwareUuid, tryResolveHardwareUuid } from './hardware_uuid.js';
|
|
17
|
+
import { buildDeferredCursorRestartCommand, enforceRemediation, fetchSync, isTrustedRestartCommandForAutofix, remediationFixSpec, reportAutofixApplied, syncRemediations, } from './remediation_sync.js';
|
|
17
18
|
import { sendConfigFile } from '../sender/batch_sender.js';
|
|
18
19
|
import { readStoredAuthKey } from '../auth/auth_key_store.js';
|
|
19
20
|
// ---------------------------------------------------------------------------
|
|
20
21
|
// Helpers
|
|
21
22
|
// ---------------------------------------------------------------------------
|
|
22
23
|
/** Traverse a JSON object using dot-notation path. Returns undefined if any segment is missing. */
|
|
23
|
-
function getByPath(obj, path) {
|
|
24
|
+
export function getByPath(obj, path) {
|
|
24
25
|
const parts = path.split('.');
|
|
25
26
|
let current = obj;
|
|
26
27
|
for (const part of parts) {
|
|
27
28
|
if (current == null || typeof current !== 'object')
|
|
28
29
|
return undefined;
|
|
30
|
+
// Cursor composerState.modes4: non-numeric segment indexes by mode id (agent row is not always [0]).
|
|
31
|
+
if (Array.isArray(current) && !/^\d+$/.test(part)) {
|
|
32
|
+
const idx = current.findIndex((item) => item !== null &&
|
|
33
|
+
typeof item === 'object' &&
|
|
34
|
+
item.id === part);
|
|
35
|
+
current = idx >= 0 ? current[idx] : undefined;
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
29
38
|
current = current[part];
|
|
30
39
|
}
|
|
31
40
|
return current;
|
|
@@ -76,6 +85,30 @@ function verifyOpsApplied(configJson, settingPath, ops) {
|
|
|
76
85
|
}
|
|
77
86
|
return { ok: true, expected: null };
|
|
78
87
|
}
|
|
88
|
+
/** Plain JSON file or virtual `…/state.vscdb#composerState` path for ItemTable-backed settings. */
|
|
89
|
+
function loadRemediationConfigJson(configFilePath) {
|
|
90
|
+
const hashIdx = configFilePath.indexOf('#');
|
|
91
|
+
if (hashIdx >= 0) {
|
|
92
|
+
const dbPath = configFilePath.slice(0, hashIdx);
|
|
93
|
+
const itemKey = configFilePath.slice(hashIdx + 1).trim();
|
|
94
|
+
if (!itemKey)
|
|
95
|
+
return { ok: false, reason: 'empty_vscdb_key' };
|
|
96
|
+
if (!existsSync(dbPath))
|
|
97
|
+
return { ok: false, reason: 'db_not_found' };
|
|
98
|
+
const wrapped = readVscdbItemTableJson(dbPath, itemKey);
|
|
99
|
+
if (wrapped === null)
|
|
100
|
+
return { ok: false, reason: 'vscdb_read_failed' };
|
|
101
|
+
return { ok: true, json: wrapped };
|
|
102
|
+
}
|
|
103
|
+
if (!existsSync(configFilePath))
|
|
104
|
+
return { ok: false, reason: 'file_not_found' };
|
|
105
|
+
try {
|
|
106
|
+
return { ok: true, json: JSON.parse(readFileSync(configFilePath, 'utf8')) };
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
return { ok: false, reason: 'parse_error' };
|
|
110
|
+
}
|
|
111
|
+
}
|
|
79
112
|
// ---------------------------------------------------------------------------
|
|
80
113
|
// Check runner — Section 6: real per-check evaluation
|
|
81
114
|
// ---------------------------------------------------------------------------
|
|
@@ -104,18 +137,21 @@ export function runLocalRemediationComplianceCheck() {
|
|
|
104
137
|
const checks = compliance.checks ?? [];
|
|
105
138
|
if (checks.length === 0)
|
|
106
139
|
continue;
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
140
|
+
const loaded = loadRemediationConfigJson(entry.config_file_path);
|
|
141
|
+
if (!loaded.ok) {
|
|
142
|
+
const msg = loaded.reason === 'file_not_found'
|
|
143
|
+
? `compliance_check: config file not found, skipping uuid=${entry.uuid}`
|
|
144
|
+
: loaded.reason === 'db_not_found'
|
|
145
|
+
? `compliance_check: vscdb file not found, skipping uuid=${entry.uuid}`
|
|
146
|
+
: loaded.reason === 'vscdb_read_failed'
|
|
147
|
+
? `compliance_check: could not read vscdb (sqlite3 missing or invalid JSON?), skipping uuid=${entry.uuid}`
|
|
148
|
+
: loaded.reason === 'empty_vscdb_key'
|
|
149
|
+
? `compliance_check: invalid vscdb path (empty # key), skipping uuid=${entry.uuid}`
|
|
150
|
+
: `compliance_check: could not parse config file, skipping uuid=${entry.uuid}`;
|
|
151
|
+
hookRunLog(msg);
|
|
117
152
|
continue;
|
|
118
153
|
}
|
|
154
|
+
const configJson = loaded.json;
|
|
119
155
|
for (const check of checks) {
|
|
120
156
|
// Prefer ops-based verification (matches delta apply semantics; doesn't require full after snapshot).
|
|
121
157
|
if (check.ops) {
|
|
@@ -127,6 +163,8 @@ export function runLocalRemediationComplianceCheck() {
|
|
|
127
163
|
finding_formatted_id: compliance.finding_formatted_id,
|
|
128
164
|
setting_path: check.setting_path,
|
|
129
165
|
description: check.description,
|
|
166
|
+
finding_title: entry.finding_title,
|
|
167
|
+
finding_description: entry.finding_description,
|
|
130
168
|
severity: compliance.severity,
|
|
131
169
|
autofix_allowed: compliance.autofix_allowed,
|
|
132
170
|
config_file_path: entry.config_file_path,
|
|
@@ -149,6 +187,8 @@ export function runLocalRemediationComplianceCheck() {
|
|
|
149
187
|
finding_formatted_id: compliance.finding_formatted_id,
|
|
150
188
|
setting_path: check.setting_path,
|
|
151
189
|
description: check.description,
|
|
190
|
+
finding_title: entry.finding_title,
|
|
191
|
+
finding_description: entry.finding_description,
|
|
152
192
|
severity: compliance.severity,
|
|
153
193
|
autofix_allowed: compliance.autofix_allowed,
|
|
154
194
|
config_file_path: entry.config_file_path,
|
|
@@ -181,15 +221,23 @@ export function runLocalRemediationComplianceCheck() {
|
|
|
181
221
|
export function applyAutofixViolations(violations) {
|
|
182
222
|
const autofixable = violations.filter((v) => v.autofix_allowed);
|
|
183
223
|
if (autofixable.length === 0)
|
|
184
|
-
return {
|
|
224
|
+
return {
|
|
225
|
+
fixed: 0,
|
|
226
|
+
appliedViolations: [],
|
|
227
|
+
restartCommands: [],
|
|
228
|
+
failedViolations: [],
|
|
229
|
+
reportPromises: [],
|
|
230
|
+
};
|
|
185
231
|
const { remediations } = readRemediationInstructionsFile();
|
|
186
232
|
const byUuid = new Map(remediations.map((r) => [r.uuid, r]));
|
|
187
233
|
let fixed = 0;
|
|
234
|
+
const appliedViolations = [];
|
|
188
235
|
const seen = new Set();
|
|
189
236
|
const restartCommands = [];
|
|
190
237
|
const failedViolations = [];
|
|
191
238
|
const reportPromises = [];
|
|
192
239
|
const oneTimeAppliedUuids = new Set();
|
|
240
|
+
let deferredSqlitePending = false;
|
|
193
241
|
for (const violation of autofixable) {
|
|
194
242
|
if (seen.has(violation.uuid))
|
|
195
243
|
continue;
|
|
@@ -200,48 +248,83 @@ export function applyAutofixViolations(violations) {
|
|
|
200
248
|
continue;
|
|
201
249
|
}
|
|
202
250
|
const inst = instruction;
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
251
|
+
complianceRunnerDiag(`autofix: calling enforceRemediation uuid=${inst.uuid} path=${inst.config_file_path}`);
|
|
252
|
+
const er = enforceRemediation(inst);
|
|
253
|
+
if (!er.ok) {
|
|
254
|
+
failedViolations.push(violation);
|
|
255
|
+
continue;
|
|
256
|
+
}
|
|
257
|
+
if (er.deferredSqlite)
|
|
258
|
+
deferredSqlitePending = true;
|
|
259
|
+
seen.add(violation.uuid);
|
|
260
|
+
fixed++;
|
|
261
|
+
appliedViolations.push(violation);
|
|
262
|
+
hookRunLog(`autofix: applied uuid=${inst.uuid} path=${inst.config_file_path}`);
|
|
263
|
+
reportPromises.push(reportAutofixApplied(inst.uuid, 'success'));
|
|
264
|
+
const authKey = readStoredAuthKey();
|
|
265
|
+
if (authKey) {
|
|
266
|
+
if (er.deferredSqlite && inst.config_file_path.includes('#')) {
|
|
267
|
+
hookRunLog(`autofix: skip immediate vscdb upload (deferred until after restart) uuid=${inst.uuid}`);
|
|
268
|
+
}
|
|
269
|
+
else {
|
|
211
270
|
let updatedContent;
|
|
212
|
-
|
|
213
|
-
|
|
271
|
+
if (inst.config_file_path.includes('#')) {
|
|
272
|
+
const hi = inst.config_file_path.indexOf('#');
|
|
273
|
+
const dbPath = inst.config_file_path.slice(0, hi);
|
|
274
|
+
const itemKey = inst.config_file_path.slice(hi + 1).trim();
|
|
275
|
+
updatedContent =
|
|
276
|
+
itemKey ? (readVscdbItemTableJson(dbPath, itemKey) ?? undefined) : undefined;
|
|
214
277
|
}
|
|
215
|
-
|
|
216
|
-
|
|
278
|
+
else {
|
|
279
|
+
try {
|
|
280
|
+
updatedContent = JSON.parse(readFileSync(inst.config_file_path, 'utf8'));
|
|
281
|
+
}
|
|
282
|
+
catch {
|
|
283
|
+
updatedContent = undefined;
|
|
284
|
+
}
|
|
217
285
|
}
|
|
218
286
|
if (updatedContent !== undefined) {
|
|
219
287
|
const fileType = (inst.file_type ?? '').trim();
|
|
220
288
|
if (fileType) {
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
289
|
+
const hw = tryResolveHardwareUuid();
|
|
290
|
+
if (hw) {
|
|
291
|
+
reportPromises.push(sendConfigFile({ file_type: fileType, file_path: inst.config_file_path, raw_content: updatedContent }, hw, authKey).then((sentOk) => {
|
|
292
|
+
hookRunLog(`autofix: uploaded remediated file uuid=${inst.uuid} path=${inst.config_file_path} ok=${sentOk}`);
|
|
293
|
+
}));
|
|
294
|
+
}
|
|
295
|
+
else {
|
|
296
|
+
hookRunLog(`autofix: skip upload uuid=${inst.uuid} (hardware UUID unavailable)`);
|
|
297
|
+
}
|
|
224
298
|
}
|
|
225
299
|
else {
|
|
226
300
|
hookRunLog(`autofix: skip upload uuid=${inst.uuid} — remediation_instructions.json missing file_type (re-sync manifest)`);
|
|
227
301
|
}
|
|
228
302
|
}
|
|
229
303
|
}
|
|
230
|
-
else {
|
|
231
|
-
hookRunLog(`autofix: skip re-upload uuid=${inst.uuid} (no stored auth key)`);
|
|
232
|
-
}
|
|
233
|
-
const spec = remediationFixSpec(inst);
|
|
234
|
-
if (spec?.restart_required && spec.restart_command) {
|
|
235
|
-
hookRunLog(`autofix: restart required uuid=${inst.uuid} command=${spec.restart_command}`);
|
|
236
|
-
restartCommands.push(spec.restart_command);
|
|
237
|
-
}
|
|
238
|
-
if (!inst.is_enforced) {
|
|
239
|
-
oneTimeAppliedUuids.add(inst.uuid);
|
|
240
|
-
}
|
|
241
304
|
}
|
|
242
305
|
else {
|
|
243
|
-
|
|
306
|
+
hookRunLog(`autofix: skip re-upload uuid=${inst.uuid} (no stored auth key)`);
|
|
244
307
|
}
|
|
308
|
+
const spec = remediationFixSpec(inst);
|
|
309
|
+
if (spec?.restart_required && spec.restart_command) {
|
|
310
|
+
if (!er.deferredSqlite) {
|
|
311
|
+
if (isTrustedRestartCommandForAutofix(spec.restart_command)) {
|
|
312
|
+
hookRunLog(`autofix: restart required uuid=${inst.uuid} command=${spec.restart_command}`);
|
|
313
|
+
restartCommands.push(spec.restart_command);
|
|
314
|
+
}
|
|
315
|
+
else {
|
|
316
|
+
hookRunLog(`autofix: restart command rejected (not an allowlisted template) uuid=${inst.uuid}`);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
if (!inst.is_enforced) {
|
|
321
|
+
oneTimeAppliedUuids.add(inst.uuid);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
if (deferredSqlitePending) {
|
|
325
|
+
restartCommands.length = 0;
|
|
326
|
+
restartCommands.push(buildDeferredCursorRestartCommand());
|
|
327
|
+
hookRunLog('autofix: deferred vscdb — restart command runs apply_deferred_vscdb.js then open -a Cursor');
|
|
245
328
|
}
|
|
246
329
|
if (oneTimeAppliedUuids.size > 0) {
|
|
247
330
|
const remaining = remediations.filter((r) => !oneTimeAppliedUuids.has(r.uuid));
|
|
@@ -250,14 +333,20 @@ export function applyAutofixViolations(violations) {
|
|
|
250
333
|
// Send a post-autofix heartbeat so the server sees the updated (reduced) UUID set immediately,
|
|
251
334
|
// without waiting for the background runner (which may be locked out).
|
|
252
335
|
const remainingUuids = remaining.map((r) => r.uuid);
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
.
|
|
336
|
+
const hwHeartbeat = tryResolveHardwareUuid();
|
|
337
|
+
if (hwHeartbeat) {
|
|
338
|
+
reportPromises.push(fetchSync(loadEndpointBase(), hwHeartbeat, remainingUuids)
|
|
339
|
+
.then(() => hookRunLog(`autofix: post-autofix heartbeat sent uuids=${remainingUuids.length}`))
|
|
340
|
+
.catch(() => undefined));
|
|
341
|
+
}
|
|
342
|
+
else {
|
|
343
|
+
hookRunLog('autofix: skip post-autofix heartbeat (hardware UUID unavailable)');
|
|
344
|
+
}
|
|
256
345
|
}
|
|
257
346
|
if (fixed > 0) {
|
|
258
347
|
hookRunLog(`autofix: total_applied=${fixed}`);
|
|
259
348
|
}
|
|
260
|
-
return { fixed, restartCommands, failedViolations, reportPromises };
|
|
349
|
+
return { fixed, appliedViolations, restartCommands, failedViolations, reportPromises, deferredSqlitePending };
|
|
261
350
|
}
|
|
262
351
|
/**
|
|
263
352
|
* Remove satisfied one-time remediations from local remediation_instructions.json.
|
|
@@ -285,18 +374,12 @@ export function pruneSatisfiedOneTimeRemediations() {
|
|
|
285
374
|
remaining.push(raw);
|
|
286
375
|
continue;
|
|
287
376
|
}
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
continue;
|
|
291
|
-
}
|
|
292
|
-
let configJson;
|
|
293
|
-
try {
|
|
294
|
-
configJson = JSON.parse(readFileSync(inst.config_file_path, 'utf8'));
|
|
295
|
-
}
|
|
296
|
-
catch {
|
|
377
|
+
const prLoaded = loadRemediationConfigJson(inst.config_file_path);
|
|
378
|
+
if (!prLoaded.ok) {
|
|
297
379
|
remaining.push(raw);
|
|
298
380
|
continue;
|
|
299
381
|
}
|
|
382
|
+
const configJson = prLoaded.json;
|
|
300
383
|
// Only prune when every check is ops-based and currently satisfied.
|
|
301
384
|
let okAll = true;
|
|
302
385
|
for (const check of checks) {
|
|
@@ -323,11 +406,16 @@ export function pruneSatisfiedOneTimeRemediations() {
|
|
|
323
406
|
writeRemediationInstructionsFile({ remediations: remaining });
|
|
324
407
|
hookRunLog(`remediation_prune: removed=${removed} remaining=${remaining.length}`);
|
|
325
408
|
const remainingUuids = remaining.map((r) => r.uuid);
|
|
326
|
-
const
|
|
327
|
-
|
|
409
|
+
const hw = tryResolveHardwareUuid();
|
|
410
|
+
const reportPromises = [];
|
|
411
|
+
if (hw) {
|
|
412
|
+
reportPromises.push(fetchSync(loadEndpointBase(), hw, remainingUuids)
|
|
328
413
|
.then(() => hookRunLog(`remediation_prune: post-prune heartbeat sent uuids=${remainingUuids.length}`))
|
|
329
|
-
.catch(() => undefined)
|
|
330
|
-
|
|
414
|
+
.catch(() => undefined));
|
|
415
|
+
}
|
|
416
|
+
else {
|
|
417
|
+
hookRunLog('remediation_prune: skip post-prune heartbeat (hardware UUID unavailable)');
|
|
418
|
+
}
|
|
331
419
|
return { removed, reportPromises };
|
|
332
420
|
}
|
|
333
421
|
/**
|
|
@@ -24,4 +24,13 @@ function resolveHardwareUuid() {
|
|
|
24
24
|
}
|
|
25
25
|
throw new Error('Unable to determine hardware UUID via ioreg or system_profiler.');
|
|
26
26
|
}
|
|
27
|
+
/** Same as {@link resolveHardwareUuid} but returns null when the host exposes no stable ID (e.g. Linux CI). */
|
|
28
|
+
export function tryResolveHardwareUuid() {
|
|
29
|
+
try {
|
|
30
|
+
return resolveHardwareUuid();
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
27
36
|
export { resolveHardwareUuid };
|
|
@@ -1,15 +1,40 @@
|
|
|
1
|
-
import { existsSync, mkdirSync,
|
|
1
|
+
import { existsSync, mkdirSync, appendFileSync } from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { OPT_AI_SEC_MANAGEMENT_REL } from '../../bootstrap_constants.js';
|
|
4
4
|
const HOOK_LOG_FILENAME = 'hook_log.txt';
|
|
5
|
+
const COMPLIANCE_RUNNER_LOG_FILENAME = 'compliance_runner.log';
|
|
5
6
|
function getHookLogPath() {
|
|
6
7
|
const homeDir = process.env.HOME || process.env.USERPROFILE;
|
|
7
8
|
if (!homeDir)
|
|
8
9
|
return null;
|
|
9
10
|
return path.join(homeDir, OPT_AI_SEC_MANAGEMENT_REL, HOOK_LOG_FILENAME);
|
|
10
11
|
}
|
|
11
|
-
|
|
12
|
-
|
|
12
|
+
function getComplianceRunnerLogPath() {
|
|
13
|
+
const homeDir = process.env.HOME || process.env.USERPROFILE;
|
|
14
|
+
if (!homeDir)
|
|
15
|
+
return null;
|
|
16
|
+
return path.join(homeDir, OPT_AI_SEC_MANAGEMENT_REL, COMPLIANCE_RUNNER_LOG_FILENAME);
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Append-only diagnostics (not truncated by main_runner). Use for remediation sync / compliance.
|
|
20
|
+
*/
|
|
21
|
+
function complianceRunnerDiag(message) {
|
|
22
|
+
const logPath = getComplianceRunnerLogPath();
|
|
23
|
+
if (!logPath)
|
|
24
|
+
return;
|
|
25
|
+
try {
|
|
26
|
+
const dir = path.dirname(logPath);
|
|
27
|
+
if (!existsSync(dir))
|
|
28
|
+
mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
29
|
+
const ts = new Date().toISOString();
|
|
30
|
+
appendFileSync(logPath, `${ts} ${message}\n`, 'utf8');
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
// best-effort
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/** Visible delimiter between hook_log.txt sessions (compliance gate, upload, etc.). */
|
|
37
|
+
function hookLogSessionBanner(label) {
|
|
13
38
|
const logPath = getHookLogPath();
|
|
14
39
|
if (!logPath)
|
|
15
40
|
return;
|
|
@@ -17,12 +42,20 @@ function hookLogReplace() {
|
|
|
17
42
|
const dir = path.dirname(logPath);
|
|
18
43
|
if (!existsSync(dir))
|
|
19
44
|
mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
20
|
-
|
|
45
|
+
const ts = new Date().toISOString();
|
|
46
|
+
const banner = `\n${'='.repeat(72)}\n${ts} session: ${label}\n${'='.repeat(72)}\n`;
|
|
47
|
+
appendFileSync(logPath, banner, 'utf8');
|
|
21
48
|
}
|
|
22
49
|
catch {
|
|
23
50
|
// best-effort
|
|
24
51
|
}
|
|
25
52
|
}
|
|
53
|
+
/**
|
|
54
|
+
* @deprecated Name kept for callers: begins a log_config_files upload section (append-only, does not truncate).
|
|
55
|
+
*/
|
|
56
|
+
function hookLogReplace() {
|
|
57
|
+
hookLogSessionBanner('log_config_files (config upload)');
|
|
58
|
+
}
|
|
26
59
|
/** Append a timestamped line to hook_log.txt. */
|
|
27
60
|
function hookRunLog(message) {
|
|
28
61
|
const logPath = getHookLogPath();
|
|
@@ -48,4 +81,4 @@ function hookLogLine(message) {
|
|
|
48
81
|
// best-effort
|
|
49
82
|
}
|
|
50
83
|
}
|
|
51
|
-
export { getHookLogPath, hookLogReplace, hookRunLog, hookLogLine };
|
|
84
|
+
export { getHookLogPath, getComplianceRunnerLogPath, hookLogReplace, hookLogSessionBanner, hookRunLog, hookLogLine, complianceRunnerDiag, };
|
|
@@ -10,6 +10,7 @@ import { resolveHardwareUuid } from './hardware_uuid.js';
|
|
|
10
10
|
import { ensureAuthentication } from '../auth/auth_flow.js';
|
|
11
11
|
import { readJSONFile, readMarkdownFile } from '../readers/file_readers.js';
|
|
12
12
|
import { isVscdbVirtualPath, tryReadVscdbVirtualFile } from '../readers/vscdb_config_builder.js';
|
|
13
|
+
import { persistVscdbComposerContractFromPatternsResponse } from '../readers/vscdb_reader.js';
|
|
13
14
|
import { collectConfigFilesFromPatterns, collectMcpToolFiles, collectConfigFilesFromInstalledPlugins, determineFileTypeFromPath } from '../collection/config_collector.js';
|
|
14
15
|
import { normalizePathSkipPrefixes } from '../paths/pattern_resolver.js';
|
|
15
16
|
import { sendConfigFile, sendConfigFilesBatch, sendHookRequestCreate, sendHookRequestUpdateManifest, sendIngestSessionStart, sendIngestSessionFinish, BATCH_CHUNK_SIZE } from '../sender/batch_sender.js';
|
|
@@ -26,6 +27,7 @@ async function collectAllConfigFiles(endpointBase) {
|
|
|
26
27
|
hookRunLog(msg);
|
|
27
28
|
throw new Error(msg);
|
|
28
29
|
}
|
|
30
|
+
persistVscdbComposerContractFromPatternsResponse(patternsResponse);
|
|
29
31
|
hookRunLog(`scanning config files`);
|
|
30
32
|
const configFiles = collectConfigFilesFromPatterns(patternsResponse.patterns, PROJECT_ROOT, hookRunLog, {
|
|
31
33
|
vscdbEntrySpecs: patternsResponse.vscdb_entry_specs,
|
|
@@ -7,12 +7,43 @@ import { dirname, join } from 'node:path';
|
|
|
7
7
|
import { homedir } from 'node:os';
|
|
8
8
|
import { OPT_AI_SEC_MANAGEMENT_REL } from '../../bootstrap_constants.js';
|
|
9
9
|
export const REMEDIATION_INSTRUCTIONS_BASENAME = 'remediation_instructions.json';
|
|
10
|
+
/** Pending state.vscdb writes applied after Cursor exits (IDE holds the DB locked). */
|
|
11
|
+
export const DEFERRED_VSCDB_APPLY_BASENAME = 'optimus_deferred_vscdb_apply.json';
|
|
12
|
+
/**
|
|
13
|
+
* Cached subset of GET file-patterns: reactive ItemTable key + composer shadow keys from the backend.
|
|
14
|
+
* Written when patterns are fetched; remediations and vscdb reads use this so the npm package stays free
|
|
15
|
+
* of hardcoded Cursor paths.
|
|
16
|
+
*/
|
|
17
|
+
export const FILE_COLLECTION_VSCDB_CONTRACT_BASENAME = 'file_collection_vscdb_contract.json';
|
|
10
18
|
export function getManagementDir() {
|
|
11
19
|
return join(homedir(), OPT_AI_SEC_MANAGEMENT_REL);
|
|
12
20
|
}
|
|
13
21
|
export function getRemediationInstructionsPath() {
|
|
14
22
|
return join(getManagementDir(), REMEDIATION_INSTRUCTIONS_BASENAME);
|
|
15
23
|
}
|
|
24
|
+
export function getDeferredVscdbApplyPath() {
|
|
25
|
+
return join(getManagementDir(), DEFERRED_VSCDB_APPLY_BASENAME);
|
|
26
|
+
}
|
|
27
|
+
export function getFileCollectionVscdbContractPath() {
|
|
28
|
+
return join(getManagementDir(), FILE_COLLECTION_VSCDB_CONTRACT_BASENAME);
|
|
29
|
+
}
|
|
30
|
+
export function readFileCollectionVscdbContract() {
|
|
31
|
+
const path = getFileCollectionVscdbContractPath();
|
|
32
|
+
if (!existsSync(path))
|
|
33
|
+
return null;
|
|
34
|
+
try {
|
|
35
|
+
const parsed = JSON.parse(readFileSync(path, 'utf8'));
|
|
36
|
+
if (parsed?.version !== 1 || !Array.isArray(parsed.composer_shadow_keys))
|
|
37
|
+
return null;
|
|
38
|
+
return parsed;
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
export function writeFileCollectionVscdbContract(contract) {
|
|
45
|
+
atomicWriteJson(getFileCollectionVscdbContractPath(), contract);
|
|
46
|
+
}
|
|
16
47
|
export function atomicWriteJson(filePath, data) {
|
|
17
48
|
const dir = dirname(filePath);
|
|
18
49
|
if (!existsSync(dir))
|