hippo-memory 1.58.0 → 1.59.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 +13 -1
- package/dist/agent-memories/apply.d.ts +1 -1
- package/dist/agent-memories/legacy.js +4 -1
- package/dist/audit.d.ts +1 -1
- package/dist/audit.js +2 -0
- package/dist/cli/output.d.ts +3 -0
- package/dist/cli/output.js +7 -0
- package/dist/cli/projects.d.ts +4 -0
- package/dist/cli/projects.js +90 -0
- package/dist/cli/shared.js +14 -8
- package/dist/cli/sleep.js +5 -3
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +389 -361
- package/dist/consolidate.js +12 -1
- package/dist/dag.js +4 -2
- package/dist/dedupe.js +1 -1
- package/dist/doctor.js +23 -0
- package/dist/dormant.d.ts +2 -2
- package/dist/extract.js +16 -16
- package/dist/project-merge.d.ts +52 -0
- package/dist/project-merge.js +168 -0
- package/dist/recall-scope.d.ts +3 -3
- package/dist/recall-scope.js +5 -4
- package/dist/rerankers/clef.d.ts +29 -0
- package/dist/rerankers/clef.js +182 -0
- package/dist/rerankers/index.js +3 -0
- package/dist/rerankers/jev.d.ts +11 -0
- package/dist/rerankers/jev.js +10 -5
- package/dist/rerankers/types.d.ts +16 -0
- package/dist/server.d.ts +6 -1
- package/dist/server.js +9 -3
- package/dist/store.js +6 -6
- 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 +5 -2
package/dist/cli.js
CHANGED
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
* hippo rejections
|
|
21
21
|
* hippo unreject <digest-prefix>
|
|
22
22
|
* hippo dormant [<query>] [--limit <n>] [--json] | restore <id> | forget <id>
|
|
23
|
+
* hippo projects [--json] | merge <from> <into> [--apply] | repair [--apply]
|
|
23
24
|
* hippo tokens [--days <n>] [--json] [--global]
|
|
24
25
|
* hippo doctor [--json]
|
|
25
26
|
* hippo inspect <id>
|
|
@@ -135,6 +136,7 @@ import { MAX_HOPS, DEFAULT_MAX_NEIGHBORS } from './graph-recall.js';
|
|
|
135
136
|
import { getReranker } from './rerankers/index.js';
|
|
136
137
|
import { rankRecall } from './recall-pipeline.js';
|
|
137
138
|
import { JEV_DEFAULT_TOP_K } from './rerankers/jev.js';
|
|
139
|
+
import { isClefModel } from './rerankers/clef.js';
|
|
138
140
|
import { computeSalience } from './salience.js';
|
|
139
141
|
import { renderAmbientSummary } from './ambient.js';
|
|
140
142
|
import { assembleCost, assembleHeading, contextCost, contextHeading, contextLine, crossProjectHeading, crossProjectLine, drillCost, handoffText, printedTokens, sessionTrailText, settleTokens, snapshotText, } from './context-render.js';
|
|
@@ -145,6 +147,8 @@ import { backfillChannel } from './connectors/slack/backfill.js';
|
|
|
145
147
|
import { slackHistoryFetcher } from './connectors/slack/web-client.js';
|
|
146
148
|
import { addWorkspace as addSlackWorkspace, listWorkspaces as listSlackWorkspaces, removeWorkspace as removeSlackWorkspace, } from './connectors/slack/workspaces.js';
|
|
147
149
|
import { cmdGithub, printGithubBackfillUsage } from './connectors/github/cli-impl.js';
|
|
150
|
+
import { log } from './log.js';
|
|
151
|
+
import { printError } from './cli/output.js';
|
|
148
152
|
import { parseLimitFlag, parseCountFlag, parseBudgetFlag, emitCliAudit, requireInit, runChurnStaleForRepo, runViaServerIfAvailable, fmt, recallEntryText, recallHeading, printAgentImport, hippoBlock, installCodexMemoryHooks, setupDailySchedule, parseAsOfFlag, engineFlags, collectHandoffEvidence, logSessionEndImport, appendSessionEndCloseLog, printActiveTaskSnapshot, printSessionEvents, printHandoff, cardStringFlag, hostSessionId, resetHookInjection, captureConsole, hookStoreRoot, withLedgerDb, learnFromRepo, HOOK_MARKERS, HOOKS, resolveAuthRoot, } from './cli/shared.js';
|
|
149
153
|
// ---------------------------------------------------------------------------
|
|
150
154
|
// Helpers
|
|
@@ -153,7 +157,7 @@ import { parseLimitFlag, parseCountFlag, parseBudgetFlag, emitCliAudit, requireI
|
|
|
153
157
|
// and as off under === true (`--pin=true` would not pin), so parseArgs and main() refuse one.
|
|
154
158
|
// tests/cli-parse-flag-equals.test.ts fails when a switch read is missing from this set.
|
|
155
159
|
export const BOOLEAN_FLAGS = new Set([
|
|
156
|
-
'agents', 'all', 'all-tenants', 'archive', 'auto', 'bad', 'bootstrap', 'classic', 'churn', 'continuity',
|
|
160
|
+
'agents', 'all', 'all-tenants', 'apply', 'archive', 'auto', 'bad', 'bootstrap', 'classic', 'churn', 'continuity',
|
|
157
161
|
'cross-project', 'dry-run', 'equal-sources', 'error', 'evc-adaptive', 'extract',
|
|
158
162
|
'filter-conflicts', 'fix', 'force', 'forget', 'git', 'global', 'good', 'graph-stream',
|
|
159
163
|
'help', 'include-logs', 'include-superseded', 'inferred', 'json', 'last-session', 'multihop', 'no-hooks',
|
|
@@ -181,7 +185,7 @@ export const KNOWN_FLAGS = new Set([
|
|
|
181
185
|
]);
|
|
182
186
|
// Commands that delete or hide memories: an unknown flag here stops the run instead of being ignored.
|
|
183
187
|
const DESTRUCTIVE_COMMANDS = new Set([
|
|
184
|
-
'audit', 'dedup', 'forget', 'invalidate', 'reject', 'resolve', 'sleep', 'supersede',
|
|
188
|
+
'audit', 'dedup', 'forget', 'invalidate', 'projects', 'reject', 'resolve', 'sleep', 'supersede',
|
|
185
189
|
]);
|
|
186
190
|
// Commands that honour --dry-run. Any other command would ignore it and run for real.
|
|
187
191
|
const DRY_RUN_COMMANDS = new Set([
|
|
@@ -637,18 +641,18 @@ async function cmdRemember(hippoRoot, text, flags) {
|
|
|
637
641
|
// 'archived' is an internal sentinel set only inside archiveRawMemory's transaction.
|
|
638
642
|
const userVisibleKinds = ['distilled', 'superseded'];
|
|
639
643
|
if (kindFlag !== undefined && !userVisibleKinds.includes(kindFlag)) {
|
|
640
|
-
|
|
641
|
-
|
|
644
|
+
printError(`Invalid --kind: "${kindFlagRaw}". Must be one of: ${userVisibleKinds.join(', ')}`);
|
|
645
|
+
printError(`(kind='raw' is reserved for ingestion connectors; kind='archived' is internal.)`);
|
|
642
646
|
process.exit(1);
|
|
643
647
|
}
|
|
644
648
|
const ownerRaw = typeof flags['owner'] === 'string' ? flags['owner'] : null;
|
|
645
649
|
const ownerCheck = validateOwner(ownerRaw, { strict: isStrictOwnerEnv() });
|
|
646
650
|
if (!ownerCheck.ok) {
|
|
647
|
-
|
|
651
|
+
printError(ownerCheck.message);
|
|
648
652
|
process.exit(1);
|
|
649
653
|
}
|
|
650
654
|
if (ownerCheck.message)
|
|
651
|
-
|
|
655
|
+
printError(ownerCheck.message);
|
|
652
656
|
const ownerFlag = ownerCheck.value ?? null;
|
|
653
657
|
const artifactRefFlag = typeof flags['artifact-ref'] === 'string' ? flags['artifact-ref'] : null;
|
|
654
658
|
const scopeForEnvelope = typeof flags['scope'] === 'string' ? flags['scope'].trim() || null : null;
|
|
@@ -698,7 +702,7 @@ async function cmdRemember(hippoRoot, text, flags) {
|
|
|
698
702
|
if (entry.pinned)
|
|
699
703
|
console.log(' Pinned (no decay)');
|
|
700
704
|
for (const w of vetSecrets(entry.content, entry.tags, false).warnings)
|
|
701
|
-
|
|
705
|
+
printError(`Warning: ${w}`);
|
|
702
706
|
void embedMemory(targetRoot, entry);
|
|
703
707
|
const config = loadConfig(targetRoot);
|
|
704
708
|
const shouldExtract = flags['extract'] || config.extraction.enabled === true;
|
|
@@ -709,31 +713,31 @@ async function cmdRemember(hippoRoot, text, flags) {
|
|
|
709
713
|
const facts = await extractFacts(entry.content, {
|
|
710
714
|
apiKey,
|
|
711
715
|
model: config.extraction.model,
|
|
712
|
-
onError: (msg) =>
|
|
716
|
+
onError: (msg) => printError(` (extraction failed: ${msg})`),
|
|
713
717
|
});
|
|
714
718
|
if (facts.length > 0) {
|
|
715
719
|
storeExtractedFacts(targetRoot, entry, facts);
|
|
716
|
-
|
|
720
|
+
printError(` extracted ${facts.length} fact(s)`);
|
|
717
721
|
}
|
|
718
722
|
}
|
|
719
723
|
catch (err) {
|
|
720
724
|
// Extraction is best-effort: report it, never block remember.
|
|
721
|
-
|
|
725
|
+
printError(` (extraction failed: ${err instanceof Error ? err.message : String(err)})`);
|
|
722
726
|
}
|
|
723
727
|
}
|
|
724
728
|
else if (shouldExtract && !apiKey) {
|
|
725
|
-
|
|
729
|
+
printError(' (extraction skipped: ANTHROPIC_API_KEY not set)');
|
|
726
730
|
}
|
|
727
731
|
}
|
|
728
732
|
function cmdSupersede(hippoRoot, oldId, newContent, flags) {
|
|
729
733
|
requireInit(hippoRoot);
|
|
730
734
|
const old = readEntry(hippoRoot, oldId, resolveTenantId({}));
|
|
731
735
|
if (!old) {
|
|
732
|
-
|
|
736
|
+
printError(`Error: memory ${oldId} not found.`);
|
|
733
737
|
process.exit(1);
|
|
734
738
|
}
|
|
735
739
|
if (old.superseded_by) {
|
|
736
|
-
|
|
740
|
+
printError(`Error: memory ${oldId} is already superseded by ${old.superseded_by}. Supersede that one instead.`);
|
|
737
741
|
process.exit(1);
|
|
738
742
|
}
|
|
739
743
|
const layer = typeof flags['layer'] === 'string' ? flags['layer'] : undefined;
|
|
@@ -765,7 +769,7 @@ function cmdSupersede(hippoRoot, oldId, newContent, flags) {
|
|
|
765
769
|
}
|
|
766
770
|
catch (err) {
|
|
767
771
|
if (err instanceof RejectedValueError) {
|
|
768
|
-
|
|
772
|
+
printError(`Error: ${err.message}`);
|
|
769
773
|
process.exit(1);
|
|
770
774
|
}
|
|
771
775
|
throw err;
|
|
@@ -777,7 +781,7 @@ function cmdSupersede(hippoRoot, oldId, newContent, flags) {
|
|
|
777
781
|
}
|
|
778
782
|
function failWith(message) {
|
|
779
783
|
return () => {
|
|
780
|
-
|
|
784
|
+
printError(message);
|
|
781
785
|
process.exit(1);
|
|
782
786
|
};
|
|
783
787
|
}
|
|
@@ -839,7 +843,7 @@ function parseRerankerFlag(flags) {
|
|
|
839
843
|
return {};
|
|
840
844
|
const topK = flags['reranker-top-k'] !== undefined
|
|
841
845
|
? parseInt(String(flags['reranker-top-k']), 10)
|
|
842
|
-
: name === 'jev' ? JEV_DEFAULT_TOP_K : 50;
|
|
846
|
+
: name === 'jev' || isClefModel(name) ? JEV_DEFAULT_TOP_K : 50;
|
|
843
847
|
return { value: { fn, topK } };
|
|
844
848
|
}
|
|
845
849
|
function parseSalienceFlag(flags) {
|
|
@@ -917,7 +921,7 @@ async function cmdRecall(hippoRoot, query, flags) {
|
|
|
917
921
|
const entryText = (r) => recallEntryText(r, query, showWhy, primaryIsGlobal || (globalOn && !localIndex.entries[r.entry.id]));
|
|
918
922
|
const printCost = (r) => printedTokens(entryText(r));
|
|
919
923
|
const entryBudget = Math.max(0, budget - printedTokens(recallHeading(budget, budget, query)));
|
|
920
|
-
const rank = await rankRecall({ hippoRoot, globalRoot: globalRoot !== hippoRoot && globalOn ? globalRoot : undefined, tenantId, note: (line) =>
|
|
924
|
+
const rank = await rankRecall({ hippoRoot, globalRoot: globalRoot !== hippoRoot && globalOn ? globalRoot : undefined, tenantId, note: (line) => printError(line) }, {
|
|
921
925
|
query, budget: entryBudget, cost: printCost, limit, why: showWhy, includeSuperseded, asOf,
|
|
922
926
|
explicitScope: recallExplicitScope, activeScope: recallActiveScope,
|
|
923
927
|
search: { ...engineFlags(flags, config), multihop: flags['multihop'] === true || config.multihop.enabled, graphStream, minResults, explain: false },
|
|
@@ -1281,7 +1285,7 @@ function noteScopeHidden(hippoRoot, globalRoot, query, tenantId, requested) {
|
|
|
1281
1285
|
// Window-capped, so the count is a floor on large stores; fine for a "why is my row missing" hint.
|
|
1282
1286
|
const hidden = probe.filter((e) => !api.passesCliRecallScopeFilter(e.scope ?? null, requested)).length;
|
|
1283
1287
|
if (hidden > 0) {
|
|
1284
|
-
|
|
1288
|
+
printError(`[note] ${hidden} candidate${hidden === 1 ? '' : 's'} hidden by recall scope policy (pass an explicit --scope to inspect).`);
|
|
1285
1289
|
}
|
|
1286
1290
|
}
|
|
1287
1291
|
async function cmdExplain(hippoRoot, query, flags) {
|
|
@@ -1441,7 +1445,7 @@ async function cmdEval(hippoRoot, corpusPath, flags) {
|
|
|
1441
1445
|
baseline = JSON.parse(fs.readFileSync(baselinePath, 'utf8'));
|
|
1442
1446
|
}
|
|
1443
1447
|
catch {
|
|
1444
|
-
|
|
1448
|
+
printError(`Warning: eval baseline ${baselinePath} is unreadable; running without it.`);
|
|
1445
1449
|
}
|
|
1446
1450
|
}
|
|
1447
1451
|
const result = await runFeatureEval(version);
|
|
@@ -1466,11 +1470,11 @@ async function cmdEval(hippoRoot, corpusPath, flags) {
|
|
|
1466
1470
|
return;
|
|
1467
1471
|
}
|
|
1468
1472
|
if (!corpusPath) {
|
|
1469
|
-
|
|
1473
|
+
printError('Usage: hippo eval <corpus.json> OR hippo eval --suite [--save-baseline] OR hippo eval --bootstrap');
|
|
1470
1474
|
process.exit(1);
|
|
1471
1475
|
}
|
|
1472
1476
|
if (!fs.existsSync(corpusPath)) {
|
|
1473
|
-
|
|
1477
|
+
printError(`Corpus file not found: ${corpusPath}`);
|
|
1474
1478
|
process.exit(1);
|
|
1475
1479
|
}
|
|
1476
1480
|
let cases;
|
|
@@ -1481,7 +1485,7 @@ async function cmdEval(hippoRoot, corpusPath, flags) {
|
|
|
1481
1485
|
throw new Error('Corpus JSON must be an array or { cases: [...] }');
|
|
1482
1486
|
}
|
|
1483
1487
|
catch (err) {
|
|
1484
|
-
|
|
1488
|
+
printError(`Failed to read corpus: ${err instanceof Error ? err.message : err}`);
|
|
1485
1489
|
process.exit(1);
|
|
1486
1490
|
}
|
|
1487
1491
|
const globalRoot = getGlobalRoot();
|
|
@@ -1539,12 +1543,12 @@ async function cmdEval(hippoRoot, corpusPath, flags) {
|
|
|
1539
1543
|
}
|
|
1540
1544
|
}
|
|
1541
1545
|
if (minMrr !== null && summary.meanMrr < minMrr) {
|
|
1542
|
-
|
|
1546
|
+
printError(`MRR ${fmt(summary.meanMrr, 4)} below threshold ${minMrr}`);
|
|
1543
1547
|
process.exit(1);
|
|
1544
1548
|
}
|
|
1545
1549
|
if (comparePath) {
|
|
1546
1550
|
if (!fs.existsSync(comparePath)) {
|
|
1547
|
-
|
|
1551
|
+
printError(`Baseline file not found: ${comparePath}`);
|
|
1548
1552
|
process.exit(1);
|
|
1549
1553
|
}
|
|
1550
1554
|
let baseline;
|
|
@@ -1552,14 +1556,14 @@ async function cmdEval(hippoRoot, corpusPath, flags) {
|
|
|
1552
1556
|
baseline = JSON.parse(fs.readFileSync(comparePath, 'utf8'));
|
|
1553
1557
|
}
|
|
1554
1558
|
catch (err) {
|
|
1555
|
-
|
|
1559
|
+
printError(`Failed to parse baseline: ${err instanceof Error ? err.message : err}`);
|
|
1556
1560
|
process.exit(1);
|
|
1557
1561
|
}
|
|
1558
1562
|
const cmp = compareSummaries(baseline, summary);
|
|
1559
1563
|
if (asJson) {
|
|
1560
1564
|
// The main JSON output already emitted; append comparison to stderr so
|
|
1561
1565
|
// both can be captured independently.
|
|
1562
|
-
|
|
1566
|
+
printError(JSON.stringify({ compare: cmp }, null, 2));
|
|
1563
1567
|
}
|
|
1564
1568
|
else {
|
|
1565
1569
|
console.log();
|
|
@@ -1596,11 +1600,11 @@ function cmdTraceRecord(hippoRoot, flags) {
|
|
|
1596
1600
|
const outcome = String(flags['outcome'] ?? '').trim();
|
|
1597
1601
|
const validOutcomes = ['success', 'failure', 'partial'];
|
|
1598
1602
|
if (!task || !stepsJson || !outcome) {
|
|
1599
|
-
|
|
1603
|
+
printError('Usage: hippo trace record --task <t> --steps <json> --outcome <success|failure|partial> [--session <id>] [--tag <t>]');
|
|
1600
1604
|
process.exit(1);
|
|
1601
1605
|
}
|
|
1602
1606
|
if (!validOutcomes.includes(outcome)) {
|
|
1603
|
-
|
|
1607
|
+
printError(`Invalid outcome: "${outcome}". Must be one of: ${validOutcomes.join(', ')}.`);
|
|
1604
1608
|
process.exit(1);
|
|
1605
1609
|
}
|
|
1606
1610
|
let steps;
|
|
@@ -1608,7 +1612,7 @@ function cmdTraceRecord(hippoRoot, flags) {
|
|
|
1608
1612
|
steps = parseSteps(stepsJson);
|
|
1609
1613
|
}
|
|
1610
1614
|
catch (err) {
|
|
1611
|
-
|
|
1615
|
+
printError(String(err instanceof Error ? err.message : err));
|
|
1612
1616
|
process.exit(1);
|
|
1613
1617
|
}
|
|
1614
1618
|
const sessionId = String(flags['session'] ?? '').trim() || null;
|
|
@@ -1648,7 +1652,7 @@ function cmdTrace(hippoRoot, id, flags) {
|
|
|
1648
1652
|
sourceLabel = 'global';
|
|
1649
1653
|
}
|
|
1650
1654
|
if (!entry) {
|
|
1651
|
-
|
|
1655
|
+
printError(`Memory not found: ${id}`);
|
|
1652
1656
|
process.exit(1);
|
|
1653
1657
|
}
|
|
1654
1658
|
const now = evalNow();
|
|
@@ -1743,7 +1747,7 @@ async function cmdRefine(hippoRoot, flags) {
|
|
|
1743
1747
|
requireInit(hippoRoot);
|
|
1744
1748
|
const apiKey = process.env.ANTHROPIC_API_KEY;
|
|
1745
1749
|
if (!apiKey) {
|
|
1746
|
-
|
|
1750
|
+
printError('hippo refine needs ANTHROPIC_API_KEY in the environment.');
|
|
1747
1751
|
process.exit(1);
|
|
1748
1752
|
}
|
|
1749
1753
|
const dryRun = Boolean(flags['dry-run']);
|
|
@@ -1778,7 +1782,7 @@ function cmdDedup(hippoRoot, flags) {
|
|
|
1778
1782
|
requireInit(hippoRoot);
|
|
1779
1783
|
const dryRun = Boolean(flags['dry-run']);
|
|
1780
1784
|
if (flags['threshold'] !== undefined) {
|
|
1781
|
-
|
|
1785
|
+
printError('hippo dedup: --threshold is ignored; a duplicate is the same text apart from spacing.');
|
|
1782
1786
|
}
|
|
1783
1787
|
const entries = loadAllEntries(hippoRoot);
|
|
1784
1788
|
console.log(`Scanning ${entries.length} memories for duplicates (same text apart from spacing)${dryRun ? ' (dry run)' : ''}...\n`);
|
|
@@ -1830,14 +1834,15 @@ function cmdLastSleep(flags) {
|
|
|
1830
1834
|
content = fs.readFileSync(logPath, 'utf8');
|
|
1831
1835
|
}
|
|
1832
1836
|
catch {
|
|
1837
|
+
// Removed or locked since the exists check: there is nothing to show this session.
|
|
1833
1838
|
return;
|
|
1834
1839
|
}
|
|
1835
1840
|
if (content.trim().length > 0) {
|
|
1836
|
-
|
|
1841
|
+
printError('=== Previous session hippo consolidation ===');
|
|
1837
1842
|
process.stderr.write(content);
|
|
1838
1843
|
if (!content.endsWith('\n'))
|
|
1839
|
-
|
|
1840
|
-
|
|
1844
|
+
printError();
|
|
1845
|
+
printError('===========================================');
|
|
1841
1846
|
}
|
|
1842
1847
|
if (!flags['keep']) {
|
|
1843
1848
|
try {
|
|
@@ -1888,6 +1893,7 @@ function cmdCompactResume(hippoRoot, stdinText, stdinTimedOut) {
|
|
|
1888
1893
|
payload = JSON.parse(stdinText.trim());
|
|
1889
1894
|
}
|
|
1890
1895
|
catch {
|
|
1896
|
+
// Malformed JSON is handled as a null payload by the fail-closed check below.
|
|
1891
1897
|
payload = null;
|
|
1892
1898
|
}
|
|
1893
1899
|
if (!payload || typeof payload !== 'object') {
|
|
@@ -1938,7 +1944,7 @@ function cmdCompactResume(hippoRoot, stdinText, stdinTimedOut) {
|
|
|
1938
1944
|
}
|
|
1939
1945
|
}
|
|
1940
1946
|
catch (err) {
|
|
1941
|
-
|
|
1947
|
+
log.warn(`hippo compact-resume: trail skipped: ${err instanceof Error ? err.message : String(err)}`);
|
|
1942
1948
|
}
|
|
1943
1949
|
// Printed in one write so the ledger books exactly the text the model is handed.
|
|
1944
1950
|
const text = captureConsole(() => {
|
|
@@ -1961,7 +1967,7 @@ function cmdCompactResume(hippoRoot, stdinText, stdinTimedOut) {
|
|
|
1961
1967
|
}
|
|
1962
1968
|
catch (err) {
|
|
1963
1969
|
// Empty stdout on any store error, never a crashed SessionStart; the reason goes to stderr, which the model never sees.
|
|
1964
|
-
|
|
1970
|
+
log.warn(`hippo compact-resume: skipped: ${err instanceof Error ? err.message : String(err)}`);
|
|
1965
1971
|
}
|
|
1966
1972
|
process.exit(0);
|
|
1967
1973
|
}
|
|
@@ -2200,7 +2206,7 @@ function cmdCodexRun(hippoRoot, args) {
|
|
|
2200
2206
|
}
|
|
2201
2207
|
const child = spawnRealCodex(metadata.realCodexPath, args, process.cwd());
|
|
2202
2208
|
child.on('error', (err) => {
|
|
2203
|
-
|
|
2209
|
+
printError(`Failed to launch Codex: ${err.message}`);
|
|
2204
2210
|
process.exit(1);
|
|
2205
2211
|
});
|
|
2206
2212
|
child.on('exit', async (code, signal) => {
|
|
@@ -2247,6 +2253,7 @@ function cmdCodexRun(hippoRoot, args) {
|
|
|
2247
2253
|
process.kill(process.pid, signal);
|
|
2248
2254
|
}
|
|
2249
2255
|
catch {
|
|
2256
|
+
// Cannot re-raise the child's signal on this platform; a non-zero exit still reports the failure.
|
|
2250
2257
|
process.exit(1);
|
|
2251
2258
|
}
|
|
2252
2259
|
return;
|
|
@@ -2417,6 +2424,7 @@ function cmdStatus(hippoRoot) {
|
|
|
2417
2424
|
return resolveEmbeddingProvider(hippoRoot);
|
|
2418
2425
|
}
|
|
2419
2426
|
catch {
|
|
2427
|
+
// Status reports a bad provider config as "misconfigured" below instead of failing.
|
|
2420
2428
|
return null;
|
|
2421
2429
|
}
|
|
2422
2430
|
})();
|
|
@@ -2493,7 +2501,7 @@ function cmdOutcome(hippoRoot, flags) {
|
|
|
2493
2501
|
const good = Boolean(flags['good']);
|
|
2494
2502
|
const bad = Boolean(flags['bad']);
|
|
2495
2503
|
if (!good && !bad) {
|
|
2496
|
-
|
|
2504
|
+
printError('Specify --good or --bad');
|
|
2497
2505
|
process.exit(1);
|
|
2498
2506
|
}
|
|
2499
2507
|
// Behavior fix (v1.11.3): cmdOutcome used to bypass api.outcome and do its
|
|
@@ -2536,7 +2544,7 @@ function cmdForget(hippoRoot, id, flags) {
|
|
|
2536
2544
|
if (flags['archive'] === true) {
|
|
2537
2545
|
const reason = typeof flags['reason'] === 'string' ? flags['reason'] : null;
|
|
2538
2546
|
if (!reason) {
|
|
2539
|
-
|
|
2547
|
+
printError(ARCHIVE_REASON_REQUIRED);
|
|
2540
2548
|
process.exit(1);
|
|
2541
2549
|
}
|
|
2542
2550
|
try {
|
|
@@ -2544,7 +2552,7 @@ function cmdForget(hippoRoot, id, flags) {
|
|
|
2544
2552
|
console.log(`Archived ${id}`);
|
|
2545
2553
|
}
|
|
2546
2554
|
catch (err) {
|
|
2547
|
-
|
|
2555
|
+
printError(`Could not archive ${id}: ${err instanceof Error ? err.message : String(err)}`);
|
|
2548
2556
|
process.exit(1);
|
|
2549
2557
|
}
|
|
2550
2558
|
return;
|
|
@@ -2558,16 +2566,16 @@ function cmdForget(hippoRoot, id, flags) {
|
|
|
2558
2566
|
if (/append-only/i.test(msg)) {
|
|
2559
2567
|
// The delete was refused by the append-only trigger — this is a raw
|
|
2560
2568
|
// memory, not a missing one. Point the user at the archive path.
|
|
2561
|
-
|
|
2569
|
+
printError(rawForgetRefusal(id));
|
|
2562
2570
|
}
|
|
2563
2571
|
else if (api.isDormant(ctx, id)) {
|
|
2564
2572
|
// Sleep moved it to the dormant store: it is not in active memory, so
|
|
2565
2573
|
// point at the command that owns it.
|
|
2566
|
-
|
|
2574
|
+
printError(`${id} is dormant, not in active memory. Delete it for good: hippo dormant forget ${id} ` +
|
|
2567
2575
|
`(or bring it back: hippo dormant restore ${id})`);
|
|
2568
2576
|
}
|
|
2569
2577
|
else {
|
|
2570
|
-
|
|
2578
|
+
printError(`Memory not found: ${id}`);
|
|
2571
2579
|
}
|
|
2572
2580
|
process.exit(1);
|
|
2573
2581
|
}
|
|
@@ -2580,15 +2588,15 @@ function previewForget(hippoRoot, id, archive) {
|
|
|
2580
2588
|
requireInit(hippoRoot);
|
|
2581
2589
|
const entry = readEntry(hippoRoot, id, resolveTenantId({}));
|
|
2582
2590
|
if (!entry) {
|
|
2583
|
-
|
|
2591
|
+
printError(`Memory not found: ${id}`);
|
|
2584
2592
|
process.exit(1);
|
|
2585
2593
|
}
|
|
2586
2594
|
if (!archive && entry.kind === 'raw') {
|
|
2587
|
-
|
|
2595
|
+
printError(rawForgetRefusal(id));
|
|
2588
2596
|
process.exit(1);
|
|
2589
2597
|
}
|
|
2590
2598
|
if (archive && entry.kind !== 'raw') {
|
|
2591
|
-
|
|
2599
|
+
printError(`Could not archive ${id}: memory ${id} is not raw (kind=${entry.kind})`);
|
|
2592
2600
|
process.exit(1);
|
|
2593
2601
|
}
|
|
2594
2602
|
const snippet = entry.content.length > 80 ? `${entry.content.slice(0, 80)}...` : entry.content;
|
|
@@ -2598,7 +2606,7 @@ function cmdInspect(hippoRoot, id) {
|
|
|
2598
2606
|
requireInit(hippoRoot);
|
|
2599
2607
|
const entry = readEntry(hippoRoot, id, resolveTenantId({}));
|
|
2600
2608
|
if (!entry) {
|
|
2601
|
-
|
|
2609
|
+
printError(`Memory not found: ${id}`);
|
|
2602
2610
|
process.exit(1);
|
|
2603
2611
|
}
|
|
2604
2612
|
const now = evalNow();
|
|
@@ -2661,7 +2669,7 @@ function cmdResolve(hippoRoot, args, flags) {
|
|
|
2661
2669
|
// Accept "42" or "conflict_42"
|
|
2662
2670
|
const conflictId = parseInt(rawId.replace(/^conflict_/, ''), 10);
|
|
2663
2671
|
if (isNaN(conflictId)) {
|
|
2664
|
-
|
|
2672
|
+
printError('Usage: hippo resolve <conflict_id> --keep <memory_id> [--forget]');
|
|
2665
2673
|
process.exit(1);
|
|
2666
2674
|
}
|
|
2667
2675
|
const tenantId = resolveTenantId({});
|
|
@@ -2671,7 +2679,7 @@ function cmdResolve(hippoRoot, args, flags) {
|
|
|
2671
2679
|
const conflicts = listMemoryConflicts(hippoRoot, 'open', tenantId);
|
|
2672
2680
|
const conflict = conflicts.find((c) => c.id === conflictId);
|
|
2673
2681
|
if (!conflict) {
|
|
2674
|
-
|
|
2682
|
+
printError(`Conflict ${conflictId} not found or already resolved.`);
|
|
2675
2683
|
process.exit(1);
|
|
2676
2684
|
}
|
|
2677
2685
|
console.log(`Conflict ${conflictId}:`);
|
|
@@ -2705,7 +2713,7 @@ function cmdResolve(hippoRoot, args, flags) {
|
|
|
2705
2713
|
reason: reasonFlag,
|
|
2706
2714
|
});
|
|
2707
2715
|
if (!result) {
|
|
2708
|
-
|
|
2716
|
+
printError(`Could not resolve conflict ${conflictId}. Check the ID and --keep value.`);
|
|
2709
2717
|
process.exit(1);
|
|
2710
2718
|
}
|
|
2711
2719
|
const action = rejectLoser
|
|
@@ -2728,20 +2736,20 @@ function cmdReject(hippoRoot, args, flags) {
|
|
|
2728
2736
|
// content, so reason is its only human-readable identity.
|
|
2729
2737
|
const reason = typeof flags['reason'] === 'string' ? flags['reason'].trim() : '';
|
|
2730
2738
|
if (!reason) {
|
|
2731
|
-
|
|
2739
|
+
printError('hippo reject requires --reason "<why>" (the tombstone stores no content; reason is its only identity).');
|
|
2732
2740
|
process.exit(1);
|
|
2733
2741
|
}
|
|
2734
2742
|
const valueFlag = typeof flags['value'] === 'string' ? flags['value'] : undefined;
|
|
2735
2743
|
const memoryId = args[0];
|
|
2736
2744
|
if (!memoryId && valueFlag === undefined) {
|
|
2737
|
-
|
|
2738
|
-
|
|
2745
|
+
printError('Usage: hippo reject <memory-id> --reason "<why>"');
|
|
2746
|
+
printError(' or: hippo reject --value "<text>" --reason "<why>"');
|
|
2739
2747
|
process.exit(1);
|
|
2740
2748
|
}
|
|
2741
2749
|
if (memoryId && valueFlag !== undefined) {
|
|
2742
2750
|
// Ambiguous ask: silently preferring one form would ignore the other
|
|
2743
2751
|
// without feedback (code-review round-1 low).
|
|
2744
|
-
|
|
2752
|
+
printError('hippo reject takes EITHER a memory id OR --value, not both.');
|
|
2745
2753
|
process.exit(1);
|
|
2746
2754
|
}
|
|
2747
2755
|
try {
|
|
@@ -2771,7 +2779,7 @@ function cmdReject(hippoRoot, args, flags) {
|
|
|
2771
2779
|
}
|
|
2772
2780
|
}
|
|
2773
2781
|
catch (err) {
|
|
2774
|
-
|
|
2782
|
+
printError(`Could not reject: ${err instanceof Error ? err.message : String(err)}`);
|
|
2775
2783
|
process.exit(1);
|
|
2776
2784
|
}
|
|
2777
2785
|
}
|
|
@@ -2805,18 +2813,18 @@ function cmdUnreject(hippoRoot, args, flags) {
|
|
|
2805
2813
|
const tenantId = resolveTenantId({});
|
|
2806
2814
|
const digestOrPrefix = (args[0] ?? '').trim();
|
|
2807
2815
|
if (!digestOrPrefix) {
|
|
2808
|
-
|
|
2816
|
+
printError('Usage: hippo unreject <digest-or-prefix>');
|
|
2809
2817
|
process.exit(1);
|
|
2810
2818
|
}
|
|
2811
2819
|
const outcome = unrejectValue(root, tenantId, digestOrPrefix, 'cli');
|
|
2812
2820
|
if (outcome.status === 'not_found') {
|
|
2813
|
-
|
|
2821
|
+
printError(`No rejected value matches "${digestOrPrefix}". Run \`hippo rejections\` to list tombstones.`);
|
|
2814
2822
|
process.exit(1);
|
|
2815
2823
|
}
|
|
2816
2824
|
if (outcome.status === 'ambiguous') {
|
|
2817
|
-
|
|
2825
|
+
printError(`"${digestOrPrefix}" matches ${outcome.candidates.length} tombstones. Use a longer prefix:`);
|
|
2818
2826
|
for (const c of outcome.candidates) {
|
|
2819
|
-
|
|
2827
|
+
printError(` ${c.digest.slice(0, 16)}... ${c.reason ?? 'none given'}`);
|
|
2820
2828
|
}
|
|
2821
2829
|
process.exit(1);
|
|
2822
2830
|
}
|
|
@@ -2839,7 +2847,7 @@ function cmdDormant(hippoRoot, args, flags) {
|
|
|
2839
2847
|
if (sub === 'restore' || sub === 'forget') {
|
|
2840
2848
|
const id = (args[1] ?? '').trim();
|
|
2841
2849
|
if (!id) {
|
|
2842
|
-
|
|
2850
|
+
printError(`Usage: hippo dormant ${sub} <id>`);
|
|
2843
2851
|
process.exit(1);
|
|
2844
2852
|
}
|
|
2845
2853
|
try {
|
|
@@ -2854,10 +2862,10 @@ function cmdDormant(hippoRoot, args, flags) {
|
|
|
2854
2862
|
}
|
|
2855
2863
|
catch (err) {
|
|
2856
2864
|
if (err instanceof RejectedValueError) {
|
|
2857
|
-
|
|
2865
|
+
printError(`Cannot restore ${id}: its value was rejected (${err.reason ?? 'no reason given'}). Run \`hippo unreject\` first to allow it.`);
|
|
2858
2866
|
}
|
|
2859
2867
|
else {
|
|
2860
|
-
|
|
2868
|
+
printError(`Could not ${sub} ${id}: ${err instanceof Error ? err.message : String(err)}`);
|
|
2861
2869
|
}
|
|
2862
2870
|
process.exit(1);
|
|
2863
2871
|
}
|
|
@@ -2902,7 +2910,7 @@ function cmdQuarantine(hippoRoot, args, flags) {
|
|
|
2902
2910
|
if (sub === 'approve' || sub === 'reject') {
|
|
2903
2911
|
const id = (args[1] ?? '').trim();
|
|
2904
2912
|
if (!id) {
|
|
2905
|
-
|
|
2913
|
+
printError(`Usage: hippo quarantine ${sub} <id>`);
|
|
2906
2914
|
process.exit(1);
|
|
2907
2915
|
}
|
|
2908
2916
|
try {
|
|
@@ -2916,7 +2924,7 @@ function cmdQuarantine(hippoRoot, args, flags) {
|
|
|
2916
2924
|
}
|
|
2917
2925
|
}
|
|
2918
2926
|
catch (err) {
|
|
2919
|
-
|
|
2927
|
+
printError(`Could not ${sub} ${id}: ${err instanceof Error ? err.message : String(err)}`);
|
|
2920
2928
|
process.exit(1);
|
|
2921
2929
|
}
|
|
2922
2930
|
return;
|
|
@@ -3033,7 +3041,7 @@ function cmdSnapshot(hippoRoot, args, flags) {
|
|
|
3033
3041
|
const nextStep = String(flags['next-step'] ?? '').trim();
|
|
3034
3042
|
const sessionId = String(flags['session'] ?? flags['id'] ?? '').trim();
|
|
3035
3043
|
if (!task || !summary || !nextStep) {
|
|
3036
|
-
|
|
3044
|
+
printError('Usage: hippo snapshot save --task <task> --summary <summary> --next-step <step> [--source <source>] [--session <session-id>]');
|
|
3037
3045
|
process.exit(1);
|
|
3038
3046
|
}
|
|
3039
3047
|
const snapshot = saveActiveTaskSnapshot(hippoRoot, resolveTenantId({}), {
|
|
@@ -3078,7 +3086,7 @@ function cmdSnapshot(hippoRoot, args, flags) {
|
|
|
3078
3086
|
printActiveTaskSnapshot(snapshot);
|
|
3079
3087
|
return;
|
|
3080
3088
|
}
|
|
3081
|
-
|
|
3089
|
+
printError('Usage: hippo snapshot <save|show|clear>');
|
|
3082
3090
|
process.exit(1);
|
|
3083
3091
|
}
|
|
3084
3092
|
function cmdSession(hippoRoot, args, flags) {
|
|
@@ -3091,7 +3099,7 @@ function cmdSession(hippoRoot, args, flags) {
|
|
|
3091
3099
|
const eventType = String(flags['type'] ?? 'note').trim();
|
|
3092
3100
|
const content = String(flags['content'] ?? '').trim();
|
|
3093
3101
|
if (!sessionId || !content) {
|
|
3094
|
-
|
|
3102
|
+
printError('Usage: hippo session log --id <session-id> --content <text> [--type <type>] [--task <task>] [--source <source>]');
|
|
3095
3103
|
process.exit(1);
|
|
3096
3104
|
}
|
|
3097
3105
|
const event = appendSessionEvent(hippoRoot, resolveTenantId({}), {
|
|
@@ -3143,11 +3151,11 @@ function cmdSession(hippoRoot, args, flags) {
|
|
|
3143
3151
|
const outcomeRaw = String(flags['outcome'] ?? '').trim();
|
|
3144
3152
|
const summary = String(flags['summary'] ?? '').trim();
|
|
3145
3153
|
if (!sessionId) {
|
|
3146
|
-
|
|
3154
|
+
printError('Usage: hippo session complete --session <session-id> --outcome <success|failure|partial> [--summary "..."]');
|
|
3147
3155
|
process.exit(1);
|
|
3148
3156
|
}
|
|
3149
3157
|
if (!isHandoffOutcome(outcomeRaw)) {
|
|
3150
|
-
|
|
3158
|
+
printError(`Invalid outcome: "${outcomeRaw}". Must be one of: success, failure, partial.`);
|
|
3151
3159
|
process.exit(1);
|
|
3152
3160
|
}
|
|
3153
3161
|
const outcome = outcomeRaw;
|
|
@@ -3214,7 +3222,7 @@ function cmdSession(hippoRoot, args, flags) {
|
|
|
3214
3222
|
console.log(lines.join('\n'));
|
|
3215
3223
|
return;
|
|
3216
3224
|
}
|
|
3217
|
-
|
|
3225
|
+
printError('Usage: hippo session <log|show|latest|resume|complete>');
|
|
3218
3226
|
process.exit(1);
|
|
3219
3227
|
}
|
|
3220
3228
|
function cmdHandoff(hippoRoot, args, flags) {
|
|
@@ -3223,12 +3231,12 @@ function cmdHandoff(hippoRoot, args, flags) {
|
|
|
3223
3231
|
if (subcommand === 'create') {
|
|
3224
3232
|
const summary = String(flags['summary'] ?? '').trim();
|
|
3225
3233
|
if (!summary) {
|
|
3226
|
-
|
|
3234
|
+
printError('Usage: hippo handoff create --summary "..." [--next "..."] [--session <id>] [--task <id>] [--artifact <path>...] [--constraint <text>...] [--outcome <success|failure|partial>] [--target-runtime <name>] [--card-id <id>] [--tests <pass|fail|unknown>]');
|
|
3227
3235
|
process.exit(1);
|
|
3228
3236
|
}
|
|
3229
3237
|
const outcomeRaw = flags['outcome'];
|
|
3230
3238
|
if (outcomeRaw !== undefined && !isHandoffOutcome(outcomeRaw)) {
|
|
3231
|
-
|
|
3239
|
+
printError(`Invalid outcome: "${String(outcomeRaw)}". Must be one of: success, failure, partial.`);
|
|
3232
3240
|
process.exit(1);
|
|
3233
3241
|
}
|
|
3234
3242
|
const sessionId = String(flags['session'] ?? flags['id'] ?? '').trim() || `fallback-${Date.now()}-${process.pid}`;
|
|
@@ -3246,7 +3254,7 @@ function cmdHandoff(hippoRoot, args, flags) {
|
|
|
3246
3254
|
for (const name of ['target-runtime', 'card-id']) {
|
|
3247
3255
|
// parseArgs turns a value-less flag into `true`; refuse rather than store "true".
|
|
3248
3256
|
if (flags[name] === true) {
|
|
3249
|
-
|
|
3257
|
+
printError(`--${name} needs a value`);
|
|
3250
3258
|
process.exit(1);
|
|
3251
3259
|
}
|
|
3252
3260
|
}
|
|
@@ -3311,12 +3319,12 @@ function cmdHandoff(hippoRoot, args, flags) {
|
|
|
3311
3319
|
if (subcommand === 'show') {
|
|
3312
3320
|
const idArg = args[1];
|
|
3313
3321
|
if (!idArg) {
|
|
3314
|
-
|
|
3322
|
+
printError('Usage: hippo handoff show <id> [--json]');
|
|
3315
3323
|
process.exit(1);
|
|
3316
3324
|
}
|
|
3317
3325
|
const handoffId = parseInt(idArg, 10);
|
|
3318
3326
|
if (!Number.isFinite(handoffId) || handoffId <= 0) {
|
|
3319
|
-
|
|
3327
|
+
printError(`Invalid handoff ID: ${idArg}`);
|
|
3320
3328
|
process.exit(1);
|
|
3321
3329
|
}
|
|
3322
3330
|
const handoff = loadHandoffById(hippoRoot, resolveTenantId({}), handoffId);
|
|
@@ -3336,7 +3344,7 @@ function cmdHandoff(hippoRoot, args, flags) {
|
|
|
3336
3344
|
printHandoff(handoff);
|
|
3337
3345
|
return;
|
|
3338
3346
|
}
|
|
3339
|
-
|
|
3347
|
+
printError('Usage: hippo handoff <create|latest|show>');
|
|
3340
3348
|
process.exit(1);
|
|
3341
3349
|
}
|
|
3342
3350
|
// Mirrors ARCHIVE_REASON_REQUIRED so the block message can't drift from its usage line.
|
|
@@ -3389,7 +3397,7 @@ function cardRunFlag(flags) {
|
|
|
3389
3397
|
return undefined;
|
|
3390
3398
|
const n = Number(raw);
|
|
3391
3399
|
if (!/^\d+$/.test(raw) || !Number.isSafeInteger(n) || n <= 0) {
|
|
3392
|
-
|
|
3400
|
+
printError(`Invalid --run: "${raw}" (expected a positive integer).`);
|
|
3393
3401
|
process.exit(1);
|
|
3394
3402
|
}
|
|
3395
3403
|
return n;
|
|
@@ -3422,7 +3430,7 @@ function cmdCard(hippoRoot, args, flags) {
|
|
|
3422
3430
|
for (const key of Object.keys(flags)) {
|
|
3423
3431
|
if (!allowedFlags.includes(key)) {
|
|
3424
3432
|
const valid = allowedFlags.length > 0 ? allowedFlags.map((f) => `--${f}`).join(', ') : '(none)';
|
|
3425
|
-
|
|
3433
|
+
printError(`Unknown flag --${key} for hippo card ${subcommand}. Valid flags: ${valid}`);
|
|
3426
3434
|
process.exit(1);
|
|
3427
3435
|
}
|
|
3428
3436
|
}
|
|
@@ -3430,7 +3438,7 @@ function cmdCard(hippoRoot, args, flags) {
|
|
|
3430
3438
|
if (subcommand === 'create') {
|
|
3431
3439
|
const title = cardStringFlag(flags, 'title') ?? '';
|
|
3432
3440
|
if (!title) {
|
|
3433
|
-
|
|
3441
|
+
printError('Usage: hippo card create --title "..." [--repo <name>] [--contract <text>] [--budget <n>] [--depends-on <id>...]');
|
|
3434
3442
|
process.exit(1);
|
|
3435
3443
|
}
|
|
3436
3444
|
const repo = cardStringFlag(flags, 'repo') || undefined;
|
|
@@ -3439,14 +3447,14 @@ function cmdCard(hippoRoot, args, flags) {
|
|
|
3439
3447
|
let budget;
|
|
3440
3448
|
if (budgetRaw !== undefined) {
|
|
3441
3449
|
if (!/^\d+$/.test(budgetRaw)) {
|
|
3442
|
-
|
|
3450
|
+
printError(`Invalid budget: "${budgetRaw}" (expected a positive integer)`);
|
|
3443
3451
|
process.exit(1);
|
|
3444
3452
|
}
|
|
3445
3453
|
budget = Number(budgetRaw);
|
|
3446
3454
|
}
|
|
3447
3455
|
const dependsOnFlag = flags['depends-on'];
|
|
3448
3456
|
if (dependsOnFlag === true) {
|
|
3449
|
-
|
|
3457
|
+
printError('--depends-on requires a value');
|
|
3450
3458
|
process.exit(1);
|
|
3451
3459
|
}
|
|
3452
3460
|
const dependsOn = Array.isArray(dependsOnFlag) ? dependsOnFlag : [];
|
|
@@ -3455,7 +3463,7 @@ function cmdCard(hippoRoot, args, flags) {
|
|
|
3455
3463
|
card = createCard(hippoRoot, tenantId, { title, repo, contract, budget, dependsOn });
|
|
3456
3464
|
}
|
|
3457
3465
|
catch (error) {
|
|
3458
|
-
|
|
3466
|
+
printError(error instanceof Error ? error.message : String(error));
|
|
3459
3467
|
process.exit(1);
|
|
3460
3468
|
}
|
|
3461
3469
|
console.log(`Created card ${card.id} (status: ${card.status})`);
|
|
@@ -3464,12 +3472,12 @@ function cmdCard(hippoRoot, args, flags) {
|
|
|
3464
3472
|
if (subcommand === 'show') {
|
|
3465
3473
|
const id = args[1];
|
|
3466
3474
|
if (!id) {
|
|
3467
|
-
|
|
3475
|
+
printError('Usage: hippo card show <id> [--json]');
|
|
3468
3476
|
process.exit(1);
|
|
3469
3477
|
}
|
|
3470
3478
|
const detail = loadCardDetail(hippoRoot, tenantId, id);
|
|
3471
3479
|
if (!detail) {
|
|
3472
|
-
|
|
3480
|
+
printError(`No card found with id ${id}.`);
|
|
3473
3481
|
process.exit(1);
|
|
3474
3482
|
}
|
|
3475
3483
|
if (flags['json']) {
|
|
@@ -3482,7 +3490,7 @@ function cmdCard(hippoRoot, args, flags) {
|
|
|
3482
3490
|
if (subcommand === 'list') {
|
|
3483
3491
|
const status = cardStringFlag(flags, 'status');
|
|
3484
3492
|
if (status !== undefined && !isCardStatus(status)) {
|
|
3485
|
-
|
|
3493
|
+
printError(`Invalid status: "${status}".`);
|
|
3486
3494
|
process.exit(1);
|
|
3487
3495
|
}
|
|
3488
3496
|
const cards = listCards(hippoRoot, tenantId, { status });
|
|
@@ -3503,7 +3511,7 @@ function cmdCard(hippoRoot, args, flags) {
|
|
|
3503
3511
|
const id = args[1];
|
|
3504
3512
|
const runtime = cardStringFlag(flags, 'runtime') ?? '';
|
|
3505
3513
|
if (!id || !runtime) {
|
|
3506
|
-
|
|
3514
|
+
printError('Usage: hippo card claim <id> --runtime <name> [--session <id>]');
|
|
3507
3515
|
process.exit(1);
|
|
3508
3516
|
}
|
|
3509
3517
|
const sessionId = cardStringFlag(flags, 'session') || undefined;
|
|
@@ -3512,11 +3520,11 @@ function cmdCard(hippoRoot, args, flags) {
|
|
|
3512
3520
|
card = claimCard(hippoRoot, tenantId, id, runtime, sessionId);
|
|
3513
3521
|
}
|
|
3514
3522
|
catch (error) {
|
|
3515
|
-
|
|
3523
|
+
printError(error instanceof Error ? error.message : String(error));
|
|
3516
3524
|
process.exit(1);
|
|
3517
3525
|
}
|
|
3518
3526
|
if (!card) {
|
|
3519
|
-
|
|
3527
|
+
printError(`Could not claim card ${id} (not ready/blocked, or already claimed).`);
|
|
3520
3528
|
process.exit(1);
|
|
3521
3529
|
}
|
|
3522
3530
|
console.log(`Claimed card ${card.id} for ${runtime} (run ${card.runId}, lease until ${card.leaseUntil})`);
|
|
@@ -3526,7 +3534,7 @@ function cmdCard(hippoRoot, args, flags) {
|
|
|
3526
3534
|
const id = args[1];
|
|
3527
3535
|
const runId = cardRunFlag(flags);
|
|
3528
3536
|
if (!id || runId === undefined) {
|
|
3529
|
-
|
|
3537
|
+
printError('Usage: hippo card heartbeat <id> --run <n>');
|
|
3530
3538
|
process.exit(1);
|
|
3531
3539
|
}
|
|
3532
3540
|
let card;
|
|
@@ -3534,11 +3542,11 @@ function cmdCard(hippoRoot, args, flags) {
|
|
|
3534
3542
|
card = heartbeatCard(hippoRoot, tenantId, id, runId);
|
|
3535
3543
|
}
|
|
3536
3544
|
catch (error) {
|
|
3537
|
-
|
|
3545
|
+
printError(error instanceof Error ? error.message : String(error));
|
|
3538
3546
|
process.exit(1);
|
|
3539
3547
|
}
|
|
3540
3548
|
if (!card) {
|
|
3541
|
-
|
|
3549
|
+
printError(`Could not heartbeat card ${id} (${cardRefusal(hippoRoot, tenantId, id)}).`);
|
|
3542
3550
|
process.exit(1);
|
|
3543
3551
|
}
|
|
3544
3552
|
console.log(`Heartbeat card ${card.id}: lease until ${card.leaseUntil}`);
|
|
@@ -3548,7 +3556,7 @@ function cmdCard(hippoRoot, args, flags) {
|
|
|
3548
3556
|
const id = args[1];
|
|
3549
3557
|
const reason = cardStringFlag(flags, 'reason') ?? '';
|
|
3550
3558
|
if (!id || !reason) {
|
|
3551
|
-
|
|
3559
|
+
printError(CARD_BLOCK_REASON_REQUIRED);
|
|
3552
3560
|
process.exit(1);
|
|
3553
3561
|
}
|
|
3554
3562
|
const runId = cardRunFlag(flags);
|
|
@@ -3557,12 +3565,12 @@ function cmdCard(hippoRoot, args, flags) {
|
|
|
3557
3565
|
card = blockCard(hippoRoot, tenantId, id, reason, runId);
|
|
3558
3566
|
}
|
|
3559
3567
|
catch (error) {
|
|
3560
|
-
|
|
3568
|
+
printError(error instanceof Error ? error.message : String(error));
|
|
3561
3569
|
process.exit(1);
|
|
3562
3570
|
}
|
|
3563
3571
|
if (!card) {
|
|
3564
3572
|
const why = runId === undefined ? 'not running' : cardRefusal(hippoRoot, tenantId, id);
|
|
3565
|
-
|
|
3573
|
+
printError(`Could not block card ${id} (${why}).`);
|
|
3566
3574
|
process.exit(1);
|
|
3567
3575
|
}
|
|
3568
3576
|
console.log(`Blocked card ${card.id}`);
|
|
@@ -3571,7 +3579,7 @@ function cmdCard(hippoRoot, args, flags) {
|
|
|
3571
3579
|
if (subcommand === 'review') {
|
|
3572
3580
|
const id = args[1];
|
|
3573
3581
|
if (!id) {
|
|
3574
|
-
|
|
3582
|
+
printError('Usage: hippo card review <id> [--run <n>]');
|
|
3575
3583
|
process.exit(1);
|
|
3576
3584
|
}
|
|
3577
3585
|
const runId = cardRunFlag(flags);
|
|
@@ -3580,12 +3588,12 @@ function cmdCard(hippoRoot, args, flags) {
|
|
|
3580
3588
|
card = reviewCard(hippoRoot, tenantId, id, runId);
|
|
3581
3589
|
}
|
|
3582
3590
|
catch (error) {
|
|
3583
|
-
|
|
3591
|
+
printError(error instanceof Error ? error.message : String(error));
|
|
3584
3592
|
process.exit(1);
|
|
3585
3593
|
}
|
|
3586
3594
|
if (!card) {
|
|
3587
3595
|
const why = runId === undefined ? 'not running' : cardRefusal(hippoRoot, tenantId, id);
|
|
3588
|
-
|
|
3596
|
+
printError(`Could not move card ${id} to review (${why}).`);
|
|
3589
3597
|
process.exit(1);
|
|
3590
3598
|
}
|
|
3591
3599
|
console.log(`Card ${card.id} moved to review`);
|
|
@@ -3595,7 +3603,7 @@ function cmdCard(hippoRoot, args, flags) {
|
|
|
3595
3603
|
const id = args[1];
|
|
3596
3604
|
const outcomeRaw = flags['outcome'];
|
|
3597
3605
|
if (!id || !isHandoffOutcome(outcomeRaw)) {
|
|
3598
|
-
|
|
3606
|
+
printError('Usage: hippo card complete <id> --outcome <success|failure|partial> [--run <n>]');
|
|
3599
3607
|
process.exit(1);
|
|
3600
3608
|
}
|
|
3601
3609
|
const runId = cardRunFlag(flags);
|
|
@@ -3604,12 +3612,12 @@ function cmdCard(hippoRoot, args, flags) {
|
|
|
3604
3612
|
result = completeCard(hippoRoot, tenantId, id, outcomeRaw, runId);
|
|
3605
3613
|
}
|
|
3606
3614
|
catch (error) {
|
|
3607
|
-
|
|
3615
|
+
printError(error instanceof Error ? error.message : String(error));
|
|
3608
3616
|
process.exit(1);
|
|
3609
3617
|
}
|
|
3610
3618
|
if (!result) {
|
|
3611
3619
|
const why = runId === undefined ? 'not in review' : cardRefusal(hippoRoot, tenantId, id);
|
|
3612
|
-
|
|
3620
|
+
printError(`Could not complete card ${id} (${why}).`);
|
|
3613
3621
|
process.exit(1);
|
|
3614
3622
|
}
|
|
3615
3623
|
console.log(`Completed card ${result.card.id} (status: ${result.card.status})`);
|
|
@@ -3620,7 +3628,7 @@ function cmdCard(hippoRoot, args, flags) {
|
|
|
3620
3628
|
}
|
|
3621
3629
|
if (subcommand === 'reclaim') {
|
|
3622
3630
|
if (args.length > 1) {
|
|
3623
|
-
|
|
3631
|
+
printError('Usage: hippo card reclaim (sweeps every expired lease; use hippo card block <id> for one card)');
|
|
3624
3632
|
process.exit(1);
|
|
3625
3633
|
}
|
|
3626
3634
|
const ids = reclaimExpiredCards(hippoRoot, tenantId);
|
|
@@ -3636,18 +3644,18 @@ function cmdCard(hippoRoot, args, flags) {
|
|
|
3636
3644
|
if (subcommand === 'comment') {
|
|
3637
3645
|
const id = args[1];
|
|
3638
3646
|
if (!id) {
|
|
3639
|
-
|
|
3647
|
+
printError('Usage: hippo card comment <id> --body "..." [--author <name>]');
|
|
3640
3648
|
process.exit(1);
|
|
3641
3649
|
}
|
|
3642
3650
|
// Only show and comment look the card up directly; claim/heartbeat/block/review/complete throw from the store instead.
|
|
3643
3651
|
const card = loadCard(hippoRoot, tenantId, id);
|
|
3644
3652
|
if (!card) {
|
|
3645
|
-
|
|
3653
|
+
printError(`No card found with id ${id}.`);
|
|
3646
3654
|
process.exit(1);
|
|
3647
3655
|
}
|
|
3648
3656
|
const body = cardStringFlag(flags, 'body') ?? '';
|
|
3649
3657
|
if (!body) {
|
|
3650
|
-
|
|
3658
|
+
printError('Usage: hippo card comment <id> --body "..." [--author <name>]');
|
|
3651
3659
|
process.exit(1);
|
|
3652
3660
|
}
|
|
3653
3661
|
const author = cardStringFlag(flags, 'author') || 'cli';
|
|
@@ -3655,7 +3663,7 @@ function cmdCard(hippoRoot, args, flags) {
|
|
|
3655
3663
|
console.log(`Added comment ${comment.id} to card ${id}`);
|
|
3656
3664
|
return;
|
|
3657
3665
|
}
|
|
3658
|
-
|
|
3666
|
+
printError('Usage: hippo card <create|show|list|claim|heartbeat|block|review|complete|reclaim|comment>');
|
|
3659
3667
|
process.exit(1);
|
|
3660
3668
|
}
|
|
3661
3669
|
// ---------------------------------------------------------------------------
|
|
@@ -3669,23 +3677,23 @@ function cmdPredict(hippoRoot, args, flags) {
|
|
|
3669
3677
|
if (subcommand === 'close') {
|
|
3670
3678
|
const idRaw = args[1];
|
|
3671
3679
|
if (!idRaw) {
|
|
3672
|
-
|
|
3680
|
+
printError('Usage: hippo predict close <id> --state <closed|closed-unknown> [--actual <v>] [--note "..."]');
|
|
3673
3681
|
process.exit(1);
|
|
3674
3682
|
}
|
|
3675
3683
|
const id = parseInt(String(idRaw), 10);
|
|
3676
3684
|
if (!Number.isFinite(id) || id <= 0) {
|
|
3677
|
-
|
|
3685
|
+
printError(`Invalid prediction id: "${idRaw}"`);
|
|
3678
3686
|
process.exit(1);
|
|
3679
3687
|
}
|
|
3680
3688
|
const stateRaw = typeof flags['state'] === 'string' ? flags['state'].trim() : '';
|
|
3681
3689
|
if (!predictionsModule.VALID_CLOSURE_STATES.has(stateRaw) || stateRaw === 'open') {
|
|
3682
|
-
|
|
3690
|
+
printError(`Invalid --state: "${stateRaw}". Must be one of: closed | closed-unknown.`);
|
|
3683
3691
|
process.exit(1);
|
|
3684
3692
|
}
|
|
3685
3693
|
const actualRaw = flags['actual'];
|
|
3686
3694
|
const actualValue = actualRaw !== undefined ? Number(actualRaw) : undefined;
|
|
3687
3695
|
if (actualRaw !== undefined && !Number.isFinite(actualValue)) {
|
|
3688
|
-
|
|
3696
|
+
printError(`Invalid --actual: "${actualRaw}". Must be a number.`);
|
|
3689
3697
|
process.exit(1);
|
|
3690
3698
|
}
|
|
3691
3699
|
const noteRaw = flags['note'];
|
|
@@ -3706,7 +3714,7 @@ function cmdPredict(hippoRoot, args, flags) {
|
|
|
3706
3714
|
const limitRaw = flags['limit'];
|
|
3707
3715
|
const limit = limitRaw !== undefined ? parseInt(String(limitRaw), 10) : 100;
|
|
3708
3716
|
if (!Number.isFinite(limit) || limit <= 0) {
|
|
3709
|
-
|
|
3717
|
+
printError(`Invalid --limit: "${limitRaw}". Must be a positive integer.`);
|
|
3710
3718
|
process.exit(1);
|
|
3711
3719
|
}
|
|
3712
3720
|
let results;
|
|
@@ -3730,7 +3738,7 @@ function cmdPredict(hippoRoot, args, flags) {
|
|
|
3730
3738
|
}
|
|
3731
3739
|
else {
|
|
3732
3740
|
if (!predictionsModule.VALID_CLOSURE_STATES.has(status)) {
|
|
3733
|
-
|
|
3741
|
+
printError(`Invalid --status: "${status}". Must be one of: open | closed | closed-unknown | all.`);
|
|
3734
3742
|
process.exit(1);
|
|
3735
3743
|
}
|
|
3736
3744
|
if (classTag) {
|
|
@@ -3741,7 +3749,7 @@ function cmdPredict(hippoRoot, args, flags) {
|
|
|
3741
3749
|
}
|
|
3742
3750
|
else {
|
|
3743
3751
|
// status filter without class — scan all classes is more complex; v1 requires --class for non-default status
|
|
3744
|
-
|
|
3752
|
+
printError('--status filter (non-open) requires --class to be set.');
|
|
3745
3753
|
process.exit(1);
|
|
3746
3754
|
}
|
|
3747
3755
|
}
|
|
@@ -3764,17 +3772,17 @@ function cmdPredict(hippoRoot, args, flags) {
|
|
|
3764
3772
|
if (subcommand === 'show') {
|
|
3765
3773
|
const idRaw = args[1];
|
|
3766
3774
|
if (!idRaw) {
|
|
3767
|
-
|
|
3775
|
+
printError('Usage: hippo predict show <id>');
|
|
3768
3776
|
process.exit(1);
|
|
3769
3777
|
}
|
|
3770
3778
|
const id = parseInt(String(idRaw), 10);
|
|
3771
3779
|
if (!Number.isFinite(id) || id <= 0) {
|
|
3772
|
-
|
|
3780
|
+
printError(`Invalid prediction id: "${idRaw}"`);
|
|
3773
3781
|
process.exit(1);
|
|
3774
3782
|
}
|
|
3775
3783
|
const pred = predictionsModule.loadPredictionById(hippoRoot, tenantId, id);
|
|
3776
3784
|
if (!pred) {
|
|
3777
|
-
|
|
3785
|
+
printError(`Prediction ${id} not found.`);
|
|
3778
3786
|
process.exit(1);
|
|
3779
3787
|
}
|
|
3780
3788
|
console.log(`Prediction #${pred.id}`);
|
|
@@ -3800,7 +3808,7 @@ function cmdPredict(hippoRoot, args, flags) {
|
|
|
3800
3808
|
// J3 reference-class / planning-fallacy detector
|
|
3801
3809
|
const classTagRaw = flags['class'];
|
|
3802
3810
|
if (typeof classTagRaw !== 'string' || !classTagRaw.trim()) {
|
|
3803
|
-
|
|
3811
|
+
printError('Usage: hippo predict baserate --class <c>');
|
|
3804
3812
|
process.exit(1);
|
|
3805
3813
|
}
|
|
3806
3814
|
const baserate = predictionsModule.computePredictionBaserate(hippoRoot, tenantId, classTagRaw.trim());
|
|
@@ -3828,22 +3836,22 @@ function cmdPredict(hippoRoot, args, flags) {
|
|
|
3828
3836
|
// Default subcommand: create. args[0] is the claim text.
|
|
3829
3837
|
const claimText = subcommand;
|
|
3830
3838
|
if (!claimText) {
|
|
3831
|
-
|
|
3832
|
-
|
|
3833
|
-
|
|
3834
|
-
|
|
3839
|
+
printError('Usage: hippo predict "<claim>" --class <c> [--estimate <v>] [--unit <u>] [--target <YYYY-MM-DD>]');
|
|
3840
|
+
printError(' hippo predict close <id> --state <closed|closed-unknown> [--actual <v>] [--note "..."]');
|
|
3841
|
+
printError(' hippo predict list [--class X] [--status open|closed|closed-unknown|all] [--limit N]');
|
|
3842
|
+
printError(' hippo predict show <id>');
|
|
3835
3843
|
process.exit(1);
|
|
3836
3844
|
}
|
|
3837
3845
|
const classTagRaw = flags['class'];
|
|
3838
3846
|
if (typeof classTagRaw !== 'string' || !classTagRaw.trim()) {
|
|
3839
|
-
|
|
3847
|
+
printError('--class is required for prediction creation.');
|
|
3840
3848
|
process.exit(1);
|
|
3841
3849
|
}
|
|
3842
3850
|
const classTag = classTagRaw.trim();
|
|
3843
3851
|
const estimateRaw = flags['estimate'];
|
|
3844
3852
|
const estimateValue = estimateRaw !== undefined ? Number(estimateRaw) : undefined;
|
|
3845
3853
|
if (estimateRaw !== undefined && !Number.isFinite(estimateValue)) {
|
|
3846
|
-
|
|
3854
|
+
printError(`Invalid --estimate: "${estimateRaw}". Must be a number.`);
|
|
3847
3855
|
process.exit(1);
|
|
3848
3856
|
}
|
|
3849
3857
|
const unitRaw = flags['unit'];
|
|
@@ -3871,7 +3879,7 @@ function cmdDecide(hippoRoot, args, flags) {
|
|
|
3871
3879
|
const limitRaw = flags['limit'];
|
|
3872
3880
|
const limit = limitRaw !== undefined ? parseInt(String(limitRaw), 10) : 100;
|
|
3873
3881
|
if (!Number.isFinite(limit) || limit <= 0) {
|
|
3874
|
-
|
|
3882
|
+
printError(`Invalid --limit: "${limitRaw}". Must be a positive integer.`);
|
|
3875
3883
|
process.exit(1);
|
|
3876
3884
|
}
|
|
3877
3885
|
let results;
|
|
@@ -3880,7 +3888,7 @@ function cmdDecide(hippoRoot, args, flags) {
|
|
|
3880
3888
|
}
|
|
3881
3889
|
else {
|
|
3882
3890
|
if (!decisionsModule.VALID_DECISION_STATES.has(status)) {
|
|
3883
|
-
|
|
3891
|
+
printError(`Invalid --status: "${status}". Must be one of: active | superseded | closed | all.`);
|
|
3884
3892
|
process.exit(1);
|
|
3885
3893
|
}
|
|
3886
3894
|
results = decisionsModule.loadDecisions(hippoRoot, tenantId, {
|
|
@@ -3905,17 +3913,17 @@ function cmdDecide(hippoRoot, args, flags) {
|
|
|
3905
3913
|
if (subcommand === 'get') {
|
|
3906
3914
|
const idRaw = args[1];
|
|
3907
3915
|
if (!idRaw) {
|
|
3908
|
-
|
|
3916
|
+
printError('Usage: hippo decide get <id>');
|
|
3909
3917
|
process.exit(1);
|
|
3910
3918
|
}
|
|
3911
3919
|
const id = parseInt(String(idRaw), 10);
|
|
3912
3920
|
if (!Number.isFinite(id) || id <= 0) {
|
|
3913
|
-
|
|
3921
|
+
printError(`Invalid decision id: "${idRaw}"`);
|
|
3914
3922
|
process.exit(1);
|
|
3915
3923
|
}
|
|
3916
3924
|
const decision = decisionsModule.loadDecisionById(hippoRoot, tenantId, id);
|
|
3917
3925
|
if (!decision) {
|
|
3918
|
-
|
|
3926
|
+
printError(`Decision ${id} not found.`);
|
|
3919
3927
|
process.exit(1);
|
|
3920
3928
|
}
|
|
3921
3929
|
console.log(`Decision #${decision.id}`);
|
|
@@ -3937,12 +3945,12 @@ function cmdDecide(hippoRoot, args, flags) {
|
|
|
3937
3945
|
if (subcommand === 'close') {
|
|
3938
3946
|
const idRaw = args[1];
|
|
3939
3947
|
if (!idRaw) {
|
|
3940
|
-
|
|
3948
|
+
printError('Usage: hippo decide close <id>');
|
|
3941
3949
|
process.exit(1);
|
|
3942
3950
|
}
|
|
3943
3951
|
const id = parseInt(String(idRaw), 10);
|
|
3944
3952
|
if (!Number.isFinite(id) || id <= 0) {
|
|
3945
|
-
|
|
3953
|
+
printError(`Invalid decision id: "${idRaw}"`);
|
|
3946
3954
|
process.exit(1);
|
|
3947
3955
|
}
|
|
3948
3956
|
const closed = decisionsModule.closeDecision(hippoRoot, tenantId, id);
|
|
@@ -3952,10 +3960,10 @@ function cmdDecide(hippoRoot, args, flags) {
|
|
|
3952
3960
|
// Default subcommand: create. args[0] is the decision text.
|
|
3953
3961
|
const decisionText = subcommand;
|
|
3954
3962
|
if (!decisionText) {
|
|
3955
|
-
|
|
3956
|
-
|
|
3957
|
-
|
|
3958
|
-
|
|
3963
|
+
printError('Usage: hippo decide "<decision>" [--context "<why>"] [--supersedes <memory-id>]');
|
|
3964
|
+
printError(' hippo decide list [--status active|superseded|closed|all] [--limit N]');
|
|
3965
|
+
printError(' hippo decide get <id>');
|
|
3966
|
+
printError(' hippo decide close <id>');
|
|
3959
3967
|
process.exit(1);
|
|
3960
3968
|
}
|
|
3961
3969
|
const contextRaw = flags['context'];
|
|
@@ -3964,7 +3972,7 @@ function cmdDecide(hippoRoot, args, flags) {
|
|
|
3964
3972
|
// request: the user asked to supersede but gave no memory id. Reject it rather
|
|
3965
3973
|
// than silently creating a non-superseding decision (codex review 2026-05-28).
|
|
3966
3974
|
if (flags['supersedes'] === true) {
|
|
3967
|
-
|
|
3975
|
+
printError('--supersedes requires a memory id, e.g. hippo decide "<text>" --supersedes mem_abc123.');
|
|
3968
3976
|
process.exit(1);
|
|
3969
3977
|
}
|
|
3970
3978
|
const supersedesMemId = typeof flags['supersedes'] === 'string' ? flags['supersedes'] : null;
|
|
@@ -3978,7 +3986,7 @@ function cmdDecide(hippoRoot, args, flags) {
|
|
|
3978
3986
|
if (supersedesMemId) {
|
|
3979
3987
|
oldEntry = readEntry(hippoRoot, supersedesMemId, tenantId) ?? null;
|
|
3980
3988
|
if (!oldEntry) {
|
|
3981
|
-
|
|
3989
|
+
printError(`Memory ${supersedesMemId} not found.`);
|
|
3982
3990
|
process.exit(1);
|
|
3983
3991
|
}
|
|
3984
3992
|
supersedesDecisionId =
|
|
@@ -4008,7 +4016,7 @@ function cmdDecide(hippoRoot, args, flags) {
|
|
|
4008
4016
|
writeEntry(hippoRoot, oldEntry);
|
|
4009
4017
|
}
|
|
4010
4018
|
catch (e) {
|
|
4011
|
-
|
|
4019
|
+
printError(` warning: decision recorded and superseded, but failed to weaken the prior memory ${supersedesMemId}: ${e.message}`);
|
|
4012
4020
|
}
|
|
4013
4021
|
}
|
|
4014
4022
|
console.log(`Decision recorded: #${created.id}`);
|
|
@@ -4029,7 +4037,7 @@ function parsePositiveIncidentId(idRaw) {
|
|
|
4029
4037
|
const s = String(idRaw ?? '').trim();
|
|
4030
4038
|
const id = parseInt(s, 10);
|
|
4031
4039
|
if (!/^\d+$/.test(s) || id <= 0) {
|
|
4032
|
-
|
|
4040
|
+
printError(`Invalid incident id: "${idRaw}" (expected a positive integer).`);
|
|
4033
4041
|
process.exit(1);
|
|
4034
4042
|
}
|
|
4035
4043
|
return id;
|
|
@@ -4044,7 +4052,7 @@ function cmdIncident(hippoRoot, args, flags) {
|
|
|
4044
4052
|
const limitRaw = flags['limit'];
|
|
4045
4053
|
const limit = limitRaw !== undefined ? parseInt(String(limitRaw), 10) : 100;
|
|
4046
4054
|
if (!Number.isFinite(limit) || limit <= 0) {
|
|
4047
|
-
|
|
4055
|
+
printError(`Invalid --limit: "${limitRaw}". Must be a positive integer.`);
|
|
4048
4056
|
process.exit(1);
|
|
4049
4057
|
}
|
|
4050
4058
|
let results;
|
|
@@ -4053,7 +4061,7 @@ function cmdIncident(hippoRoot, args, flags) {
|
|
|
4053
4061
|
}
|
|
4054
4062
|
else {
|
|
4055
4063
|
if (!incidentsModule.VALID_INCIDENT_STATES.has(status)) {
|
|
4056
|
-
|
|
4064
|
+
printError(`Invalid --status: "${status}". Must be one of: open | resolved | closed | all.`);
|
|
4057
4065
|
process.exit(1);
|
|
4058
4066
|
}
|
|
4059
4067
|
results = incidentsModule.loadIncidents(hippoRoot, tenantId, {
|
|
@@ -4078,13 +4086,13 @@ function cmdIncident(hippoRoot, args, flags) {
|
|
|
4078
4086
|
if (subcommand === 'get') {
|
|
4079
4087
|
const idRaw = args[1];
|
|
4080
4088
|
if (!idRaw) {
|
|
4081
|
-
|
|
4089
|
+
printError('Usage: hippo incident get <id>');
|
|
4082
4090
|
process.exit(1);
|
|
4083
4091
|
}
|
|
4084
4092
|
const id = parsePositiveIncidentId(idRaw);
|
|
4085
4093
|
const incident = incidentsModule.loadIncidentById(hippoRoot, tenantId, id);
|
|
4086
4094
|
if (!incident) {
|
|
4087
|
-
|
|
4095
|
+
printError(`Incident ${id} not found.`);
|
|
4088
4096
|
process.exit(1);
|
|
4089
4097
|
}
|
|
4090
4098
|
console.log(`Incident #${incident.id}`);
|
|
@@ -4109,13 +4117,13 @@ function cmdIncident(hippoRoot, args, flags) {
|
|
|
4109
4117
|
if (subcommand === 'resolve') {
|
|
4110
4118
|
const idRaw = args[1];
|
|
4111
4119
|
if (!idRaw) {
|
|
4112
|
-
|
|
4120
|
+
printError('Usage: hippo incident resolve <id> --resolution "<text>"');
|
|
4113
4121
|
process.exit(1);
|
|
4114
4122
|
}
|
|
4115
4123
|
const id = parsePositiveIncidentId(idRaw);
|
|
4116
4124
|
const resolutionRaw = flags['resolution'];
|
|
4117
4125
|
if (typeof resolutionRaw !== 'string' || !resolutionRaw.trim()) {
|
|
4118
|
-
|
|
4126
|
+
printError('--resolution requires a non-empty value, e.g. hippo incident resolve <id> --resolution "root cause fixed".');
|
|
4119
4127
|
process.exit(1);
|
|
4120
4128
|
}
|
|
4121
4129
|
const resolved = incidentsModule.resolveIncident(hippoRoot, tenantId, id, resolutionRaw);
|
|
@@ -4125,7 +4133,7 @@ function cmdIncident(hippoRoot, args, flags) {
|
|
|
4125
4133
|
if (subcommand === 'close') {
|
|
4126
4134
|
const idRaw = args[1];
|
|
4127
4135
|
if (!idRaw) {
|
|
4128
|
-
|
|
4136
|
+
printError('Usage: hippo incident close <id>');
|
|
4129
4137
|
process.exit(1);
|
|
4130
4138
|
}
|
|
4131
4139
|
const id = parsePositiveIncidentId(idRaw);
|
|
@@ -4138,11 +4146,11 @@ function cmdIncident(hippoRoot, args, flags) {
|
|
|
4138
4146
|
// `open` keyword the text is args[1], otherwise args[0] IS the text.
|
|
4139
4147
|
const incidentText = subcommand === 'open' ? (args[1] ?? '') : subcommand;
|
|
4140
4148
|
if (!incidentText) {
|
|
4141
|
-
|
|
4142
|
-
|
|
4143
|
-
|
|
4144
|
-
|
|
4145
|
-
|
|
4149
|
+
printError('Usage: hippo incident "<incident>" [--context "<details>"] [--link <memory-id>]...');
|
|
4150
|
+
printError(' hippo incident list [--status open|resolved|closed|all] [--limit N]');
|
|
4151
|
+
printError(' hippo incident get <id>');
|
|
4152
|
+
printError(' hippo incident resolve <id> --resolution "<text>"');
|
|
4153
|
+
printError(' hippo incident close <id>');
|
|
4146
4154
|
process.exit(1);
|
|
4147
4155
|
}
|
|
4148
4156
|
const contextRaw = flags['context'];
|
|
@@ -4158,7 +4166,7 @@ function cmdIncident(hippoRoot, args, flags) {
|
|
|
4158
4166
|
linkedMemoryIds = [linkRaw];
|
|
4159
4167
|
}
|
|
4160
4168
|
else if (linkRaw === true) {
|
|
4161
|
-
|
|
4169
|
+
printError('--link requires a memory id, e.g. hippo incident "<text>" --link mem_abc123.');
|
|
4162
4170
|
process.exit(1);
|
|
4163
4171
|
}
|
|
4164
4172
|
const incidentPathTags = extractPathTags(process.cwd());
|
|
@@ -4184,7 +4192,7 @@ function parsePositiveProcessId(idRaw) {
|
|
|
4184
4192
|
const s = String(idRaw ?? '').trim();
|
|
4185
4193
|
const id = parseInt(s, 10);
|
|
4186
4194
|
if (!/^\d+$/.test(s) || id <= 0) {
|
|
4187
|
-
|
|
4195
|
+
printError(`Invalid process id: "${idRaw}" (expected a positive integer).`);
|
|
4188
4196
|
process.exit(1);
|
|
4189
4197
|
}
|
|
4190
4198
|
return id;
|
|
@@ -4197,7 +4205,7 @@ function collectProcessSteps(stepRaw) {
|
|
|
4197
4205
|
if (typeof stepRaw === 'string')
|
|
4198
4206
|
return [stepRaw];
|
|
4199
4207
|
if (stepRaw === true) {
|
|
4200
|
-
|
|
4208
|
+
printError('--step requires a value, e.g. hippo process new "<name>" --step "do X".');
|
|
4201
4209
|
process.exit(1);
|
|
4202
4210
|
}
|
|
4203
4211
|
return [];
|
|
@@ -4212,7 +4220,7 @@ function cmdProcess(hippoRoot, args, flags) {
|
|
|
4212
4220
|
const limitRaw = flags['limit'];
|
|
4213
4221
|
const limit = limitRaw !== undefined ? parseInt(String(limitRaw), 10) : 100;
|
|
4214
4222
|
if (!Number.isFinite(limit) || limit <= 0) {
|
|
4215
|
-
|
|
4223
|
+
printError(`Invalid --limit: "${limitRaw}". Must be a positive integer.`);
|
|
4216
4224
|
process.exit(1);
|
|
4217
4225
|
}
|
|
4218
4226
|
let results;
|
|
@@ -4221,7 +4229,7 @@ function cmdProcess(hippoRoot, args, flags) {
|
|
|
4221
4229
|
}
|
|
4222
4230
|
else {
|
|
4223
4231
|
if (!processesModule.VALID_PROCESS_STATES.has(status)) {
|
|
4224
|
-
|
|
4232
|
+
printError(`Invalid --status: "${status}". Must be one of: active | superseded | closed | all.`);
|
|
4225
4233
|
process.exit(1);
|
|
4226
4234
|
}
|
|
4227
4235
|
results = processesModule.loadProcesses(hippoRoot, tenantId, {
|
|
@@ -4245,13 +4253,13 @@ function cmdProcess(hippoRoot, args, flags) {
|
|
|
4245
4253
|
if (subcommand === 'get') {
|
|
4246
4254
|
const idRaw = args[1];
|
|
4247
4255
|
if (!idRaw) {
|
|
4248
|
-
|
|
4256
|
+
printError('Usage: hippo process get <id>');
|
|
4249
4257
|
process.exit(1);
|
|
4250
4258
|
}
|
|
4251
4259
|
const id = parsePositiveProcessId(idRaw);
|
|
4252
4260
|
const proc = processesModule.loadProcessById(hippoRoot, tenantId, id);
|
|
4253
4261
|
if (!proc) {
|
|
4254
|
-
|
|
4262
|
+
printError(`Process ${id} not found.`);
|
|
4255
4263
|
process.exit(1);
|
|
4256
4264
|
}
|
|
4257
4265
|
console.log(`Process #${proc.id}`);
|
|
@@ -4280,13 +4288,13 @@ function cmdProcess(hippoRoot, args, flags) {
|
|
|
4280
4288
|
if (subcommand === 'supersede') {
|
|
4281
4289
|
const idRaw = args[1];
|
|
4282
4290
|
if (!idRaw) {
|
|
4283
|
-
|
|
4291
|
+
printError('Usage: hippo process supersede <id> --step "<text>" [--step ...] [--change "<summary>"] [--description "<text>"]');
|
|
4284
4292
|
process.exit(1);
|
|
4285
4293
|
}
|
|
4286
4294
|
const id = parsePositiveProcessId(idRaw);
|
|
4287
4295
|
const steps = collectProcessSteps(flags['step']);
|
|
4288
4296
|
if (steps.length === 0) {
|
|
4289
|
-
|
|
4297
|
+
printError('hippo process supersede requires at least one --step "<text>" for the new version.');
|
|
4290
4298
|
process.exit(1);
|
|
4291
4299
|
}
|
|
4292
4300
|
// A supersession is a new version of the SAME process, so the new row reuses
|
|
@@ -4295,7 +4303,7 @@ function cmdProcess(hippoRoot, args, flags) {
|
|
|
4295
4303
|
// preflight is the authoritative active-state check.
|
|
4296
4304
|
const existing = processesModule.loadProcessById(hippoRoot, tenantId, id);
|
|
4297
4305
|
if (!existing) {
|
|
4298
|
-
|
|
4306
|
+
printError(`Process ${id} not found.`);
|
|
4299
4307
|
process.exit(1);
|
|
4300
4308
|
}
|
|
4301
4309
|
const changeRaw = flags['change'];
|
|
@@ -4319,7 +4327,7 @@ function cmdProcess(hippoRoot, args, flags) {
|
|
|
4319
4327
|
if (subcommand === 'close') {
|
|
4320
4328
|
const idRaw = args[1];
|
|
4321
4329
|
if (!idRaw) {
|
|
4322
|
-
|
|
4330
|
+
printError('Usage: hippo process close <id>');
|
|
4323
4331
|
process.exit(1);
|
|
4324
4332
|
}
|
|
4325
4333
|
const id = parsePositiveProcessId(idRaw);
|
|
@@ -4332,11 +4340,11 @@ function cmdProcess(hippoRoot, args, flags) {
|
|
|
4332
4340
|
// `new` keyword the name is args[1], otherwise args[0] IS the name.
|
|
4333
4341
|
const processName = subcommand === 'new' ? (args[1] ?? '') : subcommand;
|
|
4334
4342
|
if (!processName) {
|
|
4335
|
-
|
|
4336
|
-
|
|
4337
|
-
|
|
4338
|
-
|
|
4339
|
-
|
|
4343
|
+
printError('Usage: hippo process new "<name>" --step "<text>" [--step ...] [--description "<text>"]');
|
|
4344
|
+
printError(' hippo process list [--status active|superseded|closed|all] [--limit N]');
|
|
4345
|
+
printError(' hippo process get <id>');
|
|
4346
|
+
printError(' hippo process supersede <id> --step "<text>" [--change "<summary>"]');
|
|
4347
|
+
printError(' hippo process close <id>');
|
|
4340
4348
|
process.exit(1);
|
|
4341
4349
|
}
|
|
4342
4350
|
const steps = collectProcessSteps(flags['step']);
|
|
@@ -4359,7 +4367,7 @@ function parsePositivePolicyId(idRaw) {
|
|
|
4359
4367
|
const s = String(idRaw ?? '').trim();
|
|
4360
4368
|
const id = parseInt(s, 10);
|
|
4361
4369
|
if (!/^\d+$/.test(s) || id <= 0) {
|
|
4362
|
-
|
|
4370
|
+
printError(`Invalid policy id: "${idRaw}" (expected a positive integer).`);
|
|
4363
4371
|
process.exit(1);
|
|
4364
4372
|
}
|
|
4365
4373
|
return id;
|
|
@@ -4381,7 +4389,7 @@ function cmdPolicy(hippoRoot, args, flags) {
|
|
|
4381
4389
|
const limitRaw = flags['limit'];
|
|
4382
4390
|
const limit = limitRaw !== undefined ? parseInt(String(limitRaw), 10) : 100;
|
|
4383
4391
|
if (!Number.isFinite(limit) || limit <= 0) {
|
|
4384
|
-
|
|
4392
|
+
printError(`Invalid --limit: "${limitRaw}". Must be a positive integer.`);
|
|
4385
4393
|
process.exit(1);
|
|
4386
4394
|
}
|
|
4387
4395
|
let results;
|
|
@@ -4390,7 +4398,7 @@ function cmdPolicy(hippoRoot, args, flags) {
|
|
|
4390
4398
|
}
|
|
4391
4399
|
else {
|
|
4392
4400
|
if (!policiesModule.VALID_POLICY_STATES.has(status)) {
|
|
4393
|
-
|
|
4401
|
+
printError(`Invalid --status: "${status}". Must be one of: active | superseded | closed | all.`);
|
|
4394
4402
|
process.exit(1);
|
|
4395
4403
|
}
|
|
4396
4404
|
results = policiesModule.loadPolicies(hippoRoot, tenantId, {
|
|
@@ -4410,7 +4418,7 @@ function cmdPolicy(hippoRoot, args, flags) {
|
|
|
4410
4418
|
if (subcommand === 'asof') {
|
|
4411
4419
|
const dateRaw = args[1];
|
|
4412
4420
|
if (!dateRaw) {
|
|
4413
|
-
|
|
4421
|
+
printError('Usage: hippo policy asof <iso-date> [--name "<policy>"]');
|
|
4414
4422
|
process.exit(1);
|
|
4415
4423
|
}
|
|
4416
4424
|
const nameRaw = flags['name'];
|
|
@@ -4420,7 +4428,7 @@ function cmdPolicy(hippoRoot, args, flags) {
|
|
|
4420
4428
|
results = policiesModule.loadPoliciesAsOf(hippoRoot, tenantId, dateRaw, { name });
|
|
4421
4429
|
}
|
|
4422
4430
|
catch (e) {
|
|
4423
|
-
|
|
4431
|
+
printError(e.message);
|
|
4424
4432
|
process.exit(1);
|
|
4425
4433
|
}
|
|
4426
4434
|
if (results.length === 0) {
|
|
@@ -4435,13 +4443,13 @@ function cmdPolicy(hippoRoot, args, flags) {
|
|
|
4435
4443
|
if (subcommand === 'get') {
|
|
4436
4444
|
const idRaw = args[1];
|
|
4437
4445
|
if (!idRaw) {
|
|
4438
|
-
|
|
4446
|
+
printError('Usage: hippo policy get <id>');
|
|
4439
4447
|
process.exit(1);
|
|
4440
4448
|
}
|
|
4441
4449
|
const id = parsePositivePolicyId(idRaw);
|
|
4442
4450
|
const p = policiesModule.loadPolicyById(hippoRoot, tenantId, id);
|
|
4443
4451
|
if (!p) {
|
|
4444
|
-
|
|
4452
|
+
printError(`Policy ${id} not found.`);
|
|
4445
4453
|
process.exit(1);
|
|
4446
4454
|
}
|
|
4447
4455
|
console.log(`Policy #${p.id}`);
|
|
@@ -4467,18 +4475,18 @@ function cmdPolicy(hippoRoot, args, flags) {
|
|
|
4467
4475
|
if (subcommand === 'supersede') {
|
|
4468
4476
|
const idRaw = args[1];
|
|
4469
4477
|
if (!idRaw) {
|
|
4470
|
-
|
|
4478
|
+
printError('Usage: hippo policy supersede <id> --text "<rule>" [--from <iso>] [--to <iso>] [--change "<summary>"]');
|
|
4471
4479
|
process.exit(1);
|
|
4472
4480
|
}
|
|
4473
4481
|
const id = parsePositivePolicyId(idRaw);
|
|
4474
4482
|
const textRaw = flags['text'];
|
|
4475
4483
|
if (typeof textRaw !== 'string' || !textRaw.trim()) {
|
|
4476
|
-
|
|
4484
|
+
printError('hippo policy supersede requires --text "<rule>" for the new version.');
|
|
4477
4485
|
process.exit(1);
|
|
4478
4486
|
}
|
|
4479
4487
|
const existing = policiesModule.loadPolicyById(hippoRoot, tenantId, id);
|
|
4480
4488
|
if (!existing) {
|
|
4481
|
-
|
|
4489
|
+
printError(`Policy ${id} not found.`);
|
|
4482
4490
|
process.exit(1);
|
|
4483
4491
|
}
|
|
4484
4492
|
const fromRaw = flags['from'];
|
|
@@ -4499,7 +4507,7 @@ function cmdPolicy(hippoRoot, args, flags) {
|
|
|
4499
4507
|
console.log(` memory: ${created.memoryId}`);
|
|
4500
4508
|
}
|
|
4501
4509
|
catch (e) {
|
|
4502
|
-
|
|
4510
|
+
printError(e.message);
|
|
4503
4511
|
process.exit(1);
|
|
4504
4512
|
}
|
|
4505
4513
|
return;
|
|
@@ -4507,7 +4515,7 @@ function cmdPolicy(hippoRoot, args, flags) {
|
|
|
4507
4515
|
if (subcommand === 'close') {
|
|
4508
4516
|
const idRaw = args[1];
|
|
4509
4517
|
if (!idRaw) {
|
|
4510
|
-
|
|
4518
|
+
printError('Usage: hippo policy close <id>');
|
|
4511
4519
|
process.exit(1);
|
|
4512
4520
|
}
|
|
4513
4521
|
const id = parsePositivePolicyId(idRaw);
|
|
@@ -4520,12 +4528,12 @@ function cmdPolicy(hippoRoot, args, flags) {
|
|
|
4520
4528
|
const policyName = subcommand === 'new' ? (args[1] ?? '') : subcommand;
|
|
4521
4529
|
const textRaw = flags['text'];
|
|
4522
4530
|
if (!policyName || typeof textRaw !== 'string' || !textRaw.trim()) {
|
|
4523
|
-
|
|
4524
|
-
|
|
4525
|
-
|
|
4526
|
-
|
|
4527
|
-
|
|
4528
|
-
|
|
4531
|
+
printError('Usage: hippo policy new "<name>" --text "<rule>" [--from <iso>] [--to <iso>]');
|
|
4532
|
+
printError(' hippo policy list [--status active|superseded|closed|all] [--limit N]');
|
|
4533
|
+
printError(' hippo policy get <id>');
|
|
4534
|
+
printError(' hippo policy asof <iso-date> [--name "<policy>"]');
|
|
4535
|
+
printError(' hippo policy supersede <id> --text "<rule>" [--from] [--to] [--change "<summary>"]');
|
|
4536
|
+
printError(' hippo policy close <id>');
|
|
4529
4537
|
process.exit(1);
|
|
4530
4538
|
}
|
|
4531
4539
|
const fromRaw = flags['from'];
|
|
@@ -4544,7 +4552,7 @@ function cmdPolicy(hippoRoot, args, flags) {
|
|
|
4544
4552
|
console.log(` memory: ${created.memoryId}`);
|
|
4545
4553
|
}
|
|
4546
4554
|
catch (e) {
|
|
4547
|
-
|
|
4555
|
+
printError(e.message);
|
|
4548
4556
|
process.exit(1);
|
|
4549
4557
|
}
|
|
4550
4558
|
}
|
|
@@ -4554,7 +4562,7 @@ function parsePositiveSkillId(idRaw) {
|
|
|
4554
4562
|
const s = String(idRaw ?? '').trim();
|
|
4555
4563
|
const id = parseInt(s, 10);
|
|
4556
4564
|
if (!/^\d+$/.test(s) || id <= 0) {
|
|
4557
|
-
|
|
4565
|
+
printError(`Invalid skill id: "${idRaw}" (expected a positive integer).`);
|
|
4558
4566
|
process.exit(1);
|
|
4559
4567
|
}
|
|
4560
4568
|
return id;
|
|
@@ -4576,7 +4584,7 @@ function cmdSkill(hippoRoot, args, flags) {
|
|
|
4576
4584
|
const limitRaw = flags['limit'];
|
|
4577
4585
|
const limit = limitRaw !== undefined ? parseInt(String(limitRaw), 10) : 100;
|
|
4578
4586
|
if (!Number.isFinite(limit) || limit <= 0) {
|
|
4579
|
-
|
|
4587
|
+
printError(`Invalid --limit: "${limitRaw}". Must be a positive integer.`);
|
|
4580
4588
|
process.exit(1);
|
|
4581
4589
|
}
|
|
4582
4590
|
let results;
|
|
@@ -4585,7 +4593,7 @@ function cmdSkill(hippoRoot, args, flags) {
|
|
|
4585
4593
|
}
|
|
4586
4594
|
else {
|
|
4587
4595
|
if (!skillsModule.VALID_SKILL_STATES.has(status)) {
|
|
4588
|
-
|
|
4596
|
+
printError(`Invalid --status: "${status}". Must be one of: active | superseded | closed | all.`);
|
|
4589
4597
|
process.exit(1);
|
|
4590
4598
|
}
|
|
4591
4599
|
results = skillsModule.loadSkills(hippoRoot, tenantId, {
|
|
@@ -4614,13 +4622,13 @@ function cmdSkill(hippoRoot, args, flags) {
|
|
|
4614
4622
|
if (subcommand === 'get') {
|
|
4615
4623
|
const idRaw = args[1];
|
|
4616
4624
|
if (!idRaw) {
|
|
4617
|
-
|
|
4625
|
+
printError('Usage: hippo skill get <id>');
|
|
4618
4626
|
process.exit(1);
|
|
4619
4627
|
}
|
|
4620
4628
|
const id = parsePositiveSkillId(idRaw);
|
|
4621
4629
|
const s = skillsModule.loadSkillById(hippoRoot, tenantId, id);
|
|
4622
4630
|
if (!s) {
|
|
4623
|
-
|
|
4631
|
+
printError(`Skill ${id} not found.`);
|
|
4624
4632
|
process.exit(1);
|
|
4625
4633
|
}
|
|
4626
4634
|
console.log(`Skill #${s.id}`);
|
|
@@ -4646,18 +4654,18 @@ function cmdSkill(hippoRoot, args, flags) {
|
|
|
4646
4654
|
if (subcommand === 'supersede') {
|
|
4647
4655
|
const idRaw = args[1];
|
|
4648
4656
|
if (!idRaw) {
|
|
4649
|
-
|
|
4657
|
+
printError('Usage: hippo skill supersede <id> --instructions "<text>" [--trigger "<when>"] [--change "<summary>"]');
|
|
4650
4658
|
process.exit(1);
|
|
4651
4659
|
}
|
|
4652
4660
|
const id = parsePositiveSkillId(idRaw);
|
|
4653
4661
|
const instrRaw = flags['instructions'];
|
|
4654
4662
|
if (typeof instrRaw !== 'string' || !instrRaw.trim()) {
|
|
4655
|
-
|
|
4663
|
+
printError('hippo skill supersede requires --instructions "<text>" for the new version.');
|
|
4656
4664
|
process.exit(1);
|
|
4657
4665
|
}
|
|
4658
4666
|
const existing = skillsModule.loadSkillById(hippoRoot, tenantId, id);
|
|
4659
4667
|
if (!existing) {
|
|
4660
|
-
|
|
4668
|
+
printError(`Skill ${id} not found.`);
|
|
4661
4669
|
process.exit(1);
|
|
4662
4670
|
}
|
|
4663
4671
|
const trigRaw = flags['trigger'];
|
|
@@ -4676,7 +4684,7 @@ function cmdSkill(hippoRoot, args, flags) {
|
|
|
4676
4684
|
console.log(` memory: ${created.memoryId}`);
|
|
4677
4685
|
}
|
|
4678
4686
|
catch (e) {
|
|
4679
|
-
|
|
4687
|
+
printError(e.message);
|
|
4680
4688
|
process.exit(1);
|
|
4681
4689
|
}
|
|
4682
4690
|
return;
|
|
@@ -4684,7 +4692,7 @@ function cmdSkill(hippoRoot, args, flags) {
|
|
|
4684
4692
|
if (subcommand === 'close') {
|
|
4685
4693
|
const idRaw = args[1];
|
|
4686
4694
|
if (!idRaw) {
|
|
4687
|
-
|
|
4695
|
+
printError('Usage: hippo skill close <id>');
|
|
4688
4696
|
process.exit(1);
|
|
4689
4697
|
}
|
|
4690
4698
|
const id = parsePositiveSkillId(idRaw);
|
|
@@ -4697,12 +4705,12 @@ function cmdSkill(hippoRoot, args, flags) {
|
|
|
4697
4705
|
const skillName = subcommand === 'new' ? (args[1] ?? '') : subcommand;
|
|
4698
4706
|
const instrRaw = flags['instructions'];
|
|
4699
4707
|
if (!skillName || typeof instrRaw !== 'string' || !instrRaw.trim()) {
|
|
4700
|
-
|
|
4701
|
-
|
|
4702
|
-
|
|
4703
|
-
|
|
4704
|
-
|
|
4705
|
-
|
|
4708
|
+
printError('Usage: hippo skill new "<name>" --instructions "<text>" [--trigger "<when>"]');
|
|
4709
|
+
printError(' hippo skill list [--status active|superseded|closed|all] [--limit N]');
|
|
4710
|
+
printError(' hippo skill get <id>');
|
|
4711
|
+
printError(' hippo skill export (render active skills as an AGENTS.md/CLAUDE.md block)');
|
|
4712
|
+
printError(' hippo skill supersede <id> --instructions "<text>" [--trigger] [--change "<summary>"]');
|
|
4713
|
+
printError(' hippo skill close <id>');
|
|
4706
4714
|
process.exit(1);
|
|
4707
4715
|
}
|
|
4708
4716
|
const trigRaw = flags['trigger'];
|
|
@@ -4718,7 +4726,7 @@ function cmdSkill(hippoRoot, args, flags) {
|
|
|
4718
4726
|
console.log(` memory: ${created.memoryId}`);
|
|
4719
4727
|
}
|
|
4720
4728
|
catch (e) {
|
|
4721
|
-
|
|
4729
|
+
printError(e.message);
|
|
4722
4730
|
process.exit(1);
|
|
4723
4731
|
}
|
|
4724
4732
|
}
|
|
@@ -4726,7 +4734,7 @@ function parsePositiveBriefId(idRaw) {
|
|
|
4726
4734
|
const s = String(idRaw ?? '').trim();
|
|
4727
4735
|
const id = parseInt(s, 10);
|
|
4728
4736
|
if (!/^\d+$/.test(s) || id <= 0) {
|
|
4729
|
-
|
|
4737
|
+
printError(`Invalid brief id: "${idRaw}" (expected a positive integer).`);
|
|
4730
4738
|
process.exit(1);
|
|
4731
4739
|
}
|
|
4732
4740
|
return id;
|
|
@@ -4737,12 +4745,12 @@ function printBriefRow(b) {
|
|
|
4737
4745
|
console.log(` change: ${b.changeSummary}`);
|
|
4738
4746
|
}
|
|
4739
4747
|
function briefUsage() {
|
|
4740
|
-
|
|
4741
|
-
|
|
4742
|
-
|
|
4743
|
-
|
|
4744
|
-
|
|
4745
|
-
|
|
4748
|
+
printError('Usage: hippo brief new "<repo>" --summary "<text>"');
|
|
4749
|
+
printError(' hippo brief list [--status active|superseded|closed|all] [--repo "<repo>"] [--limit N]');
|
|
4750
|
+
printError(' hippo brief get <id>');
|
|
4751
|
+
printError(' hippo brief supersede <id> --summary "<text>" [--change "<summary>"]');
|
|
4752
|
+
printError(' hippo brief close <id>');
|
|
4753
|
+
printError(' hippo brief refresh "<repo>" [--dry-run] (auto-assemble the brief from the repo\'s receipts)');
|
|
4746
4754
|
}
|
|
4747
4755
|
function cmdProjectBrief(hippoRoot, args, flags) {
|
|
4748
4756
|
requireInit(hippoRoot);
|
|
@@ -4756,13 +4764,13 @@ function cmdProjectBrief(hippoRoot, args, flags) {
|
|
|
4756
4764
|
const limitRaw = flags['limit'];
|
|
4757
4765
|
const limit = limitRaw !== undefined ? parseInt(String(limitRaw), 10) : 100;
|
|
4758
4766
|
if (!Number.isFinite(limit) || limit <= 0) {
|
|
4759
|
-
|
|
4767
|
+
printError(`Invalid --limit: "${limitRaw}". Must be a positive integer.`);
|
|
4760
4768
|
process.exit(1);
|
|
4761
4769
|
}
|
|
4762
4770
|
const opts = { limit, repo };
|
|
4763
4771
|
if (status !== 'all') {
|
|
4764
4772
|
if (!briefsModule.VALID_BRIEF_STATES.has(status)) {
|
|
4765
|
-
|
|
4773
|
+
printError(`Invalid --status: "${status}". Must be one of: active | superseded | closed | all.`);
|
|
4766
4774
|
process.exit(1);
|
|
4767
4775
|
}
|
|
4768
4776
|
opts.status = status;
|
|
@@ -4780,14 +4788,14 @@ function cmdProjectBrief(hippoRoot, args, flags) {
|
|
|
4780
4788
|
if (subcommand === 'refresh') {
|
|
4781
4789
|
const repoRaw = args[1];
|
|
4782
4790
|
if (!repoRaw) {
|
|
4783
|
-
|
|
4791
|
+
printError('Usage: hippo brief refresh "<repo>" [--dry-run]');
|
|
4784
4792
|
process.exit(1);
|
|
4785
4793
|
}
|
|
4786
4794
|
const dryRun = Boolean(flags['dry-run']);
|
|
4787
4795
|
try {
|
|
4788
4796
|
if (dryRun) {
|
|
4789
4797
|
const { markdown, receiptCount } = briefsModule.assembleBriefFromReceipts(hippoRoot, tenantId, repoRaw);
|
|
4790
|
-
|
|
4798
|
+
printError(`(dry-run: assembled from ${receiptCount} receipt(s); brief NOT written)`);
|
|
4791
4799
|
console.log(markdown);
|
|
4792
4800
|
return;
|
|
4793
4801
|
}
|
|
@@ -4799,7 +4807,7 @@ function cmdProjectBrief(hippoRoot, args, flags) {
|
|
|
4799
4807
|
console.log(` memory: ${created.memoryId}`);
|
|
4800
4808
|
}
|
|
4801
4809
|
catch (e) {
|
|
4802
|
-
|
|
4810
|
+
printError(e.message);
|
|
4803
4811
|
process.exit(1);
|
|
4804
4812
|
}
|
|
4805
4813
|
return;
|
|
@@ -4807,13 +4815,13 @@ function cmdProjectBrief(hippoRoot, args, flags) {
|
|
|
4807
4815
|
if (subcommand === 'get') {
|
|
4808
4816
|
const idRaw = args[1];
|
|
4809
4817
|
if (!idRaw) {
|
|
4810
|
-
|
|
4818
|
+
printError('Usage: hippo brief get <id>');
|
|
4811
4819
|
process.exit(1);
|
|
4812
4820
|
}
|
|
4813
4821
|
const id = parsePositiveBriefId(idRaw);
|
|
4814
4822
|
const b = briefsModule.loadProjectBriefById(hippoRoot, tenantId, id);
|
|
4815
4823
|
if (!b) {
|
|
4816
|
-
|
|
4824
|
+
printError(`Project brief ${id} not found.`);
|
|
4817
4825
|
process.exit(1);
|
|
4818
4826
|
}
|
|
4819
4827
|
console.log(`Project brief #${b.id}`);
|
|
@@ -4837,18 +4845,18 @@ function cmdProjectBrief(hippoRoot, args, flags) {
|
|
|
4837
4845
|
if (subcommand === 'supersede') {
|
|
4838
4846
|
const idRaw = args[1];
|
|
4839
4847
|
if (!idRaw) {
|
|
4840
|
-
|
|
4848
|
+
printError('Usage: hippo brief supersede <id> --summary "<text>" [--change "<summary>"]');
|
|
4841
4849
|
process.exit(1);
|
|
4842
4850
|
}
|
|
4843
4851
|
const id = parsePositiveBriefId(idRaw);
|
|
4844
4852
|
const summaryRaw = flags['summary'];
|
|
4845
4853
|
if (typeof summaryRaw !== 'string' || !summaryRaw.trim()) {
|
|
4846
|
-
|
|
4854
|
+
printError('hippo brief supersede requires --summary "<text>" for the new version.');
|
|
4847
4855
|
process.exit(1);
|
|
4848
4856
|
}
|
|
4849
4857
|
const existing = briefsModule.loadProjectBriefById(hippoRoot, tenantId, id);
|
|
4850
4858
|
if (!existing) {
|
|
4851
|
-
|
|
4859
|
+
printError(`Project brief ${id} not found.`);
|
|
4852
4860
|
process.exit(1);
|
|
4853
4861
|
}
|
|
4854
4862
|
const changeRaw = flags['change'];
|
|
@@ -4865,7 +4873,7 @@ function cmdProjectBrief(hippoRoot, args, flags) {
|
|
|
4865
4873
|
console.log(` memory: ${created.memoryId}`);
|
|
4866
4874
|
}
|
|
4867
4875
|
catch (e) {
|
|
4868
|
-
|
|
4876
|
+
printError(e.message);
|
|
4869
4877
|
process.exit(1);
|
|
4870
4878
|
}
|
|
4871
4879
|
return;
|
|
@@ -4873,7 +4881,7 @@ function cmdProjectBrief(hippoRoot, args, flags) {
|
|
|
4873
4881
|
if (subcommand === 'close') {
|
|
4874
4882
|
const idRaw = args[1];
|
|
4875
4883
|
if (!idRaw) {
|
|
4876
|
-
|
|
4884
|
+
printError('Usage: hippo brief close <id>');
|
|
4877
4885
|
process.exit(1);
|
|
4878
4886
|
}
|
|
4879
4887
|
const id = parsePositiveBriefId(idRaw);
|
|
@@ -4900,7 +4908,7 @@ function cmdProjectBrief(hippoRoot, args, flags) {
|
|
|
4900
4908
|
console.log(` memory: ${created.memoryId}`);
|
|
4901
4909
|
}
|
|
4902
4910
|
catch (e) {
|
|
4903
|
-
|
|
4911
|
+
printError(e.message);
|
|
4904
4912
|
process.exit(1);
|
|
4905
4913
|
}
|
|
4906
4914
|
}
|
|
@@ -4908,7 +4916,7 @@ function parsePositiveNoteId(idRaw) {
|
|
|
4908
4916
|
const s = String(idRaw ?? '').trim();
|
|
4909
4917
|
const id = parseInt(s, 10);
|
|
4910
4918
|
if (!/^\d+$/.test(s) || id <= 0) {
|
|
4911
|
-
|
|
4919
|
+
printError(`Invalid note id: "${idRaw}" (expected a positive integer).`);
|
|
4912
4920
|
process.exit(1);
|
|
4913
4921
|
}
|
|
4914
4922
|
return id;
|
|
@@ -4919,11 +4927,11 @@ function printNoteRow(n) {
|
|
|
4919
4927
|
console.log(` change: ${n.changeSummary}`);
|
|
4920
4928
|
}
|
|
4921
4929
|
function noteUsage() {
|
|
4922
|
-
|
|
4923
|
-
|
|
4924
|
-
|
|
4925
|
-
|
|
4926
|
-
|
|
4930
|
+
printError('Usage: hippo note new "<customer>" --text "<note>"');
|
|
4931
|
+
printError(' hippo note list [--status active|superseded|closed|all] [--customer "<id>"] [--limit N]');
|
|
4932
|
+
printError(' hippo note get <id>');
|
|
4933
|
+
printError(' hippo note supersede <id> --text "<note>" [--change "<summary>"]');
|
|
4934
|
+
printError(' hippo note close <id>');
|
|
4927
4935
|
}
|
|
4928
4936
|
function cmdGraph(hippoRoot, args, flags) {
|
|
4929
4937
|
requireInit(hippoRoot);
|
|
@@ -4937,7 +4945,7 @@ function cmdGraph(hippoRoot, args, flags) {
|
|
|
4937
4945
|
const supersedes = result.relations - result.references;
|
|
4938
4946
|
console.log(`Graph extracted: ${result.entities} entities (${byType}) + ${result.relations} relations (${supersedes} supersedes, ${result.references} references).`);
|
|
4939
4947
|
if (result.truncated.length > 0) {
|
|
4940
|
-
|
|
4948
|
+
printError(`WARNING: under-extracted (hit the per-type cap): ${result.truncated.join(', ')}. The graph is incomplete for those types.`);
|
|
4941
4949
|
}
|
|
4942
4950
|
return;
|
|
4943
4951
|
}
|
|
@@ -4976,7 +4984,7 @@ function cmdGraph(hippoRoot, args, flags) {
|
|
|
4976
4984
|
if (subcommand === 'view') {
|
|
4977
4985
|
const format = typeof flags['format'] === 'string' ? flags['format'] : 'html';
|
|
4978
4986
|
if (format !== 'html' && format !== 'canvas') {
|
|
4979
|
-
|
|
4987
|
+
printError("graph view: --format must be 'html' or 'canvas'");
|
|
4980
4988
|
process.exit(1);
|
|
4981
4989
|
}
|
|
4982
4990
|
const model = buildGraphModel(hippoRoot, tenantId, { entity, limit: DEFAULT_VIEW_LIMIT });
|
|
@@ -5006,7 +5014,7 @@ function cmdGraph(hippoRoot, args, flags) {
|
|
|
5006
5014
|
}
|
|
5007
5015
|
return;
|
|
5008
5016
|
}
|
|
5009
|
-
|
|
5017
|
+
printError('Usage:\n' +
|
|
5010
5018
|
' hippo graph extract Rebuild the entity/relation graph from consolidated objects\n' +
|
|
5011
5019
|
' hippo graph show [--entity NAME] [--json] Inspect entities + their edges (text or JSON)\n' +
|
|
5012
5020
|
' hippo graph view [--out FILE] [--open] [--format html|canvas] [--entity NAME] Generate an interactive node-link diagram');
|
|
@@ -5024,13 +5032,13 @@ function cmdCustomerNote(hippoRoot, args, flags) {
|
|
|
5024
5032
|
const limitRaw = flags['limit'];
|
|
5025
5033
|
const limit = limitRaw !== undefined ? parseInt(String(limitRaw), 10) : 100;
|
|
5026
5034
|
if (!Number.isFinite(limit) || limit <= 0) {
|
|
5027
|
-
|
|
5035
|
+
printError(`Invalid --limit: "${limitRaw}". Must be a positive integer.`);
|
|
5028
5036
|
process.exit(1);
|
|
5029
5037
|
}
|
|
5030
5038
|
const opts = { limit, customer };
|
|
5031
5039
|
if (status !== 'all') {
|
|
5032
5040
|
if (!customerNotesModule.VALID_NOTE_STATES.has(status)) {
|
|
5033
|
-
|
|
5041
|
+
printError(`Invalid --status: "${status}". Must be one of: active | superseded | closed | all.`);
|
|
5034
5042
|
process.exit(1);
|
|
5035
5043
|
}
|
|
5036
5044
|
opts.status = status;
|
|
@@ -5048,13 +5056,13 @@ function cmdCustomerNote(hippoRoot, args, flags) {
|
|
|
5048
5056
|
if (subcommand === 'get') {
|
|
5049
5057
|
const idRaw = args[1];
|
|
5050
5058
|
if (!idRaw) {
|
|
5051
|
-
|
|
5059
|
+
printError('Usage: hippo note get <id>');
|
|
5052
5060
|
process.exit(1);
|
|
5053
5061
|
}
|
|
5054
5062
|
const id = parsePositiveNoteId(idRaw);
|
|
5055
5063
|
const n = customerNotesModule.loadCustomerNoteById(hippoRoot, tenantId, id);
|
|
5056
5064
|
if (!n) {
|
|
5057
|
-
|
|
5065
|
+
printError(`Customer note ${id} not found.`);
|
|
5058
5066
|
process.exit(1);
|
|
5059
5067
|
}
|
|
5060
5068
|
console.log(`Customer note #${n.id}`);
|
|
@@ -5078,18 +5086,18 @@ function cmdCustomerNote(hippoRoot, args, flags) {
|
|
|
5078
5086
|
if (subcommand === 'supersede') {
|
|
5079
5087
|
const idRaw = args[1];
|
|
5080
5088
|
if (!idRaw) {
|
|
5081
|
-
|
|
5089
|
+
printError('Usage: hippo note supersede <id> --text "<note>" [--change "<summary>"]');
|
|
5082
5090
|
process.exit(1);
|
|
5083
5091
|
}
|
|
5084
5092
|
const id = parsePositiveNoteId(idRaw);
|
|
5085
5093
|
const textRaw = flags['text'];
|
|
5086
5094
|
if (typeof textRaw !== 'string' || !textRaw.trim()) {
|
|
5087
|
-
|
|
5095
|
+
printError('hippo note supersede requires --text "<note>" for the new version.');
|
|
5088
5096
|
process.exit(1);
|
|
5089
5097
|
}
|
|
5090
5098
|
const existing = customerNotesModule.loadCustomerNoteById(hippoRoot, tenantId, id);
|
|
5091
5099
|
if (!existing) {
|
|
5092
|
-
|
|
5100
|
+
printError(`Customer note ${id} not found.`);
|
|
5093
5101
|
process.exit(1);
|
|
5094
5102
|
}
|
|
5095
5103
|
const changeRaw = flags['change'];
|
|
@@ -5106,7 +5114,7 @@ function cmdCustomerNote(hippoRoot, args, flags) {
|
|
|
5106
5114
|
console.log(` memory: ${created.memoryId}`);
|
|
5107
5115
|
}
|
|
5108
5116
|
catch (e) {
|
|
5109
|
-
|
|
5117
|
+
printError(e.message);
|
|
5110
5118
|
process.exit(1);
|
|
5111
5119
|
}
|
|
5112
5120
|
return;
|
|
@@ -5114,7 +5122,7 @@ function cmdCustomerNote(hippoRoot, args, flags) {
|
|
|
5114
5122
|
if (subcommand === 'close') {
|
|
5115
5123
|
const idRaw = args[1];
|
|
5116
5124
|
if (!idRaw) {
|
|
5117
|
-
|
|
5125
|
+
printError('Usage: hippo note close <id>');
|
|
5118
5126
|
process.exit(1);
|
|
5119
5127
|
}
|
|
5120
5128
|
const id = parsePositiveNoteId(idRaw);
|
|
@@ -5141,7 +5149,7 @@ function cmdCustomerNote(hippoRoot, args, flags) {
|
|
|
5141
5149
|
console.log(` memory: ${created.memoryId}`);
|
|
5142
5150
|
}
|
|
5143
5151
|
catch (e) {
|
|
5144
|
-
|
|
5152
|
+
printError(e.message);
|
|
5145
5153
|
process.exit(1);
|
|
5146
5154
|
}
|
|
5147
5155
|
}
|
|
@@ -5196,7 +5204,7 @@ function cmdCurrent(hippoRoot, args, flags) {
|
|
|
5196
5204
|
}
|
|
5197
5205
|
return;
|
|
5198
5206
|
}
|
|
5199
|
-
|
|
5207
|
+
printError('Usage: hippo current <show>');
|
|
5200
5208
|
process.exit(1);
|
|
5201
5209
|
}
|
|
5202
5210
|
/** Hook commands share one handle per store and wait at most HOOK_DB_WAIT_MS for a lock; a store still busy after that skips the hook's work with one warning, exit 0. */
|
|
@@ -5236,7 +5244,8 @@ function startDeliveryRecorder(hippoRoot, flags, stdinText) {
|
|
|
5236
5244
|
});
|
|
5237
5245
|
}
|
|
5238
5246
|
catch (error) {
|
|
5239
|
-
|
|
5247
|
+
// The hook's one-line stderr contract pins this exact text, so it bypasses the leveled logger.
|
|
5248
|
+
printError(`[hippo] delivery ledger skipped:${error instanceof Error ? error.message : String(error)}`);
|
|
5240
5249
|
return null;
|
|
5241
5250
|
}
|
|
5242
5251
|
}
|
|
@@ -5248,7 +5257,8 @@ function flushDeliveryRecorder(rec, db) {
|
|
|
5248
5257
|
rec.flush((input) => (db ? writeDeliveryEventOnHandle(db, input) : writeDeliveryEventAtRoot(rec.root, input)));
|
|
5249
5258
|
}
|
|
5250
5259
|
catch (error) {
|
|
5251
|
-
|
|
5260
|
+
// Pinned stderr text, as in the recorder build above.
|
|
5261
|
+
printError(`[hippo] delivery ledger write failed:${error instanceof Error ? error.message : String(error)}`);
|
|
5252
5262
|
}
|
|
5253
5263
|
}
|
|
5254
5264
|
/**
|
|
@@ -5489,6 +5499,7 @@ async function renderContext(hippoRoot, args, flags, stdinText, rec) {
|
|
|
5489
5499
|
tenantId: ctx.tenantId, sessionId: ledgerSessionId, surface, event: 'inject',
|
|
5490
5500
|
items: staticItems.length, tokens: estimateTokens(finalStatic), hash: blockHash(finalStatic),
|
|
5491
5501
|
});
|
|
5502
|
+
// Best-effort row: only a busy store is actionable, and a ledger failure must not break the hook.
|
|
5492
5503
|
}
|
|
5493
5504
|
catch (error) {
|
|
5494
5505
|
if (isSqliteBusy(error))
|
|
@@ -5501,6 +5512,7 @@ async function renderContext(hippoRoot, args, flags, stdinText, rec) {
|
|
|
5501
5512
|
tenantId: ctx.tenantId, sessionId: ledgerSessionId, surface: 'hook_recall', event: 'inject',
|
|
5502
5513
|
items: recallItems.length, tokens: estimateTokens(recallBlock), hash: blockHash(recallBlock),
|
|
5503
5514
|
});
|
|
5515
|
+
// Same best-effort rule as the inject row above.
|
|
5504
5516
|
}
|
|
5505
5517
|
catch (error) {
|
|
5506
5518
|
if (isSqliteBusy(error))
|
|
@@ -5608,7 +5620,7 @@ async function cmdEmbed(hippoRoot, flags) {
|
|
|
5608
5620
|
return resolveEmbeddingProvider(root);
|
|
5609
5621
|
}
|
|
5610
5622
|
catch (err) {
|
|
5611
|
-
|
|
5623
|
+
printError(err instanceof Error ? err.message : String(err));
|
|
5612
5624
|
return null;
|
|
5613
5625
|
}
|
|
5614
5626
|
})();
|
|
@@ -5626,8 +5638,8 @@ async function cmdEmbed(hippoRoot, flags) {
|
|
|
5626
5638
|
console.log(' npm install @huggingface/transformers');
|
|
5627
5639
|
}
|
|
5628
5640
|
else {
|
|
5629
|
-
|
|
5630
|
-
|
|
5641
|
+
printError(`Embedding provider '${embedProvider.kind}' is configured but ${embedProvider.keyEnv} is not set.`);
|
|
5642
|
+
printError(`Export ${embedProvider.keyEnv}, or set config.embeddings.provider back to 'local'.`);
|
|
5631
5643
|
process.exitCode = 1;
|
|
5632
5644
|
}
|
|
5633
5645
|
return;
|
|
@@ -5638,9 +5650,9 @@ async function cmdEmbed(hippoRoot, flags) {
|
|
|
5638
5650
|
count = await embedAll(root, resolveEmbeddingModel(root));
|
|
5639
5651
|
}
|
|
5640
5652
|
catch (err) {
|
|
5641
|
-
|
|
5653
|
+
printError(`Embedding failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
5642
5654
|
const partial = loadEmbeddingIndex(root);
|
|
5643
|
-
|
|
5655
|
+
printError(`Partial progress saved: ${Object.keys(partial).length} embeddings on disk. Re-run \`hippo embed\` to resume.`);
|
|
5644
5656
|
process.exitCode = 1;
|
|
5645
5657
|
return;
|
|
5646
5658
|
}
|
|
@@ -5653,7 +5665,7 @@ async function cmdEmbed(hippoRoot, flags) {
|
|
|
5653
5665
|
// ---------------------------------------------------------------------------
|
|
5654
5666
|
async function cmdWatch(command, hippoRoot) {
|
|
5655
5667
|
if (!command) {
|
|
5656
|
-
|
|
5668
|
+
printError('Usage: hippo watch "<command>"');
|
|
5657
5669
|
process.exit(1);
|
|
5658
5670
|
}
|
|
5659
5671
|
const { exitCode, stderr } = await runWatched(command);
|
|
@@ -5663,7 +5675,7 @@ async function cmdWatch(command, hippoRoot) {
|
|
|
5663
5675
|
}
|
|
5664
5676
|
// Only create memory if hippo is initialized
|
|
5665
5677
|
if (!isInitialized(hippoRoot)) {
|
|
5666
|
-
|
|
5678
|
+
printError('Command failed but .hippo not initialized. Run `hippo init` to enable auto-learn.');
|
|
5667
5679
|
process.exit(exitCode);
|
|
5668
5680
|
}
|
|
5669
5681
|
const entry = captureError(exitCode, stderr, command, resolveTenantId({}));
|
|
@@ -5682,11 +5694,11 @@ async function cmdWatch(command, hippoRoot) {
|
|
|
5682
5694
|
updateStats(hippoRoot, { remembered: 1 });
|
|
5683
5695
|
void embedMemory(hippoRoot, entry);
|
|
5684
5696
|
const preview = stderr.trim().slice(0, 80);
|
|
5685
|
-
|
|
5697
|
+
printError(`\nHippo learned from failure: "${preview}"`);
|
|
5686
5698
|
}
|
|
5687
5699
|
catch (err) {
|
|
5688
5700
|
if (err instanceof RejectedValueError) {
|
|
5689
|
-
|
|
5701
|
+
printError(`\nHippo: this failure matches a rejected value (${err.reason ?? 'no reason given'}); not stored.`);
|
|
5690
5702
|
}
|
|
5691
5703
|
else {
|
|
5692
5704
|
throw err;
|
|
@@ -5700,7 +5712,7 @@ async function cmdWatch(command, hippoRoot) {
|
|
|
5700
5712
|
function cmdLearn(hippoRoot, flags) {
|
|
5701
5713
|
requireInit(hippoRoot);
|
|
5702
5714
|
if (!flags['git']) {
|
|
5703
|
-
|
|
5715
|
+
printError('Usage: hippo learn --git [--days <n>] [--repos <paths>]');
|
|
5704
5716
|
process.exit(1);
|
|
5705
5717
|
}
|
|
5706
5718
|
const days = parseInt(String(flags['days'] ?? '7'), 10);
|
|
@@ -5728,7 +5740,7 @@ function cmdLearn(hippoRoot, flags) {
|
|
|
5728
5740
|
// ---------------------------------------------------------------------------
|
|
5729
5741
|
function warnRedacted(count) {
|
|
5730
5742
|
if (count)
|
|
5731
|
-
|
|
5743
|
+
printError(`Warning: secret-shaped text was redacted from ${count} imported ${count === 1 ? 'entry' : 'entries'} before storing`);
|
|
5732
5744
|
}
|
|
5733
5745
|
function cmdImport(hippoRoot, args, flags) {
|
|
5734
5746
|
const useGlobal = Boolean(flags['global']);
|
|
@@ -5748,7 +5760,7 @@ function cmdImport(hippoRoot, args, flags) {
|
|
|
5748
5760
|
for (const line of detailLines(report, dryRun))
|
|
5749
5761
|
console.log(line);
|
|
5750
5762
|
for (const warning of report.warnings)
|
|
5751
|
-
|
|
5763
|
+
printError(`hippo: agent memories: ${warning}`);
|
|
5752
5764
|
return;
|
|
5753
5765
|
}
|
|
5754
5766
|
if (useGlobal) {
|
|
@@ -5772,11 +5784,11 @@ function cmdImport(hippoRoot, args, flags) {
|
|
|
5772
5784
|
if (flags['vault']) {
|
|
5773
5785
|
const folderPath = String(flags['vault']);
|
|
5774
5786
|
if (!fs.existsSync(folderPath) || !fs.statSync(folderPath).isDirectory()) {
|
|
5775
|
-
|
|
5787
|
+
printError(`Vault folder not found (or not a directory): ${folderPath}`);
|
|
5776
5788
|
process.exit(1);
|
|
5777
5789
|
}
|
|
5778
5790
|
if (useGlobal) {
|
|
5779
|
-
|
|
5791
|
+
printError('hippo import --vault does not support --global (raw rows are tenant-local).');
|
|
5780
5792
|
process.exit(1);
|
|
5781
5793
|
}
|
|
5782
5794
|
if (typeof flags['name'] !== 'string' || !flags['name'].trim()) {
|
|
@@ -5785,14 +5797,14 @@ function cmdImport(hippoRoot, args, flags) {
|
|
|
5785
5797
|
// clobber each other (codex R10 P2). A valueless `--name` parses as boolean
|
|
5786
5798
|
// true, and String(true) === "true" would silently import under vault:true:*
|
|
5787
5799
|
// - reject a non-string so it fails fast instead (codex R11 P2).
|
|
5788
|
-
|
|
5800
|
+
printError('hippo import --vault requires --name <vault> (a non-empty identity key for source-deletion sync).');
|
|
5789
5801
|
process.exit(1);
|
|
5790
5802
|
}
|
|
5791
5803
|
if (flags['scope'] !== undefined && (typeof flags['scope'] !== 'string' || !flags['scope'].trim())) {
|
|
5792
5804
|
// Same valueless-flag trap: a bare `--scope` must not become scope "true".
|
|
5793
5805
|
// Example uses the source-prefixed private form, since a bare `private` scope
|
|
5794
5806
|
// is NOT treated as private by recall and importVault rejects it (R13 P2).
|
|
5795
|
-
|
|
5807
|
+
printError('hippo import --vault: --scope requires a value (e.g. --scope vault:private:notes).');
|
|
5796
5808
|
process.exit(1);
|
|
5797
5809
|
}
|
|
5798
5810
|
const tenantId = resolveTenantId({});
|
|
@@ -5857,11 +5869,11 @@ function cmdImport(hippoRoot, args, flags) {
|
|
|
5857
5869
|
importerName = 'File';
|
|
5858
5870
|
}
|
|
5859
5871
|
if (!filePath || !importer) {
|
|
5860
|
-
|
|
5872
|
+
printError('Usage: hippo import <--chatgpt|--claude|--cursor|--file|--markdown|--vault> <path>, or hippo import --agents [--dry-run]');
|
|
5861
5873
|
process.exit(1);
|
|
5862
5874
|
}
|
|
5863
5875
|
if (!fs.existsSync(filePath)) {
|
|
5864
|
-
|
|
5876
|
+
printError(`File not found: ${filePath}`);
|
|
5865
5877
|
process.exit(1);
|
|
5866
5878
|
}
|
|
5867
5879
|
const result = importer(filePath, importOptions);
|
|
@@ -5905,7 +5917,7 @@ function cmdImport(hippoRoot, args, flags) {
|
|
|
5905
5917
|
function cmdPromote(hippoRoot, id) {
|
|
5906
5918
|
requireInit(hippoRoot);
|
|
5907
5919
|
if (!id) {
|
|
5908
|
-
|
|
5920
|
+
printError('Usage: hippo promote <id>');
|
|
5909
5921
|
process.exit(1);
|
|
5910
5922
|
}
|
|
5911
5923
|
const ctx = {
|
|
@@ -5919,7 +5931,7 @@ function cmdPromote(hippoRoot, id) {
|
|
|
5919
5931
|
console.log(` Global store: ${getGlobalRoot()}`);
|
|
5920
5932
|
}
|
|
5921
5933
|
catch (err) {
|
|
5922
|
-
|
|
5934
|
+
printError(`Failed to promote: ${err.message}`);
|
|
5923
5935
|
process.exit(1);
|
|
5924
5936
|
}
|
|
5925
5937
|
}
|
|
@@ -5957,8 +5969,8 @@ function cmdHook(args, flags) {
|
|
|
5957
5969
|
}
|
|
5958
5970
|
if (subcommand === 'install') {
|
|
5959
5971
|
if (!target || !HOOKS[target]) {
|
|
5960
|
-
|
|
5961
|
-
|
|
5972
|
+
printError(`Unknown hook target: ${target ?? '(none)'}`);
|
|
5973
|
+
printError(` Available: ${Object.keys(HOOKS).join(', ')}`);
|
|
5962
5974
|
process.exit(1);
|
|
5963
5975
|
}
|
|
5964
5976
|
const hook = HOOKS[target];
|
|
@@ -6058,7 +6070,7 @@ function cmdHook(args, flags) {
|
|
|
6058
6070
|
}
|
|
6059
6071
|
if (subcommand === 'uninstall') {
|
|
6060
6072
|
if (!target || !HOOKS[target]) {
|
|
6061
|
-
|
|
6073
|
+
printError(`Unknown hook target: ${target ?? '(none)'}`);
|
|
6062
6074
|
process.exit(1);
|
|
6063
6075
|
}
|
|
6064
6076
|
const hook = HOOKS[target];
|
|
@@ -6118,7 +6130,7 @@ function cmdHook(args, flags) {
|
|
|
6118
6130
|
}
|
|
6119
6131
|
return;
|
|
6120
6132
|
}
|
|
6121
|
-
|
|
6133
|
+
printError('Usage: hippo hook <install|uninstall|list> [target]');
|
|
6122
6134
|
process.exit(1);
|
|
6123
6135
|
}
|
|
6124
6136
|
function withoutHookBlock(text) {
|
|
@@ -6265,7 +6277,7 @@ function cmdDailyRunner() {
|
|
|
6265
6277
|
const globalRoot = getGlobalRoot();
|
|
6266
6278
|
// No workspace sleep ever opens the global store, yet hooks in folders without a store compact into it.
|
|
6267
6279
|
if (isInitialized(globalRoot)) {
|
|
6268
|
-
const finished = replayCompactionsAt(globalRoot, (message) =>
|
|
6280
|
+
const finished = replayCompactionsAt(globalRoot, (message) => log.warn(`compaction replay: ${message}`));
|
|
6269
6281
|
if (finished > 0)
|
|
6270
6282
|
console.log(`Finished saving ${finished} compaction${finished === 1 ? '' : 's'} left over in the global store.`);
|
|
6271
6283
|
}
|
|
@@ -6291,7 +6303,7 @@ function cmdDailyRunner() {
|
|
|
6291
6303
|
catch (err) {
|
|
6292
6304
|
failed++;
|
|
6293
6305
|
const action = args.join(' ');
|
|
6294
|
-
|
|
6306
|
+
log.error(`daily-runner failed in ${cwd} during \`${action}\`: ${err.message}`);
|
|
6295
6307
|
}
|
|
6296
6308
|
});
|
|
6297
6309
|
console.log(`Daily maintenance complete: ${processed} workspace${processed === 1 ? '' : 's'} processed, ${failed} command failure${failed === 1 ? '' : 's'}.`);
|
|
@@ -6309,7 +6321,7 @@ function cmdWm(hippoRoot, args, flags) {
|
|
|
6309
6321
|
const sessionId = flags['session'] ? String(flags['session']).trim() : undefined;
|
|
6310
6322
|
const taskId = flags['task'] ? String(flags['task']).trim() : undefined;
|
|
6311
6323
|
if (!content) {
|
|
6312
|
-
|
|
6324
|
+
printError('Usage: hippo wm push --scope <scope> --content "..." [--importance 0.8] [--session <id>] [--task <id>]');
|
|
6313
6325
|
process.exit(1);
|
|
6314
6326
|
}
|
|
6315
6327
|
const id = wmPush(hippoRoot, {
|
|
@@ -6360,7 +6372,7 @@ function cmdWm(hippoRoot, args, flags) {
|
|
|
6360
6372
|
console.log(`Flushed ${count} working memory entries.`);
|
|
6361
6373
|
return;
|
|
6362
6374
|
}
|
|
6363
|
-
|
|
6375
|
+
printError('Usage: hippo wm <push|read|clear|flush>');
|
|
6364
6376
|
process.exit(1);
|
|
6365
6377
|
}
|
|
6366
6378
|
function cmdDag(hippoRoot, flags) {
|
|
@@ -6477,7 +6489,7 @@ function cmdDrillDown(hippoRoot, summaryId, flags) {
|
|
|
6477
6489
|
let depth;
|
|
6478
6490
|
if (rawDepth !== undefined) {
|
|
6479
6491
|
if (!Number.isInteger(rawDepth) || rawDepth < 1 || rawDepth > 10) {
|
|
6480
|
-
|
|
6492
|
+
printError(`--depth must be an integer between 1 and 10 (got ${flags['depth']})`);
|
|
6481
6493
|
process.exit(2);
|
|
6482
6494
|
}
|
|
6483
6495
|
depth = rawDepth;
|
|
@@ -6498,10 +6510,10 @@ function cmdDrillDown(hippoRoot, summaryId, flags) {
|
|
|
6498
6510
|
// intentionally collapses cross-tenant + scope-blocked + missing
|
|
6499
6511
|
// (codex round 3 P1: distinguishing scope_blocked leaked existence).
|
|
6500
6512
|
if (r.failure === 'not_drillable') {
|
|
6501
|
-
|
|
6513
|
+
printError(`Id ${summaryId} is a leaf row, not a level-2+ summary; nothing to drill into.`);
|
|
6502
6514
|
}
|
|
6503
6515
|
else {
|
|
6504
|
-
|
|
6516
|
+
printError(`No drillable summary at id=${summaryId}.`);
|
|
6505
6517
|
}
|
|
6506
6518
|
process.exit(1);
|
|
6507
6519
|
}
|
|
@@ -6531,7 +6543,7 @@ function cmdAuthCreate(hippoRoot, flags) {
|
|
|
6531
6543
|
let role = 'admin';
|
|
6532
6544
|
if (roleFlag !== undefined) {
|
|
6533
6545
|
if (roleFlag !== 'admin' && roleFlag !== 'member') {
|
|
6534
|
-
|
|
6546
|
+
printError(`Invalid --role value: '${roleFlag}'. Use 'admin' or 'member'.`);
|
|
6535
6547
|
process.exit(1);
|
|
6536
6548
|
}
|
|
6537
6549
|
role = roleFlag;
|
|
@@ -6610,7 +6622,7 @@ function cmdAuthRevoke(hippoRoot, keyId, flags) {
|
|
|
6610
6622
|
closeHippoDb(db);
|
|
6611
6623
|
}
|
|
6612
6624
|
if (keyTenant === undefined) {
|
|
6613
|
-
|
|
6625
|
+
printError(`Unknown key_id: ${keyId}`);
|
|
6614
6626
|
process.exit(1);
|
|
6615
6627
|
}
|
|
6616
6628
|
const ctx = { hippoRoot: root, tenantId: keyTenant, actor: api.adminActor('cli') };
|
|
@@ -6619,7 +6631,7 @@ function cmdAuthRevoke(hippoRoot, keyId, flags) {
|
|
|
6619
6631
|
revokedAt = api.authRevoke(ctx, keyId).revokedAt;
|
|
6620
6632
|
}
|
|
6621
6633
|
catch (err) {
|
|
6622
|
-
|
|
6634
|
+
printError(`Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
6623
6635
|
process.exit(1);
|
|
6624
6636
|
}
|
|
6625
6637
|
if (flags['json']) {
|
|
@@ -6643,7 +6655,7 @@ function cmdAuthScopeGrant(hippoRoot, keyId, scope, grant, flags) {
|
|
|
6643
6655
|
closeHippoDb(db);
|
|
6644
6656
|
}
|
|
6645
6657
|
if (keyTenant === undefined) {
|
|
6646
|
-
|
|
6658
|
+
printError(`Unknown key_id: ${keyId}`);
|
|
6647
6659
|
process.exit(1);
|
|
6648
6660
|
}
|
|
6649
6661
|
const ctx = { hippoRoot: root, tenantId: keyTenant, actor: api.adminActor('cli') };
|
|
@@ -6654,7 +6666,7 @@ function cmdAuthScopeGrant(hippoRoot, keyId, scope, grant, flags) {
|
|
|
6654
6666
|
api.authUngrant(ctx, keyId, scope);
|
|
6655
6667
|
}
|
|
6656
6668
|
catch (err) {
|
|
6657
|
-
|
|
6669
|
+
printError(`Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
6658
6670
|
process.exit(1);
|
|
6659
6671
|
}
|
|
6660
6672
|
if (flags['json']) {
|
|
@@ -6681,13 +6693,13 @@ function cmdAuditList(hippoRoot, flags) {
|
|
|
6681
6693
|
// Regenerate from Set to prevent future drift (v1.11.5: pre-v1.11.5 message
|
|
6682
6694
|
// was hand-maintained and had drifted — missed 'auth_revoke' and 'outcome').
|
|
6683
6695
|
const expected = Array.from(VALID_AUDIT_OPS).join(' | ');
|
|
6684
|
-
|
|
6696
|
+
printError(`Unknown --op value: ${opFlag}. Expected one of: ${expected}.`);
|
|
6685
6697
|
process.exit(1);
|
|
6686
6698
|
}
|
|
6687
6699
|
const op = opFlag;
|
|
6688
6700
|
const since = typeof flags['since'] === 'string' ? flags['since'] : undefined;
|
|
6689
6701
|
if (since !== undefined && !Number.isFinite(new Date(since).getTime())) {
|
|
6690
|
-
|
|
6702
|
+
printError(`Invalid --since: ${since} (expected an ISO timestamp like 2026-04-22 or 2026-04-22T12:00:00Z).`);
|
|
6691
6703
|
process.exit(1);
|
|
6692
6704
|
}
|
|
6693
6705
|
const limitRaw = flags['limit'];
|
|
@@ -6695,13 +6707,13 @@ function cmdAuditList(hippoRoot, flags) {
|
|
|
6695
6707
|
if (limitRaw !== undefined && typeof limitRaw !== 'boolean') {
|
|
6696
6708
|
const parsed = parseInt(String(limitRaw), 10);
|
|
6697
6709
|
if (!Number.isFinite(parsed)) {
|
|
6698
|
-
|
|
6710
|
+
printError(`Invalid --limit value: ${String(limitRaw)} (expected a positive integer).`);
|
|
6699
6711
|
process.exit(1);
|
|
6700
6712
|
}
|
|
6701
6713
|
limit = parsed;
|
|
6702
6714
|
}
|
|
6703
6715
|
if (limit < 1 || limit > 10000) {
|
|
6704
|
-
|
|
6716
|
+
printError(`--limit must be between 1 and 10000 (got ${limit}).`);
|
|
6705
6717
|
process.exit(1);
|
|
6706
6718
|
}
|
|
6707
6719
|
const ctx = { hippoRoot: root, tenantId, actor: { subject: 'cli', role: 'admin' } };
|
|
@@ -6729,7 +6741,7 @@ function printAuditPruneUsage() {
|
|
|
6729
6741
|
function cmdAuditPrune(hippoRoot, flags) {
|
|
6730
6742
|
const olderThanRaw = typeof flags['older-than'] === 'string' ? flags['older-than'] : '';
|
|
6731
6743
|
if (!olderThanRaw) {
|
|
6732
|
-
|
|
6744
|
+
printError('Usage: hippo audit prune --older-than <Nd> [--dry-run] [--tenant <t>]');
|
|
6733
6745
|
process.exit(1);
|
|
6734
6746
|
}
|
|
6735
6747
|
let olderThanDays;
|
|
@@ -6737,7 +6749,7 @@ function cmdAuditPrune(hippoRoot, flags) {
|
|
|
6737
6749
|
olderThanDays = parseOlderThanFlag(olderThanRaw);
|
|
6738
6750
|
}
|
|
6739
6751
|
catch (e) {
|
|
6740
|
-
|
|
6752
|
+
printError(e.message);
|
|
6741
6753
|
process.exit(1);
|
|
6742
6754
|
}
|
|
6743
6755
|
const tenantId = typeof flags['tenant'] === 'string'
|
|
@@ -6773,7 +6785,7 @@ function cmdAuditLog(hippoRoot, args, flags) {
|
|
|
6773
6785
|
cmdAuditPrune(hippoRoot, flags);
|
|
6774
6786
|
return;
|
|
6775
6787
|
}
|
|
6776
|
-
|
|
6788
|
+
printError(`Unknown audit subcommand: ${sub}. Expected: list | prune.`);
|
|
6777
6789
|
process.exit(1);
|
|
6778
6790
|
}
|
|
6779
6791
|
// ---------------------------------------------------------------------------
|
|
@@ -6794,7 +6806,7 @@ function resolveGoalSession(flags) {
|
|
|
6794
6806
|
? String(flags['session-id'])
|
|
6795
6807
|
: process.env.HIPPO_SESSION_ID ?? '').trim();
|
|
6796
6808
|
if (!sessionId) {
|
|
6797
|
-
|
|
6809
|
+
printError('session id required (set HIPPO_SESSION_ID or pass --session-id)');
|
|
6798
6810
|
process.exit(1);
|
|
6799
6811
|
}
|
|
6800
6812
|
const tenantId = (flags['tenant-id'] !== undefined
|
|
@@ -6805,51 +6817,51 @@ function resolveGoalSession(flags) {
|
|
|
6805
6817
|
function cmdGoalPush(hippoRoot, args, flags) {
|
|
6806
6818
|
const rawName = args.join(' ').trim();
|
|
6807
6819
|
if (!rawName) {
|
|
6808
|
-
|
|
6820
|
+
printError('Usage: hippo goal push <name> [--policy <type>] [--success "<condition>"] [--level N] [--parent <goalId>]');
|
|
6809
6821
|
process.exit(1);
|
|
6810
6822
|
}
|
|
6811
6823
|
// Sanitize at WRITE time so corrupt names never enter the DB.
|
|
6812
6824
|
const name = sanitizeGoalName(rawName);
|
|
6813
6825
|
if (name !== rawName) {
|
|
6814
|
-
|
|
6826
|
+
printError('note: stripped control characters from goal name');
|
|
6815
6827
|
}
|
|
6816
6828
|
const { sessionId, tenantId } = resolveGoalSession(flags);
|
|
6817
6829
|
let policy;
|
|
6818
6830
|
const policyRaw = flags['policy'];
|
|
6819
6831
|
if (policyRaw === true) {
|
|
6820
|
-
|
|
6832
|
+
printError('--policy requires a value (e.g., --policy error-prioritized)');
|
|
6821
6833
|
process.exit(1);
|
|
6822
6834
|
}
|
|
6823
6835
|
if (typeof policyRaw === 'string') {
|
|
6824
6836
|
if (!GOAL_POLICY_TYPES.includes(policyRaw)) {
|
|
6825
|
-
|
|
6837
|
+
printError(`Unknown --policy '${policyRaw}'. Expected one of: ${GOAL_POLICY_TYPES.join(' | ')}.`);
|
|
6826
6838
|
process.exit(1);
|
|
6827
6839
|
}
|
|
6828
6840
|
policy = { policyType: policyRaw };
|
|
6829
6841
|
}
|
|
6830
6842
|
const successRaw = flags['success'];
|
|
6831
6843
|
if (successRaw === true) {
|
|
6832
|
-
|
|
6844
|
+
printError('--success requires a value (e.g., --success "<condition>")');
|
|
6833
6845
|
process.exit(1);
|
|
6834
6846
|
}
|
|
6835
6847
|
const successCondition = typeof successRaw === 'string' ? successRaw : undefined;
|
|
6836
6848
|
const levelRaw = flags['level'];
|
|
6837
6849
|
let level;
|
|
6838
6850
|
if (levelRaw === true) {
|
|
6839
|
-
|
|
6851
|
+
printError('--level requires a value (e.g., --level 1)');
|
|
6840
6852
|
process.exit(1);
|
|
6841
6853
|
}
|
|
6842
6854
|
if (levelRaw !== undefined) {
|
|
6843
6855
|
const parsed = Number(levelRaw);
|
|
6844
6856
|
if (!Number.isFinite(parsed) || parsed < 0 || parsed > 2 || !Number.isInteger(parsed)) {
|
|
6845
|
-
|
|
6857
|
+
printError('--level must be an integer in [0, 2]');
|
|
6846
6858
|
process.exit(1);
|
|
6847
6859
|
}
|
|
6848
6860
|
level = parsed;
|
|
6849
6861
|
}
|
|
6850
6862
|
const parentRaw = flags['parent'];
|
|
6851
6863
|
if (parentRaw === true) {
|
|
6852
|
-
|
|
6864
|
+
printError('--parent requires a value (e.g., --parent <goalId>)');
|
|
6853
6865
|
process.exit(1);
|
|
6854
6866
|
}
|
|
6855
6867
|
const parentGoalId = typeof parentRaw === 'string' ? parentRaw : undefined;
|
|
@@ -6915,19 +6927,19 @@ function cmdGoalList(hippoRoot, flags) {
|
|
|
6915
6927
|
function cmdGoalComplete(hippoRoot, args, flags) {
|
|
6916
6928
|
const id = args[0];
|
|
6917
6929
|
if (!id) {
|
|
6918
|
-
|
|
6930
|
+
printError('Usage: hippo goal complete <id> [--outcome <0..1>] [--no-propagate]');
|
|
6919
6931
|
process.exit(1);
|
|
6920
6932
|
}
|
|
6921
6933
|
let outcomeScore;
|
|
6922
6934
|
const outcomeRaw = flags['outcome'];
|
|
6923
6935
|
if (outcomeRaw === true) {
|
|
6924
|
-
|
|
6936
|
+
printError('--outcome requires a value (e.g., --outcome 0.9)');
|
|
6925
6937
|
process.exit(1);
|
|
6926
6938
|
}
|
|
6927
6939
|
if (outcomeRaw !== undefined) {
|
|
6928
6940
|
const parsed = Number(outcomeRaw);
|
|
6929
6941
|
if (!Number.isFinite(parsed) || parsed < 0 || parsed > 1) {
|
|
6930
|
-
|
|
6942
|
+
printError('--outcome must be a number in [0, 1]');
|
|
6931
6943
|
process.exit(1);
|
|
6932
6944
|
}
|
|
6933
6945
|
outcomeScore = parsed;
|
|
@@ -6939,7 +6951,7 @@ function cmdGoalComplete(hippoRoot, args, flags) {
|
|
|
6939
6951
|
function cmdGoalSuspend(hippoRoot, args) {
|
|
6940
6952
|
const id = args[0];
|
|
6941
6953
|
if (!id) {
|
|
6942
|
-
|
|
6954
|
+
printError('Usage: hippo goal suspend <id>');
|
|
6943
6955
|
process.exit(1);
|
|
6944
6956
|
}
|
|
6945
6957
|
suspendGoal(hippoRoot, id);
|
|
@@ -6948,7 +6960,7 @@ function cmdGoalSuspend(hippoRoot, args) {
|
|
|
6948
6960
|
function cmdGoalResume(hippoRoot, args) {
|
|
6949
6961
|
const id = args[0];
|
|
6950
6962
|
if (!id) {
|
|
6951
|
-
|
|
6963
|
+
printError('Usage: hippo goal resume <id>');
|
|
6952
6964
|
process.exit(1);
|
|
6953
6965
|
}
|
|
6954
6966
|
resumeGoal(hippoRoot, id);
|
|
@@ -6957,7 +6969,7 @@ function cmdGoalResume(hippoRoot, args) {
|
|
|
6957
6969
|
function cmdGoal(hippoRoot, args, flags) {
|
|
6958
6970
|
const sub = args[0];
|
|
6959
6971
|
if (!sub) {
|
|
6960
|
-
|
|
6972
|
+
printError('Usage: hippo goal <push|list|complete|suspend|resume> [args]');
|
|
6961
6973
|
process.exit(1);
|
|
6962
6974
|
}
|
|
6963
6975
|
const subArgs = args.slice(1);
|
|
@@ -6978,14 +6990,14 @@ function cmdGoal(hippoRoot, args, flags) {
|
|
|
6978
6990
|
cmdGoalResume(hippoRoot, subArgs);
|
|
6979
6991
|
return;
|
|
6980
6992
|
default:
|
|
6981
|
-
|
|
6993
|
+
printError(`Unknown goal subcommand: ${sub}. Expected: push | list | complete | suspend | resume.`);
|
|
6982
6994
|
process.exit(1);
|
|
6983
6995
|
}
|
|
6984
6996
|
}
|
|
6985
6997
|
function cmdAuth(hippoRoot, args, flags) {
|
|
6986
6998
|
const sub = args[0];
|
|
6987
6999
|
if (!sub) {
|
|
6988
|
-
|
|
7000
|
+
printError('Usage: hippo auth <create|list|revoke|grant|ungrant> [options]');
|
|
6989
7001
|
process.exit(1);
|
|
6990
7002
|
}
|
|
6991
7003
|
const subArgs = args.slice(1);
|
|
@@ -6999,7 +7011,7 @@ function cmdAuth(hippoRoot, args, flags) {
|
|
|
6999
7011
|
case 'revoke': {
|
|
7000
7012
|
const keyId = subArgs[0];
|
|
7001
7013
|
if (!keyId) {
|
|
7002
|
-
|
|
7014
|
+
printError('Usage: hippo auth revoke <key_id>');
|
|
7003
7015
|
process.exit(1);
|
|
7004
7016
|
}
|
|
7005
7017
|
cmdAuthRevoke(hippoRoot, keyId, flags);
|
|
@@ -7009,14 +7021,14 @@ function cmdAuth(hippoRoot, args, flags) {
|
|
|
7009
7021
|
case 'ungrant': {
|
|
7010
7022
|
const [keyId, scope] = subArgs;
|
|
7011
7023
|
if (!keyId || !scope) {
|
|
7012
|
-
|
|
7024
|
+
printError(`Usage: hippo auth ${sub} <key_id> <scope>`);
|
|
7013
7025
|
process.exit(1);
|
|
7014
7026
|
}
|
|
7015
7027
|
cmdAuthScopeGrant(hippoRoot, keyId, scope, sub === 'grant', flags);
|
|
7016
7028
|
return;
|
|
7017
7029
|
}
|
|
7018
7030
|
default:
|
|
7019
|
-
|
|
7031
|
+
printError(`Unknown auth subcommand: ${sub}. Expected: create | list | revoke | grant | ungrant.`);
|
|
7020
7032
|
process.exit(1);
|
|
7021
7033
|
}
|
|
7022
7034
|
}
|
|
@@ -7037,7 +7049,7 @@ function cmdSlackBackfill(hippoRoot, flags) {
|
|
|
7037
7049
|
// Real fetcher requires SLACK_BOT_TOKEN with channels:history scope.
|
|
7038
7050
|
const token = process.env.SLACK_BOT_TOKEN;
|
|
7039
7051
|
if (!token) {
|
|
7040
|
-
|
|
7052
|
+
printError('SLACK_BOT_TOKEN is not set. Backfill requires a Slack bot token with channels:history scope.');
|
|
7041
7053
|
process.exit(2);
|
|
7042
7054
|
}
|
|
7043
7055
|
// --since is advisory in V1: the slack_cursors row drives resume, so the
|
|
@@ -7060,7 +7072,7 @@ function cmdSlackBackfill(hippoRoot, flags) {
|
|
|
7060
7072
|
console.log(`backfill ${channel}: ${r.ingested} new messages across ${r.pages} pages`);
|
|
7061
7073
|
})
|
|
7062
7074
|
.catch((e) => {
|
|
7063
|
-
|
|
7075
|
+
printError('backfill failed:', e.message);
|
|
7064
7076
|
process.exit(3);
|
|
7065
7077
|
});
|
|
7066
7078
|
}
|
|
@@ -7080,12 +7092,12 @@ function cmdSlackDlqList(hippoRoot, _flags) {
|
|
|
7080
7092
|
function cmdSlackDlqReplay(hippoRoot, args, flags) {
|
|
7081
7093
|
const idArg = args[2];
|
|
7082
7094
|
if (!idArg) {
|
|
7083
|
-
|
|
7095
|
+
printError('Usage: hippo slack dlq replay <id> [--force]');
|
|
7084
7096
|
process.exit(1);
|
|
7085
7097
|
}
|
|
7086
7098
|
const id = Number(idArg);
|
|
7087
7099
|
if (!Number.isFinite(id) || !Number.isInteger(id) || id < 1) {
|
|
7088
|
-
|
|
7100
|
+
printError(`replay: invalid id ${idArg}`);
|
|
7089
7101
|
process.exit(1);
|
|
7090
7102
|
}
|
|
7091
7103
|
const force = flags.force === true;
|
|
@@ -7094,7 +7106,7 @@ function cmdSlackDlqReplay(hippoRoot, args, flags) {
|
|
|
7094
7106
|
signingSecret: process.env.SLACK_SIGNING_SECRET,
|
|
7095
7107
|
});
|
|
7096
7108
|
if (!result.ok) {
|
|
7097
|
-
|
|
7109
|
+
printError(`replay failed: status=${result.status} retry_count=${result.retryCount}${result.reason ? ` reason=${result.reason}` : ''}`);
|
|
7098
7110
|
process.exit(1);
|
|
7099
7111
|
}
|
|
7100
7112
|
console.log(`replay ok: status=${result.status} memory_id=${result.memoryId ?? '(none)'} retry_count=${result.retryCount}`);
|
|
@@ -7109,7 +7121,7 @@ function cmdSlackWorkspacesAdd(hippoRoot, flags) {
|
|
|
7109
7121
|
const teamId = typeof flags['team'] === 'string' ? flags['team'].trim() : '';
|
|
7110
7122
|
const tenantId = typeof flags['tenant'] === 'string' ? flags['tenant'].trim() : '';
|
|
7111
7123
|
if (!teamId || !tenantId) {
|
|
7112
|
-
|
|
7124
|
+
printError('Usage: hippo slack workspaces add --team <T> --tenant <t>');
|
|
7113
7125
|
process.exit(1);
|
|
7114
7126
|
}
|
|
7115
7127
|
const db = openHippoDb(hippoRoot);
|
|
@@ -7140,14 +7152,14 @@ function cmdSlackWorkspacesList(hippoRoot) {
|
|
|
7140
7152
|
function cmdSlackWorkspacesRemove(hippoRoot, flags) {
|
|
7141
7153
|
const teamId = typeof flags['team'] === 'string' ? flags['team'].trim() : '';
|
|
7142
7154
|
if (!teamId) {
|
|
7143
|
-
|
|
7155
|
+
printError('Usage: hippo slack workspaces remove --team <T>');
|
|
7144
7156
|
process.exit(1);
|
|
7145
7157
|
}
|
|
7146
7158
|
const db = openHippoDb(hippoRoot);
|
|
7147
7159
|
try {
|
|
7148
7160
|
const removed = removeSlackWorkspace(db, teamId);
|
|
7149
7161
|
if (!removed) {
|
|
7150
|
-
|
|
7162
|
+
printError(`no workspace registered for team ${teamId}`);
|
|
7151
7163
|
process.exit(1);
|
|
7152
7164
|
}
|
|
7153
7165
|
console.log(`removed: ${teamId}`);
|
|
@@ -7187,7 +7199,7 @@ function cmdSlack(hippoRoot, args, flags) {
|
|
|
7187
7199
|
printSlackWorkspacesUsage();
|
|
7188
7200
|
process.exit(1);
|
|
7189
7201
|
}
|
|
7190
|
-
|
|
7202
|
+
printError('Usage: hippo slack <backfill|dlq list|dlq replay <id> [--force]|workspaces add|workspaces list|workspaces remove> [...]');
|
|
7191
7203
|
process.exit(1);
|
|
7192
7204
|
}
|
|
7193
7205
|
export function usageText() {
|
|
@@ -7261,17 +7273,21 @@ Commands:
|
|
|
7261
7273
|
where cost_factor = min(0.3, tokens / 10000). Re-sorts
|
|
7262
7274
|
results by utility. Default off. RESEARCH.md §PFC.OFC.
|
|
7263
7275
|
--reranker <name> Apply a reranker pass after retrieval
|
|
7264
|
-
(cross-encoder|jev|llm). Looks up
|
|
7265
|
-
reranker from src/rerankers/index.ts and
|
|
7266
|
-
the top-K candidates. Default unset (no reranker).
|
|
7276
|
+
(cross-encoder|jev|clef-flash|clef|llm). Looks up
|
|
7277
|
+
the named reranker from src/rerankers/index.ts and
|
|
7278
|
+
re-orders the top-K candidates. Default unset (no reranker).
|
|
7267
7279
|
jev calls the hosted TypeSafe Jev API: it needs
|
|
7268
7280
|
TYPESAFE_API_KEY, sends the query and candidate
|
|
7269
7281
|
text to that API, costs about 0.0004 USD a recall,
|
|
7270
7282
|
and falls back to cross-encoder on any failure.
|
|
7283
|
+
clef-flash and clef send the same request to
|
|
7284
|
+
Cloudflare Workers AI (CLOUDFLARE_ACCOUNT_ID and
|
|
7285
|
+
CLOUDFLARE_API_TOKEN) or to HIPPO_CLEF_ENDPOINT, and
|
|
7286
|
+
keep the native order on any failure.
|
|
7271
7287
|
See docs/evals/2026-09-19-jev-reranker.md and
|
|
7272
7288
|
docs/plans/2026-05-10-f6-reranker-hardening.md.
|
|
7273
7289
|
--reranker-top-k <n> Cap candidates passed to the reranker (default 50;
|
|
7274
|
-
40 for jev).
|
|
7290
|
+
40 for jev, clef-flash and clef).
|
|
7275
7291
|
--goal <tag> dlPFC goal-conditioned recall: memories tagged with
|
|
7276
7292
|
the goal tag get a 1.5x score boost and results are
|
|
7277
7293
|
re-sorted. Default off. RESEARCH.md §PFC.dlPFC.
|
|
@@ -7410,7 +7426,16 @@ Commands:
|
|
|
7410
7426
|
--global Operate on the global store
|
|
7411
7427
|
dormant restore <id> Bring a dormant memory back to active memory
|
|
7412
7428
|
dormant forget <id> Delete a dormant memory permanently
|
|
7413
|
-
|
|
7429
|
+
projects [list] List the project names in a store, with a hint for old worktree names
|
|
7430
|
+
--json Output as JSON
|
|
7431
|
+
--global Operate on the global store
|
|
7432
|
+
projects merge <from> <into> [--apply]
|
|
7433
|
+
Fold one project name into another (dry run unless --apply;
|
|
7434
|
+
writes a backup and one audit event first)
|
|
7435
|
+
projects repair [--apply]
|
|
7436
|
+
Re-tag merged rows older versions of sleep saved as user-global,
|
|
7437
|
+
by their parents' project (dry run unless --apply)
|
|
7438
|
+
quarantine [list] List memories a connector flagged as an instruction attempt, pending review
|
|
7414
7439
|
--all Include approved and rejected rows too (default: pending only)
|
|
7415
7440
|
--json Output as JSON
|
|
7416
7441
|
--global Operate on the global store
|
|
@@ -7882,7 +7907,7 @@ async function main(command, args, flags, hippoRoot) {
|
|
|
7882
7907
|
* --hops value-less guard, so every current and future command - including the
|
|
7883
7908
|
* thin-client dispatch relays - sees --scope only as a non-empty string. */
|
|
7884
7909
|
if ('scope' in flags && (typeof flags['scope'] !== 'string' || !flags['scope'].trim())) {
|
|
7885
|
-
|
|
7910
|
+
printError('--scope requires a non-empty value (e.g. --scope slack:private:C1).');
|
|
7886
7911
|
process.exit(1);
|
|
7887
7912
|
}
|
|
7888
7913
|
// parseArgs stores a value-less flag as boolean true, and NaN then survives every
|
|
@@ -7896,7 +7921,7 @@ async function main(command, args, flags, hippoRoot) {
|
|
|
7896
7921
|
if (raw === undefined)
|
|
7897
7922
|
continue;
|
|
7898
7923
|
if (typeof raw !== 'string' || !raw.trim() || !Number.isFinite(Number(raw))) {
|
|
7899
|
-
|
|
7924
|
+
printError(`--${key} requires a numeric value.`);
|
|
7900
7925
|
process.exit(1);
|
|
7901
7926
|
}
|
|
7902
7927
|
}
|
|
@@ -7904,7 +7929,7 @@ async function main(command, args, flags, hippoRoot) {
|
|
|
7904
7929
|
// so no single coercion of an inline value would be correct for every one of them.
|
|
7905
7930
|
for (const key of BOOLEAN_FLAGS) {
|
|
7906
7931
|
if (Object.hasOwn(flags, key) && typeof flags[key] !== 'boolean') {
|
|
7907
|
-
|
|
7932
|
+
printError(`--${key} takes no value`);
|
|
7908
7933
|
process.exit(1);
|
|
7909
7934
|
}
|
|
7910
7935
|
}
|
|
@@ -7913,14 +7938,14 @@ async function main(command, args, flags, hippoRoot) {
|
|
|
7913
7938
|
if (unknownFlags.length > 0) {
|
|
7914
7939
|
const names = unknownFlags.map((key) => `--${key}`).join(', ');
|
|
7915
7940
|
if (DESTRUCTIVE_COMMANDS.has(command)) {
|
|
7916
|
-
|
|
7941
|
+
printError(`Unknown flag ${names} for hippo ${command}. Nothing was changed.`);
|
|
7917
7942
|
process.exit(2);
|
|
7918
7943
|
}
|
|
7919
|
-
|
|
7944
|
+
printError(`hippo: ignoring unknown flag ${names}. A later release will reject it.`);
|
|
7920
7945
|
}
|
|
7921
7946
|
const refusal = Object.hasOwn(flags, 'dry-run') ? dryRunRefusal(command, args, flags) : null;
|
|
7922
7947
|
if (refusal) {
|
|
7923
|
-
|
|
7948
|
+
printError(refusal);
|
|
7924
7949
|
process.exit(2);
|
|
7925
7950
|
}
|
|
7926
7951
|
switch (command) {
|
|
@@ -7936,7 +7961,7 @@ async function main(command, args, flags, hippoRoot) {
|
|
|
7936
7961
|
text = args.join(' ').trim();
|
|
7937
7962
|
}
|
|
7938
7963
|
if (!text || text.length < 3) {
|
|
7939
|
-
|
|
7964
|
+
printError('Memory content too short (minimum 3 characters).');
|
|
7940
7965
|
process.exit(1);
|
|
7941
7966
|
}
|
|
7942
7967
|
// Thin-client routing. When a server is up, simple `remember` calls go
|
|
@@ -7960,11 +7985,11 @@ async function main(command, args, flags, hippoRoot) {
|
|
|
7960
7985
|
const thinOwnerRaw = typeof flags['owner'] === 'string' ? flags['owner'] : undefined;
|
|
7961
7986
|
const thinOwnerCheck = validateOwner(thinOwnerRaw, { strict: isStrictOwnerEnv() });
|
|
7962
7987
|
if (!thinOwnerCheck.ok) {
|
|
7963
|
-
|
|
7988
|
+
printError(thinOwnerCheck.message);
|
|
7964
7989
|
process.exit(1);
|
|
7965
7990
|
}
|
|
7966
7991
|
if (thinOwnerCheck.message)
|
|
7967
|
-
|
|
7992
|
+
printError(thinOwnerCheck.message);
|
|
7968
7993
|
const remembered = await runViaServerIfAvailable(hippoRoot, async (info, apiKey) => {
|
|
7969
7994
|
const result = await client.remember(info.url, apiKey, {
|
|
7970
7995
|
content: text,
|
|
@@ -7977,7 +8002,7 @@ async function main(command, args, flags, hippoRoot) {
|
|
|
7977
8002
|
console.log(`Remembered [${result.id}] (via ${info.url})`);
|
|
7978
8003
|
console.log(` Kind: ${result.kind} | Tenant: ${result.tenantId}`);
|
|
7979
8004
|
for (const w of result.warnings ?? [])
|
|
7980
|
-
|
|
8005
|
+
printError(`Warning: ${w}`);
|
|
7981
8006
|
});
|
|
7982
8007
|
if (remembered)
|
|
7983
8008
|
break;
|
|
@@ -7989,7 +8014,7 @@ async function main(command, args, flags, hippoRoot) {
|
|
|
7989
8014
|
case 'recall': {
|
|
7990
8015
|
const query = args.join(' ').trim();
|
|
7991
8016
|
if (!query) {
|
|
7992
|
-
|
|
8017
|
+
printError('Please provide a search query.');
|
|
7993
8018
|
process.exit(1);
|
|
7994
8019
|
}
|
|
7995
8020
|
await cmdRecall(hookStoreRoot(hippoRoot), query, flags);
|
|
@@ -7998,7 +8023,7 @@ async function main(command, args, flags, hippoRoot) {
|
|
|
7998
8023
|
case 'drill': {
|
|
7999
8024
|
const summaryId = args[0];
|
|
8000
8025
|
if (!summaryId) {
|
|
8001
|
-
|
|
8026
|
+
printError('Usage: hippo drill <summary-id> [--limit N] [--budget N]');
|
|
8002
8027
|
process.exit(1);
|
|
8003
8028
|
}
|
|
8004
8029
|
cmdDrillDown(hippoRoot, summaryId, flags);
|
|
@@ -8007,7 +8032,7 @@ async function main(command, args, flags, hippoRoot) {
|
|
|
8007
8032
|
case 'assemble': {
|
|
8008
8033
|
const sessionId = typeof flags['session'] === 'string' ? flags['session'] : args[0];
|
|
8009
8034
|
if (!sessionId) {
|
|
8010
|
-
|
|
8035
|
+
printError('Usage: hippo assemble --session <id> [--budget N] [--fresh-tail N] [--no-summarize-older] [--json]');
|
|
8011
8036
|
process.exit(1);
|
|
8012
8037
|
}
|
|
8013
8038
|
cmdAssemble(hippoRoot, sessionId, flags);
|
|
@@ -8017,7 +8042,7 @@ async function main(command, args, flags, hippoRoot) {
|
|
|
8017
8042
|
const oldId = args[0];
|
|
8018
8043
|
const newContent = args.slice(1).join(' ').trim();
|
|
8019
8044
|
if (!oldId || !newContent) {
|
|
8020
|
-
|
|
8045
|
+
printError('Usage: hippo supersede <old-id> "<new content>" [--layer L] [--tag T] [--pin]');
|
|
8021
8046
|
process.exit(1);
|
|
8022
8047
|
}
|
|
8023
8048
|
cmdSupersede(hippoRoot, oldId, newContent, flags);
|
|
@@ -8026,7 +8051,7 @@ async function main(command, args, flags, hippoRoot) {
|
|
|
8026
8051
|
case 'explain': {
|
|
8027
8052
|
const query = args.join(' ').trim();
|
|
8028
8053
|
if (!query) {
|
|
8029
|
-
|
|
8054
|
+
printError('Please provide a search query.');
|
|
8030
8055
|
process.exit(1);
|
|
8031
8056
|
}
|
|
8032
8057
|
await cmdExplain(hippoRoot, query, flags);
|
|
@@ -8044,7 +8069,7 @@ async function main(command, args, flags, hippoRoot) {
|
|
|
8044
8069
|
break;
|
|
8045
8070
|
}
|
|
8046
8071
|
if (!sub) {
|
|
8047
|
-
|
|
8072
|
+
printError('Usage: hippo trace <memory-id> | hippo trace record --task <t> --steps <json> --outcome <o>');
|
|
8048
8073
|
process.exit(1);
|
|
8049
8074
|
}
|
|
8050
8075
|
cmdTrace(hippoRoot, sub, flags);
|
|
@@ -8273,6 +8298,9 @@ async function main(command, args, flags, hippoRoot) {
|
|
|
8273
8298
|
case 'dormant':
|
|
8274
8299
|
cmdDormant(hippoRoot, args, flags);
|
|
8275
8300
|
break;
|
|
8301
|
+
case 'projects':
|
|
8302
|
+
(await import('./cli/projects.js')).cmdProjects(hippoRoot, args, flags);
|
|
8303
|
+
break;
|
|
8276
8304
|
case 'quarantine':
|
|
8277
8305
|
cmdQuarantine(hippoRoot, args, flags);
|
|
8278
8306
|
break;
|
|
@@ -8294,7 +8322,7 @@ async function main(command, args, flags, hippoRoot) {
|
|
|
8294
8322
|
case 'support-bundle': {
|
|
8295
8323
|
const outFlag = cardStringFlag(flags, 'out');
|
|
8296
8324
|
if (outFlag === '') {
|
|
8297
|
-
|
|
8325
|
+
printError('--out requires a file path.');
|
|
8298
8326
|
process.exit(1);
|
|
8299
8327
|
}
|
|
8300
8328
|
const includeLogs = flags['include-logs'] === true;
|
|
@@ -8309,10 +8337,10 @@ async function main(command, args, flags, hippoRoot) {
|
|
|
8309
8337
|
}
|
|
8310
8338
|
catch (err) {
|
|
8311
8339
|
if (err instanceof Error && 'code' in err && err.code === 'EEXIST') {
|
|
8312
|
-
|
|
8340
|
+
printError(`${file} already exists; pass --out to choose another file. Nothing was written.`);
|
|
8313
8341
|
}
|
|
8314
8342
|
else {
|
|
8315
|
-
|
|
8343
|
+
printError(err instanceof Error ? err.message : String(err));
|
|
8316
8344
|
}
|
|
8317
8345
|
process.exit(1);
|
|
8318
8346
|
}
|
|
@@ -8344,7 +8372,7 @@ async function main(command, args, flags, hippoRoot) {
|
|
|
8344
8372
|
case 'forget': {
|
|
8345
8373
|
const id = args[0];
|
|
8346
8374
|
if (!id) {
|
|
8347
|
-
|
|
8375
|
+
printError('Please provide a memory ID.');
|
|
8348
8376
|
process.exit(1);
|
|
8349
8377
|
}
|
|
8350
8378
|
// Archive has its own HTTP route (POST /v1/memories/:id/archive); route
|
|
@@ -8352,7 +8380,7 @@ async function main(command, args, flags, hippoRoot) {
|
|
|
8352
8380
|
const archive = flags['archive'] === true;
|
|
8353
8381
|
const reason = typeof flags['reason'] === 'string' ? flags['reason'] : null;
|
|
8354
8382
|
if (archive && !reason) {
|
|
8355
|
-
|
|
8383
|
+
printError(ARCHIVE_REASON_REQUIRED);
|
|
8356
8384
|
process.exit(1);
|
|
8357
8385
|
}
|
|
8358
8386
|
if (flags['dry-run'] === true) {
|
|
@@ -8376,7 +8404,7 @@ async function main(command, args, flags, hippoRoot) {
|
|
|
8376
8404
|
if (client.classifyTransportFailure(err) !== 'none')
|
|
8377
8405
|
throw err;
|
|
8378
8406
|
const msg = err instanceof Error ? err.message : String(err);
|
|
8379
|
-
|
|
8407
|
+
printError(archive ? `Could not archive ${id}: ${msg}` : msg);
|
|
8380
8408
|
process.exit(1);
|
|
8381
8409
|
}
|
|
8382
8410
|
});
|
|
@@ -8388,7 +8416,7 @@ async function main(command, args, flags, hippoRoot) {
|
|
|
8388
8416
|
case 'inspect': {
|
|
8389
8417
|
const id = args[0];
|
|
8390
8418
|
if (!id) {
|
|
8391
|
-
|
|
8419
|
+
printError('Please provide a memory ID.');
|
|
8392
8420
|
process.exit(1);
|
|
8393
8421
|
}
|
|
8394
8422
|
cmdInspect(hippoRoot, id);
|
|
@@ -8424,7 +8452,7 @@ async function main(command, args, flags, hippoRoot) {
|
|
|
8424
8452
|
case 'promote': {
|
|
8425
8453
|
const id = args[0];
|
|
8426
8454
|
if (!id) {
|
|
8427
|
-
|
|
8455
|
+
printError('Please provide a memory ID.');
|
|
8428
8456
|
process.exit(1);
|
|
8429
8457
|
}
|
|
8430
8458
|
const promoted = await runViaServerIfAvailable(hippoRoot, async (info, apiKey) => {
|
|
@@ -8433,7 +8461,7 @@ async function main(command, args, flags, hippoRoot) {
|
|
|
8433
8461
|
console.log(`Promoted ${id} to global store as ${result.globalId}`);
|
|
8434
8462
|
}
|
|
8435
8463
|
catch (err) {
|
|
8436
|
-
|
|
8464
|
+
printError(`Failed to promote: ${err.message}`);
|
|
8437
8465
|
process.exit(1);
|
|
8438
8466
|
}
|
|
8439
8467
|
});
|
|
@@ -8487,13 +8515,13 @@ async function main(command, args, flags, hippoRoot) {
|
|
|
8487
8515
|
console.log('Use --force to share anyway.');
|
|
8488
8516
|
}
|
|
8489
8517
|
else {
|
|
8490
|
-
|
|
8518
|
+
printError(`Memory not found: ${shareId}`);
|
|
8491
8519
|
process.exit(1);
|
|
8492
8520
|
}
|
|
8493
8521
|
}
|
|
8494
8522
|
}
|
|
8495
8523
|
else {
|
|
8496
|
-
|
|
8524
|
+
printError('Usage: hippo share <memory_id> [--force] or hippo share --auto [--dry-run]');
|
|
8497
8525
|
process.exit(1);
|
|
8498
8526
|
}
|
|
8499
8527
|
break;
|
|
@@ -8571,7 +8599,7 @@ async function main(command, args, flags, hippoRoot) {
|
|
|
8571
8599
|
captureSource = 'last-session';
|
|
8572
8600
|
}
|
|
8573
8601
|
if (!captureSource) {
|
|
8574
|
-
|
|
8602
|
+
printError('Usage: hippo capture --stdin|--file <path>|--last-session [--transcript <path>] [--log-file <path>] [--dry-run] [--global]');
|
|
8575
8603
|
process.exit(1);
|
|
8576
8604
|
}
|
|
8577
8605
|
// Bounded, and only when last-session has no explicit path: the
|
|
@@ -8619,7 +8647,7 @@ async function main(command, args, flags, hippoRoot) {
|
|
|
8619
8647
|
const portRaw = flags['port'] ?? process.env['HIPPO_PORT'] ?? '6789';
|
|
8620
8648
|
const port = Number(portRaw);
|
|
8621
8649
|
if (!Number.isFinite(port) || port < 0) {
|
|
8622
|
-
|
|
8650
|
+
printError(`Invalid --port: ${String(portRaw)}`);
|
|
8623
8651
|
process.exit(1);
|
|
8624
8652
|
}
|
|
8625
8653
|
const host = typeof flags['host'] === 'string' ? flags['host'] : '127.0.0.1';
|
|
@@ -8636,19 +8664,19 @@ async function main(command, args, flags, hippoRoot) {
|
|
|
8636
8664
|
requireInit(hippoRoot);
|
|
8637
8665
|
if (flags['churn'] === true) {
|
|
8638
8666
|
if (args[0] || flags['id'] !== undefined) {
|
|
8639
|
-
|
|
8640
|
-
|
|
8667
|
+
printError('Usage: hippo invalidate --churn [--dry-run]');
|
|
8668
|
+
printError('--churn takes no pattern or --id.');
|
|
8641
8669
|
process.exit(1);
|
|
8642
8670
|
}
|
|
8643
8671
|
if (!isGitRepo(process.cwd())) {
|
|
8644
|
-
|
|
8672
|
+
printError('hippo invalidate --churn must run inside a git repository.');
|
|
8645
8673
|
process.exit(1);
|
|
8646
8674
|
}
|
|
8647
8675
|
const churnDryRun = flags['dry-run'] === true;
|
|
8648
8676
|
let churnFailed = false;
|
|
8649
8677
|
for (const { root, result } of runChurnStaleForRepo(hippoRoot, churnDryRun)) {
|
|
8650
8678
|
if (result.error) {
|
|
8651
|
-
|
|
8679
|
+
printError(`Churn-staleness check failed for ${root}: ${result.error}`);
|
|
8652
8680
|
churnFailed = true;
|
|
8653
8681
|
continue;
|
|
8654
8682
|
}
|
|
@@ -8674,21 +8702,21 @@ async function main(command, args, flags, hippoRoot) {
|
|
|
8674
8702
|
if (flags['id'] === true) {
|
|
8675
8703
|
// Value-less --id must never silently fall through to pattern mode
|
|
8676
8704
|
// (pattern mode writes broadly; an ignored --id reverses user intent).
|
|
8677
|
-
|
|
8705
|
+
printError('--id requires a memory id');
|
|
8678
8706
|
process.exit(1);
|
|
8679
8707
|
}
|
|
8680
8708
|
const onlyId = typeof flags['id'] === 'string' ? flags['id'] : undefined;
|
|
8681
8709
|
if (typeof flags['dry-run'] === 'string') {
|
|
8682
8710
|
// Dead: the earlier global BOOLEAN_FLAGS guard now exits first on any --dry-run=<v>.
|
|
8683
8711
|
// Kept as defence in depth on a destructive command.
|
|
8684
|
-
|
|
8712
|
+
printError('--dry-run takes no value');
|
|
8685
8713
|
process.exit(1);
|
|
8686
8714
|
}
|
|
8687
8715
|
const dryRun = flags['dry-run'] === true;
|
|
8688
8716
|
if ((target && onlyId) || (!target && !onlyId)) {
|
|
8689
|
-
|
|
8690
|
-
|
|
8691
|
-
|
|
8717
|
+
printError('Usage: hippo invalidate "<old pattern>" [--dry-run] [--reason "<why>"]');
|
|
8718
|
+
printError(' hippo invalidate --id <memory-id> [--dry-run] [--reason "<why>"]');
|
|
8719
|
+
printError('Pass a pattern OR --id, not both. Tag matching is EXACT: the full pattern must equal a tag.');
|
|
8692
8720
|
process.exit(1);
|
|
8693
8721
|
}
|
|
8694
8722
|
const reason = flags['reason'] || null;
|
|
@@ -8747,7 +8775,7 @@ async function main(command, args, flags, hippoRoot) {
|
|
|
8747
8775
|
cmdGraph(hippoRoot, args, flags);
|
|
8748
8776
|
break;
|
|
8749
8777
|
default:
|
|
8750
|
-
|
|
8778
|
+
printError(`Unknown command: ${command}`);
|
|
8751
8779
|
printUsage();
|
|
8752
8780
|
process.exit(1);
|
|
8753
8781
|
}
|
|
@@ -8758,7 +8786,7 @@ export async function runCli(argv = process.argv) {
|
|
|
8758
8786
|
await main(command, args, flags, getHippoRoot(process.cwd()));
|
|
8759
8787
|
}
|
|
8760
8788
|
catch (err) {
|
|
8761
|
-
|
|
8789
|
+
printError('Error:', err instanceof Error ? err.message : err);
|
|
8762
8790
|
process.exit(1);
|
|
8763
8791
|
}
|
|
8764
8792
|
}
|