@antodevs/groundtruth 0.1.4 → 0.1.5
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 +1 -1
- package/assets/banner.svg +106 -0
- package/index.js +7 -3
- package/package.json +1 -1
- package/specification.yaml +1 -1
- package/src/circuit-breaker.js +8 -3
- package/src/cli.js +4 -2
- package/src/env.js +40 -0
- package/src/inject.js +1 -0
- package/src/packages.js +3 -1
- package/src/proxy.js +3 -2
- package/src/sanitize.js +35 -0
- package/src/search.js +3 -2
- package/src/state.js +3 -2
- package/src/watcher.js +4 -3
- package/assets/banner.png +0 -0
package/README.md
CHANGED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1200 300" width="100%">
|
|
2
|
+
<defs>
|
|
3
|
+
<!-- Background Gradient -->
|
|
4
|
+
<linearGradient id="bg" x1="0%" y1="0%" x2="100%" y2="100%">
|
|
5
|
+
<stop offset="0%" stop-color="#090a0f" />
|
|
6
|
+
<stop offset="50%" stop-color="#121521" />
|
|
7
|
+
<stop offset="100%" stop-color="#040508" />
|
|
8
|
+
</linearGradient>
|
|
9
|
+
|
|
10
|
+
<!-- Glowing Text Gradient -->
|
|
11
|
+
<linearGradient id="textGlow" x1="0%" y1="0%" x2="100%" y2="0%">
|
|
12
|
+
<stop offset="0%" stop-color="#4F46E5" />
|
|
13
|
+
<stop offset="50%" stop-color="#0ea5e9" />
|
|
14
|
+
<stop offset="100%" stop-color="#8b5cf6" />
|
|
15
|
+
</linearGradient>
|
|
16
|
+
|
|
17
|
+
<!-- Node Gradient -->
|
|
18
|
+
<linearGradient id="nodeGrad" x1="0%" y1="0%" x2="100%" y2="100%">
|
|
19
|
+
<stop offset="0%" stop-color="#38bdf8" />
|
|
20
|
+
<stop offset="100%" stop-color="#818cf8" />
|
|
21
|
+
</linearGradient>
|
|
22
|
+
|
|
23
|
+
<!-- Grid Pattern -->
|
|
24
|
+
<pattern id="grid" width="40" height="40" patternUnits="userSpaceOnUse">
|
|
25
|
+
<path d="M 40 0 L 0 0 0 40" fill="none" stroke="rgba(255, 255, 255, 0.03)" stroke-width="1"/>
|
|
26
|
+
</pattern>
|
|
27
|
+
|
|
28
|
+
<filter id="glow" x="-20%" y="-20%" width="140%" height="140%">
|
|
29
|
+
<feGaussianBlur stdDeviation="6" result="blur" />
|
|
30
|
+
<feMerge>
|
|
31
|
+
<feMergeNode in="blur" />
|
|
32
|
+
<feMergeNode in="SourceGraphic" />
|
|
33
|
+
</feMerge>
|
|
34
|
+
</filter>
|
|
35
|
+
</defs>
|
|
36
|
+
|
|
37
|
+
<!-- Deep Dark Solid Background -->
|
|
38
|
+
<rect width="100%" height="100%" fill="url(#bg)" />
|
|
39
|
+
|
|
40
|
+
<!-- Overlay Subtle Grid -->
|
|
41
|
+
<rect width="100%" height="100%" fill="url(#grid)" />
|
|
42
|
+
|
|
43
|
+
<!-- Connecting Lines representing Context Injection (Left) -->
|
|
44
|
+
<g opacity="0.6" stroke-width="2" fill="none" filter="url(#glow)">
|
|
45
|
+
<!-- Primary flow -->
|
|
46
|
+
<path d="M0 150 C 200 150, 300 100, 450 150" stroke="#0ea5e9" opacity="0.8"/>
|
|
47
|
+
<path d="M0 80 C 150 80, 250 150, 400 130" stroke="#4F46E5" opacity="0.5"/>
|
|
48
|
+
<path d="M0 220 C 180 220, 280 150, 420 170" stroke="#8b5cf6" opacity="0.5"/>
|
|
49
|
+
|
|
50
|
+
<!-- Minor branches -->
|
|
51
|
+
<path d="M100 80 L 150 110" stroke="#0ea5e9"/>
|
|
52
|
+
<path d="M200 220 L 250 185" stroke="#8b5cf6"/>
|
|
53
|
+
<path d="M300 100 L 320 130" stroke="#4F46E5"/>
|
|
54
|
+
</g>
|
|
55
|
+
|
|
56
|
+
<!-- Connecting Lines representing Context Injection (Right) -->
|
|
57
|
+
<g opacity="0.6" stroke-width="2" fill="none" filter="url(#glow)">
|
|
58
|
+
<!-- Primary flow -->
|
|
59
|
+
<path d="M1200 150 C 1000 150, 900 200, 750 150" stroke="#0ea5e9" opacity="0.8"/>
|
|
60
|
+
<path d="M1200 80 C 1050 80, 950 150, 800 130" stroke="#8b5cf6" opacity="0.5"/>
|
|
61
|
+
<path d="M1200 220 C 1020 220, 920 150, 780 170" stroke="#4F46E5" opacity="0.5"/>
|
|
62
|
+
|
|
63
|
+
<!-- Minor branches -->
|
|
64
|
+
<path d="M1100 80 L 1050 110" stroke="#0ea5e9"/>
|
|
65
|
+
<path d="M1000 220 L 950 185" stroke="#4F46E5"/>
|
|
66
|
+
<path d="M900 200 L 880 170" stroke="#8b5cf6"/>
|
|
67
|
+
</g>
|
|
68
|
+
|
|
69
|
+
<!-- Data Nodes (Left) -->
|
|
70
|
+
<g fill="url(#nodeGrad)" filter="url(#glow)">
|
|
71
|
+
<circle cx="100" cy="80" r="5" />
|
|
72
|
+
<circle cx="150" cy="110" r="4" />
|
|
73
|
+
<circle cx="200" cy="220" r="6" />
|
|
74
|
+
<circle cx="250" cy="185" r="4" />
|
|
75
|
+
<circle cx="300" cy="100" r="7" />
|
|
76
|
+
<circle cx="320" cy="130" r="3" />
|
|
77
|
+
<circle cx="450" cy="150" r="8" fill="#ffffff"/>
|
|
78
|
+
</g>
|
|
79
|
+
|
|
80
|
+
<!-- Data Nodes (Right) -->
|
|
81
|
+
<g fill="url(#nodeGrad)" filter="url(#glow)">
|
|
82
|
+
<circle cx="1100" cy="80" r="5" />
|
|
83
|
+
<circle cx="1050" cy="110" r="4" />
|
|
84
|
+
<circle cx="1000" cy="220" r="6" />
|
|
85
|
+
<circle cx="950" cy="185" r="4" />
|
|
86
|
+
<circle cx="900" cy="200" r="7" />
|
|
87
|
+
<circle cx="880" cy="170" r="3" />
|
|
88
|
+
<circle cx="750" cy="150" r="8" fill="#ffffff"/>
|
|
89
|
+
</g>
|
|
90
|
+
|
|
91
|
+
<!-- Typography -->
|
|
92
|
+
<text x="50%" y="48%" text-anchor="middle" font-family="-apple-system, system-ui, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif" font-size="94" font-weight="900" fill="url(#textGlow)" filter="url(#glow)" letter-spacing="2">
|
|
93
|
+
GROUNDTRUTH
|
|
94
|
+
</text>
|
|
95
|
+
|
|
96
|
+
<text x="50%" y="48%" text-anchor="middle" font-family="-apple-system, system-ui, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif" font-size="94" font-weight="900" fill="#ffffff" letter-spacing="2">
|
|
97
|
+
GROUNDTRUTH
|
|
98
|
+
</text>
|
|
99
|
+
|
|
100
|
+
<text x="50%" y="68%" text-anchor="middle" font-family="-apple-system, system-ui, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif" font-size="28" font-weight="500" fill="#94a3b8" letter-spacing="4">
|
|
101
|
+
ZERO ZERO-CONFIGURATION CONTEXT INJECTION
|
|
102
|
+
</text>
|
|
103
|
+
|
|
104
|
+
<rect x="350" y="72%" width="500" height="2" fill="rgba(255,255,255,0.1)" />
|
|
105
|
+
<rect x="450" y="72%" width="300" height="2" fill="#0ea5e9" filter="url(#glow)" />
|
|
106
|
+
</svg>
|
package/index.js
CHANGED
|
@@ -4,14 +4,18 @@
|
|
|
4
4
|
* @description Entry point runtime groundtruth delegazione CLI o proxy flow logic.
|
|
5
5
|
*/
|
|
6
6
|
import { chalk, label } from './src/logger.js';
|
|
7
|
-
import { usePackageJson, antigravityMode, claudeCodeMode, port, intervalMinutes, batchSize, version } from './src/cli.js';
|
|
7
|
+
import { usePackageJson, antigravityMode, claudeCodeMode, uninstallMode, port, intervalMinutes, batchSize, version } from './src/cli.js';
|
|
8
8
|
import { createServer } from './src/proxy.js';
|
|
9
|
-
import { autoSetEnv } from './src/env.js';
|
|
9
|
+
import { autoSetEnv, removeEnv } from './src/env.js';
|
|
10
10
|
import { startWatcher } from './src/watcher.js';
|
|
11
11
|
|
|
12
12
|
// ─── Dispatcher start app logic ──────────────────────
|
|
13
13
|
|
|
14
|
-
if (
|
|
14
|
+
if (uninstallMode) {
|
|
15
|
+
console.log(`\n ${chalk.white.bold('GroundTruth')} ${chalk.gray(`v${version}`)} ${chalk.gray('[uninstall]')}\n`);
|
|
16
|
+
await removeEnv();
|
|
17
|
+
process.exit(0);
|
|
18
|
+
} else if (antigravityMode) {
|
|
15
19
|
startWatcher({ intervalMinutes, usePackageJson, batchSize });
|
|
16
20
|
} else if (claudeCodeMode) {
|
|
17
21
|
const server = await createServer(usePackageJson);
|
package/package.json
CHANGED
package/specification.yaml
CHANGED
package/src/circuit-breaker.js
CHANGED
|
@@ -35,7 +35,7 @@ export class CircuitBreaker {
|
|
|
35
35
|
this.onSuccess();
|
|
36
36
|
return result;
|
|
37
37
|
} catch (err) {
|
|
38
|
-
this.onFailure();
|
|
38
|
+
this.onFailure(err);
|
|
39
39
|
throw err;
|
|
40
40
|
}
|
|
41
41
|
}
|
|
@@ -53,8 +53,13 @@ export class CircuitBreaker {
|
|
|
53
53
|
}
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
-
onFailure() {
|
|
57
|
-
|
|
56
|
+
onFailure(err) {
|
|
57
|
+
// 429 rate limit apre il circuito immediatamente
|
|
58
|
+
if (err?.message?.includes('429')) {
|
|
59
|
+
this.failures = this.failureThreshold;
|
|
60
|
+
} else {
|
|
61
|
+
this.failures++;
|
|
62
|
+
}
|
|
58
63
|
this.lastFailureTime = Date.now();
|
|
59
64
|
if (this.failures >= this.failureThreshold) {
|
|
60
65
|
this.state = 'OPEN';
|
package/src/cli.js
CHANGED
|
@@ -13,15 +13,17 @@ const args = process.argv.slice(2);
|
|
|
13
13
|
const usePackageJson = args.includes('--use-package-json');
|
|
14
14
|
const antigravityMode = args.includes('--antigravity');
|
|
15
15
|
const claudeCodeMode = args.includes('--claude-code');
|
|
16
|
+
const uninstallMode = args.includes('--uninstall');
|
|
16
17
|
|
|
17
18
|
// Stop immediato se nessun mode definito
|
|
18
|
-
if (!antigravityMode && !claudeCodeMode) {
|
|
19
|
+
if (!antigravityMode && !claudeCodeMode && !uninstallMode) {
|
|
19
20
|
console.log();
|
|
20
21
|
console.log(` ${chalk.white.bold('GroundTruth')} ${chalk.gray(`v${version}`)}`);
|
|
21
22
|
console.log();
|
|
22
23
|
console.log(` Usage:`);
|
|
23
24
|
console.log(` groundtruth --claude-code proxy mode (Claude Code)`);
|
|
24
25
|
console.log(` groundtruth --antigravity rules mode (Antigravity/Gemini)`);
|
|
26
|
+
console.log(` groundtruth --uninstall remove shell env config`);
|
|
25
27
|
console.log();
|
|
26
28
|
console.log(` Options:`);
|
|
27
29
|
console.log(` --use-package-json use package.json as search query`);
|
|
@@ -55,4 +57,4 @@ const batchSize = batchSizeIndex !== -1
|
|
|
55
57
|
? Math.max(2, Math.min(parseInt(args[batchSizeIndex + 1]) || 3, 5))
|
|
56
58
|
: 3;
|
|
57
59
|
|
|
58
|
-
export { args, usePackageJson, antigravityMode, claudeCodeMode, port, intervalMinutes, batchSize, version };
|
|
60
|
+
export { args, usePackageJson, antigravityMode, claudeCodeMode, uninstallMode, port, intervalMinutes, batchSize, version };
|
package/src/env.js
CHANGED
|
@@ -118,3 +118,43 @@ export async function autoSetEnv(p) {
|
|
|
118
118
|
log(LOG_WARN, chalk.yellow, chalk.white('env setup error') + ` → ${chalk.yellow(err.message)}`);
|
|
119
119
|
}
|
|
120
120
|
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* @description Rimuove ANTHROPIC_BASE_URL da tutti i file di configurazione shell.
|
|
124
|
+
* @returns {Promise<void>}
|
|
125
|
+
*/
|
|
126
|
+
export async function removeEnv() {
|
|
127
|
+
const homeDir = os.homedir();
|
|
128
|
+
const targets = [
|
|
129
|
+
{ file: path.join(homeDir, '.zshrc'), pattern: /^export ANTHROPIC_BASE_URL=.*\n?/gm },
|
|
130
|
+
{ file: path.join(homeDir, '.bashrc'), pattern: /^export ANTHROPIC_BASE_URL=.*\n?/gm },
|
|
131
|
+
{ file: path.join(homeDir, '.bash_profile'), pattern: /^export ANTHROPIC_BASE_URL=.*\n?/gm },
|
|
132
|
+
{ file: path.join(homeDir, '.profile'), pattern: /^export ANTHROPIC_BASE_URL=.*\n?/gm },
|
|
133
|
+
{ file: path.join(homeDir, '.config', 'fish', 'config.fish'), pattern: /^set -gx ANTHROPIC_BASE_URL .*\n?/gm },
|
|
134
|
+
];
|
|
135
|
+
|
|
136
|
+
let cleaned = 0;
|
|
137
|
+
for (const t of targets) {
|
|
138
|
+
if (!existsSync(t.file)) continue;
|
|
139
|
+
try {
|
|
140
|
+
const content = await fs.readFile(t.file, 'utf8');
|
|
141
|
+
const result = content.replace(t.pattern, '').replace(/\n{3,}/g, '\n\n');
|
|
142
|
+
if (result !== content) {
|
|
143
|
+
await atomicWrite(t.file, result);
|
|
144
|
+
const rel = t.file.replace(homeDir, '~');
|
|
145
|
+
log(LOG_OK, chalk.green, chalk.white('removed ANTHROPIC_BASE_URL from') + ' ' + chalk.white(rel));
|
|
146
|
+
cleaned++;
|
|
147
|
+
}
|
|
148
|
+
} catch (e) {
|
|
149
|
+
log(LOG_WARN, chalk.yellow, chalk.white(`cannot clean ${path.basename(t.file)}`) + ` → ${chalk.yellow(e.message)}`);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (cleaned === 0) {
|
|
154
|
+
log(LOG_WARN, chalk.yellow, chalk.white('nothing to clean') + ` → ${chalk.yellow('no ANTHROPIC_BASE_URL found in shell configs')}`);
|
|
155
|
+
} else {
|
|
156
|
+
log(LOG_OK, chalk.green, chalk.white(`cleaned ${cleaned} file(s)`));
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
delete process.env.ANTHROPIC_BASE_URL;
|
|
160
|
+
}
|
package/src/inject.js
CHANGED
|
@@ -33,6 +33,7 @@ export async function injectBlock(filePath, content, blockId) {
|
|
|
33
33
|
if (startIndex !== -1 && endIndex !== -1 && endIndex > startIndex) {
|
|
34
34
|
fileContent = fileContent.slice(0, startIndex) + block + fileContent.slice(endIndex + endTag.length);
|
|
35
35
|
} else {
|
|
36
|
+
fileContent = fileContent.trimEnd() + '\n\n' + block + '\n';
|
|
36
37
|
}
|
|
37
38
|
|
|
38
39
|
await atomicWrite(filePath, fileContent);
|
package/src/packages.js
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
import fs from 'fs/promises';
|
|
6
6
|
import path from 'path';
|
|
7
7
|
import { createHash } from 'crypto';
|
|
8
|
+
import { chalk, log, LOG_WARN } from './logger.js';
|
|
8
9
|
|
|
9
10
|
// ─── Logica Dipendenze ───────────────────────────────
|
|
10
11
|
|
|
@@ -41,7 +42,8 @@ export async function readPackageDeps() {
|
|
|
41
42
|
selected = selected.concat(filterAndFormat(pkg.devDependencies));
|
|
42
43
|
|
|
43
44
|
return selected.length > 0 ? selected : null;
|
|
44
|
-
} catch (
|
|
45
|
+
} catch (err) {
|
|
46
|
+
log(LOG_WARN, chalk.yellow, chalk.white('package.json parse error') + ` → ${chalk.yellow(err.message)}`);
|
|
45
47
|
return null;
|
|
46
48
|
}
|
|
47
49
|
}
|
package/src/proxy.js
CHANGED
|
@@ -8,6 +8,7 @@ import { webSearch } from './search.js';
|
|
|
8
8
|
import { readPackageDeps, buildQuery } from './packages.js';
|
|
9
9
|
import { chalk, log, LOG_WARN, LOG_BOLT } from './logger.js';
|
|
10
10
|
import { httpsAgent } from './http-agent.js';
|
|
11
|
+
import { sanitizeWebContent } from './sanitize.js';
|
|
11
12
|
|
|
12
13
|
// ─── HTTP Node server daemon ─────────────────────────
|
|
13
14
|
|
|
@@ -98,9 +99,9 @@ export async function createServer(usePackageJson) {
|
|
|
98
99
|
|
|
99
100
|
contextBlock = `\n\n--- WEB CONTEXT (live, ${new Date().toISOString()}) ---\n`;
|
|
100
101
|
results.forEach((r, i) => {
|
|
101
|
-
contextBlock += `${i + 1}. ${r.title}: ${r.snippet} (${r.url})\n`;
|
|
102
|
+
contextBlock += `${i + 1}. ${r.title}: ${sanitizeWebContent(r.snippet, 500)} (${r.url})\n`;
|
|
102
103
|
});
|
|
103
|
-
if (pageText) contextBlock += `\nFULL TEXT:\n${pageText}\n`;
|
|
104
|
+
if (pageText) contextBlock += `\nFULL TEXT:\n${sanitizeWebContent(pageText)}\n`;
|
|
104
105
|
contextBlock += `--- END WEB CONTEXT ---\n`;
|
|
105
106
|
didInject = true;
|
|
106
107
|
} catch (_) {
|
package/src/sanitize.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module sanitize
|
|
3
|
+
* @description Sanitizzazione contenuto web contro prompt injection attacks.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// Pattern noti di prompt injection che devono essere filtrati
|
|
7
|
+
const DANGEROUS_PATTERNS = [
|
|
8
|
+
/ignore\s+(all\s+)?previous\s+instructions?/gi,
|
|
9
|
+
/disregard\s+(all\s+)?previous/gi,
|
|
10
|
+
/you\s+are\s+now\s+/gi,
|
|
11
|
+
/forget\s+(all\s+)?(your\s+)?instructions?/gi,
|
|
12
|
+
/new\s+instructions?\s*:/gi,
|
|
13
|
+
/system\s*prompt\s*:/gi,
|
|
14
|
+
/\[INST\]/gi,
|
|
15
|
+
/<\|im_start\|>/gi,
|
|
16
|
+
/<\|im_end\|>/gi,
|
|
17
|
+
/```system/gi,
|
|
18
|
+
/ASSISTANT:\s/gi,
|
|
19
|
+
/HUMAN:\s/gi,
|
|
20
|
+
];
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* @description Filtra pattern pericolosi di prompt injection dal testo web scrappato.
|
|
24
|
+
* @param {string} text - Testo raw proveniente da web scraping
|
|
25
|
+
* @param {number} maxLen - Lunghezza massima output (default 8000)
|
|
26
|
+
* @returns {string} Testo sanitizzato
|
|
27
|
+
*/
|
|
28
|
+
export function sanitizeWebContent(text, maxLen = 8000) {
|
|
29
|
+
if (!text || typeof text !== 'string') return '';
|
|
30
|
+
let cleaned = text;
|
|
31
|
+
for (const p of DANGEROUS_PATTERNS) {
|
|
32
|
+
cleaned = cleaned.replace(p, '[FILTERED]');
|
|
33
|
+
}
|
|
34
|
+
return cleaned.slice(0, maxLen);
|
|
35
|
+
}
|
package/src/search.js
CHANGED
|
@@ -9,6 +9,7 @@ import { DOMParser } from 'linkedom';
|
|
|
9
9
|
import { searchCache } from './cache.js';
|
|
10
10
|
import { CircuitBreaker } from './circuit-breaker.js';
|
|
11
11
|
import { httpAgent, httpsAgent } from './http-agent.js';
|
|
12
|
+
import { sanitizeWebContent } from './sanitize.js';
|
|
12
13
|
|
|
13
14
|
// ─── Config & Cache ──────────────────────────────────
|
|
14
15
|
|
|
@@ -118,7 +119,7 @@ export async function webSearch(query, parallel = false) {
|
|
|
118
119
|
} catch (_) {
|
|
119
120
|
text = document.body?.textContent || '';
|
|
120
121
|
}
|
|
121
|
-
if (text) return text.replace(/\s+/g, ' ')
|
|
122
|
+
if (text) return sanitizeWebContent(text.replace(/\s+/g, ' '), 4000);
|
|
122
123
|
}
|
|
123
124
|
} catch (_) { // fail silenzioso parallelo tollerato per timeout link third-party
|
|
124
125
|
}
|
|
@@ -143,7 +144,7 @@ export async function webSearch(query, parallel = false) {
|
|
|
143
144
|
text = document.body?.textContent || '';
|
|
144
145
|
}
|
|
145
146
|
if (text) {
|
|
146
|
-
pageText = text.replace(/\s+/g, ' ')
|
|
147
|
+
pageText = sanitizeWebContent(text.replace(/\s+/g, ' '), 4000);
|
|
147
148
|
}
|
|
148
149
|
}
|
|
149
150
|
}
|
package/src/state.js
CHANGED
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
* @module state
|
|
3
3
|
* @description Persiste la memoria di antigravity prev-hash per fault tolleranza riavvii.
|
|
4
4
|
*/
|
|
5
|
-
import { readFile,
|
|
5
|
+
import { readFile, mkdir } from 'fs/promises';
|
|
6
|
+
import { atomicWrite } from './utils/atomic-write.js';
|
|
6
7
|
import { existsSync } from 'fs';
|
|
7
8
|
import path from 'path';
|
|
8
9
|
import os from 'os';
|
|
@@ -33,5 +34,5 @@ export async function loadBatchState() {
|
|
|
33
34
|
export async function saveBatchState(map) {
|
|
34
35
|
await mkdir(STATE_DIR, { recursive: true });
|
|
35
36
|
const obj = Object.fromEntries(map);
|
|
36
|
-
await
|
|
37
|
+
await atomicWrite(STATE_FILE, JSON.stringify(obj, null, 2), { backup: false });
|
|
37
38
|
}
|
package/src/watcher.js
CHANGED
|
@@ -6,6 +6,7 @@ import os from 'os';
|
|
|
6
6
|
import path from 'path';
|
|
7
7
|
import { webSearch } from './search.js';
|
|
8
8
|
import { readPackageDeps, buildQuery, groupIntoBatches, batchHash } from './packages.js';
|
|
9
|
+
import { sanitizeWebContent } from './sanitize.js';
|
|
9
10
|
import { updateGeminiFiles, removeStaleBlocks } from './inject.js';
|
|
10
11
|
import { chalk, label, log, LOG_WARN, LOG_REFRESH } from './logger.js';
|
|
11
12
|
import { version } from './cli.js';
|
|
@@ -84,16 +85,16 @@ export function startWatcher({ intervalMinutes, usePackageJson, batchSize }) {
|
|
|
84
85
|
globalMd += `**Query:** ${query}\n\n`;
|
|
85
86
|
if (results.length > 0) {
|
|
86
87
|
globalMd += `### ${results[0].title}\n`;
|
|
87
|
-
globalMd += `${results[0].snippet
|
|
88
|
+
globalMd += `${sanitizeWebContent(results[0].snippet, 300)} — ${results[0].url}\n`;
|
|
88
89
|
}
|
|
89
90
|
|
|
90
91
|
let md = `## Live Context — ${batchTitle} (${nowStr})\n`;
|
|
91
92
|
md += `**Query:** ${query}\n\n`;
|
|
92
93
|
for (const r of results) {
|
|
93
|
-
md += `### ${r.title}\n${r.snippet} — ${r.url}\n\n`;
|
|
94
|
+
md += `### ${r.title}\n${sanitizeWebContent(r.snippet, 500)} — ${r.url}\n\n`;
|
|
94
95
|
}
|
|
95
96
|
if (pageText) {
|
|
96
|
-
md += `FULL TEXT: ${pageText}\n`;
|
|
97
|
+
md += `FULL TEXT: ${sanitizeWebContent(pageText)}\n`;
|
|
97
98
|
}
|
|
98
99
|
|
|
99
100
|
await updateGeminiFiles([{
|
package/assets/banner.png
DELETED
|
Binary file
|