hippo-memory 1.12.12 → 1.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api.d.ts +89 -0
- package/dist/api.d.ts.map +1 -1
- package/dist/api.js +57 -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 +279 -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/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +105 -1
- package/dist/mcp/server.js.map +1 -1
- package/dist/predictions.d.ts +122 -0
- package/dist/predictions.d.ts.map +1 -0
- package/dist/predictions.js +386 -0
- package/dist/predictions.js.map +1 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +175 -0
- package/dist/server.js.map +1 -1
- package/dist/src/api.js +57 -1
- package/dist/src/api.js.map +1 -1
- package/dist/src/audit.js.map +1 -1
- package/dist/src/cli.js +279 -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/mcp/server.js +105 -1
- package/dist/src/mcp/server.js.map +1 -1
- package/dist/src/predictions.js +386 -0
- package/dist/src/predictions.js.map +1 -0
- package/dist/src/server.js +175 -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/openclaw.plugin.json +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -56,6 +56,7 @@ 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';
|
|
59
60
|
import * as client from './client.js';
|
|
60
61
|
import { detectServer, removePidfileIfOwned } from './server-detect.js';
|
|
61
62
|
import { resolveTenantId } from './tenant.js';
|
|
@@ -682,6 +683,13 @@ async function cmdRecall(hippoRoot, query, flags) {
|
|
|
682
683
|
const tenantId = resolveTenantId({});
|
|
683
684
|
let localEntries = loadSearchEntries(hippoRoot, query, undefined, tenantId);
|
|
684
685
|
let globalEntries = isInitialized(globalRoot) ? loadSearchEntries(globalRoot, query, undefined, tenantId) : [];
|
|
686
|
+
// v1.12.13 / C5 — WYSIATI counters. Track filter activity per the plan v3
|
|
687
|
+
// Task 3 mapping table. dropped_pre_rank is the SUM of all non-budget
|
|
688
|
+
// filter drops (pre-rank AND post-rank). Search-engine internal drops
|
|
689
|
+
// (scored-to-zero rows that hybridSearch/physicsSearch returns fewer of)
|
|
690
|
+
// are NOT counted in v1 — they are part of the rank step, not a filter.
|
|
691
|
+
const totalCandidatesCountCmd = localEntries.length + globalEntries.length;
|
|
692
|
+
let droppedPreRankCountCmd = 0;
|
|
685
693
|
// Bi-temporal filtering for physics path (hybridSearch handles it internally)
|
|
686
694
|
if (asOf) {
|
|
687
695
|
const filterAsOf = (entries) => {
|
|
@@ -703,12 +711,16 @@ async function cmdRecall(hippoRoot, query, flags) {
|
|
|
703
711
|
return succVf ? new Date(succVf) > asOfDate : true;
|
|
704
712
|
});
|
|
705
713
|
};
|
|
714
|
+
const beforeAsOf = localEntries.length + globalEntries.length;
|
|
706
715
|
localEntries = filterAsOf(localEntries);
|
|
707
716
|
globalEntries = filterAsOf(globalEntries);
|
|
717
|
+
droppedPreRankCountCmd += beforeAsOf - (localEntries.length + globalEntries.length);
|
|
708
718
|
}
|
|
709
719
|
else if (!includeSuperseded) {
|
|
720
|
+
const beforeSupersededDrop = localEntries.length + globalEntries.length;
|
|
710
721
|
localEntries = localEntries.filter(e => !e.superseded_by);
|
|
711
722
|
globalEntries = globalEntries.filter(e => !e.superseded_by);
|
|
723
|
+
droppedPreRankCountCmd += beforeSupersededDrop - (localEntries.length + globalEntries.length);
|
|
712
724
|
}
|
|
713
725
|
const hasGlobal = globalEntries.length > 0;
|
|
714
726
|
// Determine search mode: --physics forces physics, --classic forces BM25+cosine,
|
|
@@ -817,7 +829,9 @@ async function cmdRecall(hippoRoot, query, flags) {
|
|
|
817
829
|
// We never infer conflicts from lexical overlap. The v1 salience gate did
|
|
818
830
|
// that and destroyed LoCoMo (0.28 → 0.02). Recorded structure only.
|
|
819
831
|
if (flags['filter-conflicts']) {
|
|
832
|
+
const beforeFilterConflicts = results.length;
|
|
820
833
|
results = results.filter((r) => !r.entry.superseded_by);
|
|
834
|
+
droppedPreRankCountCmd += beforeFilterConflicts - results.length;
|
|
821
835
|
const presentIds = new Set(results.map((r) => r.entry.id));
|
|
822
836
|
results = results.map((r) => {
|
|
823
837
|
const peers = r.entry.conflicts_with || [];
|
|
@@ -981,11 +995,13 @@ async function cmdRecall(hippoRoot, query, flags) {
|
|
|
981
995
|
console.error(`Invalid --outcome: "${outcomeFilter}". Must be one of: ${validOutcomes.join(', ')}.`);
|
|
982
996
|
process.exit(1);
|
|
983
997
|
}
|
|
998
|
+
const beforeOutcomeFilter = results.length;
|
|
984
999
|
results = results.filter((r) => {
|
|
985
1000
|
if (r.entry.layer !== Layer.Trace)
|
|
986
1001
|
return true;
|
|
987
1002
|
return r.entry.trace_outcome === outcomeFilter;
|
|
988
1003
|
});
|
|
1004
|
+
droppedPreRankCountCmd += beforeOutcomeFilter - results.length;
|
|
989
1005
|
}
|
|
990
1006
|
// --layer filter: strict, drops entries whose layer does not match.
|
|
991
1007
|
const layerFilter = flags['layer'] !== undefined ? String(flags['layer']).trim() : '';
|
|
@@ -995,11 +1011,28 @@ async function cmdRecall(hippoRoot, query, flags) {
|
|
|
995
1011
|
console.error(`Invalid --layer: "${layerFilter}". Must be one of: ${validLayers.join(', ')}.`);
|
|
996
1012
|
process.exit(1);
|
|
997
1013
|
}
|
|
1014
|
+
const beforeLayerFilter = results.length;
|
|
998
1015
|
results = results.filter((r) => r.entry.layer === layerFilter);
|
|
1016
|
+
droppedPreRankCountCmd += beforeLayerFilter - results.length;
|
|
999
1017
|
}
|
|
1018
|
+
// v1.12.13 / C5 — WYSIATI dropped_by_budget counter (final limit cut).
|
|
1019
|
+
let droppedByBudgetCountCmd = 0;
|
|
1000
1020
|
if (limit < results.length) {
|
|
1021
|
+
droppedByBudgetCountCmd = results.length - limit;
|
|
1001
1022
|
results = results.slice(0, limit);
|
|
1002
1023
|
}
|
|
1024
|
+
// v1.12.13 / C5 — Build suppressionSummary for cmdRecall pipeline. Surfaced
|
|
1025
|
+
// in --why text output and in the --json JSON output. cmdRecall does not
|
|
1026
|
+
// run the summarizeOverflow path (api.recall does) and does not currently
|
|
1027
|
+
// expose fresh-tail in the CLI, so those two counters are 0 here.
|
|
1028
|
+
const cmdSuppressionSummary = api.buildSuppressionSummary({
|
|
1029
|
+
totalCandidates: totalCandidatesCountCmd,
|
|
1030
|
+
droppedPreRank: droppedPreRankCountCmd,
|
|
1031
|
+
droppedByBudget: droppedByBudgetCountCmd,
|
|
1032
|
+
summarySubstitutionsAdded: 0,
|
|
1033
|
+
freshTailAdded: 0,
|
|
1034
|
+
suppressedByInterference: 0,
|
|
1035
|
+
});
|
|
1003
1036
|
// A5 audit: emit one 'recall' event per query, capturing the (truncated)
|
|
1004
1037
|
// query text and the post-filter result count. Tenant resolved by emitCliAudit.
|
|
1005
1038
|
// Emit before the early-empty return so zero-result recalls are still logged.
|
|
@@ -1079,7 +1112,12 @@ async function cmdRecall(hippoRoot, query, flags) {
|
|
|
1079
1112
|
|| recentSessionEvents.length > 0;
|
|
1080
1113
|
if (results.length === 0) {
|
|
1081
1114
|
if (asJson) {
|
|
1082
|
-
const out = {
|
|
1115
|
+
const out = {
|
|
1116
|
+
query,
|
|
1117
|
+
results: [],
|
|
1118
|
+
total: 0,
|
|
1119
|
+
suppressionSummary: cmdSuppressionSummary,
|
|
1120
|
+
};
|
|
1083
1121
|
if (includeContinuity) {
|
|
1084
1122
|
out.continuity = {
|
|
1085
1123
|
activeSnapshot,
|
|
@@ -1149,7 +1187,13 @@ async function cmdRecall(hippoRoot, query, flags) {
|
|
|
1149
1187
|
}
|
|
1150
1188
|
return base;
|
|
1151
1189
|
});
|
|
1152
|
-
const jsonOut = {
|
|
1190
|
+
const jsonOut = {
|
|
1191
|
+
query,
|
|
1192
|
+
budget,
|
|
1193
|
+
results: output,
|
|
1194
|
+
total: output.length,
|
|
1195
|
+
suppressionSummary: cmdSuppressionSummary,
|
|
1196
|
+
};
|
|
1153
1197
|
if (includeContinuity) {
|
|
1154
1198
|
jsonOut.continuity = {
|
|
1155
1199
|
activeSnapshot,
|
|
@@ -1204,6 +1248,30 @@ async function cmdRecall(hippoRoot, query, flags) {
|
|
|
1204
1248
|
console.log(e.content);
|
|
1205
1249
|
console.log();
|
|
1206
1250
|
}
|
|
1251
|
+
// v1.12.13 / C5 — WYSIATI cutoff transparency in --why text output.
|
|
1252
|
+
// Single-line summary after the result list, emitted only when --why is
|
|
1253
|
+
// set AND at least one counter is non-zero. Skip zero-count clauses to
|
|
1254
|
+
// keep the line tight. The calling agent uses this to spot when the
|
|
1255
|
+
// shown set is a small slice of a much larger candidate pool (Kahneman
|
|
1256
|
+
// "What You See Is All There Is" failure mode).
|
|
1257
|
+
if (showWhy) {
|
|
1258
|
+
const s = cmdSuppressionSummary;
|
|
1259
|
+
const clauses = [];
|
|
1260
|
+
if (s.droppedByBudget > 0)
|
|
1261
|
+
clauses.push(`${s.droppedByBudget} dropped by limit`);
|
|
1262
|
+
if (s.droppedPreRank > 0)
|
|
1263
|
+
clauses.push(`${s.droppedPreRank} pre-rank filtered`);
|
|
1264
|
+
if (s.summarySubstitutionsAdded > 0)
|
|
1265
|
+
clauses.push(`${s.summarySubstitutionsAdded} summary substitutions added`);
|
|
1266
|
+
if (s.freshTailAdded > 0)
|
|
1267
|
+
clauses.push(`${s.freshTailAdded} fresh-tail added`);
|
|
1268
|
+
if (s.suppressedByInterference > 0)
|
|
1269
|
+
clauses.push(`${s.suppressedByInterference} suppressed by interference`);
|
|
1270
|
+
if (clauses.length > 0) {
|
|
1271
|
+
console.log(`WYSIATI: showing ${results.length}/${s.totalCandidates}; ${clauses.join('; ')}.`);
|
|
1272
|
+
console.log();
|
|
1273
|
+
}
|
|
1274
|
+
}
|
|
1207
1275
|
}
|
|
1208
1276
|
async function cmdExplain(hippoRoot, query, flags) {
|
|
1209
1277
|
requireInit(hippoRoot);
|
|
@@ -2840,6 +2908,209 @@ function cmdHandoff(hippoRoot, args, flags) {
|
|
|
2840
2908
|
console.error('Usage: hippo handoff <create|latest|show>');
|
|
2841
2909
|
process.exit(1);
|
|
2842
2910
|
}
|
|
2911
|
+
// ---------------------------------------------------------------------------
|
|
2912
|
+
// E2 prediction first-class object (v0.31)
|
|
2913
|
+
// docs/plans/2026-05-26-e2-prediction-object.md
|
|
2914
|
+
// ---------------------------------------------------------------------------
|
|
2915
|
+
function cmdPredict(hippoRoot, args, flags) {
|
|
2916
|
+
requireInit(hippoRoot);
|
|
2917
|
+
const tenantId = resolveTenantId({});
|
|
2918
|
+
const subcommand = args[0] ?? '';
|
|
2919
|
+
if (subcommand === 'close') {
|
|
2920
|
+
const idRaw = args[1];
|
|
2921
|
+
if (!idRaw) {
|
|
2922
|
+
console.error('Usage: hippo predict close <id> --state <closed|closed-unknown> [--actual <v>] [--note "..."]');
|
|
2923
|
+
process.exit(1);
|
|
2924
|
+
}
|
|
2925
|
+
const id = parseInt(String(idRaw), 10);
|
|
2926
|
+
if (!Number.isFinite(id) || id <= 0) {
|
|
2927
|
+
console.error(`Invalid prediction id: "${idRaw}"`);
|
|
2928
|
+
process.exit(1);
|
|
2929
|
+
}
|
|
2930
|
+
const stateRaw = typeof flags['state'] === 'string' ? flags['state'].trim() : '';
|
|
2931
|
+
if (!predictionsModule.VALID_CLOSURE_STATES.has(stateRaw) || stateRaw === 'open') {
|
|
2932
|
+
console.error(`Invalid --state: "${stateRaw}". Must be one of: closed | closed-unknown.`);
|
|
2933
|
+
process.exit(1);
|
|
2934
|
+
}
|
|
2935
|
+
const actualRaw = flags['actual'];
|
|
2936
|
+
const actualValue = actualRaw !== undefined ? Number(actualRaw) : undefined;
|
|
2937
|
+
if (actualRaw !== undefined && !Number.isFinite(actualValue)) {
|
|
2938
|
+
console.error(`Invalid --actual: "${actualRaw}". Must be a number.`);
|
|
2939
|
+
process.exit(1);
|
|
2940
|
+
}
|
|
2941
|
+
const noteRaw = flags['note'];
|
|
2942
|
+
const closureNote = typeof noteRaw === 'string' ? noteRaw : undefined;
|
|
2943
|
+
const closed = predictionsModule.closePrediction(hippoRoot, tenantId, id, {
|
|
2944
|
+
closureState: stateRaw,
|
|
2945
|
+
actualValue,
|
|
2946
|
+
closureNote,
|
|
2947
|
+
});
|
|
2948
|
+
console.log(`Prediction ${closed.id} closed: state=${closed.closureState}${closed.actualValue !== null ? ` actual=${closed.actualValue}` : ''}`);
|
|
2949
|
+
return;
|
|
2950
|
+
}
|
|
2951
|
+
if (subcommand === 'list') {
|
|
2952
|
+
const classTagRaw = flags['class'];
|
|
2953
|
+
const classTag = typeof classTagRaw === 'string' ? classTagRaw.trim() : '';
|
|
2954
|
+
const statusRaw = flags['status'];
|
|
2955
|
+
const status = typeof statusRaw === 'string' ? statusRaw.trim() : 'all';
|
|
2956
|
+
const limitRaw = flags['limit'];
|
|
2957
|
+
const limit = limitRaw !== undefined ? parseInt(String(limitRaw), 10) : 100;
|
|
2958
|
+
if (!Number.isFinite(limit) || limit <= 0) {
|
|
2959
|
+
console.error(`Invalid --limit: "${limitRaw}". Must be a positive integer.`);
|
|
2960
|
+
process.exit(1);
|
|
2961
|
+
}
|
|
2962
|
+
let results;
|
|
2963
|
+
if (status === 'open') {
|
|
2964
|
+
results = predictionsModule.loadOpenPredictions(hippoRoot, tenantId, {
|
|
2965
|
+
classTag: classTag || undefined,
|
|
2966
|
+
limit,
|
|
2967
|
+
});
|
|
2968
|
+
}
|
|
2969
|
+
else if (status === 'all') {
|
|
2970
|
+
// No closure-state filter; pull both via loadPredictionsByClass if class given
|
|
2971
|
+
if (classTag) {
|
|
2972
|
+
results = predictionsModule.loadPredictionsByClass(hippoRoot, tenantId, classTag, { limit });
|
|
2973
|
+
}
|
|
2974
|
+
else {
|
|
2975
|
+
// No class filter + status=all = pull open + closed across all classes
|
|
2976
|
+
// (kept simple: report open via loadOpenPredictions; closed via two
|
|
2977
|
+
// class scans isn't symmetrical. v1 callers typically pass --class.)
|
|
2978
|
+
results = predictionsModule.loadOpenPredictions(hippoRoot, tenantId, { limit });
|
|
2979
|
+
}
|
|
2980
|
+
}
|
|
2981
|
+
else {
|
|
2982
|
+
if (!predictionsModule.VALID_CLOSURE_STATES.has(status)) {
|
|
2983
|
+
console.error(`Invalid --status: "${status}". Must be one of: open | closed | closed-unknown | all.`);
|
|
2984
|
+
process.exit(1);
|
|
2985
|
+
}
|
|
2986
|
+
if (classTag) {
|
|
2987
|
+
results = predictionsModule.loadPredictionsByClass(hippoRoot, tenantId, classTag, {
|
|
2988
|
+
closureState: status,
|
|
2989
|
+
limit,
|
|
2990
|
+
});
|
|
2991
|
+
}
|
|
2992
|
+
else {
|
|
2993
|
+
// status filter without class — scan all classes is more complex; v1 requires --class for non-default status
|
|
2994
|
+
console.error('--status filter (non-open) requires --class to be set.');
|
|
2995
|
+
process.exit(1);
|
|
2996
|
+
}
|
|
2997
|
+
}
|
|
2998
|
+
if (results.length === 0) {
|
|
2999
|
+
console.log(classTag ? `No predictions in class "${classTag}".` : 'No predictions.');
|
|
3000
|
+
return;
|
|
3001
|
+
}
|
|
3002
|
+
console.log(`Found ${results.length} predictions:\n`);
|
|
3003
|
+
for (const p of results) {
|
|
3004
|
+
const estPart = p.estimateValue !== null ? ` estimate=${p.estimateValue}${p.estimateUnit ? ` ${p.estimateUnit}` : ''}` : '';
|
|
3005
|
+
const actPart = p.actualValue !== null ? ` actual=${p.actualValue}` : '';
|
|
3006
|
+
const tgtPart = p.targetDate ? ` target=${p.targetDate}` : '';
|
|
3007
|
+
console.log(`#${p.id} [${p.closureState}] class=${p.classTag}${estPart}${actPart}${tgtPart}`);
|
|
3008
|
+
console.log(` ${p.claimText}`);
|
|
3009
|
+
if (p.closureNote)
|
|
3010
|
+
console.log(` note: ${p.closureNote}`);
|
|
3011
|
+
}
|
|
3012
|
+
return;
|
|
3013
|
+
}
|
|
3014
|
+
if (subcommand === 'show') {
|
|
3015
|
+
const idRaw = args[1];
|
|
3016
|
+
if (!idRaw) {
|
|
3017
|
+
console.error('Usage: hippo predict show <id>');
|
|
3018
|
+
process.exit(1);
|
|
3019
|
+
}
|
|
3020
|
+
const id = parseInt(String(idRaw), 10);
|
|
3021
|
+
if (!Number.isFinite(id) || id <= 0) {
|
|
3022
|
+
console.error(`Invalid prediction id: "${idRaw}"`);
|
|
3023
|
+
process.exit(1);
|
|
3024
|
+
}
|
|
3025
|
+
const pred = predictionsModule.loadPredictionById(hippoRoot, tenantId, id);
|
|
3026
|
+
if (!pred) {
|
|
3027
|
+
console.error(`Prediction ${id} not found.`);
|
|
3028
|
+
process.exit(1);
|
|
3029
|
+
}
|
|
3030
|
+
console.log(`Prediction #${pred.id}`);
|
|
3031
|
+
console.log(` class: ${pred.classTag}`);
|
|
3032
|
+
console.log(` claim: ${pred.claimText}`);
|
|
3033
|
+
console.log(` state: ${pred.closureState}`);
|
|
3034
|
+
if (pred.estimateValue !== null)
|
|
3035
|
+
console.log(` estimate: ${pred.estimateValue}${pred.estimateUnit ? ' ' + pred.estimateUnit : ''}`);
|
|
3036
|
+
if (pred.targetDate)
|
|
3037
|
+
console.log(` target: ${pred.targetDate}`);
|
|
3038
|
+
if (pred.actualValue !== null)
|
|
3039
|
+
console.log(` actual: ${pred.actualValue}`);
|
|
3040
|
+
if (pred.closedAt)
|
|
3041
|
+
console.log(` closed: ${pred.closedAt}`);
|
|
3042
|
+
if (pred.closureNote)
|
|
3043
|
+
console.log(` note: ${pred.closureNote}`);
|
|
3044
|
+
if (pred.memoryId)
|
|
3045
|
+
console.log(` memory: ${pred.memoryId}`);
|
|
3046
|
+
console.log(` created: ${pred.createdAt}`);
|
|
3047
|
+
return;
|
|
3048
|
+
}
|
|
3049
|
+
if (subcommand === 'baserate') {
|
|
3050
|
+
// J3 reference-class / planning-fallacy detector
|
|
3051
|
+
const classTagRaw = flags['class'];
|
|
3052
|
+
if (typeof classTagRaw !== 'string' || !classTagRaw.trim()) {
|
|
3053
|
+
console.error('Usage: hippo predict baserate --class <c>');
|
|
3054
|
+
process.exit(1);
|
|
3055
|
+
}
|
|
3056
|
+
const baserate = predictionsModule.computePredictionBaserate(hippoRoot, tenantId, classTagRaw.trim());
|
|
3057
|
+
if (baserate.nClosed === 0) {
|
|
3058
|
+
console.log(`No closed predictions in class "${baserate.classTag}" yet.`);
|
|
3059
|
+
console.log(` Create one with: hippo predict "<claim>" --class ${baserate.classTag} --estimate N`);
|
|
3060
|
+
console.log(` Close it later: hippo predict close <id> --state closed --actual N`);
|
|
3061
|
+
return;
|
|
3062
|
+
}
|
|
3063
|
+
console.log(baserate.summary);
|
|
3064
|
+
console.log(` n_closed: ${baserate.nClosed}`);
|
|
3065
|
+
console.log(` n_ratio_eligible: ${baserate.nRatioEligible}`);
|
|
3066
|
+
if (baserate.meanEstimate !== null)
|
|
3067
|
+
console.log(` mean_estimate: ${baserate.meanEstimate.toFixed(3)}`);
|
|
3068
|
+
if (baserate.meanActual !== null)
|
|
3069
|
+
console.log(` mean_actual: ${baserate.meanActual.toFixed(3)}`);
|
|
3070
|
+
if (baserate.meanRatio !== null)
|
|
3071
|
+
console.log(` mean_ratio: ${baserate.meanRatio.toFixed(3)}x`);
|
|
3072
|
+
if (baserate.p50Ratio !== null)
|
|
3073
|
+
console.log(` p50_ratio: ${baserate.p50Ratio.toFixed(3)}x`);
|
|
3074
|
+
if (baserate.mae !== null)
|
|
3075
|
+
console.log(` mae: ${baserate.mae.toFixed(3)}`);
|
|
3076
|
+
return;
|
|
3077
|
+
}
|
|
3078
|
+
// Default subcommand: create. args[0] is the claim text.
|
|
3079
|
+
const claimText = subcommand;
|
|
3080
|
+
if (!claimText) {
|
|
3081
|
+
console.error('Usage: hippo predict "<claim>" --class <c> [--estimate <v>] [--unit <u>] [--target <YYYY-MM-DD>]');
|
|
3082
|
+
console.error(' hippo predict close <id> --state <closed|closed-unknown> [--actual <v>] [--note "..."]');
|
|
3083
|
+
console.error(' hippo predict list [--class X] [--status open|closed|closed-unknown|all] [--limit N]');
|
|
3084
|
+
console.error(' hippo predict show <id>');
|
|
3085
|
+
process.exit(1);
|
|
3086
|
+
}
|
|
3087
|
+
const classTagRaw = flags['class'];
|
|
3088
|
+
if (typeof classTagRaw !== 'string' || !classTagRaw.trim()) {
|
|
3089
|
+
console.error('--class is required for prediction creation.');
|
|
3090
|
+
process.exit(1);
|
|
3091
|
+
}
|
|
3092
|
+
const classTag = classTagRaw.trim();
|
|
3093
|
+
const estimateRaw = flags['estimate'];
|
|
3094
|
+
const estimateValue = estimateRaw !== undefined ? Number(estimateRaw) : undefined;
|
|
3095
|
+
if (estimateRaw !== undefined && !Number.isFinite(estimateValue)) {
|
|
3096
|
+
console.error(`Invalid --estimate: "${estimateRaw}". Must be a number.`);
|
|
3097
|
+
process.exit(1);
|
|
3098
|
+
}
|
|
3099
|
+
const unitRaw = flags['unit'];
|
|
3100
|
+
const estimateUnit = typeof unitRaw === 'string' ? unitRaw : undefined;
|
|
3101
|
+
const targetRaw = flags['target'];
|
|
3102
|
+
const targetDate = typeof targetRaw === 'string' ? targetRaw : undefined;
|
|
3103
|
+
const created = predictionsModule.savePrediction(hippoRoot, tenantId, {
|
|
3104
|
+
classTag,
|
|
3105
|
+
claimText,
|
|
3106
|
+
estimateValue,
|
|
3107
|
+
estimateUnit,
|
|
3108
|
+
targetDate,
|
|
3109
|
+
});
|
|
3110
|
+
console.log(`Prediction recorded: #${created.id} class=${created.classTag}`);
|
|
3111
|
+
if (created.memoryId)
|
|
3112
|
+
console.log(` memory: ${created.memoryId}`);
|
|
3113
|
+
}
|
|
2843
3114
|
function cmdCurrent(hippoRoot, args, flags) {
|
|
2844
3115
|
requireInit(hippoRoot);
|
|
2845
3116
|
const subcommand = args[0] ?? 'show';
|
|
@@ -4244,6 +4515,9 @@ const VALID_AUDIT_OPS = new Set([
|
|
|
4244
4515
|
'summary_marked_dirty', // v0.30 / E1 — lockstep with AuditOp union + server.ts VALID_AUDIT_OPS (v1.11.5 CRIT A institutional rule)
|
|
4245
4516
|
'summary_marked_clean', // v0.30 / E3 — buildDag post-link clean op; lockstep
|
|
4246
4517
|
'summary_rebuilt', // v0.30 / E3 — sleep-cycle rebuild op; lockstep
|
|
4518
|
+
'predict_create', // v0.31 / E2 prediction first-class object — emitted by savePrediction
|
|
4519
|
+
'predict_close', // v0.31 / E2 — emitted by closePrediction
|
|
4520
|
+
'predict_baserate', // v0.31 / J3 — emitted by computePredictionBaserate
|
|
4247
4521
|
]);
|
|
4248
4522
|
function formatAuditRow(ev) {
|
|
4249
4523
|
const target = ev.targetId ?? '-';
|
|
@@ -5426,6 +5700,9 @@ async function main() {
|
|
|
5426
5700
|
case 'handoff':
|
|
5427
5701
|
cmdHandoff(hippoRoot, args, flags);
|
|
5428
5702
|
break;
|
|
5703
|
+
case 'predict':
|
|
5704
|
+
cmdPredict(hippoRoot, args, flags);
|
|
5705
|
+
break;
|
|
5429
5706
|
case 'current':
|
|
5430
5707
|
cmdCurrent(hippoRoot, args, flags);
|
|
5431
5708
|
break;
|