thumbgate 1.2.0 → 1.4.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/.claude-plugin/README.md +4 -4
- package/.claude-plugin/marketplace.json +32 -13
- package/.claude-plugin/plugin.json +15 -2
- package/.well-known/llms.txt +60 -0
- package/.well-known/mcp/server-card.json +1 -1
- package/README.md +133 -23
- package/adapters/README.md +1 -1
- package/adapters/chatgpt/openapi.yaml +168 -0
- package/adapters/claude/.mcp.json +2 -2
- package/adapters/codex/config.toml +2 -2
- package/adapters/mcp/server-stdio.js +85 -2
- package/adapters/opencode/opencode.json +1 -1
- package/bin/cli.js +215 -19
- package/bin/postinstall.js +8 -2
- package/config/budget.json +18 -0
- package/config/gates/code-edit.json +61 -0
- package/config/gates/db-write.json +61 -0
- package/config/gates/default.json +154 -3
- package/config/gates/deploy.json +61 -0
- package/config/github-about.json +2 -1
- package/config/merge-quality-checks.json +23 -0
- package/config/model-tiers.json +11 -0
- package/openapi/openapi.yaml +168 -0
- package/package.json +47 -13
- package/plugins/claude-codex-bridge/.claude-plugin/plugin.json +1 -1
- package/plugins/claude-codex-bridge/.mcp.json +1 -1
- package/plugins/claude-codex-bridge/scripts/codex-bridge.js +1 -3
- package/plugins/codex-profile/.codex-plugin/plugin.json +1 -1
- package/plugins/codex-profile/.mcp.json +1 -1
- package/plugins/codex-profile/INSTALL.md +27 -4
- package/plugins/codex-profile/README.md +33 -9
- package/plugins/cursor-marketplace/.cursor-plugin/plugin.json +1 -1
- package/plugins/cursor-marketplace/README.md +2 -2
- package/plugins/cursor-marketplace/commands/capture-feedback.md +2 -2
- package/plugins/cursor-marketplace/rules/feedback-capture.mdc +3 -3
- package/plugins/cursor-marketplace/skills/capture-feedback/SKILL.md +3 -2
- package/plugins/opencode-profile/INSTALL.md +1 -1
- package/public/blog.html +73 -0
- package/public/compare/mem0.html +189 -0
- package/public/compare/speclock.html +180 -0
- package/public/compare.html +12 -4
- package/public/guide.html +5 -5
- package/public/guides/claude-code-prevent-repeated-mistakes.html +161 -0
- package/public/guides/codex-cli-guardrails.html +158 -0
- package/public/guides/cursor-prevent-repeated-mistakes.html +161 -0
- package/public/guides/pre-action-gates.html +162 -0
- package/public/guides/stop-repeated-ai-agent-mistakes.html +159 -0
- package/public/index.html +169 -70
- package/public/learn/ai-agent-persistent-memory.html +1 -0
- package/public/lessons.html +334 -17
- package/public/llm-context.md +140 -0
- package/public/pro.html +24 -22
- package/scripts/__pycache__/train_from_feedback.cpython-312.pyc +0 -0
- package/scripts/access-anomaly-detector.js +1 -1
- package/scripts/adk-consolidator.js +1 -5
- package/scripts/agent-security-hardening.js +4 -6
- package/scripts/agentic-data-pipeline.js +1 -3
- package/scripts/async-job-runner.js +1 -5
- package/scripts/audit-trail.js +7 -5
- package/scripts/background-agent-governance.js +2 -10
- package/scripts/billing.js +2 -16
- package/scripts/budget-enforcer.js +173 -0
- package/scripts/build-codex-plugin.js +152 -0
- package/scripts/capture-railway-diagnostics.sh +97 -0
- package/scripts/check-congruence.js +133 -15
- package/scripts/claude-feedback-sync.js +320 -0
- package/scripts/cli-telemetry.js +4 -1
- package/scripts/commercial-offer.js +5 -7
- package/scripts/content-engine/linkedin-content-generator.js +154 -0
- package/scripts/content-engine/output/linkedin-memento-validation.md +17 -0
- package/scripts/content-engine/output/linkedin-posts-2026-04-09.md +175 -0
- package/scripts/content-engine/reddit-thread-finder.js +154 -0
- package/scripts/context-engine.js +21 -6
- package/scripts/contextfs.js +33 -44
- package/scripts/dashboard.js +104 -0
- package/scripts/decision-journal.js +341 -0
- package/scripts/delegation-runtime.js +1 -5
- package/scripts/distribution-surfaces.js +26 -0
- package/scripts/document-intake.js +927 -0
- package/scripts/ephemeral-agent-store.js +1 -8
- package/scripts/evolution-state.js +1 -5
- package/scripts/experiment-tracker.js +1 -5
- package/scripts/export-databricks-bundle.js +1 -5
- package/scripts/export-hf-dataset.js +1 -5
- package/scripts/export-training.js +1 -5
- package/scripts/feedback-attribution.js +1 -16
- package/scripts/feedback-history-distiller.js +1 -16
- package/scripts/feedback-loop.js +17 -5
- package/scripts/feedback-root-consolidator.js +2 -21
- package/scripts/feedback-session.js +49 -0
- package/scripts/feedback-to-rules.js +188 -28
- package/scripts/filesystem-search.js +1 -9
- package/scripts/fs-utils.js +104 -0
- package/scripts/gates-engine.js +149 -4
- package/scripts/github-about.js +32 -8
- package/scripts/gtm-revenue-loop.js +1 -5
- package/scripts/harness-selector.js +148 -0
- package/scripts/hosted-job-launcher.js +1 -5
- package/scripts/hybrid-feedback-context.js +7 -33
- package/scripts/intervention-policy.js +753 -0
- package/scripts/lesson-db.js +3 -18
- package/scripts/lesson-inference.js +194 -16
- package/scripts/lesson-retrieval.js +60 -24
- package/scripts/llm-client.js +59 -0
- package/scripts/local-model-profile.js +18 -2
- package/scripts/managed-lesson-agent.js +183 -0
- package/scripts/marketing-experiment.js +8 -22
- package/scripts/meta-agent-loop.js +624 -0
- package/scripts/metered-billing.js +1 -1
- package/scripts/model-tier-router.js +10 -1
- package/scripts/money-watcher.js +1 -4
- package/scripts/obsidian-export.js +1 -5
- package/scripts/operational-integrity.js +369 -34
- package/scripts/org-dashboard.js +6 -1
- package/scripts/per-step-scoring.js +2 -4
- package/scripts/pr-manager.js +201 -19
- package/scripts/pro-features.js +3 -2
- package/scripts/prompt-dlp.js +3 -3
- package/scripts/prove-adapters.js +2 -5
- package/scripts/prove-attribution.js +1 -5
- package/scripts/prove-automation.js +3 -5
- package/scripts/prove-cloudflare-sandbox.js +1 -3
- package/scripts/prove-data-pipeline.js +1 -3
- package/scripts/prove-intelligence.js +1 -3
- package/scripts/prove-lancedb.js +1 -5
- package/scripts/prove-local-intelligence.js +1 -3
- package/scripts/prove-packaged-runtime.js +326 -0
- package/scripts/prove-predictive-insights.js +1 -3
- package/scripts/prove-runtime.js +13 -0
- package/scripts/prove-training-export.js +1 -3
- package/scripts/prove-workflow-contract.js +1 -5
- package/scripts/rate-limiter.js +6 -4
- package/scripts/reddit-dm-outreach.js +14 -4
- package/scripts/schedule-manager.js +3 -5
- package/scripts/security-scanner.js +448 -0
- package/scripts/self-distill-agent.js +579 -0
- package/scripts/semantic-dedup.js +115 -0
- package/scripts/skill-exporter.js +1 -3
- package/scripts/skill-generator.js +1 -5
- package/scripts/social-analytics/engagement-audit.js +1 -18
- package/scripts/social-analytics/pollers/linkedin.js +26 -16
- package/scripts/social-analytics/publishers/linkedin.js +1 -1
- package/scripts/social-analytics/publishers/zernio.js +51 -0
- package/scripts/social-pipeline.js +1 -3
- package/scripts/social-post-hourly.js +47 -4
- package/scripts/statusline-links.js +6 -5
- package/scripts/statusline-local-stats.js +2 -0
- package/scripts/statusline.sh +38 -7
- package/scripts/sync-branch-protection.js +340 -0
- package/scripts/tessl-export.js +1 -3
- package/scripts/thumbgate-search.js +32 -1
- package/scripts/tool-kpi-tracker.js +1 -1
- package/scripts/tool-registry.js +108 -4
- package/scripts/vector-store.js +1 -5
- package/scripts/weekly-auto-post.js +1 -1
- package/scripts/workflow-sentinel.js +205 -4
- package/skills/thumbgate/SKILL.md +2 -2
- package/src/api/server.js +273 -4
- package/scripts/social-analytics/db/social-analytics.db-shm +0 -0
- /package/scripts/social-analytics/db/{social-analytics.db-wal → analytics.sqlite} +0 -0
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
# LinkedIn Content: ThumbGate Gates (2026-04-09)
|
|
2
|
+
|
|
3
|
+
Generated from: `config/gates/default.json`
|
|
4
|
+
Gate count in config: 25
|
|
5
|
+
Posts generated: 7
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Post 1: Local Only Git Writes
|
|
10
|
+
|
|
11
|
+
🚨 Your AI agents are running without guardrails.
|
|
12
|
+
|
|
13
|
+
Blocks git writes when local-only mode is active, preventing accidental remote pushes during development.
|
|
14
|
+
|
|
15
|
+
The problem? AI agents run autonomously. A single unchecked operation—a force-push, an unapproved deploy, a dependency injection—can unwind days of work in seconds. Traditional CI won't catch it. Your human reviewer might miss it.
|
|
16
|
+
|
|
17
|
+
The solution? **Gate `local-only-git-writes`** in ThumbGate stops high-risk operations *before* they execute. No second chances. Just prevention.
|
|
18
|
+
|
|
19
|
+
This isn't about slowing down. It's about building trust in autonomous systems. Every gate is a rule learned from real failures.
|
|
20
|
+
|
|
21
|
+
🔒 Install ThumbGate today:
|
|
22
|
+
```bash
|
|
23
|
+
npx thumbgate@latest init
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Then add this gate to your config and sleep better.
|
|
27
|
+
|
|
28
|
+
#AIGovernance #DevTools #AgentSafety #EngineeringTeams
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
## Post 2: Gh Pr Create Restricted
|
|
34
|
+
|
|
35
|
+
⚠️ One missing gate. One catastrophic mistake.
|
|
36
|
+
|
|
37
|
+
Restricts PR creation to explicitly approved workflows, preventing unvetted code changes.
|
|
38
|
+
|
|
39
|
+
The problem? AI agents run autonomously. A single unchecked operation—a force-push, an unapproved deploy, a dependency injection—can unwind days of work in seconds. Traditional CI won't catch it. Your human reviewer might miss it.
|
|
40
|
+
|
|
41
|
+
The solution? **Gate `gh-pr-create-restricted`** in ThumbGate stops high-risk operations *before* they execute. No second chances. Just prevention.
|
|
42
|
+
|
|
43
|
+
This isn't about slowing down. It's about building trust in autonomous systems. Every gate is a rule learned from real failures.
|
|
44
|
+
|
|
45
|
+
🔒 Install ThumbGate today:
|
|
46
|
+
```bash
|
|
47
|
+
npx thumbgate@latest init
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Then add this gate to your config and sleep better.
|
|
51
|
+
|
|
52
|
+
#AIGovernance #DevTools #AgentSafety #EngineeringTeams
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
## Post 3: Env File Edit
|
|
58
|
+
|
|
59
|
+
🛡️ Even the best engineers miss edge cases.
|
|
60
|
+
|
|
61
|
+
Warns when editing .env files—catches accidental token deletion.
|
|
62
|
+
|
|
63
|
+
The problem? AI agents run autonomously. A single unchecked operation—a force-push, an unapproved deploy, a dependency injection—can unwind days of work in seconds. Traditional CI won't catch it. Your human reviewer might miss it.
|
|
64
|
+
|
|
65
|
+
The solution? **Gate `env-file-edit`** in ThumbGate stops high-risk operations *before* they execute. No second chances. Just prevention.
|
|
66
|
+
|
|
67
|
+
This isn't about slowing down. It's about building trust in autonomous systems. Every gate is a rule learned from real failures.
|
|
68
|
+
|
|
69
|
+
🔒 Install ThumbGate today:
|
|
70
|
+
```bash
|
|
71
|
+
npx thumbgate@latest init
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Then add this gate to your config and sleep better.
|
|
75
|
+
|
|
76
|
+
#AIGovernance #DevTools #AgentSafety #EngineeringTeams
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
## Post 4: Style Violation Log
|
|
82
|
+
|
|
83
|
+
💥 Your deployment pipeline has a blind spot.
|
|
84
|
+
|
|
85
|
+
Protects your workflow by style audit mode active. action recorded for review but allowed to proceed.
|
|
86
|
+
|
|
87
|
+
The problem? AI agents run autonomously. A single unchecked operation—a force-push, an unapproved deploy, a dependency injection—can unwind days of work in seconds. Traditional CI won't catch it. Your human reviewer might miss it.
|
|
88
|
+
|
|
89
|
+
The solution? **Gate `style-violation-log`** in ThumbGate stops high-risk operations *before* they execute. No second chances. Just prevention.
|
|
90
|
+
|
|
91
|
+
This isn't about slowing down. It's about building trust in autonomous systems. Every gate is a rule learned from real failures.
|
|
92
|
+
|
|
93
|
+
🔒 Install ThumbGate today:
|
|
94
|
+
```bash
|
|
95
|
+
npx thumbgate@latest init
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Then add this gate to your config and sleep better.
|
|
99
|
+
|
|
100
|
+
#AIGovernance #DevTools #AgentSafety #EngineeringTeams
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
## Post 5: Loop Abuse Prevention
|
|
106
|
+
|
|
107
|
+
🔓 Git operations—unguarded by default.
|
|
108
|
+
|
|
109
|
+
Protects your workflow by high-risk command detected inside a loop. scheduled tasks must not perform egress or destructive writes without explicit approval.
|
|
110
|
+
|
|
111
|
+
The problem? AI agents run autonomously. A single unchecked operation—a force-push, an unapproved deploy, a dependency injection—can unwind days of work in seconds. Traditional CI won't catch it. Your human reviewer might miss it.
|
|
112
|
+
|
|
113
|
+
The solution? **Gate `loop-abuse-prevention`** in ThumbGate stops high-risk operations *before* they execute. No second chances. Just prevention.
|
|
114
|
+
|
|
115
|
+
This isn't about slowing down. It's about building trust in autonomous systems. Every gate is a rule learned from real failures.
|
|
116
|
+
|
|
117
|
+
🔒 Install ThumbGate today:
|
|
118
|
+
```bash
|
|
119
|
+
npx thumbgate@latest init
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Then add this gate to your config and sleep better.
|
|
123
|
+
|
|
124
|
+
#AIGovernance #DevTools #AgentSafety #EngineeringTeams
|
|
125
|
+
|
|
126
|
+
---
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
## Post 6: Release Readiness Required
|
|
130
|
+
|
|
131
|
+
🎯 Prevention beats firefighting.
|
|
132
|
+
|
|
133
|
+
Ensures releases only happen from releasable mainline commits with version alignment.
|
|
134
|
+
|
|
135
|
+
The problem? AI agents run autonomously. A single unchecked operation—a force-push, an unapproved deploy, a dependency injection—can unwind days of work in seconds. Traditional CI won't catch it. Your human reviewer might miss it.
|
|
136
|
+
|
|
137
|
+
The solution? **Gate `release-readiness-required`** in ThumbGate stops high-risk operations *before* they execute. No second chances. Just prevention.
|
|
138
|
+
|
|
139
|
+
This isn't about slowing down. It's about building trust in autonomous systems. Every gate is a rule learned from real failures.
|
|
140
|
+
|
|
141
|
+
🔒 Install ThumbGate today:
|
|
142
|
+
```bash
|
|
143
|
+
npx thumbgate@latest init
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
Then add this gate to your config and sleep better.
|
|
147
|
+
|
|
148
|
+
#AIGovernance #DevTools #AgentSafety #EngineeringTeams
|
|
149
|
+
|
|
150
|
+
---
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
## Post 7: Protected Branch Push
|
|
154
|
+
|
|
155
|
+
⏱️ How fast can your agent destroy a month of work?
|
|
156
|
+
|
|
157
|
+
Prevents direct pushes to main/develop. All changes flow through PR review.
|
|
158
|
+
|
|
159
|
+
The problem? AI agents run autonomously. A single unchecked operation—a force-push, an unapproved deploy, a dependency injection—can unwind days of work in seconds. Traditional CI won't catch it. Your human reviewer might miss it.
|
|
160
|
+
|
|
161
|
+
The solution? **Gate `protected-branch-push`** in ThumbGate stops high-risk operations *before* they execute. No second chances. Just prevention.
|
|
162
|
+
|
|
163
|
+
This isn't about slowing down. It's about building trust in autonomous systems. Every gate is a rule learned from real failures.
|
|
164
|
+
|
|
165
|
+
🔒 Install ThumbGate today:
|
|
166
|
+
```bash
|
|
167
|
+
npx thumbgate@latest init
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
Then add this gate to your config and sleep better.
|
|
171
|
+
|
|
172
|
+
#AIGovernance #DevTools #AgentSafety #EngineeringTeams
|
|
173
|
+
|
|
174
|
+
---
|
|
175
|
+
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Reddit Thread Finder for ThumbGate Engagement
|
|
4
|
+
*
|
|
5
|
+
* Add to package.json:
|
|
6
|
+
* "content:reddit": "node scripts/content-engine/reddit-thread-finder.js"
|
|
7
|
+
* "content:reddit:dry": "node scripts/content-engine/reddit-thread-finder.js --dry-run"
|
|
8
|
+
* "content:reddit:limit": "node scripts/content-engine/reddit-thread-finder.js --limit 20"
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const https = require('https');
|
|
12
|
+
const fs = require('fs');
|
|
13
|
+
const path = require('path');
|
|
14
|
+
|
|
15
|
+
const SUBREDDITS = [
|
|
16
|
+
'ChatGPTCoding', 'ClaudeAI', 'cursor', 'devops',
|
|
17
|
+
'SoftwareEngineering', 'ExperiencedDevs', 'MachineLearning', 'LocalLLaMA'
|
|
18
|
+
];
|
|
19
|
+
|
|
20
|
+
const KEYWORDS = [
|
|
21
|
+
'agent broke', 'agent deleted', 'force push', 'prevent AI from',
|
|
22
|
+
'guardrails', 'agent governance', 'coding agent mistakes'
|
|
23
|
+
];
|
|
24
|
+
|
|
25
|
+
const USER_AGENT = 'script:thumbgate-content:v1.0';
|
|
26
|
+
const DELAY = 2000; // ms between requests
|
|
27
|
+
const DEFAULT_LIMIT = 10;
|
|
28
|
+
|
|
29
|
+
let dryRun = false;
|
|
30
|
+
let outputLimit = DEFAULT_LIMIT;
|
|
31
|
+
|
|
32
|
+
process.argv.slice(2).forEach(arg => {
|
|
33
|
+
if (arg === '--dry-run') dryRun = true;
|
|
34
|
+
if (arg.startsWith('--limit')) outputLimit = parseInt(arg.split('=')[1] || arg.split(' ')[1], 10);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
function fetchReddit(url) {
|
|
38
|
+
return new Promise((resolve, reject) => {
|
|
39
|
+
const req = https.get(url, { headers: { 'User-Agent': USER_AGENT } }, res => {
|
|
40
|
+
let data = '';
|
|
41
|
+
res.on('data', chunk => data += chunk);
|
|
42
|
+
res.on('end', () => {
|
|
43
|
+
try {
|
|
44
|
+
resolve(JSON.parse(data));
|
|
45
|
+
} catch (e) {
|
|
46
|
+
reject(e);
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
req.on('error', reject);
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async function searchSubreddit(sub, keyword) {
|
|
55
|
+
const url = `https://www.reddit.com/r/${sub}/search.json?q=${encodeURIComponent(keyword)}&sort=new&t=week&limit=5`;
|
|
56
|
+
try {
|
|
57
|
+
const data = await fetchReddit(url);
|
|
58
|
+
return (data.data?.children || []).map(post => ({
|
|
59
|
+
id: post.data.id,
|
|
60
|
+
title: post.data.title,
|
|
61
|
+
url: `https://reddit.com${post.data.permalink}`,
|
|
62
|
+
subreddit: post.data.subreddit,
|
|
63
|
+
score: post.data.score,
|
|
64
|
+
numComments: post.data.num_comments,
|
|
65
|
+
created: post.data.created_utc,
|
|
66
|
+
selftext: post.data.selftext
|
|
67
|
+
}));
|
|
68
|
+
} catch (err) {
|
|
69
|
+
console.error(`Error searching ${sub} for "${keyword}": ${err.message}`);
|
|
70
|
+
return [];
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function scoreThread(thread) {
|
|
75
|
+
const now = Math.floor(Date.now() / 1000);
|
|
76
|
+
const ageHours = (now - thread.created) / 3600;
|
|
77
|
+
const recencyScore = Math.max(0, 1 - ageHours / 168); // 0-1 over a week
|
|
78
|
+
const upvoteScore = Math.log(Math.max(1, thread.score)) / Math.log(100);
|
|
79
|
+
const commentScore = Math.log(Math.max(1, thread.numComments)) / Math.log(100);
|
|
80
|
+
|
|
81
|
+
return (recencyScore * 0.5) + (upvoteScore * 0.3) + (commentScore * 0.2);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function generateReply(thread) {
|
|
85
|
+
const context = thread.selftext.substring(0, 200);
|
|
86
|
+
return `
|
|
87
|
+
**ThumbGate can help prevent this.** Our pre-action gates catch agent mistakes before they happen:
|
|
88
|
+
- Stop force pushes on protected branches
|
|
89
|
+
- Prevent deletions of critical files
|
|
90
|
+
- Verify AI actions before execution
|
|
91
|
+
- Capture lessons from failures to block similar mistakes
|
|
92
|
+
|
|
93
|
+
Learn more: https://thumbgate-production.up.railway.app/dashboard
|
|
94
|
+
`;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
async function main() {
|
|
98
|
+
console.log(`[${new Date().toISOString()}] Starting Reddit thread finder...`);
|
|
99
|
+
|
|
100
|
+
const threads = {};
|
|
101
|
+
let requestCount = 0;
|
|
102
|
+
|
|
103
|
+
for (const sub of SUBREDDITS) {
|
|
104
|
+
for (const keyword of KEYWORDS) {
|
|
105
|
+
if (requestCount > 0) await new Promise(r => setTimeout(r, DELAY));
|
|
106
|
+
|
|
107
|
+
console.log(` Searching r/${sub} for "${keyword}"...`);
|
|
108
|
+
const results = await searchSubreddit(sub, keyword);
|
|
109
|
+
|
|
110
|
+
results.forEach(thread => {
|
|
111
|
+
if (!threads[thread.id]) {
|
|
112
|
+
threads[thread.id] = thread;
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
requestCount++;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const sorted = Object.values(threads)
|
|
120
|
+
.sort((a, b) => scoreThread(b) - scoreThread(a))
|
|
121
|
+
.slice(0, outputLimit);
|
|
122
|
+
|
|
123
|
+
const date = new Date().toISOString().split('T')[0];
|
|
124
|
+
const outputDir = path.join(__dirname, 'output');
|
|
125
|
+
|
|
126
|
+
if (!fs.existsSync(outputDir)) {
|
|
127
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
let markdown = `# Reddit Threads - ${date}\n\nFound ${sorted.length} high-relevance threads.\n\n`;
|
|
131
|
+
|
|
132
|
+
sorted.forEach((thread, idx) => {
|
|
133
|
+
const score = scoreThread(thread);
|
|
134
|
+
markdown += `## ${idx + 1}. ${thread.title}\n`;
|
|
135
|
+
markdown += `**r/${thread.subreddit}** | [Link](${thread.url}) | Score: ${thread.score} | Comments: ${thread.numComments}\n`;
|
|
136
|
+
markdown += `**Relevance Score:** ${score.toFixed(2)}\n\n`;
|
|
137
|
+
|
|
138
|
+
if (!dryRun) {
|
|
139
|
+
markdown += `**Suggested Reply:**\n${generateReply(thread)}\n\n`;
|
|
140
|
+
}
|
|
141
|
+
markdown += '---\n\n';
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
const outputFile = path.join(outputDir, `reddit-threads-${date}.md`);
|
|
145
|
+
fs.writeFileSync(outputFile, markdown);
|
|
146
|
+
|
|
147
|
+
console.log(`\n✅ Generated ${sorted.length} threads to ${outputFile}`);
|
|
148
|
+
if (dryRun) console.log(' (--dry-run: no reply suggestions included)');
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
main().catch(err => {
|
|
152
|
+
console.error('❌ Fatal error:', err.message);
|
|
153
|
+
process.exit(1);
|
|
154
|
+
});
|
|
@@ -20,6 +20,7 @@ const fs = require('fs');
|
|
|
20
20
|
const path = require('path');
|
|
21
21
|
const crypto = require('crypto');
|
|
22
22
|
const { constructContextPack } = require('./contextfs');
|
|
23
|
+
const { ensureDir } = require('./fs-utils');
|
|
23
24
|
|
|
24
25
|
// ---------------------------------------------------------------------------
|
|
25
26
|
// Default paths
|
|
@@ -75,11 +76,6 @@ const TOOL_CONSOLIDATION = {
|
|
|
75
76
|
// Utility: ensure directory exists
|
|
76
77
|
// ---------------------------------------------------------------------------
|
|
77
78
|
|
|
78
|
-
function ensureDir(dirPath) {
|
|
79
|
-
if (!fs.existsSync(dirPath)) {
|
|
80
|
-
fs.mkdirSync(dirPath, { recursive: true });
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
79
|
|
|
84
80
|
// ---------------------------------------------------------------------------
|
|
85
81
|
// Knowledge Bundle Builder
|
|
@@ -649,10 +645,29 @@ function compactContext(entries, anchors, opts) {
|
|
|
649
645
|
return true;
|
|
650
646
|
});
|
|
651
647
|
|
|
648
|
+
// Stage 6: Global token budget — drop entries (oldest first) until total chars fit
|
|
649
|
+
let finalStage = 5;
|
|
650
|
+
const totalMaxChars = typeof options.totalMaxChars === 'number' ? options.totalMaxChars : null;
|
|
651
|
+
if (totalMaxChars !== null) {
|
|
652
|
+
let budget = totalMaxChars;
|
|
653
|
+
const budgeted = [];
|
|
654
|
+
// Iterate newest-first so most recent entries are preserved
|
|
655
|
+
for (let i = working.length - 1; i >= 0; i--) {
|
|
656
|
+
const entrySize = JSON.stringify(working[i]).length;
|
|
657
|
+
if (budget - entrySize < 0) break;
|
|
658
|
+
budget -= entrySize;
|
|
659
|
+
budgeted.unshift(working[i]);
|
|
660
|
+
}
|
|
661
|
+
if (budgeted.length < working.length) {
|
|
662
|
+
working = budgeted;
|
|
663
|
+
finalStage = 6;
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
|
|
652
667
|
const removedCount = initial - working.length;
|
|
653
668
|
return {
|
|
654
669
|
entries: [...anchorEntries, ...working],
|
|
655
|
-
stage:
|
|
670
|
+
stage: finalStage,
|
|
656
671
|
removedCount,
|
|
657
672
|
compacted: removedCount > 0,
|
|
658
673
|
};
|
package/scripts/contextfs.js
CHANGED
|
@@ -12,6 +12,7 @@ const fs = require('fs');
|
|
|
12
12
|
const path = require('path');
|
|
13
13
|
const crypto = require('crypto');
|
|
14
14
|
const { resolveFeedbackDir } = require('./feedback-paths');
|
|
15
|
+
const { ensureDir, readJsonl } = require('./fs-utils');
|
|
15
16
|
const {
|
|
16
17
|
retrieveHierarchicalDocuments,
|
|
17
18
|
shouldUseHierarchicalRetrieval,
|
|
@@ -23,9 +24,15 @@ function getFeedbackBaseDir() {
|
|
|
23
24
|
return resolveFeedbackDir();
|
|
24
25
|
}
|
|
25
26
|
|
|
26
|
-
|
|
27
|
-
const
|
|
28
|
-
|
|
27
|
+
function getContextFsRoot() {
|
|
28
|
+
const feedbackDir = getFeedbackBaseDir();
|
|
29
|
+
if (process.env.THUMBGATE_CONTEXTFS_DIR) return process.env.THUMBGATE_CONTEXTFS_DIR;
|
|
30
|
+
return feedbackDir.endsWith('contextfs') ? feedbackDir : path.join(feedbackDir, 'contextfs');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function contextFsPath(...segments) {
|
|
34
|
+
return path.join(getContextFsRoot(), ...segments);
|
|
35
|
+
}
|
|
29
36
|
|
|
30
37
|
const NAMESPACES = {
|
|
31
38
|
rawHistory: 'raw_history',
|
|
@@ -93,15 +100,10 @@ const PACK_TEMPLATES = {
|
|
|
93
100
|
},
|
|
94
101
|
};
|
|
95
102
|
|
|
96
|
-
function ensureDir(dirPath) {
|
|
97
|
-
if (!fs.existsSync(dirPath)) {
|
|
98
|
-
fs.mkdirSync(dirPath, { recursive: true });
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
103
|
|
|
102
104
|
function ensureContextFs() {
|
|
103
105
|
Object.values(NAMESPACES).forEach((subPath) => {
|
|
104
|
-
ensureDir(
|
|
106
|
+
ensureDir(contextFsPath(subPath));
|
|
105
107
|
});
|
|
106
108
|
}
|
|
107
109
|
|
|
@@ -111,7 +113,7 @@ function nowIso() {
|
|
|
111
113
|
|
|
112
114
|
function inferNamespaceFromPath(filePath) {
|
|
113
115
|
if (!filePath) return '';
|
|
114
|
-
const relativeDir = path.relative(
|
|
116
|
+
const relativeDir = path.relative(getContextFsRoot(), path.dirname(filePath));
|
|
115
117
|
if (!relativeDir || relativeDir.startsWith('..')) return '';
|
|
116
118
|
return relativeDir;
|
|
117
119
|
}
|
|
@@ -134,22 +136,6 @@ function appendJsonl(filePath, payload) {
|
|
|
134
136
|
fs.appendFileSync(filePath, `${JSON.stringify(payload)}\n`);
|
|
135
137
|
}
|
|
136
138
|
|
|
137
|
-
function readJsonl(filePath) {
|
|
138
|
-
if (!fs.existsSync(filePath)) return [];
|
|
139
|
-
const raw = fs.readFileSync(filePath, 'utf-8').trim();
|
|
140
|
-
if (!raw) return [];
|
|
141
|
-
return raw
|
|
142
|
-
.split('\n')
|
|
143
|
-
.map((line) => {
|
|
144
|
-
try {
|
|
145
|
-
return JSON.parse(line);
|
|
146
|
-
} catch {
|
|
147
|
-
return null;
|
|
148
|
-
}
|
|
149
|
-
})
|
|
150
|
-
.filter(Boolean);
|
|
151
|
-
}
|
|
152
|
-
|
|
153
139
|
function listJsonFiles(dirPath) {
|
|
154
140
|
if (!fs.existsSync(dirPath)) return [];
|
|
155
141
|
const files = fs.readdirSync(dirPath, { withFileTypes: true });
|
|
@@ -211,7 +197,7 @@ function getSemanticCacheConfig() {
|
|
|
211
197
|
}
|
|
212
198
|
|
|
213
199
|
function getSemanticCachePath() {
|
|
214
|
-
return
|
|
200
|
+
return contextFsPath(NAMESPACES.provenance, 'semantic-cache.jsonl');
|
|
215
201
|
}
|
|
216
202
|
|
|
217
203
|
function loadSemanticCacheEntries() {
|
|
@@ -227,7 +213,7 @@ function getSourceHash(namespaces) {
|
|
|
227
213
|
const normalizedNamespaces = normalizeNamespaces(namespaces);
|
|
228
214
|
|
|
229
215
|
for (const ns of normalizedNamespaces) {
|
|
230
|
-
const dirPath =
|
|
216
|
+
const dirPath = contextFsPath(ns);
|
|
231
217
|
if (!fs.existsSync(dirPath)) continue;
|
|
232
218
|
|
|
233
219
|
const files = fs.readdirSync(dirPath).sort();
|
|
@@ -291,7 +277,7 @@ function recordProvenance(event) {
|
|
|
291
277
|
timestamp: nowIso(),
|
|
292
278
|
...event,
|
|
293
279
|
};
|
|
294
|
-
appendJsonl(
|
|
280
|
+
appendJsonl(contextFsPath(NAMESPACES.provenance, 'events.jsonl'), payload);
|
|
295
281
|
return payload;
|
|
296
282
|
}
|
|
297
283
|
|
|
@@ -299,7 +285,7 @@ function writeContextObject({ namespace, title, content, tags = [], source, ttl
|
|
|
299
285
|
ensureContextFs();
|
|
300
286
|
|
|
301
287
|
const id = `${Date.now()}_${toSlug(title)}`;
|
|
302
|
-
const filePath =
|
|
288
|
+
const filePath = contextFsPath(namespace, `${id}.json`);
|
|
303
289
|
|
|
304
290
|
const doc = {
|
|
305
291
|
id,
|
|
@@ -361,7 +347,7 @@ function findExistingContextObject({ namespace, title, content, tags = [], sourc
|
|
|
361
347
|
ensureContextFs();
|
|
362
348
|
|
|
363
349
|
const expectedTags = normalizeTagList(tags);
|
|
364
|
-
const dirPath =
|
|
350
|
+
const dirPath = contextFsPath(namespace);
|
|
365
351
|
const files = listJsonFiles(dirPath).sort();
|
|
366
352
|
|
|
367
353
|
for (const filePath of files) {
|
|
@@ -507,7 +493,7 @@ function loadCandidates(namespaces) {
|
|
|
507
493
|
const docs = [];
|
|
508
494
|
|
|
509
495
|
selected.forEach((namespace) => {
|
|
510
|
-
const dir =
|
|
496
|
+
const dir = contextFsPath(namespace);
|
|
511
497
|
const files = listJsonFiles(dir);
|
|
512
498
|
files.forEach((filePath) => {
|
|
513
499
|
try {
|
|
@@ -624,7 +610,7 @@ function selectFlatContextItems(candidates, maxItems, maxChars) {
|
|
|
624
610
|
const MEMEX_INDEX_FILE = 'memex-index.jsonl';
|
|
625
611
|
|
|
626
612
|
function getMemexIndexPath() {
|
|
627
|
-
return
|
|
613
|
+
return contextFsPath(NAMESPACES.provenance, MEMEX_INDEX_FILE);
|
|
628
614
|
}
|
|
629
615
|
|
|
630
616
|
function buildIndexEntry(doc, filePath) {
|
|
@@ -751,7 +737,7 @@ function constructMemexPack({ query = '', maxItems = 8, maxChars = 6000, namespa
|
|
|
751
737
|
cache: { hit: false },
|
|
752
738
|
};
|
|
753
739
|
|
|
754
|
-
appendJsonl(
|
|
740
|
+
appendJsonl(contextFsPath(NAMESPACES.provenance, 'packs.jsonl'), pack);
|
|
755
741
|
recordProvenance({
|
|
756
742
|
type: 'memex_pack_constructed',
|
|
757
743
|
packId,
|
|
@@ -792,7 +778,7 @@ function constructContextPack({ query = '', maxItems = 8, maxChars = 6000, names
|
|
|
792
778
|
},
|
|
793
779
|
};
|
|
794
780
|
|
|
795
|
-
appendJsonl(
|
|
781
|
+
appendJsonl(contextFsPath(NAMESPACES.provenance, 'packs.jsonl'), pack);
|
|
796
782
|
recordProvenance({
|
|
797
783
|
type: 'context_pack_cache_hit',
|
|
798
784
|
packId,
|
|
@@ -861,7 +847,7 @@ function constructContextPack({ query = '', maxItems = 8, maxChars = 6000, names
|
|
|
861
847
|
retrieval: selection.retrieval,
|
|
862
848
|
};
|
|
863
849
|
|
|
864
|
-
appendJsonl(
|
|
850
|
+
appendJsonl(contextFsPath(NAMESPACES.provenance, 'packs.jsonl'), pack);
|
|
865
851
|
appendSemanticCacheEntry({
|
|
866
852
|
id: `cache_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
|
|
867
853
|
timestamp: nowIso(),
|
|
@@ -899,7 +885,7 @@ function evaluateContextPack({ packId, outcome, signal = null, notes = '', rubri
|
|
|
899
885
|
timestamp: nowIso(),
|
|
900
886
|
};
|
|
901
887
|
|
|
902
|
-
appendJsonl(
|
|
888
|
+
appendJsonl(contextFsPath(NAMESPACES.provenance, 'evaluations.jsonl'), evaluation);
|
|
903
889
|
recordProvenance({
|
|
904
890
|
type: 'context_pack_evaluated',
|
|
905
891
|
packId,
|
|
@@ -912,7 +898,7 @@ function evaluateContextPack({ packId, outcome, signal = null, notes = '', rubri
|
|
|
912
898
|
}
|
|
913
899
|
|
|
914
900
|
function getProvenance(limit = 50) {
|
|
915
|
-
const eventsPath =
|
|
901
|
+
const eventsPath = contextFsPath(NAMESPACES.provenance, 'events.jsonl');
|
|
916
902
|
const events = readJsonl(eventsPath);
|
|
917
903
|
return events.slice(-limit);
|
|
918
904
|
}
|
|
@@ -923,7 +909,7 @@ function getProvenance(limit = 50) {
|
|
|
923
909
|
* session starts with full context — no manual primer.md needed.
|
|
924
910
|
*/
|
|
925
911
|
function writeSessionHandoff({ project, branch, lastTask, nextStep, blockers, openFiles, customContext } = {}) {
|
|
926
|
-
ensureDir(
|
|
912
|
+
ensureDir(contextFsPath(NAMESPACES.session));
|
|
927
913
|
|
|
928
914
|
let gitContext = {};
|
|
929
915
|
try {
|
|
@@ -951,7 +937,7 @@ function writeSessionHandoff({ project, branch, lastTask, nextStep, blockers, op
|
|
|
951
937
|
customContext: customContext || null,
|
|
952
938
|
};
|
|
953
939
|
|
|
954
|
-
const primerPath =
|
|
940
|
+
const primerPath = contextFsPath(NAMESPACES.session, 'primer.json');
|
|
955
941
|
fs.writeFileSync(primerPath, JSON.stringify(primer, null, 2));
|
|
956
942
|
|
|
957
943
|
// Sync to primer.md if it exists
|
|
@@ -991,7 +977,7 @@ function writeSessionHandoff({ project, branch, lastTask, nextStep, blockers, op
|
|
|
991
977
|
* Read the most recent session handoff primer.
|
|
992
978
|
*/
|
|
993
979
|
function readSessionHandoff() {
|
|
994
|
-
const primerPath =
|
|
980
|
+
const primerPath = contextFsPath(NAMESPACES.session, 'primer.json');
|
|
995
981
|
if (!fs.existsSync(primerPath)) return null;
|
|
996
982
|
try {
|
|
997
983
|
return JSON.parse(fs.readFileSync(primerPath, 'utf8'));
|
|
@@ -1192,7 +1178,7 @@ function constructMultiHopPack({ query = '', maxItems = 8, maxChars = 6000, name
|
|
|
1192
1178
|
},
|
|
1193
1179
|
};
|
|
1194
1180
|
|
|
1195
|
-
appendJsonl(
|
|
1181
|
+
appendJsonl(contextFsPath(NAMESPACES.provenance, 'packs.jsonl'), pack);
|
|
1196
1182
|
appendSemanticCacheEntry({
|
|
1197
1183
|
id: `cache_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
|
|
1198
1184
|
timestamp: nowIso(),
|
|
@@ -1244,7 +1230,10 @@ function listPackTemplates() {
|
|
|
1244
1230
|
}
|
|
1245
1231
|
|
|
1246
1232
|
module.exports = {
|
|
1247
|
-
CONTEXTFS_ROOT
|
|
1233
|
+
get CONTEXTFS_ROOT() {
|
|
1234
|
+
return getContextFsRoot();
|
|
1235
|
+
},
|
|
1236
|
+
getContextFsRoot,
|
|
1248
1237
|
NAMESPACES,
|
|
1249
1238
|
ensureContextFs,
|
|
1250
1239
|
recordProvenance,
|
|
@@ -1283,5 +1272,5 @@ module.exports = {
|
|
|
1283
1272
|
|
|
1284
1273
|
if (require.main === module) {
|
|
1285
1274
|
ensureContextFs();
|
|
1286
|
-
console.log(`ContextFS ready at ${
|
|
1275
|
+
console.log(`ContextFS ready at ${getContextFsRoot()}`);
|
|
1287
1276
|
}
|