log-llm-config 1.4.12 → 1.5.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.
Files changed (28) hide show
  1. package/dist/apply_deferred_vscdb.js +2 -2
  2. package/dist/bootstrap_constants.js +10 -0
  3. package/dist/cli.js +31 -1
  4. package/dist/compliance_check_runner.js +7 -2
  5. package/dist/compliance_prompt_gate.js +46 -17
  6. package/dist/log_config_files/auth/auth_flow.js +1 -1
  7. package/dist/log_config_files/auth/auth_key_store.js +1 -1
  8. package/dist/log_config_files/collection/config_collector.js +12 -0
  9. package/dist/log_config_files/collection/ensure_cursor_user_settings_snapshot.js +50 -0
  10. package/dist/log_config_files/paths/pattern_resolver.js +1 -1
  11. package/dist/log_config_files/runtime/client_event_reporter.js +347 -0
  12. package/dist/log_config_files/runtime/compliance_check.js +67 -20
  13. package/dist/log_config_files/runtime/compliance_session_log.js +22 -16
  14. package/dist/log_config_files/runtime/hook_logger.js +33 -20
  15. package/dist/log_config_files/runtime/log_metadata.js +122 -0
  16. package/dist/log_config_files/runtime/main_runner.js +25 -5
  17. package/dist/log_config_files/runtime/management_storage.js +36 -7
  18. package/dist/log_config_files/runtime/remediation_apply_tracking.js +4 -2
  19. package/dist/log_config_files/runtime/remediation_config_path.js +12 -1
  20. package/dist/log_config_files/runtime/remediation_sync.js +76 -8
  21. package/dist/log_config_files/sender/batch_sender.js +1 -1
  22. package/dist/log_sensitive_paths_audit.js +3 -3
  23. package/dist/log_uuid/auth_key_store.js +1 -1
  24. package/dist/log_uuid/startup_sender.js +21 -3
  25. package/package.json +1 -1
  26. package/dist/log_config_files/readers/vscdb_item_table_registry.js +0 -82
  27. package/dist/log_config_files/runtime/secretlint_scan.js +0 -69
  28. package/dist/post_restart_verify.js +0 -48
@@ -25,13 +25,13 @@ applyDeferredVscdbFromDisk()
25
25
  catch (e) {
26
26
  hookRunLog(`apply_deferred_vscdb: post_apply_verification error: ${e instanceof Error ? e.message : String(e)}`);
27
27
  if (complianceLogPath) {
28
- await finalizeAndUploadComplianceSessionLog(complianceLogPath, 'error');
28
+ await finalizeAndUploadComplianceSessionLog(complianceLogPath, 'failures');
29
29
  }
30
30
  }
31
31
  }
32
32
  else if (complianceLogPath) {
33
33
  hookRunLog('apply_deferred_vscdb: apply failed');
34
- await finalizeAndUploadComplianceSessionLog(complianceLogPath, 'error');
34
+ await finalizeAndUploadComplianceSessionLog(complianceLogPath, 'failures');
35
35
  }
36
36
  process.exit(ok ? 0 : 1);
37
37
  })
@@ -3,3 +3,13 @@
3
3
  * Used by auth, hook logging, and audit output. Do not add agent-specific paths here.
4
4
  */
5
5
  export const OPT_AI_SEC_MANAGEMENT_REL = 'opt-ai-sec/management';
6
+ /**
7
+ * All hook-written log files live under this subdirectory, grouped by type
8
+ * (see LOG_GROUP_* below). Keeping logs in one subtree leaves the management
9
+ * root limited to secrets/identity/state (auth_key, organization-uuid, contracts).
10
+ */
11
+ export const OPT_AI_SEC_LOGS_REL = `${OPT_AI_SEC_MANAGEMENT_REL}/logs`;
12
+ /** Log group subdirectories under {@link OPT_AI_SEC_LOGS_REL}. */
13
+ export const LOG_GROUP_HOOK = 'hook';
14
+ export const LOG_GROUP_COMPLIANCE = 'compliance';
15
+ export const LOG_GROUP_AUDIT = 'audit';
package/dist/cli.js CHANGED
@@ -111,4 +111,34 @@ function parseAndSetLogUuidEnvVars() {
111
111
  if (agentArg)
112
112
  process.env.OPTIMUS_AGENT = agentArg;
113
113
  }
114
- void main();
114
+ /** Map each cli command to its functional unit for failure telemetry. */
115
+ const COMMAND_UNIT = {
116
+ log_uuid: 'auth_version',
117
+ log_config_files: 'inventory',
118
+ log_sensitive_paths_audit: 'inventory',
119
+ compliance_prompt_gate: 'compliance',
120
+ compliance_check_runner: 'compliance',
121
+ 'apply-deferred-vscdb': 'compliance',
122
+ 'execute-trusted-restarts': 'compliance',
123
+ dialog_prefs: 'compliance',
124
+ };
125
+ main().catch(async (err) => {
126
+ // Top-level safety net: any uncaught error in a cli command is reported as a failed
127
+ // FunctionalRun (awaited so the POST lands before exit), then surfaced + non-zero exit.
128
+ try {
129
+ const cmd = args[0] || '';
130
+ const unit = COMMAND_UNIT[cmd] || 'inventory';
131
+ const { reportFunctionalFailureAwait } = await import('./log_config_files/runtime/client_event_reporter.js');
132
+ await reportFunctionalFailureAwait(unit, cmd, err);
133
+ }
134
+ catch {
135
+ /* never let telemetry mask the original error */
136
+ }
137
+ console.error(err instanceof Error ? err.stack || err.message : String(err));
138
+ try {
139
+ process.exit(1);
140
+ }
141
+ catch {
142
+ /* process.exit may be mocked in tests */
143
+ }
144
+ });
@@ -3,6 +3,7 @@ import { complianceRunnerRunnerLine, hookLogAppendSection } from './log_config_f
3
3
  import { normalizeAgentToken, runComplianceCheck, } from './log_config_files/runtime/compliance_check.js';
4
4
  import { finalizeAndUploadComplianceSessionLog, initComplianceSessionForRunner, isFinalizeComplianceSessionArgv, isInflightComplianceLogPath, parseComplianceLogArg, resolveComplianceSessionLogPath, } from './log_config_files/runtime/compliance_session_log.js';
5
5
  import { existsSync } from 'node:fs';
6
+ import { getStoryId, flushBufferedClientEvents, reportFunctionalRun, reportFunctionalFailureAwait, } from './log_config_files/runtime/client_event_reporter.js';
6
7
  function parseAgentArg() {
7
8
  const eq = process.argv.find((a) => a.startsWith('--agent='));
8
9
  if (!eq)
@@ -24,7 +25,6 @@ function parseAgentArg() {
24
25
  complianceLogPath = initComplianceSessionForRunner(resolved);
25
26
  }
26
27
  else {
27
- // Gate may have finalized+moved the session before this background runner started.
28
28
  complianceLogPath = null;
29
29
  }
30
30
  }
@@ -32,9 +32,13 @@ function parseAgentArg() {
32
32
  hookLogAppendSection('compliance_check_runner (background sync + check)');
33
33
  }
34
34
  complianceRunnerRunnerLine('compliance_check_runner: start');
35
+ // Background runner: opportunistically flush any telemetry the (non-blocking) gate buffered.
36
+ getStoryId();
37
+ await flushBufferedClientEvents();
35
38
  try {
36
39
  await runComplianceCheck(parseAgentArg());
37
40
  complianceRunnerRunnerLine('compliance_check_runner: finished ok');
41
+ await reportFunctionalRun({ unit: 'compliance', command: 'compliance_check_runner', outcome: 'ok' });
38
42
  }
39
43
  catch (err) {
40
44
  const detail = err instanceof Error ? err.stack ?? err.message : String(err);
@@ -42,8 +46,9 @@ function parseAgentArg() {
42
46
  complianceRunnerRunnerLine(`compliance_check_runner: stack_or_detail ${detail.slice(0, 4000)}`);
43
47
  process.stderr.write(`compliance_check_runner error: ${err instanceof Error ? err.message : String(err)}\n`);
44
48
  if (complianceLogPath) {
45
- await finalizeAndUploadComplianceSessionLog(complianceLogPath, 'error');
49
+ await finalizeAndUploadComplianceSessionLog(complianceLogPath, 'failures');
46
50
  }
51
+ await reportFunctionalFailureAwait('compliance', 'compliance_check_runner', err);
47
52
  process.exit(1);
48
53
  }
49
54
  if (complianceLogPath) {
@@ -6,17 +6,20 @@
6
6
  * When autofix returns restart_commands, this process does not spawn them — the shell hook pipes
7
7
  * the same JSON line to execute_trusted_restarts (TS allowlist + spawn).
8
8
  */
9
- import { applyAutofixViolations, confirmAppliedAutofixVerified, normalizeAgentToken, pruneSatisfiedOneTimeRemediations, reportPostRestartVerificationOutcomes, runLocalRemediationComplianceCheck, uploadSatisfiedManifestConfigs, } from './log_config_files/runtime/compliance_check.js';
9
+ import { applyAutofixViolations, confirmAppliedAutofixVerified, normalizeAgentToken, pruneSatisfiedOneTimeRemediations, reportCompliantRemediationVerifiedStatus, reportPostRestartVerificationOutcomes, runLocalRemediationComplianceCheck, uploadSatisfiedManifestConfigs, } from './log_config_files/runtime/compliance_check.js';
10
10
  import { isRemediationQuarantined } from './log_config_files/runtime/remediation_apply_tracking.js';
11
11
  import { existsSync, readFileSync, statSync } from 'node:fs';
12
12
  import { getRemediationInstructionsPath } from './log_config_files/runtime/management_storage.js';
13
13
  import { hookLogSessionBanner, hookRunLog, logRemediationApplyFailure } from './log_config_files/runtime/hook_logger.js';
14
+ import { getStoryId, bufferFunctionalRun } from './log_config_files/runtime/client_event_reporter.js';
14
15
  import { finalizeAndUploadComplianceSessionLog, initComplianceSessionForGate, parseComplianceLogArg, } from './log_config_files/runtime/compliance_session_log.js';
15
16
  import { isThisCliModule } from './cli_invocation_match.js';
16
17
  import { ensureAuthentication } from './log_config_files/auth/auth_flow.js';
17
18
  import { sendConfigFile } from './log_config_files/sender/batch_sender.js';
19
+ import { parseJsonWithJsoncFallback } from './log_config_files/readers/file_readers.js';
20
+ import { PORTABLE_CURSOR_USER_SETTINGS, resolveRemediationConfigPath, resolveRemediationUploadFileType, } from './log_config_files/runtime/remediation_config_path.js';
18
21
  import { tryResolveHardwareUuid } from './log_config_files/runtime/hardware_uuid.js';
19
- import { syncRemediations } from './log_config_files/runtime/remediation_sync.js';
22
+ import { cursorAutofixDefersInlineVerify, syncRemediations, } from './log_config_files/runtime/remediation_sync.js';
20
23
  import { loadEndpointBase } from './log_config_files/sender/endpoint_config.js';
21
24
  import { formatRemediationChangePreviewForApplied } from './remediation_change_preview.js';
22
25
  const MANIFEST_STALE_MS = 7 * 24 * 60 * 60 * 1000;
@@ -146,9 +149,9 @@ export function formatCopilotAutofixDialog(appliedViolations) {
146
149
  export function formatOpenCodeAutofixDialog(appliedViolations) {
147
150
  return formatPreventiveAutofixDialog(appliedViolations, 'OpenCode will now apply this policy to your environment.');
148
151
  }
149
- /** Cursor restart dialog after enforced/preventive remediation is applied locally. */
152
+ /** Cursor restart after local autofix (all JSON settings + vscdb paths use restart_commands). */
150
153
  export function formatCursorRestartAutofixDialog(appliedViolations) {
151
- return formatPreventiveAutofixDialog(appliedViolations, 'Cursor will now restart to apply this policy, and your context will be retained.');
154
+ return formatPreventiveAutofixDialog(appliedViolations, 'Cursor will restart so the running app reloads policy from disk. Your context will be retained.');
152
155
  }
153
156
  /**
154
157
  * Upload a secondary compliance file to the backend so the server can resolve the finding.
@@ -156,16 +159,23 @@ export function formatCursorRestartAutofixDialog(appliedViolations) {
156
159
  */
157
160
  async function _uploadSecondaryFile(entry) {
158
161
  const { uuid, config_file_path, file_type } = entry;
159
- if (!file_type) {
162
+ const uploadFileType = resolveRemediationUploadFileType(config_file_path, file_type ?? undefined);
163
+ if (!uploadFileType) {
160
164
  hookRunLog(`secondary_upload: skipping uuid=${uuid} — no file_type on secondary group`);
161
165
  return;
162
166
  }
167
+ const diskPath = resolveRemediationConfigPath(config_file_path);
163
168
  let rawContent;
164
169
  try {
165
- rawContent = JSON.parse(readFileSync(config_file_path, 'utf8'));
170
+ const parsed = parseJsonWithJsoncFallback(readFileSync(diskPath, 'utf8'));
171
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
172
+ hookRunLog(`secondary_upload: invalid JSON uuid=${uuid} path=${diskPath}`);
173
+ return;
174
+ }
175
+ rawContent = parsed;
166
176
  }
167
177
  catch {
168
- hookRunLog(`secondary_upload: could not read file uuid=${uuid} path=${config_file_path}`);
178
+ hookRunLog(`secondary_upload: could not read file uuid=${uuid} path=${diskPath}`);
169
179
  return;
170
180
  }
171
181
  const hw = tryResolveHardwareUuid();
@@ -173,13 +183,16 @@ async function _uploadSecondaryFile(entry) {
173
183
  hookRunLog(`secondary_upload: hardware UUID unavailable, skipping uuid=${uuid}`);
174
184
  return;
175
185
  }
186
+ const portablePath = config_file_path.includes('Cursor/User/settings.json')
187
+ ? PORTABLE_CURSOR_USER_SETTINGS
188
+ : config_file_path.trim() || diskPath;
176
189
  try {
177
190
  const authKey = await ensureAuthentication(hw);
178
- const sent = await sendConfigFile({ file_type, file_path: config_file_path, raw_content: rawContent }, hw, authKey);
179
- hookRunLog(`secondary_upload: uuid=${uuid} path=${config_file_path} sent=${sent}`);
191
+ const sent = await sendConfigFile({ file_type: uploadFileType, file_path: portablePath, raw_content: rawContent }, hw, authKey);
192
+ hookRunLog(`secondary_upload: uuid=${uuid} path=${portablePath} sent=${sent}`);
180
193
  }
181
194
  catch (err) {
182
- hookRunLog(`secondary_upload: upload failed uuid=${uuid} path=${config_file_path} err=${err instanceof Error ? err.message : String(err)}`);
195
+ hookRunLog(`secondary_upload: upload failed uuid=${uuid} path=${portablePath} err=${err instanceof Error ? err.message : String(err)}`);
183
196
  }
184
197
  }
185
198
  /**
@@ -188,7 +201,22 @@ async function _uploadSecondaryFile(entry) {
188
201
  * {@link isRunAsCliModule} is false and the gate must be invoked explicitly from cli.ts.
189
202
  */
190
203
  export async function runCompliancePromptGateCli() {
191
- await runCompliancePromptGate().catch(() => printAllow(parseIde()));
204
+ getStoryId();
205
+ try {
206
+ await runCompliancePromptGate();
207
+ // Gate blocks prompt submit — buffer telemetry (no inline POST); the background runner flushes it.
208
+ bufferFunctionalRun({ unit: 'compliance', command: 'compliance_prompt_gate', outcome: 'ok' });
209
+ }
210
+ catch (err) {
211
+ bufferFunctionalRun({
212
+ unit: 'compliance',
213
+ command: 'compliance_prompt_gate',
214
+ outcome: 'failed',
215
+ message: err instanceof Error ? `${err.name}: ${err.message}` : String(err),
216
+ detail: err instanceof Error && err.stack ? err.stack : String(err),
217
+ });
218
+ printAllow(parseIde());
219
+ }
192
220
  }
193
221
  /** Exported for tests; runs when `dist/compliance_prompt_gate.js` is the process entry (argv[1]). */
194
222
  export async function runCompliancePromptGate() {
@@ -202,8 +230,6 @@ export async function runCompliancePromptGate() {
202
230
  hookLogSessionBanner('compliance_prompt_gate (before submit)');
203
231
  }
204
232
  let status = runLocalRemediationComplianceCheck(agent);
205
- // Post-restart verify + server reports BEFORE sync so a manifest UUID swap cannot delay
206
- // verified / activity-log until the following prompt.
207
233
  const postRestartVerify = reportPostRestartVerificationOutcomes(status.violations);
208
234
  if (postRestartVerify.outcomes.length > 0) {
209
235
  await Promise.allSettled(postRestartVerify.reportPromises);
@@ -290,12 +316,14 @@ export async function runCompliancePromptGate() {
290
316
  // disk synchronously; a restart_command, if present, only reloads the already-compliant file
291
317
  // into the running app — it is not the mechanism that makes the fix take effect. So report
292
318
  // "verified" now instead of leaving the finding "pending" in the UI until the next prompt.
293
- // Cursor's restart can carry a deferred state.vscdb write that only lands post-restart, so
294
- // Cursor still verifies inline only when no restart is pending.
319
+ // Cursor JSON: disk recheck OK → verified before restart. Cursor deferred vscdb verify after restart.
295
320
  const immediateJsonAgent = ide === 'claude' || ide === 'copilot' || ide === 'opencode';
321
+ const diskProvenCompliant = recheckOk || claudeRecheckStaleAfterImmediateApply;
322
+ const cursorRestartVerifyPending = cursorAutofixDefersInlineVerify(ide, restartCommands.length, deferredSqlitePending === true, diskProvenCompliant);
296
323
  const immediateVerified = !deferredSqlitePending &&
297
- (recheckOk || claudeRecheckStaleAfterImmediateApply) &&
298
- (restartCommands.length === 0 || immediateJsonAgent);
324
+ !cursorRestartVerifyPending &&
325
+ diskProvenCompliant &&
326
+ (restartCommands.length === 0 || immediateJsonAgent || ide === 'cursor');
299
327
  if (immediateVerified) {
300
328
  confirmAppliedAutofixVerified(appliedViolations, reportPromises);
301
329
  await Promise.allSettled(reportPromises);
@@ -360,6 +388,7 @@ export async function runCompliancePromptGate() {
360
388
  await Promise.allSettled(pruned.reportPromises);
361
389
  }
362
390
  await Promise.allSettled(uploadSatisfiedManifestConfigs(agent));
391
+ await Promise.allSettled(reportCompliantRemediationVerifiedStatus(agent));
363
392
  printAllow(ide);
364
393
  }
365
394
  if (isRunAsCliModule()) {
@@ -4,7 +4,7 @@ import { ensureAuthentication as ensureTofuAuthentication } from '../../tofu.js'
4
4
  import { loadEndpointBase } from '../sender/endpoint_config.js';
5
5
  import { OPT_AI_SEC_MANAGEMENT_REL } from '../../bootstrap_constants.js';
6
6
  import { hookRunLog } from '../runtime/hook_logger.js';
7
- const AUTH_KEY_RELATIVE_PATH = path.join(OPT_AI_SEC_MANAGEMENT_REL, 'auth_key.txt');
7
+ const AUTH_KEY_RELATIVE_PATH = path.join(OPT_AI_SEC_MANAGEMENT_REL, 'auth', 'auth_key.txt');
8
8
  /** Ensure authentication — verify stored key or request new one via handshake. */
9
9
  async function ensureAuthentication(hardwareUuid, endpointBase) {
10
10
  const key = await ensureTofuAuthentication({
@@ -1,7 +1,7 @@
1
1
  import path from 'node:path';
2
2
  import { getAuthKeyPath as getSharedAuthKeyPath, readStoredAuthKey as readSharedStoredAuthKey, } from '../../tofu.js';
3
3
  import { OPT_AI_SEC_MANAGEMENT_REL } from '../../bootstrap_constants.js';
4
- const AUTH_KEY_RELATIVE_PATH = path.join(OPT_AI_SEC_MANAGEMENT_REL, 'auth_key.txt');
4
+ const AUTH_KEY_RELATIVE_PATH = path.join(OPT_AI_SEC_MANAGEMENT_REL, 'auth', 'auth_key.txt');
5
5
  const AUTH_KEY_OPTIONS = { authRelativePath: AUTH_KEY_RELATIVE_PATH };
6
6
  /** Return absolute path to auth_key.txt, or null if home dir is unavailable. */
7
7
  function getAuthKeyPath() {
@@ -86,6 +86,12 @@ function readContentByFormat(path, format) {
86
86
  // json (default) — fall back to markdown for .md files from agents not yet annotated
87
87
  return readJSONFile(path) ?? readMCPConfig(path) ?? (path.endsWith('.md') ? readMarkdownFile(path) : null);
88
88
  }
89
+ function isPortableCursorUserSettingsTarget(t) {
90
+ const p = (t.logicalFilePath ?? t.path).replace(/\\/g, '/').toLowerCase();
91
+ return (p === 'cursor/user/settings.json' ||
92
+ p.endsWith('/cursor/user/settings.json') ||
93
+ p.includes('application support/cursor/user/settings.json'));
94
+ }
89
95
  function collectRegularFileEntry(t, enrichByFileType) {
90
96
  const format = t.content_format || 'json';
91
97
  let content = readContentByFormat(t.path, format);
@@ -120,6 +126,12 @@ function collectRegularFileEntry(t, enrichByFileType) {
120
126
  return null;
121
127
  raw = filtered;
122
128
  }
129
+ if (t.file_type === 'vscode_settings' &&
130
+ isPortableCursorUserSettingsTarget(t) &&
131
+ Object.keys(raw).length === 0) {
132
+ console.warn(`Skipping empty Cursor User/settings.json upload (path=${t.path}); global ignore would be invisible to the server.`);
133
+ return null;
134
+ }
123
135
  return { file_type: t.file_type, file_path: t.logicalFilePath ?? t.path, raw_content: raw };
124
136
  }
125
137
  function collectMetadataEntry(t) {
@@ -0,0 +1,50 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { readJSONFile } from '../readers/file_readers.js';
3
+ import { PORTABLE_CURSOR_USER_SETTINGS, cursorUserSettingsAbsolutePaths, } from '../runtime/remediation_config_path.js';
4
+ export function isPortableCursorUserSettingsUploadPath(filePath) {
5
+ const p = filePath.replace(/\\/g, '/').toLowerCase();
6
+ return (p === 'cursor/user/settings.json' ||
7
+ p.endsWith('/cursor/user/settings.json') ||
8
+ p.includes('application support/cursor/user/settings.json'));
9
+ }
10
+ function settingsPayloadHasGlobalIgnoreListKey(raw) {
11
+ if (!raw || typeof raw !== 'object')
12
+ return false;
13
+ const nested = raw.cursor
14
+ ?.general?.globalCursorIgnoreList;
15
+ if (Array.isArray(nested))
16
+ return true;
17
+ const flat = raw['cursor.general.globalCursorIgnoreList'];
18
+ return Array.isArray(flat);
19
+ }
20
+ /**
21
+ * Sensitive Directories scans need full User/settings.json (globalCursorIgnoreList).
22
+ * Pattern collection can omit the key or send an empty object; merge disk when the batch
23
+ * lacks an explicit globalCursorIgnoreList array (including []).
24
+ */
25
+ export function ensureCursorUserSettingsSnapshotInBatch(configFiles) {
26
+ const hasIgnoreKeyInBatch = configFiles.some((c) => c.file_type === 'vscode_settings' &&
27
+ isPortableCursorUserSettingsUploadPath(c.file_path) &&
28
+ settingsPayloadHasGlobalIgnoreListKey(c.raw_content));
29
+ if (hasIgnoreKeyInBatch)
30
+ return;
31
+ for (const abs of cursorUserSettingsAbsolutePaths()) {
32
+ if (!existsSync(abs))
33
+ continue;
34
+ const raw = readJSONFile(abs);
35
+ if (!raw || Object.keys(raw).length === 0)
36
+ continue;
37
+ const entry = {
38
+ file_type: 'vscode_settings',
39
+ file_path: PORTABLE_CURSOR_USER_SETTINGS,
40
+ raw_content: raw,
41
+ };
42
+ const idx = configFiles.findIndex((c) => c.file_type === 'vscode_settings' &&
43
+ isPortableCursorUserSettingsUploadPath(c.file_path));
44
+ if (idx >= 0)
45
+ configFiles[idx] = entry;
46
+ else
47
+ configFiles.push(entry);
48
+ return;
49
+ }
50
+ }
@@ -9,7 +9,7 @@ function normalizePathSkipPrefixes(prefixes) {
9
9
  return prefixes.filter((p) => typeof p === 'string' && p.length > 0);
10
10
  }
11
11
  /**
12
- * Expands glob patterns containing double-asterisk (recursive directory traversal).
12
+ * Expands glob patterns with double-asterisk (recursive directory traversal).
13
13
  *
14
14
  * Example: ~/.cursor/plugins/cache/ ** /skills/ recursively finds all skills
15
15
  * directories under the cache folder, up to RECURSIVE_GLOB_MAX_DEPTH.