@antodevs/groundtruth 0.1.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.
- package/README.md +130 -0
- package/assets/banner.png +0 -0
- package/index.js +32 -0
- package/package.json +48 -0
- package/specification.yaml +143 -0
- package/src/cache.js +107 -0
- package/src/circuit-breaker.js +63 -0
- package/src/cli.js +58 -0
- package/src/env.js +120 -0
- package/src/http-agent.js +21 -0
- package/src/inject.js +93 -0
- package/src/logger.js +47 -0
- package/src/packages.js +87 -0
- package/src/proxy.js +164 -0
- package/src/search.js +157 -0
- package/src/state.js +37 -0
- package/src/utils/atomic-write.js +58 -0
- package/src/watcher.js +146 -0
package/README.md
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+

|
|
2
|
+
|
|
3
|
+
# GroundTruth
|
|
4
|
+
|
|
5
|
+
> Zero-configuration context injection layer for LLM-based coding agents.
|
|
6
|
+
|
|
7
|
+

|
|
8
|
+

|
|
9
|
+

|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Architecture Overview
|
|
14
|
+
|
|
15
|
+
Current-generation AI coding assistants (Claude Code, Antigravity, Cursor) suffer from deterministic knowledge cutoffs, rendering them ineffective when working with bleeding-edge frameworks (e.g., Svelte 5+, React 19).
|
|
16
|
+
|
|
17
|
+
**GroundTruth** acts as a transparent middleware layer that resolves this by dynamically injecting real-time, stack-specific documentation directly into the agent's context window prior to inference.
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Architecture & Operational Mechanics
|
|
22
|
+
|
|
23
|
+
GroundTruth operates in two distinct execution modes depending on the target agent's architecture.
|
|
24
|
+
|
|
25
|
+
### 1. Proxy Intercept Mode (Designed for `claude-code`)
|
|
26
|
+
|
|
27
|
+
In this mode, GroundTruth provisions a local HTTP proxy that intercepts outbound API calls targeting Anthropic's endpoints.
|
|
28
|
+
|
|
29
|
+
```mermaid
|
|
30
|
+
sequenceDiagram
|
|
31
|
+
participant Agent as Claude Code
|
|
32
|
+
participant Proxy as GroundTruth Proxy
|
|
33
|
+
participant Search as DuckDuckGo
|
|
34
|
+
participant API as Anthropic API
|
|
35
|
+
|
|
36
|
+
Agent->>Proxy: Send Prompt (POST /v1/messages)
|
|
37
|
+
Proxy->>Search: Extract stack/query & scrape live docs
|
|
38
|
+
Search-->>Proxy: Return real-time web context
|
|
39
|
+
Note over Proxy: Injects live context<br/>into System Prompt
|
|
40
|
+
Proxy->>API: Forward mutated request
|
|
41
|
+
API-->>Agent: Return completion with fresh knowledge
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
- **Query Extraction**: Parses the user prompt to identify context dependencies.
|
|
45
|
+
- **Data Hydration**: Orchestrates an automated DuckDuckGo search to fetch the most recent documentation. It relies on a deterministic `LRUCache`, TCP keep-alive Pool configurations, and a 429-aware `CircuitBreaker` pattern to safeguard network operations safely.
|
|
46
|
+
- **Payload Mutation**: Mutates the outgoing system prompt to inject the scraped live context before forwarding the request to the Anthropic completion endpoint. (It includes type-guard structures making it safe from undocumented Gemini system changes).
|
|
47
|
+
|
|
48
|
+
### 2. File Watcher Mode (Designed for `antigravity` / `gemini`)
|
|
49
|
+
|
|
50
|
+
For agents that support side-channel context ingestion via dotfiles (like Antigravity Rules), GroundTruth runs as a background daemon.
|
|
51
|
+
|
|
52
|
+
```mermaid
|
|
53
|
+
flowchart TD
|
|
54
|
+
pkg([package.json]) -->|Parse Dependencies| GT{GroundTruth Watcher}
|
|
55
|
+
GT -->|Search Stack Queries| DDG[(DuckDuckGo)]
|
|
56
|
+
DDG -->|Return Live Docs| GT
|
|
57
|
+
GT -->|Sync periodically| File[~/.gemini/GEMINI.md]
|
|
58
|
+
File -->|Auto-loaded| Agent(Antigravity Agent)
|
|
59
|
+
Agent -->|Execute| Prompt[Prompt with Fresh Context]
|
|
60
|
+
|
|
61
|
+
classDef core fill:#3B82F6,stroke:#fff,stroke-width:2px,color:#fff;
|
|
62
|
+
class GT,Agent core;
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
- **Stack Introspection**: Analyzes the local `package.json` to infer the project's dependency graph.
|
|
66
|
+
- **Intelligent Chunking**: Groups the filtered dependencies in configurable size batches (default 3) and uniquely hashes them to avoid redundant context-fetching loops unless changes are detected.
|
|
67
|
+
- **Automated Polling**: Periodically fetches updated documentation for the detected stack chunks in parallel.
|
|
68
|
+
- **State Persistence**: Hashes are serialized persistently avoiding redundant DuckDuckGo scraping operations across application crashes.
|
|
69
|
+
- **Block-Based Synchronization**: Writes the parsed context discretely into hash-oriented blocks inside `~/.gemini/GEMINI.md`. Native POSIX bindings are leveraged ensuring `Atomic Writes`. Stale contexts are efficiently garbage-collected via regex matching over tracked batch hashes.
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
## Installation & Usage
|
|
74
|
+
|
|
75
|
+
### Usage with Claude Code
|
|
76
|
+
```bash
|
|
77
|
+
# Initialize GroundTruth in proxy mode (auto-exports ANTHROPIC_BASE_URL)
|
|
78
|
+
npx groundtruth --claude-code
|
|
79
|
+
|
|
80
|
+
# Execute your agent in a separate TTY
|
|
81
|
+
claude
|
|
82
|
+
```
|
|
83
|
+
> **Note:** The daemon automatically mutates your shell environment (`~/.zshrc`, `~/.bashrc`, `config.fish`) to route traffic through the localhost proxy.
|
|
84
|
+
|
|
85
|
+
### Usage with Antigravity / Gemini
|
|
86
|
+
```bash
|
|
87
|
+
cd /workspace/your-project
|
|
88
|
+
|
|
89
|
+
# Initialize the daemon in file watcher mode
|
|
90
|
+
npx groundtruth --antigravity
|
|
91
|
+
```
|
|
92
|
+
> **Note:** GroundTruth will continuously poll and sync documentation based on your `package.json` manifest.
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
## CLI Reference
|
|
97
|
+
|
|
98
|
+
| Flag | Mode | Technical Description |
|
|
99
|
+
|------|------|-------------|
|
|
100
|
+
| `--claude-code` | Proxy | Initializes HTTP interceptor for Anthropic API payloads. |
|
|
101
|
+
| `--antigravity` | Rules | Initializes background daemon for dotfile synchronization. |
|
|
102
|
+
| `--use-package-json` | Both | Enforces AST/manifest parsing of `package.json` for query generation. |
|
|
103
|
+
| `--port <n>` | Proxy | Overrides default proxy listener port (Default: `8080`). |
|
|
104
|
+
| `--interval <n>` | Rules | Overrides the polling interval for documentation refresh in minutes (Default: `5`). |
|
|
105
|
+
| `--batch-size <n>` | Rules | Changes the amount of dependencies per query chunk for block fetching (Default: `3`, Min: `2`, Max: `5`). |
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
## Benchmark & Comparison
|
|
110
|
+
|
|
111
|
+
GroundTruth is heavily optimized for zero-configuration deployments and minimal token overhead compared to existing MCP (Model Context Protocol) solutions.
|
|
112
|
+
|
|
113
|
+
| Feature | GroundTruth | Brave MCP | Playwright MCP | Firecrawl |
|
|
114
|
+
|---------|-------------|-----------|----------------|-----------|
|
|
115
|
+
| **Authentication** | None Required | API Key | None Required | API Key |
|
|
116
|
+
| **Token Overhead** | ~500 tokens | ~800 tokens | ~13,000 tokens | ~800 tokens |
|
|
117
|
+
| **Antigravity Support** | Native | Unsupported | Unsupported | Unsupported |
|
|
118
|
+
| **Runtime Footprint** | < 1MB | < 1MB | ~200MB | < 1MB |
|
|
119
|
+
| **Shell Auto-config** | Automated | Manual | Manual | Manual |
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
## System Requirements
|
|
124
|
+
- Node.js runtime (v18.0.0 or higher)
|
|
125
|
+
- Supported Agent (Antigravity or Claude Code)
|
|
126
|
+
|
|
127
|
+
---
|
|
128
|
+
|
|
129
|
+
## License
|
|
130
|
+
MIT
|
|
Binary file
|
package/index.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* @module index
|
|
4
|
+
* @description Entry point runtime groundtruth delegazione CLI o proxy flow logic.
|
|
5
|
+
*/
|
|
6
|
+
import { chalk, label } from './src/logger.js';
|
|
7
|
+
import { usePackageJson, antigravityMode, claudeCodeMode, port, intervalMinutes, batchSize, version } from './src/cli.js';
|
|
8
|
+
import { createServer } from './src/proxy.js';
|
|
9
|
+
import { autoSetEnv } from './src/env.js';
|
|
10
|
+
import { startWatcher } from './src/watcher.js';
|
|
11
|
+
|
|
12
|
+
// ─── Dispatcher start app logic ──────────────────────
|
|
13
|
+
|
|
14
|
+
if (antigravityMode) {
|
|
15
|
+
startWatcher({ intervalMinutes, usePackageJson, batchSize });
|
|
16
|
+
} else if (claudeCodeMode) {
|
|
17
|
+
const server = await createServer(usePackageJson);
|
|
18
|
+
const startServer = (p) => {
|
|
19
|
+
// EADDRINUSE fallback listener fail chain ricorsivo su port shift param logic
|
|
20
|
+
server.on('error', (e) => (e.code === 'EADDRINUSE' ? startServer(p + 1) : console.error(chalk.red(`Server error: ${e.message}`))));
|
|
21
|
+
server.listen(p, async () => {
|
|
22
|
+
console.log(`\n ${chalk.white.bold('GroundTruth')} ${chalk.gray(`v${version}`)} ${chalk.gray('[claude-code mode]')}\n`);
|
|
23
|
+
console.log(label('◆', 'proxy', `localhost:${p}`));
|
|
24
|
+
console.log(label('◆', 'anthropic', '/v1/messages'));
|
|
25
|
+
console.log(label('◆', 'gemini', '/v1beta/…'));
|
|
26
|
+
console.log(label('◆', 'context', 'DuckDuckGo → live'));
|
|
27
|
+
console.log(`\n ${chalk.cyan('✻')} Listening. Set ANTHROPIC_BASE_URL=http://localhost:${p}\n`);
|
|
28
|
+
await autoSetEnv(p);
|
|
29
|
+
});
|
|
30
|
+
};
|
|
31
|
+
startServer(port);
|
|
32
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@antodevs/groundtruth",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Lightweight Node.js proxy to intercept API requests from coding agents and inject fresh web context",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"author": "Anto",
|
|
8
|
+
"keywords": [
|
|
9
|
+
"llm",
|
|
10
|
+
"context",
|
|
11
|
+
"ai",
|
|
12
|
+
"proxy",
|
|
13
|
+
"gemini",
|
|
14
|
+
"anthropic",
|
|
15
|
+
"claude",
|
|
16
|
+
"antigravity",
|
|
17
|
+
"coding-assistant"
|
|
18
|
+
],
|
|
19
|
+
"repository": {
|
|
20
|
+
"type": "git",
|
|
21
|
+
"url": "git+https://github.com/anto0102/GroundTruth.git"
|
|
22
|
+
},
|
|
23
|
+
"engines": {
|
|
24
|
+
"node": ">=18.0.0"
|
|
25
|
+
},
|
|
26
|
+
"files": [
|
|
27
|
+
"index.js",
|
|
28
|
+
"src/",
|
|
29
|
+
"assets/",
|
|
30
|
+
"specification.yaml",
|
|
31
|
+
"README.md",
|
|
32
|
+
"LICENSE"
|
|
33
|
+
],
|
|
34
|
+
"bin": {
|
|
35
|
+
"groundtruth": "index.js"
|
|
36
|
+
},
|
|
37
|
+
"scripts": {
|
|
38
|
+
"start": "node index.js",
|
|
39
|
+
"dev": "node --watch index.js"
|
|
40
|
+
},
|
|
41
|
+
"dependencies": {
|
|
42
|
+
"@mozilla/readability": "^0.5.0",
|
|
43
|
+
"chalk": "^5.3.0",
|
|
44
|
+
"cheerio": "^1.0.0",
|
|
45
|
+
"linkedom": "^0.18.12",
|
|
46
|
+
"node-fetch": "^3.3.2"
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
name: GroundTruth
|
|
2
|
+
version: 0.1.0
|
|
3
|
+
description: |
|
|
4
|
+
GroundTruth is a zero-configuration, transparent middleware context injection layer.
|
|
5
|
+
It is designed to bridge the deterministic knowledge cutoff gap of LLM-based coding agents
|
|
6
|
+
(such as Claude Code or Antigravity) by dynamically fetching and injecting live,
|
|
7
|
+
dependency-specific documentation right before inference or via out-of-band rule files.
|
|
8
|
+
|
|
9
|
+
architecture:
|
|
10
|
+
modes:
|
|
11
|
+
- name: Proxy Intercept Mode
|
|
12
|
+
alias: claude-code
|
|
13
|
+
description: |
|
|
14
|
+
Operates as a local HTTP reverse-proxy interceptor that captures outgoing API payloads
|
|
15
|
+
targeting Anthropic or Google Gemini endpoints, mutating them in transit.
|
|
16
|
+
target_endpoints:
|
|
17
|
+
- "https://api.anthropic.com/v1/messages"
|
|
18
|
+
- "https://generativelanguage.googleapis.com/v1beta/models/*"
|
|
19
|
+
flow:
|
|
20
|
+
1: "Listen on localhost port (default: 8080) and capture POST requests."
|
|
21
|
+
2: "Extract the last user message from the JSON body (supports `messages` array for Anthropic and `contents` array for Gemini)."
|
|
22
|
+
3: "Determine search query: use `--use-package-json` AST parsing, or fallback to the user message text."
|
|
23
|
+
4: "Scrape DuckDuckGo concurrently to retrieve live context (title, snippet, Readability-parsed text up to 4000 chars)."
|
|
24
|
+
5: "Mutate the `system` instruction prompt in the JSON payload by appending the live context block."
|
|
25
|
+
6: "Forward the modified request to the actual LLM provider, streaming the response back to the client."
|
|
26
|
+
components:
|
|
27
|
+
- src/proxy.js
|
|
28
|
+
|
|
29
|
+
- name: File Watcher Mode
|
|
30
|
+
alias: antigravity
|
|
31
|
+
description: |
|
|
32
|
+
Runs as a persistent background daemon. It polls the local project's dependencies,
|
|
33
|
+
fetches up-to-date documentation, and generates synchronized knowledge base dotfiles
|
|
34
|
+
(`GEMINI.md`) that the agent natively reads on invocation.
|
|
35
|
+
flow:
|
|
36
|
+
1: "Parses `package.json` dependencies and filters out build/tooling packages (e.g., eslint, vite, adapter)."
|
|
37
|
+
2: "Groups the filtered dependencies into chunks (batching) of configurable size (default: 3, max: 5) using `groupIntoBatches`."
|
|
38
|
+
3: "Hashes each dependency chunk (`batchHash` md5 sliced to 8 chars) to uniquely identify rule blocks and track state."
|
|
39
|
+
4: "Checks previous state to avoid redundant network fetches if the batch hasn't changed (`previousDepsKey` mapping)."
|
|
40
|
+
5: "Fetches live DuckDuckGo results per batch asynchronously, filtering out low-quality pages (403, captcha, < 200 chars)."
|
|
41
|
+
6: "Injects distinct dependency rule blocks bounded by `<!-- groundtruth:block-{hash}:start/end -->` directly inside `~/.gemini/GEMINI.md` (global) and `./.gemini/GEMINI.md` (workspace)."
|
|
42
|
+
7: "Garbage-collects stale blocks (`removeStaleBlocks`) belonging to evicted or resolved dependencies by regex matching active block IDs."
|
|
43
|
+
components:
|
|
44
|
+
- src/watcher.js
|
|
45
|
+
- src/inject.js
|
|
46
|
+
|
|
47
|
+
core_modules:
|
|
48
|
+
- name: cli.js
|
|
49
|
+
responsibilities:
|
|
50
|
+
- "Process `process.argv` argument parsing."
|
|
51
|
+
- "Validation and defaulting of arguments (`--port`, `--interval`, `--batch-size`)."
|
|
52
|
+
- "Help/Docs Screen rendering and early exit conditions."
|
|
53
|
+
|
|
54
|
+
- name: search.js
|
|
55
|
+
responsibilities:
|
|
56
|
+
- "DuckDuckGo HTML scraping using `cheerio`."
|
|
57
|
+
- "URL resolution from DuckDuckGo's `uddg` tracking links."
|
|
58
|
+
- "User-Agent rotation to mitigate scraping blocks."
|
|
59
|
+
- "Integration with `CircuitBreaker` pattern for rate-limit protection."
|
|
60
|
+
- "Integration with bounded custom O(1) `LRUCache` from `cache.js`."
|
|
61
|
+
- "Page content extraction using `linkedom` and Mozilla's `Readability`."
|
|
62
|
+
- "Integration with persistent connection pooling components from `http-agent.js`."
|
|
63
|
+
|
|
64
|
+
- name: packages.js
|
|
65
|
+
responsibilities:
|
|
66
|
+
- "Read local Node modules context (`package.json`)."
|
|
67
|
+
- "Clean semantic versions (e.g., `^1.2.3` -> `1.2`)."
|
|
68
|
+
- "Filter out non-informative tooling (`vite`, `prettier`, `eslint`)."
|
|
69
|
+
- "Group dependencies into manageable batches (`groupIntoBatches`)."
|
|
70
|
+
- "Generate deterministic MD5 identifiers per batch for block management (`batchHash`)."
|
|
71
|
+
- "Construct search queries based on dependency batches plus temporal identifiers (`latest 2026`)."
|
|
72
|
+
|
|
73
|
+
- name: logger.js
|
|
74
|
+
responsibilities:
|
|
75
|
+
- "Chalk-driven aesthetic terminal formatting."
|
|
76
|
+
- "Centralized status symbolizing constants (✓, ⚠, ⚡, ↻, ◆, ✻)."
|
|
77
|
+
- "Timestamp generation mapped to `it-IT` locale."
|
|
78
|
+
|
|
79
|
+
- name: env.js
|
|
80
|
+
responsibilities:
|
|
81
|
+
- "Shell configuration auto-instrumentation (`.zshrc`, `.bashrc`, `.bash_profile`, `config.fish`)."
|
|
82
|
+
- "Exporting `ANTHROPIC_BASE_URL` to route CLI tools (like Claude Code) through the proxy."
|
|
83
|
+
- "Cross-Platform Environment Override (Bypassing Windows systems safely)."
|
|
84
|
+
|
|
85
|
+
- name: inject.js
|
|
86
|
+
responsibilities:
|
|
87
|
+
- "File I/O operations for `GEMINI.md` in both `$HOME` and `$CWD`."
|
|
88
|
+
- "Regex-based block injection using exact start/end bounds matching."
|
|
89
|
+
- "Stale block eviction via `removeStaleBlocks`."
|
|
90
|
+
- "Uses `atomicWrite` for zero-corruption file replacements."
|
|
91
|
+
|
|
92
|
+
- name: cache.js
|
|
93
|
+
responsibilities:
|
|
94
|
+
- "Implements zero-dependency O(1) bounded LRU caching logic."
|
|
95
|
+
- "Provides getter/setter mechanisms tied to temporal eviction limits."
|
|
96
|
+
|
|
97
|
+
- name: circuit-breaker.js
|
|
98
|
+
responsibilities:
|
|
99
|
+
- "Manages DuckDuckGo fetch attempts via threshold-based error state wrapping (OPEN/HALF_OPEN/CLOSED)."
|
|
100
|
+
|
|
101
|
+
- name: state.js
|
|
102
|
+
responsibilities:
|
|
103
|
+
- "Persistent recovery system for dependency batch hashes mapping across system crash/restarts."
|
|
104
|
+
- "Reads and writes `.gemini/watcher-state.json`."
|
|
105
|
+
|
|
106
|
+
- name: http-agent.js
|
|
107
|
+
responsibilities:
|
|
108
|
+
- "Creates reusable Keep-Alive HTTP and HTTPS configuration agents to mitigate handshake overheads."
|
|
109
|
+
|
|
110
|
+
- name: utils/atomic-write.js
|
|
111
|
+
responsibilities:
|
|
112
|
+
- "Creates temporary file blocks performing `fs.rename` (POSIX) or falls back to standard `fs.copyFile` safe-copies (Windows)."
|
|
113
|
+
|
|
114
|
+
dependencies:
|
|
115
|
+
runtime: "Node.js >= 18.0.0 (uses ES Modules)"
|
|
116
|
+
built_ins:
|
|
117
|
+
- fs
|
|
118
|
+
- path
|
|
119
|
+
- os
|
|
120
|
+
- http
|
|
121
|
+
- https
|
|
122
|
+
- crypto
|
|
123
|
+
third_party:
|
|
124
|
+
- chalk: "^5.3.0" # Terminal styling
|
|
125
|
+
- cheerio: "^1.0.0" # Fast HTML parsing for DDG results
|
|
126
|
+
- linkedom: "^0.18.5" # Lightweight DOM emulation for Mozilla Readability
|
|
127
|
+
- node-fetch: "^3.3.2" # WHATWG Fetch API polyfill for Node
|
|
128
|
+
- "@mozilla/readability": "^0.5.0" # Main content extraction
|
|
129
|
+
|
|
130
|
+
mechanics:
|
|
131
|
+
caching_and_eviction:
|
|
132
|
+
- "Search level caching: Runtime searches are cached for 5 minutes (`CACHE_TTL`), matching identical queries to avoid redundant network transit."
|
|
133
|
+
- "Watcher level caching: the daemon uses a Map tracking `blockId` -> `JSON.stringify(batch)`. If the hash signature matches across cycles, the network layer is skipped."
|
|
134
|
+
quality_assurance:
|
|
135
|
+
- "Content verification: Extracted text is sanitized and evaluated. If a page returns < 200 characters, or contains indicators of bot protection (e.g., '403', 'captcha', 'access denied'), the result is flagged."
|
|
136
|
+
- "Fallback mechanism: If a result is flagged as low-quality, the watcher rolls back and retains the successfully injected markdown block from the previous cycle."
|
|
137
|
+
network_resilience:
|
|
138
|
+
- "Timeouts: All outbound `node-fetch` requests strictly adhere to a 5-second `AbortSignal.timeout(5000)`."
|
|
139
|
+
- "Retries & Bans: `search.js` relies on a `CircuitBreaker` class mitigating recursive DuckDuckGo IP bans."
|
|
140
|
+
- "Resource Connection: Avoids TCP handshakes through persistent keep-alive Agent dispatching."
|
|
141
|
+
shell_integration:
|
|
142
|
+
- "Darwin/Linux-first: Windows OS (`win32`) skips autoconfig cleanly."
|
|
143
|
+
- "Fish Shell paths uniquely utilize `set -gx` constructs unlike standard Bash/Zsh `export` syntax, appending recursively or mutating existing assignments."
|
package/src/cache.js
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module cache
|
|
3
|
+
* @description Implementazione zero-dep LRU Cache limitata memory bounds.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @description Store memory con eviction LRU O(1) in get/set.
|
|
8
|
+
*/
|
|
9
|
+
class LRUCache {
|
|
10
|
+
/**
|
|
11
|
+
* @param {Object} options - Impostazioni max size e ttl ms
|
|
12
|
+
*/
|
|
13
|
+
constructor(options = {}) {
|
|
14
|
+
this.max = options.max || 500;
|
|
15
|
+
this.ttl = options.ttl || 5 * 60 * 1000; // 5 min default TTL
|
|
16
|
+
this.cache = new Map();
|
|
17
|
+
// Doubly-linked list base structure per LRU policy cost O(1)
|
|
18
|
+
this.head = { key: null, next: null, prev: null };
|
|
19
|
+
this.tail = { key: null, next: null, prev: null };
|
|
20
|
+
this.head.next = this.tail;
|
|
21
|
+
this.tail.prev = this.head;
|
|
22
|
+
this.size = 0;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
_remove(node) {
|
|
26
|
+
node.prev.next = node.next;
|
|
27
|
+
node.next.prev = node.prev;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
_addToFront(node) {
|
|
31
|
+
node.next = this.head.next;
|
|
32
|
+
node.prev = this.head;
|
|
33
|
+
this.head.next.prev = node;
|
|
34
|
+
this.head.next = node;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* @description Fetch nodo con eviction passiva su scadenza TTL.
|
|
39
|
+
* @param {string} key - Chiave archivio identificativa
|
|
40
|
+
* @returns {any} Valore salvato o undefined su miss
|
|
41
|
+
*/
|
|
42
|
+
get(key) {
|
|
43
|
+
const node = this.cache.get(key);
|
|
44
|
+
if (!node) return undefined;
|
|
45
|
+
|
|
46
|
+
// TTL lazy evict check se expiro
|
|
47
|
+
if (Date.now() > node.expiresAt) {
|
|
48
|
+
this.delete(key);
|
|
49
|
+
return undefined;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Promote a recency target node
|
|
53
|
+
this._remove(node);
|
|
54
|
+
this._addToFront(node);
|
|
55
|
+
return node.value;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* @description Assegna un nodo alla testa (recent).
|
|
60
|
+
* @param {string} key - Target key
|
|
61
|
+
* @param {any} value - Target val
|
|
62
|
+
* @returns {LRUCache} Istanza chaining
|
|
63
|
+
*/
|
|
64
|
+
set(key, value) {
|
|
65
|
+
// Pulizia pre-insert per sovrascrittura o cap bounds
|
|
66
|
+
if (this.cache.has(key)) {
|
|
67
|
+
const old = this.cache.get(key);
|
|
68
|
+
this._remove(old);
|
|
69
|
+
this.cache.delete(key);
|
|
70
|
+
this.size--;
|
|
71
|
+
} else if (this.size >= this.max) {
|
|
72
|
+
const lru = this.tail.prev;
|
|
73
|
+
if (lru.key) {
|
|
74
|
+
this._remove(lru);
|
|
75
|
+
this.cache.delete(lru.key);
|
|
76
|
+
this.size--;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const node = {
|
|
81
|
+
key,
|
|
82
|
+
value,
|
|
83
|
+
expiresAt: Date.now() + this.ttl,
|
|
84
|
+
next: null,
|
|
85
|
+
prev: null
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
this.cache.set(key, node);
|
|
89
|
+
this._addToFront(node);
|
|
90
|
+
this.size++;
|
|
91
|
+
return this;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
delete(key) {
|
|
95
|
+
const node = this.cache.get(key);
|
|
96
|
+
if (node) {
|
|
97
|
+
this._remove(node);
|
|
98
|
+
this.cache.delete(key);
|
|
99
|
+
this.size--;
|
|
100
|
+
return true;
|
|
101
|
+
}
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Esporta singola cache istanza module globale (singleton per memory context node)
|
|
107
|
+
export const searchCache = new LRUCache({ max: 500, ttl: 5 * 60 * 1000 });
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module circuit-breaker
|
|
3
|
+
* @description Fail-fast strategy su DDG in caso di cap HTTP bloccati IP.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export class CircuitBreaker {
|
|
7
|
+
/**
|
|
8
|
+
* @param {Object} options Imposta le policies di fault limit e timeout windows.
|
|
9
|
+
*/
|
|
10
|
+
constructor(options = {}) {
|
|
11
|
+
this.failureThreshold = options.failureThreshold || 5;
|
|
12
|
+
this.resetTimeout = options.resetTimeout || 60000;
|
|
13
|
+
this.state = 'CLOSED'; // CLOSED, OPEN, HALF_OPEN
|
|
14
|
+
this.failures = 0;
|
|
15
|
+
this.lastFailureTime = null;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @description Esegue logica async proteggendola da cascading failure DDG
|
|
20
|
+
* @param {Function} fn - Ritorna Promise async operation DDG fetch
|
|
21
|
+
* @returns {Promise<any>} Risultato esecuzione
|
|
22
|
+
* @throws {Error} Fallimento execution o cb open rejection error
|
|
23
|
+
*/
|
|
24
|
+
async execute(fn) {
|
|
25
|
+
if (this.state === 'OPEN') {
|
|
26
|
+
if (Date.now() - this.lastFailureTime < this.resetTimeout) {
|
|
27
|
+
throw new Error('Circuit breaker is OPEN');
|
|
28
|
+
}
|
|
29
|
+
// Tentativo check dopo finestra timeout
|
|
30
|
+
this.state = 'HALF_OPEN';
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
const result = await fn();
|
|
35
|
+
this.onSuccess();
|
|
36
|
+
return result;
|
|
37
|
+
} catch (err) {
|
|
38
|
+
this.onFailure();
|
|
39
|
+
throw err;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
onSuccess() {
|
|
44
|
+
if (this.state === 'HALF_OPEN') {
|
|
45
|
+
this.halfOpenSuccesses = (this.halfOpenSuccesses || 0) + 1;
|
|
46
|
+
if (this.halfOpenSuccesses >= 2) {
|
|
47
|
+
this.failures = 0;
|
|
48
|
+
this.state = 'CLOSED';
|
|
49
|
+
this.halfOpenSuccesses = 0;
|
|
50
|
+
}
|
|
51
|
+
} else {
|
|
52
|
+
this.failures = 0;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
onFailure() {
|
|
57
|
+
this.failures++;
|
|
58
|
+
this.lastFailureTime = Date.now();
|
|
59
|
+
if (this.failures >= this.failureThreshold) {
|
|
60
|
+
this.state = 'OPEN';
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
package/src/cli.js
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module cli
|
|
3
|
+
* @description Parsing degli argomenti CLI process.argv e logica help screen.
|
|
4
|
+
*/
|
|
5
|
+
import { chalk } from './logger.js';
|
|
6
|
+
import { createRequire } from 'module';
|
|
7
|
+
|
|
8
|
+
const { version } = createRequire(import.meta.url)('../package.json');
|
|
9
|
+
|
|
10
|
+
// ─── Arg Parsers ─────────────────────────────────────
|
|
11
|
+
|
|
12
|
+
const args = process.argv.slice(2);
|
|
13
|
+
const usePackageJson = args.includes('--use-package-json');
|
|
14
|
+
const antigravityMode = args.includes('--antigravity');
|
|
15
|
+
const claudeCodeMode = args.includes('--claude-code');
|
|
16
|
+
|
|
17
|
+
// Stop immediato se nessun mode definito
|
|
18
|
+
if (!antigravityMode && !claudeCodeMode) {
|
|
19
|
+
console.log();
|
|
20
|
+
console.log(` ${chalk.white.bold('GroundTruth')} ${chalk.gray(`v${version}`)}`);
|
|
21
|
+
console.log();
|
|
22
|
+
console.log(` Usage:`);
|
|
23
|
+
console.log(` groundtruth --claude-code proxy mode (Claude Code)`);
|
|
24
|
+
console.log(` groundtruth --antigravity rules mode (Antigravity/Gemini)`);
|
|
25
|
+
console.log();
|
|
26
|
+
console.log(` Options:`);
|
|
27
|
+
console.log(` --use-package-json use package.json as search query`);
|
|
28
|
+
console.log(` --port <n> custom port, default 8080 (claude-code only)`);
|
|
29
|
+
console.log(` --interval <n> refresh in minutes, default 5 (antigravity only)`);
|
|
30
|
+
console.log(` --batch-size <n> deps per search batch (default: 3)`);
|
|
31
|
+
console.log();
|
|
32
|
+
console.log(` Docs:`);
|
|
33
|
+
console.log(` Claude Code → export ANTHROPIC_BASE_URL=http://localhost:8080`);
|
|
34
|
+
console.log(` Antigravity → just run groundtruth --antigravity in your project`);
|
|
35
|
+
console.log();
|
|
36
|
+
process.exit(0);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// ─── Default params override ─────────────────────────
|
|
40
|
+
|
|
41
|
+
let port = 8080; // Default Anthropic proxy
|
|
42
|
+
const portArgIndex = args.indexOf('--port');
|
|
43
|
+
if (portArgIndex !== -1 && args[portArgIndex + 1]) {
|
|
44
|
+
port = parseInt(args[portArgIndex + 1], 10);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
let intervalMinutes = 5; // Default context refresh
|
|
48
|
+
const intervalArgIndex = args.indexOf('--interval');
|
|
49
|
+
if (intervalArgIndex !== -1 && args[intervalArgIndex + 1]) {
|
|
50
|
+
intervalMinutes = parseInt(args[intervalArgIndex + 1], 10) || 5;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const batchSizeIndex = args.indexOf('--batch-size');
|
|
54
|
+
const batchSize = batchSizeIndex !== -1
|
|
55
|
+
? Math.max(2, Math.min(parseInt(args[batchSizeIndex + 1]) || 3, 5))
|
|
56
|
+
: 3;
|
|
57
|
+
|
|
58
|
+
export { args, usePackageJson, antigravityMode, claudeCodeMode, port, intervalMinutes, batchSize, version };
|