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
@@ -100,7 +100,10 @@ const PACK_TEMPLATES = {
100
100
  },
101
101
  };
102
102
 
103
-
103
+ const CONTEXT_ENVELOPE_VERSION = 'six-block-v1';
104
+ const CONTEXT_ENVELOPE_KEYS = [
105
+ 'goal', 'businessData', 'examples', 'procedures', 'constraints', 'rubric',
106
+ ];
104
107
  function ensureContextFs() {
105
108
  Object.values(NAMESPACES).forEach((subPath) => {
106
109
  ensureDir(contextFsPath(subPath));
@@ -178,12 +181,15 @@ function querySimilarity(tokensA, tokensB) {
178
181
  return union === 0 ? 0 : intersection / union;
179
182
  }
180
183
 
181
- function buildSemanticCacheKey({ namespaces, maxItems, maxChars }) {
184
+ function buildSemanticCacheKey({ namespaces, maxItems, maxChars, strategy = 'auto', contextEnvelope = null }) {
182
185
  return JSON.stringify({
183
186
  retrievalVersion: CONTEXTFS_RETRIEVAL_VERSION,
184
187
  namespaces: normalizeNamespaces(namespaces),
185
188
  maxItems,
186
189
  maxChars,
190
+ strategy,
191
+ contextEnvelopeHash: contextEnvelope ? crypto.createHash('sha256')
192
+ .update(JSON.stringify(contextEnvelope)).digest('hex') : null,
187
193
  });
188
194
  }
189
195
 
@@ -223,16 +229,14 @@ function getSourceHash(namespaces) {
223
229
  try {
224
230
  const stats = fs.statSync(filePath);
225
231
  hasher.update(`${file}:${stats.mtimeMs}:${stats.size}`);
226
- } catch {
227
- // Skip if file disappeared
228
- }
232
+ } catch { /* ignore */ }
229
233
  }
230
234
  }
231
235
  }
232
236
  return hasher.digest('hex');
233
237
  }
234
238
 
235
- function findSemanticCacheHit({ query, namespaces, maxItems, maxChars }) {
239
+ function findSemanticCacheHit({ query, namespaces, maxItems, maxChars, strategy = 'auto', contextEnvelope = null }) {
236
240
  const { enabled, threshold, ttlSeconds } = getSemanticCacheConfig();
237
241
  if (!enabled) return null;
238
242
 
@@ -241,14 +245,13 @@ function findSemanticCacheHit({ query, namespaces, maxItems, maxChars }) {
241
245
 
242
246
  const now = Date.now();
243
247
  const queryTokens = tokenizeQuery(query);
244
- const key = buildSemanticCacheKey({ namespaces, maxItems, maxChars });
248
+ const key = buildSemanticCacheKey({ namespaces, maxItems, maxChars, strategy, contextEnvelope });
245
249
  const currentSourceHash = getSourceHash(namespaces);
246
250
 
247
251
  for (let i = entries.length - 1; i >= 0; i -= 1) {
248
252
  const entry = entries[i];
249
253
  if (!entry || entry.key !== key || !entry.pack) continue;
250
254
 
251
- // Zero-Waste Caching: validate source hash
252
255
  if (entry.sourceHash !== currentSourceHash) {
253
256
  continue;
254
257
  }
@@ -343,6 +346,86 @@ function normalizeTagList(tags) {
343
346
  : [];
344
347
  }
345
348
 
349
+ function invalidContextEnvelope(message) {
350
+ return Object.assign(new Error(message), { code: 'INVALID_CONTEXT_ENVELOPE' });
351
+ }
352
+
353
+ function normalizeContextEnvelope(value) {
354
+ if (value == null) return null;
355
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
356
+ throw invalidContextEnvelope('contextEnvelope must be an object');
357
+ }
358
+
359
+ const unknownKeys = Object.keys(value).filter((key) => !CONTEXT_ENVELOPE_KEYS.includes(key));
360
+ if (unknownKeys.length > 0) {
361
+ throw invalidContextEnvelope(
362
+ `contextEnvelope contains unsupported blocks: ${unknownKeys.join(', ')}`,
363
+ );
364
+ }
365
+
366
+ const missingKeys = CONTEXT_ENVELOPE_KEYS.filter((key) => !Object.hasOwn(value, key));
367
+ if (missingKeys.length > 0) {
368
+ throw invalidContextEnvelope(
369
+ `contextEnvelope is missing required blocks: ${missingKeys.join(', ')}`,
370
+ );
371
+ }
372
+
373
+ const envelope = {
374
+ version: CONTEXT_ENVELOPE_VERSION,
375
+ };
376
+ for (const key of CONTEXT_ENVELOPE_KEYS) {
377
+ const items = key === 'goal' ? [value[key]] : value[key];
378
+ if (!Array.isArray(items)
379
+ || items.length === 0
380
+ || items.some((item) => (
381
+ typeof item !== 'string'
382
+ || !item.trim()
383
+ ))) {
384
+ throw invalidContextEnvelope(`contextEnvelope.${key} has invalid content`);
385
+ }
386
+ const normalized = items.map((item) => item.trim());
387
+ envelope[key] = key === 'goal' ? normalized[0] : normalized;
388
+ }
389
+ return envelope;
390
+ }
391
+
392
+ function measureContextEnvelopeChars(contextEnvelope) {
393
+ return contextEnvelope ? JSON.stringify(contextEnvelope).length : 0;
394
+ }
395
+
396
+ function getFreshness(observedAt, maxAgeSeconds, currentTimeMs) {
397
+ if (!observedAt) return 'unknown';
398
+ const observedAtMs = new Date(observedAt).getTime();
399
+ if (!Number.isFinite(observedAtMs)) return 'invalid';
400
+ if (observedAtMs > currentTimeMs + 300_000) return 'future';
401
+ if (!(Number.isFinite(maxAgeSeconds) && maxAgeSeconds > 0)) return 'unknown';
402
+ return currentTimeMs - observedAtMs <= maxAgeSeconds * 1000 ? 'fresh' : 'stale';
403
+ }
404
+
405
+ function buildItemProvenance(doc, currentTimeMs = Date.now()) {
406
+ const metadata = doc && typeof doc.metadata === 'object' ? doc.metadata : {};
407
+ const observedAtRaw = metadata.sourceUpdatedAt || doc.createdAt || null;
408
+ const observedAtMs = observedAtRaw ? new Date(observedAtRaw).getTime() : NaN;
409
+ const parsedMaxAge = Number(metadata.maxAgeSeconds);
410
+ const maxAgeSeconds = Number.isFinite(parsedMaxAge) && parsedMaxAge > 0 ? parsedMaxAge : null;
411
+
412
+ return {
413
+ source: doc.source || 'unknown',
414
+ sourceUrl: typeof metadata.sourceUrl === 'string' && metadata.sourceUrl.trim()
415
+ ? metadata.sourceUrl.trim()
416
+ : null,
417
+ observedAt: Number.isFinite(observedAtMs) ? new Date(observedAtMs).toISOString() : null,
418
+ maxAgeSeconds,
419
+ freshness: getFreshness(observedAtRaw, maxAgeSeconds, currentTimeMs),
420
+ };
421
+ }
422
+
423
+ function refreshItemProvenance(provenance, currentTimeMs = Date.now()) {
424
+ if (!provenance) return provenance;
425
+ const freshness = getFreshness(provenance.observedAt, Number(provenance.maxAgeSeconds), currentTimeMs);
426
+ return { ...provenance, freshness };
427
+ }
428
+
346
429
  function findExistingContextObject({ namespace, title, content, tags = [], source }) {
347
430
  ensureContextFs();
348
431
 
@@ -365,9 +448,7 @@ function findExistingContextObject({ namespace, title, content, tags = [], sourc
365
448
  filePath,
366
449
  document: doc,
367
450
  };
368
- } catch {
369
- // Ignore malformed entries while searching for exact duplicates.
370
- }
451
+ } catch { /* ignore */ }
371
452
  }
372
453
 
373
454
  return null;
@@ -502,9 +583,7 @@ function loadCandidates(namespaces) {
502
583
  ...payload,
503
584
  namespace,
504
585
  });
505
- } catch {
506
- // ignore malformed files
507
- }
586
+ } catch { /* ignore */ }
508
587
  });
509
588
  });
510
589
 
@@ -580,6 +659,7 @@ function selectFlatContextItems(candidates, maxItems, maxChars) {
580
659
  namespace: item.doc.namespace,
581
660
  title: item.doc.title,
582
661
  structuredContext: buildStructuredContext(item.doc),
662
+ provenance: buildItemProvenance(item.doc),
583
663
  tags: item.doc.tags || [],
584
664
  score: item.score,
585
665
  });
@@ -687,6 +767,7 @@ function selectSummarizeThenExpand(candidates, maxItems, maxChars) {
687
767
  namespace: item.doc.namespace,
688
768
  title: item.doc.title,
689
769
  structuredContext: buildSummaryContext(item.doc),
770
+ provenance: buildItemProvenance(item.doc),
690
771
  tags: item.doc.tags || [],
691
772
  score: item.score,
692
773
  tier: 'summary',
@@ -888,31 +969,33 @@ function constructContextPack({
888
969
  namespaces = [],
889
970
  strategy = null,
890
971
  summarizeThenExpand = false,
972
+ contextEnvelope = null,
891
973
  } = {}) {
892
974
  const normalizedNamespaces = normalizeNamespaces(namespaces);
975
+ const normalizedContextEnvelope = normalizeContextEnvelope(contextEnvelope);
976
+ const contextEnvelopeChars = measureContextEnvelopeChars(normalizedContextEnvelope);
977
+ if (contextEnvelopeChars >= maxChars) {
978
+ const error = new Error(
979
+ `contextEnvelope requires ${contextEnvelopeChars} characters but maxChars is ${maxChars}`,
980
+ );
981
+ error.code = 'CONTEXT_BUDGET_EXCEEDED';
982
+ throw error;
983
+ }
984
+ const retrievalMaxChars = maxChars - contextEnvelopeChars;
893
985
  const tokens = tokenizeQuery(query);
894
986
  const sourceHash = getSourceHash(normalizedNamespaces);
895
987
 
896
- // Resolve the effective strategy. Explicit `strategy` wins; otherwise
897
- // `summarizeThenExpand: true` flips the flag. Default remains auto
898
- // (flat | hierarchical) so callers that don't opt in keep their cached
899
- // packs addressable.
900
988
  const effectiveStrategy = strategy
901
989
  || (summarizeThenExpand ? 'summarize-then-expand' : null);
990
+ const hierarchicalRetrievalEnabled = shouldUseHierarchicalRetrieval(normalizedNamespaces);
991
+ const retrievalStrategy = effectiveStrategy === 'summarize-then-expand'
992
+ ? 'summarize-then-expand'
993
+ : (hierarchicalRetrievalEnabled ? 'hierarchical' : 'flat');
902
994
 
903
- // Skip the semantic cache for summarize-then-expand packs. The cache key
904
- // is (namespaces, maxItems, maxChars) — it doesn't include the strategy,
905
- // so a cached flat pack would be served to an STE caller (and vice versa)
906
- // with the wrong shape. Cheaper to recompute than to extend the cache key
907
- // and invalidate every entry on disk.
908
- const cacheHit = effectiveStrategy === 'summarize-then-expand'
909
- ? null
910
- : findSemanticCacheHit({
911
- query,
912
- namespaces: normalizedNamespaces,
913
- maxItems,
914
- maxChars,
915
- });
995
+ const cacheHit = findSemanticCacheHit({
996
+ query, namespaces: normalizedNamespaces, maxItems, maxChars,
997
+ strategy: retrievalStrategy, contextEnvelope: normalizedContextEnvelope,
998
+ });
916
999
 
917
1000
  if (cacheHit) {
918
1001
  const packId = `pack_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
@@ -922,6 +1005,12 @@ function constructContextPack({
922
1005
  packId,
923
1006
  query,
924
1007
  createdAt: nowIso(),
1008
+ items: Array.isArray(cachedPack.items)
1009
+ ? cachedPack.items.map((item) => ({
1010
+ ...item,
1011
+ provenance: refreshItemProvenance(item.provenance),
1012
+ }))
1013
+ : cachedPack.items,
925
1014
  cache: {
926
1015
  hit: true,
927
1016
  similarity: Number(cacheHit.score.toFixed(4)),
@@ -947,31 +1036,22 @@ function constructContextPack({
947
1036
  .map((doc) => ({ doc, score: scoreDocument(doc, tokens) }))
948
1037
  .sort((a, b) => b.score - a.score);
949
1038
 
950
- const hierarchicalRetrievalEnabled = shouldUseHierarchicalRetrieval(normalizedNamespaces);
951
1039
  let selection;
952
1040
  if (effectiveStrategy === 'summarize-then-expand') {
953
- // Explicit opt-in: bypass the hierarchical path entirely. The
954
- // summarize-then-expand selector assumes a flat ranked list where each
955
- // item is a single episode, and mixing it with theme-based hierarchical
956
- // retrieval would double-compress the top-of-list.
957
- selection = selectSummarizeThenExpand(candidates, maxItems, maxChars);
1041
+ selection = selectSummarizeThenExpand(candidates, maxItems, retrievalMaxChars);
958
1042
  } else if (hierarchicalRetrievalEnabled) {
959
1043
  selection = retrieveHierarchicalDocuments({
960
1044
  documents: candidates.map((candidate) => candidate.doc),
961
1045
  query,
962
1046
  maxItems,
963
- maxChars,
1047
+ maxChars: retrievalMaxChars,
964
1048
  scorer: scoreDocument,
965
1049
  measureDocument: measureDocumentChars,
966
1050
  });
967
1051
  } else {
968
- selection = selectFlatContextItems(candidates, maxItems, maxChars);
1052
+ selection = selectFlatContextItems(candidates, maxItems, retrievalMaxChars);
969
1053
  }
970
1054
 
971
- // The flat + hierarchical paths emit raw docs; summarize-then-expand emits
972
- // fully-shaped items that already carry structuredContext and a `tier`
973
- // marker. Detect the shape so we don't double-canonicalize STE items
974
- // (which would re-expand every summary into full content).
975
1055
  const selected = selection.items.map((item) => {
976
1056
  if (item && item.structuredContext) {
977
1057
  return {
@@ -979,6 +1059,7 @@ function constructContextPack({
979
1059
  namespace: item.namespace,
980
1060
  title: item.title,
981
1061
  structuredContext: item.structuredContext,
1062
+ provenance: item.provenance || buildItemProvenance(item),
982
1063
  tags: item.tags || [],
983
1064
  score: typeof item.score === 'number' ? item.score : scoreDocument(item, tokens),
984
1065
  ...(item.tier ? { tier: item.tier } : {}),
@@ -989,11 +1070,12 @@ function constructContextPack({
989
1070
  namespace: item.namespace,
990
1071
  title: item.title,
991
1072
  structuredContext: buildStructuredContext(item),
1073
+ provenance: buildItemProvenance(item),
992
1074
  tags: item.tags || [],
993
1075
  score: scoreDocument(item, tokens),
994
1076
  };
995
1077
  });
996
- const usedChars = selection.usedChars;
1078
+ const usedChars = selection.usedChars + contextEnvelopeChars;
997
1079
  const skippedByMaxChars = selection.skippedByMaxChars;
998
1080
 
999
1081
  const visibility = {
@@ -1014,6 +1096,8 @@ function constructContextPack({
1014
1096
  maxItems,
1015
1097
  maxChars,
1016
1098
  usedChars,
1099
+ contextEnvelope: normalizedContextEnvelope,
1100
+ contextEnvelopeChars,
1017
1101
  namespaces: normalizedNamespaces,
1018
1102
  createdAt: nowIso(),
1019
1103
  items: selected,
@@ -1026,29 +1110,27 @@ function constructContextPack({
1026
1110
  };
1027
1111
 
1028
1112
  appendJsonl(contextFsPath(NAMESPACES.provenance, 'packs.jsonl'), pack);
1029
- // Symmetric with the cache read: don't persist STE packs into the shared
1030
- // semantic cache because the cache key is strategy-agnostic.
1031
- if (effectiveStrategy !== 'summarize-then-expand') {
1032
- appendSemanticCacheEntry({
1033
- id: `cache_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
1034
- timestamp: nowIso(),
1035
- key: buildSemanticCacheKey({
1036
- namespaces: normalizedNamespaces,
1037
- maxItems,
1038
- maxChars,
1039
- }),
1040
- query,
1041
- tokens,
1042
- sourceHash,
1043
- pack,
1044
- });
1045
- }
1113
+ appendSemanticCacheEntry({
1114
+ id: `cache_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
1115
+ timestamp: nowIso(),
1116
+ key: buildSemanticCacheKey({
1117
+ namespaces: normalizedNamespaces, maxItems, maxChars,
1118
+ strategy: retrievalStrategy, contextEnvelope: normalizedContextEnvelope,
1119
+ }),
1120
+ query,
1121
+ tokens,
1122
+ sourceHash,
1123
+ pack,
1124
+ });
1046
1125
  recordProvenance({
1047
1126
  type: 'context_pack_constructed',
1048
1127
  packId,
1049
1128
  query,
1050
1129
  itemCount: selected.length,
1051
1130
  usedChars,
1131
+ contextEnvelopeVersion: normalizedContextEnvelope
1132
+ ? normalizedContextEnvelope.version
1133
+ : null,
1052
1134
  sourceHash,
1053
1135
  retrievalStrategy: selection.retrieval.strategy,
1054
1136
  });
@@ -0,0 +1,27 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Shared helpers for dashboard size / heap failures (prod feedback logs).
5
+ */
6
+
7
+ function isDashboardDataLimitError(err) {
8
+ const message = String(err && err.message ? err.message : err || '');
9
+ return /string longer than|Cannot create a string|ENOMEM|JavaScript heap|out of memory/i.test(message);
10
+ }
11
+
12
+ function formatDashboardLimitDetail(err, { phase = 'assembly' } = {}) {
13
+ const cause = err && err.message ? err.message : 'string/heap limit';
14
+ if (phase === 'stringify') {
15
+ return 'Dashboard JSON exceeded the runtime string limit. '
16
+ + 'Use a bounded feedback window or rotate oversized logs. '
17
+ + `Cause: ${cause}`;
18
+ }
19
+ return 'Feedback/memory logs exceeded the safe in-memory limit for dashboard assembly. '
20
+ + 'Logs are now tail-capped; if this persists, rotate oversized JSONL under the feedback dir. '
21
+ + `Cause: ${cause}`;
22
+ }
23
+
24
+ module.exports = {
25
+ isDashboardDataLimitError,
26
+ formatDashboardLimitDetail,
27
+ };