claude-flow 3.16.3 → 3.18.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.
@@ -24,6 +24,13 @@
24
24
  *
25
25
  * - metaharness_redblue red-team → judge → blue-patch → retest → report
26
26
  *
27
+ * metaharness@0.3.0 + @metaharness/darwin@0.8.0 add two more:
28
+ *
29
+ * - metaharness_learn upstream ADR-235 GEPA learning run ($0 dry-run
30
+ * default; run=true to spend; needs repo checkout)
31
+ * - metaharness_gepa GEPA library surface — genome load/validate/
32
+ * render + transcript failure analysis
33
+ *
27
34
  * Every tool resolves the corresponding plugin script
28
35
  * (`plugins/ruflo-metaharness/scripts/<X>.mjs`) via the same locator
29
36
  * the commands/metaharness.ts dispatcher uses, then spawns it with
@@ -591,5 +598,87 @@ export const metaharnessTools = [
591
598
  return { success: r.success, data: r.json, degraded: r.degraded, exitCode: r.exitCode };
592
599
  },
593
600
  },
601
+ // ───────────────────────────────────────────────────────────────────────
602
+ // metaharness@0.3.0 `learn` + @metaharness/darwin@0.8.0 GEPA (2 tools).
603
+ //
604
+ // learn — upstream ADR-235 GEPA learning run. $0 dry-run by default;
605
+ // spending requires the explicit `run: true` opt-in, forwarded as --run.
606
+ // Requires a metaharness repo checkout (the learning harness is too heavy
607
+ // for the npm package) — absent checkout returns a structured
608
+ // {status: "checkout-required"} payload, distinct from degraded.
609
+ //
610
+ // gepa — the darwin GEPA *library* surface (genome load/validate/render,
611
+ // transcript analysis). gepaOptimize itself is deliberately NOT exposed:
612
+ // it takes an in-process evaluate() callback that cannot cross the
613
+ // subprocess boundary; optimization runs live behind metaharness_evolve.
614
+ // ───────────────────────────────────────────────────────────────────────
615
+ {
616
+ name: 'metaharness_learn',
617
+ description: 'ADR-235 (upstream) — GEPA learning run via `metaharness learn`: optimizes a harness genome against a SWE-bench-style slice manifest. $0 DRY-RUN BY DEFAULT — it resolves the slice and prices the run without model calls; pass run=true to actually spend (model calls + Docker sandboxes). Requires a local metaharness repo checkout (repo param or $METAHARNESS_REPO); without one the tool returns {status:"checkout-required"} with clone instructions — that is a precondition report, not an error. Use when you want the harness policy to LEARN from a task corpus rather than hand-editing prompts; manual prompt tweaking is wrong because GEPA scores candidates against held-out slices and only promotes measured winners. Long real runs exceed the 120s MCP subprocess budget — run those via `ruflo metaharness learn` in a terminal instead. ' + MCP_SUCCESS_SEMANTIC,
618
+ category: 'metaharness',
619
+ inputSchema: {
620
+ type: 'object',
621
+ properties: {
622
+ host: { type: 'string', description: 'Target host harness (e.g. claude-code, codex, pi-dev, hermes)' },
623
+ model: { type: 'string', description: 'Model to learn against (upstream model id)' },
624
+ slice: { type: 'string', description: 'Path to a slice manifest JSON' },
625
+ repo: { type: 'string', description: 'Path to a metaharness repo checkout (sets $METAHARNESS_REPO)' },
626
+ run: { type: 'boolean', description: 'EXPLICIT SPEND OPT-IN — without this the run is a $0 dry-run', default: false },
627
+ alertOnFail: { type: 'boolean', description: 'Exit 1 when the learn run reports failure', default: false },
628
+ timeoutMs: { type: 'number', description: 'Subprocess hard timeout override' },
629
+ },
630
+ },
631
+ handler: async (input) => {
632
+ const args = [];
633
+ if (input.host)
634
+ args.push('--host', String(input.host));
635
+ if (input.model)
636
+ args.push('--model', String(input.model));
637
+ if (input.slice)
638
+ args.push('--slice', String(input.slice));
639
+ if (input.repo)
640
+ args.push('--repo', String(input.repo));
641
+ if (input.run === true)
642
+ args.push('--run');
643
+ if (input.alertOnFail === true)
644
+ args.push('--alert-on-fail');
645
+ if (input.timeoutMs !== undefined)
646
+ args.push('--timeout-ms', String(input.timeoutMs));
647
+ const r = await runScript('learn.mjs', args);
648
+ return { success: r.success, data: r.json, degraded: r.degraded, exitCode: r.exitCode };
649
+ },
650
+ },
651
+ {
652
+ name: 'metaharness_gepa',
653
+ description: 'GEPA genome operations from the `@metaharness/darwin/gepa` library entry (darwin 0.8.0). op=genome loads + validates a genome (default: the shipped cand-6 — first holdout-confirmed cheap-tier policy promotion, provenance in the package); op=validate returns structural errors for a genome JSON; op=render compiles a genome to the system prompt it encodes (inspect what a policy actually says before adopting it); op=analyze classifies failure modes in a transcript JSON array. Use when adopting/auditing/debugging evolved harness policies — reading genome JSON by eye is wrong because the behavior lives in the rendered system prompt and the component interactions, not the raw fields. NOTE: gepaOptimize (bring-your-own-evaluator optimization) is library-only — import @metaharness/darwin/gepa directly, or use metaharness_evolve for sandbox-scored evolution. ' + MCP_SUCCESS_SEMANTIC,
654
+ category: 'metaharness',
655
+ inputSchema: {
656
+ type: 'object',
657
+ properties: {
658
+ op: { type: 'string', enum: ['genome', 'validate', 'render', 'analyze'], description: 'genome = load + validate; validate = structural errors only; render = genome → system prompt; analyze = transcript failure classes' },
659
+ path: { type: 'string', description: 'Genome JSON path (genome/validate/render; default: shipped cand-6)' },
660
+ transcript: { type: 'string', description: 'Transcript JSON array path (required for op=analyze)' },
661
+ ext: { type: 'string', description: 'render only — target file extension hint' },
662
+ glob: { type: 'string', description: 'render only — target glob hint' },
663
+ alertOnInvalid: { type: 'boolean', description: 'Exit 1 when validation finds errors (gate-style)', default: false },
664
+ },
665
+ required: ['op'],
666
+ },
667
+ handler: async (input) => {
668
+ const args = ['--op', String(input.op)];
669
+ if (input.path)
670
+ args.push('--path', String(input.path));
671
+ if (input.transcript)
672
+ args.push('--transcript', String(input.transcript));
673
+ if (input.ext)
674
+ args.push('--ext', String(input.ext));
675
+ if (input.glob)
676
+ args.push('--glob', String(input.glob));
677
+ if (input.alertOnInvalid === true)
678
+ args.push('--alert-on-invalid');
679
+ const r = await runScript('gepa.mjs', args);
680
+ return { success: r.success, data: r.json, degraded: r.degraded, exitCode: r.exitCode };
681
+ },
682
+ },
594
683
  ];
595
684
  //# sourceMappingURL=metaharness-tools.js.map
@@ -15,99 +15,120 @@ import { getProjectCwd } from './types.js';
15
15
  import { validateIdentifier, validateText } from './validate-input.js';
16
16
  import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';
17
17
  import { join } from 'node:path';
18
- // Try to import real embeddings.
19
- // Tier 0 (NEW, ADR-089): ruvector@0.2.27 bundled ONNX (no sharp dep, fixes ADR-086's
18
+ // Real embeddings resolved LAZILY on first use.
19
+ // Perf (measured 2026-07): the previous top-level-await version of this block
20
+ // ran `await import('ruvector')` + `initOnnxEmbedder()` + a probe embed at
21
+ // module-import time, adding ~450ms (warm) to ~2800ms (cold) to EVERY CLI
22
+ // command, because mcp-client statically imports this module. ensureEmbeddings()
23
+ // keeps the exact same tier chain and degraded/fallback semantics, but only
24
+ // pays the cost when a handler actually needs an embedding.
25
+ // Tier 0 (ADR-089): ruvector@0.2.27 bundled ONNX (no sharp dep, fixes ADR-086's
20
26
  // silent-fallback bug at source; closes the chain described in ruvnet/ruvector#523).
21
27
  // Tier 1: agentic-flow v3 ReasoningBank (was Tier 1 — broken on darwin-arm64 without sharp)
22
28
  // Tier 2-3: @claude-flow/embeddings
23
29
  let realEmbeddings = null;
24
30
  let embeddingServiceName = 'none';
25
- try {
26
- // Tier 0: ruvector@0.2.27 — bundled all-MiniLM-L6-v2 + parallel worker pool.
27
- // Probe with isOnnxAvailable() and verify an actual embed succeeds (avoids
28
- // the type-load-success-but-runtime-fails trap from ADR-086).
29
- // NOTE: ruvector's embed() returns `{embedding, dimension, timeMs}` we
30
- // unwrap to plain number[] for the shared interface.
31
- const rv = await import('ruvector').catch(() => null);
32
- if (rv?.embed && typeof rv.embed === 'function' && rv.isOnnxAvailable?.()) {
31
+ let embeddingsPromise = null;
32
+ /**
33
+ * Memoized lazy initialiser for the embedding provider chain. Safe to call
34
+ * concurrently (single shared promise). If every tier fails to import or
35
+ * probe, `realEmbeddings` stays null and callers use the explicit
36
+ * hash-fallback path identical degraded semantics to the old eager block.
37
+ */
38
+ function ensureEmbeddings() {
39
+ if (embeddingsPromise)
40
+ return embeddingsPromise;
41
+ embeddingsPromise = (async () => {
33
42
  try {
34
- if (typeof rv.initOnnxEmbedder === 'function')
35
- await rv.initOnnxEmbedder();
36
- const probe = await rv.embed('probe');
37
- // Handle both shapes: ruvector wraps as {embedding, dimension, timeMs};
38
- // some versions returned raw Float32Array.
39
- const probeVec = probe?.embedding ?? probe;
40
- if (probeVec && (Array.isArray(probeVec) || probeVec.length > 0)) {
41
- realEmbeddings = {
42
- embed: async (text) => {
43
- const r = await rv.embed(text);
44
- const v = r?.embedding ?? r;
45
- return Array.isArray(v) ? v : Array.from(v);
46
- },
47
- };
48
- embeddingServiceName = 'ruvector@0.2.27 (bundled all-MiniLM-L6-v2)';
49
- }
50
- }
51
- catch {
52
- // ruvector embed failed at runtime; fall through to next tier
53
- }
54
- }
55
- // Tier 1: agentic-flow v3 ReasoningBank (kept for backward-compat; may
56
- // silently fall back on darwin-arm64 without sharp — that's the bug
57
- // Tier 0 was added to bypass).
58
- if (!realEmbeddings) {
59
- const rb = await import('agentic-flow/reasoningbank').catch(() => null);
60
- if (rb?.computeEmbedding) {
61
- realEmbeddings = { embed: (text) => rb.computeEmbedding(text) };
62
- embeddingServiceName = 'agentic-flow/reasoningbank';
63
- }
64
- }
65
- // Tier 2: @claude-flow/embeddings with agentic-flow provider
66
- if (!realEmbeddings) {
67
- const embeddingsModule = await import('@claude-flow/embeddings').catch(() => null);
68
- if (embeddingsModule?.createEmbeddingService) {
69
- try {
70
- const service = embeddingsModule.createEmbeddingService({ provider: 'agentic-flow' });
71
- realEmbeddings = {
72
- embed: async (text) => {
73
- const result = await service.embed(text);
74
- return Array.from(result.embedding);
75
- },
76
- };
77
- embeddingServiceName = 'agentic-flow';
43
+ // Tier 0: ruvector@0.2.27 bundled all-MiniLM-L6-v2 + parallel worker pool.
44
+ // Probe with isOnnxAvailable() and verify an actual embed succeeds (avoids
45
+ // the type-load-success-but-runtime-fails trap from ADR-086). The probe now
46
+ // runs on first embed request instead of at import time.
47
+ // NOTE: ruvector's embed() returns `{embedding, dimension, timeMs}` — we
48
+ // unwrap to plain number[] for the shared interface.
49
+ const rv = await import('ruvector').catch(() => null);
50
+ if (rv?.embed && typeof rv.embed === 'function' && rv.isOnnxAvailable?.()) {
51
+ try {
52
+ if (typeof rv.initOnnxEmbedder === 'function')
53
+ await rv.initOnnxEmbedder();
54
+ const probe = await rv.embed('probe');
55
+ // Handle both shapes: ruvector wraps as {embedding, dimension, timeMs};
56
+ // some versions returned raw Float32Array.
57
+ const probeVec = probe?.embedding ?? probe;
58
+ if (probeVec && (Array.isArray(probeVec) || probeVec.length > 0)) {
59
+ realEmbeddings = {
60
+ embed: async (text) => {
61
+ const r = await rv.embed(text);
62
+ const v = r?.embedding ?? r;
63
+ return Array.isArray(v) ? v : Array.from(v);
64
+ },
65
+ };
66
+ embeddingServiceName = 'ruvector@0.2.27 (bundled all-MiniLM-L6-v2)';
67
+ }
68
+ }
69
+ catch {
70
+ // ruvector embed failed at runtime; fall through to next tier
71
+ }
78
72
  }
79
- catch {
80
- // agentic-flow provider not available, try ONNX
73
+ // Tier 1: agentic-flow v3 ReasoningBank (kept for backward-compat; may
74
+ // silently fall back on darwin-arm64 without sharp that's the bug
75
+ // Tier 0 was added to bypass).
76
+ if (!realEmbeddings) {
77
+ const rb = await import('agentic-flow/reasoningbank').catch(() => null);
78
+ if (rb?.computeEmbedding) {
79
+ realEmbeddings = { embed: (text) => rb.computeEmbedding(text) };
80
+ embeddingServiceName = 'agentic-flow/reasoningbank';
81
+ }
81
82
  }
82
- }
83
- }
84
- // Tier 3: @claude-flow/embeddings with ONNX provider
85
- if (!realEmbeddings) {
86
- const embeddingsModule = await import('@claude-flow/embeddings').catch(() => null);
87
- if (embeddingsModule?.createEmbeddingService) {
88
- try {
89
- const service = embeddingsModule.createEmbeddingService({ provider: 'onnx' });
90
- realEmbeddings = {
91
- embed: async (text) => {
92
- const result = await service.embed(text);
93
- return Array.from(result.embedding);
94
- },
95
- };
96
- embeddingServiceName = 'onnx';
83
+ // Tier 2: @claude-flow/embeddings with agentic-flow provider
84
+ if (!realEmbeddings) {
85
+ const embeddingsModule = await import('@claude-flow/embeddings').catch(() => null);
86
+ if (embeddingsModule?.createEmbeddingService) {
87
+ try {
88
+ const service = embeddingsModule.createEmbeddingService({ provider: 'agentic-flow' });
89
+ realEmbeddings = {
90
+ embed: async (text) => {
91
+ const result = await service.embed(text);
92
+ return Array.from(result.embedding);
93
+ },
94
+ };
95
+ embeddingServiceName = 'agentic-flow';
96
+ }
97
+ catch {
98
+ // agentic-flow provider not available, try ONNX
99
+ }
100
+ }
97
101
  }
98
- catch {
99
- // ONNX provider not available, fall through to mock
102
+ // Tier 3: @claude-flow/embeddings with ONNX provider
103
+ if (!realEmbeddings) {
104
+ const embeddingsModule = await import('@claude-flow/embeddings').catch(() => null);
105
+ if (embeddingsModule?.createEmbeddingService) {
106
+ try {
107
+ const service = embeddingsModule.createEmbeddingService({ provider: 'onnx' });
108
+ realEmbeddings = {
109
+ embed: async (text) => {
110
+ const result = await service.embed(text);
111
+ return Array.from(result.embedding);
112
+ },
113
+ };
114
+ embeddingServiceName = 'onnx';
115
+ }
116
+ catch {
117
+ // ONNX provider not available, fall through to mock
118
+ }
119
+ }
100
120
  }
121
+ // No Tier 4 mock fallback. If all real-embedder tiers fail to import or
122
+ // probe, leave realEmbeddings null and let downstream code use the
123
+ // explicit hash-fallback path with a clear _embeddingNote in stats.
124
+ // Silently substituting mock embeddings would hide a missing production
125
+ // dependency from callers — that's the bug ADR-086 was about.
101
126
  }
102
- }
103
- // No Tier 4 mock fallback. If all real-embedder tiers fail to import or
104
- // probe, leave realEmbeddings null and let downstream code use the
105
- // explicit hash-fallback path with a clear _embeddingNote in stats.
106
- // Silently substituting mock embeddings would hide a missing production
107
- // dependency from callers — that's the bug ADR-086 was about.
108
- }
109
- catch {
110
- // No embedding provider available, will use fallback
127
+ catch {
128
+ // No embedding provider available, will use fallback
129
+ }
130
+ })();
131
+ return embeddingsPromise;
111
132
  }
112
133
  // Storage paths
113
134
  const STORAGE_DIR = '.claude-flow';
@@ -173,8 +194,9 @@ export function getNeuralStoreStats() {
173
194
  export async function storeNeuralPatterns(items) {
174
195
  if (!items || items.length === 0)
175
196
  return { stored: 0, total: 0 };
176
- // realEmbeddings is initialised by the top-level IIFE in this module;
177
- // generateEmbedding() falls back to a hash-based embedding if it isn't.
197
+ // realEmbeddings is initialised lazily by ensureEmbeddings() (invoked from
198
+ // generateEmbedding()); it falls back to a hash-based embedding if no
199
+ // provider is available.
178
200
  const store = loadNeuralStore();
179
201
  let stored = 0;
180
202
  for (const item of items) {
@@ -200,6 +222,10 @@ export async function storeNeuralPatterns(items) {
200
222
  }
201
223
  // Generate embedding - uses real ML embeddings if available, falls back to deterministic hash
202
224
  async function generateEmbedding(text, dims = 384) {
225
+ // Lazily resolve the embedding provider on first real use (perf: keeps
226
+ // ONNX init off the CLI startup path — see ensureEmbeddings()).
227
+ if (text)
228
+ await ensureEmbeddings();
203
229
  // If real embeddings available and text provided, use them
204
230
  if (realEmbeddings && text) {
205
231
  try {
@@ -747,6 +773,9 @@ export const neuralTools = [
747
773
  if (!v.valid)
748
774
  return { success: false, error: v.error };
749
775
  }
776
+ // Resolve provider so embeddingProvider in the result matches the old
777
+ // eager-init reporting.
778
+ await ensureEmbeddings();
750
779
  const store = loadNeuralStore();
751
780
  const method = input.method || 'quantize';
752
781
  const targetReduction = input.targetSize || 0.5;
@@ -860,6 +889,9 @@ export const neuralTools = [
860
889
  if (!v.valid)
861
890
  return { success: false, error: v.error };
862
891
  }
892
+ // Resolve provider so _realEmbeddings/embeddingProvider report the same
893
+ // values the old eager-init version produced.
894
+ await ensureEmbeddings();
863
895
  const store = loadNeuralStore();
864
896
  if (input.modelId) {
865
897
  const model = store.models[input.modelId];
@@ -928,6 +960,9 @@ export const neuralTools = [
928
960
  if (!v.valid)
929
961
  return { success: false, error: v.error };
930
962
  }
963
+ // Resolve provider so embeddingProvider in the result matches the old
964
+ // eager-init reporting.
965
+ await ensureEmbeddings();
931
966
  const store = loadNeuralStore();
932
967
  const target = input.target || 'balanced';
933
968
  const patterns = Object.values(store.patterns);
@@ -419,14 +419,26 @@ export declare function bridgeHealthCheck(dbPath?: string): Promise<{
419
419
  *
420
420
  * Real HierarchicalMemory API (agentdb alpha.10+):
421
421
  * store(content, importance?, tier?, options?) → Promise<string>
422
- * Stub API (fallback):
423
- * store(key, value, tier) — synchronous
422
+ * Fallback API (@claude-flow/memory TieredMemoryStore):
423
+ * store(key, value, tier, temporalOptions?) — synchronous, returns
424
+ * { id, key, tier, superseded? }
425
+ *
426
+ * Temporal validity (Zep/Graphiti-style, impl/memory-sota):
427
+ * - validFrom / validUntil (ISO) travel with the entry.
428
+ * - supersedes=<entryId|key> INVALIDATES the old entry (validUntil=now +
429
+ * supersededBy=newId) instead of deleting it. Natively supported by the
430
+ * TieredMemoryStore fallback; on the real agentdb HierarchicalMemory the
431
+ * temporal fields are stored in metadata, and supersede is reported as
432
+ * unsupported (no public update API) rather than silently dropped.
424
433
  */
425
434
  export declare function bridgeHierarchicalStore(params: {
426
435
  key: string;
427
436
  value: string;
428
437
  tier?: string;
429
438
  importance?: number;
439
+ validFrom?: string;
440
+ validUntil?: string;
441
+ supersedes?: string;
430
442
  }): Promise<any>;
431
443
  /**
432
444
  * Recall from hierarchical memory.
@@ -441,6 +453,7 @@ export declare function bridgeHierarchicalRecall(params: {
441
453
  query: string;
442
454
  tier?: string;
443
455
  topK?: number;
456
+ includeExpired?: boolean;
444
457
  }): Promise<any>;
445
458
  /**
446
459
  * Run memory consolidation.
@@ -1970,8 +1970,17 @@ export async function bridgeHealthCheck(dbPath) {
1970
1970
  *
1971
1971
  * Real HierarchicalMemory API (agentdb alpha.10+):
1972
1972
  * store(content, importance?, tier?, options?) → Promise<string>
1973
- * Stub API (fallback):
1974
- * store(key, value, tier) — synchronous
1973
+ * Fallback API (@claude-flow/memory TieredMemoryStore):
1974
+ * store(key, value, tier, temporalOptions?) — synchronous, returns
1975
+ * { id, key, tier, superseded? }
1976
+ *
1977
+ * Temporal validity (Zep/Graphiti-style, impl/memory-sota):
1978
+ * - validFrom / validUntil (ISO) travel with the entry.
1979
+ * - supersedes=<entryId|key> INVALIDATES the old entry (validUntil=now +
1980
+ * supersededBy=newId) instead of deleting it. Natively supported by the
1981
+ * TieredMemoryStore fallback; on the real agentdb HierarchicalMemory the
1982
+ * temporal fields are stored in metadata, and supersede is reported as
1983
+ * unsupported (no public update API) rather than silently dropped.
1975
1984
  */
1976
1985
  export async function bridgeHierarchicalStore(params) {
1977
1986
  const registry = await getRegistry();
@@ -1984,15 +1993,36 @@ export async function bridgeHierarchicalStore(params) {
1984
1993
  const tier = params.tier || 'working';
1985
1994
  // Detect real HierarchicalMemory (has async store returning id) vs stub
1986
1995
  if (typeof hm.getStats === 'function' && typeof hm.promote === 'function') {
1987
- // Real agentdb HierarchicalMemory
1996
+ // Real agentdb HierarchicalMemory — temporal fields ride in metadata
1997
+ // so bridgeHierarchicalRecall can filter on them.
1998
+ const metadata = { key: params.key };
1999
+ if (params.validFrom)
2000
+ metadata.validFrom = params.validFrom;
2001
+ if (params.validUntil)
2002
+ metadata.validUntil = params.validUntil;
1988
2003
  const id = await hm.store(params.value, params.importance || 0.5, tier, {
1989
- metadata: { key: params.key },
2004
+ metadata,
1990
2005
  tags: [params.key],
1991
2006
  });
1992
- return { success: true, id, key: params.key, tier };
2007
+ const result = { success: true, id, key: params.key, tier };
2008
+ if (params.supersedes) {
2009
+ // No public update/invalidate API on agentdb HierarchicalMemory —
2010
+ // surface the limitation honestly instead of silently dropping it.
2011
+ result.superseded = null;
2012
+ result.warning = 'supersedes is not supported by the native agentdb HierarchicalMemory backend (no update API); the referenced entry was left untouched';
2013
+ }
2014
+ return result;
2015
+ }
2016
+ // TieredMemoryStore fallback (temporal-aware) / legacy stub
2017
+ const storeResult = hm.store(params.key, params.value, tier, {
2018
+ validFrom: params.validFrom,
2019
+ validUntil: params.validUntil,
2020
+ supersedes: params.supersedes,
2021
+ });
2022
+ if (storeResult && typeof storeResult === 'object') {
2023
+ return { success: true, ...storeResult };
1993
2024
  }
1994
- // Stub fallback
1995
- hm.store(params.key, params.value, tier);
2025
+ // Legacy stub (returns void) — temporal options were ignored
1996
2026
  return { success: true, key: params.key, tier };
1997
2027
  }
1998
2028
  catch (e) {
@@ -2027,10 +2057,34 @@ export async function bridgeHierarchicalRecall(params) {
2027
2057
  memoryQuery.tier = params.tier;
2028
2058
  }
2029
2059
  const results = await hm.recall(memoryQuery);
2030
- return { results: results || [], controller: 'hierarchicalMemory' };
2060
+ // Temporal filter over metadata (validFrom/validUntil stamped by
2061
+ // bridgeHierarchicalStore). Entries without the fields are always
2062
+ // valid — legacy behavior unchanged.
2063
+ const filtered = params.includeExpired
2064
+ ? (results || [])
2065
+ : (results || []).filter((r) => {
2066
+ const meta = r?.metadata || {};
2067
+ const now = Date.now();
2068
+ if (meta.validFrom) {
2069
+ const from = Date.parse(String(meta.validFrom));
2070
+ if (!Number.isNaN(from) && from > now)
2071
+ return false;
2072
+ }
2073
+ if (meta.validUntil) {
2074
+ const until = Date.parse(String(meta.validUntil));
2075
+ if (!Number.isNaN(until) && until <= now)
2076
+ return false;
2077
+ }
2078
+ return true;
2079
+ });
2080
+ return { results: filtered, controller: 'hierarchicalMemory' };
2031
2081
  }
2032
- // Stub fallback — recall(string, number)
2033
- const results = hm.recall(params.query, params.topK || 5);
2082
+ // TieredMemoryStore fallback — recall(string, number, options?).
2083
+ // Temporal filtering happens inside the store; the legacy stub simply
2084
+ // ignores the extra argument.
2085
+ const results = hm.recall(params.query, params.topK || 5, {
2086
+ includeExpired: params.includeExpired === true,
2087
+ });
2034
2088
  const filtered = params.tier
2035
2089
  ? results.filter((r) => r.tier === params.tier)
2036
2090
  : results;
@@ -0,0 +1,83 @@
1
+ /**
2
+ * output-verifier.ts — Confidence-gated tier escalation (post-generation).
3
+ *
4
+ * ADR-026/143 route tasks to a tier BEFORE generation. 2026 SOTA cascade
5
+ * routing adds a post-generation gate: attempt the cheap tier, run a CHEAP
6
+ * verifier over the produced output, and escalate to the next tier only when
7
+ * the verifier is not confident. This module is that verifier.
8
+ *
9
+ * Design constraints (load-bearing):
10
+ * - $0 by default — NO LLM call. Every signal is a structural / lexical
11
+ * check: emptiness, refusal patterns, truncation, delimiter balance,
12
+ * degenerate repetition, and (for code tasks) a real syntax parse via the
13
+ * TypeScript compiler (lazy-imported; degrades to delimiter checks when
14
+ * typescript is not installed) or JSON.parse for JSON output.
15
+ * - Pure with respect to router state — recording the verdict into the
16
+ * bandit's learning stream is the CALLER's job (the hooks_model-verify
17
+ * MCP tool does it via ModelRouter.recordOutcome), keeping this module
18
+ * trivially unit-testable.
19
+ * - Escalation ladder mirrors the tier table: tier 2 (haiku) → tier 3
20
+ * (sonnet), sonnet → opus, opus has no bump (escalate=false even when the
21
+ * verdict is not confident — the caller should retry or surface instead).
22
+ *
23
+ * Verdict semantics: `confident=false` means "cheap signals say this output
24
+ * is likely unusable"; it is NOT a semantic-quality judgment. False
25
+ * negatives (bad output that parses fine) are expected — this gate trades
26
+ * recall for being free.
27
+ *
28
+ * @module ruvector/output-verifier
29
+ */
30
+ export type VerifyTier = 1 | 2 | 3;
31
+ export type VerifyModel = 'haiku' | 'sonnet' | 'opus';
32
+ export type VerifyTaskKind = 'code' | 'json' | 'text' | 'auto';
33
+ export interface VerifyInput {
34
+ /** The task the output was generated for. */
35
+ task: string;
36
+ /** The generated output to verify. */
37
+ output: string;
38
+ /** Model that produced the output (drives the escalation ladder). */
39
+ model?: VerifyModel;
40
+ /** Tier that produced the output; derived from `model` when absent. */
41
+ tierUsed?: VerifyTier;
42
+ /** Force the task kind; 'auto' (default) detects from task + output. */
43
+ taskKind?: VerifyTaskKind;
44
+ /** Minimum trimmed output length considered plausible (default 20). */
45
+ minLength?: number;
46
+ }
47
+ export interface VerifySignal {
48
+ name: string;
49
+ ok: boolean;
50
+ detail?: string;
51
+ }
52
+ export interface VerifyVerdict {
53
+ confident: boolean;
54
+ /** Fraction of signals that passed (0..1). */
55
+ score: number;
56
+ /** Human-readable failure reasons; empty when confident. */
57
+ reasons: string[];
58
+ /** Every signal evaluated, pass or fail. */
59
+ signals: VerifySignal[];
60
+ /** Tier the caller should use next: unchanged when confident, bumped 2→3 on failure. */
61
+ suggestedTier: VerifyTier;
62
+ /** Concrete next model on the ladder (haiku→sonnet→opus); null when no bump exists. */
63
+ suggestedModel: VerifyModel | null;
64
+ /** True when not confident AND a higher tier exists to escalate to. */
65
+ escalate: boolean;
66
+ /** Detected task kind after 'auto' resolution. */
67
+ taskKind: Exclude<VerifyTaskKind, 'auto'>;
68
+ }
69
+ interface ExtractedCode {
70
+ lang: string;
71
+ code: string;
72
+ }
73
+ /** Pull fenced code blocks out of markdown-ish output. */
74
+ export declare function extractCodeBlocks(output: string): ExtractedCode[];
75
+ /** Cheap stack-based bracket balance check that skips string/comment-ish content. */
76
+ export declare function bracketsBalanced(code: string): boolean;
77
+ /**
78
+ * Compute a confidence verdict for `output` from cheap structural signals,
79
+ * and suggest an escalation target when not confident.
80
+ */
81
+ export declare function verifyAndEscalate(input: VerifyInput): Promise<VerifyVerdict>;
82
+ export default verifyAndEscalate;
83
+ //# sourceMappingURL=output-verifier.d.ts.map