@kevinbermudezc/context-guard 1.3.2 → 1.5.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 (45) hide show
  1. package/README.md +67 -2
  2. package/dist/bin/claude-hook.js +35 -14
  3. package/dist/bin/claude-hook.js.map +1 -1
  4. package/dist/bin/context-guard.js +190 -1
  5. package/dist/bin/context-guard.js.map +1 -1
  6. package/dist/bin/mcp-server.js +4 -2
  7. package/dist/bin/mcp-server.js.map +1 -1
  8. package/dist/src/index.d.ts +3 -0
  9. package/dist/src/index.d.ts.map +1 -1
  10. package/dist/src/index.js +6 -0
  11. package/dist/src/index.js.map +1 -1
  12. package/dist/src/telemetry.d.ts +32 -0
  13. package/dist/src/telemetry.d.ts.map +1 -0
  14. package/dist/src/telemetry.js +118 -0
  15. package/dist/src/telemetry.js.map +1 -0
  16. package/dist/src/update-notifier.d.ts +30 -0
  17. package/dist/src/update-notifier.d.ts.map +1 -0
  18. package/dist/src/update-notifier.js +150 -0
  19. package/dist/src/update-notifier.js.map +1 -0
  20. package/dist/src/version.d.ts +2 -0
  21. package/dist/src/version.d.ts.map +1 -0
  22. package/dist/src/version.js +2 -0
  23. package/dist/src/version.js.map +1 -0
  24. package/dist/tests/antigravity-hook.test.d.ts +2 -0
  25. package/dist/tests/antigravity-hook.test.d.ts.map +1 -0
  26. package/dist/tests/antigravity-hook.test.js +88 -0
  27. package/dist/tests/antigravity-hook.test.js.map +1 -0
  28. package/dist/tests/init.test.d.ts +2 -0
  29. package/dist/tests/init.test.d.ts.map +1 -0
  30. package/dist/tests/init.test.js +63 -0
  31. package/dist/tests/init.test.js.map +1 -0
  32. package/dist/tests/mcp-server.test.js +1 -1
  33. package/dist/tests/telemetry.test.d.ts +2 -0
  34. package/dist/tests/telemetry.test.d.ts.map +1 -0
  35. package/dist/tests/telemetry.test.js +79 -0
  36. package/dist/tests/telemetry.test.js.map +1 -0
  37. package/dist/tests/update-notifier.test.d.ts +2 -0
  38. package/dist/tests/update-notifier.test.d.ts.map +1 -0
  39. package/dist/tests/update-notifier.test.js +60 -0
  40. package/dist/tests/update-notifier.test.js.map +1 -0
  41. package/examples/hooks/antigravity-hooks.json +17 -0
  42. package/examples/rules/antigravity/context-guard.md +15 -0
  43. package/examples/rules/cursor/context-guard.mdc +25 -0
  44. package/examples/skills/context-guard/SKILL.md +48 -0
  45. package/package.json +1 -1
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Tests for context-guard init workspace command
3
+ */
4
+ import { describe, it, beforeEach, afterEach } from 'node:test';
5
+ import assert from 'node:assert';
6
+ import { spawnSync } from 'node:child_process';
7
+ import fs from 'node:fs';
8
+ import os from 'node:os';
9
+ import path from 'node:path';
10
+ const CLI_BIN = path.resolve(process.cwd(), 'dist/bin/context-guard.js');
11
+ describe('context-guard init (Phase 3)', () => {
12
+ let tmpWorkspace;
13
+ beforeEach(() => {
14
+ tmpWorkspace = fs.mkdtempSync(path.join(os.tmpdir(), 'cg-init-test-'));
15
+ });
16
+ afterEach(() => {
17
+ if (fs.existsSync(tmpWorkspace)) {
18
+ try {
19
+ fs.rmSync(tmpWorkspace, { recursive: true, force: true });
20
+ }
21
+ catch { }
22
+ }
23
+ });
24
+ it('should initialize Antigravity rules, skill and mcp config when targeting antigravity', () => {
25
+ const res = spawnSync('node', [CLI_BIN, 'init', 'antigravity'], {
26
+ cwd: tmpWorkspace,
27
+ encoding: 'utf-8'
28
+ });
29
+ assert.strictEqual(res.status, 0);
30
+ assert.ok(res.stdout.includes('Google Antigravity'));
31
+ const rulePath = path.join(tmpWorkspace, '.agents/rules/context-guard.md');
32
+ const skillPath = path.join(tmpWorkspace, '.agents/skills/context-guard/SKILL.md');
33
+ const mcpPath = path.join(tmpWorkspace, '.agents/mcp.json');
34
+ assert.ok(fs.existsSync(rulePath), 'Antigravity rule must exist');
35
+ assert.ok(fs.existsSync(skillPath), 'Antigravity skill must exist');
36
+ assert.ok(fs.existsSync(mcpPath), 'Antigravity MCP config must exist');
37
+ const mcpJson = JSON.parse(fs.readFileSync(mcpPath, 'utf-8'));
38
+ assert.ok(mcpJson.mcpServers['context-guard']);
39
+ });
40
+ it('should initialize Cursor rules and mcp config when targeting cursor', () => {
41
+ const res = spawnSync('node', [CLI_BIN, 'init', 'cursor'], {
42
+ cwd: tmpWorkspace,
43
+ encoding: 'utf-8'
44
+ });
45
+ assert.strictEqual(res.status, 0);
46
+ assert.ok(res.stdout.includes('Cursor'));
47
+ const rulePath = path.join(tmpWorkspace, '.cursor/rules/context-guard.mdc');
48
+ const mcpPath = path.join(tmpWorkspace, '.cursor/mcp.json');
49
+ assert.ok(fs.existsSync(rulePath), 'Cursor rule must exist');
50
+ assert.ok(fs.existsSync(mcpPath), 'Cursor MCP config must exist');
51
+ });
52
+ it('should configure all adapters when run with no arguments or "all"', () => {
53
+ const res = spawnSync('node', [CLI_BIN, 'init'], {
54
+ cwd: tmpWorkspace,
55
+ encoding: 'utf-8'
56
+ });
57
+ assert.strictEqual(res.status, 0);
58
+ assert.ok(fs.existsSync(path.join(tmpWorkspace, '.agents/rules/context-guard.md')));
59
+ assert.ok(fs.existsSync(path.join(tmpWorkspace, '.cursor/rules/context-guard.mdc')));
60
+ assert.ok(fs.existsSync(path.join(tmpWorkspace, '.claude/settings.json')));
61
+ });
62
+ });
63
+ //# sourceMappingURL=init.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"init.test.js","sourceRoot":"","sources":["../../tests/init.test.ts"],"names":[],"mappings":"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"}
@@ -90,7 +90,7 @@ describe('ContextGuard MCP Server (Phase 2)', () => {
90
90
  assert.ok(result.serverInfo, 'Must include serverInfo');
91
91
  const info = result.serverInfo;
92
92
  assert.strictEqual(info.name, 'context-guard');
93
- assert.strictEqual(info.version, '1.3.2');
93
+ assert.strictEqual(info.version, '1.5.0');
94
94
  });
95
95
  it('should list exactly 3 registered tools', async () => {
96
96
  const res = await sendRequest(proc, req('tools/list'));
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=telemetry.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"telemetry.test.d.ts","sourceRoot":"","sources":["../../tests/telemetry.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Unit tests for Anonymous Opt-Out Telemetry
3
+ */
4
+ import fs from 'node:fs';
5
+ import os from 'node:os';
6
+ import path from 'node:path';
7
+ import { describe, it, beforeEach, afterEach } from 'node:test';
8
+ import assert from 'node:assert';
9
+ import { isTelemetryEnabled, enableTelemetry, disableTelemetry, getAnonymousId, sendTelemetryPing } from '../src/telemetry.js';
10
+ describe('Anonymous Telemetry System', () => {
11
+ const originalEnv = { ...process.env };
12
+ const testDir = path.join(os.tmpdir(), `cg-telemetry-test-${Date.now()}-${Math.random().toString(36).slice(2)}`);
13
+ const disabledFile = path.join(testDir, 'telemetry-disabled');
14
+ beforeEach(() => {
15
+ delete process.env.CONTEXT_GUARD_TELEMETRY;
16
+ delete process.env.DO_NOT_TRACK;
17
+ process.env.CONTEXT_GUARD_DIR = testDir;
18
+ if (fs.existsSync(disabledFile)) {
19
+ try {
20
+ fs.unlinkSync(disabledFile);
21
+ }
22
+ catch { }
23
+ }
24
+ });
25
+ afterEach(() => {
26
+ process.env = { ...originalEnv };
27
+ if (fs.existsSync(testDir)) {
28
+ try {
29
+ fs.rmSync(testDir, { recursive: true, force: true });
30
+ }
31
+ catch { }
32
+ }
33
+ });
34
+ it('should be enabled by default', () => {
35
+ assert.strictEqual(isTelemetryEnabled(), true);
36
+ });
37
+ it('should respect CONTEXT_GUARD_TELEMETRY=0 environment variable', () => {
38
+ process.env.CONTEXT_GUARD_TELEMETRY = '0';
39
+ assert.strictEqual(isTelemetryEnabled(), false);
40
+ process.env.CONTEXT_GUARD_TELEMETRY = 'false';
41
+ assert.strictEqual(isTelemetryEnabled(), false);
42
+ });
43
+ it('should respect global DO_NOT_TRACK=1 standard', () => {
44
+ process.env.DO_NOT_TRACK = '1';
45
+ assert.strictEqual(isTelemetryEnabled(), false);
46
+ });
47
+ it('should respect disableTelemetry() and enableTelemetry() flags', () => {
48
+ disableTelemetry();
49
+ assert.strictEqual(isTelemetryEnabled(), false);
50
+ assert.strictEqual(fs.existsSync(disabledFile), true);
51
+ enableTelemetry();
52
+ assert.strictEqual(isTelemetryEnabled(), true);
53
+ assert.strictEqual(fs.existsSync(disabledFile), false);
54
+ });
55
+ it('should generate a persistent, valid UUIDv4 anonymous machine ID', () => {
56
+ const id1 = getAnonymousId();
57
+ assert.ok(id1);
58
+ assert.strictEqual(typeof id1, 'string');
59
+ // UUID v4 regex validation
60
+ assert.match(id1, /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i);
61
+ const id2 = getAnonymousId();
62
+ assert.strictEqual(id1, id2, 'Anonymous ID must be persistent across calls');
63
+ });
64
+ it('should send telemetry ping non-blockingly without throwing errors even if offline or invalid endpoint', () => {
65
+ process.env.CONTEXT_GUARD_TELEMETRY_ENDPOINT = 'http://127.0.0.1:54321/non-existent';
66
+ assert.doesNotThrow(() => {
67
+ sendTelemetryPing('cli', 'file_intercepted', '1.4.0', 1500);
68
+ });
69
+ });
70
+ it('should not send telemetry ping when telemetry is disabled', () => {
71
+ disableTelemetry();
72
+ // Point to an invalid endpoint that would fail if attempted
73
+ process.env.CONTEXT_GUARD_TELEMETRY_ENDPOINT = 'http://127.0.0.1:54321/must-not-be-called';
74
+ assert.doesNotThrow(() => {
75
+ sendTelemetryPing('mcp', 'outline_inspected', '1.4.0', 500);
76
+ });
77
+ });
78
+ });
79
+ //# sourceMappingURL=telemetry.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"telemetry.test.js","sourceRoot":"","sources":["../../tests/telemetry.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=update-notifier.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"update-notifier.test.d.ts","sourceRoot":"","sources":["../../tests/update-notifier.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Tests for the update notifier system
3
+ */
4
+ import { describe, it, beforeEach, afterEach } from 'node:test';
5
+ import assert from 'node:assert';
6
+ import fs from 'node:fs';
7
+ import os from 'node:os';
8
+ import path from 'node:path';
9
+ import { isNewerVersion, getCachedLatestVersion, saveUpdateCache, shouldCheckRegistry, getUpdateNotice } from '../src/update-notifier.js';
10
+ describe('Update Notifier System', () => {
11
+ let tempDir;
12
+ const originalEnv = { ...process.env };
13
+ beforeEach(() => {
14
+ tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'cg-update-test-'));
15
+ process.env.CONTEXT_GUARD_DIR = tempDir;
16
+ });
17
+ afterEach(() => {
18
+ process.env = { ...originalEnv };
19
+ if (fs.existsSync(tempDir)) {
20
+ try {
21
+ fs.rmSync(tempDir, { recursive: true, force: true });
22
+ }
23
+ catch { }
24
+ }
25
+ });
26
+ it('should accurately compare semver versions', () => {
27
+ assert.strictEqual(isNewerVersion('1.4.1', '1.4.0'), true);
28
+ assert.strictEqual(isNewerVersion('1.5.0', '1.4.9'), true);
29
+ assert.strictEqual(isNewerVersion('2.0.0', '1.9.9'), true);
30
+ assert.strictEqual(isNewerVersion('1.4.0', '1.4.0'), false);
31
+ assert.strictEqual(isNewerVersion('1.3.2', '1.4.0'), false);
32
+ });
33
+ it('should read and write cache properly', () => {
34
+ assert.strictEqual(getCachedLatestVersion(), null);
35
+ assert.strictEqual(shouldCheckRegistry(), true);
36
+ saveUpdateCache('1.4.5');
37
+ assert.strictEqual(getCachedLatestVersion(), '1.4.5');
38
+ assert.strictEqual(shouldCheckRegistry(), false);
39
+ });
40
+ it('should return a clean, unboxed notification message when a newer version is available', () => {
41
+ saveUpdateCache('1.5.0');
42
+ const notice = getUpdateNotice('1.4.0');
43
+ assert.ok(notice);
44
+ assert.strictEqual(notice.latestVersion, '1.5.0');
45
+ assert.ok(notice.message.includes('ContextGuard update available'));
46
+ assert.ok(notice.message.includes('v1.4.0'));
47
+ assert.ok(notice.message.includes('v1.5.0'));
48
+ assert.ok(notice.message.includes('npm i -g @kevinbermudezc/context-guard@latest'));
49
+ // Ensure no broken ASCII box-drawing characters
50
+ assert.strictEqual(notice.message.includes('╭'), false);
51
+ assert.strictEqual(notice.message.includes('╰'), false);
52
+ assert.strictEqual(notice.message.includes('│'), false);
53
+ });
54
+ it('should return null when the current version is equal or newer', () => {
55
+ saveUpdateCache('1.4.0');
56
+ const notice = getUpdateNotice('1.4.0');
57
+ assert.strictEqual(notice, null);
58
+ });
59
+ });
60
+ //# sourceMappingURL=update-notifier.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"update-notifier.test.js","sourceRoot":"","sources":["../../tests/update-notifier.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ {
2
+ "context-guard-admission": {
3
+ "enabled": true,
4
+ "PreToolUse": [
5
+ {
6
+ "matcher": "view_file",
7
+ "hooks": [
8
+ {
9
+ "type": "command",
10
+ "command": "npx -y -p @kevinbermudezc/context-guard context-guard-hook",
11
+ "timeout": 5
12
+ }
13
+ ]
14
+ }
15
+ ]
16
+ }
17
+ }
@@ -0,0 +1,15 @@
1
+ # 🛡️ ContextGuard Project Rule for Google Antigravity (AGY)
2
+
3
+ You MUST follow these context admission control and token optimization rules:
4
+
5
+ ## 1. Context Admission Control
6
+ - **Do NOT read large files (>300 lines) into context unrestricted.** Dumping large files floods the context window and accelerates model degradation.
7
+ - When `read_file_safe` is available via MCP:
8
+ - Use `inspect_outline(file_path)` to get a structural map of the file with line numbers `[L#]`.
9
+ - Use `read_file_safe(file_path)` which automatically returns an AST skeleton for files >300 lines.
10
+ - When inspecting implementations, read targeted line ranges:
11
+ `read_file_safe(file_path, start_line=..., end_line=...)` or `view_file(AbsolutePath=..., StartLine=..., EndLine=...)`.
12
+
13
+ ## 2. Lockfiles and Binaries
14
+ - **Never open lockfiles** (`package-lock.json`, `pnpm-lock.yaml`, `yarn.lock`, `bun.lockb`) or compiled/minified bundles (`.min.js`, `.wasm`, `.pyc`).
15
+ - Use command-line tools (`pnpm why <pkg>`, `npm ls <pkg>`, or read `package.json` directly) to check package dependencies.
@@ -0,0 +1,25 @@
1
+ ---
2
+ description: ContextGuard FinOps and Context Window Admission Control
3
+ globs: *
4
+ alwaysApply: true
5
+ ---
6
+
7
+ # 🛡️ ContextGuard Rule for Cursor (Composer & Chat)
8
+
9
+ You MUST follow these context admission control and token optimization rules to protect the model context window and prevent latency degradation from massive files:
10
+
11
+ ## 1. Zero Direct Reads for Large Files (>300 lines)
12
+ - **NEVER** read files larger than 300 lines without line constraints (`start_line`, `end_line`).
13
+ - Instead of using default read tools, prioritize calling ContextGuard MCP tools:
14
+ - Call `read_file_safe(file_path: "path/to/file")` to automatically receive an AST structural skeleton if the file is large (>300 lines).
15
+ - Call `inspect_outline(file_path: "path/to/file")` (< 5ms, 0 tokens) to inspect classes, methods, and types with their exact line tags `[L#]`.
16
+ - Once the relevant code block is located, read only the target slice:
17
+ `read_file_safe(file_path: "path/to/file", start_line: 120, end_line: 180)`
18
+
19
+ ## 2. Pattern Searching
20
+ - When searching for symbols or references across files, use `grep_distilled(pattern: "myFunction")`.
21
+ - It isolates only matching signatures and structural surroundings rather than returning huge dumps of implementation code.
22
+
23
+ ## 3. Binaries and Lockfiles
24
+ - Do NOT read `package-lock.json`, `pnpm-lock.yaml`, `bun.lockb`, or minified bundles (`.min.js`).
25
+ - Use CLI commands (`npm ls`, `pnpm why`, `cat package.json`) to inspect dependencies.
@@ -0,0 +1,48 @@
1
+ ---
2
+ name: context-guard
3
+ description: >-
4
+ Uses ContextGuard to inspect large files, outlines, and structural AST skeletons safely without blowing up the context window. Use whenever exploring unfamiliar files, large components (>300 lines), or auditing token consumption.
5
+ ---
6
+
7
+ # 🛡️ ContextGuard Skill for Antigravity
8
+
9
+ ContextGuard provides intelligent context admission control, reducing token usage by up to 90% when reading large source files.
10
+
11
+ ## When to Use
12
+ - Whenever you need to read or understand a source file larger than 300 lines.
13
+ - When locating functions, classes, interfaces, or types in a large codebase.
14
+ - When searching patterns across unfamiliar repositories.
15
+
16
+ ## Workflow
17
+
18
+ ### 1. Inspect File Structure First (<5ms, 0 tokens)
19
+ Instead of dumping full files into context, call `inspect_outline`:
20
+ ```json
21
+ {
22
+ "file_path": "path/to/large-service.ts"
23
+ }
24
+ ```
25
+ This returns all class declarations, function signatures, and exported types with exact line numbers `[L120]`.
26
+
27
+ ### 2. Read Safe Protected Content
28
+ When reading files through MCP, call `read_file_safe`:
29
+ ```json
30
+ {
31
+ "file_path": "path/to/large-service.ts"
32
+ }
33
+ ```
34
+ - If the file is small (≤300 lines), full content is returned.
35
+ - If the file is large (>300 lines), an AST skeleton is returned with instructions.
36
+
37
+ ### 3. Read Only Targeted Slices
38
+ Once you identify the line numbers from the outline, read only the relevant function:
39
+ ```json
40
+ {
41
+ "file_path": "path/to/large-service.ts",
42
+ "start_line": 120,
43
+ "end_line": 160
44
+ }
45
+ ```
46
+
47
+ ### 4. Search Symbols with Structural Distillation
48
+ Use `grep_distilled` instead of raw regex searches to see structural context around matches without dumping hundreds of lines of implementation.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kevinbermudezc/context-guard",
3
- "version": "1.3.2",
3
+ "version": "1.5.0",
4
4
  "description": "Intelligent context admission control and token optimization for Claude Code, Cursor, Windsurf, Antigravity, and Agentic AI workflows.",
5
5
  "publishConfig": {
6
6
  "access": "public"