thumbgate 1.34.3 → 1.37.1

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.
Files changed (200) hide show
  1. package/.agents/skills/cyberstrike-compare-not-clone/SKILL.md +36 -0
  2. package/.agents/skills/gitlab-sandbox-allowlist-not-trust/SKILL.md +77 -0
  3. package/.agents/skills/jit-harness-compare-not-clone/SKILL.md +34 -0
  4. package/.agents/skills/openui-catalog-compose-honesty/SKILL.md +64 -0
  5. package/.agents/skills/zvec-grep-compare-not-clone/SKILL.md +34 -0
  6. package/.claude-plugin/plugin.json +1 -1
  7. package/.well-known/llms.txt +1 -0
  8. package/.well-known/mcp/server-card.json +1 -1
  9. package/CONTRIBUTING.md +95 -0
  10. package/README.md +195 -632
  11. package/THIRD_PARTY_NOTICES.md +89 -0
  12. package/adapters/claude/.mcp.json +2 -2
  13. package/adapters/forge/forge.yaml +3 -3
  14. package/adapters/future-agi/.mcp.json +8 -0
  15. package/adapters/future-agi/FUTURE_AGI.md +23 -0
  16. package/adapters/future-agi/config.toml +3 -0
  17. package/adapters/future-agi/future-agi-bridge.js +9 -0
  18. package/adapters/future-agi/opencode.json +8 -0
  19. package/adapters/herdr/herdr-plugin.toml +18 -0
  20. package/adapters/mcp/server-stdio.js +238 -25
  21. package/adapters/opencode/opencode.json +1 -1
  22. package/adapters/workos/WORKOS.md +52 -0
  23. package/bin/cli.js +373 -5
  24. package/bin/futureagi-bridge +9 -0
  25. package/config/gate-templates.json +653 -4
  26. package/config/gates/actor-critic-audit.json +34 -0
  27. package/config/gates/default.json +21 -2
  28. package/config/gates/five-walls-governance.json +34 -0
  29. package/config/gates/future-agi-guardrails.json +34 -0
  30. package/config/gates/radware-threat-defense-2026.json +61 -0
  31. package/config/gates/simatree-data-governance.json +33 -0
  32. package/config/mcp-allowlists.json +4 -0
  33. package/config/merge-quality-checks.json +10 -1
  34. package/config/model-candidates.json +382 -24
  35. package/config/model-tiers.json +18 -0
  36. package/config/post-deploy-marketing-pages.json +10 -0
  37. package/config/progressive/01-wire-only.json +11 -0
  38. package/config/progressive/02-dashboard-empty-ok.json +10 -0
  39. package/config/progressive/03-one-lesson.json +10 -0
  40. package/config/progressive/04-warn-fires.json +11 -0
  41. package/config/progressive/05-strict-optional.json +11 -0
  42. package/config/progressive/README.md +15 -0
  43. package/config/schemas/broker-execution-receipt.schema.json +139 -0
  44. package/config/schemas/provider-execution-attestation-v1.schema.json +58 -0
  45. package/conformance/provider-attestation/vectors.json +320 -0
  46. package/docs/specs/provider-execution-attestation-v1.md +69 -0
  47. package/openapi/openapi.yaml +15 -0
  48. package/package.json +401 -147
  49. package/public/about.html +2 -2
  50. package/public/ai-malpractice-prevention.html +7 -7
  51. package/public/blog/a-10-dollar-vps-is-not-a-computer.html +143 -0
  52. package/public/blog/a-receipt-is-not-world-state.html +388 -0
  53. package/public/blog/git-at-agent-scale.html +374 -0
  54. package/public/blog/no-llm-in-the-gate.html +133 -0
  55. package/public/blog.html +80 -0
  56. package/public/case-studies.html +16 -1
  57. package/public/compare.html +28 -0
  58. package/public/diagnostic.html +216 -7
  59. package/public/docs/connectors.html +39 -0
  60. package/public/federal.html +2 -2
  61. package/public/founders.html +639 -0
  62. package/public/index.html +87 -9
  63. package/public/install.html +8 -8
  64. package/public/learn.html +39 -0
  65. package/public/numbers.html +2 -2
  66. package/public/peter.html +310 -0
  67. package/public/platform-partners.html +119 -0
  68. package/public/pricing.html +24 -3
  69. package/public/privacy.html +117 -0
  70. package/public/pro.html +17 -0
  71. package/public/support.html +62 -0
  72. package/public/terms.html +130 -0
  73. package/public/third-party-notices.html +95 -0
  74. package/public/yt.html +351 -0
  75. package/scripts/action-receipts.js +133 -3
  76. package/scripts/adaptive-governance-arena.js +349 -0
  77. package/scripts/admin-override.js +205 -0
  78. package/scripts/agent-action-inventory.js +869 -0
  79. package/scripts/agent-audit-trace.js +42 -2
  80. package/scripts/agent-egress-policy.js +1117 -0
  81. package/scripts/agent-memory-lifecycle.js +141 -2
  82. package/scripts/agent-operations-planner.js +441 -1
  83. package/scripts/agent-readiness.js +68 -0
  84. package/scripts/agent-security-central.js +647 -0
  85. package/scripts/allowlist-bridge-honesty.js +417 -0
  86. package/scripts/async-job-runner.js +102 -11
  87. package/scripts/audit-trail.js +212 -0
  88. package/scripts/auto-promote-gates.js +178 -27
  89. package/scripts/billing.js +1 -1
  90. package/scripts/broker-execution-receipts.js +719 -0
  91. package/scripts/budget-aware-gates-proof.js +423 -0
  92. package/scripts/claude-feedback-sync.js +29 -3
  93. package/scripts/claw-harness-production.js +237 -0
  94. package/scripts/cli-progress.js +111 -0
  95. package/scripts/cli-schema.js +163 -1
  96. package/scripts/codex-runbook-flywheel.js +318 -0
  97. package/scripts/context-footprint.js +186 -0
  98. package/scripts/contextfs.js +143 -61
  99. package/scripts/dashboard-limits.js +27 -0
  100. package/scripts/dashboard.js +279 -9
  101. package/scripts/deepseek-v4-runtime-guardrails.js +72 -6
  102. package/scripts/docker-sandbox-planner.js +18 -0
  103. package/scripts/double-blind-eval-protocol.js +252 -0
  104. package/scripts/edotenv-rl-gateway.js +259 -0
  105. package/scripts/ensure-production-search-corpus.js +162 -0
  106. package/scripts/eval-holdout.js +311 -0
  107. package/scripts/feedback-aggregate.js +21 -2
  108. package/scripts/feedback-loop.js +87 -5
  109. package/scripts/feedback-quality.js +9 -0
  110. package/scripts/file-ledger-lock.js +4 -1
  111. package/scripts/financial-control-plane.js +41 -1
  112. package/scripts/find-dormant-requires.js +118 -0
  113. package/scripts/fs-utils.js +84 -8
  114. package/scripts/gate-stats.js +2 -2
  115. package/scripts/gates-engine.js +859 -58
  116. package/scripts/generate-case-study-outreach.js +24 -15
  117. package/scripts/git-at-scale.js +628 -0
  118. package/scripts/governance-conflict-audit.js +1650 -0
  119. package/scripts/governance-difficulty-curriculum.js +328 -0
  120. package/scripts/graphrag-retrieval.js +275 -0
  121. package/scripts/gurobi-optimizer.js +324 -0
  122. package/scripts/gurobi_optimizer.py +485 -0
  123. package/scripts/harness-selector.js +82 -1
  124. package/scripts/hidden-entry-points.js +284 -0
  125. package/scripts/human-escalation.js +199 -1
  126. package/scripts/hybrid-feedback-context.js +152 -19
  127. package/scripts/intent-governed-execution.js +602 -0
  128. package/scripts/intervention-policy.js +123 -20
  129. package/scripts/jit-harness-compose.js +628 -0
  130. package/scripts/jsonl-watcher.js +10 -0
  131. package/scripts/lesson-embedding-index.js +95 -12
  132. package/scripts/lesson-retrieval.js +105 -19
  133. package/scripts/local-model-profile.js +19 -2
  134. package/scripts/mailer/resend-mailer.js +1 -1
  135. package/scripts/matryoshka-embedding.js +235 -0
  136. package/scripts/mcp-oauth.js +42 -4
  137. package/scripts/mcp-session-handles.js +1016 -0
  138. package/scripts/mcp-wiring-doctor.js +314 -0
  139. package/scripts/memory-firewall.js +115 -2
  140. package/scripts/memory-scope-readiness.js +299 -0
  141. package/scripts/memory-vs-rag-route.js +161 -0
  142. package/scripts/model-tier-router.js +148 -21
  143. package/scripts/nvidia-specdecode-al-doctor.js +536 -0
  144. package/scripts/openui-catalog-compose-honesty.js +593 -0
  145. package/scripts/operational-integrity.js +19 -1
  146. package/scripts/override-audit.js +213 -0
  147. package/scripts/package-manager-honesty-doctor.js +458 -0
  148. package/scripts/pr-manager.js +63 -1
  149. package/scripts/prove-herdr-adapter.js +52 -0
  150. package/scripts/prove-memory-pyramid-and-symbolic-canvas.js +95 -0
  151. package/scripts/prove-workos.js +73 -0
  152. package/scripts/provider-attestation-conformance.js +192 -0
  153. package/scripts/provider-receipt-contract.js +136 -0
  154. package/scripts/qwen38-max-cost-optimizer.js +401 -0
  155. package/scripts/radware-threat-defense.js +280 -0
  156. package/scripts/rag-embedding-identity.js +221 -0
  157. package/scripts/rag-precision-guardrails.js +112 -2
  158. package/scripts/remote-feedback-capture.js +159 -0
  159. package/scripts/research-agent-harness.js +256 -0
  160. package/scripts/rsi-safety-hillclimb.js +200 -0
  161. package/scripts/rule-sprawl.js +188 -0
  162. package/scripts/schedule-manager.js +147 -0
  163. package/scripts/self-heal.js +8 -0
  164. package/scripts/session-lease.js +415 -0
  165. package/scripts/simatree-data-governance.js +347 -0
  166. package/scripts/slo-alert-engine.js +172 -7
  167. package/scripts/solver-parity.js +539 -0
  168. package/scripts/stealth-memory-injection-gate.js +333 -0
  169. package/scripts/switchyard-router.js +366 -0
  170. package/scripts/telemetry-analytics.js +84 -27
  171. package/scripts/temporal-decay-weighting.js +138 -0
  172. package/scripts/test-all.js +165 -0
  173. package/scripts/token-savings.js +42 -0
  174. package/scripts/tool-kpi-tracker.js +108 -5
  175. package/scripts/tool-registry.js +193 -5
  176. package/scripts/universal-claim-evaluator.js +14 -2
  177. package/scripts/vector-store.js +279 -9
  178. package/scripts/workflow-notebook.js +391 -0
  179. package/scripts/workflow-sentinel.js +111 -12
  180. package/scripts/workos-production-guard.js +260 -0
  181. package/scripts/workspace-search-route.js +515 -0
  182. package/server.json +2 -2
  183. package/src/agent-identity-boundary.js +76 -0
  184. package/src/agent-retrieval-cache.js +155 -0
  185. package/src/alert-noise-ledger.js +502 -0
  186. package/src/api/server.js +802 -185
  187. package/src/git-fast-cache.js +220 -0
  188. package/src/git-wal-sync.js +156 -0
  189. package/src/hash-anchored-edit.js +82 -0
  190. package/src/hermes-platform-protocol.js +475 -0
  191. package/src/hermes-sync-plane.js +241 -0
  192. package/src/index.js +30 -1
  193. package/src/iso42001-compliance-guard.js +97 -0
  194. package/src/latency-budget.js +244 -0
  195. package/src/mcp-writeguard.js +316 -0
  196. package/src/miminions-adapter.js +106 -0
  197. package/src/pipeline-compass.js +104 -0
  198. package/src/ppl-alert-pipeline.js +284 -0
  199. package/src/rendezvous-router.js +90 -0
  200. package/src/security-questionnaire.js +195 -0
@@ -0,0 +1,314 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ /**
5
+ * mcp-wiring-doctor.js
6
+ *
7
+ * Detects the structural RAG-loop break that unattended agents hit:
8
+ * AGENTS.md mandates recall/capture_feedback, but:
9
+ * 1. `.mcp.json` may not list the thumbgate MCP server
10
+ * 2. The lessons store may be missing (Mac-local only / empty container)
11
+ * 3. Remote hosted capture env may be unset, so cloud runs cannot write
12
+ *
13
+ * High-ROI: make the gap loud at session-start / doctor instead of silent.
14
+ */
15
+
16
+ const fs = require('fs');
17
+ const path = require('path');
18
+ const { resolveMcpEntry } = require('./mcp-config');
19
+ const {
20
+ isRemoteCaptureConfigured,
21
+ resolveApiKey,
22
+ resolveBaseUrl,
23
+ } = require('./remote-feedback-capture');
24
+
25
+ const PKG_ROOT = path.resolve(__dirname, '..');
26
+ const PKG_VERSION = require('../package.json').version;
27
+
28
+ const LEGACY_MCP_KEYS = new Set(['mcp-memory-gateway', 'rlhf']);
29
+
30
+ function readJsonIfExists(filePath) {
31
+ try {
32
+ if (!fs.existsSync(filePath)) return null;
33
+ return JSON.parse(fs.readFileSync(filePath, 'utf8'));
34
+ } catch {
35
+ return { __parseError: true };
36
+ }
37
+ }
38
+
39
+ function listMcpServers(config) {
40
+ if (!config || typeof config !== 'object') return [];
41
+ const servers = config.mcpServers || config.servers || {};
42
+ return Object.keys(servers || {});
43
+ }
44
+
45
+ function hasThumbgateServer(config) {
46
+ if (!config || config.__parseError) return false;
47
+ const servers = config.mcpServers || config.servers || {};
48
+ if (servers.thumbgate) return true;
49
+ // Accept explicit package/npx forms under alternate keys only if command mentions thumbgate
50
+ return Object.entries(servers).some(([key, entry]) => {
51
+ if (LEGACY_MCP_KEYS.has(key)) return false;
52
+ const blob = JSON.stringify(entry || {}).toLowerCase();
53
+ return blob.includes('thumbgate') || blob.includes('server-stdio.js');
54
+ });
55
+ }
56
+
57
+ function resolveLessonsStore(projectRoot, env = process.env) {
58
+ const candidates = [
59
+ env.THUMBGATE_FEEDBACK_DIR,
60
+ path.join(projectRoot, '.thumbgate'),
61
+ path.join(projectRoot, '.claude', 'memory', 'feedback'),
62
+ env.HOME ? path.join(env.HOME, '.thumbgate') : null,
63
+ ].filter(Boolean);
64
+
65
+ for (const dir of candidates) {
66
+ try {
67
+ if (fs.existsSync(dir) && fs.statSync(dir).isDirectory()) {
68
+ const writable = (() => {
69
+ try {
70
+ fs.accessSync(dir, fs.constants.W_OK);
71
+ return true;
72
+ } catch {
73
+ return false;
74
+ }
75
+ })();
76
+ return { path: dir, present: true, writable };
77
+ }
78
+ } catch {
79
+ /* continue */
80
+ }
81
+ }
82
+
83
+ const preferred = candidates[0] || path.join(projectRoot, '.thumbgate');
84
+ let parent = preferred;
85
+ while (!fs.existsSync(parent)) {
86
+ const next = path.dirname(parent);
87
+ if (next === parent) break;
88
+ parent = next;
89
+ }
90
+ try {
91
+ fs.accessSync(parent, fs.constants.W_OK);
92
+ return { path: preferred, present: false, writable: true, creatable: true };
93
+ } catch {
94
+ return { path: preferred, present: false, writable: false, creatable: false };
95
+ }
96
+ }
97
+
98
+ function remoteCaptureConfigured(env = process.env) {
99
+ const base = resolveBaseUrl(env);
100
+ const key = resolveApiKey(env);
101
+ return {
102
+ configured: isRemoteCaptureConfigured(env),
103
+ baseUrl: base || null,
104
+ hasKey: Boolean(key),
105
+ };
106
+ }
107
+
108
+ function isContainerLike(env = process.env) {
109
+ if (env.container || env.CONTAINER) return true;
110
+ if (fs.existsSync('/.dockerenv')) return true;
111
+ return false;
112
+ }
113
+
114
+ /**
115
+ * @returns {{
116
+ * overall: 'ok'|'warning'|'error',
117
+ * findings: string[],
118
+ * mcp: object,
119
+ * lessonsStore: object,
120
+ * remote: object,
121
+ * unattendedCaptureReady: boolean,
122
+ * }}
123
+ */
124
+ function wiringReport(projectRoot = process.cwd(), env = process.env) {
125
+ const root = path.resolve(projectRoot);
126
+ const findings = [];
127
+ let overall = 'ok';
128
+
129
+ const mcpPath = path.join(root, '.mcp.json');
130
+ const claudeSettingsPath = path.join(root, '.claude', 'settings.json');
131
+ const cursorMcpPath = path.join(root, '.cursor', 'mcp.json');
132
+
133
+ const mcpConfig = readJsonIfExists(mcpPath);
134
+ const claudeSettings = readJsonIfExists(claudeSettingsPath);
135
+ const cursorMcp = readJsonIfExists(cursorMcpPath);
136
+
137
+ const mcpServers = listMcpServers(mcpConfig);
138
+ const hasProjectMcpFile = fs.existsSync(mcpPath);
139
+ const thumbgateInProjectMcp = hasThumbgateServer(mcpConfig);
140
+ const thumbgateInCursor = hasThumbgateServer(cursorMcp);
141
+ const thumbgateInClaudeSettings = hasThumbgateServer(claudeSettings);
142
+
143
+ if (!hasProjectMcpFile) {
144
+ findings.push('Missing project .mcp.json — bootstrap file required by agent-readiness; MCP tools are not project-wired.');
145
+ overall = 'error';
146
+ } else if (mcpConfig && mcpConfig.__parseError) {
147
+ findings.push('.mcp.json is not valid JSON.');
148
+ overall = 'error';
149
+ } else if (!thumbgateInProjectMcp) {
150
+ findings.push(
151
+ `.mcp.json has no thumbgate server (found: ${mcpServers.join(', ') || 'none'}). ` +
152
+ 'capture_feedback / recall are uncallable for agents using this project MCP config. ' +
153
+ 'Add mcpServers.thumbgate (see adapters/claude/.mcp.json) or run: node scripts/install-mcp.js --project'
154
+ );
155
+ overall = 'error';
156
+ }
157
+
158
+ for (const key of mcpServers) {
159
+ if (LEGACY_MCP_KEYS.has(key)) {
160
+ findings.push(`Detected legacy MCP key "${key}" in .mcp.json — migrate to "thumbgate".`);
161
+ if (overall === 'ok') overall = 'warning';
162
+ }
163
+ }
164
+
165
+ if (!thumbgateInClaudeSettings && !thumbgateInCursor && !thumbgateInProjectMcp) {
166
+ findings.push('No thumbgate MCP entry in .mcp.json, .claude/settings.json, or .cursor/mcp.json.');
167
+ overall = 'error';
168
+ }
169
+
170
+ const lessonsStore = resolveLessonsStore(root, env);
171
+ const remote = remoteCaptureConfigured(env);
172
+ const container = isContainerLike(env);
173
+
174
+ if (!lessonsStore.present && !lessonsStore.writable) {
175
+ findings.push(
176
+ 'No lessons store found (.thumbgate/ or THUMBGATE_FEEDBACK_DIR). ' +
177
+ 'Local capture_feedback cannot persist. Unattended/cloud runs need THUMBGATE_FEEDBACK_DIR ' +
178
+ 'or hosted capture via THUMBGATE_API_BASE_URL + THUMBGATE_API_KEY.'
179
+ );
180
+ if (overall === 'ok') overall = 'warning';
181
+ if (container) overall = 'error';
182
+ } else if (!lessonsStore.writable) {
183
+ findings.push(`Lessons store present but not writable: ${lessonsStore.path}`);
184
+ if (overall === 'ok') overall = 'warning';
185
+ }
186
+
187
+ if (container && !lessonsStore.writable && !remote.configured) {
188
+ findings.push(
189
+ 'Container/unattended runtime: neither local lessons store nor hosted capture env is configured. ' +
190
+ 'AGENTS.md RAG loop (recall → act → capture_feedback) is unenforceable in this environment.'
191
+ );
192
+ overall = 'error';
193
+ }
194
+
195
+ if (container && remote.configured) {
196
+ findings.push('Hosted capture env is set — unattended runs can POST feedback to the API when local store is missing.');
197
+ }
198
+
199
+ const unattendedCaptureReady = Boolean(
200
+ lessonsStore.writable || remote.configured
201
+ );
202
+
203
+ if (!unattendedCaptureReady && overall === 'ok') {
204
+ overall = 'warning';
205
+ }
206
+
207
+ return {
208
+ overall,
209
+ findings,
210
+ mcp: {
211
+ projectMcpPath: mcpPath,
212
+ projectMcpPresent: hasProjectMcpFile,
213
+ servers: mcpServers,
214
+ thumbgateInProjectMcp,
215
+ thumbgateInCursor,
216
+ thumbgateInClaudeSettings,
217
+ },
218
+ lessonsStore,
219
+ remote,
220
+ container,
221
+ unattendedCaptureReady,
222
+ recommendation: unattendedCaptureReady
223
+ ? 'RAG capture path is available (local store and/or hosted API).'
224
+ : 'Wire mcpServers.thumbgate in .mcp.json and set THUMBGATE_FEEDBACK_DIR or THUMBGATE_API_BASE_URL+THUMBGATE_API_KEY for unattended agents.',
225
+ };
226
+ }
227
+
228
+ function formatReport(report) {
229
+ const lines = [
230
+ `ThumbGate MCP wiring doctor: ${report.overall.toUpperCase()}`,
231
+ ` Project .mcp.json: ${report.mcp.projectMcpPresent ? 'present' : 'MISSING'} (thumbgate: ${report.mcp.thumbgateInProjectMcp ? 'yes' : 'NO'})`,
232
+ ` Lessons store: ${report.lessonsStore.present ? report.lessonsStore.path : 'NONE'} (writable: ${report.lessonsStore.writable})`,
233
+ ` Hosted capture: ${report.remote.configured ? 'configured' : 'not configured'}`,
234
+ ` Unattended capture ready: ${report.unattendedCaptureReady ? 'yes' : 'NO'}`,
235
+ ` Container-like: ${report.container ? 'yes' : 'no'}`,
236
+ ];
237
+ if (report.findings.length) {
238
+ lines.push(' Findings:');
239
+ for (const f of report.findings) lines.push(` - ${f}`);
240
+ }
241
+ lines.push(` ${report.recommendation}`);
242
+ return lines.join('\n');
243
+ }
244
+
245
+ function applyFix(projectRoot = process.cwd()) {
246
+ const root = path.resolve(projectRoot);
247
+ const mcpPath = path.join(root, '.mcp.json');
248
+ const desired = {
249
+ mcpServers: {
250
+ thumbgate: resolveMcpEntry({
251
+ pkgRoot: PKG_ROOT,
252
+ pkgVersion: PKG_VERSION,
253
+ scope: 'project',
254
+ targetDir: root,
255
+ }),
256
+ },
257
+ };
258
+
259
+ let existing = {};
260
+ if (fs.existsSync(mcpPath)) {
261
+ try {
262
+ existing = JSON.parse(fs.readFileSync(mcpPath, 'utf8'));
263
+ } catch {
264
+ existing = {};
265
+ }
266
+ }
267
+
268
+ if (!existing.mcpServers || typeof existing.mcpServers !== 'object') {
269
+ existing.mcpServers = {};
270
+ }
271
+ // Preserve other servers; force thumbgate entry.
272
+ existing.mcpServers.thumbgate = desired.mcpServers.thumbgate;
273
+ for (const legacy of LEGACY_MCP_KEYS) {
274
+ if (Object.prototype.hasOwnProperty.call(existing.mcpServers, legacy)) {
275
+ delete existing.mcpServers[legacy];
276
+ }
277
+ }
278
+
279
+ fs.writeFileSync(mcpPath, `${JSON.stringify(existing, null, 2)}\n`, 'utf8');
280
+ return { wrote: mcpPath, config: existing };
281
+ }
282
+
283
+ if (path.resolve(process.argv[1] || '') === path.resolve(__filename)) {
284
+ const args = process.argv.slice(2);
285
+ const fix = args.includes('--fix');
286
+ const json = args.includes('--json');
287
+ const projectRoot = process.cwd();
288
+
289
+ if (fix) {
290
+ const result = applyFix(projectRoot);
291
+ if (json) {
292
+ console.log(JSON.stringify({ fixed: true, ...result }, null, 2));
293
+ } else {
294
+ console.log(`Wrote ${result.wrote}`);
295
+ }
296
+ }
297
+
298
+ const report = wiringReport(projectRoot);
299
+ if (json) {
300
+ console.log(JSON.stringify(report, null, 2));
301
+ } else {
302
+ console.log(formatReport(report));
303
+ }
304
+ process.exit(report.overall === 'error' ? 2 : report.overall === 'warning' ? 1 : 0);
305
+ }
306
+
307
+ module.exports = {
308
+ wiringReport,
309
+ formatReport,
310
+ applyFix,
311
+ hasThumbgateServer,
312
+ resolveLessonsStore,
313
+ remoteCaptureConfigured,
314
+ };
@@ -8,6 +8,39 @@ const { scanText, buildSafeSummary, redactText } = require('./secret-scanner');
8
8
  const SHIELDCORTEX_RUNNER_PATH = path.join(__dirname, 'shieldcortex-memory-firewall-runner.mjs');
9
9
  const VALID_PROVIDERS = new Set(['auto', 'shieldcortex', 'local', 'off']);
10
10
  const VALID_MODES = new Set(['strict', 'balanced', 'permissive']);
11
+ const UNTRUSTED_SOURCE_TYPES = new Set([
12
+ 'browser',
13
+ 'document',
14
+ 'email',
15
+ 'external',
16
+ 'file',
17
+ 'imap',
18
+ 'third-party',
19
+ 'tool-output',
20
+ 'web',
21
+ 'webhook',
22
+ ]);
23
+ const MEMORY_WRITE_PATTERNS = [
24
+ /\b(?:add|commit|persist|record|remember|save|store|write)\b.{0,60}\b(?:memory|memories|preference|profile|workspace)\b/i,
25
+ /\bremember\b.{0,40}\b(?:forever|permanently|across (?:future|subsequent) sessions?|for (?:future|subsequent) sessions?)\b/i,
26
+ /\b(?:update|change|replace|set)\b.{0,50}\b(?:memory|preference|policy|rule|profile)\b/i,
27
+ /\b(?:from now on|going forward|in future sessions?)\b/i,
28
+ /\b(?:always|never)\b.{0,50}\b(?:use|allow|approve|block|prefer|trust|verify)\b/i,
29
+ ];
30
+ const STEALTH_PATTERNS = [
31
+ /\b(?:do not|don't|never)\b.{0,40}\b(?:disclose|mention|notify|reveal|say|show|tell|warn)\b/i,
32
+ /\b(?:hide|silently|stealthily|without (?:alerting|mentioning|notifying|telling|warning))\b/i,
33
+ /\bno need to (?:mention|notify|tell|warn)\b/i,
34
+ ];
35
+ const OVERRIDE_PATTERNS = [
36
+ /\bignore (?:all |any )?(?:earlier|previous|prior|system) instructions?\b/i,
37
+ /\b(?:developer|system) message\b/i,
38
+ /\b(?:administrator|admin|security team) (?:approved|requires|says)\b/i,
39
+ ];
40
+ const DELAYED_INFLUENCE_PATTERNS = [
41
+ /\b(?:later|next time|subsequent|future)\b.{0,45}\b(?:action|behavior|decision|request|session|task)\b/i,
42
+ /\b(?:when|whenever)\b.{0,80}\b(?:approve|buy|deploy|install|login|merge|pay|publish|release|send|transfer)\b/i,
43
+ ];
11
44
 
12
45
  function resolveMemoryFirewallProvider(provider) {
13
46
  const configured = String(
@@ -33,6 +66,9 @@ function canResolveShieldCortex() {
33
66
  }
34
67
 
35
68
  function buildIngressRecord(feedbackEvent = {}, memoryRecord = null) {
69
+ const memorySource = feedbackEvent.memorySource && typeof feedbackEvent.memorySource === 'object'
70
+ ? feedbackEvent.memorySource
71
+ : {};
36
72
  const feedbackPayload = {
37
73
  signal: feedbackEvent.signal || null,
38
74
  context: feedbackEvent.context || '',
@@ -79,24 +115,96 @@ function buildIngressRecord(feedbackEvent = {}, memoryRecord = null) {
79
115
  project: 'thumbgate',
80
116
  feedbackSignal: feedbackPayload.signal || null,
81
117
  memoryCategory: memoryPayload ? memoryPayload.category : null,
118
+ sourceType: memorySource.type || feedbackEvent.sourceType || null,
119
+ sourceIdentifier: memorySource.identifier || feedbackEvent.sourceIdentifier || null,
120
+ sourceTrust: memorySource.trust || feedbackEvent.sourceTrust || null,
82
121
  },
83
122
  };
84
123
  }
85
124
 
125
+ function normalizeSourceType(value) {
126
+ return String(value || '').trim().toLowerCase().replace(/[_\s]+/g, '-');
127
+ }
128
+
129
+ function assessStealthMemoryInjection(record, options = {}) {
130
+ const text = String(record && record.content || '');
131
+ const sourceType = normalizeSourceType(
132
+ options.sourceType || (record && record.metadata && record.metadata.sourceType)
133
+ );
134
+ const sourceTrust = String(
135
+ options.sourceTrust || (record && record.metadata && record.metadata.sourceTrust) || ''
136
+ ).trim().toLowerCase();
137
+ const untrusted = sourceTrust === 'untrusted' || UNTRUSTED_SOURCE_TYPES.has(sourceType);
138
+ const indicators = [];
139
+
140
+ if (MEMORY_WRITE_PATTERNS.some((pattern) => pattern.test(text))) indicators.push('memory_write_instruction');
141
+ if (STEALTH_PATTERNS.some((pattern) => pattern.test(text))) indicators.push('conversational_stealth');
142
+ if (OVERRIDE_PATTERNS.some((pattern) => pattern.test(text))) indicators.push('instruction_override');
143
+ if (DELAYED_INFLUENCE_PATTERNS.some((pattern) => pattern.test(text))) indicators.push('delayed_influence');
144
+
145
+ const highConfidence = indicators.includes('memory_write_instruction')
146
+ && indicators.includes('conversational_stealth');
147
+ const mode = resolveMemoryFirewallMode(options.mode);
148
+ const blocked = untrusted && (
149
+ (mode === 'strict' && indicators.includes('memory_write_instruction'))
150
+ || (mode === 'balanced' && highConfidence)
151
+ );
152
+
153
+ return {
154
+ blocked,
155
+ highConfidence,
156
+ untrusted,
157
+ sourceType: sourceType || 'unknown',
158
+ sourceTrust: sourceTrust || 'unknown',
159
+ indicators,
160
+ };
161
+ }
162
+
163
+ function buildMemoryInjectionTelemetry(assessment) {
164
+ const indicators = Array.isArray(assessment && assessment.indicators)
165
+ ? assessment.indicators
166
+ : [];
167
+ return {
168
+ 'gen_ai.security.memory_injection.detected': indicators.length > 0,
169
+ 'gen_ai.security.memory_injection.blocked': Boolean(assessment && assessment.blocked),
170
+ 'gen_ai.security.memory_injection.high_confidence': Boolean(assessment && assessment.highConfidence),
171
+ 'gen_ai.security.memory_injection.indicators': indicators,
172
+ 'gen_ai.security.memory_source.type': assessment && assessment.sourceType || 'unknown',
173
+ 'gen_ai.security.memory_source.trust': assessment && assessment.sourceTrust || 'unknown',
174
+ };
175
+ }
176
+
86
177
  function buildLocalFirewallDecision(record, options = {}) {
178
+ const injectionAssessment = assessStealthMemoryInjection(record, options);
87
179
  const scanResult = scanText(record.content, {
88
180
  provider: options.secretProvider,
89
181
  source: 'memory_ingress',
90
182
  });
91
183
 
184
+ if (injectionAssessment.blocked) {
185
+ return {
186
+ allowed: false,
187
+ provider: 'local',
188
+ mode: options.mode,
189
+ reason: `Stealth memory injection blocked from untrusted ${injectionAssessment.sourceType} content.`,
190
+ threatIndicators: ['stealth_memory_injection', ...injectionAssessment.indicators],
191
+ findings: [],
192
+ provenance: injectionAssessment,
193
+ telemetry: buildMemoryInjectionTelemetry(injectionAssessment),
194
+ redactedPreview: redactText(record.content).slice(0, 400),
195
+ };
196
+ }
197
+
92
198
  if (!scanResult.detected) {
93
199
  return {
94
200
  allowed: true,
95
201
  provider: 'local',
96
202
  mode: options.mode,
97
203
  reason: 'Local memory-ingress scan passed.',
98
- threatIndicators: [],
204
+ threatIndicators: injectionAssessment.untrusted ? injectionAssessment.indicators : [],
99
205
  findings: [],
206
+ provenance: injectionAssessment,
207
+ telemetry: buildMemoryInjectionTelemetry(injectionAssessment),
100
208
  redactedPreview: redactText(record.content).slice(0, 400),
101
209
  };
102
210
  }
@@ -164,8 +272,9 @@ function evaluateMemoryIngress({
164
272
  memoryRecord = null,
165
273
  provider,
166
274
  mode,
167
- sourceType = 'hook',
275
+ sourceType,
168
276
  sourceIdentifier = 'feedback-loop',
277
+ sourceTrust,
169
278
  secretProvider,
170
279
  } = {}) {
171
280
  const resolvedProvider = resolveMemoryFirewallProvider(provider);
@@ -199,6 +308,8 @@ function evaluateMemoryIngress({
199
308
  const localDecision = buildLocalFirewallDecision(record, {
200
309
  mode: resolvedMode,
201
310
  secretProvider,
311
+ sourceType,
312
+ sourceTrust,
202
313
  });
203
314
 
204
315
  if (resolvedProvider === 'shieldcortex') {
@@ -216,6 +327,8 @@ function evaluateMemoryIngress({
216
327
  module.exports = {
217
328
  buildIngressRecord,
218
329
  buildLocalFirewallDecision,
330
+ assessStealthMemoryInjection,
331
+ buildMemoryInjectionTelemetry,
219
332
  evaluateMemoryIngress,
220
333
  resolveMemoryFirewallMode,
221
334
  resolveMemoryFirewallProvider,