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.
Files changed (57) hide show
  1. package/README.md +28 -53
  2. package/dist/agent-memories/apply.d.ts +1 -1
  3. package/dist/agent-memories/claude-code.d.ts +3 -1
  4. package/dist/agent-memories/claude-code.js +53 -9
  5. package/dist/agent-memories/sync.d.ts +3 -3
  6. package/dist/agent-memories/sync.js +14 -6
  7. package/dist/agent-memories/types.d.ts +0 -2
  8. package/dist/api/assemble.js +55 -54
  9. package/dist/api/context-select.d.ts +49 -0
  10. package/dist/api/context-select.js +344 -0
  11. package/dist/api/context.d.ts +2 -2
  12. package/dist/api/context.js +195 -522
  13. package/dist/api/drill-down.js +36 -33
  14. package/dist/api/promote.js +55 -66
  15. package/dist/api/recall.js +303 -438
  16. package/dist/api/sleep.js +203 -218
  17. package/dist/capture/compact.d.ts +1 -1
  18. package/dist/capture/compact.js +2 -2
  19. package/dist/cli/briefs.js +324 -306
  20. package/dist/cli/context.js +44 -34
  21. package/dist/cli/continuity.js +283 -271
  22. package/dist/cli/curate.js +35 -34
  23. package/dist/cli/decisions.js +333 -333
  24. package/dist/cli/explain.js +66 -60
  25. package/dist/cli/maintenance.js +62 -51
  26. package/dist/cli/playbooks.js +387 -370
  27. package/dist/cli/projects.js +8 -5
  28. package/dist/cli/recall.js +28 -43
  29. package/dist/cli/remember.js +113 -70
  30. package/dist/cli/session-hooks.js +100 -90
  31. package/dist/cli/setup.js +267 -246
  32. package/dist/cli/status.js +73 -64
  33. package/dist/cli/transfer.js +85 -99
  34. package/dist/compaction-record.d.ts +0 -2
  35. package/dist/compaction-record.js +1 -1
  36. package/dist/customer-notes.js +77 -68
  37. package/dist/dag.js +222 -186
  38. package/dist/decisions.js +93 -76
  39. package/dist/doctor.js +11 -7
  40. package/dist/goals.js +99 -86
  41. package/dist/incidents.js +45 -38
  42. package/dist/policies.js +85 -68
  43. package/dist/processes.js +87 -71
  44. package/dist/project-briefs.js +135 -108
  45. package/dist/project-merge.d.ts +12 -4
  46. package/dist/project-merge.js +130 -45
  47. package/dist/shared.d.ts +9 -0
  48. package/dist/shared.js +10 -8
  49. package/dist/skills.js +81 -65
  50. package/dist/store/search-rows.d.ts +2 -2
  51. package/dist/store/search-rows.js +15 -9
  52. package/dist/version.d.ts +1 -1
  53. package/dist/version.js +1 -1
  54. package/extensions/openclaw-plugin/openclaw.plugin.json +1 -1
  55. package/extensions/openclaw-plugin/package.json +1 -1
  56. package/openclaw.plugin.json +1 -1
  57. package/package.json +1 -1
@@ -33,135 +33,144 @@ function collectProcessSteps(stepRaw) {
33
33
  }
34
34
  return [];
35
35
  }
36
- export function cmdProcess(hippoRoot, args, flags) {
37
- requireInit(hippoRoot);
38
- const tenantId = resolveTenantId({});
39
- const subcommand = args[0] ?? '';
40
- if (subcommand === 'list') {
41
- const statusRaw = flags['status'];
42
- const status = typeof statusRaw === 'string' ? statusRaw.trim() : 'all';
43
- const limitRaw = flags['limit'];
44
- const limit = limitRaw !== undefined ? parseInt(String(limitRaw), 10) : 100;
45
- if (!Number.isFinite(limit) || limit <= 0) {
46
- printError(`Invalid --limit: "${limitRaw}". Must be a positive integer.`);
47
- process.exit(1);
48
- }
49
- let results;
50
- if (status === 'all') {
51
- results = processesModule.loadProcesses(hippoRoot, tenantId, { limit });
52
- }
53
- else {
54
- if (!processesModule.VALID_PROCESS_STATES.has(status)) {
55
- printError(`Invalid --status: "${status}". Must be one of: active | superseded | closed | all.`);
56
- process.exit(1);
57
- }
58
- results = processesModule.loadProcesses(hippoRoot, tenantId, {
59
- status: status,
60
- limit,
61
- });
62
- }
63
- if (results.length === 0) {
64
- console.log('No processes.');
65
- return;
66
- }
67
- console.log(`Found ${results.length} processes:\n`);
68
- for (const proc of results) {
69
- console.log(`#${proc.id} [${proc.status}] v${proc.version} steps=${proc.steps.length} memory=${proc.memoryId ?? '-'}`);
70
- console.log(` ${proc.processName}`);
71
- if (proc.changeSummary)
72
- console.log(` change: ${proc.changeSummary}`);
73
- }
74
- return;
36
+ function parseListLimit(flags) {
37
+ const limitRaw = flags['limit'];
38
+ const limit = limitRaw !== undefined ? parseInt(String(limitRaw), 10) : 100;
39
+ if (!Number.isFinite(limit) || limit <= 0) {
40
+ printError(`Invalid --limit: "${limitRaw}". Must be a positive integer.`);
41
+ process.exit(1);
75
42
  }
76
- if (subcommand === 'get') {
77
- const idRaw = args[1];
78
- if (!idRaw) {
79
- printError('Usage: hippo process get <id>');
80
- process.exit(1);
81
- }
82
- const id = parsePositiveProcessId(idRaw);
83
- const proc = processesModule.loadProcessById(hippoRoot, tenantId, id);
84
- if (!proc) {
85
- printError(`Process ${id} not found.`);
86
- process.exit(1);
87
- }
88
- console.log(`Process #${proc.id}`);
89
- console.log(` name: ${proc.processName}`);
90
- console.log(` status: ${proc.status}`);
91
- console.log(` version: ${proc.version}`);
92
- if (proc.description)
93
- console.log(` description: ${proc.description}`);
94
- if (proc.steps.length > 0) {
95
- console.log(` steps:`);
96
- proc.steps.forEach((s, i) => console.log(` ${i + 1}. ${s}`));
97
- }
98
- if (proc.changeSummary)
99
- console.log(` change_summary: ${proc.changeSummary}`);
100
- if (proc.supersededBy !== null)
101
- console.log(` superseded_by: #${proc.supersededBy}`);
102
- if (proc.supersededAt)
103
- console.log(` superseded_at: ${proc.supersededAt}`);
104
- if (proc.closedAt)
105
- console.log(` closed_at: ${proc.closedAt}`);
106
- if (proc.memoryId)
107
- console.log(` memory: ${proc.memoryId}`);
108
- console.log(` created: ${proc.createdAt}`);
109
- return;
43
+ return limit;
44
+ }
45
+ function processList(hippoRoot, tenantId, flags) {
46
+ const statusRaw = flags['status'];
47
+ const status = typeof statusRaw === 'string' ? statusRaw.trim() : 'all';
48
+ const limit = parseListLimit(flags);
49
+ let results;
50
+ if (status === 'all') {
51
+ results = processesModule.loadProcesses(hippoRoot, tenantId, { limit });
110
52
  }
111
- if (subcommand === 'supersede') {
112
- const idRaw = args[1];
113
- if (!idRaw) {
114
- printError('Usage: hippo process supersede <id> --step "<text>" [--step ...] [--change "<summary>"] [--description "<text>"]');
53
+ else {
54
+ if (!processesModule.VALID_PROCESS_STATES.has(status)) {
55
+ printError(`Invalid --status: "${status}". Must be one of: active | superseded | closed | all.`);
115
56
  process.exit(1);
116
57
  }
117
- const id = parsePositiveProcessId(idRaw);
118
- const steps = collectProcessSteps(flags['step']);
119
- if (steps.length === 0) {
120
- printError('hippo process supersede requires at least one --step "<text>" for the new version.');
121
- process.exit(1);
122
- }
123
- // A supersession is a new version of the SAME process, so the new row reuses
124
- // the predecessor's name (stable identity across versions). loadProcessById
125
- // gives an early not-found before the write; saveProcess's in-SAVEPOINT
126
- // preflight is the authoritative active-state check.
127
- const existing = processesModule.loadProcessById(hippoRoot, tenantId, id);
128
- if (!existing) {
129
- printError(`Process ${id} not found.`);
130
- process.exit(1);
131
- }
132
- const changeRaw = flags['change'];
133
- const changeSummary = typeof changeRaw === 'string' && changeRaw ? changeRaw : undefined;
134
- const descRaw = flags['description'];
135
- const description = typeof descRaw === 'string' && descRaw ? descRaw : undefined;
136
- const procPathTags = extractPathTags(process.cwd());
137
- const created = processesModule.saveProcess(hippoRoot, tenantId, {
138
- processName: existing.processName,
139
- steps,
140
- description,
141
- changeSummary,
142
- supersedesProcessId: id,
143
- extraTags: procPathTags,
58
+ results = processesModule.loadProcesses(hippoRoot, tenantId, {
59
+ status: status,
60
+ limit,
144
61
  });
145
- console.log(`Process #${created.id} recorded (v${created.version}), superseding #${id}.`);
146
- if (created.memoryId)
147
- console.log(` memory: ${created.memoryId}`);
148
- return;
149
62
  }
150
- if (subcommand === 'close') {
151
- const idRaw = args[1];
152
- if (!idRaw) {
153
- printError('Usage: hippo process close <id>');
154
- process.exit(1);
155
- }
156
- const id = parsePositiveProcessId(idRaw);
157
- const closed = processesModule.closeProcess(hippoRoot, tenantId, id);
158
- console.log(`Process #${closed.id} closed.`);
63
+ if (results.length === 0) {
64
+ console.log('No processes.');
159
65
  return;
160
66
  }
67
+ console.log(`Found ${results.length} processes:\n`);
68
+ for (const proc of results) {
69
+ console.log(`#${proc.id} [${proc.status}] v${proc.version} steps=${proc.steps.length} memory=${proc.memoryId ?? '-'}`);
70
+ console.log(` ${proc.processName}`);
71
+ if (proc.changeSummary)
72
+ console.log(` change: ${proc.changeSummary}`);
73
+ }
74
+ }
75
+ function processGet(hippoRoot, tenantId, args) {
76
+ const idRaw = args[1];
77
+ if (!idRaw) {
78
+ printError('Usage: hippo process get <id>');
79
+ process.exit(1);
80
+ }
81
+ const id = parsePositiveProcessId(idRaw);
82
+ const proc = processesModule.loadProcessById(hippoRoot, tenantId, id);
83
+ if (!proc) {
84
+ printError(`Process ${id} not found.`);
85
+ process.exit(1);
86
+ }
87
+ console.log(`Process #${proc.id}`);
88
+ console.log(` name: ${proc.processName}`);
89
+ console.log(` status: ${proc.status}`);
90
+ console.log(` version: ${proc.version}`);
91
+ if (proc.description)
92
+ console.log(` description: ${proc.description}`);
93
+ if (proc.steps.length > 0) {
94
+ console.log(` steps:`);
95
+ proc.steps.forEach((s, i) => console.log(` ${i + 1}. ${s}`));
96
+ }
97
+ if (proc.changeSummary)
98
+ console.log(` change_summary: ${proc.changeSummary}`);
99
+ if (proc.supersededBy !== null)
100
+ console.log(` superseded_by: #${proc.supersededBy}`);
101
+ if (proc.supersededAt)
102
+ console.log(` superseded_at: ${proc.supersededAt}`);
103
+ if (proc.closedAt)
104
+ console.log(` closed_at: ${proc.closedAt}`);
105
+ if (proc.memoryId)
106
+ console.log(` memory: ${proc.memoryId}`);
107
+ console.log(` created: ${proc.createdAt}`);
108
+ }
109
+ function processSupersede(hippoRoot, tenantId, args, flags) {
110
+ const idRaw = args[1];
111
+ if (!idRaw) {
112
+ printError('Usage: hippo process supersede <id> --step "<text>" [--step ...] [--change "<summary>"] [--description "<text>"]');
113
+ process.exit(1);
114
+ }
115
+ const id = parsePositiveProcessId(idRaw);
116
+ const steps = collectProcessSteps(flags['step']);
117
+ if (steps.length === 0) {
118
+ printError('hippo process supersede requires at least one --step "<text>" for the new version.');
119
+ process.exit(1);
120
+ }
121
+ // A supersession is a new version of the SAME process, so the new row reuses the predecessor's name (stable identity
122
+ // across versions). loadProcessById gives an early not-found before the write; saveProcess's in-SAVEPOINT preflight
123
+ // is the authoritative active-state check.
124
+ const existing = processesModule.loadProcessById(hippoRoot, tenantId, id);
125
+ if (!existing) {
126
+ printError(`Process ${id} not found.`);
127
+ process.exit(1);
128
+ }
129
+ const changeRaw = flags['change'];
130
+ const changeSummary = typeof changeRaw === 'string' && changeRaw ? changeRaw : undefined;
131
+ const descRaw = flags['description'];
132
+ const description = typeof descRaw === 'string' && descRaw ? descRaw : undefined;
133
+ const procPathTags = extractPathTags(process.cwd());
134
+ const created = processesModule.saveProcess(hippoRoot, tenantId, {
135
+ processName: existing.processName,
136
+ steps,
137
+ description,
138
+ changeSummary,
139
+ supersedesProcessId: id,
140
+ extraTags: procPathTags,
141
+ });
142
+ console.log(`Process #${created.id} recorded (v${created.version}), superseding #${id}.`);
143
+ if (created.memoryId)
144
+ console.log(` memory: ${created.memoryId}`);
145
+ }
146
+ function processClose(hippoRoot, tenantId, args) {
147
+ const idRaw = args[1];
148
+ if (!idRaw) {
149
+ printError('Usage: hippo process close <id>');
150
+ process.exit(1);
151
+ }
152
+ const id = parsePositiveProcessId(idRaw);
153
+ const closed = processesModule.closeProcess(hippoRoot, tenantId, id);
154
+ console.log(`Process #${closed.id} closed.`);
155
+ }
156
+ export function cmdProcess(hippoRoot, args, flags) {
157
+ requireInit(hippoRoot);
158
+ const tenantId = resolveTenantId({});
159
+ const subcommand = args[0] ?? '';
160
+ if (subcommand === 'list')
161
+ return processList(hippoRoot, tenantId, flags);
162
+ if (subcommand === 'get')
163
+ return processGet(hippoRoot, tenantId, args);
164
+ if (subcommand === 'supersede')
165
+ return processSupersede(hippoRoot, tenantId, args, flags);
166
+ if (subcommand === 'close')
167
+ return processClose(hippoRoot, tenantId, args);
161
168
  // Default subcommand: new (create). Accept both the documented
162
169
  // `process new "<name>"` form and the bare `process "<name>"` form: for the
163
170
  // `new` keyword the name is args[1], otherwise args[0] IS the name.
164
- const processName = subcommand === 'new' ? (args[1] ?? '') : subcommand;
171
+ processCreate(hippoRoot, tenantId, subcommand === 'new' ? (args[1] ?? '') : subcommand, flags);
172
+ }
173
+ function processCreate(hippoRoot, tenantId, processName, flags) {
165
174
  if (!processName) {
166
175
  printError('Usage: hippo process new "<name>" --step "<text>" [--step ...] [--description "<text>"]');
167
176
  printError(' hippo process list [--status active|superseded|closed|all] [--limit N]');
@@ -202,150 +211,154 @@ function printPolicyRow(p) {
202
211
  if (p.changeSummary)
203
212
  console.log(` change: ${p.changeSummary}`);
204
213
  }
205
- export function cmdPolicy(hippoRoot, args, flags) {
206
- requireInit(hippoRoot);
207
- const tenantId = resolveTenantId({});
208
- const subcommand = args[0] ?? '';
209
- if (subcommand === 'list') {
210
- const statusRaw = flags['status'];
211
- const status = typeof statusRaw === 'string' ? statusRaw.trim() : 'all';
212
- const limitRaw = flags['limit'];
213
- const limit = limitRaw !== undefined ? parseInt(String(limitRaw), 10) : 100;
214
- if (!Number.isFinite(limit) || limit <= 0) {
215
- printError(`Invalid --limit: "${limitRaw}". Must be a positive integer.`);
216
- process.exit(1);
217
- }
218
- let results;
219
- if (status === 'all') {
220
- results = policiesModule.loadPolicies(hippoRoot, tenantId, { limit });
221
- }
222
- else {
223
- if (!policiesModule.VALID_POLICY_STATES.has(status)) {
224
- printError(`Invalid --status: "${status}". Must be one of: active | superseded | closed | all.`);
225
- process.exit(1);
226
- }
227
- results = policiesModule.loadPolicies(hippoRoot, tenantId, {
228
- status: status,
229
- limit,
230
- });
231
- }
232
- if (results.length === 0) {
233
- console.log('No policies.');
234
- return;
235
- }
236
- console.log(`Found ${results.length} policies:\n`);
237
- for (const p of results)
238
- printPolicyRow(p);
239
- return;
214
+ function policyList(hippoRoot, tenantId, flags) {
215
+ const statusRaw = flags['status'];
216
+ const status = typeof statusRaw === 'string' ? statusRaw.trim() : 'all';
217
+ const limit = parseListLimit(flags);
218
+ let results;
219
+ if (status === 'all') {
220
+ results = policiesModule.loadPolicies(hippoRoot, tenantId, { limit });
240
221
  }
241
- if (subcommand === 'asof') {
242
- const dateRaw = args[1];
243
- if (!dateRaw) {
244
- printError('Usage: hippo policy asof <iso-date> [--name "<policy>"]');
245
- process.exit(1);
246
- }
247
- const nameRaw = flags['name'];
248
- const name = typeof nameRaw === 'string' && nameRaw ? nameRaw : undefined;
249
- let results;
250
- try {
251
- results = policiesModule.loadPoliciesAsOf(hippoRoot, tenantId, dateRaw, { name });
252
- }
253
- catch (e) {
254
- printError(e.message);
222
+ else {
223
+ if (!policiesModule.VALID_POLICY_STATES.has(status)) {
224
+ printError(`Invalid --status: "${status}". Must be one of: active | superseded | closed | all.`);
255
225
  process.exit(1);
256
226
  }
257
- if (results.length === 0) {
258
- console.log(`No active policies in force at ${dateRaw}${name ? ` for "${name}"` : ''}.`);
259
- return;
260
- }
261
- console.log(`Policies in force at ${dateRaw}:\n`);
262
- for (const p of results)
263
- printPolicyRow(p);
264
- return;
227
+ results = policiesModule.loadPolicies(hippoRoot, tenantId, {
228
+ status: status,
229
+ limit,
230
+ });
265
231
  }
266
- if (subcommand === 'get') {
267
- const idRaw = args[1];
268
- if (!idRaw) {
269
- printError('Usage: hippo policy get <id>');
270
- process.exit(1);
271
- }
272
- const id = parsePositivePolicyId(idRaw);
273
- const p = policiesModule.loadPolicyById(hippoRoot, tenantId, id);
274
- if (!p) {
275
- printError(`Policy ${id} not found.`);
276
- process.exit(1);
277
- }
278
- console.log(`Policy #${p.id}`);
279
- console.log(` name: ${p.policyName}`);
280
- console.log(` text: ${p.policyText}`);
281
- console.log(` status: ${p.status}`);
282
- console.log(` version: ${p.version}`);
283
- console.log(` valid_from: ${p.validFrom}`);
284
- console.log(` valid_to: ${p.validTo ?? '(open-ended)'}`);
285
- if (p.changeSummary)
286
- console.log(` change_summary: ${p.changeSummary}`);
287
- if (p.supersededBy !== null)
288
- console.log(` superseded_by: #${p.supersededBy}`);
289
- if (p.supersededAt)
290
- console.log(` superseded_at: ${p.supersededAt}`);
291
- if (p.closedAt)
292
- console.log(` closed_at: ${p.closedAt}`);
293
- if (p.memoryId)
294
- console.log(` memory: ${p.memoryId}`);
295
- console.log(` created: ${p.createdAt}`);
232
+ if (results.length === 0) {
233
+ console.log('No policies.');
296
234
  return;
297
235
  }
298
- if (subcommand === 'supersede') {
299
- const idRaw = args[1];
300
- if (!idRaw) {
301
- printError('Usage: hippo policy supersede <id> --text "<rule>" [--from <iso>] [--to <iso>] [--change "<summary>"]');
302
- process.exit(1);
303
- }
304
- const id = parsePositivePolicyId(idRaw);
305
- const textRaw = flags['text'];
306
- if (typeof textRaw !== 'string' || !textRaw.trim()) {
307
- printError('hippo policy supersede requires --text "<rule>" for the new version.');
308
- process.exit(1);
309
- }
310
- const existing = policiesModule.loadPolicyById(hippoRoot, tenantId, id);
311
- if (!existing) {
312
- printError(`Policy ${id} not found.`);
313
- process.exit(1);
314
- }
315
- const fromRaw = flags['from'];
316
- const toRaw = flags['to'];
317
- const changeRaw = flags['change'];
318
- try {
319
- const created = policiesModule.savePolicy(hippoRoot, tenantId, {
320
- policyName: existing.policyName,
321
- policyText: textRaw,
322
- validFrom: typeof fromRaw === 'string' && fromRaw ? fromRaw : undefined,
323
- validTo: typeof toRaw === 'string' && toRaw ? toRaw : undefined,
324
- changeSummary: typeof changeRaw === 'string' && changeRaw ? changeRaw : undefined,
325
- supersedesPolicyId: id,
326
- extraTags: extractPathTags(process.cwd()),
327
- });
328
- console.log(`Policy #${created.id} recorded (v${created.version}), superseding #${id}.`);
329
- if (created.memoryId)
330
- console.log(` memory: ${created.memoryId}`);
331
- }
332
- catch (e) {
333
- printError(e.message);
334
- process.exit(1);
335
- }
336
- return;
236
+ console.log(`Found ${results.length} policies:\n`);
237
+ for (const p of results)
238
+ printPolicyRow(p);
239
+ }
240
+ function policyAsOf(hippoRoot, tenantId, args, flags) {
241
+ const dateRaw = args[1];
242
+ if (!dateRaw) {
243
+ printError('Usage: hippo policy asof <iso-date> [--name "<policy>"]');
244
+ process.exit(1);
337
245
  }
338
- if (subcommand === 'close') {
339
- const idRaw = args[1];
340
- if (!idRaw) {
341
- printError('Usage: hippo policy close <id>');
342
- process.exit(1);
343
- }
344
- const id = parsePositivePolicyId(idRaw);
345
- const closed = policiesModule.closePolicy(hippoRoot, tenantId, id);
346
- console.log(`Policy #${closed.id} closed.`);
246
+ const nameRaw = flags['name'];
247
+ const name = typeof nameRaw === 'string' && nameRaw ? nameRaw : undefined;
248
+ let results;
249
+ try {
250
+ results = policiesModule.loadPoliciesAsOf(hippoRoot, tenantId, dateRaw, { name });
251
+ }
252
+ catch (e) {
253
+ printError(e.message);
254
+ process.exit(1);
255
+ }
256
+ if (results.length === 0) {
257
+ console.log(`No active policies in force at ${dateRaw}${name ? ` for "${name}"` : ''}.`);
347
258
  return;
348
259
  }
260
+ console.log(`Policies in force at ${dateRaw}:\n`);
261
+ for (const p of results)
262
+ printPolicyRow(p);
263
+ }
264
+ function policyGet(hippoRoot, tenantId, args) {
265
+ const idRaw = args[1];
266
+ if (!idRaw) {
267
+ printError('Usage: hippo policy get <id>');
268
+ process.exit(1);
269
+ }
270
+ const id = parsePositivePolicyId(idRaw);
271
+ const p = policiesModule.loadPolicyById(hippoRoot, tenantId, id);
272
+ if (!p) {
273
+ printError(`Policy ${id} not found.`);
274
+ process.exit(1);
275
+ }
276
+ console.log(`Policy #${p.id}`);
277
+ console.log(` name: ${p.policyName}`);
278
+ console.log(` text: ${p.policyText}`);
279
+ console.log(` status: ${p.status}`);
280
+ console.log(` version: ${p.version}`);
281
+ console.log(` valid_from: ${p.validFrom}`);
282
+ console.log(` valid_to: ${p.validTo ?? '(open-ended)'}`);
283
+ if (p.changeSummary)
284
+ console.log(` change_summary: ${p.changeSummary}`);
285
+ if (p.supersededBy !== null)
286
+ console.log(` superseded_by: #${p.supersededBy}`);
287
+ if (p.supersededAt)
288
+ console.log(` superseded_at: ${p.supersededAt}`);
289
+ if (p.closedAt)
290
+ console.log(` closed_at: ${p.closedAt}`);
291
+ if (p.memoryId)
292
+ console.log(` memory: ${p.memoryId}`);
293
+ console.log(` created: ${p.createdAt}`);
294
+ }
295
+ function policySupersede(hippoRoot, tenantId, args, flags) {
296
+ const idRaw = args[1];
297
+ if (!idRaw) {
298
+ printError('Usage: hippo policy supersede <id> --text "<rule>" [--from <iso>] [--to <iso>] [--change "<summary>"]');
299
+ process.exit(1);
300
+ }
301
+ const id = parsePositivePolicyId(idRaw);
302
+ const textRaw = flags['text'];
303
+ if (typeof textRaw !== 'string' || !textRaw.trim()) {
304
+ printError('hippo policy supersede requires --text "<rule>" for the new version.');
305
+ process.exit(1);
306
+ }
307
+ const existing = policiesModule.loadPolicyById(hippoRoot, tenantId, id);
308
+ if (!existing) {
309
+ printError(`Policy ${id} not found.`);
310
+ process.exit(1);
311
+ }
312
+ const fromRaw = flags['from'];
313
+ const toRaw = flags['to'];
314
+ const changeRaw = flags['change'];
315
+ try {
316
+ const created = policiesModule.savePolicy(hippoRoot, tenantId, {
317
+ policyName: existing.policyName,
318
+ policyText: textRaw,
319
+ validFrom: typeof fromRaw === 'string' && fromRaw ? fromRaw : undefined,
320
+ validTo: typeof toRaw === 'string' && toRaw ? toRaw : undefined,
321
+ changeSummary: typeof changeRaw === 'string' && changeRaw ? changeRaw : undefined,
322
+ supersedesPolicyId: id,
323
+ extraTags: extractPathTags(process.cwd()),
324
+ });
325
+ console.log(`Policy #${created.id} recorded (v${created.version}), superseding #${id}.`);
326
+ if (created.memoryId)
327
+ console.log(` memory: ${created.memoryId}`);
328
+ }
329
+ catch (e) {
330
+ printError(e.message);
331
+ process.exit(1);
332
+ }
333
+ }
334
+ function policyClose(hippoRoot, tenantId, args) {
335
+ const idRaw = args[1];
336
+ if (!idRaw) {
337
+ printError('Usage: hippo policy close <id>');
338
+ process.exit(1);
339
+ }
340
+ const id = parsePositivePolicyId(idRaw);
341
+ const closed = policiesModule.closePolicy(hippoRoot, tenantId, id);
342
+ console.log(`Policy #${closed.id} closed.`);
343
+ }
344
+ export function cmdPolicy(hippoRoot, args, flags) {
345
+ requireInit(hippoRoot);
346
+ const tenantId = resolveTenantId({});
347
+ const subcommand = args[0] ?? '';
348
+ if (subcommand === 'list')
349
+ return policyList(hippoRoot, tenantId, flags);
350
+ if (subcommand === 'asof')
351
+ return policyAsOf(hippoRoot, tenantId, args, flags);
352
+ if (subcommand === 'get')
353
+ return policyGet(hippoRoot, tenantId, args);
354
+ if (subcommand === 'supersede')
355
+ return policySupersede(hippoRoot, tenantId, args, flags);
356
+ if (subcommand === 'close')
357
+ return policyClose(hippoRoot, tenantId, args);
358
+ policyCreate(hippoRoot, tenantId, args, flags);
359
+ }
360
+ function policyCreate(hippoRoot, tenantId, args, flags) {
361
+ const subcommand = args[0] ?? '';
349
362
  // Default subcommand: new (create). Accept both `policy new "<name>"` and the
350
363
  // bare `policy "<name>"` form: for the `new` keyword the name is args[1].
351
364
  const policyName = subcommand === 'new' ? (args[1] ?? '') : subcommand;
@@ -397,132 +410,136 @@ function printSkillRow(s) {
397
410
  if (s.changeSummary)
398
411
  console.log(` change: ${s.changeSummary}`);
399
412
  }
400
- export function cmdSkill(hippoRoot, args, flags) {
401
- requireInit(hippoRoot);
402
- const tenantId = resolveTenantId({});
403
- const subcommand = args[0] ?? '';
404
- if (subcommand === 'list') {
405
- const statusRaw = flags['status'];
406
- const status = typeof statusRaw === 'string' ? statusRaw.trim() : 'all';
407
- const limitRaw = flags['limit'];
408
- const limit = limitRaw !== undefined ? parseInt(String(limitRaw), 10) : 100;
409
- if (!Number.isFinite(limit) || limit <= 0) {
410
- printError(`Invalid --limit: "${limitRaw}". Must be a positive integer.`);
413
+ function skillList(hippoRoot, tenantId, flags) {
414
+ const statusRaw = flags['status'];
415
+ const status = typeof statusRaw === 'string' ? statusRaw.trim() : 'all';
416
+ const limit = parseListLimit(flags);
417
+ let results;
418
+ if (status === 'all') {
419
+ results = skillsModule.loadSkills(hippoRoot, tenantId, { limit });
420
+ }
421
+ else {
422
+ if (!skillsModule.VALID_SKILL_STATES.has(status)) {
423
+ printError(`Invalid --status: "${status}". Must be one of: active | superseded | closed | all.`);
411
424
  process.exit(1);
412
425
  }
413
- let results;
414
- if (status === 'all') {
415
- results = skillsModule.loadSkills(hippoRoot, tenantId, { limit });
416
- }
417
- else {
418
- if (!skillsModule.VALID_SKILL_STATES.has(status)) {
419
- printError(`Invalid --status: "${status}". Must be one of: active | superseded | closed | all.`);
420
- process.exit(1);
421
- }
422
- results = skillsModule.loadSkills(hippoRoot, tenantId, {
423
- status: status,
424
- limit,
425
- });
426
- }
427
- if (results.length === 0) {
428
- console.log('No skills.');
429
- return;
430
- }
431
- console.log(`Found ${results.length} skills:\n`);
432
- for (const s of results)
433
- printSkillRow(s);
434
- return;
426
+ results = skillsModule.loadSkills(hippoRoot, tenantId, {
427
+ status: status,
428
+ limit,
429
+ });
435
430
  }
436
- if (subcommand === 'export') {
437
- const md = skillsModule.exportSkills(hippoRoot, tenantId);
438
- if (!md) {
439
- console.log('No active skills.');
440
- return;
441
- }
442
- console.log(md);
431
+ if (results.length === 0) {
432
+ console.log('No skills.');
443
433
  return;
444
434
  }
445
- if (subcommand === 'get') {
446
- const idRaw = args[1];
447
- if (!idRaw) {
448
- printError('Usage: hippo skill get <id>');
449
- process.exit(1);
450
- }
451
- const id = parsePositiveSkillId(idRaw);
452
- const s = skillsModule.loadSkillById(hippoRoot, tenantId, id);
453
- if (!s) {
454
- printError(`Skill ${id} not found.`);
455
- process.exit(1);
456
- }
457
- console.log(`Skill #${s.id}`);
458
- console.log(` name: ${s.skillName}`);
459
- console.log(` status: ${s.status}`);
460
- console.log(` version: ${s.version}`);
461
- if (s.trigger)
462
- console.log(` when: ${s.trigger}`);
463
- console.log(` instructions: ${s.instructions}`);
464
- if (s.changeSummary)
465
- console.log(` change_summary: ${s.changeSummary}`);
466
- if (s.supersededBy !== null)
467
- console.log(` superseded_by: #${s.supersededBy}`);
468
- if (s.supersededAt)
469
- console.log(` superseded_at: ${s.supersededAt}`);
470
- if (s.closedAt)
471
- console.log(` closed_at: ${s.closedAt}`);
472
- if (s.memoryId)
473
- console.log(` memory: ${s.memoryId}`);
474
- console.log(` created: ${s.createdAt}`);
435
+ console.log(`Found ${results.length} skills:\n`);
436
+ for (const s of results)
437
+ printSkillRow(s);
438
+ }
439
+ function skillExport(hippoRoot, tenantId) {
440
+ const md = skillsModule.exportSkills(hippoRoot, tenantId);
441
+ if (!md) {
442
+ console.log('No active skills.');
475
443
  return;
476
444
  }
477
- if (subcommand === 'supersede') {
478
- const idRaw = args[1];
479
- if (!idRaw) {
480
- printError('Usage: hippo skill supersede <id> --instructions "<text>" [--trigger "<when>"] [--change "<summary>"]');
481
- process.exit(1);
482
- }
483
- const id = parsePositiveSkillId(idRaw);
484
- const instrRaw = flags['instructions'];
485
- if (typeof instrRaw !== 'string' || !instrRaw.trim()) {
486
- printError('hippo skill supersede requires --instructions "<text>" for the new version.');
487
- process.exit(1);
488
- }
489
- const existing = skillsModule.loadSkillById(hippoRoot, tenantId, id);
490
- if (!existing) {
491
- printError(`Skill ${id} not found.`);
492
- process.exit(1);
493
- }
494
- const trigRaw = flags['trigger'];
495
- const changeRaw = flags['change'];
496
- try {
497
- const created = skillsModule.saveSkill(hippoRoot, tenantId, {
498
- skillName: existing.skillName,
499
- instructions: instrRaw,
500
- trigger: typeof trigRaw === 'string' && trigRaw ? trigRaw : undefined,
501
- changeSummary: typeof changeRaw === 'string' && changeRaw ? changeRaw : undefined,
502
- supersedesSkillId: id,
503
- extraTags: extractPathTags(process.cwd()),
504
- });
505
- console.log(`Skill #${created.id} recorded (v${created.version}), superseding #${id}.`);
506
- if (created.memoryId)
507
- console.log(` memory: ${created.memoryId}`);
508
- }
509
- catch (e) {
510
- printError(e.message);
511
- process.exit(1);
512
- }
513
- return;
445
+ console.log(md);
446
+ }
447
+ function skillGet(hippoRoot, tenantId, args) {
448
+ const idRaw = args[1];
449
+ if (!idRaw) {
450
+ printError('Usage: hippo skill get <id>');
451
+ process.exit(1);
514
452
  }
515
- if (subcommand === 'close') {
516
- const idRaw = args[1];
517
- if (!idRaw) {
518
- printError('Usage: hippo skill close <id>');
519
- process.exit(1);
520
- }
521
- const id = parsePositiveSkillId(idRaw);
522
- const closed = skillsModule.closeSkill(hippoRoot, tenantId, id);
523
- console.log(`Skill #${closed.id} closed.`);
524
- return;
453
+ const id = parsePositiveSkillId(idRaw);
454
+ const s = skillsModule.loadSkillById(hippoRoot, tenantId, id);
455
+ if (!s) {
456
+ printError(`Skill ${id} not found.`);
457
+ process.exit(1);
458
+ }
459
+ console.log(`Skill #${s.id}`);
460
+ console.log(` name: ${s.skillName}`);
461
+ console.log(` status: ${s.status}`);
462
+ console.log(` version: ${s.version}`);
463
+ if (s.trigger)
464
+ console.log(` when: ${s.trigger}`);
465
+ console.log(` instructions: ${s.instructions}`);
466
+ if (s.changeSummary)
467
+ console.log(` change_summary: ${s.changeSummary}`);
468
+ if (s.supersededBy !== null)
469
+ console.log(` superseded_by: #${s.supersededBy}`);
470
+ if (s.supersededAt)
471
+ console.log(` superseded_at: ${s.supersededAt}`);
472
+ if (s.closedAt)
473
+ console.log(` closed_at: ${s.closedAt}`);
474
+ if (s.memoryId)
475
+ console.log(` memory: ${s.memoryId}`);
476
+ console.log(` created: ${s.createdAt}`);
477
+ }
478
+ function skillSupersede(hippoRoot, tenantId, args, flags) {
479
+ const idRaw = args[1];
480
+ if (!idRaw) {
481
+ printError('Usage: hippo skill supersede <id> --instructions "<text>" [--trigger "<when>"] [--change "<summary>"]');
482
+ process.exit(1);
483
+ }
484
+ const id = parsePositiveSkillId(idRaw);
485
+ const instrRaw = flags['instructions'];
486
+ if (typeof instrRaw !== 'string' || !instrRaw.trim()) {
487
+ printError('hippo skill supersede requires --instructions "<text>" for the new version.');
488
+ process.exit(1);
525
489
  }
490
+ const existing = skillsModule.loadSkillById(hippoRoot, tenantId, id);
491
+ if (!existing) {
492
+ printError(`Skill ${id} not found.`);
493
+ process.exit(1);
494
+ }
495
+ const trigRaw = flags['trigger'];
496
+ const changeRaw = flags['change'];
497
+ try {
498
+ const created = skillsModule.saveSkill(hippoRoot, tenantId, {
499
+ skillName: existing.skillName,
500
+ instructions: instrRaw,
501
+ trigger: typeof trigRaw === 'string' && trigRaw ? trigRaw : undefined,
502
+ changeSummary: typeof changeRaw === 'string' && changeRaw ? changeRaw : undefined,
503
+ supersedesSkillId: id,
504
+ extraTags: extractPathTags(process.cwd()),
505
+ });
506
+ console.log(`Skill #${created.id} recorded (v${created.version}), superseding #${id}.`);
507
+ if (created.memoryId)
508
+ console.log(` memory: ${created.memoryId}`);
509
+ }
510
+ catch (e) {
511
+ printError(e.message);
512
+ process.exit(1);
513
+ }
514
+ }
515
+ function skillClose(hippoRoot, tenantId, args) {
516
+ const idRaw = args[1];
517
+ if (!idRaw) {
518
+ printError('Usage: hippo skill close <id>');
519
+ process.exit(1);
520
+ }
521
+ const id = parsePositiveSkillId(idRaw);
522
+ const closed = skillsModule.closeSkill(hippoRoot, tenantId, id);
523
+ console.log(`Skill #${closed.id} closed.`);
524
+ }
525
+ export function cmdSkill(hippoRoot, args, flags) {
526
+ requireInit(hippoRoot);
527
+ const tenantId = resolveTenantId({});
528
+ const subcommand = args[0] ?? '';
529
+ if (subcommand === 'list')
530
+ return skillList(hippoRoot, tenantId, flags);
531
+ if (subcommand === 'export')
532
+ return skillExport(hippoRoot, tenantId);
533
+ if (subcommand === 'get')
534
+ return skillGet(hippoRoot, tenantId, args);
535
+ if (subcommand === 'supersede')
536
+ return skillSupersede(hippoRoot, tenantId, args, flags);
537
+ if (subcommand === 'close')
538
+ return skillClose(hippoRoot, tenantId, args);
539
+ skillCreate(hippoRoot, tenantId, args, flags);
540
+ }
541
+ function skillCreate(hippoRoot, tenantId, args, flags) {
542
+ const subcommand = args[0] ?? '';
526
543
  // Default subcommand: new (create). Accept both `skill new "<name>"` and the
527
544
  // bare `skill "<name>"` form: for the `new` keyword the name is args[1].
528
545
  const skillName = subcommand === 'new' ? (args[1] ?? '') : subcommand;