hippo-memory 1.61.0 → 1.62.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 +28 -53
- package/dist/agent-memories/apply.d.ts +1 -1
- package/dist/agent-memories/claude-code.d.ts +3 -1
- package/dist/agent-memories/claude-code.js +53 -9
- package/dist/agent-memories/sync.d.ts +3 -3
- package/dist/agent-memories/sync.js +14 -6
- package/dist/agent-memories/types.d.ts +0 -2
- package/dist/api/assemble.js +55 -54
- package/dist/api/context-select.d.ts +49 -0
- package/dist/api/context-select.js +344 -0
- package/dist/api/context.d.ts +2 -2
- package/dist/api/context.js +195 -522
- package/dist/api/drill-down.js +36 -33
- package/dist/api/promote.js +55 -66
- package/dist/api/recall.js +303 -438
- package/dist/api/sleep.js +203 -218
- package/dist/capture/compact.d.ts +1 -1
- package/dist/capture/compact.js +2 -2
- 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.js +35 -34
- package/dist/cli/decisions.js +333 -333
- package/dist/cli/explain.js +66 -60
- package/dist/cli/maintenance.js +62 -51
- package/dist/cli/playbooks.js +387 -370
- package/dist/cli/projects.js +8 -5
- package/dist/cli/recall.js +28 -43
- package/dist/cli/remember.js +113 -70
- package/dist/cli/session-hooks.js +100 -90
- package/dist/cli/setup.js +267 -246
- package/dist/cli/status.js +73 -64
- package/dist/cli/transfer.js +85 -99
- package/dist/compaction-record.d.ts +0 -2
- package/dist/compaction-record.js +1 -1
- package/dist/customer-notes.js +77 -68
- package/dist/dag.js +222 -186
- package/dist/decisions.js +93 -76
- package/dist/doctor.js +11 -7
- package/dist/goals.js +99 -86
- package/dist/incidents.js +45 -38
- package/dist/policies.js +85 -68
- package/dist/processes.js +87 -71
- package/dist/project-briefs.js +135 -108
- package/dist/project-merge.d.ts +12 -4
- package/dist/project-merge.js +130 -45
- package/dist/shared.d.ts +9 -0
- package/dist/shared.js +10 -8
- package/dist/skills.js +81 -65
- package/dist/store/search-rows.d.ts +2 -2
- package/dist/store/search-rows.js +15 -9
- package/dist/version.d.ts +1 -1
- package/dist/version.js +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,182 @@ 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
30
|
// E2 prediction first-class object (v0.31)
|
|
13
31
|
// docs/plans/2026-05-26-e2-prediction-object.md
|
|
14
32
|
// ---------------------------------------------------------------------------
|
|
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
|
-
|
|
33
|
+
function predictClose(hippoRoot, tenantId, args, flags) {
|
|
34
|
+
const idRaw = args[1];
|
|
35
|
+
if (!idRaw) {
|
|
36
|
+
printError('Usage: hippo predict close <id> --state <closed|closed-unknown> [--actual <v>] [--note "..."]');
|
|
37
|
+
process.exit(1);
|
|
38
|
+
}
|
|
39
|
+
const id = parseObjectId(idRaw, 'prediction');
|
|
40
|
+
const stateRaw = typeof flags['state'] === 'string' ? flags['state'].trim() : '';
|
|
41
|
+
if (!predictionsModule.VALID_CLOSURE_STATES.has(stateRaw) || stateRaw === 'open') {
|
|
42
|
+
printError(`Invalid --state: "${stateRaw}". Must be one of: closed | closed-unknown.`);
|
|
43
|
+
process.exit(1);
|
|
44
|
+
}
|
|
45
|
+
const actualRaw = flags['actual'];
|
|
46
|
+
const actualValue = actualRaw !== undefined ? Number(actualRaw) : undefined;
|
|
47
|
+
if (actualRaw !== undefined && !Number.isFinite(actualValue)) {
|
|
48
|
+
printError(`Invalid --actual: "${actualRaw}". Must be a number.`);
|
|
49
|
+
process.exit(1);
|
|
50
|
+
}
|
|
51
|
+
const noteRaw = flags['note'];
|
|
52
|
+
const closureNote = typeof noteRaw === 'string' ? noteRaw : undefined;
|
|
53
|
+
const closed = predictionsModule.closePrediction(hippoRoot, tenantId, id, {
|
|
54
|
+
closureState: stateRaw,
|
|
55
|
+
actualValue,
|
|
56
|
+
closureNote,
|
|
57
|
+
});
|
|
58
|
+
console.log(`Prediction ${closed.id} closed: state=${closed.closureState}${closed.actualValue !== null ? ` actual=${closed.actualValue}` : ''}`);
|
|
59
|
+
}
|
|
60
|
+
function loadPredictionList(hippoRoot, tenantId, status, classTag, limit) {
|
|
61
|
+
if (status === 'open') {
|
|
62
|
+
return predictionsModule.loadOpenPredictions(hippoRoot, tenantId, {
|
|
63
|
+
classTag: classTag || undefined,
|
|
64
|
+
limit,
|
|
47
65
|
});
|
|
48
|
-
console.log(`Prediction ${closed.id} closed: state=${closed.closureState}${closed.actualValue !== null ? ` actual=${closed.actualValue}` : ''}`);
|
|
49
|
-
return;
|
|
50
66
|
}
|
|
51
|
-
if (
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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;
|
|
67
|
+
if (status === 'all') {
|
|
68
|
+
// No closure-state filter; pull both via loadPredictionsByClass if class given
|
|
69
|
+
if (classTag) {
|
|
70
|
+
return predictionsModule.loadPredictionsByClass(hippoRoot, tenantId, classTag, { limit });
|
|
71
|
+
}
|
|
72
|
+
// No class filter + status=all = pull open + closed across all classes
|
|
73
|
+
// (kept simple: report open via loadOpenPredictions; closed via two
|
|
74
|
+
// class scans isn't symmetrical. v1 callers typically pass --class.)
|
|
75
|
+
return predictionsModule.loadOpenPredictions(hippoRoot, tenantId, { limit });
|
|
113
76
|
}
|
|
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}`);
|
|
77
|
+
if (!predictionsModule.VALID_CLOSURE_STATES.has(status)) {
|
|
78
|
+
printError(`Invalid --status: "${status}". Must be one of: open | closed | closed-unknown | all.`);
|
|
79
|
+
process.exit(1);
|
|
80
|
+
}
|
|
81
|
+
if (!classTag) {
|
|
82
|
+
// status filter without class — scan all classes is more complex; v1 requires --class for non-default status
|
|
83
|
+
printError('--status filter (non-open) requires --class to be set.');
|
|
84
|
+
process.exit(1);
|
|
85
|
+
}
|
|
86
|
+
return predictionsModule.loadPredictionsByClass(hippoRoot, tenantId, classTag, {
|
|
87
|
+
closureState: status,
|
|
88
|
+
limit,
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
function predictList(hippoRoot, tenantId, flags) {
|
|
92
|
+
const classTagRaw = flags['class'];
|
|
93
|
+
const classTag = typeof classTagRaw === 'string' ? classTagRaw.trim() : '';
|
|
94
|
+
const statusRaw = flags['status'];
|
|
95
|
+
const status = typeof statusRaw === 'string' ? statusRaw.trim() : 'all';
|
|
96
|
+
const limit = parseListLimit(flags);
|
|
97
|
+
const results = loadPredictionList(hippoRoot, tenantId, status, classTag, limit);
|
|
98
|
+
if (results.length === 0) {
|
|
99
|
+
console.log(classTag ? `No predictions in class "${classTag}".` : 'No predictions.');
|
|
147
100
|
return;
|
|
148
101
|
}
|
|
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
|
-
|
|
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
|
+
}
|
|
113
|
+
function predictShow(hippoRoot, tenantId, args) {
|
|
114
|
+
const idRaw = args[1];
|
|
115
|
+
if (!idRaw) {
|
|
116
|
+
printError('Usage: hippo predict show <id>');
|
|
117
|
+
process.exit(1);
|
|
118
|
+
}
|
|
119
|
+
const id = parseObjectId(idRaw, 'prediction');
|
|
120
|
+
const pred = predictionsModule.loadPredictionById(hippoRoot, tenantId, id);
|
|
121
|
+
if (!pred) {
|
|
122
|
+
printError(`Prediction ${id} not found.`);
|
|
123
|
+
process.exit(1);
|
|
124
|
+
}
|
|
125
|
+
console.log(`Prediction #${pred.id}`);
|
|
126
|
+
console.log(` class: ${pred.classTag}`);
|
|
127
|
+
console.log(` claim: ${pred.claimText}`);
|
|
128
|
+
console.log(` state: ${pred.closureState}`);
|
|
129
|
+
if (pred.estimateValue !== null)
|
|
130
|
+
console.log(` estimate: ${pred.estimateValue}${pred.estimateUnit ? ' ' + pred.estimateUnit : ''}`);
|
|
131
|
+
if (pred.targetDate)
|
|
132
|
+
console.log(` target: ${pred.targetDate}`);
|
|
133
|
+
if (pred.actualValue !== null)
|
|
134
|
+
console.log(` actual: ${pred.actualValue}`);
|
|
135
|
+
if (pred.closedAt)
|
|
136
|
+
console.log(` closed: ${pred.closedAt}`);
|
|
137
|
+
if (pred.closureNote)
|
|
138
|
+
console.log(` note: ${pred.closureNote}`);
|
|
139
|
+
if (pred.memoryId)
|
|
140
|
+
console.log(` memory: ${pred.memoryId}`);
|
|
141
|
+
console.log(` created: ${pred.createdAt}`);
|
|
142
|
+
}
|
|
143
|
+
function predictBaserate(hippoRoot, tenantId, flags) {
|
|
144
|
+
// J3 reference-class / planning-fallacy detector
|
|
145
|
+
const classTagRaw = flags['class'];
|
|
146
|
+
if (typeof classTagRaw !== 'string' || !classTagRaw.trim()) {
|
|
147
|
+
printError('Usage: hippo predict baserate --class <c>');
|
|
148
|
+
process.exit(1);
|
|
149
|
+
}
|
|
150
|
+
const baserate = predictionsModule.computePredictionBaserate(hippoRoot, tenantId, classTagRaw.trim());
|
|
151
|
+
if (baserate.nClosed === 0) {
|
|
152
|
+
console.log(`No closed predictions in class "${baserate.classTag}" yet.`);
|
|
153
|
+
console.log(` Create one with: hippo predict "<claim>" --class ${baserate.classTag} --estimate N`);
|
|
154
|
+
console.log(` Close it later: hippo predict close <id> --state closed --actual N`);
|
|
176
155
|
return;
|
|
177
156
|
}
|
|
157
|
+
console.log(baserate.summary);
|
|
158
|
+
console.log(` n_closed: ${baserate.nClosed}`);
|
|
159
|
+
console.log(` n_ratio_eligible: ${baserate.nRatioEligible}`);
|
|
160
|
+
if (baserate.meanEstimate !== null)
|
|
161
|
+
console.log(` mean_estimate: ${baserate.meanEstimate.toFixed(3)}`);
|
|
162
|
+
if (baserate.meanActual !== null)
|
|
163
|
+
console.log(` mean_actual: ${baserate.meanActual.toFixed(3)}`);
|
|
164
|
+
if (baserate.meanRatio !== null)
|
|
165
|
+
console.log(` mean_ratio: ${baserate.meanRatio.toFixed(3)}x`);
|
|
166
|
+
if (baserate.p50Ratio !== null)
|
|
167
|
+
console.log(` p50_ratio: ${baserate.p50Ratio.toFixed(3)}x`);
|
|
168
|
+
if (baserate.mae !== null)
|
|
169
|
+
console.log(` mae: ${baserate.mae.toFixed(3)}`);
|
|
170
|
+
}
|
|
171
|
+
export function cmdPredict(hippoRoot, args, flags) {
|
|
172
|
+
requireInit(hippoRoot);
|
|
173
|
+
const tenantId = resolveTenantId({});
|
|
174
|
+
const subcommand = args[0] ?? '';
|
|
175
|
+
if (subcommand === 'close')
|
|
176
|
+
return predictClose(hippoRoot, tenantId, args, flags);
|
|
177
|
+
if (subcommand === 'list')
|
|
178
|
+
return predictList(hippoRoot, tenantId, flags);
|
|
179
|
+
if (subcommand === 'show')
|
|
180
|
+
return predictShow(hippoRoot, tenantId, args);
|
|
181
|
+
if (subcommand === 'baserate')
|
|
182
|
+
return predictBaserate(hippoRoot, tenantId, flags);
|
|
178
183
|
// Default subcommand: create. args[0] is the claim text.
|
|
179
|
-
|
|
184
|
+
predictCreate(hippoRoot, tenantId, subcommand, flags);
|
|
185
|
+
}
|
|
186
|
+
function predictCreate(hippoRoot, tenantId, claimText, flags) {
|
|
180
187
|
if (!claimText) {
|
|
181
188
|
printError('Usage: hippo predict "<claim>" --class <c> [--estimate <v>] [--unit <u>] [--target <YYYY-MM-DD>]');
|
|
182
189
|
printError(' hippo predict close <id> --state <closed|closed-unknown> [--actual <v>] [--note "..."]');
|
|
@@ -211,96 +218,88 @@ export function cmdPredict(hippoRoot, args, flags) {
|
|
|
211
218
|
if (created.memoryId)
|
|
212
219
|
console.log(` memory: ${created.memoryId}`);
|
|
213
220
|
}
|
|
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;
|
|
221
|
+
function decideList(hippoRoot, tenantId, flags) {
|
|
222
|
+
const statusRaw = flags['status'];
|
|
223
|
+
const status = typeof statusRaw === 'string' ? statusRaw.trim() : 'all';
|
|
224
|
+
const limit = parseListLimit(flags);
|
|
225
|
+
let results;
|
|
226
|
+
if (status === 'all') {
|
|
227
|
+
results = decisionsModule.loadDecisions(hippoRoot, tenantId, { limit });
|
|
254
228
|
}
|
|
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.`);
|
|
229
|
+
else {
|
|
230
|
+
if (!decisionsModule.VALID_DECISION_STATES.has(status)) {
|
|
231
|
+
printError(`Invalid --status: "${status}". Must be one of: active | superseded | closed | all.`);
|
|
269
232
|
process.exit(1);
|
|
270
233
|
}
|
|
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;
|
|
234
|
+
results = decisionsModule.loadDecisions(hippoRoot, tenantId, {
|
|
235
|
+
status: status,
|
|
236
|
+
limit,
|
|
237
|
+
});
|
|
286
238
|
}
|
|
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.`);
|
|
239
|
+
if (results.length === 0) {
|
|
240
|
+
console.log('No decisions.');
|
|
300
241
|
return;
|
|
301
242
|
}
|
|
243
|
+
console.log(`Found ${results.length} decisions:\n`);
|
|
244
|
+
for (const d of results) {
|
|
245
|
+
const supPart = d.supersededBy !== null ? ` superseded_by=#${d.supersededBy}` : '';
|
|
246
|
+
console.log(`#${d.id} [${d.status}]${supPart} memory=${d.memoryId ?? '-'}`);
|
|
247
|
+
console.log(` ${d.decisionText}`);
|
|
248
|
+
if (d.context)
|
|
249
|
+
console.log(` context: ${d.context}`);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
function decideGet(hippoRoot, tenantId, args) {
|
|
253
|
+
const idRaw = args[1];
|
|
254
|
+
if (!idRaw) {
|
|
255
|
+
printError('Usage: hippo decide get <id>');
|
|
256
|
+
process.exit(1);
|
|
257
|
+
}
|
|
258
|
+
const id = parseObjectId(idRaw, 'decision');
|
|
259
|
+
const decision = decisionsModule.loadDecisionById(hippoRoot, tenantId, id);
|
|
260
|
+
if (!decision) {
|
|
261
|
+
printError(`Decision ${id} not found.`);
|
|
262
|
+
process.exit(1);
|
|
263
|
+
}
|
|
264
|
+
console.log(`Decision #${decision.id}`);
|
|
265
|
+
console.log(` status: ${decision.status}`);
|
|
266
|
+
console.log(` text: ${decision.decisionText}`);
|
|
267
|
+
if (decision.context)
|
|
268
|
+
console.log(` context: ${decision.context}`);
|
|
269
|
+
if (decision.supersededBy !== null)
|
|
270
|
+
console.log(` superseded_by: #${decision.supersededBy}`);
|
|
271
|
+
if (decision.supersededAt)
|
|
272
|
+
console.log(` superseded_at: ${decision.supersededAt}`);
|
|
273
|
+
if (decision.closedAt)
|
|
274
|
+
console.log(` closed_at: ${decision.closedAt}`);
|
|
275
|
+
if (decision.memoryId)
|
|
276
|
+
console.log(` memory: ${decision.memoryId}`);
|
|
277
|
+
console.log(` created: ${decision.createdAt}`);
|
|
278
|
+
}
|
|
279
|
+
function decideClose(hippoRoot, tenantId, args) {
|
|
280
|
+
const idRaw = args[1];
|
|
281
|
+
if (!idRaw) {
|
|
282
|
+
printError('Usage: hippo decide close <id>');
|
|
283
|
+
process.exit(1);
|
|
284
|
+
}
|
|
285
|
+
const id = parseObjectId(idRaw, 'decision');
|
|
286
|
+
const closed = decisionsModule.closeDecision(hippoRoot, tenantId, id);
|
|
287
|
+
console.log(`Decision #${closed.id} closed.`);
|
|
288
|
+
}
|
|
289
|
+
export function cmdDecide(hippoRoot, args, flags) {
|
|
290
|
+
requireInit(hippoRoot);
|
|
291
|
+
const tenantId = resolveTenantId({});
|
|
292
|
+
const subcommand = args[0] ?? '';
|
|
293
|
+
if (subcommand === 'list')
|
|
294
|
+
return decideList(hippoRoot, tenantId, flags);
|
|
295
|
+
if (subcommand === 'get')
|
|
296
|
+
return decideGet(hippoRoot, tenantId, args);
|
|
297
|
+
if (subcommand === 'close')
|
|
298
|
+
return decideClose(hippoRoot, tenantId, args);
|
|
302
299
|
// Default subcommand: create. args[0] is the decision text.
|
|
303
|
-
|
|
300
|
+
decideCreate(hippoRoot, tenantId, subcommand, flags);
|
|
301
|
+
}
|
|
302
|
+
function decideCreate(hippoRoot, tenantId, decisionText, flags) {
|
|
304
303
|
if (!decisionText) {
|
|
305
304
|
printError('Usage: hippo decide "<decision>" [--context "<why>"] [--supersedes <memory-id>]');
|
|
306
305
|
printError(' hippo decide list [--status active|superseded|closed|all] [--limit N]');
|
|
@@ -384,109 +383,110 @@ function parsePositiveIncidentId(idRaw) {
|
|
|
384
383
|
}
|
|
385
384
|
return id;
|
|
386
385
|
}
|
|
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;
|
|
386
|
+
function incidentList(hippoRoot, tenantId, flags) {
|
|
387
|
+
const statusRaw = flags['status'];
|
|
388
|
+
const status = typeof statusRaw === 'string' ? statusRaw.trim() : 'all';
|
|
389
|
+
const limit = parseListLimit(flags);
|
|
390
|
+
let results;
|
|
391
|
+
if (status === 'all') {
|
|
392
|
+
results = incidentsModule.loadIncidents(hippoRoot, tenantId, { limit });
|
|
427
393
|
}
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
printError('Usage: hippo incident get <id>');
|
|
394
|
+
else {
|
|
395
|
+
if (!incidentsModule.VALID_INCIDENT_STATES.has(status)) {
|
|
396
|
+
printError(`Invalid --status: "${status}". Must be one of: open | resolved | closed | all.`);
|
|
432
397
|
process.exit(1);
|
|
433
398
|
}
|
|
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;
|
|
399
|
+
results = incidentsModule.loadIncidents(hippoRoot, tenantId, {
|
|
400
|
+
status: status,
|
|
401
|
+
limit,
|
|
402
|
+
});
|
|
458
403
|
}
|
|
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.`);
|
|
404
|
+
if (results.length === 0) {
|
|
405
|
+
console.log('No incidents.');
|
|
473
406
|
return;
|
|
474
407
|
}
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
const closed = incidentsModule.closeIncident(hippoRoot, tenantId, id);
|
|
483
|
-
console.log(`Incident #${closed.id} closed.`);
|
|
484
|
-
return;
|
|
408
|
+
console.log(`Found ${results.length} incidents:\n`);
|
|
409
|
+
for (const inc of results) {
|
|
410
|
+
const linkPart = inc.linkedMemoryIds.length > 0 ? ` links=${inc.linkedMemoryIds.length}` : '';
|
|
411
|
+
console.log(`#${inc.id} [${inc.status}]${linkPart} memory=${inc.memoryId ?? '-'}`);
|
|
412
|
+
console.log(` ${inc.incidentText}`);
|
|
413
|
+
if (inc.context)
|
|
414
|
+
console.log(` context: ${inc.context}`);
|
|
485
415
|
}
|
|
416
|
+
}
|
|
417
|
+
function incidentGet(hippoRoot, tenantId, args) {
|
|
418
|
+
const idRaw = args[1];
|
|
419
|
+
if (!idRaw) {
|
|
420
|
+
printError('Usage: hippo incident get <id>');
|
|
421
|
+
process.exit(1);
|
|
422
|
+
}
|
|
423
|
+
const id = parsePositiveIncidentId(idRaw);
|
|
424
|
+
const incident = incidentsModule.loadIncidentById(hippoRoot, tenantId, id);
|
|
425
|
+
if (!incident) {
|
|
426
|
+
printError(`Incident ${id} not found.`);
|
|
427
|
+
process.exit(1);
|
|
428
|
+
}
|
|
429
|
+
console.log(`Incident #${incident.id}`);
|
|
430
|
+
console.log(` status: ${incident.status}`);
|
|
431
|
+
console.log(` text: ${incident.incidentText}`);
|
|
432
|
+
if (incident.context)
|
|
433
|
+
console.log(` context: ${incident.context}`);
|
|
434
|
+
if (incident.resolutionText)
|
|
435
|
+
console.log(` resolution: ${incident.resolutionText}`);
|
|
436
|
+
if (incident.resolvedAt)
|
|
437
|
+
console.log(` resolved_at: ${incident.resolvedAt}`);
|
|
438
|
+
if (incident.closedAt)
|
|
439
|
+
console.log(` closed_at: ${incident.closedAt}`);
|
|
440
|
+
if (incident.linkedMemoryIds.length > 0) {
|
|
441
|
+
console.log(` linked memories: ${incident.linkedMemoryIds.join(', ')}`);
|
|
442
|
+
}
|
|
443
|
+
if (incident.memoryId)
|
|
444
|
+
console.log(` memory: ${incident.memoryId}`);
|
|
445
|
+
console.log(` created: ${incident.createdAt}`);
|
|
446
|
+
}
|
|
447
|
+
function incidentResolve(hippoRoot, tenantId, args, flags) {
|
|
448
|
+
const idRaw = args[1];
|
|
449
|
+
if (!idRaw) {
|
|
450
|
+
printError('Usage: hippo incident resolve <id> --resolution "<text>"');
|
|
451
|
+
process.exit(1);
|
|
452
|
+
}
|
|
453
|
+
const id = parsePositiveIncidentId(idRaw);
|
|
454
|
+
const resolutionRaw = flags['resolution'];
|
|
455
|
+
if (typeof resolutionRaw !== 'string' || !resolutionRaw.trim()) {
|
|
456
|
+
printError('--resolution requires a non-empty value, e.g. hippo incident resolve <id> --resolution "root cause fixed".');
|
|
457
|
+
process.exit(1);
|
|
458
|
+
}
|
|
459
|
+
const resolved = incidentsModule.resolveIncident(hippoRoot, tenantId, id, resolutionRaw);
|
|
460
|
+
console.log(`Incident #${resolved.id} resolved.`);
|
|
461
|
+
}
|
|
462
|
+
function incidentClose(hippoRoot, tenantId, args) {
|
|
463
|
+
const idRaw = args[1];
|
|
464
|
+
if (!idRaw) {
|
|
465
|
+
printError('Usage: hippo incident close <id>');
|
|
466
|
+
process.exit(1);
|
|
467
|
+
}
|
|
468
|
+
const id = parsePositiveIncidentId(idRaw);
|
|
469
|
+
const closed = incidentsModule.closeIncident(hippoRoot, tenantId, id);
|
|
470
|
+
console.log(`Incident #${closed.id} closed.`);
|
|
471
|
+
}
|
|
472
|
+
export function cmdIncident(hippoRoot, args, flags) {
|
|
473
|
+
requireInit(hippoRoot);
|
|
474
|
+
const tenantId = resolveTenantId({});
|
|
475
|
+
const subcommand = args[0] ?? '';
|
|
476
|
+
if (subcommand === 'list')
|
|
477
|
+
return incidentList(hippoRoot, tenantId, flags);
|
|
478
|
+
if (subcommand === 'get')
|
|
479
|
+
return incidentGet(hippoRoot, tenantId, args);
|
|
480
|
+
if (subcommand === 'resolve')
|
|
481
|
+
return incidentResolve(hippoRoot, tenantId, args, flags);
|
|
482
|
+
if (subcommand === 'close')
|
|
483
|
+
return incidentClose(hippoRoot, tenantId, args);
|
|
486
484
|
// Default subcommand: open (create). Accept both the documented
|
|
487
485
|
// `incident open "<text>"` form and the bare `incident "<text>"` form: for the
|
|
488
486
|
// `open` keyword the text is args[1], otherwise args[0] IS the text.
|
|
489
|
-
|
|
487
|
+
incidentCreate(hippoRoot, tenantId, subcommand === 'open' ? (args[1] ?? '') : subcommand, flags);
|
|
488
|
+
}
|
|
489
|
+
function incidentCreate(hippoRoot, tenantId, incidentText, flags) {
|
|
490
490
|
if (!incidentText) {
|
|
491
491
|
printError('Usage: hippo incident "<incident>" [--context "<details>"] [--link <memory-id>]...');
|
|
492
492
|
printError(' hippo incident list [--status open|resolved|closed|all] [--limit N]');
|