thumbgate 1.4.2 → 1.4.4
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 +45 -34
- package/.claude-plugin/marketplace.json +3 -3
- package/.claude-plugin/plugin.json +3 -3
- package/.well-known/llms.txt +1 -1
- package/.well-known/mcp/server-card.json +1 -1
- package/README.md +26 -2
- package/adapters/README.md +4 -1
- package/adapters/claude/.mcp.json +2 -2
- package/adapters/codex/config.toml +2 -2
- package/adapters/mcp/server-stdio.js +10 -4
- package/adapters/opencode/opencode.json +1 -1
- package/bin/cli.js +246 -90
- package/config/mcp-allowlists.json +11 -3
- package/package.json +184 -21
- package/scripts/audit-trail.js +25 -15
- package/scripts/auto-wire-hooks.js +127 -0
- package/scripts/cli-demo.js +102 -0
- package/scripts/cli-schema.js +285 -0
- package/scripts/cli-status.js +166 -0
- package/scripts/cross-encoder-reranker.js +235 -0
- package/scripts/explore-subcommands.js +277 -0
- package/scripts/explore.js +569 -0
- package/scripts/feedback-loop.js +20 -6
- package/scripts/lesson-inference.js +7 -1
- package/scripts/lesson-reranker.js +263 -0
- package/scripts/lesson-retrieval.js +34 -17
- package/scripts/lesson-search.js +69 -0
- package/scripts/perplexity-client.js +210 -0
- package/scripts/reflector-agent.js +2 -2
- package/scripts/statusline-local-stats.js +3 -1
- package/scripts/statusline.sh +12 -11
- package/src/api/server.js +178 -17
- package/src/index.js +3 -0
- package/.claude-plugin/bundle/icon.png +0 -0
- package/.claude-plugin/bundle/icon.svg +0 -18
- package/.claude-plugin/bundle/server/index.js +0 -24
- package/adapters/chatgpt/INSTALL.md +0 -138
- package/bin/memory.sh +0 -64
- package/bin/obsidian-sync.sh +0 -20
- package/plugins/amp-skill/INSTALL.md +0 -52
- package/plugins/amp-skill/SKILL.md +0 -64
- package/plugins/claude-codex-bridge/.claude-plugin/plugin.json +0 -22
- package/plugins/claude-codex-bridge/.mcp.json +0 -14
- package/plugins/claude-codex-bridge/INSTALL.md +0 -43
- package/plugins/claude-codex-bridge/README.md +0 -46
- package/plugins/claude-codex-bridge/scripts/codex-bridge.js +0 -286
- package/plugins/claude-codex-bridge/skills/adversarial-review/SKILL.md +0 -24
- package/plugins/claude-codex-bridge/skills/result/SKILL.md +0 -22
- package/plugins/claude-codex-bridge/skills/review/SKILL.md +0 -28
- package/plugins/claude-codex-bridge/skills/second-pass/SKILL.md +0 -27
- package/plugins/claude-codex-bridge/skills/setup/SKILL.md +0 -21
- package/plugins/claude-codex-bridge/skills/status/SKILL.md +0 -19
- package/plugins/claude-skill/INSTALL.md +0 -55
- package/plugins/claude-skill/SKILL.md +0 -46
- package/plugins/codex-profile/.codex-plugin/plugin.json +0 -43
- package/plugins/codex-profile/.mcp.json +0 -14
- package/plugins/codex-profile/AGENTS.md +0 -20
- package/plugins/codex-profile/INSTALL.md +0 -89
- package/plugins/codex-profile/README.md +0 -61
- package/plugins/cursor-marketplace/.cursor-plugin/plugin.json +0 -23
- package/plugins/cursor-marketplace/CHANGELOG.md +0 -30
- package/plugins/cursor-marketplace/LICENSE +0 -21
- package/plugins/cursor-marketplace/README.md +0 -124
- package/plugins/cursor-marketplace/agents/reliability-reviewer.md +0 -31
- package/plugins/cursor-marketplace/assets/logo-400x400.png +0 -0
- package/plugins/cursor-marketplace/commands/capture-feedback.md +0 -33
- package/plugins/cursor-marketplace/commands/check-gates.md +0 -25
- package/plugins/cursor-marketplace/commands/show-lessons.md +0 -27
- package/plugins/cursor-marketplace/hooks/hooks.json +0 -10
- package/plugins/cursor-marketplace/mcp.json +0 -14
- package/plugins/cursor-marketplace/rules/feedback-capture.mdc +0 -34
- package/plugins/cursor-marketplace/rules/pre-action-gates.mdc +0 -30
- package/plugins/cursor-marketplace/rules/session-continuity.mdc +0 -28
- package/plugins/cursor-marketplace/scripts/gate-check.sh +0 -21
- package/plugins/cursor-marketplace/skills/capture-feedback/SKILL.md +0 -48
- package/plugins/cursor-marketplace/skills/prevention-rules/SKILL.md +0 -31
- package/plugins/cursor-marketplace/skills/recall-context/SKILL.md +0 -30
- package/plugins/cursor-marketplace/skills/search-lessons/SKILL.md +0 -33
- package/plugins/gemini-extension/INSTALL.md +0 -92
- package/plugins/gemini-extension/gemini_prompt.txt +0 -14
- package/plugins/gemini-extension/tool_contract.json +0 -45
- package/plugins/opencode-profile/INSTALL.md +0 -57
- package/public/assets/instagram-card.png +0 -0
- package/public/assets/tiktok-agent-memory.mp4 +0 -0
- package/public/blog.html +0 -474
- package/public/compare/mem0.html +0 -189
- package/public/compare/speclock.html +0 -180
- package/public/compare.html +0 -310
- package/public/dashboard.html +0 -1100
- package/public/guide.html +0 -317
- package/public/guides/claude-code-prevent-repeated-mistakes.html +0 -161
- package/public/guides/codex-cli-guardrails.html +0 -158
- package/public/guides/cursor-prevent-repeated-mistakes.html +0 -161
- package/public/guides/pre-action-gates.html +0 -162
- package/public/guides/stop-repeated-ai-agent-mistakes.html +0 -159
- package/public/index.html +0 -1128
- package/public/js/buyer-intent.js +0 -252
- package/public/learn/agent-harness-pattern.html +0 -180
- package/public/learn/ai-agent-persistent-memory.html +0 -203
- package/public/learn/learn.css +0 -45
- package/public/learn/mcp-pre-action-gates-explained.html +0 -172
- package/public/learn/stop-ai-agent-force-push.html +0 -134
- package/public/learn/vibe-coding-safety-net.html +0 -142
- package/public/learn.html +0 -274
- package/public/lessons.html +0 -967
- package/public/llm-context.md +0 -140
- package/public/pro.html +0 -1087
- package/public/vercel.json +0 -8
- package/scripts/a2ui-engine.js +0 -73
- package/scripts/adk-consolidator.js +0 -274
- package/scripts/agent-security-hardening.js +0 -225
- package/scripts/ai-search-visibility.js +0 -142
- package/scripts/autonomous-sales-agent.js +0 -39
- package/scripts/autoresearch-runner.js +0 -216
- package/scripts/background-agent-governance.js +0 -229
- package/scripts/behavioral-extraction.js +0 -93
- package/scripts/budget-enforcer.js +0 -173
- package/scripts/budget-guard.js +0 -173
- package/scripts/build-claude-mcpb.js +0 -255
- package/scripts/build-codex-plugin.js +0 -152
- package/scripts/capture-railway-diagnostics.sh +0 -97
- package/scripts/changeset-check.js +0 -372
- package/scripts/check-congruence.js +0 -443
- package/scripts/computer-use-firewall.js +0 -280
- package/scripts/content-engine/linkedin-content-generator.js +0 -154
- package/scripts/content-engine/output/linkedin-memento-validation.md +0 -17
- package/scripts/content-engine/output/linkedin-posts-2026-04-09.md +0 -175
- package/scripts/content-engine/reddit-thread-finder.js +0 -154
- package/scripts/context-engine.js +0 -710
- package/scripts/daily-digest.js +0 -11
- package/scripts/data-governance.js +0 -173
- package/scripts/deploy-gcp.sh +0 -44
- package/scripts/deploy-policy.js +0 -249
- package/scripts/disagreement-mining.js +0 -315
- package/scripts/dpo-optimizer.js +0 -206
- package/scripts/ensure-repo-bootstrap.js +0 -130
- package/scripts/ephemeral-agent-store.js +0 -212
- package/scripts/eval-harness.js +0 -56
- package/scripts/export-kto-pairs.js +0 -309
- package/scripts/export-training.js +0 -446
- package/scripts/feedback-fallback.js +0 -111
- package/scripts/feedback-inbox-read.js +0 -162
- package/scripts/feedback-root-consolidator.js +0 -233
- package/scripts/feedback-to-memory.js +0 -185
- package/scripts/gate-satisfy.js +0 -42
- package/scripts/generate-paperbanana-diagrams.sh +0 -99
- package/scripts/generate-pretool-hook.sh +0 -40
- package/scripts/github-about.js +0 -430
- package/scripts/github-outreach.js +0 -65
- package/scripts/gtm-revenue-loop.js +0 -535
- package/scripts/hallucination-detector.js +0 -226
- package/scripts/hf-papers.js +0 -317
- package/scripts/hook-auto-capture.sh +0 -100
- package/scripts/hook-stop-pr-thread-check.sh +0 -68
- package/scripts/hook-stop-self-score.sh +0 -51
- package/scripts/hook-stop-verify-deploy.sh +0 -31
- package/scripts/hook-verify-before-done.sh +0 -20
- package/scripts/managed-dpo-export.js +0 -91
- package/scripts/markdown-escape.js +0 -12
- package/scripts/marketing-experiment.js +0 -657
- package/scripts/memalign-recall.js +0 -111
- package/scripts/memory-migration.js +0 -296
- package/scripts/meta-policy.js +0 -190
- package/scripts/metered-billing.js +0 -16
- package/scripts/model-tier-router.js +0 -310
- package/scripts/money-watcher.js +0 -218
- package/scripts/multi-hop-recall.js +0 -240
- package/scripts/per-step-scoring.js +0 -163
- package/scripts/perplexity-marketing.js +0 -466
- package/scripts/pii-scanner.js +0 -153
- package/scripts/plan-gate.js +0 -154
- package/scripts/post-everywhere.js +0 -341
- package/scripts/post-to-x-retry.sh +0 -22
- package/scripts/post-to-x.js +0 -369
- package/scripts/pr-manager.js +0 -421
- package/scripts/principle-extractor.js +0 -162
- package/scripts/pro-features.js +0 -41
- package/scripts/prompt-dlp.js +0 -222
- package/scripts/prove-adapters.js +0 -860
- package/scripts/prove-attribution.js +0 -361
- package/scripts/prove-automation.js +0 -651
- package/scripts/prove-autoresearch.js +0 -304
- package/scripts/prove-claim-verification.js +0 -277
- package/scripts/prove-cloudflare-sandbox.js +0 -161
- package/scripts/prove-data-pipeline.js +0 -408
- package/scripts/prove-data-quality.js +0 -227
- package/scripts/prove-evolution.js +0 -352
- package/scripts/prove-harnesses.js +0 -287
- package/scripts/prove-intelligence.js +0 -257
- package/scripts/prove-lancedb.js +0 -425
- package/scripts/prove-local-intelligence.js +0 -340
- package/scripts/prove-loop-closure.js +0 -263
- package/scripts/prove-packaged-runtime.js +0 -326
- package/scripts/prove-predictive-insights.js +0 -355
- package/scripts/prove-runtime.js +0 -363
- package/scripts/prove-seo-gsd.js +0 -234
- package/scripts/prove-settings.js +0 -279
- package/scripts/prove-subway-upgrades.js +0 -277
- package/scripts/prove-tessl.js +0 -229
- package/scripts/prove-training-export.js +0 -325
- package/scripts/prove-workflow-contract.js +0 -112
- package/scripts/prove-xmemory.js +0 -332
- package/scripts/publish-decision.js +0 -159
- package/scripts/ralph-loop.js +0 -376
- package/scripts/ralph-mode-ci.js +0 -331
- package/scripts/reddit-dm-outreach.js +0 -192
- package/scripts/reddit-monitor-cron.sh +0 -26
- package/scripts/reminder-engine.js +0 -132
- package/scripts/revenue-status.js +0 -472
- package/scripts/rotate-stripe-webhook-secret.js +0 -314
- package/scripts/schedule-manager.js +0 -249
- package/scripts/self-healing-check.js +0 -193
- package/scripts/shieldcortex-memory-firewall-runner.mjs +0 -53
- package/scripts/skill-exporter.js +0 -260
- package/scripts/skill-materializer.js +0 -134
- package/scripts/skill-packs.js +0 -136
- package/scripts/skill-proposer.js +0 -99
- package/scripts/skill-quality-tracker.js +0 -282
- package/scripts/slow-loop.js +0 -72
- package/scripts/social-analytics/db/analytics.sqlite +0 -0
- package/scripts/social-analytics/db/schema.sql +0 -32
- package/scripts/social-analytics/digest.js +0 -256
- package/scripts/social-analytics/engagement-audit.js +0 -185
- package/scripts/social-analytics/generate-instagram-card.js +0 -97
- package/scripts/social-analytics/instagram-thumbgate-post.js +0 -111
- package/scripts/social-analytics/install-growth-automation.js +0 -114
- package/scripts/social-analytics/load-env.js +0 -77
- package/scripts/social-analytics/mcp-server.js +0 -289
- package/scripts/social-analytics/normalizer.js +0 -580
- package/scripts/social-analytics/notify.js +0 -162
- package/scripts/social-analytics/poll-all.js +0 -107
- package/scripts/social-analytics/pollers/github.js +0 -195
- package/scripts/social-analytics/pollers/instagram.js +0 -253
- package/scripts/social-analytics/pollers/linkedin.js +0 -340
- package/scripts/social-analytics/pollers/plausible.js +0 -245
- package/scripts/social-analytics/pollers/reddit.js +0 -306
- package/scripts/social-analytics/pollers/threads.js +0 -233
- package/scripts/social-analytics/pollers/tiktok.js +0 -203
- package/scripts/social-analytics/pollers/x.js +0 -227
- package/scripts/social-analytics/pollers/youtube.js +0 -304
- package/scripts/social-analytics/pollers/zernio.js +0 -183
- package/scripts/social-analytics/publish-instagram-thumbgate.js +0 -104
- package/scripts/social-analytics/publish-thumbgate-launch.js +0 -322
- package/scripts/social-analytics/publishers/devto.js +0 -122
- package/scripts/social-analytics/publishers/instagram.js +0 -317
- package/scripts/social-analytics/publishers/linkedin.js +0 -294
- package/scripts/social-analytics/publishers/reddit.js +0 -385
- package/scripts/social-analytics/publishers/threads.js +0 -275
- package/scripts/social-analytics/publishers/tiktok.js +0 -217
- package/scripts/social-analytics/publishers/x.js +0 -259
- package/scripts/social-analytics/publishers/youtube.js +0 -223
- package/scripts/social-analytics/publishers/zernio.js +0 -539
- package/scripts/social-analytics/reconcile-thumbgate-campaign.js +0 -165
- package/scripts/social-analytics/run-digest.js +0 -34
- package/scripts/social-analytics/schedule-thumbgate-campaign.js +0 -275
- package/scripts/social-analytics/store.js +0 -455
- package/scripts/social-analytics/sync-launch-assets.js +0 -185
- package/scripts/social-analytics/utm.js +0 -143
- package/scripts/social-pipeline.js +0 -2626
- package/scripts/social-post-hourly.js +0 -228
- package/scripts/social-quality-gate.js +0 -134
- package/scripts/social-reply-monitor.js +0 -592
- package/scripts/status-dashboard.js +0 -155
- package/scripts/stripe-live-status.js +0 -115
- package/scripts/subagent-profiles.js +0 -79
- package/scripts/sync-branch-protection.js +0 -340
- package/scripts/sync-gh-secrets-from-env.sh +0 -70
- package/scripts/sync-github-about.js +0 -55
- package/scripts/sync-version.js +0 -479
- package/scripts/synthetic-dpo.js +0 -234
- package/scripts/tessl-export.js +0 -369
- package/scripts/test-coverage.js +0 -128
- package/scripts/thumbgate_session_start.sh +0 -32
- package/scripts/train_from_feedback.py +0 -929
- package/scripts/validate-feedback.js +0 -581
- package/scripts/verify-obsidian-setup.sh +0 -269
- package/scripts/verify-run.js +0 -269
- package/scripts/weekly-auto-post.js +0 -124
- package/scripts/x-autonomous-marketing.js +0 -139
package/scripts/skill-packs.js
DELETED
|
@@ -1,136 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
'use strict';
|
|
3
|
-
const fs = require('fs');
|
|
4
|
-
const path = require('path');
|
|
5
|
-
const { registerPreventionRules } = require('./contextfs');
|
|
6
|
-
const SKILL_PACKS_DIR = path.join(__dirname, '..', 'config', 'skill-packs');
|
|
7
|
-
const BUILTIN_PACKS = {
|
|
8
|
-
'stripe-integration': { name: 'stripe-integration', description: 'Stripe API best practices', triggers: ['stripe', 'payment', 'checkout', 'subscription', 'webhook signature'], rules: ['ALWAYS use idempotency keys on PaymentIntent creation to prevent duplicate charges.', 'NEVER log or store raw card numbers — use Stripe tokens or PaymentMethod IDs.', 'ALWAYS verify webhook signatures with stripe.webhooks.constructEvent() before processing.', 'Use Checkout Sessions instead of raw PaymentIntents for new integrations.', 'ALWAYS handle payment_intent.succeeded AND payment_intent.payment_failed webhooks.'], packTemplate: { namespaces: ['memoryError', 'memoryLearning', 'rules'], maxItems: 8, maxChars: 6000, queryPrefix: 'stripe payment checkout webhook idempotency' } },
|
|
9
|
-
'railway-deploy': { name: 'railway-deploy', description: 'Railway deployment best practices', triggers: ['railway', 'deploy', 'dockerfile', 'health check'], rules: ['ALWAYS verify /health endpoint returns new version after deploy.', 'NEVER say "deployed" without curling the health endpoint and showing version match.', 'ALWAYS check Railway build logs for warnings even when deploy succeeds.', 'Use RAILWAY_VOLUME_MOUNT_PATH for persistent data.', 'ALWAYS wait 2-5 minutes after merge before verifying.'], packTemplate: { namespaces: ['memoryError', 'memoryLearning', 'rules'], maxItems: 8, maxChars: 6000, queryPrefix: 'railway deploy health version dockerfile' } },
|
|
10
|
-
'database-migration': { name: 'database-migration', description: 'Database migration best practices', triggers: ['migration', 'prisma', 'sqlite', 'schema', 'alter table'], rules: ['ALWAYS back up the database before running destructive migrations.', 'NEVER drop columns in production without verifying no code references them.', 'ALWAYS run migrations against a test database first.', 'Use reversible migrations — every up() should have a corresponding down().', 'ALWAYS check for pending migrations before deploying new code.'], packTemplate: { namespaces: ['memoryError', 'rules'], maxItems: 6, maxChars: 5000, queryPrefix: 'migration database schema prisma sqlite' } },
|
|
11
|
-
};
|
|
12
|
-
const registry = new Map(); for (const [id, p] of Object.entries(BUILTIN_PACKS)) registry.set(id, p);
|
|
13
|
-
function ensurePacksDir() { if (!fs.existsSync(SKILL_PACKS_DIR)) fs.mkdirSync(SKILL_PACKS_DIR, { recursive: true }); }
|
|
14
|
-
function registerSkillPack(pack) { if (!pack.name) throw new Error('Skill pack requires a name'); if (!Array.isArray(pack.rules) || pack.rules.length === 0) throw new Error('Skill pack requires at least one rule'); const n = { name: pack.name, description: pack.description || '', triggers: Array.isArray(pack.triggers) ? pack.triggers : [], rules: pack.rules, packTemplate: pack.packTemplate || null, registeredAt: new Date().toISOString() }; registry.set(n.name, n); ensurePacksDir(); fs.writeFileSync(path.join(SKILL_PACKS_DIR, `${n.name}.json`), JSON.stringify(n, null, 2) + '\n'); return n; }
|
|
15
|
-
function loadSkillPacksFromDisk() { ensurePacksDir(); for (const f of fs.readdirSync(SKILL_PACKS_DIR).filter((x) => x.endsWith('.json'))) { try { const p = JSON.parse(fs.readFileSync(path.join(SKILL_PACKS_DIR, f), 'utf-8')); if (p.name) registry.set(p.name, p); } catch { /* skip */ } } }
|
|
16
|
-
function listSkillPacks() { loadSkillPacksFromDisk(); return Array.from(registry.values()).map((p) => ({ name: p.name, description: p.description, triggers: p.triggers, ruleCount: p.rules.length, hasPackTemplate: !!p.packTemplate })); }
|
|
17
|
-
function getSkillPack(name) { loadSkillPacksFromDisk(); return registry.get(name) || null; }
|
|
18
|
-
function matchSkillPacks(query) { const tokens = String(query || '').toLowerCase().split(/\s+/).filter(Boolean); if (tokens.length === 0) return []; loadSkillPacksFromDisk(); const scored = []; for (const pack of registry.values()) { let score = 0; for (const trigger of pack.triggers) { for (const t of trigger.toLowerCase().split(/\s+/)) { if (tokens.some((qt) => qt.includes(t) || t.includes(qt))) score += 1; } } if (score > 0) scored.push({ pack, score }); } return scored.sort((a, b) => b.score - a.score).map((s) => s.pack); }
|
|
19
|
-
function installSkillPackRules(name) { const pack = getSkillPack(name); if (!pack) throw new Error(`Skill pack not found: "${name}"`); return registerPreventionRules([`# Skill Pack: ${pack.name}`, '', pack.description || '', '', ...pack.rules.map((r, i) => `${i + 1}. ${r}`)].join('\n'), { skillPack: pack.name }); }
|
|
20
|
-
// ---------------------------------------------------------------------------
|
|
21
|
-
// L3 Resource Loading (ADK progressive disclosure)
|
|
22
|
-
// ---------------------------------------------------------------------------
|
|
23
|
-
|
|
24
|
-
const RESOURCES_DIR_NAME = 'references';
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Load an L3 resource file for a skill pack.
|
|
28
|
-
* Resources live in config/skill-packs/{pack-name}/references/{filename}.
|
|
29
|
-
*/
|
|
30
|
-
function loadSkillResource(packName, resourceName) {
|
|
31
|
-
const resDir = path.join(SKILL_PACKS_DIR, packName, RESOURCES_DIR_NAME);
|
|
32
|
-
const resPath = path.join(resDir, resourceName);
|
|
33
|
-
if (!fs.existsSync(resPath)) return null;
|
|
34
|
-
return { name: resourceName, path: resPath, content: fs.readFileSync(resPath, 'utf-8'), sizeBytes: fs.statSync(resPath).size };
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* List available L3 resources for a skill pack.
|
|
39
|
-
*/
|
|
40
|
-
function listSkillResources(packName) {
|
|
41
|
-
const resDir = path.join(SKILL_PACKS_DIR, packName, RESOURCES_DIR_NAME);
|
|
42
|
-
if (!fs.existsSync(resDir)) return [];
|
|
43
|
-
return fs.readdirSync(resDir).filter((f) => !f.startsWith('.')).map((f) => {
|
|
44
|
-
const fp = path.join(resDir, f);
|
|
45
|
-
return { name: f, sizeBytes: fs.statSync(fp).size };
|
|
46
|
-
});
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Add an L3 resource file to a skill pack.
|
|
51
|
-
*/
|
|
52
|
-
function addSkillResource(packName, resourceName, content) {
|
|
53
|
-
const resDir = path.join(SKILL_PACKS_DIR, packName, RESOURCES_DIR_NAME);
|
|
54
|
-
ensurePacksDir();
|
|
55
|
-
if (!fs.existsSync(resDir)) fs.mkdirSync(resDir, { recursive: true });
|
|
56
|
-
const resPath = path.join(resDir, resourceName);
|
|
57
|
-
fs.writeFileSync(resPath, content);
|
|
58
|
-
return { name: resourceName, path: resPath, sizeBytes: Buffer.byteLength(content) };
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// ---------------------------------------------------------------------------
|
|
62
|
-
// Skill Factory — agent-driven skill generation
|
|
63
|
-
// ---------------------------------------------------------------------------
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* Auto-generate a skill pack from recurring failure patterns.
|
|
67
|
-
* Uses distilled lessons to propose rules for a new domain.
|
|
68
|
-
*
|
|
69
|
-
* @param {Object} opts
|
|
70
|
-
* @param {string} opts.domain - Domain name (e.g., 'graphql-api')
|
|
71
|
-
* @param {Array} opts.lessons - Array of lesson strings from history distiller
|
|
72
|
-
* @param {string} [opts.description] - Pack description
|
|
73
|
-
* @param {Array} [opts.triggers] - Trigger keywords
|
|
74
|
-
* @returns {Object} The created skill pack
|
|
75
|
-
*/
|
|
76
|
-
function generateSkillPack({ domain, lessons, description, triggers } = {}) {
|
|
77
|
-
if (!domain) throw new Error('Skill factory requires a domain name');
|
|
78
|
-
if (!Array.isArray(lessons) || lessons.length === 0) throw new Error('Skill factory requires at least one lesson');
|
|
79
|
-
|
|
80
|
-
// Convert lessons into NEVER/ALWAYS rules
|
|
81
|
-
const rules = lessons.map((lesson) => {
|
|
82
|
-
const l = String(lesson).trim();
|
|
83
|
-
if (/^(NEVER|ALWAYS|DO NOT|MUST)/i.test(l)) return l;
|
|
84
|
-
if (/fail|error|broke|wrong|bug|crash/i.test(l)) return `NEVER ${l.replace(/^(avoid|don'?t|stop)\s*/i, '').trim()}`;
|
|
85
|
-
return `ALWAYS ${l.replace(/^(repeat|keep|continue)\s*/i, '').trim()}`;
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
// Infer triggers from domain + lesson content
|
|
89
|
-
const inferredTriggers = triggers || [domain, ...domain.split('-').filter((t) => t.length > 2)];
|
|
90
|
-
|
|
91
|
-
return registerSkillPack({
|
|
92
|
-
name: domain,
|
|
93
|
-
description: description || `Auto-generated skill pack for ${domain} from ${lessons.length} lessons`,
|
|
94
|
-
triggers: inferredTriggers,
|
|
95
|
-
rules,
|
|
96
|
-
packTemplate: { namespaces: ['memoryError', 'memoryLearning', 'rules'], maxItems: 8, maxChars: 6000, queryPrefix: inferredTriggers.join(' ') },
|
|
97
|
-
});
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
// ---------------------------------------------------------------------------
|
|
101
|
-
// Token-Efficient Progressive Disclosure Metrics
|
|
102
|
-
// ---------------------------------------------------------------------------
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
* Measure token cost of each disclosure level for a skill pack.
|
|
106
|
-
* Helps agents decide which packs to load.
|
|
107
|
-
*/
|
|
108
|
-
function measureSkillTokens(packName) {
|
|
109
|
-
const pack = getSkillPack(packName);
|
|
110
|
-
if (!pack) return null;
|
|
111
|
-
|
|
112
|
-
// L1: metadata only (~name + description + triggers)
|
|
113
|
-
const l1Text = `${pack.name}: ${pack.description} [${(pack.triggers || []).join(', ')}]`;
|
|
114
|
-
const l1Chars = l1Text.length;
|
|
115
|
-
|
|
116
|
-
// L2: full rules
|
|
117
|
-
const l2Text = pack.rules.join('\n');
|
|
118
|
-
const l2Chars = l2Text.length;
|
|
119
|
-
|
|
120
|
-
// L3: resources
|
|
121
|
-
const resources = listSkillResources(packName);
|
|
122
|
-
const l3Chars = resources.reduce((sum, r) => sum + r.sizeBytes, 0);
|
|
123
|
-
|
|
124
|
-
const totalChars = l1Chars + l2Chars + l3Chars;
|
|
125
|
-
|
|
126
|
-
return {
|
|
127
|
-
packName,
|
|
128
|
-
l1: { chars: l1Chars, estimatedTokens: Math.ceil(l1Chars / 4) },
|
|
129
|
-
l2: { chars: l2Chars, estimatedTokens: Math.ceil(l2Chars / 4), ruleCount: pack.rules.length },
|
|
130
|
-
l3: { chars: l3Chars, estimatedTokens: Math.ceil(l3Chars / 4), resourceCount: resources.length },
|
|
131
|
-
total: { chars: totalChars, estimatedTokens: Math.ceil(totalChars / 4) },
|
|
132
|
-
disclosureSavings: totalChars > 0 ? Math.round((1 - l1Chars / totalChars) * 100) : 0,
|
|
133
|
-
};
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
module.exports = { BUILTIN_PACKS, registerSkillPack, listSkillPacks, getSkillPack, matchSkillPacks, installSkillPackRules, SKILL_PACKS_DIR, loadSkillResource, listSkillResources, addSkillResource, generateSkillPack, measureSkillTokens };
|
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* Skill Proposer (EvoSkill Phase 1)
|
|
4
|
-
*
|
|
5
|
-
* Analyzes recurring failure patterns in memory-log.jsonl.
|
|
6
|
-
* Diagnoses the root cause using 'Reasoning' traces and proposes
|
|
7
|
-
* a new functional skill (tool) to solve the capability gap.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
const fs = require('fs');
|
|
11
|
-
const path = require('path');
|
|
12
|
-
const {
|
|
13
|
-
parseFeedbackFile,
|
|
14
|
-
clusterByTags,
|
|
15
|
-
extractTags,
|
|
16
|
-
discoverFeedbackDir
|
|
17
|
-
} = require('./skill-generator');
|
|
18
|
-
|
|
19
|
-
function proposeSkills(options = {}) {
|
|
20
|
-
const feedbackDir = options.feedbackDir || discoverFeedbackDir();
|
|
21
|
-
const logPath = path.join(feedbackDir, 'memory-log.jsonl');
|
|
22
|
-
const proposalsDir = path.join(feedbackDir, 'skill-proposals');
|
|
23
|
-
|
|
24
|
-
const memories = parseFeedbackFile(logPath);
|
|
25
|
-
const mistakes = memories.filter(m => m.category === 'error' || m.title.startsWith('MISTAKE:'));
|
|
26
|
-
|
|
27
|
-
if (mistakes.length === 0) {
|
|
28
|
-
console.log('No mistakes found in memory log.');
|
|
29
|
-
return [];
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
// Cluster by tags (EvoSkill refinement)
|
|
33
|
-
const clusters = clusterByTags(mistakes, 2);
|
|
34
|
-
const proposals = [];
|
|
35
|
-
|
|
36
|
-
for (const [tagKey, cluster] of clusters) {
|
|
37
|
-
if (cluster.entries.length < 2) continue; // Lower threshold for autonomous discovery
|
|
38
|
-
|
|
39
|
-
console.log(`Analyzing cluster: [${tagKey}] (${cluster.entries.length} evidences)`);
|
|
40
|
-
|
|
41
|
-
// Extract root cause from reasoning traces
|
|
42
|
-
const reasoningTraces = cluster.entries
|
|
43
|
-
.map(e => {
|
|
44
|
-
const match = e.content.match(/Reasoning: (.*)/);
|
|
45
|
-
return match ? match[1] : null;
|
|
46
|
-
})
|
|
47
|
-
.filter(Boolean);
|
|
48
|
-
|
|
49
|
-
const commonProblem = cluster.entries[0].title.replace('MISTAKE: ', '');
|
|
50
|
-
const tags = cluster.tags;
|
|
51
|
-
|
|
52
|
-
const proposal = {
|
|
53
|
-
id: `prop_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
|
|
54
|
-
status: 'pending',
|
|
55
|
-
problem: commonProblem,
|
|
56
|
-
diagnosis: reasoningTraces.length > 0 ? reasoningTraces[0] : 'Repeated execution failure in this domain.',
|
|
57
|
-
suggestedSkill: {
|
|
58
|
-
name: `solve-${tags[0]}-${tags[1] || 'logic'}`.toLowerCase().replace(/[^a-z-]/g, ''),
|
|
59
|
-
description: `Automated skill to handle ${tags.join(', ')} patterns efficiently.`,
|
|
60
|
-
tags,
|
|
61
|
-
// Propose a generic tool structure that the Materializer can flesh out
|
|
62
|
-
toolSpec: {
|
|
63
|
-
name: `handle_${tags[0].replace(/-/g, '_')}`,
|
|
64
|
-
description: `Fixes ${commonProblem}`,
|
|
65
|
-
parameters: {
|
|
66
|
-
type: 'object',
|
|
67
|
-
properties: {
|
|
68
|
-
context: { type: 'string', description: 'The current task context' }
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
},
|
|
73
|
-
evidenceIds: cluster.entries.map(e => e.id),
|
|
74
|
-
timestamp: new Date().toISOString()
|
|
75
|
-
};
|
|
76
|
-
|
|
77
|
-
proposals.push(proposal);
|
|
78
|
-
|
|
79
|
-
if (!fs.existsSync(proposalsDir)) fs.mkdirSync(proposalsDir, { recursive: true });
|
|
80
|
-
fs.writeFileSync(
|
|
81
|
-
path.join(proposalsDir, `${proposal.suggestedSkill.name}.json`),
|
|
82
|
-
JSON.stringify(proposal, null, 2)
|
|
83
|
-
);
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
return proposals;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
if (require.main === module) {
|
|
90
|
-
const props = proposeSkills();
|
|
91
|
-
if (props && props.length > 0) {
|
|
92
|
-
const feedbackDir = discoverFeedbackDir();
|
|
93
|
-
const proposalsDir = path.join(feedbackDir, 'skill-proposals');
|
|
94
|
-
console.log(`\nGenerated ${props.length} skill proposals in ${proposalsDir}`);
|
|
95
|
-
props.forEach(p => console.log(` - ${p.suggestedSkill.name}: ${p.problem}`));
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
module.exports = { proposeSkills };
|
|
@@ -1,282 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* Skill Quality Tracker
|
|
4
|
-
*
|
|
5
|
-
* Correlates tool call metrics to feedback signals by timestamp proximity.
|
|
6
|
-
* After a sequence of tool calls and feedback captures, produces a per-skill
|
|
7
|
-
* quality score derived from timestamp-proximity correlation.
|
|
8
|
-
*
|
|
9
|
-
* Ported from Subway_RN_Demo/.claude/scripts/feedback/skill-quality-tracker.js
|
|
10
|
-
* PATH: PROJECT_ROOT = path.join(__dirname, '..') — 1 level up from scripts/
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
'use strict';
|
|
14
|
-
|
|
15
|
-
const fs = require('fs');
|
|
16
|
-
const readline = require('readline');
|
|
17
|
-
const path = require('path');
|
|
18
|
-
const { resolveFeedbackDir } = require('./feedback-paths');
|
|
19
|
-
|
|
20
|
-
const METRICS_PATH = process.env.METRICS_PATH
|
|
21
|
-
|| path.join(resolveFeedbackDir(), 'tool-metrics.jsonl');
|
|
22
|
-
|
|
23
|
-
const FEEDBACK_PATH = process.env.FEEDBACK_PATH
|
|
24
|
-
|| path.join(resolveFeedbackDir(), 'feedback-log.jsonl');
|
|
25
|
-
|
|
26
|
-
// Correlation window: feedback within 60 seconds of a tool call is considered correlated
|
|
27
|
-
const CORRELATION_WINDOW_MS = 60_000;
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Safely parse a single JSON line.
|
|
31
|
-
*
|
|
32
|
-
* @param {string} line
|
|
33
|
-
* @returns {object|null}
|
|
34
|
-
*/
|
|
35
|
-
function parseLine(line) {
|
|
36
|
-
try {
|
|
37
|
-
return JSON.parse(line);
|
|
38
|
-
} catch {
|
|
39
|
-
return null;
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Load feedback entries from JSONL file.
|
|
45
|
-
* Each entry needs: timestamp, feedback (signal).
|
|
46
|
-
*
|
|
47
|
-
* @param {string} filePath
|
|
48
|
-
* @returns {Promise<Array<{ ts: number, feedback: string, tool: string|null }>>}
|
|
49
|
-
*/
|
|
50
|
-
async function loadFeedback(filePath) {
|
|
51
|
-
const entries = [];
|
|
52
|
-
if (!fs.existsSync(filePath)) return entries;
|
|
53
|
-
|
|
54
|
-
const rl = readline.createInterface({
|
|
55
|
-
input: fs.createReadStream(filePath),
|
|
56
|
-
crlfDelay: Infinity,
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
for await (const line of rl) {
|
|
60
|
-
const obj = parseLine(line);
|
|
61
|
-
if (obj && obj.timestamp) {
|
|
62
|
-
// Support both 'feedback' (Subway) and 'signal' (ThumbGate) field names
|
|
63
|
-
const feedbackVal = obj.feedback || obj.signal;
|
|
64
|
-
if (feedbackVal) {
|
|
65
|
-
// Normalize to 'positive'/'negative' regardless of source schema
|
|
66
|
-
let normalized = feedbackVal;
|
|
67
|
-
if (feedbackVal === 'up') normalized = 'positive';
|
|
68
|
-
else if (feedbackVal === 'down') normalized = 'negative';
|
|
69
|
-
|
|
70
|
-
entries.push({
|
|
71
|
-
ts: new Date(obj.timestamp).getTime(),
|
|
72
|
-
feedback: normalized,
|
|
73
|
-
tool: obj.tool_name || null,
|
|
74
|
-
});
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
entries.sort((a, b) => a.ts - b.ts);
|
|
80
|
-
return entries;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* Find correlated feedback for a tool call by timestamp proximity.
|
|
85
|
-
*
|
|
86
|
-
* Searches feedback entries within CORRELATION_WINDOW_MS of the metric timestamp.
|
|
87
|
-
* If the feedback entry has a tool_name, it must match the metric's tool name.
|
|
88
|
-
*
|
|
89
|
-
* @param {number} metricTs - Timestamp of the tool call (ms)
|
|
90
|
-
* @param {string} metricTool - Tool name
|
|
91
|
-
* @param {Array<{ ts: number, feedback: string, tool: string|null }>} feedbackEntries
|
|
92
|
-
* @returns {string|null} 'positive', 'negative', or null if no correlation found
|
|
93
|
-
*/
|
|
94
|
-
function correlateFeedback(metricTs, metricTool, feedbackEntries) {
|
|
95
|
-
for (const fb of feedbackEntries) {
|
|
96
|
-
if (Math.abs(fb.ts - metricTs) <= CORRELATION_WINDOW_MS) {
|
|
97
|
-
// If feedback has a tool name, it must match; otherwise correlate by time alone
|
|
98
|
-
if (!fb.tool || fb.tool === metricTool) {
|
|
99
|
-
return fb.feedback;
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
return null;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
/**
|
|
107
|
-
* Process tool metrics JSONL and correlate with feedback.
|
|
108
|
-
*
|
|
109
|
-
* @param {string} metricsPath
|
|
110
|
-
* @param {Array<{ ts: number, feedback: string, tool: string|null }>} feedbackEntries
|
|
111
|
-
* @returns {Promise<{ totalToolUses: number, breakdown: object }>}
|
|
112
|
-
*/
|
|
113
|
-
async function processMetrics(metricsPath, feedbackEntries) {
|
|
114
|
-
const breakdown = {};
|
|
115
|
-
let totalToolUses = 0;
|
|
116
|
-
|
|
117
|
-
if (!fs.existsSync(metricsPath)) return { totalToolUses, breakdown };
|
|
118
|
-
|
|
119
|
-
const rl = readline.createInterface({
|
|
120
|
-
input: fs.createReadStream(metricsPath),
|
|
121
|
-
crlfDelay: Infinity,
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
for await (const line of rl) {
|
|
125
|
-
const obj = parseLine(line);
|
|
126
|
-
if (!obj || !obj.tool_name) continue;
|
|
127
|
-
|
|
128
|
-
totalToolUses++;
|
|
129
|
-
const name = obj.tool_name;
|
|
130
|
-
|
|
131
|
-
if (!breakdown[name]) {
|
|
132
|
-
breakdown[name] = { uses: 0, correlatedPositive: 0, correlatedNegative: 0 };
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
breakdown[name].uses++;
|
|
136
|
-
|
|
137
|
-
const ts = new Date(obj.timestamp).getTime();
|
|
138
|
-
if (!isNaN(ts)) {
|
|
139
|
-
const signal = correlateFeedback(ts, name, feedbackEntries);
|
|
140
|
-
if (signal === 'positive') breakdown[name].correlatedPositive++;
|
|
141
|
-
else if (signal === 'negative') breakdown[name].correlatedNegative++;
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
return { totalToolUses, breakdown };
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
/**
|
|
149
|
-
* Compute per-tool success rates from correlation counts.
|
|
150
|
-
* Mutates the breakdown object in place.
|
|
151
|
-
*
|
|
152
|
-
* @param {object} breakdown - { toolName: { uses, correlatedPositive, correlatedNegative } }
|
|
153
|
-
*/
|
|
154
|
-
function computeSuccessRates(breakdown) {
|
|
155
|
-
for (const tool of Object.values(breakdown)) {
|
|
156
|
-
const correlated = tool.correlatedPositive + tool.correlatedNegative;
|
|
157
|
-
tool.successRate = correlated > 0
|
|
158
|
-
? +(tool.correlatedPositive / correlated).toFixed(4)
|
|
159
|
-
: null;
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
/**
|
|
164
|
-
* Return top-performing tools sorted by success rate.
|
|
165
|
-
*
|
|
166
|
-
* @param {object} breakdown
|
|
167
|
-
* @param {number} [min=10] - Minimum uses threshold
|
|
168
|
-
* @param {number} [limit=5] - Maximum entries to return
|
|
169
|
-
* @returns {Array<{ tool: string, successRate: number, uses: number }>}
|
|
170
|
-
*/
|
|
171
|
-
function topPerformers(breakdown, min = 10, limit = 5) {
|
|
172
|
-
return Object.entries(breakdown)
|
|
173
|
-
.filter(([, v]) => v.uses >= min && v.successRate !== null)
|
|
174
|
-
.sort((a, b) => b[1].successRate - a[1].successRate || b[1].uses - a[1].uses)
|
|
175
|
-
.slice(0, limit)
|
|
176
|
-
.map(([name, v]) => ({ tool: name, successRate: v.successRate, uses: v.uses }));
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
/**
|
|
180
|
-
* Return tools with high negative correlation (potential trouble spots).
|
|
181
|
-
* Threshold: >30% negative rate among correlated feedback.
|
|
182
|
-
*
|
|
183
|
-
* @param {object} breakdown
|
|
184
|
-
* @returns {Array<{ tool: string, negativeRate: number, uses: number }>}
|
|
185
|
-
*/
|
|
186
|
-
function troubleSpots(breakdown) {
|
|
187
|
-
return Object.entries(breakdown)
|
|
188
|
-
.filter(([, v]) => {
|
|
189
|
-
const total = v.correlatedPositive + v.correlatedNegative;
|
|
190
|
-
return total > 0 && v.correlatedNegative / total > 0.3;
|
|
191
|
-
})
|
|
192
|
-
.map(([name, v]) => {
|
|
193
|
-
const total = v.correlatedPositive + v.correlatedNegative;
|
|
194
|
-
return {
|
|
195
|
-
tool: name,
|
|
196
|
-
negativeRate: +(v.correlatedNegative / total).toFixed(4),
|
|
197
|
-
uses: v.uses,
|
|
198
|
-
};
|
|
199
|
-
})
|
|
200
|
-
.sort((a, b) => b.negativeRate - a.negativeRate);
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
/**
|
|
204
|
-
* Generate actionable recommendations from top performers and trouble spots.
|
|
205
|
-
*
|
|
206
|
-
* @param {Array} top - topPerformers result
|
|
207
|
-
* @param {Array} trouble - troubleSpots result
|
|
208
|
-
* @param {object} breakdown - full breakdown
|
|
209
|
-
* @returns {string[]}
|
|
210
|
-
*/
|
|
211
|
-
function generateRecommendations(top, trouble, breakdown) {
|
|
212
|
-
const recs = [];
|
|
213
|
-
|
|
214
|
-
for (const t of trouble) {
|
|
215
|
-
recs.push(
|
|
216
|
-
`Investigate "${t.tool}" — ${(t.negativeRate * 100).toFixed(1)}% negative correlation across ${t.uses} uses.`
|
|
217
|
-
);
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
if (top.length > 0) {
|
|
221
|
-
recs.push(
|
|
222
|
-
`"${top[0].tool}" is the top performer (${(top[0].successRate * 100).toFixed(1)}% success). Consider expanding its usage patterns.`
|
|
223
|
-
);
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
const uncorrelated = Object.entries(breakdown).filter(
|
|
227
|
-
([, v]) => v.uses >= 10 && v.successRate === null
|
|
228
|
-
);
|
|
229
|
-
if (uncorrelated.length > 0) {
|
|
230
|
-
recs.push(
|
|
231
|
-
`${uncorrelated.length} tool(s) with 10+ uses have no correlated feedback — consider adding coverage.`
|
|
232
|
-
);
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
if (recs.length === 0) recs.push('No actionable recommendations at this time.');
|
|
236
|
-
return recs;
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
/**
|
|
240
|
-
* Main entry point: load data, correlate, produce report.
|
|
241
|
-
*
|
|
242
|
-
* @returns {Promise<object>} Full skill quality report
|
|
243
|
-
*/
|
|
244
|
-
async function run() {
|
|
245
|
-
const feedbackEntries = await loadFeedback(FEEDBACK_PATH);
|
|
246
|
-
const { totalToolUses, breakdown } = await processMetrics(METRICS_PATH, feedbackEntries);
|
|
247
|
-
|
|
248
|
-
computeSuccessRates(breakdown);
|
|
249
|
-
|
|
250
|
-
const top = topPerformers(breakdown);
|
|
251
|
-
const trouble = troubleSpots(breakdown);
|
|
252
|
-
const recommendations = generateRecommendations(top, trouble, breakdown);
|
|
253
|
-
|
|
254
|
-
const report = {
|
|
255
|
-
generatedAt: new Date().toISOString(),
|
|
256
|
-
totalToolUses,
|
|
257
|
-
toolBreakdown: breakdown,
|
|
258
|
-
topPerformers: top,
|
|
259
|
-
troubleSpots: trouble,
|
|
260
|
-
recommendations,
|
|
261
|
-
};
|
|
262
|
-
|
|
263
|
-
console.log(JSON.stringify(report, null, 2));
|
|
264
|
-
return report;
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
if (require.main === module) {
|
|
268
|
-
run().catch(() => {}).finally(() => process.exit(0));
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
module.exports = {
|
|
272
|
-
parseLine,
|
|
273
|
-
correlateFeedback,
|
|
274
|
-
computeSuccessRates,
|
|
275
|
-
topPerformers,
|
|
276
|
-
troubleSpots,
|
|
277
|
-
generateRecommendations,
|
|
278
|
-
loadFeedback,
|
|
279
|
-
processMetrics,
|
|
280
|
-
run,
|
|
281
|
-
CORRELATION_WINDOW_MS,
|
|
282
|
-
};
|
package/scripts/slow-loop.js
DELETED
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
'use strict';
|
|
3
|
-
|
|
4
|
-
const fs = require('fs');
|
|
5
|
-
const path = require('path');
|
|
6
|
-
const { createSchedule } = require('./schedule-manager');
|
|
7
|
-
const { resolveFeedbackDir } = require('./feedback-paths');
|
|
8
|
-
|
|
9
|
-
const IDLE_THRESHOLD_MINUTES = 30;
|
|
10
|
-
const SLOW_LOOP_STATE_FILE = 'slow-loop-state.json';
|
|
11
|
-
|
|
12
|
-
function getStatePath() {
|
|
13
|
-
return path.join(resolveFeedbackDir(), SLOW_LOOP_STATE_FILE);
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
function loadState() {
|
|
17
|
-
const p = getStatePath();
|
|
18
|
-
if (!fs.existsSync(p)) return { lastExportAt: null, exportCount: 0, lastIdleCheckAt: null, totalPairsExported: 0 };
|
|
19
|
-
try { return JSON.parse(fs.readFileSync(p, 'utf-8')); } catch { return { lastExportAt: null, exportCount: 0, lastIdleCheckAt: null, totalPairsExported: 0 }; }
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
function saveState(state) {
|
|
23
|
-
const p = getStatePath();
|
|
24
|
-
const dir = path.dirname(p);
|
|
25
|
-
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
26
|
-
fs.writeFileSync(p, JSON.stringify(state, null, 2) + '\n');
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
function isIdle({ thresholdMinutes = IDLE_THRESHOLD_MINUTES } = {}) {
|
|
30
|
-
const feedbackDir = resolveFeedbackDir();
|
|
31
|
-
const logPath = path.join(feedbackDir, 'feedback-log.jsonl');
|
|
32
|
-
if (!fs.existsSync(logPath)) return true;
|
|
33
|
-
try {
|
|
34
|
-
const stats = fs.statSync(logPath);
|
|
35
|
-
const minutesSinceModified = (Date.now() - stats.mtimeMs) / (1000 * 60);
|
|
36
|
-
return minutesSinceModified >= thresholdMinutes;
|
|
37
|
-
} catch { return true; }
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
function runSlowLoop({ thresholdMinutes = IDLE_THRESHOLD_MINUTES, force = false } = {}) {
|
|
41
|
-
const state = loadState();
|
|
42
|
-
const idle = force || isIdle({ thresholdMinutes });
|
|
43
|
-
if (!idle) { state.lastIdleCheckAt = new Date().toISOString(); saveState(state); return { action: 'skipped', reason: 'system not idle', idle: false, state }; }
|
|
44
|
-
|
|
45
|
-
const feedbackDir = resolveFeedbackDir();
|
|
46
|
-
const logPath = path.join(feedbackDir, 'feedback-log.jsonl');
|
|
47
|
-
let newEntries = 0;
|
|
48
|
-
if (fs.existsSync(logPath)) {
|
|
49
|
-
const totalEntries = fs.readFileSync(logPath, 'utf-8').trim().split('\n').filter(Boolean).length;
|
|
50
|
-
newEntries = totalEntries - (state.lastFeedbackCount || 0);
|
|
51
|
-
state.lastFeedbackCount = totalEntries;
|
|
52
|
-
}
|
|
53
|
-
if (newEntries <= 0 && !force) { state.lastIdleCheckAt = new Date().toISOString(); saveState(state); return { action: 'skipped', reason: 'no new feedback since last export', idle: true, newEntries: 0, state }; }
|
|
54
|
-
|
|
55
|
-
let dpoResult = null;
|
|
56
|
-
try { const { exportDpoPairs } = require('./feedback-loop'); dpoResult = exportDpoPairs(); } catch (err) { dpoResult = { error: err.message, pairsExported: 0 }; }
|
|
57
|
-
|
|
58
|
-
const pairsExported = dpoResult && dpoResult.pairs ? dpoResult.pairs.length : (dpoResult && dpoResult.pairsExported) || 0;
|
|
59
|
-
state.lastExportAt = new Date().toISOString();
|
|
60
|
-
state.exportCount = (state.exportCount || 0) + 1;
|
|
61
|
-
state.totalPairsExported = (state.totalPairsExported || 0) + pairsExported;
|
|
62
|
-
state.lastIdleCheckAt = new Date().toISOString();
|
|
63
|
-
saveState(state);
|
|
64
|
-
return { action: 'exported', idle: true, newEntries, pairsExported, totalExports: state.exportCount, totalPairsExported: state.totalPairsExported, exportedAt: state.lastExportAt, state };
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
function createSlowLoopSchedule({ schedule = 'hourly', thresholdMinutes = IDLE_THRESHOLD_MINUTES } = {}) {
|
|
68
|
-
const command = [`const sl = require(${JSON.stringify(__filename)});`, `const result = sl.runSlowLoop(${JSON.stringify({ thresholdMinutes })});`, 'process.stdout.write(JSON.stringify(result, null, 2) + "\\n");'].join(' ');
|
|
69
|
-
return createSchedule({ id: 'thumbgate-slow-loop', name: 'ThumbGate Slow Loop (DPO Export)', description: `Idle-time DPO export, runs ${schedule}`, schedule, command });
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
module.exports = { isIdle, runSlowLoop, createSlowLoopSchedule, loadState, getStatePath };
|
|
File without changes
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
CREATE TABLE IF NOT EXISTS engagement_metrics (
|
|
2
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
3
|
-
platform TEXT NOT NULL,
|
|
4
|
-
content_type TEXT NOT NULL,
|
|
5
|
-
post_id TEXT NOT NULL,
|
|
6
|
-
post_url TEXT,
|
|
7
|
-
published_at TEXT,
|
|
8
|
-
metric_date TEXT NOT NULL,
|
|
9
|
-
impressions INTEGER DEFAULT 0,
|
|
10
|
-
reach INTEGER DEFAULT 0,
|
|
11
|
-
likes INTEGER DEFAULT 0,
|
|
12
|
-
comments INTEGER DEFAULT 0,
|
|
13
|
-
shares INTEGER DEFAULT 0,
|
|
14
|
-
saves INTEGER DEFAULT 0,
|
|
15
|
-
clicks INTEGER DEFAULT 0,
|
|
16
|
-
video_views INTEGER DEFAULT 0,
|
|
17
|
-
followers_delta INTEGER DEFAULT 0,
|
|
18
|
-
extra_json TEXT,
|
|
19
|
-
fetched_at TEXT NOT NULL,
|
|
20
|
-
UNIQUE(platform, post_id, metric_date)
|
|
21
|
-
);
|
|
22
|
-
|
|
23
|
-
CREATE TABLE IF NOT EXISTS follower_snapshots (
|
|
24
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
25
|
-
platform TEXT NOT NULL,
|
|
26
|
-
follower_count INTEGER NOT NULL,
|
|
27
|
-
snapshot_date TEXT NOT NULL,
|
|
28
|
-
UNIQUE(platform, snapshot_date)
|
|
29
|
-
);
|
|
30
|
-
|
|
31
|
-
CREATE INDEX IF NOT EXISTS idx_metrics_platform_date ON engagement_metrics(platform, metric_date);
|
|
32
|
-
CREATE INDEX IF NOT EXISTS idx_metrics_published ON engagement_metrics(published_at);
|