hippo-memory 1.12.12 → 1.13.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.
- package/dist/api.d.ts +110 -0
- package/dist/api.d.ts.map +1 -1
- package/dist/api.js +68 -1
- package/dist/api.js.map +1 -1
- package/dist/audit.d.ts +1 -1
- package/dist/audit.d.ts.map +1 -1
- package/dist/audit.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +321 -2
- package/dist/cli.js.map +1 -1
- package/dist/db.d.ts.map +1 -1
- package/dist/db.js +77 -1
- package/dist/db.js.map +1 -1
- package/dist/forward-claim-detector.d.ts +38 -0
- package/dist/forward-claim-detector.d.ts.map +1 -0
- package/dist/forward-claim-detector.js +117 -0
- package/dist/forward-claim-detector.js.map +1 -0
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +128 -2
- package/dist/mcp/server.js.map +1 -1
- package/dist/predictions.d.ts +194 -0
- package/dist/predictions.d.ts.map +1 -0
- package/dist/predictions.js +580 -0
- package/dist/predictions.js.map +1 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +178 -0
- package/dist/server.js.map +1 -1
- package/dist/src/api.js +68 -1
- package/dist/src/api.js.map +1 -1
- package/dist/src/audit.js.map +1 -1
- package/dist/src/cli.js +321 -2
- package/dist/src/cli.js.map +1 -1
- package/dist/src/db.js +77 -1
- package/dist/src/db.js.map +1 -1
- package/dist/src/forward-claim-detector.js +117 -0
- package/dist/src/forward-claim-detector.js.map +1 -0
- package/dist/src/mcp/server.js +128 -2
- package/dist/src/mcp/server.js.map +1 -1
- package/dist/src/predictions.js +580 -0
- package/dist/src/predictions.js.map +1 -0
- package/dist/src/server.js +178 -0
- package/dist/src/server.js.map +1 -1
- package/dist/src/store.js +1 -1
- package/dist/src/store.js.map +1 -1
- package/dist/store.d.ts +17 -0
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +1 -1
- package/dist/store.js.map +1 -1
- package/extensions/openclaw-plugin/openclaw.plugin.json +1 -1
- package/extensions/openclaw-plugin/package.json +1 -1
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -56,6 +56,8 @@ import { listApiKeys, revokeApiKey } from './auth.js';
|
|
|
56
56
|
import { buildProvenanceCoverage } from './provenance-coverage.js';
|
|
57
57
|
import { buildCorrectionLatency } from './correction-latency.js';
|
|
58
58
|
import * as api from './api.js';
|
|
59
|
+
import * as predictionsModule from './predictions.js';
|
|
60
|
+
import { computePlanningFallacyHint } from './predictions.js';
|
|
59
61
|
import * as client from './client.js';
|
|
60
62
|
import { detectServer, removePidfileIfOwned } from './server-detect.js';
|
|
61
63
|
import { resolveTenantId } from './tenant.js';
|
|
@@ -682,6 +684,13 @@ async function cmdRecall(hippoRoot, query, flags) {
|
|
|
682
684
|
const tenantId = resolveTenantId({});
|
|
683
685
|
let localEntries = loadSearchEntries(hippoRoot, query, undefined, tenantId);
|
|
684
686
|
let globalEntries = isInitialized(globalRoot) ? loadSearchEntries(globalRoot, query, undefined, tenantId) : [];
|
|
687
|
+
// v1.12.13 / C5 — WYSIATI counters. Track filter activity per the plan v3
|
|
688
|
+
// Task 3 mapping table. dropped_pre_rank is the SUM of all non-budget
|
|
689
|
+
// filter drops (pre-rank AND post-rank). Search-engine internal drops
|
|
690
|
+
// (scored-to-zero rows that hybridSearch/physicsSearch returns fewer of)
|
|
691
|
+
// are NOT counted in v1 — they are part of the rank step, not a filter.
|
|
692
|
+
const totalCandidatesCountCmd = localEntries.length + globalEntries.length;
|
|
693
|
+
let droppedPreRankCountCmd = 0;
|
|
685
694
|
// Bi-temporal filtering for physics path (hybridSearch handles it internally)
|
|
686
695
|
if (asOf) {
|
|
687
696
|
const filterAsOf = (entries) => {
|
|
@@ -703,12 +712,16 @@ async function cmdRecall(hippoRoot, query, flags) {
|
|
|
703
712
|
return succVf ? new Date(succVf) > asOfDate : true;
|
|
704
713
|
});
|
|
705
714
|
};
|
|
715
|
+
const beforeAsOf = localEntries.length + globalEntries.length;
|
|
706
716
|
localEntries = filterAsOf(localEntries);
|
|
707
717
|
globalEntries = filterAsOf(globalEntries);
|
|
718
|
+
droppedPreRankCountCmd += beforeAsOf - (localEntries.length + globalEntries.length);
|
|
708
719
|
}
|
|
709
720
|
else if (!includeSuperseded) {
|
|
721
|
+
const beforeSupersededDrop = localEntries.length + globalEntries.length;
|
|
710
722
|
localEntries = localEntries.filter(e => !e.superseded_by);
|
|
711
723
|
globalEntries = globalEntries.filter(e => !e.superseded_by);
|
|
724
|
+
droppedPreRankCountCmd += beforeSupersededDrop - (localEntries.length + globalEntries.length);
|
|
712
725
|
}
|
|
713
726
|
const hasGlobal = globalEntries.length > 0;
|
|
714
727
|
// Determine search mode: --physics forces physics, --classic forces BM25+cosine,
|
|
@@ -817,7 +830,9 @@ async function cmdRecall(hippoRoot, query, flags) {
|
|
|
817
830
|
// We never infer conflicts from lexical overlap. The v1 salience gate did
|
|
818
831
|
// that and destroyed LoCoMo (0.28 → 0.02). Recorded structure only.
|
|
819
832
|
if (flags['filter-conflicts']) {
|
|
833
|
+
const beforeFilterConflicts = results.length;
|
|
820
834
|
results = results.filter((r) => !r.entry.superseded_by);
|
|
835
|
+
droppedPreRankCountCmd += beforeFilterConflicts - results.length;
|
|
821
836
|
const presentIds = new Set(results.map((r) => r.entry.id));
|
|
822
837
|
results = results.map((r) => {
|
|
823
838
|
const peers = r.entry.conflicts_with || [];
|
|
@@ -981,11 +996,13 @@ async function cmdRecall(hippoRoot, query, flags) {
|
|
|
981
996
|
console.error(`Invalid --outcome: "${outcomeFilter}". Must be one of: ${validOutcomes.join(', ')}.`);
|
|
982
997
|
process.exit(1);
|
|
983
998
|
}
|
|
999
|
+
const beforeOutcomeFilter = results.length;
|
|
984
1000
|
results = results.filter((r) => {
|
|
985
1001
|
if (r.entry.layer !== Layer.Trace)
|
|
986
1002
|
return true;
|
|
987
1003
|
return r.entry.trace_outcome === outcomeFilter;
|
|
988
1004
|
});
|
|
1005
|
+
droppedPreRankCountCmd += beforeOutcomeFilter - results.length;
|
|
989
1006
|
}
|
|
990
1007
|
// --layer filter: strict, drops entries whose layer does not match.
|
|
991
1008
|
const layerFilter = flags['layer'] !== undefined ? String(flags['layer']).trim() : '';
|
|
@@ -995,11 +1012,38 @@ async function cmdRecall(hippoRoot, query, flags) {
|
|
|
995
1012
|
console.error(`Invalid --layer: "${layerFilter}". Must be one of: ${validLayers.join(', ')}.`);
|
|
996
1013
|
process.exit(1);
|
|
997
1014
|
}
|
|
1015
|
+
const beforeLayerFilter = results.length;
|
|
998
1016
|
results = results.filter((r) => r.entry.layer === layerFilter);
|
|
1017
|
+
droppedPreRankCountCmd += beforeLayerFilter - results.length;
|
|
999
1018
|
}
|
|
1019
|
+
// v1.12.13 / C5 — WYSIATI dropped_by_budget counter (final limit cut).
|
|
1020
|
+
let droppedByBudgetCountCmd = 0;
|
|
1000
1021
|
if (limit < results.length) {
|
|
1022
|
+
droppedByBudgetCountCmd = results.length - limit;
|
|
1001
1023
|
results = results.slice(0, limit);
|
|
1002
1024
|
}
|
|
1025
|
+
// v1.12.13 / C5 — Build suppressionSummary for cmdRecall pipeline. Surfaced
|
|
1026
|
+
// in --why text output and in the --json JSON output. cmdRecall does not
|
|
1027
|
+
// run the summarizeOverflow path (api.recall does) and does not currently
|
|
1028
|
+
// expose fresh-tail in the CLI, so those two counters are 0 here.
|
|
1029
|
+
const cmdSuppressionSummary = api.buildSuppressionSummary({
|
|
1030
|
+
totalCandidates: totalCandidatesCountCmd,
|
|
1031
|
+
droppedPreRank: droppedPreRankCountCmd,
|
|
1032
|
+
droppedByBudget: droppedByBudgetCountCmd,
|
|
1033
|
+
summarySubstitutionsAdded: 0,
|
|
1034
|
+
freshTailAdded: 0,
|
|
1035
|
+
suppressedByInterference: 0,
|
|
1036
|
+
});
|
|
1037
|
+
// v0.32 / J3.2 — auto-injection of reference-class baserate when the
|
|
1038
|
+
// CLI query carries a forward-prediction phrase AND a class matches.
|
|
1039
|
+
// cmdRecall runs its own pipeline (doesn't go through api.recall for
|
|
1040
|
+
// the memory list), so it computes the hint here. The hint VALUE is
|
|
1041
|
+
// pipeline-invariant — same (hippoRoot, tenantId, query) inputs would
|
|
1042
|
+
// produce the same hint in api.recall — but the audit emission is
|
|
1043
|
+
// pipeline-local (one audit row per actual call, actor='cli' here).
|
|
1044
|
+
// computePlanningFallacyHint short-circuits BEFORE the regex gate
|
|
1045
|
+
// when HIPPO_AUTODEBIAS=off so the no-match path is effectively free.
|
|
1046
|
+
const cmdPlanningFallacyHint = computePlanningFallacyHint(hippoRoot, tenantId, query, { actor: 'cli' });
|
|
1003
1047
|
// A5 audit: emit one 'recall' event per query, capturing the (truncated)
|
|
1004
1048
|
// query text and the post-filter result count. Tenant resolved by emitCliAudit.
|
|
1005
1049
|
// Emit before the early-empty return so zero-result recalls are still logged.
|
|
@@ -1079,7 +1123,19 @@ async function cmdRecall(hippoRoot, query, flags) {
|
|
|
1079
1123
|
|| recentSessionEvents.length > 0;
|
|
1080
1124
|
if (results.length === 0) {
|
|
1081
1125
|
if (asJson) {
|
|
1082
|
-
const out = {
|
|
1126
|
+
const out = {
|
|
1127
|
+
query,
|
|
1128
|
+
results: [],
|
|
1129
|
+
total: 0,
|
|
1130
|
+
suppressionSummary: cmdSuppressionSummary,
|
|
1131
|
+
// v0.32 / J3.2 — preserve planningFallacyHint on zero-result
|
|
1132
|
+
// recalls. Codex review round 1 catch: hint was previously only
|
|
1133
|
+
// included in the populated-results JSON branch, breaking parity
|
|
1134
|
+
// with HTTP/MCP which surface the hint regardless of memory
|
|
1135
|
+
// matches. A forward-claim query that finds no memories STILL
|
|
1136
|
+
// produces useful planning-fallacy debias when the class resolves.
|
|
1137
|
+
...(cmdPlanningFallacyHint ? { planningFallacyHint: cmdPlanningFallacyHint } : {}),
|
|
1138
|
+
};
|
|
1083
1139
|
if (includeContinuity) {
|
|
1084
1140
|
out.continuity = {
|
|
1085
1141
|
activeSnapshot,
|
|
@@ -1091,6 +1147,15 @@ async function cmdRecall(hippoRoot, query, flags) {
|
|
|
1091
1147
|
console.log(JSON.stringify(out));
|
|
1092
1148
|
return;
|
|
1093
1149
|
}
|
|
1150
|
+
// v0.32 / J3.2 — render hint BEFORE the no-memories message so the
|
|
1151
|
+
// calling agent sees its track record even when the query missed
|
|
1152
|
+
// every memory. Same single-line shape + JSON.stringify-safe phrase
|
|
1153
|
+
// as the populated-results path below.
|
|
1154
|
+
if (cmdPlanningFallacyHint) {
|
|
1155
|
+
const safePhrase = JSON.stringify(cmdPlanningFallacyHint.detectedPhrase);
|
|
1156
|
+
console.log(`Planning fallacy hint (class: ${cmdPlanningFallacyHint.classTag}): ${cmdPlanningFallacyHint.baserateSummary} [detected: ${safePhrase}]`);
|
|
1157
|
+
console.log();
|
|
1158
|
+
}
|
|
1094
1159
|
if (hasContinuity) {
|
|
1095
1160
|
// Print continuity even when no memories matched. The resume packet
|
|
1096
1161
|
// is the whole point of `--continuity` and must not be dropped here.
|
|
@@ -1149,7 +1214,14 @@ async function cmdRecall(hippoRoot, query, flags) {
|
|
|
1149
1214
|
}
|
|
1150
1215
|
return base;
|
|
1151
1216
|
});
|
|
1152
|
-
const jsonOut = {
|
|
1217
|
+
const jsonOut = {
|
|
1218
|
+
query,
|
|
1219
|
+
budget,
|
|
1220
|
+
results: output,
|
|
1221
|
+
total: output.length,
|
|
1222
|
+
suppressionSummary: cmdSuppressionSummary,
|
|
1223
|
+
...(cmdPlanningFallacyHint ? { planningFallacyHint: cmdPlanningFallacyHint } : {}),
|
|
1224
|
+
};
|
|
1153
1225
|
if (includeContinuity) {
|
|
1154
1226
|
jsonOut.continuity = {
|
|
1155
1227
|
activeSnapshot,
|
|
@@ -1170,6 +1242,17 @@ async function cmdRecall(hippoRoot, query, flags) {
|
|
|
1170
1242
|
if (recentSessionEvents.length > 0)
|
|
1171
1243
|
printSessionEvents(recentSessionEvents);
|
|
1172
1244
|
}
|
|
1245
|
+
// v0.32 / J3.2 — render planning-fallacy hint ABOVE the result list so
|
|
1246
|
+
// the agent sees its track record before scrolling. Hint absent (env
|
|
1247
|
+
// disabled or no forward-claim match) is silent. detectedPhrase is
|
|
1248
|
+
// sanitised against control chars and ASCII quotes via JSON.stringify
|
|
1249
|
+
// to head off rendering ambiguity when a regex match contains quotes
|
|
1250
|
+
// or parens (plan-eng-critic round 2 LOW).
|
|
1251
|
+
if (cmdPlanningFallacyHint) {
|
|
1252
|
+
const safePhrase = JSON.stringify(cmdPlanningFallacyHint.detectedPhrase);
|
|
1253
|
+
console.log(`Planning fallacy hint (class: ${cmdPlanningFallacyHint.classTag}): ${cmdPlanningFallacyHint.baserateSummary} [detected: ${safePhrase}]`);
|
|
1254
|
+
console.log();
|
|
1255
|
+
}
|
|
1173
1256
|
console.log(`Found ${results.length} memories (${totalTokens} tokens) for: "${query}"\n`);
|
|
1174
1257
|
for (const r of results) {
|
|
1175
1258
|
const e = r.entry;
|
|
@@ -1204,6 +1287,30 @@ async function cmdRecall(hippoRoot, query, flags) {
|
|
|
1204
1287
|
console.log(e.content);
|
|
1205
1288
|
console.log();
|
|
1206
1289
|
}
|
|
1290
|
+
// v1.12.13 / C5 — WYSIATI cutoff transparency in --why text output.
|
|
1291
|
+
// Single-line summary after the result list, emitted only when --why is
|
|
1292
|
+
// set AND at least one counter is non-zero. Skip zero-count clauses to
|
|
1293
|
+
// keep the line tight. The calling agent uses this to spot when the
|
|
1294
|
+
// shown set is a small slice of a much larger candidate pool (Kahneman
|
|
1295
|
+
// "What You See Is All There Is" failure mode).
|
|
1296
|
+
if (showWhy) {
|
|
1297
|
+
const s = cmdSuppressionSummary;
|
|
1298
|
+
const clauses = [];
|
|
1299
|
+
if (s.droppedByBudget > 0)
|
|
1300
|
+
clauses.push(`${s.droppedByBudget} dropped by limit`);
|
|
1301
|
+
if (s.droppedPreRank > 0)
|
|
1302
|
+
clauses.push(`${s.droppedPreRank} pre-rank filtered`);
|
|
1303
|
+
if (s.summarySubstitutionsAdded > 0)
|
|
1304
|
+
clauses.push(`${s.summarySubstitutionsAdded} summary substitutions added`);
|
|
1305
|
+
if (s.freshTailAdded > 0)
|
|
1306
|
+
clauses.push(`${s.freshTailAdded} fresh-tail added`);
|
|
1307
|
+
if (s.suppressedByInterference > 0)
|
|
1308
|
+
clauses.push(`${s.suppressedByInterference} suppressed by interference`);
|
|
1309
|
+
if (clauses.length > 0) {
|
|
1310
|
+
console.log(`WYSIATI: showing ${results.length}/${s.totalCandidates}; ${clauses.join('; ')}.`);
|
|
1311
|
+
console.log();
|
|
1312
|
+
}
|
|
1313
|
+
}
|
|
1207
1314
|
}
|
|
1208
1315
|
async function cmdExplain(hippoRoot, query, flags) {
|
|
1209
1316
|
requireInit(hippoRoot);
|
|
@@ -2840,6 +2947,209 @@ function cmdHandoff(hippoRoot, args, flags) {
|
|
|
2840
2947
|
console.error('Usage: hippo handoff <create|latest|show>');
|
|
2841
2948
|
process.exit(1);
|
|
2842
2949
|
}
|
|
2950
|
+
// ---------------------------------------------------------------------------
|
|
2951
|
+
// E2 prediction first-class object (v0.31)
|
|
2952
|
+
// docs/plans/2026-05-26-e2-prediction-object.md
|
|
2953
|
+
// ---------------------------------------------------------------------------
|
|
2954
|
+
function cmdPredict(hippoRoot, args, flags) {
|
|
2955
|
+
requireInit(hippoRoot);
|
|
2956
|
+
const tenantId = resolveTenantId({});
|
|
2957
|
+
const subcommand = args[0] ?? '';
|
|
2958
|
+
if (subcommand === 'close') {
|
|
2959
|
+
const idRaw = args[1];
|
|
2960
|
+
if (!idRaw) {
|
|
2961
|
+
console.error('Usage: hippo predict close <id> --state <closed|closed-unknown> [--actual <v>] [--note "..."]');
|
|
2962
|
+
process.exit(1);
|
|
2963
|
+
}
|
|
2964
|
+
const id = parseInt(String(idRaw), 10);
|
|
2965
|
+
if (!Number.isFinite(id) || id <= 0) {
|
|
2966
|
+
console.error(`Invalid prediction id: "${idRaw}"`);
|
|
2967
|
+
process.exit(1);
|
|
2968
|
+
}
|
|
2969
|
+
const stateRaw = typeof flags['state'] === 'string' ? flags['state'].trim() : '';
|
|
2970
|
+
if (!predictionsModule.VALID_CLOSURE_STATES.has(stateRaw) || stateRaw === 'open') {
|
|
2971
|
+
console.error(`Invalid --state: "${stateRaw}". Must be one of: closed | closed-unknown.`);
|
|
2972
|
+
process.exit(1);
|
|
2973
|
+
}
|
|
2974
|
+
const actualRaw = flags['actual'];
|
|
2975
|
+
const actualValue = actualRaw !== undefined ? Number(actualRaw) : undefined;
|
|
2976
|
+
if (actualRaw !== undefined && !Number.isFinite(actualValue)) {
|
|
2977
|
+
console.error(`Invalid --actual: "${actualRaw}". Must be a number.`);
|
|
2978
|
+
process.exit(1);
|
|
2979
|
+
}
|
|
2980
|
+
const noteRaw = flags['note'];
|
|
2981
|
+
const closureNote = typeof noteRaw === 'string' ? noteRaw : undefined;
|
|
2982
|
+
const closed = predictionsModule.closePrediction(hippoRoot, tenantId, id, {
|
|
2983
|
+
closureState: stateRaw,
|
|
2984
|
+
actualValue,
|
|
2985
|
+
closureNote,
|
|
2986
|
+
});
|
|
2987
|
+
console.log(`Prediction ${closed.id} closed: state=${closed.closureState}${closed.actualValue !== null ? ` actual=${closed.actualValue}` : ''}`);
|
|
2988
|
+
return;
|
|
2989
|
+
}
|
|
2990
|
+
if (subcommand === 'list') {
|
|
2991
|
+
const classTagRaw = flags['class'];
|
|
2992
|
+
const classTag = typeof classTagRaw === 'string' ? classTagRaw.trim() : '';
|
|
2993
|
+
const statusRaw = flags['status'];
|
|
2994
|
+
const status = typeof statusRaw === 'string' ? statusRaw.trim() : 'all';
|
|
2995
|
+
const limitRaw = flags['limit'];
|
|
2996
|
+
const limit = limitRaw !== undefined ? parseInt(String(limitRaw), 10) : 100;
|
|
2997
|
+
if (!Number.isFinite(limit) || limit <= 0) {
|
|
2998
|
+
console.error(`Invalid --limit: "${limitRaw}". Must be a positive integer.`);
|
|
2999
|
+
process.exit(1);
|
|
3000
|
+
}
|
|
3001
|
+
let results;
|
|
3002
|
+
if (status === 'open') {
|
|
3003
|
+
results = predictionsModule.loadOpenPredictions(hippoRoot, tenantId, {
|
|
3004
|
+
classTag: classTag || undefined,
|
|
3005
|
+
limit,
|
|
3006
|
+
});
|
|
3007
|
+
}
|
|
3008
|
+
else if (status === 'all') {
|
|
3009
|
+
// No closure-state filter; pull both via loadPredictionsByClass if class given
|
|
3010
|
+
if (classTag) {
|
|
3011
|
+
results = predictionsModule.loadPredictionsByClass(hippoRoot, tenantId, classTag, { limit });
|
|
3012
|
+
}
|
|
3013
|
+
else {
|
|
3014
|
+
// No class filter + status=all = pull open + closed across all classes
|
|
3015
|
+
// (kept simple: report open via loadOpenPredictions; closed via two
|
|
3016
|
+
// class scans isn't symmetrical. v1 callers typically pass --class.)
|
|
3017
|
+
results = predictionsModule.loadOpenPredictions(hippoRoot, tenantId, { limit });
|
|
3018
|
+
}
|
|
3019
|
+
}
|
|
3020
|
+
else {
|
|
3021
|
+
if (!predictionsModule.VALID_CLOSURE_STATES.has(status)) {
|
|
3022
|
+
console.error(`Invalid --status: "${status}". Must be one of: open | closed | closed-unknown | all.`);
|
|
3023
|
+
process.exit(1);
|
|
3024
|
+
}
|
|
3025
|
+
if (classTag) {
|
|
3026
|
+
results = predictionsModule.loadPredictionsByClass(hippoRoot, tenantId, classTag, {
|
|
3027
|
+
closureState: status,
|
|
3028
|
+
limit,
|
|
3029
|
+
});
|
|
3030
|
+
}
|
|
3031
|
+
else {
|
|
3032
|
+
// status filter without class — scan all classes is more complex; v1 requires --class for non-default status
|
|
3033
|
+
console.error('--status filter (non-open) requires --class to be set.');
|
|
3034
|
+
process.exit(1);
|
|
3035
|
+
}
|
|
3036
|
+
}
|
|
3037
|
+
if (results.length === 0) {
|
|
3038
|
+
console.log(classTag ? `No predictions in class "${classTag}".` : 'No predictions.');
|
|
3039
|
+
return;
|
|
3040
|
+
}
|
|
3041
|
+
console.log(`Found ${results.length} predictions:\n`);
|
|
3042
|
+
for (const p of results) {
|
|
3043
|
+
const estPart = p.estimateValue !== null ? ` estimate=${p.estimateValue}${p.estimateUnit ? ` ${p.estimateUnit}` : ''}` : '';
|
|
3044
|
+
const actPart = p.actualValue !== null ? ` actual=${p.actualValue}` : '';
|
|
3045
|
+
const tgtPart = p.targetDate ? ` target=${p.targetDate}` : '';
|
|
3046
|
+
console.log(`#${p.id} [${p.closureState}] class=${p.classTag}${estPart}${actPart}${tgtPart}`);
|
|
3047
|
+
console.log(` ${p.claimText}`);
|
|
3048
|
+
if (p.closureNote)
|
|
3049
|
+
console.log(` note: ${p.closureNote}`);
|
|
3050
|
+
}
|
|
3051
|
+
return;
|
|
3052
|
+
}
|
|
3053
|
+
if (subcommand === 'show') {
|
|
3054
|
+
const idRaw = args[1];
|
|
3055
|
+
if (!idRaw) {
|
|
3056
|
+
console.error('Usage: hippo predict show <id>');
|
|
3057
|
+
process.exit(1);
|
|
3058
|
+
}
|
|
3059
|
+
const id = parseInt(String(idRaw), 10);
|
|
3060
|
+
if (!Number.isFinite(id) || id <= 0) {
|
|
3061
|
+
console.error(`Invalid prediction id: "${idRaw}"`);
|
|
3062
|
+
process.exit(1);
|
|
3063
|
+
}
|
|
3064
|
+
const pred = predictionsModule.loadPredictionById(hippoRoot, tenantId, id);
|
|
3065
|
+
if (!pred) {
|
|
3066
|
+
console.error(`Prediction ${id} not found.`);
|
|
3067
|
+
process.exit(1);
|
|
3068
|
+
}
|
|
3069
|
+
console.log(`Prediction #${pred.id}`);
|
|
3070
|
+
console.log(` class: ${pred.classTag}`);
|
|
3071
|
+
console.log(` claim: ${pred.claimText}`);
|
|
3072
|
+
console.log(` state: ${pred.closureState}`);
|
|
3073
|
+
if (pred.estimateValue !== null)
|
|
3074
|
+
console.log(` estimate: ${pred.estimateValue}${pred.estimateUnit ? ' ' + pred.estimateUnit : ''}`);
|
|
3075
|
+
if (pred.targetDate)
|
|
3076
|
+
console.log(` target: ${pred.targetDate}`);
|
|
3077
|
+
if (pred.actualValue !== null)
|
|
3078
|
+
console.log(` actual: ${pred.actualValue}`);
|
|
3079
|
+
if (pred.closedAt)
|
|
3080
|
+
console.log(` closed: ${pred.closedAt}`);
|
|
3081
|
+
if (pred.closureNote)
|
|
3082
|
+
console.log(` note: ${pred.closureNote}`);
|
|
3083
|
+
if (pred.memoryId)
|
|
3084
|
+
console.log(` memory: ${pred.memoryId}`);
|
|
3085
|
+
console.log(` created: ${pred.createdAt}`);
|
|
3086
|
+
return;
|
|
3087
|
+
}
|
|
3088
|
+
if (subcommand === 'baserate') {
|
|
3089
|
+
// J3 reference-class / planning-fallacy detector
|
|
3090
|
+
const classTagRaw = flags['class'];
|
|
3091
|
+
if (typeof classTagRaw !== 'string' || !classTagRaw.trim()) {
|
|
3092
|
+
console.error('Usage: hippo predict baserate --class <c>');
|
|
3093
|
+
process.exit(1);
|
|
3094
|
+
}
|
|
3095
|
+
const baserate = predictionsModule.computePredictionBaserate(hippoRoot, tenantId, classTagRaw.trim());
|
|
3096
|
+
if (baserate.nClosed === 0) {
|
|
3097
|
+
console.log(`No closed predictions in class "${baserate.classTag}" yet.`);
|
|
3098
|
+
console.log(` Create one with: hippo predict "<claim>" --class ${baserate.classTag} --estimate N`);
|
|
3099
|
+
console.log(` Close it later: hippo predict close <id> --state closed --actual N`);
|
|
3100
|
+
return;
|
|
3101
|
+
}
|
|
3102
|
+
console.log(baserate.summary);
|
|
3103
|
+
console.log(` n_closed: ${baserate.nClosed}`);
|
|
3104
|
+
console.log(` n_ratio_eligible: ${baserate.nRatioEligible}`);
|
|
3105
|
+
if (baserate.meanEstimate !== null)
|
|
3106
|
+
console.log(` mean_estimate: ${baserate.meanEstimate.toFixed(3)}`);
|
|
3107
|
+
if (baserate.meanActual !== null)
|
|
3108
|
+
console.log(` mean_actual: ${baserate.meanActual.toFixed(3)}`);
|
|
3109
|
+
if (baserate.meanRatio !== null)
|
|
3110
|
+
console.log(` mean_ratio: ${baserate.meanRatio.toFixed(3)}x`);
|
|
3111
|
+
if (baserate.p50Ratio !== null)
|
|
3112
|
+
console.log(` p50_ratio: ${baserate.p50Ratio.toFixed(3)}x`);
|
|
3113
|
+
if (baserate.mae !== null)
|
|
3114
|
+
console.log(` mae: ${baserate.mae.toFixed(3)}`);
|
|
3115
|
+
return;
|
|
3116
|
+
}
|
|
3117
|
+
// Default subcommand: create. args[0] is the claim text.
|
|
3118
|
+
const claimText = subcommand;
|
|
3119
|
+
if (!claimText) {
|
|
3120
|
+
console.error('Usage: hippo predict "<claim>" --class <c> [--estimate <v>] [--unit <u>] [--target <YYYY-MM-DD>]');
|
|
3121
|
+
console.error(' hippo predict close <id> --state <closed|closed-unknown> [--actual <v>] [--note "..."]');
|
|
3122
|
+
console.error(' hippo predict list [--class X] [--status open|closed|closed-unknown|all] [--limit N]');
|
|
3123
|
+
console.error(' hippo predict show <id>');
|
|
3124
|
+
process.exit(1);
|
|
3125
|
+
}
|
|
3126
|
+
const classTagRaw = flags['class'];
|
|
3127
|
+
if (typeof classTagRaw !== 'string' || !classTagRaw.trim()) {
|
|
3128
|
+
console.error('--class is required for prediction creation.');
|
|
3129
|
+
process.exit(1);
|
|
3130
|
+
}
|
|
3131
|
+
const classTag = classTagRaw.trim();
|
|
3132
|
+
const estimateRaw = flags['estimate'];
|
|
3133
|
+
const estimateValue = estimateRaw !== undefined ? Number(estimateRaw) : undefined;
|
|
3134
|
+
if (estimateRaw !== undefined && !Number.isFinite(estimateValue)) {
|
|
3135
|
+
console.error(`Invalid --estimate: "${estimateRaw}". Must be a number.`);
|
|
3136
|
+
process.exit(1);
|
|
3137
|
+
}
|
|
3138
|
+
const unitRaw = flags['unit'];
|
|
3139
|
+
const estimateUnit = typeof unitRaw === 'string' ? unitRaw : undefined;
|
|
3140
|
+
const targetRaw = flags['target'];
|
|
3141
|
+
const targetDate = typeof targetRaw === 'string' ? targetRaw : undefined;
|
|
3142
|
+
const created = predictionsModule.savePrediction(hippoRoot, tenantId, {
|
|
3143
|
+
classTag,
|
|
3144
|
+
claimText,
|
|
3145
|
+
estimateValue,
|
|
3146
|
+
estimateUnit,
|
|
3147
|
+
targetDate,
|
|
3148
|
+
});
|
|
3149
|
+
console.log(`Prediction recorded: #${created.id} class=${created.classTag}`);
|
|
3150
|
+
if (created.memoryId)
|
|
3151
|
+
console.log(` memory: ${created.memoryId}`);
|
|
3152
|
+
}
|
|
2843
3153
|
function cmdCurrent(hippoRoot, args, flags) {
|
|
2844
3154
|
requireInit(hippoRoot);
|
|
2845
3155
|
const subcommand = args[0] ?? 'show';
|
|
@@ -4244,6 +4554,12 @@ const VALID_AUDIT_OPS = new Set([
|
|
|
4244
4554
|
'summary_marked_dirty', // v0.30 / E1 — lockstep with AuditOp union + server.ts VALID_AUDIT_OPS (v1.11.5 CRIT A institutional rule)
|
|
4245
4555
|
'summary_marked_clean', // v0.30 / E3 — buildDag post-link clean op; lockstep
|
|
4246
4556
|
'summary_rebuilt', // v0.30 / E3 — sleep-cycle rebuild op; lockstep
|
|
4557
|
+
'predict_create', // v0.31 / E2 prediction first-class object — emitted by savePrediction
|
|
4558
|
+
'predict_close', // v0.31 / E2 — emitted by closePrediction
|
|
4559
|
+
'predict_baserate', // v0.31 / J3 — emitted by computePredictionBaserate
|
|
4560
|
+
'recall_autodebias_hint', // v0.32 / J3.2 — emitted by computePlanningFallacyHint on success
|
|
4561
|
+
'recall_autodebias_hint_no_class_match', // v0.32 / J3.2 — telemetry: forward-claim, no class scored
|
|
4562
|
+
'recall_autodebias_hint_tiebreak', // v0.32 / J3.2 — telemetry: forward-claim, >=2 classes tied
|
|
4247
4563
|
]);
|
|
4248
4564
|
function formatAuditRow(ev) {
|
|
4249
4565
|
const target = ev.targetId ?? '-';
|
|
@@ -5426,6 +5742,9 @@ async function main() {
|
|
|
5426
5742
|
case 'handoff':
|
|
5427
5743
|
cmdHandoff(hippoRoot, args, flags);
|
|
5428
5744
|
break;
|
|
5745
|
+
case 'predict':
|
|
5746
|
+
cmdPredict(hippoRoot, args, flags);
|
|
5747
|
+
break;
|
|
5429
5748
|
case 'current':
|
|
5430
5749
|
cmdCurrent(hippoRoot, args, flags);
|
|
5431
5750
|
break;
|