hippo-memory 1.61.0 → 1.63.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/README.md +37 -53
- package/dist/agent-memories/apply.d.ts +3 -1
- package/dist/agent-memories/apply.js +23 -1
- package/dist/agent-memories/claude-code.d.ts +4 -1
- package/dist/agent-memories/claude-code.js +53 -9
- package/dist/agent-memories/report.d.ts +1 -0
- package/dist/agent-memories/report.js +2 -2
- package/dist/agent-memories/sync.d.ts +3 -3
- package/dist/agent-memories/sync.js +46 -24
- package/dist/agent-memories/types.d.ts +0 -2
- package/dist/ambient-store.d.ts +4 -4
- package/dist/ambient-store.js +4 -3
- package/dist/api/assemble.d.ts +7 -10
- package/dist/api/assemble.js +62 -66
- package/dist/api/audit.d.ts +2 -2
- package/dist/api/audit.js +2 -2
- package/dist/api/auth.d.ts +6 -6
- package/dist/api/auth.js +5 -7
- package/dist/api/context-select.d.ts +50 -0
- package/dist/api/context-select.js +342 -0
- package/dist/api/context-types.d.ts +12 -17
- package/dist/api/context.d.ts +5 -4
- package/dist/api/context.js +208 -535
- package/dist/api/dormant.js +1 -1
- package/dist/api/drill-down.d.ts +4 -4
- package/dist/api/drill-down.js +56 -41
- package/dist/api/outcome.d.ts +8 -13
- package/dist/api/outcome.js +13 -17
- package/dist/api/promote.d.ts +5 -8
- package/dist/api/promote.js +55 -66
- package/dist/api/quarantine.js +3 -2
- package/dist/api/recall-types.d.ts +43 -55
- package/dist/api/recall.d.ts +3 -3
- package/dist/api/recall.js +310 -456
- package/dist/api/remember.d.ts +2 -2
- package/dist/api/sleep.d.ts +7 -35
- package/dist/api/sleep.js +206 -220
- package/dist/api/tokens.d.ts +2 -2
- package/dist/api/tokens.js +2 -2
- package/dist/api/types.d.ts +6 -10
- package/dist/api/types.js +2 -4
- package/dist/audit-prune.d.ts +4 -6
- package/dist/audit-prune.js +3 -5
- package/dist/audit.js +11 -33
- package/dist/auth.d.ts +8 -9
- package/dist/auth.js +5 -7
- package/dist/autolearn.d.ts +1 -1
- package/dist/autolearn.js +1 -1
- package/dist/availability.js +3 -5
- package/dist/capture/command.d.ts +5 -4
- package/dist/capture/command.js +8 -22
- package/dist/capture/compact.d.ts +2 -2
- package/dist/capture/compact.js +11 -15
- package/dist/capture/extract.js +37 -115
- package/dist/capture-error.d.ts +1 -1
- package/dist/capture-error.js +1 -1
- package/dist/churn-git.d.ts +1 -1
- package/dist/churn-git.js +1 -1
- package/dist/cli/audit.js +3 -4
- package/dist/cli/auth.js +3 -6
- package/dist/cli/briefs.js +324 -306
- package/dist/cli/context.js +44 -34
- package/dist/cli/continuity.js +283 -271
- package/dist/cli/curate.d.ts +1 -1
- package/dist/cli/curate.js +43 -58
- package/dist/cli/dag.js +5 -9
- package/dist/cli/decisions.js +334 -345
- package/dist/cli/explain.js +68 -61
- package/dist/cli/goals.js +1 -1
- package/dist/cli/init.js +1 -1
- package/dist/cli/maintenance.js +62 -51
- package/dist/cli/playbooks.js +391 -379
- package/dist/cli/projects.js +11 -6
- package/dist/cli/recall.js +30 -44
- package/dist/cli/remember.js +118 -87
- package/dist/cli/session-hooks.js +106 -115
- package/dist/cli/setup.d.ts +1 -1
- package/dist/cli/setup.js +267 -250
- package/dist/cli/shared.js +3 -3
- package/dist/cli/slack.js +1 -1
- package/dist/cli/sleep.js +27 -1
- package/dist/cli/status.d.ts +4 -4
- package/dist/cli/status.js +80 -76
- package/dist/cli/transfer.js +88 -107
- package/dist/cli/usage.js +9 -6
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +4 -9
- package/dist/compaction-record.d.ts +2 -2
- package/dist/compaction-record.js +89 -68
- package/dist/compare.d.ts +11 -16
- package/dist/compare.js +11 -16
- package/dist/config.d.ts +19 -18
- package/dist/config.js +90 -67
- package/dist/connectors/github/backfill.d.ts +2 -2
- package/dist/connectors/github/backfill.js +8 -15
- package/dist/connectors/github/cli-impl.js +3 -8
- package/dist/connectors/github/deletion.d.ts +5 -12
- package/dist/connectors/github/deletion.js +5 -12
- package/dist/connectors/github/dlq.d.ts +6 -9
- package/dist/connectors/github/dlq.js +2 -3
- package/dist/connectors/github/ingest.d.ts +5 -7
- package/dist/connectors/github/ingest.js +8 -12
- package/dist/connectors/github/octokit-client.d.ts +3 -5
- package/dist/connectors/github/octokit-client.js +5 -6
- package/dist/connectors/github/signature.d.ts +9 -39
- package/dist/connectors/github/signature.js +9 -39
- package/dist/connectors/github/tenant-routing.d.ts +1 -1
- package/dist/connectors/github/tenant-routing.js +1 -1
- package/dist/connectors/github/transform.js +2 -2
- package/dist/connectors/github/types.d.ts +2 -10
- package/dist/connectors/github/types.js +1 -3
- package/dist/connectors/slack/deletion.d.ts +3 -8
- package/dist/connectors/slack/deletion.js +3 -8
- package/dist/connectors/slack/dlq.d.ts +1 -1
- package/dist/connectors/slack/ingest.d.ts +1 -1
- package/dist/connectors/slack/ingest.js +7 -16
- package/dist/connectors/slack/signature.d.ts +1 -1
- package/dist/connectors/slack/tenant-routing.d.ts +3 -5
- package/dist/connectors/slack/tenant-routing.js +3 -5
- package/dist/connectors/slack/transform.d.ts +5 -6
- package/dist/connectors/slack/transform.js +5 -6
- package/dist/connectors/slack/types.d.ts +2 -6
- package/dist/connectors/slack/types.js +1 -3
- package/dist/connectors/slack/web-client.js +10 -3
- package/dist/connectors/slack/workspaces.d.ts +3 -5
- package/dist/connectors/slack/workspaces.js +3 -5
- package/dist/consolidate/conflicts.js +3 -14
- package/dist/consolidate/decay.js +9 -29
- package/dist/consolidate/llm-passes.js +4 -5
- package/dist/consolidate/merge.js +8 -23
- package/dist/consolidate/run.d.ts +1 -8
- package/dist/consolidate/run.js +3 -25
- package/dist/consolidate/sleep.js +5 -17
- package/dist/consolidate/traces.js +9 -21
- package/dist/customer-notes.d.ts +5 -7
- package/dist/customer-notes.js +82 -76
- package/dist/dag.d.ts +10 -21
- package/dist/dag.js +189 -203
- package/dist/db/continuity.js +2 -2
- package/dist/db/migrations/v14.js +1 -1
- package/dist/db/migrations/v15.js +1 -2
- package/dist/db/migrations/v16.js +3 -4
- package/dist/db/migrations/v17.js +2 -3
- package/dist/db/migrations/v19.js +1 -1
- package/dist/db/migrations/v20.js +1 -1
- package/dist/db/migrations/v21.js +2 -6
- package/dist/db/migrations/v22.js +2 -4
- package/dist/db/migrations/v23.js +1 -1
- package/dist/db/migrations/v24.js +4 -6
- package/dist/db/migrations/v25.js +2 -3
- package/dist/db/migrations/v26.js +3 -3
- package/dist/db/migrations/v27.js +2 -10
- package/dist/db/migrations/v28.js +5 -8
- package/dist/db/migrations/v29.js +3 -4
- package/dist/db/migrations/v30.js +2 -2
- package/dist/db/migrations/v31.js +1 -1
- package/dist/db/migrations/v32.js +1 -1
- package/dist/db/migrations/v33.js +3 -3
- package/dist/db/migrations/v34.js +1 -1
- package/dist/db/migrations/v35.js +3 -4
- package/dist/db/migrations/v36.js +3 -4
- package/dist/db/migrations/v37.js +5 -5
- package/dist/db/migrations/v38.js +7 -8
- package/dist/db/migrations/v39.js +1 -1
- package/dist/db/migrations/v40.js +4 -16
- package/dist/db/migrations/v41.js +3 -4
- package/dist/db/migrations/v42.js +3 -4
- package/dist/db/migrations/v45.js +1 -1
- package/dist/db/migrations/v46.js +1 -1
- package/dist/db/migrations/v47.js +1 -1
- package/dist/db/migrations/v48.js +1 -1
- package/dist/decisions.d.ts +2 -2
- package/dist/decisions.js +97 -80
- package/dist/dedupe.js +86 -61
- package/dist/delivery-recorder.js +154 -135
- package/dist/doctor.js +129 -110
- package/dist/dormant.js +1 -4
- package/dist/embedding-provider.d.ts +4 -8
- package/dist/embedding-provider.js +4 -8
- package/dist/embeddings.js +55 -47
- package/dist/env.d.ts +1 -1
- package/dist/env.js +12 -12
- package/dist/escape.d.ts +5 -0
- package/dist/escape.js +10 -0
- package/dist/eval-stats.d.ts +1 -2
- package/dist/eval-stats.js +1 -2
- package/dist/eval-suite.js +27 -21
- package/dist/extract.js +4 -9
- package/dist/failure-log.d.ts +3 -3
- package/dist/failure-log.js +1 -1
- package/dist/forward-claim-detector.d.ts +2 -4
- package/dist/forward-claim-detector.js +6 -11
- package/dist/goals.d.ts +3 -3
- package/dist/goals.js +103 -91
- package/dist/graph/read.d.ts +2 -2
- package/dist/graph/read.js +5 -6
- package/dist/graph/types.d.ts +8 -8
- package/dist/graph/write.d.ts +7 -14
- package/dist/graph/write.js +16 -23
- package/dist/graph-extract.d.ts +7 -8
- package/dist/graph-extract.js +62 -72
- package/dist/graph-recall.d.ts +2 -2
- package/dist/graph-recall.js +55 -49
- package/dist/graph-stream.d.ts +5 -6
- package/dist/graph-stream.js +66 -57
- package/dist/graph-view.d.ts +2 -2
- package/dist/graph-view.js +7 -7
- package/dist/half-life-migration.d.ts +1 -2
- package/dist/half-life-migration.js +2 -3
- package/dist/hooks/codex-session.js +1 -1
- package/dist/hooks/codex-wrapper.d.ts +1 -1
- package/dist/hooks/codex-wrapper.js +3 -2
- package/dist/hooks/json-hooks.d.ts +2 -2
- package/dist/hooks/json-hooks.js +5 -4
- package/dist/hooks/opencode.d.ts +1 -1
- package/dist/hooks/opencode.js +5 -4
- package/dist/hooks/shared.d.ts +3 -7
- package/dist/hooks/shared.js +1 -8
- package/dist/http-util.d.ts +2 -3
- package/dist/http-util.js +3 -0
- package/dist/importers/core.d.ts +2 -9
- package/dist/importers/core.js +15 -30
- package/dist/importers/sources.js +2 -1
- package/dist/importers/vault.js +2 -20
- package/dist/incidents.d.ts +1 -1
- package/dist/incidents.js +46 -39
- package/dist/instruction-detect.d.ts +1 -1
- package/dist/instruction-detect.js +1 -1
- package/dist/invalidation.d.ts +3 -0
- package/dist/invalidation.js +160 -114
- package/dist/json.d.ts +5 -0
- package/dist/json.js +4 -0
- package/dist/judgment.js +1 -2
- package/dist/local-embedding.js +1 -1
- package/dist/mcp/admin-tools.js +7 -17
- package/dist/mcp/format.js +1 -1
- package/dist/mcp/framing.js +3 -6
- package/dist/mcp/protocol.d.ts +2 -5
- package/dist/mcp/protocol.js +1 -3
- package/dist/mcp/recall-tools.js +12 -15
- package/dist/mcp/request.js +4 -3
- package/dist/mcp/session-state.js +2 -3
- package/dist/mcp/stdio.js +2 -1
- package/dist/mcp/tools.js +9 -6
- package/dist/memory-value-weights.d.ts +5 -8
- package/dist/memory-value-weights.js +5 -8
- package/dist/memory-value.d.ts +13 -13
- package/dist/memory-value.js +26 -37
- package/dist/memory.d.ts +20 -22
- package/dist/memory.js +24 -48
- package/dist/multihop.d.ts +1 -1
- package/dist/multihop.js +3 -2
- package/dist/owner-validation.d.ts +4 -5
- package/dist/owner-validation.js +4 -5
- package/dist/physics.d.ts +4 -4
- package/dist/physics.js +7 -9
- package/dist/policies.d.ts +9 -10
- package/dist/policies.js +96 -81
- package/dist/postinstall.js +3 -6
- package/dist/predictions/planning-fallacy.d.ts +9 -14
- package/dist/predictions/planning-fallacy.js +10 -16
- package/dist/predictions/store.d.ts +15 -23
- package/dist/predictions/store.js +36 -33
- package/dist/processes.d.ts +2 -7
- package/dist/processes.js +88 -72
- package/dist/project-briefs.d.ts +2 -3
- package/dist/project-briefs.js +141 -118
- package/dist/project-identity.d.ts +22 -9
- package/dist/project-identity.js +47 -12
- package/dist/project-merge.d.ts +28 -5
- package/dist/project-merge.js +213 -46
- package/dist/project-remote.d.ts +12 -0
- package/dist/project-remote.js +138 -0
- package/dist/prompt-recall.js +1 -2
- package/dist/rate-limit.d.ts +1 -1
- package/dist/rate-limit.js +1 -1
- package/dist/raw-archive.d.ts +9 -0
- package/dist/raw-archive.js +70 -53
- package/dist/recall-history.d.ts +19 -20
- package/dist/recall-history.js +24 -42
- package/dist/recall-pipeline.js +4 -28
- package/dist/recall-scope.d.ts +7 -8
- package/dist/recall-scope.js +7 -8
- package/dist/recall-trace.d.ts +5 -9
- package/dist/recall-trace.js +6 -10
- package/dist/refine-llm.d.ts +1 -1
- package/dist/refine-llm.js +2 -2
- package/dist/reject-flow.d.ts +3 -4
- package/dist/reject-flow.js +122 -117
- package/dist/rejection.d.ts +5 -6
- package/dist/rejection.js +7 -15
- package/dist/rerankers/clef.d.ts +1 -1
- package/dist/rerankers/jev.d.ts +1 -2
- package/dist/rerankers/jev.js +4 -5
- package/dist/rerankers/llm.d.ts +1 -2
- package/dist/rerankers/llm.js +1 -2
- package/dist/rerankers/types.d.ts +1 -2
- package/dist/rrf.d.ts +2 -2
- package/dist/rrf.js +2 -2
- package/dist/search/bm25-search.d.ts +1 -1
- package/dist/search/bm25-search.js +2 -1
- package/dist/search/boosts.js +2 -1
- package/dist/search/hybrid.d.ts +1 -1
- package/dist/search/hybrid.js +2 -1
- package/dist/search/physics-search.d.ts +1 -1
- package/dist/search/physics-search.js +2 -1
- package/dist/search/types.d.ts +2 -0
- package/dist/search/types.js +3 -1
- package/dist/secret-detect.d.ts +4 -5
- package/dist/secret-detect.js +6 -10
- package/dist/server/auth.js +5 -5
- package/dist/server/client-ip.js +1 -1
- package/dist/server/cursor.js +2 -1
- package/dist/server/mcp-http.js +4 -4
- package/dist/server/request.d.ts +3 -6
- package/dist/server/request.js +6 -7
- package/dist/server/routes/admin.js +5 -4
- package/dist/server/routes/customer-notes.js +6 -5
- package/dist/server/routes/decisions.js +4 -3
- package/dist/server/routes/incidents.js +7 -5
- package/dist/server/routes/memories.js +7 -7
- package/dist/server/routes/policies.js +3 -2
- package/dist/server/routes/predictions.js +12 -15
- package/dist/server/routes/processes.js +3 -2
- package/dist/server/routes/project-briefs.js +8 -7
- package/dist/server/routes/recall.js +95 -93
- package/dist/server/routes/skills.js +6 -5
- package/dist/server/types.d.ts +1 -1
- package/dist/server/validation.d.ts +1 -2
- package/dist/server/validation.js +7 -14
- package/dist/server-detect.js +72 -58
- package/dist/server.d.ts +2 -2
- package/dist/server.js +131 -117
- package/dist/shared.d.ts +26 -17
- package/dist/shared.js +102 -104
- package/dist/skills.d.ts +3 -3
- package/dist/skills.js +88 -72
- package/dist/store/audit-event.d.ts +2 -2
- package/dist/store/audit-event.js +1 -1
- package/dist/store/candidates.d.ts +2 -2
- package/dist/store/candidates.js +4 -3
- package/dist/store/conflicts.js +30 -22
- package/dist/store/delete-and-batch.d.ts +11 -14
- package/dist/store/delete-and-batch.js +40 -91
- package/dist/store/entry-reads.d.ts +17 -25
- package/dist/store/entry-reads.js +59 -40
- package/dist/store/entry-row.d.ts +6 -24
- package/dist/store/entry-row.js +6 -24
- package/dist/store/entry-writes.d.ts +6 -7
- package/dist/store/entry-writes.js +13 -11
- package/dist/store/handoffs.d.ts +1 -1
- package/dist/store/handoffs.js +7 -10
- package/dist/store/index-and-stats.d.ts +2 -6
- package/dist/store/index-and-stats.js +4 -10
- package/dist/store/mirrors.d.ts +6 -19
- package/dist/store/mirrors.js +14 -39
- package/dist/store/open.js +9 -31
- package/dist/store/rows.d.ts +5 -11
- package/dist/store/rows.js +6 -11
- package/dist/store/search-rows.d.ts +17 -34
- package/dist/store/search-rows.js +34 -56
- package/dist/store/sessions.d.ts +4 -5
- package/dist/store/sessions.js +5 -6
- package/dist/store/summaries.d.ts +13 -17
- package/dist/store/summaries.js +26 -70
- package/dist/support-bundle.js +4 -8
- package/dist/tenant.d.ts +1 -5
- package/dist/token-ledger.d.ts +1 -1
- package/dist/token-ledger.js +3 -5
- package/dist/trace.js +1 -3
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/working-memory.d.ts +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/decisions.js
CHANGED
|
@@ -8,175 +8,176 @@ import * as incidentsModule from '../incidents.js';
|
|
|
8
8
|
import { resolveTenantId } from '../tenant.js';
|
|
9
9
|
import { printError } from './output.js';
|
|
10
10
|
import { requireInit } from './shared.js';
|
|
11
|
+
function parseListLimit(flags) {
|
|
12
|
+
const limitRaw = flags['limit'];
|
|
13
|
+
const limit = limitRaw !== undefined ? parseInt(String(limitRaw), 10) : 100;
|
|
14
|
+
if (!Number.isFinite(limit) || limit <= 0) {
|
|
15
|
+
printError(`Invalid --limit: "${limitRaw}". Must be a positive integer.`);
|
|
16
|
+
process.exit(1);
|
|
17
|
+
}
|
|
18
|
+
return limit;
|
|
19
|
+
}
|
|
20
|
+
/** parseInt-based id parse for predictions and decisions; incidents use the strict parse below. */
|
|
21
|
+
function parseObjectId(idRaw, noun) {
|
|
22
|
+
const id = parseInt(String(idRaw), 10);
|
|
23
|
+
if (!Number.isFinite(id) || id <= 0) {
|
|
24
|
+
printError(`Invalid ${noun} id: "${idRaw}"`);
|
|
25
|
+
process.exit(1);
|
|
26
|
+
}
|
|
27
|
+
return id;
|
|
28
|
+
}
|
|
11
29
|
// ---------------------------------------------------------------------------
|
|
12
|
-
//
|
|
13
|
-
// docs/plans/2026-05-26-e2-prediction-object.md
|
|
30
|
+
// Prediction first-class object
|
|
14
31
|
// ---------------------------------------------------------------------------
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
32
|
+
function predictClose(hippoRoot, tenantId, args, flags) {
|
|
33
|
+
const idRaw = args[1];
|
|
34
|
+
if (!idRaw) {
|
|
35
|
+
printError('Usage: hippo predict close <id> --state <closed|closed-unknown> [--actual <v>] [--note "..."]');
|
|
36
|
+
process.exit(1);
|
|
37
|
+
}
|
|
38
|
+
const id = parseObjectId(idRaw, 'prediction');
|
|
39
|
+
const stateRaw = typeof flags['state'] === 'string' ? flags['state'].trim() : '';
|
|
40
|
+
if (!predictionsModule.VALID_CLOSURE_STATES.has(stateRaw) || stateRaw === 'open') {
|
|
41
|
+
printError(`Invalid --state: "${stateRaw}". Must be one of: closed | closed-unknown.`);
|
|
42
|
+
process.exit(1);
|
|
43
|
+
}
|
|
44
|
+
const actualRaw = flags['actual'];
|
|
45
|
+
const actualValue = actualRaw !== undefined ? Number(actualRaw) : undefined;
|
|
46
|
+
if (actualRaw !== undefined && !Number.isFinite(actualValue)) {
|
|
47
|
+
printError(`Invalid --actual: "${actualRaw}". Must be a number.`);
|
|
48
|
+
process.exit(1);
|
|
49
|
+
}
|
|
50
|
+
const noteRaw = flags['note'];
|
|
51
|
+
const closureNote = typeof noteRaw === 'string' ? noteRaw : undefined;
|
|
52
|
+
const closed = predictionsModule.closePrediction(hippoRoot, tenantId, id, {
|
|
53
|
+
closureState: stateRaw,
|
|
54
|
+
actualValue,
|
|
55
|
+
closureNote,
|
|
56
|
+
});
|
|
57
|
+
console.log(`Prediction ${closed.id} closed: state=${closed.closureState}${closed.actualValue !== null ? ` actual=${closed.actualValue}` : ''}`);
|
|
58
|
+
}
|
|
59
|
+
function loadPredictionList(hippoRoot, tenantId, status, classTag, limit) {
|
|
60
|
+
if (status === 'open') {
|
|
61
|
+
return predictionsModule.loadOpenPredictions(hippoRoot, tenantId, {
|
|
62
|
+
classTag: classTag || undefined,
|
|
63
|
+
limit,
|
|
47
64
|
});
|
|
48
|
-
console.log(`Prediction ${closed.id} closed: state=${closed.closureState}${closed.actualValue !== null ? ` actual=${closed.actualValue}` : ''}`);
|
|
49
|
-
return;
|
|
50
65
|
}
|
|
51
|
-
if (
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
const status = typeof statusRaw === 'string' ? statusRaw.trim() : 'all';
|
|
56
|
-
const limitRaw = flags['limit'];
|
|
57
|
-
const limit = limitRaw !== undefined ? parseInt(String(limitRaw), 10) : 100;
|
|
58
|
-
if (!Number.isFinite(limit) || limit <= 0) {
|
|
59
|
-
printError(`Invalid --limit: "${limitRaw}". Must be a positive integer.`);
|
|
60
|
-
process.exit(1);
|
|
61
|
-
}
|
|
62
|
-
let results;
|
|
63
|
-
if (status === 'open') {
|
|
64
|
-
results = predictionsModule.loadOpenPredictions(hippoRoot, tenantId, {
|
|
65
|
-
classTag: classTag || undefined,
|
|
66
|
-
limit,
|
|
67
|
-
});
|
|
68
|
-
}
|
|
69
|
-
else if (status === 'all') {
|
|
70
|
-
// No closure-state filter; pull both via loadPredictionsByClass if class given
|
|
71
|
-
if (classTag) {
|
|
72
|
-
results = predictionsModule.loadPredictionsByClass(hippoRoot, tenantId, classTag, { limit });
|
|
73
|
-
}
|
|
74
|
-
else {
|
|
75
|
-
// No class filter + status=all = pull open + closed across all classes
|
|
76
|
-
// (kept simple: report open via loadOpenPredictions; closed via two
|
|
77
|
-
// class scans isn't symmetrical. v1 callers typically pass --class.)
|
|
78
|
-
results = predictionsModule.loadOpenPredictions(hippoRoot, tenantId, { limit });
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
else {
|
|
82
|
-
if (!predictionsModule.VALID_CLOSURE_STATES.has(status)) {
|
|
83
|
-
printError(`Invalid --status: "${status}". Must be one of: open | closed | closed-unknown | all.`);
|
|
84
|
-
process.exit(1);
|
|
85
|
-
}
|
|
86
|
-
if (classTag) {
|
|
87
|
-
results = predictionsModule.loadPredictionsByClass(hippoRoot, tenantId, classTag, {
|
|
88
|
-
closureState: status,
|
|
89
|
-
limit,
|
|
90
|
-
});
|
|
91
|
-
}
|
|
92
|
-
else {
|
|
93
|
-
// status filter without class — scan all classes is more complex; v1 requires --class for non-default status
|
|
94
|
-
printError('--status filter (non-open) requires --class to be set.');
|
|
95
|
-
process.exit(1);
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
if (results.length === 0) {
|
|
99
|
-
console.log(classTag ? `No predictions in class "${classTag}".` : 'No predictions.');
|
|
100
|
-
return;
|
|
101
|
-
}
|
|
102
|
-
console.log(`Found ${results.length} predictions:\n`);
|
|
103
|
-
for (const p of results) {
|
|
104
|
-
const estPart = p.estimateValue !== null ? ` estimate=${p.estimateValue}${p.estimateUnit ? ` ${p.estimateUnit}` : ''}` : '';
|
|
105
|
-
const actPart = p.actualValue !== null ? ` actual=${p.actualValue}` : '';
|
|
106
|
-
const tgtPart = p.targetDate ? ` target=${p.targetDate}` : '';
|
|
107
|
-
console.log(`#${p.id} [${p.closureState}] class=${p.classTag}${estPart}${actPart}${tgtPart}`);
|
|
108
|
-
console.log(` ${p.claimText}`);
|
|
109
|
-
if (p.closureNote)
|
|
110
|
-
console.log(` note: ${p.closureNote}`);
|
|
111
|
-
}
|
|
112
|
-
return;
|
|
66
|
+
if (status === 'all') {
|
|
67
|
+
return classTag
|
|
68
|
+
? predictionsModule.loadPredictionsByClass(hippoRoot, tenantId, classTag, { limit })
|
|
69
|
+
: predictionsModule.loadAllPredictions(hippoRoot, tenantId, { limit });
|
|
113
70
|
}
|
|
114
|
-
if (
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
console.log(` target: ${pred.targetDate}`);
|
|
138
|
-
if (pred.actualValue !== null)
|
|
139
|
-
console.log(` actual: ${pred.actualValue}`);
|
|
140
|
-
if (pred.closedAt)
|
|
141
|
-
console.log(` closed: ${pred.closedAt}`);
|
|
142
|
-
if (pred.closureNote)
|
|
143
|
-
console.log(` note: ${pred.closureNote}`);
|
|
144
|
-
if (pred.memoryId)
|
|
145
|
-
console.log(` memory: ${pred.memoryId}`);
|
|
146
|
-
console.log(` created: ${pred.createdAt}`);
|
|
71
|
+
if (!predictionsModule.VALID_CLOSURE_STATES.has(status)) {
|
|
72
|
+
printError(`Invalid --status: "${status}". Must be one of: open | closed | closed-unknown | all.`);
|
|
73
|
+
process.exit(1);
|
|
74
|
+
}
|
|
75
|
+
if (!classTag) {
|
|
76
|
+
// status filter without class — scan all classes is more complex; v1 requires --class for non-default status
|
|
77
|
+
printError('--status filter (non-open) requires --class to be set.');
|
|
78
|
+
process.exit(1);
|
|
79
|
+
}
|
|
80
|
+
return predictionsModule.loadPredictionsByClass(hippoRoot, tenantId, classTag, {
|
|
81
|
+
closureState: status,
|
|
82
|
+
limit,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
function predictList(hippoRoot, tenantId, flags) {
|
|
86
|
+
const classTagRaw = flags['class'];
|
|
87
|
+
const classTag = typeof classTagRaw === 'string' ? classTagRaw.trim() : '';
|
|
88
|
+
const statusRaw = flags['status'];
|
|
89
|
+
const status = typeof statusRaw === 'string' ? statusRaw.trim() : 'all';
|
|
90
|
+
const limit = parseListLimit(flags);
|
|
91
|
+
const results = loadPredictionList(hippoRoot, tenantId, status, classTag, limit);
|
|
92
|
+
if (results.length === 0) {
|
|
93
|
+
console.log(classTag ? `No predictions in class "${classTag}".` : 'No predictions.');
|
|
147
94
|
return;
|
|
148
95
|
}
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
const
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
96
|
+
console.log(`Found ${results.length} predictions:\n`);
|
|
97
|
+
for (const p of results) {
|
|
98
|
+
const estPart = p.estimateValue !== null ? ` estimate=${p.estimateValue}${p.estimateUnit ? ` ${p.estimateUnit}` : ''}` : '';
|
|
99
|
+
const actPart = p.actualValue !== null ? ` actual=${p.actualValue}` : '';
|
|
100
|
+
const tgtPart = p.targetDate ? ` target=${p.targetDate}` : '';
|
|
101
|
+
console.log(`#${p.id} [${p.closureState}] class=${p.classTag}${estPart}${actPart}${tgtPart}`);
|
|
102
|
+
console.log(` ${p.claimText}`);
|
|
103
|
+
if (p.closureNote)
|
|
104
|
+
console.log(` note: ${p.closureNote}`);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
function predictShow(hippoRoot, tenantId, args) {
|
|
108
|
+
const idRaw = args[1];
|
|
109
|
+
if (!idRaw) {
|
|
110
|
+
printError('Usage: hippo predict show <id>');
|
|
111
|
+
process.exit(1);
|
|
112
|
+
}
|
|
113
|
+
const id = parseObjectId(idRaw, 'prediction');
|
|
114
|
+
const pred = predictionsModule.loadPredictionById(hippoRoot, tenantId, id);
|
|
115
|
+
if (!pred) {
|
|
116
|
+
printError(`Prediction ${id} not found.`);
|
|
117
|
+
process.exit(1);
|
|
118
|
+
}
|
|
119
|
+
console.log(`Prediction #${pred.id}`);
|
|
120
|
+
console.log(` class: ${pred.classTag}`);
|
|
121
|
+
console.log(` claim: ${pred.claimText}`);
|
|
122
|
+
console.log(` state: ${pred.closureState}`);
|
|
123
|
+
if (pred.estimateValue !== null)
|
|
124
|
+
console.log(` estimate: ${pred.estimateValue}${pred.estimateUnit ? ' ' + pred.estimateUnit : ''}`);
|
|
125
|
+
if (pred.targetDate)
|
|
126
|
+
console.log(` target: ${pred.targetDate}`);
|
|
127
|
+
if (pred.actualValue !== null)
|
|
128
|
+
console.log(` actual: ${pred.actualValue}`);
|
|
129
|
+
if (pred.closedAt)
|
|
130
|
+
console.log(` closed: ${pred.closedAt}`);
|
|
131
|
+
if (pred.closureNote)
|
|
132
|
+
console.log(` note: ${pred.closureNote}`);
|
|
133
|
+
if (pred.memoryId)
|
|
134
|
+
console.log(` memory: ${pred.memoryId}`);
|
|
135
|
+
console.log(` created: ${pred.createdAt}`);
|
|
136
|
+
}
|
|
137
|
+
function predictBaserate(hippoRoot, tenantId, flags) {
|
|
138
|
+
// Reference-class / planning-fallacy detector
|
|
139
|
+
const classTagRaw = flags['class'];
|
|
140
|
+
if (typeof classTagRaw !== 'string' || !classTagRaw.trim()) {
|
|
141
|
+
printError('Usage: hippo predict baserate --class <c>');
|
|
142
|
+
process.exit(1);
|
|
143
|
+
}
|
|
144
|
+
const baserate = predictionsModule.computePredictionBaserate(hippoRoot, tenantId, classTagRaw.trim());
|
|
145
|
+
if (baserate.nClosed === 0) {
|
|
146
|
+
console.log(`No closed predictions in class "${baserate.classTag}" yet.`);
|
|
147
|
+
console.log(` Create one with: hippo predict "<claim>" --class ${baserate.classTag} --estimate N`);
|
|
148
|
+
console.log(` Close it later: hippo predict close <id> --state closed --actual N`);
|
|
176
149
|
return;
|
|
177
150
|
}
|
|
151
|
+
console.log(baserate.summary);
|
|
152
|
+
console.log(` n_closed: ${baserate.nClosed}`);
|
|
153
|
+
console.log(` n_ratio_eligible: ${baserate.nRatioEligible}`);
|
|
154
|
+
if (baserate.meanEstimate !== null)
|
|
155
|
+
console.log(` mean_estimate: ${baserate.meanEstimate.toFixed(3)}`);
|
|
156
|
+
if (baserate.meanActual !== null)
|
|
157
|
+
console.log(` mean_actual: ${baserate.meanActual.toFixed(3)}`);
|
|
158
|
+
if (baserate.meanRatio !== null)
|
|
159
|
+
console.log(` mean_ratio: ${baserate.meanRatio.toFixed(3)}x`);
|
|
160
|
+
if (baserate.p50Ratio !== null)
|
|
161
|
+
console.log(` p50_ratio: ${baserate.p50Ratio.toFixed(3)}x`);
|
|
162
|
+
if (baserate.mae !== null)
|
|
163
|
+
console.log(` mae: ${baserate.mae.toFixed(3)}`);
|
|
164
|
+
}
|
|
165
|
+
export function cmdPredict(hippoRoot, args, flags) {
|
|
166
|
+
requireInit(hippoRoot);
|
|
167
|
+
const tenantId = resolveTenantId({});
|
|
168
|
+
const subcommand = args[0] ?? '';
|
|
169
|
+
if (subcommand === 'close')
|
|
170
|
+
return predictClose(hippoRoot, tenantId, args, flags);
|
|
171
|
+
if (subcommand === 'list')
|
|
172
|
+
return predictList(hippoRoot, tenantId, flags);
|
|
173
|
+
if (subcommand === 'show')
|
|
174
|
+
return predictShow(hippoRoot, tenantId, args);
|
|
175
|
+
if (subcommand === 'baserate')
|
|
176
|
+
return predictBaserate(hippoRoot, tenantId, flags);
|
|
178
177
|
// Default subcommand: create. args[0] is the claim text.
|
|
179
|
-
|
|
178
|
+
predictCreate(hippoRoot, tenantId, subcommand, flags);
|
|
179
|
+
}
|
|
180
|
+
function predictCreate(hippoRoot, tenantId, claimText, flags) {
|
|
180
181
|
if (!claimText) {
|
|
181
182
|
printError('Usage: hippo predict "<claim>" --class <c> [--estimate <v>] [--unit <u>] [--target <YYYY-MM-DD>]');
|
|
182
183
|
printError(' hippo predict close <id> --state <closed|closed-unknown> [--actual <v>] [--note "..."]');
|
|
@@ -211,96 +212,88 @@ export function cmdPredict(hippoRoot, args, flags) {
|
|
|
211
212
|
if (created.memoryId)
|
|
212
213
|
console.log(` memory: ${created.memoryId}`);
|
|
213
214
|
}
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
const
|
|
217
|
-
const
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
const limitRaw = flags['limit'];
|
|
222
|
-
const limit = limitRaw !== undefined ? parseInt(String(limitRaw), 10) : 100;
|
|
223
|
-
if (!Number.isFinite(limit) || limit <= 0) {
|
|
224
|
-
printError(`Invalid --limit: "${limitRaw}". Must be a positive integer.`);
|
|
225
|
-
process.exit(1);
|
|
226
|
-
}
|
|
227
|
-
let results;
|
|
228
|
-
if (status === 'all') {
|
|
229
|
-
results = decisionsModule.loadDecisions(hippoRoot, tenantId, { limit });
|
|
230
|
-
}
|
|
231
|
-
else {
|
|
232
|
-
if (!decisionsModule.VALID_DECISION_STATES.has(status)) {
|
|
233
|
-
printError(`Invalid --status: "${status}". Must be one of: active | superseded | closed | all.`);
|
|
234
|
-
process.exit(1);
|
|
235
|
-
}
|
|
236
|
-
results = decisionsModule.loadDecisions(hippoRoot, tenantId, {
|
|
237
|
-
status: status,
|
|
238
|
-
limit,
|
|
239
|
-
});
|
|
240
|
-
}
|
|
241
|
-
if (results.length === 0) {
|
|
242
|
-
console.log('No decisions.');
|
|
243
|
-
return;
|
|
244
|
-
}
|
|
245
|
-
console.log(`Found ${results.length} decisions:\n`);
|
|
246
|
-
for (const d of results) {
|
|
247
|
-
const supPart = d.supersededBy !== null ? ` superseded_by=#${d.supersededBy}` : '';
|
|
248
|
-
console.log(`#${d.id} [${d.status}]${supPart} memory=${d.memoryId ?? '-'}`);
|
|
249
|
-
console.log(` ${d.decisionText}`);
|
|
250
|
-
if (d.context)
|
|
251
|
-
console.log(` context: ${d.context}`);
|
|
252
|
-
}
|
|
253
|
-
return;
|
|
215
|
+
function decideList(hippoRoot, tenantId, flags) {
|
|
216
|
+
const statusRaw = flags['status'];
|
|
217
|
+
const status = typeof statusRaw === 'string' ? statusRaw.trim() : 'all';
|
|
218
|
+
const limit = parseListLimit(flags);
|
|
219
|
+
let results;
|
|
220
|
+
if (status === 'all') {
|
|
221
|
+
results = decisionsModule.loadDecisions(hippoRoot, tenantId, { limit });
|
|
254
222
|
}
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
printError('Usage: hippo decide get <id>');
|
|
259
|
-
process.exit(1);
|
|
260
|
-
}
|
|
261
|
-
const id = parseInt(String(idRaw), 10);
|
|
262
|
-
if (!Number.isFinite(id) || id <= 0) {
|
|
263
|
-
printError(`Invalid decision id: "${idRaw}"`);
|
|
264
|
-
process.exit(1);
|
|
265
|
-
}
|
|
266
|
-
const decision = decisionsModule.loadDecisionById(hippoRoot, tenantId, id);
|
|
267
|
-
if (!decision) {
|
|
268
|
-
printError(`Decision ${id} not found.`);
|
|
223
|
+
else {
|
|
224
|
+
if (!decisionsModule.VALID_DECISION_STATES.has(status)) {
|
|
225
|
+
printError(`Invalid --status: "${status}". Must be one of: active | superseded | closed | all.`);
|
|
269
226
|
process.exit(1);
|
|
270
227
|
}
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
console.log(` context: ${decision.context}`);
|
|
276
|
-
if (decision.supersededBy !== null)
|
|
277
|
-
console.log(` superseded_by: #${decision.supersededBy}`);
|
|
278
|
-
if (decision.supersededAt)
|
|
279
|
-
console.log(` superseded_at: ${decision.supersededAt}`);
|
|
280
|
-
if (decision.closedAt)
|
|
281
|
-
console.log(` closed_at: ${decision.closedAt}`);
|
|
282
|
-
if (decision.memoryId)
|
|
283
|
-
console.log(` memory: ${decision.memoryId}`);
|
|
284
|
-
console.log(` created: ${decision.createdAt}`);
|
|
285
|
-
return;
|
|
228
|
+
results = decisionsModule.loadDecisions(hippoRoot, tenantId, {
|
|
229
|
+
status: status,
|
|
230
|
+
limit,
|
|
231
|
+
});
|
|
286
232
|
}
|
|
287
|
-
if (
|
|
288
|
-
|
|
289
|
-
if (!idRaw) {
|
|
290
|
-
printError('Usage: hippo decide close <id>');
|
|
291
|
-
process.exit(1);
|
|
292
|
-
}
|
|
293
|
-
const id = parseInt(String(idRaw), 10);
|
|
294
|
-
if (!Number.isFinite(id) || id <= 0) {
|
|
295
|
-
printError(`Invalid decision id: "${idRaw}"`);
|
|
296
|
-
process.exit(1);
|
|
297
|
-
}
|
|
298
|
-
const closed = decisionsModule.closeDecision(hippoRoot, tenantId, id);
|
|
299
|
-
console.log(`Decision #${closed.id} closed.`);
|
|
233
|
+
if (results.length === 0) {
|
|
234
|
+
console.log('No decisions.');
|
|
300
235
|
return;
|
|
301
236
|
}
|
|
237
|
+
console.log(`Found ${results.length} decisions:\n`);
|
|
238
|
+
for (const d of results) {
|
|
239
|
+
const supPart = d.supersededBy !== null ? ` superseded_by=#${d.supersededBy}` : '';
|
|
240
|
+
console.log(`#${d.id} [${d.status}]${supPart} memory=${d.memoryId ?? '-'}`);
|
|
241
|
+
console.log(` ${d.decisionText}`);
|
|
242
|
+
if (d.context)
|
|
243
|
+
console.log(` context: ${d.context}`);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
function decideGet(hippoRoot, tenantId, args) {
|
|
247
|
+
const idRaw = args[1];
|
|
248
|
+
if (!idRaw) {
|
|
249
|
+
printError('Usage: hippo decide get <id>');
|
|
250
|
+
process.exit(1);
|
|
251
|
+
}
|
|
252
|
+
const id = parseObjectId(idRaw, 'decision');
|
|
253
|
+
const decision = decisionsModule.loadDecisionById(hippoRoot, tenantId, id);
|
|
254
|
+
if (!decision) {
|
|
255
|
+
printError(`Decision ${id} not found.`);
|
|
256
|
+
process.exit(1);
|
|
257
|
+
}
|
|
258
|
+
console.log(`Decision #${decision.id}`);
|
|
259
|
+
console.log(` status: ${decision.status}`);
|
|
260
|
+
console.log(` text: ${decision.decisionText}`);
|
|
261
|
+
if (decision.context)
|
|
262
|
+
console.log(` context: ${decision.context}`);
|
|
263
|
+
if (decision.supersededBy !== null)
|
|
264
|
+
console.log(` superseded_by: #${decision.supersededBy}`);
|
|
265
|
+
if (decision.supersededAt)
|
|
266
|
+
console.log(` superseded_at: ${decision.supersededAt}`);
|
|
267
|
+
if (decision.closedAt)
|
|
268
|
+
console.log(` closed_at: ${decision.closedAt}`);
|
|
269
|
+
if (decision.memoryId)
|
|
270
|
+
console.log(` memory: ${decision.memoryId}`);
|
|
271
|
+
console.log(` created: ${decision.createdAt}`);
|
|
272
|
+
}
|
|
273
|
+
function decideClose(hippoRoot, tenantId, args) {
|
|
274
|
+
const idRaw = args[1];
|
|
275
|
+
if (!idRaw) {
|
|
276
|
+
printError('Usage: hippo decide close <id>');
|
|
277
|
+
process.exit(1);
|
|
278
|
+
}
|
|
279
|
+
const id = parseObjectId(idRaw, 'decision');
|
|
280
|
+
const closed = decisionsModule.closeDecision(hippoRoot, tenantId, id);
|
|
281
|
+
console.log(`Decision #${closed.id} closed.`);
|
|
282
|
+
}
|
|
283
|
+
export function cmdDecide(hippoRoot, args, flags) {
|
|
284
|
+
requireInit(hippoRoot);
|
|
285
|
+
const tenantId = resolveTenantId({});
|
|
286
|
+
const subcommand = args[0] ?? '';
|
|
287
|
+
if (subcommand === 'list')
|
|
288
|
+
return decideList(hippoRoot, tenantId, flags);
|
|
289
|
+
if (subcommand === 'get')
|
|
290
|
+
return decideGet(hippoRoot, tenantId, args);
|
|
291
|
+
if (subcommand === 'close')
|
|
292
|
+
return decideClose(hippoRoot, tenantId, args);
|
|
302
293
|
// Default subcommand: create. args[0] is the decision text.
|
|
303
|
-
|
|
294
|
+
decideCreate(hippoRoot, tenantId, subcommand, flags);
|
|
295
|
+
}
|
|
296
|
+
function decideCreate(hippoRoot, tenantId, decisionText, flags) {
|
|
304
297
|
if (!decisionText) {
|
|
305
298
|
printError('Usage: hippo decide "<decision>" [--context "<why>"] [--supersedes <memory-id>]');
|
|
306
299
|
printError(' hippo decide list [--status active|superseded|closed|all] [--limit N]');
|
|
@@ -310,16 +303,15 @@ export function cmdDecide(hippoRoot, args, flags) {
|
|
|
310
303
|
}
|
|
311
304
|
const contextRaw = flags['context'];
|
|
312
305
|
const context = typeof contextRaw === 'string' && contextRaw ? contextRaw : undefined;
|
|
313
|
-
// A value-less `--supersedes`
|
|
314
|
-
//
|
|
315
|
-
// than silently creating a non-superseding decision (codex review 2026-05-28).
|
|
306
|
+
// A value-less `--supersedes` asks to supersede but gives no memory id: reject it rather
|
|
307
|
+
// than silently creating a non-superseding decision.
|
|
316
308
|
if (flags['supersedes'] === true) {
|
|
317
309
|
printError('--supersedes requires a memory id, e.g. hippo decide "<text>" --supersedes mem_abc123.');
|
|
318
310
|
process.exit(1);
|
|
319
311
|
}
|
|
320
312
|
const supersedesMemId = typeof flags['supersedes'] === 'string' ? flags['supersedes'] : null;
|
|
321
313
|
// Backward-compat: --supersedes takes a MEMORY id. Validate it exists and
|
|
322
|
-
// resolve it to the active decision row (if any).
|
|
314
|
+
// resolve it to the active decision row (if any). Commit the
|
|
323
315
|
// canonical table create+supersede FIRST (inside saveDecision's SAVEPOINT),
|
|
324
316
|
// weaken the old memory LAST (best-effort) so a memory-write failure cannot
|
|
325
317
|
// leave the memory stale without the table reflecting the supersession.
|
|
@@ -345,11 +337,8 @@ export function cmdDecide(hippoRoot, args, flags) {
|
|
|
345
337
|
// 'superseded' tag. Preserves the exact pre-promotion behavior for the memory
|
|
346
338
|
// mirror; the canonical table supersession already committed above.
|
|
347
339
|
if (oldEntry) {
|
|
348
|
-
// Best-effort: saveDecision already committed
|
|
349
|
-
// decision
|
|
350
|
-
// throws, do NOT fail the command — a retry would find no active decision
|
|
351
|
-
// for the old memory and create a duplicate active successor. Warn instead
|
|
352
|
-
// (codex review 2026-05-28).
|
|
340
|
+
// Best-effort: saveDecision already committed. Failing here would make a retry find no active
|
|
341
|
+
// decision for the old memory and create a duplicate active successor, so warn instead.
|
|
353
342
|
try {
|
|
354
343
|
oldEntry.half_life_days = Math.max(1, Math.floor(oldEntry.half_life_days / 2));
|
|
355
344
|
oldEntry.confidence = 'stale';
|
|
@@ -374,7 +363,6 @@ export function cmdDecide(hippoRoot, args, flags) {
|
|
|
374
363
|
// Strict positive-integer parse for incident id args. parseInt() alone accepts
|
|
375
364
|
// trailing junk ("1abc" -> 1), which would let a mutating subcommand (close/
|
|
376
365
|
// resolve) silently hit the wrong row; require the whole arg to be digits.
|
|
377
|
-
// (codex P2, 2026-05-29.)
|
|
378
366
|
function parsePositiveIncidentId(idRaw) {
|
|
379
367
|
const s = String(idRaw ?? '').trim();
|
|
380
368
|
const id = parseInt(s, 10);
|
|
@@ -384,109 +372,110 @@ function parsePositiveIncidentId(idRaw) {
|
|
|
384
372
|
}
|
|
385
373
|
return id;
|
|
386
374
|
}
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
const
|
|
390
|
-
const
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
const limitRaw = flags['limit'];
|
|
395
|
-
const limit = limitRaw !== undefined ? parseInt(String(limitRaw), 10) : 100;
|
|
396
|
-
if (!Number.isFinite(limit) || limit <= 0) {
|
|
397
|
-
printError(`Invalid --limit: "${limitRaw}". Must be a positive integer.`);
|
|
398
|
-
process.exit(1);
|
|
399
|
-
}
|
|
400
|
-
let results;
|
|
401
|
-
if (status === 'all') {
|
|
402
|
-
results = incidentsModule.loadIncidents(hippoRoot, tenantId, { limit });
|
|
403
|
-
}
|
|
404
|
-
else {
|
|
405
|
-
if (!incidentsModule.VALID_INCIDENT_STATES.has(status)) {
|
|
406
|
-
printError(`Invalid --status: "${status}". Must be one of: open | resolved | closed | all.`);
|
|
407
|
-
process.exit(1);
|
|
408
|
-
}
|
|
409
|
-
results = incidentsModule.loadIncidents(hippoRoot, tenantId, {
|
|
410
|
-
status: status,
|
|
411
|
-
limit,
|
|
412
|
-
});
|
|
413
|
-
}
|
|
414
|
-
if (results.length === 0) {
|
|
415
|
-
console.log('No incidents.');
|
|
416
|
-
return;
|
|
417
|
-
}
|
|
418
|
-
console.log(`Found ${results.length} incidents:\n`);
|
|
419
|
-
for (const inc of results) {
|
|
420
|
-
const linkPart = inc.linkedMemoryIds.length > 0 ? ` links=${inc.linkedMemoryIds.length}` : '';
|
|
421
|
-
console.log(`#${inc.id} [${inc.status}]${linkPart} memory=${inc.memoryId ?? '-'}`);
|
|
422
|
-
console.log(` ${inc.incidentText}`);
|
|
423
|
-
if (inc.context)
|
|
424
|
-
console.log(` context: ${inc.context}`);
|
|
425
|
-
}
|
|
426
|
-
return;
|
|
375
|
+
function incidentList(hippoRoot, tenantId, flags) {
|
|
376
|
+
const statusRaw = flags['status'];
|
|
377
|
+
const status = typeof statusRaw === 'string' ? statusRaw.trim() : 'all';
|
|
378
|
+
const limit = parseListLimit(flags);
|
|
379
|
+
let results;
|
|
380
|
+
if (status === 'all') {
|
|
381
|
+
results = incidentsModule.loadIncidents(hippoRoot, tenantId, { limit });
|
|
427
382
|
}
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
printError('Usage: hippo incident get <id>');
|
|
383
|
+
else {
|
|
384
|
+
if (!incidentsModule.VALID_INCIDENT_STATES.has(status)) {
|
|
385
|
+
printError(`Invalid --status: "${status}". Must be one of: open | resolved | closed | all.`);
|
|
432
386
|
process.exit(1);
|
|
433
387
|
}
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
process.exit(1);
|
|
439
|
-
}
|
|
440
|
-
console.log(`Incident #${incident.id}`);
|
|
441
|
-
console.log(` status: ${incident.status}`);
|
|
442
|
-
console.log(` text: ${incident.incidentText}`);
|
|
443
|
-
if (incident.context)
|
|
444
|
-
console.log(` context: ${incident.context}`);
|
|
445
|
-
if (incident.resolutionText)
|
|
446
|
-
console.log(` resolution: ${incident.resolutionText}`);
|
|
447
|
-
if (incident.resolvedAt)
|
|
448
|
-
console.log(` resolved_at: ${incident.resolvedAt}`);
|
|
449
|
-
if (incident.closedAt)
|
|
450
|
-
console.log(` closed_at: ${incident.closedAt}`);
|
|
451
|
-
if (incident.linkedMemoryIds.length > 0) {
|
|
452
|
-
console.log(` linked memories: ${incident.linkedMemoryIds.join(', ')}`);
|
|
453
|
-
}
|
|
454
|
-
if (incident.memoryId)
|
|
455
|
-
console.log(` memory: ${incident.memoryId}`);
|
|
456
|
-
console.log(` created: ${incident.createdAt}`);
|
|
457
|
-
return;
|
|
388
|
+
results = incidentsModule.loadIncidents(hippoRoot, tenantId, {
|
|
389
|
+
status: status,
|
|
390
|
+
limit,
|
|
391
|
+
});
|
|
458
392
|
}
|
|
459
|
-
if (
|
|
460
|
-
|
|
461
|
-
if (!idRaw) {
|
|
462
|
-
printError('Usage: hippo incident resolve <id> --resolution "<text>"');
|
|
463
|
-
process.exit(1);
|
|
464
|
-
}
|
|
465
|
-
const id = parsePositiveIncidentId(idRaw);
|
|
466
|
-
const resolutionRaw = flags['resolution'];
|
|
467
|
-
if (typeof resolutionRaw !== 'string' || !resolutionRaw.trim()) {
|
|
468
|
-
printError('--resolution requires a non-empty value, e.g. hippo incident resolve <id> --resolution "root cause fixed".');
|
|
469
|
-
process.exit(1);
|
|
470
|
-
}
|
|
471
|
-
const resolved = incidentsModule.resolveIncident(hippoRoot, tenantId, id, resolutionRaw);
|
|
472
|
-
console.log(`Incident #${resolved.id} resolved.`);
|
|
393
|
+
if (results.length === 0) {
|
|
394
|
+
console.log('No incidents.');
|
|
473
395
|
return;
|
|
474
396
|
}
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
const closed = incidentsModule.closeIncident(hippoRoot, tenantId, id);
|
|
483
|
-
console.log(`Incident #${closed.id} closed.`);
|
|
484
|
-
return;
|
|
397
|
+
console.log(`Found ${results.length} incidents:\n`);
|
|
398
|
+
for (const inc of results) {
|
|
399
|
+
const linkPart = inc.linkedMemoryIds.length > 0 ? ` links=${inc.linkedMemoryIds.length}` : '';
|
|
400
|
+
console.log(`#${inc.id} [${inc.status}]${linkPart} memory=${inc.memoryId ?? '-'}`);
|
|
401
|
+
console.log(` ${inc.incidentText}`);
|
|
402
|
+
if (inc.context)
|
|
403
|
+
console.log(` context: ${inc.context}`);
|
|
485
404
|
}
|
|
405
|
+
}
|
|
406
|
+
function incidentGet(hippoRoot, tenantId, args) {
|
|
407
|
+
const idRaw = args[1];
|
|
408
|
+
if (!idRaw) {
|
|
409
|
+
printError('Usage: hippo incident get <id>');
|
|
410
|
+
process.exit(1);
|
|
411
|
+
}
|
|
412
|
+
const id = parsePositiveIncidentId(idRaw);
|
|
413
|
+
const incident = incidentsModule.loadIncidentById(hippoRoot, tenantId, id);
|
|
414
|
+
if (!incident) {
|
|
415
|
+
printError(`Incident ${id} not found.`);
|
|
416
|
+
process.exit(1);
|
|
417
|
+
}
|
|
418
|
+
console.log(`Incident #${incident.id}`);
|
|
419
|
+
console.log(` status: ${incident.status}`);
|
|
420
|
+
console.log(` text: ${incident.incidentText}`);
|
|
421
|
+
if (incident.context)
|
|
422
|
+
console.log(` context: ${incident.context}`);
|
|
423
|
+
if (incident.resolutionText)
|
|
424
|
+
console.log(` resolution: ${incident.resolutionText}`);
|
|
425
|
+
if (incident.resolvedAt)
|
|
426
|
+
console.log(` resolved_at: ${incident.resolvedAt}`);
|
|
427
|
+
if (incident.closedAt)
|
|
428
|
+
console.log(` closed_at: ${incident.closedAt}`);
|
|
429
|
+
if (incident.linkedMemoryIds.length > 0) {
|
|
430
|
+
console.log(` linked memories: ${incident.linkedMemoryIds.join(', ')}`);
|
|
431
|
+
}
|
|
432
|
+
if (incident.memoryId)
|
|
433
|
+
console.log(` memory: ${incident.memoryId}`);
|
|
434
|
+
console.log(` created: ${incident.createdAt}`);
|
|
435
|
+
}
|
|
436
|
+
function incidentResolve(hippoRoot, tenantId, args, flags) {
|
|
437
|
+
const idRaw = args[1];
|
|
438
|
+
if (!idRaw) {
|
|
439
|
+
printError('Usage: hippo incident resolve <id> --resolution "<text>"');
|
|
440
|
+
process.exit(1);
|
|
441
|
+
}
|
|
442
|
+
const id = parsePositiveIncidentId(idRaw);
|
|
443
|
+
const resolutionRaw = flags['resolution'];
|
|
444
|
+
if (typeof resolutionRaw !== 'string' || !resolutionRaw.trim()) {
|
|
445
|
+
printError('--resolution requires a non-empty value, e.g. hippo incident resolve <id> --resolution "root cause fixed".');
|
|
446
|
+
process.exit(1);
|
|
447
|
+
}
|
|
448
|
+
const resolved = incidentsModule.resolveIncident(hippoRoot, tenantId, id, resolutionRaw);
|
|
449
|
+
console.log(`Incident #${resolved.id} resolved.`);
|
|
450
|
+
}
|
|
451
|
+
function incidentClose(hippoRoot, tenantId, args) {
|
|
452
|
+
const idRaw = args[1];
|
|
453
|
+
if (!idRaw) {
|
|
454
|
+
printError('Usage: hippo incident close <id>');
|
|
455
|
+
process.exit(1);
|
|
456
|
+
}
|
|
457
|
+
const id = parsePositiveIncidentId(idRaw);
|
|
458
|
+
const closed = incidentsModule.closeIncident(hippoRoot, tenantId, id);
|
|
459
|
+
console.log(`Incident #${closed.id} closed.`);
|
|
460
|
+
}
|
|
461
|
+
export function cmdIncident(hippoRoot, args, flags) {
|
|
462
|
+
requireInit(hippoRoot);
|
|
463
|
+
const tenantId = resolveTenantId({});
|
|
464
|
+
const subcommand = args[0] ?? '';
|
|
465
|
+
if (subcommand === 'list')
|
|
466
|
+
return incidentList(hippoRoot, tenantId, flags);
|
|
467
|
+
if (subcommand === 'get')
|
|
468
|
+
return incidentGet(hippoRoot, tenantId, args);
|
|
469
|
+
if (subcommand === 'resolve')
|
|
470
|
+
return incidentResolve(hippoRoot, tenantId, args, flags);
|
|
471
|
+
if (subcommand === 'close')
|
|
472
|
+
return incidentClose(hippoRoot, tenantId, args);
|
|
486
473
|
// Default subcommand: open (create). Accept both the documented
|
|
487
474
|
// `incident open "<text>"` form and the bare `incident "<text>"` form: for the
|
|
488
475
|
// `open` keyword the text is args[1], otherwise args[0] IS the text.
|
|
489
|
-
|
|
476
|
+
incidentCreate(hippoRoot, tenantId, subcommand === 'open' ? (args[1] ?? '') : subcommand, flags);
|
|
477
|
+
}
|
|
478
|
+
function incidentCreate(hippoRoot, tenantId, incidentText, flags) {
|
|
490
479
|
if (!incidentText) {
|
|
491
480
|
printError('Usage: hippo incident "<incident>" [--context "<details>"] [--link <memory-id>]...');
|
|
492
481
|
printError(' hippo incident list [--status open|resolved|closed|all] [--limit N]');
|