thumbgate 0.9.13 → 1.0.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/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.well-known/mcp/server-card.json +1 -1
- package/README.md +6 -3
- package/adapters/README.md +1 -1
- package/adapters/chatgpt/openapi.yaml +105 -0
- package/adapters/claude/.mcp.json +2 -2
- package/adapters/codex/config.toml +2 -2
- package/adapters/forge/forge.yaml +28 -0
- package/adapters/mcp/server-stdio.js +32 -1
- package/adapters/opencode/opencode.json +1 -1
- package/bin/cli.js +53 -3
- package/config/mcp-allowlists.json +10 -0
- package/openapi/openapi.yaml +105 -0
- package/package.json +4 -4
- package/plugins/amp-skill/INSTALL.md +3 -4
- package/plugins/amp-skill/SKILL.md +0 -1
- package/plugins/claude-codex-bridge/.claude-plugin/plugin.json +1 -1
- package/plugins/claude-codex-bridge/.mcp.json +1 -1
- package/plugins/claude-skill/INSTALL.md +1 -2
- package/plugins/codex-profile/.codex-plugin/plugin.json +1 -1
- package/plugins/codex-profile/.mcp.json +1 -1
- package/plugins/codex-profile/INSTALL.md +1 -1
- package/plugins/codex-profile/README.md +1 -1
- package/plugins/cursor-marketplace/.cursor-plugin/plugin.json +1 -1
- package/plugins/opencode-profile/INSTALL.md +1 -1
- package/public/blog.html +1 -0
- package/public/dashboard.html +1 -1
- package/public/guide.html +1 -1
- package/public/index.html +29 -5
- package/public/learn/agent-harness-pattern.html +1 -1
- package/public/learn/ai-agent-persistent-memory.html +1 -1
- package/public/learn/mcp-pre-action-gates-explained.html +1 -1
- package/public/learn/stop-ai-agent-force-push.html +1 -1
- package/public/learn/vibe-coding-safety-net.html +1 -1
- package/public/learn.html +62 -1
- package/public/lessons.html +1 -1
- package/public/pro.html +1 -1
- package/scripts/__pycache__/train_from_feedback.cpython-312.pyc +0 -0
- package/scripts/agent-security-hardening.js +4 -4
- package/scripts/async-job-runner.js +84 -24
- package/scripts/auto-wire-hooks.js +59 -1
- package/scripts/context-manager.js +330 -0
- package/scripts/dashboard.js +1 -1
- package/scripts/distribution-surfaces.js +12 -0
- package/scripts/ensure-repo-bootstrap.js +15 -14
- package/scripts/feedback-history-distiller.js +7 -1
- package/scripts/feedback-loop.js +10 -4
- package/scripts/feedback-paths.js +142 -10
- package/scripts/feedback-root-consolidator.js +18 -4
- package/scripts/gates-engine.js +96 -10
- package/scripts/hook-auto-capture.sh +1 -1
- package/scripts/hosted-job-launcher.js +260 -0
- package/scripts/managed-dpo-export.js +91 -0
- package/scripts/obsidian-export.js +0 -1
- package/scripts/operational-integrity.js +50 -7
- package/scripts/post-everywhere.js +10 -0
- package/scripts/prove-lancedb.js +62 -4
- package/scripts/publish-decision.js +16 -0
- package/scripts/self-healing-check.js +6 -1
- package/scripts/seo-gsd.js +217 -4
- package/scripts/social-analytics/load-env.js +33 -2
- package/scripts/social-analytics/store.js +200 -2
- package/scripts/statusline-cache-path.js +9 -6
- package/scripts/sync-version.js +18 -11
- package/scripts/tool-registry.js +37 -0
- package/scripts/train_from_feedback.py +0 -4
- package/scripts/workflow-sentinel.js +793 -0
- package/src/api/server.js +297 -38
- /package/scripts/{rlhf_session_start.sh → thumbgate_session_start.sh} +0 -0
|
@@ -132,11 +132,54 @@ function serializeJobForState(job) {
|
|
|
132
132
|
skill: job.skill || null,
|
|
133
133
|
partnerProfile: job.partnerProfile || null,
|
|
134
134
|
autoImprove: job.autoImprove !== false,
|
|
135
|
+
verificationMode: job.verificationMode === 'none' ? 'none' : 'standard',
|
|
136
|
+
recordFeedback: job.recordFeedback !== false,
|
|
135
137
|
jobFilePath: job.jobFilePath || null,
|
|
136
138
|
stages,
|
|
137
139
|
};
|
|
138
140
|
}
|
|
139
141
|
|
|
142
|
+
function queueJob(job) {
|
|
143
|
+
const normalizedJob = {
|
|
144
|
+
...job,
|
|
145
|
+
id: job.id || generateJobId(),
|
|
146
|
+
tags: Array.isArray(job.tags) ? job.tags : [],
|
|
147
|
+
autoImprove: job.autoImprove !== false,
|
|
148
|
+
verificationMode: job.verificationMode === 'none' ? 'none' : 'standard',
|
|
149
|
+
recordFeedback: job.recordFeedback !== false,
|
|
150
|
+
stages: normalizeStages(job),
|
|
151
|
+
};
|
|
152
|
+
const previousState = readJobState(normalizedJob.id);
|
|
153
|
+
const currentStage = normalizedJob.stages[0] ? normalizedJob.stages[0].name : null;
|
|
154
|
+
return writeJobState({
|
|
155
|
+
jobId: normalizedJob.id,
|
|
156
|
+
status: 'queued',
|
|
157
|
+
createdAt: previousState && previousState.createdAt ? previousState.createdAt : nowIso(),
|
|
158
|
+
startedAt: previousState && previousState.startedAt ? previousState.startedAt : null,
|
|
159
|
+
resumedAt: null,
|
|
160
|
+
updatedAt: nowIso(),
|
|
161
|
+
endedAt: null,
|
|
162
|
+
tags: normalizedJob.tags,
|
|
163
|
+
skill: normalizedJob.skill || null,
|
|
164
|
+
partnerProfile: normalizedJob.partnerProfile || null,
|
|
165
|
+
autoImprove: normalizedJob.autoImprove,
|
|
166
|
+
verificationMode: normalizedJob.verificationMode,
|
|
167
|
+
recordFeedback: normalizedJob.recordFeedback,
|
|
168
|
+
totalStages: normalizedJob.stages.length,
|
|
169
|
+
nextStageIndex: 0,
|
|
170
|
+
currentStage,
|
|
171
|
+
currentContext: '',
|
|
172
|
+
checkpoints: [],
|
|
173
|
+
stageHistory: [],
|
|
174
|
+
jobFilePath: normalizedJob.jobFilePath || null,
|
|
175
|
+
jobSpec: serializeJobForState(normalizedJob),
|
|
176
|
+
lastError: null,
|
|
177
|
+
stopReason: null,
|
|
178
|
+
improvementExperimentId: null,
|
|
179
|
+
verification: null,
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
|
|
140
183
|
function readJobState(jobId) {
|
|
141
184
|
if (!jobId) return null;
|
|
142
185
|
return readJson(getJobRuntimePaths(jobId).statePath);
|
|
@@ -618,6 +661,8 @@ function executeJob(job, options = {}) {
|
|
|
618
661
|
id: job.id || generateJobId(),
|
|
619
662
|
tags: Array.isArray(job.tags) ? job.tags : [],
|
|
620
663
|
autoImprove: job.autoImprove !== false,
|
|
664
|
+
verificationMode: job.verificationMode === 'none' ? 'none' : 'standard',
|
|
665
|
+
recordFeedback: job.recordFeedback !== false,
|
|
621
666
|
stages: normalizeStages(job),
|
|
622
667
|
};
|
|
623
668
|
const previousState = options.previousState || readJobState(normalizedJob.id);
|
|
@@ -643,6 +688,8 @@ function executeJob(job, options = {}) {
|
|
|
643
688
|
skill: normalizedJob.skill || null,
|
|
644
689
|
partnerProfile: normalizedJob.partnerProfile || null,
|
|
645
690
|
autoImprove: normalizedJob.autoImprove,
|
|
691
|
+
verificationMode: normalizedJob.verificationMode,
|
|
692
|
+
recordFeedback: normalizedJob.recordFeedback,
|
|
646
693
|
totalStages: normalizedJob.stages.length,
|
|
647
694
|
nextStageIndex,
|
|
648
695
|
currentStage: normalizedJob.stages[nextStageIndex] ? normalizedJob.stages[nextStageIndex].name : 'verification',
|
|
@@ -733,7 +780,7 @@ function executeJob(job, options = {}) {
|
|
|
733
780
|
|
|
734
781
|
clearJobControl(normalizedJob.id);
|
|
735
782
|
|
|
736
|
-
const feedback = error && error.code === 'JOB_CANCELLED'
|
|
783
|
+
const feedback = (error && error.code === 'JOB_CANCELLED') || normalizedJob.recordFeedback === false
|
|
737
784
|
? null
|
|
738
785
|
: captureFeedback({
|
|
739
786
|
signal: 'down',
|
|
@@ -756,48 +803,60 @@ function executeJob(job, options = {}) {
|
|
|
756
803
|
}
|
|
757
804
|
}
|
|
758
805
|
|
|
759
|
-
const verification =
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
806
|
+
const verification = normalizedJob.verificationMode === 'none'
|
|
807
|
+
? null
|
|
808
|
+
: runVerificationLoop({
|
|
809
|
+
context: currentContext,
|
|
810
|
+
tags: normalizedJob.tags,
|
|
811
|
+
skill: normalizedJob.skill,
|
|
812
|
+
partnerProfile: normalizedJob.partnerProfile,
|
|
813
|
+
onRetry: normalizedJob.onRetry,
|
|
814
|
+
maxRetries: normalizedJob.maxRetries,
|
|
815
|
+
});
|
|
767
816
|
|
|
768
|
-
const improvementExperiment = verification.accepted
|
|
817
|
+
const improvementExperiment = !verification || verification.accepted
|
|
769
818
|
? null
|
|
770
819
|
: maybeQueueImprovementExperiment(normalizedJob, state, recall, {
|
|
771
820
|
type: 'verification',
|
|
772
821
|
verification,
|
|
773
822
|
});
|
|
774
823
|
|
|
775
|
-
const feedback =
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
:
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
824
|
+
const feedback = normalizedJob.recordFeedback === false
|
|
825
|
+
? null
|
|
826
|
+
: captureFeedback({
|
|
827
|
+
signal: !verification || verification.accepted ? 'up' : 'down',
|
|
828
|
+
context: !verification
|
|
829
|
+
? `Job ${normalizedJob.id} completed without post-run verification`
|
|
830
|
+
: verification.accepted
|
|
831
|
+
? `Job ${normalizedJob.id} passed verification after ${verification.attempts} attempt(s)`
|
|
832
|
+
: `Job ${normalizedJob.id} failed verification after ${verification.attempts} attempt(s): ${(verification.finalVerification.violations || []).map((violation) => violation.pattern).join('; ')}`,
|
|
833
|
+
whatWorked: !verification
|
|
834
|
+
? 'Operational job completed successfully'
|
|
835
|
+
: verification.accepted
|
|
836
|
+
? 'Verification loop accepted output'
|
|
837
|
+
: undefined,
|
|
838
|
+
whatWentWrong: verification && !verification.accepted ? `Failed ${verification.attempts} verification attempts` : undefined,
|
|
839
|
+
whatToChange: verification && !verification.accepted ? 'Improve output to avoid known mistake patterns' : undefined,
|
|
840
|
+
tags: !verification
|
|
841
|
+
? [...normalizedJob.tags, 'async-job-runner', 'verification-skipped']
|
|
842
|
+
: [...normalizedJob.tags, 'verification-loop'],
|
|
843
|
+
skill: normalizedJob.skill || 'async-job-runner',
|
|
844
|
+
});
|
|
786
845
|
|
|
787
846
|
const terminalState = writeJobState({
|
|
788
847
|
...(readJobState(normalizedJob.id) || state),
|
|
789
|
-
status: verification.accepted ? 'completed' : 'failed',
|
|
848
|
+
status: !verification || verification.accepted ? 'completed' : 'failed',
|
|
790
849
|
updatedAt: nowIso(),
|
|
791
850
|
endedAt: nowIso(),
|
|
792
851
|
currentStage: null,
|
|
793
852
|
nextStageIndex: normalizedJob.stages.length,
|
|
794
853
|
currentContext,
|
|
795
854
|
improvementExperimentId: improvementExperiment ? improvementExperiment.id : null,
|
|
796
|
-
verification: {
|
|
855
|
+
verification: verification ? {
|
|
797
856
|
accepted: verification.accepted,
|
|
798
857
|
attempts: verification.attempts,
|
|
799
858
|
score: verification.finalVerification ? verification.finalVerification.score : 0,
|
|
800
|
-
},
|
|
859
|
+
} : null,
|
|
801
860
|
});
|
|
802
861
|
|
|
803
862
|
clearJobControl(normalizedJob.id);
|
|
@@ -880,6 +939,7 @@ function runBatch(jobs) {
|
|
|
880
939
|
module.exports = {
|
|
881
940
|
recallContext,
|
|
882
941
|
executeJob,
|
|
942
|
+
queueJob,
|
|
883
943
|
runBatch,
|
|
884
944
|
appendJobLog,
|
|
885
945
|
readJobLog,
|
|
@@ -53,6 +53,7 @@ function detectAgent(flagAgent) {
|
|
|
53
53
|
if (['claude-code', 'claude'].includes(normalized)) return 'claude-code';
|
|
54
54
|
if (['codex'].includes(normalized)) return 'codex';
|
|
55
55
|
if (['gemini'].includes(normalized)) return 'gemini';
|
|
56
|
+
if (['forge', 'forgecode', 'forge-code'].includes(normalized)) return 'forge';
|
|
56
57
|
return null;
|
|
57
58
|
}
|
|
58
59
|
|
|
@@ -61,6 +62,7 @@ function detectAgent(flagAgent) {
|
|
|
61
62
|
if (fs.existsSync(path.join(home, '.claude'))) return 'claude-code';
|
|
62
63
|
if (fs.existsSync(path.join(home, '.codex'))) return 'codex';
|
|
63
64
|
if (fs.existsSync(path.join(home, '.gemini'))) return 'gemini';
|
|
65
|
+
if (fs.existsSync(path.join(process.cwd(), 'forge.yaml'))) return 'forge';
|
|
64
66
|
return null;
|
|
65
67
|
}
|
|
66
68
|
|
|
@@ -310,13 +312,64 @@ function wireGeminiHooks(options) {
|
|
|
310
312
|
return { changed: true, settingsPath, added };
|
|
311
313
|
}
|
|
312
314
|
|
|
315
|
+
// --- ForgeCode wiring ---
|
|
316
|
+
|
|
317
|
+
function forgeConfigPath() {
|
|
318
|
+
return path.join(process.cwd(), 'forge.yaml');
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
function wireForgeHooks(options) {
|
|
322
|
+
const dryRun = options.dryRun || false;
|
|
323
|
+
|
|
324
|
+
const preToolCmd = preToolHookCommand();
|
|
325
|
+
const userPromptCmd = userPromptHookCommand();
|
|
326
|
+
|
|
327
|
+
// ForgeCode uses YAML config (forge.yaml). We write a JSON-based hooks
|
|
328
|
+
// sidecar file (.thumbgate/forge-hooks.json) and append skill entries to
|
|
329
|
+
// forge.yaml if they are not already present.
|
|
330
|
+
const hooksPath = options.settingsPath || path.join(path.dirname(forgeConfigPath()), '.thumbgate', 'forge-hooks.json');
|
|
331
|
+
let existing = loadJsonFile(hooksPath) || {};
|
|
332
|
+
existing.hooks = existing.hooks || {};
|
|
333
|
+
|
|
334
|
+
const added = [];
|
|
335
|
+
|
|
336
|
+
if (!hookAlreadyPresent(existing.hooks.PreToolUse, preToolCmd)) {
|
|
337
|
+
existing.hooks.PreToolUse = existing.hooks.PreToolUse || [];
|
|
338
|
+
existing.hooks.PreToolUse.push({
|
|
339
|
+
matcher: 'Bash',
|
|
340
|
+
hooks: [{ type: 'command', command: preToolCmd }],
|
|
341
|
+
});
|
|
342
|
+
added.push({ lifecycle: 'PreToolUse', command: preToolCmd });
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
if (!hookAlreadyPresent(existing.hooks.UserPromptSubmit, userPromptCmd)) {
|
|
346
|
+
existing.hooks.UserPromptSubmit = existing.hooks.UserPromptSubmit || [];
|
|
347
|
+
existing.hooks.UserPromptSubmit.push({
|
|
348
|
+
hooks: [{ type: 'command', command: userPromptCmd }],
|
|
349
|
+
});
|
|
350
|
+
added.push({ lifecycle: 'UserPromptSubmit', command: userPromptCmd });
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
if (added.length === 0) {
|
|
354
|
+
return { changed: false, settingsPath: hooksPath, added: [] };
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
if (!dryRun) {
|
|
358
|
+
const dir = path.dirname(hooksPath);
|
|
359
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
360
|
+
fs.writeFileSync(hooksPath, JSON.stringify(existing, null, 2) + '\n');
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
return { changed: true, settingsPath: hooksPath, added };
|
|
364
|
+
}
|
|
365
|
+
|
|
313
366
|
// --- Dispatcher ---
|
|
314
367
|
|
|
315
368
|
function wireHooks(options) {
|
|
316
369
|
const agent = detectAgent(options.agent);
|
|
317
370
|
if (!agent) {
|
|
318
371
|
return {
|
|
319
|
-
error: 'Could not detect AI agent. Use --agent=claude-code|codex|gemini',
|
|
372
|
+
error: 'Could not detect AI agent. Use --agent=claude-code|codex|gemini|forge',
|
|
320
373
|
agent: null,
|
|
321
374
|
changed: false,
|
|
322
375
|
};
|
|
@@ -333,6 +386,9 @@ function wireHooks(options) {
|
|
|
333
386
|
case 'gemini':
|
|
334
387
|
result = wireGeminiHooks(options);
|
|
335
388
|
break;
|
|
389
|
+
case 'forge':
|
|
390
|
+
result = wireForgeHooks(options);
|
|
391
|
+
break;
|
|
336
392
|
default:
|
|
337
393
|
return { error: `Unsupported agent: ${agent}`, agent, changed: false };
|
|
338
394
|
}
|
|
@@ -364,6 +420,7 @@ module.exports = {
|
|
|
364
420
|
wireClaudeHooks,
|
|
365
421
|
wireCodexHooks,
|
|
366
422
|
wireGeminiHooks,
|
|
423
|
+
wireForgeHooks,
|
|
367
424
|
hookAlreadyPresent,
|
|
368
425
|
loadJsonFile,
|
|
369
426
|
parseFlags,
|
|
@@ -372,6 +429,7 @@ module.exports = {
|
|
|
372
429
|
codexConfigPath,
|
|
373
430
|
geminiSettingsPath,
|
|
374
431
|
syncClaudeStatusLine,
|
|
432
|
+
forgeConfigPath,
|
|
375
433
|
CLAUDE_HOOKS,
|
|
376
434
|
preToolHookCommand,
|
|
377
435
|
userPromptHookCommand,
|
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Context Manager — Unified Context-Augmented Generation (CAG) Orchestrator
|
|
6
|
+
*
|
|
7
|
+
* Single entry point that assembles a normalized context object from:
|
|
8
|
+
* - Session state (primer / handoff)
|
|
9
|
+
* - User profile (role, preferences, agent type)
|
|
10
|
+
* - Relevant lessons (per-action retrieval)
|
|
11
|
+
* - Prevention rules / pre-tool guards
|
|
12
|
+
* - Context pack (ContextFS retrieval)
|
|
13
|
+
* - Code-graph impact (optional, for coding tasks)
|
|
14
|
+
*
|
|
15
|
+
* Implements tiered graceful degradation:
|
|
16
|
+
* Tier 1 (full) — session + lessons + rules + context pack + code-graph
|
|
17
|
+
* Tier 2 (warm) — lessons + rules + context pack (no session)
|
|
18
|
+
* Tier 3 (cold) — prevention rules + global defaults only
|
|
19
|
+
*
|
|
20
|
+
* Role-aware filtering shapes output by agent type and license tier.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
const {
|
|
24
|
+
ensureContextFs,
|
|
25
|
+
constructContextPack,
|
|
26
|
+
readSessionHandoff,
|
|
27
|
+
recordProvenance,
|
|
28
|
+
} = require('./contextfs');
|
|
29
|
+
const { retrieveRelevantLessons } = require('./lesson-retrieval');
|
|
30
|
+
const { evaluatePretool } = require('./hybrid-feedback-context');
|
|
31
|
+
const { loadProfile } = require('./user-profile');
|
|
32
|
+
const {
|
|
33
|
+
analyzeCodeGraphImpact,
|
|
34
|
+
formatCodeGraphRecallSection,
|
|
35
|
+
} = require('./codegraph-context');
|
|
36
|
+
|
|
37
|
+
// ---------------------------------------------------------------------------
|
|
38
|
+
// Agent capability profiles — shapes what context each agent type receives
|
|
39
|
+
// ---------------------------------------------------------------------------
|
|
40
|
+
|
|
41
|
+
const AGENT_PROFILES = {
|
|
42
|
+
claude: {
|
|
43
|
+
maxLessons: 8,
|
|
44
|
+
includeCodeGraph: true,
|
|
45
|
+
includeStructuredRules: true,
|
|
46
|
+
contextBudget: 10000,
|
|
47
|
+
},
|
|
48
|
+
cursor: {
|
|
49
|
+
maxLessons: 5,
|
|
50
|
+
includeCodeGraph: true,
|
|
51
|
+
includeStructuredRules: true,
|
|
52
|
+
contextBudget: 6000,
|
|
53
|
+
},
|
|
54
|
+
forgecode: {
|
|
55
|
+
maxLessons: 5,
|
|
56
|
+
includeCodeGraph: false,
|
|
57
|
+
includeStructuredRules: true,
|
|
58
|
+
contextBudget: 6000,
|
|
59
|
+
},
|
|
60
|
+
codex: {
|
|
61
|
+
maxLessons: 6,
|
|
62
|
+
includeCodeGraph: true,
|
|
63
|
+
includeStructuredRules: true,
|
|
64
|
+
contextBudget: 8000,
|
|
65
|
+
},
|
|
66
|
+
default: {
|
|
67
|
+
maxLessons: 5,
|
|
68
|
+
includeCodeGraph: false,
|
|
69
|
+
includeStructuredRules: true,
|
|
70
|
+
contextBudget: 6000,
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
function getAgentProfile(agentType) {
|
|
75
|
+
const key = String(agentType || 'default').toLowerCase();
|
|
76
|
+
return AGENT_PROFILES[key] || AGENT_PROFILES.default;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// ---------------------------------------------------------------------------
|
|
80
|
+
// Tier assembly helpers
|
|
81
|
+
// ---------------------------------------------------------------------------
|
|
82
|
+
|
|
83
|
+
function assembleSession() {
|
|
84
|
+
try {
|
|
85
|
+
return readSessionHandoff();
|
|
86
|
+
} catch {
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function assembleLessons(query, agentProfile, options = {}) {
|
|
92
|
+
try {
|
|
93
|
+
return retrieveRelevantLessons(
|
|
94
|
+
options.toolName || '',
|
|
95
|
+
query,
|
|
96
|
+
{ maxResults: agentProfile.maxLessons, feedbackDir: options.feedbackDir },
|
|
97
|
+
);
|
|
98
|
+
} catch {
|
|
99
|
+
return [];
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function assembleGuards(toolName, toolInput) {
|
|
104
|
+
try {
|
|
105
|
+
return evaluatePretool(toolName || '', toolInput || {});
|
|
106
|
+
} catch {
|
|
107
|
+
return { mode: 'allow', reason: 'guard-unavailable' };
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function assembleContextPack(query, agentProfile) {
|
|
112
|
+
try {
|
|
113
|
+
ensureContextFs();
|
|
114
|
+
return constructContextPack({
|
|
115
|
+
query,
|
|
116
|
+
maxItems: Math.min(8, Math.ceil(agentProfile.contextBudget / 1000)),
|
|
117
|
+
maxChars: agentProfile.contextBudget,
|
|
118
|
+
});
|
|
119
|
+
} catch {
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function assembleCodeGraph(query, repoPath, agentProfile) {
|
|
125
|
+
if (!agentProfile.includeCodeGraph) return null;
|
|
126
|
+
try {
|
|
127
|
+
const impact = analyzeCodeGraphImpact({
|
|
128
|
+
intentId: null,
|
|
129
|
+
context: query,
|
|
130
|
+
repoPath,
|
|
131
|
+
});
|
|
132
|
+
return formatCodeGraphRecallSection(impact) || null;
|
|
133
|
+
} catch {
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function assembleUserProfile() {
|
|
139
|
+
try {
|
|
140
|
+
const profile = loadProfile();
|
|
141
|
+
if (!profile || !profile.entries || profile.entries.length === 0) return null;
|
|
142
|
+
return {
|
|
143
|
+
entries: profile.entries,
|
|
144
|
+
charCount: profile.charCount || 0,
|
|
145
|
+
};
|
|
146
|
+
} catch {
|
|
147
|
+
return null;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// ---------------------------------------------------------------------------
|
|
152
|
+
// Tier classification
|
|
153
|
+
// ---------------------------------------------------------------------------
|
|
154
|
+
|
|
155
|
+
function classifyTier(components) {
|
|
156
|
+
const hasSession = !!components.session;
|
|
157
|
+
const hasLessons = components.lessons && components.lessons.length > 0;
|
|
158
|
+
const hasPack = !!components.contextPack;
|
|
159
|
+
|
|
160
|
+
if (hasSession && (hasLessons || hasPack)) return 'full';
|
|
161
|
+
if (hasLessons || hasPack) return 'warm';
|
|
162
|
+
return 'cold';
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// ---------------------------------------------------------------------------
|
|
166
|
+
// Main orchestrator
|
|
167
|
+
// ---------------------------------------------------------------------------
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Assemble a unified context object for a given query.
|
|
171
|
+
*
|
|
172
|
+
* @param {Object} params
|
|
173
|
+
* @param {string} params.query - Task description / context query
|
|
174
|
+
* @param {string} [params.toolName] - Current tool being invoked (for lesson retrieval)
|
|
175
|
+
* @param {Object} [params.toolInput] - Current tool input (for guard evaluation)
|
|
176
|
+
* @param {string} [params.agentType] - Agent type: claude, cursor, forgecode, codex
|
|
177
|
+
* @param {string} [params.repoPath] - Repo path for code-graph analysis
|
|
178
|
+
* @param {string} [params.feedbackDir] - Override feedback directory
|
|
179
|
+
* @returns {Object} Normalized context object
|
|
180
|
+
*/
|
|
181
|
+
function assembleUnifiedContext(params = {}) {
|
|
182
|
+
const {
|
|
183
|
+
query = '',
|
|
184
|
+
toolName,
|
|
185
|
+
toolInput,
|
|
186
|
+
agentType,
|
|
187
|
+
repoPath,
|
|
188
|
+
feedbackDir,
|
|
189
|
+
} = params;
|
|
190
|
+
|
|
191
|
+
const agentProfile = getAgentProfile(agentType);
|
|
192
|
+
|
|
193
|
+
// Assemble all components — each is fault-tolerant
|
|
194
|
+
const session = assembleSession();
|
|
195
|
+
const userProfile = assembleUserProfile();
|
|
196
|
+
const lessons = assembleLessons(query, agentProfile, { toolName, feedbackDir });
|
|
197
|
+
const guards = assembleGuards(toolName, toolInput);
|
|
198
|
+
const contextPack = assembleContextPack(query, agentProfile);
|
|
199
|
+
const codeGraph = assembleCodeGraph(query, repoPath, agentProfile);
|
|
200
|
+
|
|
201
|
+
const components = { session, userProfile, lessons, guards, contextPack, codeGraph };
|
|
202
|
+
const tier = classifyTier(components);
|
|
203
|
+
|
|
204
|
+
const result = {
|
|
205
|
+
tier,
|
|
206
|
+
agentType: agentType || 'default',
|
|
207
|
+
agentProfile: {
|
|
208
|
+
maxLessons: agentProfile.maxLessons,
|
|
209
|
+
contextBudget: agentProfile.contextBudget,
|
|
210
|
+
includeCodeGraph: agentProfile.includeCodeGraph,
|
|
211
|
+
},
|
|
212
|
+
session: session || null,
|
|
213
|
+
userProfile: userProfile || null,
|
|
214
|
+
lessons,
|
|
215
|
+
guards,
|
|
216
|
+
contextPack: contextPack ? {
|
|
217
|
+
packId: contextPack.packId,
|
|
218
|
+
itemCount: Array.isArray(contextPack.items) ? contextPack.items.length : 0,
|
|
219
|
+
items: (contextPack.items || []).slice(0, 5).map((item) => ({
|
|
220
|
+
id: item.id,
|
|
221
|
+
namespace: item.namespace,
|
|
222
|
+
title: item.title,
|
|
223
|
+
tags: item.tags || [],
|
|
224
|
+
score: item.score,
|
|
225
|
+
})),
|
|
226
|
+
visibility: contextPack.visibility || null,
|
|
227
|
+
cached: !!(contextPack.cache && contextPack.cache.hit),
|
|
228
|
+
} : null,
|
|
229
|
+
codeGraph: codeGraph || null,
|
|
230
|
+
assembledAt: new Date().toISOString(),
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
// Record provenance for audit trail
|
|
234
|
+
try {
|
|
235
|
+
recordProvenance({
|
|
236
|
+
type: 'unified_context_assembled',
|
|
237
|
+
tier,
|
|
238
|
+
agentType: result.agentType,
|
|
239
|
+
lessonCount: lessons.length,
|
|
240
|
+
guardDecision: guards.mode || 'allow',
|
|
241
|
+
hasSession: !!session,
|
|
242
|
+
hasUserProfile: !!userProfile,
|
|
243
|
+
hasCodeGraph: !!codeGraph,
|
|
244
|
+
packId: result.contextPack ? result.contextPack.packId : null,
|
|
245
|
+
});
|
|
246
|
+
} catch {
|
|
247
|
+
// Provenance write failure must never break context assembly
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
return result;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// ---------------------------------------------------------------------------
|
|
254
|
+
// Formatting for MCP tool response
|
|
255
|
+
// ---------------------------------------------------------------------------
|
|
256
|
+
|
|
257
|
+
function formatUnifiedContext(ctx) {
|
|
258
|
+
const lines = [];
|
|
259
|
+
|
|
260
|
+
lines.push(`## Unified Context (Tier: ${ctx.tier})`);
|
|
261
|
+
lines.push(`Agent: ${ctx.agentType} | Assembled: ${ctx.assembledAt}`);
|
|
262
|
+
lines.push('');
|
|
263
|
+
|
|
264
|
+
// Session
|
|
265
|
+
if (ctx.session) {
|
|
266
|
+
lines.push('### Session');
|
|
267
|
+
if (ctx.session.lastTask) lines.push(`Last task: ${ctx.session.lastTask}`);
|
|
268
|
+
if (ctx.session.nextStep) lines.push(`Next step: ${ctx.session.nextStep}`);
|
|
269
|
+
if (ctx.session.blockers && ctx.session.blockers.length > 0) {
|
|
270
|
+
lines.push(`Blockers: ${ctx.session.blockers.join(', ')}`);
|
|
271
|
+
}
|
|
272
|
+
lines.push('');
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// User profile
|
|
276
|
+
if (ctx.userProfile) {
|
|
277
|
+
lines.push('### User Profile');
|
|
278
|
+
ctx.userProfile.entries.slice(0, 3).forEach((entry) => {
|
|
279
|
+
lines.push(`- ${entry.slice(0, 120)}`);
|
|
280
|
+
});
|
|
281
|
+
lines.push('');
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// Guards
|
|
285
|
+
if (ctx.guards && ctx.guards.mode !== 'allow') {
|
|
286
|
+
lines.push(`### Guard: ${ctx.guards.mode.toUpperCase()}`);
|
|
287
|
+
lines.push(ctx.guards.reason || 'No reason provided');
|
|
288
|
+
lines.push('');
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// Lessons
|
|
292
|
+
if (ctx.lessons && ctx.lessons.length > 0) {
|
|
293
|
+
lines.push(`### Lessons (${ctx.lessons.length})`);
|
|
294
|
+
ctx.lessons.forEach((lesson) => {
|
|
295
|
+
const signal = lesson.signal === 'negative' ? '[-]' : '[+]';
|
|
296
|
+
lines.push(`${signal} ${lesson.title || lesson.id} (score: ${lesson.relevanceScore})`);
|
|
297
|
+
if (lesson.rule) {
|
|
298
|
+
lines.push(` Rule: IF ${lesson.rule.condition || '?'} THEN ${lesson.rule.action || '?'}`);
|
|
299
|
+
}
|
|
300
|
+
});
|
|
301
|
+
lines.push('');
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// Context pack
|
|
305
|
+
if (ctx.contextPack) {
|
|
306
|
+
lines.push(`### Context Pack (${ctx.contextPack.itemCount} items)`);
|
|
307
|
+
ctx.contextPack.items.forEach((item) => {
|
|
308
|
+
lines.push(`- [${item.namespace}] ${item.title} (score: ${item.score})`);
|
|
309
|
+
});
|
|
310
|
+
if (ctx.contextPack.cached) lines.push('(cached)');
|
|
311
|
+
lines.push('');
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// Code graph
|
|
315
|
+
if (ctx.codeGraph) {
|
|
316
|
+
lines.push('### Code Graph Impact');
|
|
317
|
+
lines.push(ctx.codeGraph);
|
|
318
|
+
lines.push('');
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
return lines.join('\n');
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
module.exports = {
|
|
325
|
+
assembleUnifiedContext,
|
|
326
|
+
formatUnifiedContext,
|
|
327
|
+
getAgentProfile,
|
|
328
|
+
AGENT_PROFILES,
|
|
329
|
+
classifyTier,
|
|
330
|
+
};
|
package/scripts/dashboard.js
CHANGED
|
@@ -523,7 +523,7 @@ function computeInstrumentationReadiness(analytics, billing) {
|
|
|
523
523
|
const cli = telemetry.cli || {};
|
|
524
524
|
|
|
525
525
|
return {
|
|
526
|
-
plausibleConfigured:
|
|
526
|
+
plausibleConfigured: /plausible\.io\/js\/script\.js|\/js\/analytics\.js/.test(landingPage),
|
|
527
527
|
ga4Configured: Boolean(runtimeConfig.gaMeasurementId),
|
|
528
528
|
googleSearchConsoleConfigured: Boolean(runtimeConfig.googleSiteVerification),
|
|
529
529
|
softwareApplicationSchemaPresent: /"@type": "SoftwareApplication"/.test(landingPage),
|
|
@@ -6,6 +6,7 @@ const path = require('node:path');
|
|
|
6
6
|
const ROOT = path.join(__dirname, '..');
|
|
7
7
|
const PRODUCTHUNT_URL = 'https://www.producthunt.com/products/thumbgate';
|
|
8
8
|
const CLAUDE_PLUGIN_LATEST_ASSET_NAME = 'thumbgate-claude-desktop.mcpb';
|
|
9
|
+
const CLAUDE_PLUGIN_NEXT_ASSET_NAME = 'thumbgate-claude-desktop-next.mcpb';
|
|
9
10
|
|
|
10
11
|
function readJson(root, relativePath) {
|
|
11
12
|
return JSON.parse(fs.readFileSync(path.join(root, relativePath), 'utf8'));
|
|
@@ -24,6 +25,14 @@ function getClaudePluginVersionedAssetName(version = getPackageVersion(ROOT)) {
|
|
|
24
25
|
return `thumbgate-claude-desktop-v${normalized}.mcpb`;
|
|
25
26
|
}
|
|
26
27
|
|
|
28
|
+
function isPrereleaseVersion(version = getPackageVersion(ROOT)) {
|
|
29
|
+
return /^\d+\.\d+\.\d+-[0-9A-Za-z.-]+$/.test(String(version || '').trim());
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function getClaudePluginChannelAssetName(version = getPackageVersion(ROOT)) {
|
|
33
|
+
return isPrereleaseVersion(version) ? CLAUDE_PLUGIN_NEXT_ASSET_NAME : CLAUDE_PLUGIN_LATEST_ASSET_NAME;
|
|
34
|
+
}
|
|
35
|
+
|
|
27
36
|
function getClaudePluginLatestDownloadUrl(root = ROOT) {
|
|
28
37
|
return `${getRepositoryUrl(root)}/releases/latest/download/${CLAUDE_PLUGIN_LATEST_ASSET_NAME}`;
|
|
29
38
|
}
|
|
@@ -35,10 +44,13 @@ function getClaudePluginVersionedDownloadUrl(root = ROOT, version = getPackageVe
|
|
|
35
44
|
|
|
36
45
|
module.exports = {
|
|
37
46
|
CLAUDE_PLUGIN_LATEST_ASSET_NAME,
|
|
47
|
+
CLAUDE_PLUGIN_NEXT_ASSET_NAME,
|
|
38
48
|
PRODUCTHUNT_URL,
|
|
49
|
+
getClaudePluginChannelAssetName,
|
|
39
50
|
getClaudePluginLatestDownloadUrl,
|
|
40
51
|
getClaudePluginVersionedAssetName,
|
|
41
52
|
getClaudePluginVersionedDownloadUrl,
|
|
42
53
|
getPackageVersion,
|
|
43
54
|
getRepositoryUrl,
|
|
55
|
+
isPrereleaseVersion,
|
|
44
56
|
};
|