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.
Files changed (377) hide show
  1. package/README.md +37 -53
  2. package/dist/agent-memories/apply.d.ts +3 -1
  3. package/dist/agent-memories/apply.js +23 -1
  4. package/dist/agent-memories/claude-code.d.ts +4 -1
  5. package/dist/agent-memories/claude-code.js +53 -9
  6. package/dist/agent-memories/report.d.ts +1 -0
  7. package/dist/agent-memories/report.js +2 -2
  8. package/dist/agent-memories/sync.d.ts +3 -3
  9. package/dist/agent-memories/sync.js +46 -24
  10. package/dist/agent-memories/types.d.ts +0 -2
  11. package/dist/ambient-store.d.ts +4 -4
  12. package/dist/ambient-store.js +4 -3
  13. package/dist/api/assemble.d.ts +7 -10
  14. package/dist/api/assemble.js +62 -66
  15. package/dist/api/audit.d.ts +2 -2
  16. package/dist/api/audit.js +2 -2
  17. package/dist/api/auth.d.ts +6 -6
  18. package/dist/api/auth.js +5 -7
  19. package/dist/api/context-select.d.ts +50 -0
  20. package/dist/api/context-select.js +342 -0
  21. package/dist/api/context-types.d.ts +12 -17
  22. package/dist/api/context.d.ts +5 -4
  23. package/dist/api/context.js +208 -535
  24. package/dist/api/dormant.js +1 -1
  25. package/dist/api/drill-down.d.ts +4 -4
  26. package/dist/api/drill-down.js +56 -41
  27. package/dist/api/outcome.d.ts +8 -13
  28. package/dist/api/outcome.js +13 -17
  29. package/dist/api/promote.d.ts +5 -8
  30. package/dist/api/promote.js +55 -66
  31. package/dist/api/quarantine.js +3 -2
  32. package/dist/api/recall-types.d.ts +43 -55
  33. package/dist/api/recall.d.ts +3 -3
  34. package/dist/api/recall.js +310 -456
  35. package/dist/api/remember.d.ts +2 -2
  36. package/dist/api/sleep.d.ts +7 -35
  37. package/dist/api/sleep.js +206 -220
  38. package/dist/api/tokens.d.ts +2 -2
  39. package/dist/api/tokens.js +2 -2
  40. package/dist/api/types.d.ts +6 -10
  41. package/dist/api/types.js +2 -4
  42. package/dist/audit-prune.d.ts +4 -6
  43. package/dist/audit-prune.js +3 -5
  44. package/dist/audit.js +11 -33
  45. package/dist/auth.d.ts +8 -9
  46. package/dist/auth.js +5 -7
  47. package/dist/autolearn.d.ts +1 -1
  48. package/dist/autolearn.js +1 -1
  49. package/dist/availability.js +3 -5
  50. package/dist/capture/command.d.ts +5 -4
  51. package/dist/capture/command.js +8 -22
  52. package/dist/capture/compact.d.ts +2 -2
  53. package/dist/capture/compact.js +11 -15
  54. package/dist/capture/extract.js +37 -115
  55. package/dist/capture-error.d.ts +1 -1
  56. package/dist/capture-error.js +1 -1
  57. package/dist/churn-git.d.ts +1 -1
  58. package/dist/churn-git.js +1 -1
  59. package/dist/cli/audit.js +3 -4
  60. package/dist/cli/auth.js +3 -6
  61. package/dist/cli/briefs.js +324 -306
  62. package/dist/cli/context.js +44 -34
  63. package/dist/cli/continuity.js +283 -271
  64. package/dist/cli/curate.d.ts +1 -1
  65. package/dist/cli/curate.js +43 -58
  66. package/dist/cli/dag.js +5 -9
  67. package/dist/cli/decisions.js +334 -345
  68. package/dist/cli/explain.js +68 -61
  69. package/dist/cli/goals.js +1 -1
  70. package/dist/cli/init.js +1 -1
  71. package/dist/cli/maintenance.js +62 -51
  72. package/dist/cli/playbooks.js +391 -379
  73. package/dist/cli/projects.js +11 -6
  74. package/dist/cli/recall.js +30 -44
  75. package/dist/cli/remember.js +118 -87
  76. package/dist/cli/session-hooks.js +106 -115
  77. package/dist/cli/setup.d.ts +1 -1
  78. package/dist/cli/setup.js +267 -250
  79. package/dist/cli/shared.js +3 -3
  80. package/dist/cli/slack.js +1 -1
  81. package/dist/cli/sleep.js +27 -1
  82. package/dist/cli/status.d.ts +4 -4
  83. package/dist/cli/status.js +80 -76
  84. package/dist/cli/transfer.js +88 -107
  85. package/dist/cli/usage.js +9 -6
  86. package/dist/cli.d.ts +1 -1
  87. package/dist/cli.js +4 -9
  88. package/dist/compaction-record.d.ts +2 -2
  89. package/dist/compaction-record.js +89 -68
  90. package/dist/compare.d.ts +11 -16
  91. package/dist/compare.js +11 -16
  92. package/dist/config.d.ts +19 -18
  93. package/dist/config.js +90 -67
  94. package/dist/connectors/github/backfill.d.ts +2 -2
  95. package/dist/connectors/github/backfill.js +8 -15
  96. package/dist/connectors/github/cli-impl.js +3 -8
  97. package/dist/connectors/github/deletion.d.ts +5 -12
  98. package/dist/connectors/github/deletion.js +5 -12
  99. package/dist/connectors/github/dlq.d.ts +6 -9
  100. package/dist/connectors/github/dlq.js +2 -3
  101. package/dist/connectors/github/ingest.d.ts +5 -7
  102. package/dist/connectors/github/ingest.js +8 -12
  103. package/dist/connectors/github/octokit-client.d.ts +3 -5
  104. package/dist/connectors/github/octokit-client.js +5 -6
  105. package/dist/connectors/github/signature.d.ts +9 -39
  106. package/dist/connectors/github/signature.js +9 -39
  107. package/dist/connectors/github/tenant-routing.d.ts +1 -1
  108. package/dist/connectors/github/tenant-routing.js +1 -1
  109. package/dist/connectors/github/transform.js +2 -2
  110. package/dist/connectors/github/types.d.ts +2 -10
  111. package/dist/connectors/github/types.js +1 -3
  112. package/dist/connectors/slack/deletion.d.ts +3 -8
  113. package/dist/connectors/slack/deletion.js +3 -8
  114. package/dist/connectors/slack/dlq.d.ts +1 -1
  115. package/dist/connectors/slack/ingest.d.ts +1 -1
  116. package/dist/connectors/slack/ingest.js +7 -16
  117. package/dist/connectors/slack/signature.d.ts +1 -1
  118. package/dist/connectors/slack/tenant-routing.d.ts +3 -5
  119. package/dist/connectors/slack/tenant-routing.js +3 -5
  120. package/dist/connectors/slack/transform.d.ts +5 -6
  121. package/dist/connectors/slack/transform.js +5 -6
  122. package/dist/connectors/slack/types.d.ts +2 -6
  123. package/dist/connectors/slack/types.js +1 -3
  124. package/dist/connectors/slack/web-client.js +10 -3
  125. package/dist/connectors/slack/workspaces.d.ts +3 -5
  126. package/dist/connectors/slack/workspaces.js +3 -5
  127. package/dist/consolidate/conflicts.js +3 -14
  128. package/dist/consolidate/decay.js +9 -29
  129. package/dist/consolidate/llm-passes.js +4 -5
  130. package/dist/consolidate/merge.js +8 -23
  131. package/dist/consolidate/run.d.ts +1 -8
  132. package/dist/consolidate/run.js +3 -25
  133. package/dist/consolidate/sleep.js +5 -17
  134. package/dist/consolidate/traces.js +9 -21
  135. package/dist/customer-notes.d.ts +5 -7
  136. package/dist/customer-notes.js +82 -76
  137. package/dist/dag.d.ts +10 -21
  138. package/dist/dag.js +189 -203
  139. package/dist/db/continuity.js +2 -2
  140. package/dist/db/migrations/v14.js +1 -1
  141. package/dist/db/migrations/v15.js +1 -2
  142. package/dist/db/migrations/v16.js +3 -4
  143. package/dist/db/migrations/v17.js +2 -3
  144. package/dist/db/migrations/v19.js +1 -1
  145. package/dist/db/migrations/v20.js +1 -1
  146. package/dist/db/migrations/v21.js +2 -6
  147. package/dist/db/migrations/v22.js +2 -4
  148. package/dist/db/migrations/v23.js +1 -1
  149. package/dist/db/migrations/v24.js +4 -6
  150. package/dist/db/migrations/v25.js +2 -3
  151. package/dist/db/migrations/v26.js +3 -3
  152. package/dist/db/migrations/v27.js +2 -10
  153. package/dist/db/migrations/v28.js +5 -8
  154. package/dist/db/migrations/v29.js +3 -4
  155. package/dist/db/migrations/v30.js +2 -2
  156. package/dist/db/migrations/v31.js +1 -1
  157. package/dist/db/migrations/v32.js +1 -1
  158. package/dist/db/migrations/v33.js +3 -3
  159. package/dist/db/migrations/v34.js +1 -1
  160. package/dist/db/migrations/v35.js +3 -4
  161. package/dist/db/migrations/v36.js +3 -4
  162. package/dist/db/migrations/v37.js +5 -5
  163. package/dist/db/migrations/v38.js +7 -8
  164. package/dist/db/migrations/v39.js +1 -1
  165. package/dist/db/migrations/v40.js +4 -16
  166. package/dist/db/migrations/v41.js +3 -4
  167. package/dist/db/migrations/v42.js +3 -4
  168. package/dist/db/migrations/v45.js +1 -1
  169. package/dist/db/migrations/v46.js +1 -1
  170. package/dist/db/migrations/v47.js +1 -1
  171. package/dist/db/migrations/v48.js +1 -1
  172. package/dist/decisions.d.ts +2 -2
  173. package/dist/decisions.js +97 -80
  174. package/dist/dedupe.js +86 -61
  175. package/dist/delivery-recorder.js +154 -135
  176. package/dist/doctor.js +129 -110
  177. package/dist/dormant.js +1 -4
  178. package/dist/embedding-provider.d.ts +4 -8
  179. package/dist/embedding-provider.js +4 -8
  180. package/dist/embeddings.js +55 -47
  181. package/dist/env.d.ts +1 -1
  182. package/dist/env.js +12 -12
  183. package/dist/escape.d.ts +5 -0
  184. package/dist/escape.js +10 -0
  185. package/dist/eval-stats.d.ts +1 -2
  186. package/dist/eval-stats.js +1 -2
  187. package/dist/eval-suite.js +27 -21
  188. package/dist/extract.js +4 -9
  189. package/dist/failure-log.d.ts +3 -3
  190. package/dist/failure-log.js +1 -1
  191. package/dist/forward-claim-detector.d.ts +2 -4
  192. package/dist/forward-claim-detector.js +6 -11
  193. package/dist/goals.d.ts +3 -3
  194. package/dist/goals.js +103 -91
  195. package/dist/graph/read.d.ts +2 -2
  196. package/dist/graph/read.js +5 -6
  197. package/dist/graph/types.d.ts +8 -8
  198. package/dist/graph/write.d.ts +7 -14
  199. package/dist/graph/write.js +16 -23
  200. package/dist/graph-extract.d.ts +7 -8
  201. package/dist/graph-extract.js +62 -72
  202. package/dist/graph-recall.d.ts +2 -2
  203. package/dist/graph-recall.js +55 -49
  204. package/dist/graph-stream.d.ts +5 -6
  205. package/dist/graph-stream.js +66 -57
  206. package/dist/graph-view.d.ts +2 -2
  207. package/dist/graph-view.js +7 -7
  208. package/dist/half-life-migration.d.ts +1 -2
  209. package/dist/half-life-migration.js +2 -3
  210. package/dist/hooks/codex-session.js +1 -1
  211. package/dist/hooks/codex-wrapper.d.ts +1 -1
  212. package/dist/hooks/codex-wrapper.js +3 -2
  213. package/dist/hooks/json-hooks.d.ts +2 -2
  214. package/dist/hooks/json-hooks.js +5 -4
  215. package/dist/hooks/opencode.d.ts +1 -1
  216. package/dist/hooks/opencode.js +5 -4
  217. package/dist/hooks/shared.d.ts +3 -7
  218. package/dist/hooks/shared.js +1 -8
  219. package/dist/http-util.d.ts +2 -3
  220. package/dist/http-util.js +3 -0
  221. package/dist/importers/core.d.ts +2 -9
  222. package/dist/importers/core.js +15 -30
  223. package/dist/importers/sources.js +2 -1
  224. package/dist/importers/vault.js +2 -20
  225. package/dist/incidents.d.ts +1 -1
  226. package/dist/incidents.js +46 -39
  227. package/dist/instruction-detect.d.ts +1 -1
  228. package/dist/instruction-detect.js +1 -1
  229. package/dist/invalidation.d.ts +3 -0
  230. package/dist/invalidation.js +160 -114
  231. package/dist/json.d.ts +5 -0
  232. package/dist/json.js +4 -0
  233. package/dist/judgment.js +1 -2
  234. package/dist/local-embedding.js +1 -1
  235. package/dist/mcp/admin-tools.js +7 -17
  236. package/dist/mcp/format.js +1 -1
  237. package/dist/mcp/framing.js +3 -6
  238. package/dist/mcp/protocol.d.ts +2 -5
  239. package/dist/mcp/protocol.js +1 -3
  240. package/dist/mcp/recall-tools.js +12 -15
  241. package/dist/mcp/request.js +4 -3
  242. package/dist/mcp/session-state.js +2 -3
  243. package/dist/mcp/stdio.js +2 -1
  244. package/dist/mcp/tools.js +9 -6
  245. package/dist/memory-value-weights.d.ts +5 -8
  246. package/dist/memory-value-weights.js +5 -8
  247. package/dist/memory-value.d.ts +13 -13
  248. package/dist/memory-value.js +26 -37
  249. package/dist/memory.d.ts +20 -22
  250. package/dist/memory.js +24 -48
  251. package/dist/multihop.d.ts +1 -1
  252. package/dist/multihop.js +3 -2
  253. package/dist/owner-validation.d.ts +4 -5
  254. package/dist/owner-validation.js +4 -5
  255. package/dist/physics.d.ts +4 -4
  256. package/dist/physics.js +7 -9
  257. package/dist/policies.d.ts +9 -10
  258. package/dist/policies.js +96 -81
  259. package/dist/postinstall.js +3 -6
  260. package/dist/predictions/planning-fallacy.d.ts +9 -14
  261. package/dist/predictions/planning-fallacy.js +10 -16
  262. package/dist/predictions/store.d.ts +15 -23
  263. package/dist/predictions/store.js +36 -33
  264. package/dist/processes.d.ts +2 -7
  265. package/dist/processes.js +88 -72
  266. package/dist/project-briefs.d.ts +2 -3
  267. package/dist/project-briefs.js +141 -118
  268. package/dist/project-identity.d.ts +22 -9
  269. package/dist/project-identity.js +47 -12
  270. package/dist/project-merge.d.ts +28 -5
  271. package/dist/project-merge.js +213 -46
  272. package/dist/project-remote.d.ts +12 -0
  273. package/dist/project-remote.js +138 -0
  274. package/dist/prompt-recall.js +1 -2
  275. package/dist/rate-limit.d.ts +1 -1
  276. package/dist/rate-limit.js +1 -1
  277. package/dist/raw-archive.d.ts +9 -0
  278. package/dist/raw-archive.js +70 -53
  279. package/dist/recall-history.d.ts +19 -20
  280. package/dist/recall-history.js +24 -42
  281. package/dist/recall-pipeline.js +4 -28
  282. package/dist/recall-scope.d.ts +7 -8
  283. package/dist/recall-scope.js +7 -8
  284. package/dist/recall-trace.d.ts +5 -9
  285. package/dist/recall-trace.js +6 -10
  286. package/dist/refine-llm.d.ts +1 -1
  287. package/dist/refine-llm.js +2 -2
  288. package/dist/reject-flow.d.ts +3 -4
  289. package/dist/reject-flow.js +122 -117
  290. package/dist/rejection.d.ts +5 -6
  291. package/dist/rejection.js +7 -15
  292. package/dist/rerankers/clef.d.ts +1 -1
  293. package/dist/rerankers/jev.d.ts +1 -2
  294. package/dist/rerankers/jev.js +4 -5
  295. package/dist/rerankers/llm.d.ts +1 -2
  296. package/dist/rerankers/llm.js +1 -2
  297. package/dist/rerankers/types.d.ts +1 -2
  298. package/dist/rrf.d.ts +2 -2
  299. package/dist/rrf.js +2 -2
  300. package/dist/search/bm25-search.d.ts +1 -1
  301. package/dist/search/bm25-search.js +2 -1
  302. package/dist/search/boosts.js +2 -1
  303. package/dist/search/hybrid.d.ts +1 -1
  304. package/dist/search/hybrid.js +2 -1
  305. package/dist/search/physics-search.d.ts +1 -1
  306. package/dist/search/physics-search.js +2 -1
  307. package/dist/search/types.d.ts +2 -0
  308. package/dist/search/types.js +3 -1
  309. package/dist/secret-detect.d.ts +4 -5
  310. package/dist/secret-detect.js +6 -10
  311. package/dist/server/auth.js +5 -5
  312. package/dist/server/client-ip.js +1 -1
  313. package/dist/server/cursor.js +2 -1
  314. package/dist/server/mcp-http.js +4 -4
  315. package/dist/server/request.d.ts +3 -6
  316. package/dist/server/request.js +6 -7
  317. package/dist/server/routes/admin.js +5 -4
  318. package/dist/server/routes/customer-notes.js +6 -5
  319. package/dist/server/routes/decisions.js +4 -3
  320. package/dist/server/routes/incidents.js +7 -5
  321. package/dist/server/routes/memories.js +7 -7
  322. package/dist/server/routes/policies.js +3 -2
  323. package/dist/server/routes/predictions.js +12 -15
  324. package/dist/server/routes/processes.js +3 -2
  325. package/dist/server/routes/project-briefs.js +8 -7
  326. package/dist/server/routes/recall.js +95 -93
  327. package/dist/server/routes/skills.js +6 -5
  328. package/dist/server/types.d.ts +1 -1
  329. package/dist/server/validation.d.ts +1 -2
  330. package/dist/server/validation.js +7 -14
  331. package/dist/server-detect.js +72 -58
  332. package/dist/server.d.ts +2 -2
  333. package/dist/server.js +131 -117
  334. package/dist/shared.d.ts +26 -17
  335. package/dist/shared.js +102 -104
  336. package/dist/skills.d.ts +3 -3
  337. package/dist/skills.js +88 -72
  338. package/dist/store/audit-event.d.ts +2 -2
  339. package/dist/store/audit-event.js +1 -1
  340. package/dist/store/candidates.d.ts +2 -2
  341. package/dist/store/candidates.js +4 -3
  342. package/dist/store/conflicts.js +30 -22
  343. package/dist/store/delete-and-batch.d.ts +11 -14
  344. package/dist/store/delete-and-batch.js +40 -91
  345. package/dist/store/entry-reads.d.ts +17 -25
  346. package/dist/store/entry-reads.js +59 -40
  347. package/dist/store/entry-row.d.ts +6 -24
  348. package/dist/store/entry-row.js +6 -24
  349. package/dist/store/entry-writes.d.ts +6 -7
  350. package/dist/store/entry-writes.js +13 -11
  351. package/dist/store/handoffs.d.ts +1 -1
  352. package/dist/store/handoffs.js +7 -10
  353. package/dist/store/index-and-stats.d.ts +2 -6
  354. package/dist/store/index-and-stats.js +4 -10
  355. package/dist/store/mirrors.d.ts +6 -19
  356. package/dist/store/mirrors.js +14 -39
  357. package/dist/store/open.js +9 -31
  358. package/dist/store/rows.d.ts +5 -11
  359. package/dist/store/rows.js +6 -11
  360. package/dist/store/search-rows.d.ts +17 -34
  361. package/dist/store/search-rows.js +34 -56
  362. package/dist/store/sessions.d.ts +4 -5
  363. package/dist/store/sessions.js +5 -6
  364. package/dist/store/summaries.d.ts +13 -17
  365. package/dist/store/summaries.js +26 -70
  366. package/dist/support-bundle.js +4 -8
  367. package/dist/tenant.d.ts +1 -5
  368. package/dist/token-ledger.d.ts +1 -1
  369. package/dist/token-ledger.js +3 -5
  370. package/dist/trace.js +1 -3
  371. package/dist/version.d.ts +1 -1
  372. package/dist/version.js +1 -1
  373. package/dist/working-memory.d.ts +1 -1
  374. package/extensions/openclaw-plugin/openclaw.plugin.json +1 -1
  375. package/extensions/openclaw-plugin/package.json +1 -1
  376. package/openclaw.plugin.json +1 -1
  377. package/package.json +1 -1
@@ -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
- // E2 prediction first-class object (v0.31)
13
- // docs/plans/2026-05-26-e2-prediction-object.md
30
+ // Prediction first-class object
14
31
  // ---------------------------------------------------------------------------
15
- export function cmdPredict(hippoRoot, args, flags) {
16
- requireInit(hippoRoot);
17
- const tenantId = resolveTenantId({});
18
- const subcommand = args[0] ?? '';
19
- if (subcommand === 'close') {
20
- const idRaw = args[1];
21
- if (!idRaw) {
22
- printError('Usage: hippo predict close <id> --state <closed|closed-unknown> [--actual <v>] [--note "..."]');
23
- process.exit(1);
24
- }
25
- const id = parseInt(String(idRaw), 10);
26
- if (!Number.isFinite(id) || id <= 0) {
27
- printError(`Invalid prediction id: "${idRaw}"`);
28
- process.exit(1);
29
- }
30
- const stateRaw = typeof flags['state'] === 'string' ? flags['state'].trim() : '';
31
- if (!predictionsModule.VALID_CLOSURE_STATES.has(stateRaw) || stateRaw === 'open') {
32
- printError(`Invalid --state: "${stateRaw}". Must be one of: closed | closed-unknown.`);
33
- process.exit(1);
34
- }
35
- const actualRaw = flags['actual'];
36
- const actualValue = actualRaw !== undefined ? Number(actualRaw) : undefined;
37
- if (actualRaw !== undefined && !Number.isFinite(actualValue)) {
38
- printError(`Invalid --actual: "${actualRaw}". Must be a number.`);
39
- process.exit(1);
40
- }
41
- const noteRaw = flags['note'];
42
- const closureNote = typeof noteRaw === 'string' ? noteRaw : undefined;
43
- const closed = predictionsModule.closePrediction(hippoRoot, tenantId, id, {
44
- closureState: stateRaw,
45
- actualValue,
46
- closureNote,
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 (subcommand === 'list') {
52
- const classTagRaw = flags['class'];
53
- const classTag = typeof classTagRaw === 'string' ? classTagRaw.trim() : '';
54
- const statusRaw = flags['status'];
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 (subcommand === 'show') {
115
- const idRaw = args[1];
116
- if (!idRaw) {
117
- printError('Usage: hippo predict show <id>');
118
- process.exit(1);
119
- }
120
- const id = parseInt(String(idRaw), 10);
121
- if (!Number.isFinite(id) || id <= 0) {
122
- printError(`Invalid prediction id: "${idRaw}"`);
123
- process.exit(1);
124
- }
125
- const pred = predictionsModule.loadPredictionById(hippoRoot, tenantId, id);
126
- if (!pred) {
127
- printError(`Prediction ${id} not found.`);
128
- process.exit(1);
129
- }
130
- console.log(`Prediction #${pred.id}`);
131
- console.log(` class: ${pred.classTag}`);
132
- console.log(` claim: ${pred.claimText}`);
133
- console.log(` state: ${pred.closureState}`);
134
- if (pred.estimateValue !== null)
135
- console.log(` estimate: ${pred.estimateValue}${pred.estimateUnit ? ' ' + pred.estimateUnit : ''}`);
136
- if (pred.targetDate)
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
- if (subcommand === 'baserate') {
150
- // J3 reference-class / planning-fallacy detector
151
- const classTagRaw = flags['class'];
152
- if (typeof classTagRaw !== 'string' || !classTagRaw.trim()) {
153
- printError('Usage: hippo predict baserate --class <c>');
154
- process.exit(1);
155
- }
156
- const baserate = predictionsModule.computePredictionBaserate(hippoRoot, tenantId, classTagRaw.trim());
157
- if (baserate.nClosed === 0) {
158
- console.log(`No closed predictions in class "${baserate.classTag}" yet.`);
159
- console.log(` Create one with: hippo predict "<claim>" --class ${baserate.classTag} --estimate N`);
160
- console.log(` Close it later: hippo predict close <id> --state closed --actual N`);
161
- return;
162
- }
163
- console.log(baserate.summary);
164
- console.log(` n_closed: ${baserate.nClosed}`);
165
- console.log(` n_ratio_eligible: ${baserate.nRatioEligible}`);
166
- if (baserate.meanEstimate !== null)
167
- console.log(` mean_estimate: ${baserate.meanEstimate.toFixed(3)}`);
168
- if (baserate.meanActual !== null)
169
- console.log(` mean_actual: ${baserate.meanActual.toFixed(3)}`);
170
- if (baserate.meanRatio !== null)
171
- console.log(` mean_ratio: ${baserate.meanRatio.toFixed(3)}x`);
172
- if (baserate.p50Ratio !== null)
173
- console.log(` p50_ratio: ${baserate.p50Ratio.toFixed(3)}x`);
174
- if (baserate.mae !== null)
175
- console.log(` mae: ${baserate.mae.toFixed(3)}`);
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
- const claimText = subcommand;
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
- export function cmdDecide(hippoRoot, args, flags) {
215
- requireInit(hippoRoot);
216
- const tenantId = resolveTenantId({});
217
- const subcommand = args[0] ?? '';
218
- if (subcommand === 'list') {
219
- const statusRaw = flags['status'];
220
- const status = typeof statusRaw === 'string' ? statusRaw.trim() : 'all';
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
- if (subcommand === 'get') {
256
- const idRaw = args[1];
257
- if (!idRaw) {
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
- console.log(`Decision #${decision.id}`);
272
- console.log(` status: ${decision.status}`);
273
- console.log(` text: ${decision.decisionText}`);
274
- if (decision.context)
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 (subcommand === 'close') {
288
- const idRaw = args[1];
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
- const decisionText = subcommand;
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` (parseArgs stores boolean true) is a malformed
314
- // request: the user asked to supersede but gave no memory id. Reject it rather
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). Grill fix: commit the
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 the canonical mutation (new
349
- // decision created + old row superseded). If this legacy memory-weaken
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
- export function cmdIncident(hippoRoot, args, flags) {
388
- requireInit(hippoRoot);
389
- const tenantId = resolveTenantId({});
390
- const subcommand = args[0] ?? '';
391
- if (subcommand === 'list') {
392
- const statusRaw = flags['status'];
393
- const status = typeof statusRaw === 'string' ? statusRaw.trim() : 'all';
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
- if (subcommand === 'get') {
429
- const idRaw = args[1];
430
- if (!idRaw) {
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
- const id = parsePositiveIncidentId(idRaw);
435
- const incident = incidentsModule.loadIncidentById(hippoRoot, tenantId, id);
436
- if (!incident) {
437
- printError(`Incident ${id} not found.`);
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 (subcommand === 'resolve') {
460
- const idRaw = args[1];
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
- if (subcommand === 'close') {
476
- const idRaw = args[1];
477
- if (!idRaw) {
478
- printError('Usage: hippo incident close <id>');
479
- process.exit(1);
480
- }
481
- const id = parsePositiveIncidentId(idRaw);
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
- const incidentText = subcommand === 'open' ? (args[1] ?? '') : subcommand;
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]');