hippo-memory 1.39.0 → 1.41.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 +12 -0
- package/dist/card.d.ts +47 -0
- package/dist/card.d.ts.map +1 -0
- package/dist/card.js +19 -0
- package/dist/card.js.map +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +383 -3
- package/dist/cli.js.map +1 -1
- package/dist/connectors/slack/backfill.js +1 -1
- package/dist/db.d.ts.map +1 -1
- package/dist/db.js +123 -1
- package/dist/db.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/src/card.js +19 -0
- package/dist/src/card.js.map +1 -0
- package/dist/src/cli.js +383 -3
- package/dist/src/cli.js.map +1 -1
- package/dist/src/connectors/slack/backfill.js +1 -1
- package/dist/src/db.js +123 -1
- package/dist/src/db.js.map +1 -1
- package/dist/src/index.js +3 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/store.js +521 -1
- package/dist/src/store.js.map +1 -1
- package/dist/src/version.js +2 -15
- package/dist/src/version.js.map +1 -1
- package/dist/store.d.ts +50 -0
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +521 -1
- package/dist/store.js.map +1 -1
- package/dist/version.d.ts +2 -15
- package/dist/version.d.ts.map +1 -1
- package/dist/version.js +2 -15
- package/dist/version.js.map +1 -1
- package/dist-ui/index.html +12 -12
- package/extensions/openclaw-plugin/openclaw.plugin.json +1 -1
- package/extensions/openclaw-plugin/package.json +1 -1
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/dist/benchmarks/e1.3/scenarios.json +0 -2587
package/README.md
CHANGED
|
@@ -619,6 +619,16 @@ hippo watch "npm run build"
|
|
|
619
619
|
| `hippo session latest` | Show latest task snapshot + events |
|
|
620
620
|
| `hippo session resume` | Re-inject latest handoff as context |
|
|
621
621
|
| `hippo current show` | Compact current state (task + session events) |
|
|
622
|
+
| `hippo card create --title "..."` | Create a work-queue card (`--repo`, `--contract`, `--budget`, repeatable `--depends-on <id>`) |
|
|
623
|
+
| `hippo card show <id>` | Show a card, its deps, runs, comments and latest handoff |
|
|
624
|
+
| `hippo card list [--status <status>]` | List cards, newest-updated first |
|
|
625
|
+
| `hippo card claim <id> --runtime <name>` | Claim a ready or blocked card; prints its run id and the time its 4-hour lease expires |
|
|
626
|
+
| `hippo card heartbeat <id> --run <n>` | Extend a claimed card's lease |
|
|
627
|
+
| `hippo card block <id> --reason "<why>" [--run <n>]` | Block a running card; the reason is recorded as a comment |
|
|
628
|
+
| `hippo card review <id> [--run <n>]` | Move a running card to review |
|
|
629
|
+
| `hippo card complete <id> --outcome <success\|failure\|partial> [--run <n>]` | Complete a card in review: `success` marks it done and promotes children whose parents are all done; `failure` or `partial` shelves it |
|
|
630
|
+
| `hippo card reclaim` | Sweep every card whose lease has passed and return it to ready |
|
|
631
|
+
| `hippo card comment <id> --body "..."` | Add a comment to a card |
|
|
622
632
|
| `hippo wm push --scope <s> --content "..."` | Push to working memory |
|
|
623
633
|
| `hippo wm read --scope <s>` | Read working memory entries |
|
|
624
634
|
| `hippo wm clear --scope <s>` | Clear working memory |
|
|
@@ -627,6 +637,8 @@ hippo watch "npm run build"
|
|
|
627
637
|
| `hippo dashboard --port <n>` | Use custom port |
|
|
628
638
|
| `hippo mcp` | Start MCP server (stdio transport) |
|
|
629
639
|
|
|
640
|
+
On `heartbeat`, `block`, `review` and `complete`, a given `--run` is checked against the card's live run and the command is refused, unchanged, if the two do not match.
|
|
641
|
+
|
|
630
642
|
---
|
|
631
643
|
|
|
632
644
|
## Framework Integrations
|
package/dist/card.d.ts
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/** A work-queue card's lifecycle state; CARD_TRANSITIONS lists the legal moves between them. */
|
|
2
|
+
export type CardStatus = 'backlog' | 'ready' | 'running' | 'blocked' | 'review' | 'done' | 'shelved';
|
|
3
|
+
/** Narrows a raw string, such as a CLI flag value, to a CardStatus. */
|
|
4
|
+
export declare function isCardStatus(v: string): v is CardStatus;
|
|
5
|
+
/** A card row; leaseUntil and heartbeatAt are null unless the card is running, and a card claimed under 1.40.0 runs with both null until a heartbeat sets them. */
|
|
6
|
+
export interface Card {
|
|
7
|
+
id: string;
|
|
8
|
+
title: string;
|
|
9
|
+
status: CardStatus;
|
|
10
|
+
assigneeRuntime: string | null;
|
|
11
|
+
repo: string | null;
|
|
12
|
+
contract: string | null;
|
|
13
|
+
budget: number | null;
|
|
14
|
+
leaseUntil: string | null;
|
|
15
|
+
heartbeatAt: string | null;
|
|
16
|
+
createdAt: string;
|
|
17
|
+
updatedAt: string;
|
|
18
|
+
tenantId: string;
|
|
19
|
+
scope: string | null;
|
|
20
|
+
}
|
|
21
|
+
/** One claim-to-close attempt at a card by a runtime. */
|
|
22
|
+
export interface CardRun {
|
|
23
|
+
id: number;
|
|
24
|
+
card: string;
|
|
25
|
+
runtime: string;
|
|
26
|
+
sessionId: string | null;
|
|
27
|
+
started: string;
|
|
28
|
+
ended: string | null;
|
|
29
|
+
outcome: string | null;
|
|
30
|
+
}
|
|
31
|
+
/** A comment left on a card. */
|
|
32
|
+
export interface CardComment {
|
|
33
|
+
id: number;
|
|
34
|
+
cardId: string;
|
|
35
|
+
author: string;
|
|
36
|
+
body: string;
|
|
37
|
+
createdAt: string;
|
|
38
|
+
}
|
|
39
|
+
/** The legal next-statuses for each CardStatus. */
|
|
40
|
+
export type CardTransitions = {
|
|
41
|
+
readonly [S in CardStatus]: readonly CardStatus[];
|
|
42
|
+
};
|
|
43
|
+
/** transitionCard checks every status write against this map. */
|
|
44
|
+
export declare const CARD_TRANSITIONS: CardTransitions;
|
|
45
|
+
/** How long a claim or a heartbeat keeps a running card's lease before reclaimExpiredCards may return it to ready. */
|
|
46
|
+
export declare const CARD_LEASE_MS: number;
|
|
47
|
+
//# sourceMappingURL=card.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"card.d.ts","sourceRoot":"","sources":["../src/card.ts"],"names":[],"mappings":"AACA,gGAAgG;AAChG,MAAM,MAAM,UAAU,GAAG,SAAS,GAAG,OAAO,GAAG,SAAS,GAAG,SAAS,GAAG,QAAQ,GAAG,MAAM,GAAG,SAAS,CAAC;AAIrG,uEAAuE;AACvE,wBAAgB,YAAY,CAAC,CAAC,EAAE,MAAM,GAAG,CAAC,IAAI,UAAU,CAEvD;AAED,mKAAmK;AACnK,MAAM,WAAW,IAAI;IACnB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,UAAU,CAAC;IACnB,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB;AAED,yDAAyD;AACzD,MAAM,WAAW,OAAO;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CACxB;AAED,gCAAgC;AAChC,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,mDAAmD;AACnD,MAAM,MAAM,eAAe,GAAG;IAAE,QAAQ,EAAE,CAAC,IAAI,UAAU,GAAG,SAAS,UAAU,EAAE;CAAE,CAAC;AAEpF,iEAAiE;AACjE,eAAO,MAAM,gBAAgB,EAAE,eAQ9B,CAAC;AAGF,sHAAsH;AACtH,eAAO,MAAM,aAAa,QAAqB,CAAC"}
|
package/dist/card.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
const CARD_STATUSES = ['backlog', 'ready', 'running', 'blocked', 'review', 'done', 'shelved'];
|
|
2
|
+
/** Narrows a raw string, such as a CLI flag value, to a CardStatus. */
|
|
3
|
+
export function isCardStatus(v) {
|
|
4
|
+
return CARD_STATUSES.some((s) => s === v);
|
|
5
|
+
}
|
|
6
|
+
/** transitionCard checks every status write against this map. */
|
|
7
|
+
export const CARD_TRANSITIONS = {
|
|
8
|
+
backlog: ['ready'],
|
|
9
|
+
ready: ['running'],
|
|
10
|
+
running: ['blocked', 'review', 'ready'],
|
|
11
|
+
blocked: ['running'],
|
|
12
|
+
review: ['done', 'shelved'],
|
|
13
|
+
done: [],
|
|
14
|
+
shelved: [],
|
|
15
|
+
};
|
|
16
|
+
// SHORTCUT: one fixed lease for every claim; add a per-claim lease when the W3 dispatcher heartbeats on a timer.
|
|
17
|
+
/** How long a claim or a heartbeat keeps a running card's lease before reclaimExpiredCards may return it to ready. */
|
|
18
|
+
export const CARD_LEASE_MS = 4 * 60 * 60 * 1000;
|
|
19
|
+
//# sourceMappingURL=card.js.map
|
package/dist/card.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"card.js","sourceRoot":"","sources":["../src/card.ts"],"names":[],"mappings":"AAIA,MAAM,aAAa,GAA0B,CAAC,SAAS,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,CAAC,CAAC;AAErH,uEAAuE;AACvE,MAAM,UAAU,YAAY,CAAC,CAAS;IACpC,OAAO,aAAa,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC;AAC5C,CAAC;AA0CD,iEAAiE;AACjE,MAAM,CAAC,MAAM,gBAAgB,GAAoB;IAC/C,OAAO,EAAE,CAAC,OAAO,CAAC;IAClB,KAAK,EAAE,CAAC,SAAS,CAAC;IAClB,OAAO,EAAE,CAAC,SAAS,EAAE,QAAQ,EAAE,OAAO,CAAC;IACvC,OAAO,EAAE,CAAC,SAAS,CAAC;IACpB,MAAM,EAAE,CAAC,MAAM,EAAE,SAAS,CAAC;IAC3B,IAAI,EAAE,EAAE;IACR,OAAO,EAAE,EAAE;CACZ,CAAC;AAEF,iHAAiH;AACjH,sHAAsH;AACtH,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC"}
|
package/dist/cli.d.ts
CHANGED
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
* hippo snapshot <save|show|clear>
|
|
14
14
|
* hippo session <log|show|latest|resume|complete>
|
|
15
15
|
* hippo handoff <create|latest|show>
|
|
16
|
+
* hippo card <create|show|list|claim|heartbeat|block|review|complete|reclaim|comment>
|
|
16
17
|
* hippo current <show>
|
|
17
18
|
* hippo forget <id> [--archive --reason "<why>"]
|
|
18
19
|
* hippo reject <id>|--value "<text>" --reason "<why>"
|
package/dist/cli.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AA2BH,OAAO,EAWL,WAAW,EAEZ,MAAM,aAAa,CAAC;AA6HrB,OAAO,KAAK,GAAG,MAAM,UAAU,CAAC;AA4ChC,kFAAkF;AAClF,wBAAgB,8BAA8B,IAAI,IAAI,CAErD;AAsJD,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,EAAE,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,GAAG,MAAM,EAAE,CAAC,CAAA;CAAE,CAwCjI;AAqxED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,GAAE,MAAqB,GAAG,MAAM,CAyF3F;AAyGD;;;GAGG;AACH,oHAAoH;AACpH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,GAAG,CAAC,WAAW,GAAG,IAAI,CAuD/D;AAigHD,oHAAoH;AACpH,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,KAAK,CAAC;IAAE,KAAK,EAAE,WAAW,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,OAAO,CAAA;CAAE,CAAC,EACtF,WAAW,EAAE,MAAM,EACnB,OAAO,GAAE,MAAkB,GAC1B,IAAI,CA8BN"}
|
package/dist/cli.js
CHANGED
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
* hippo snapshot <save|show|clear>
|
|
14
14
|
* hippo session <log|show|latest|resume|complete>
|
|
15
15
|
* hippo handoff <create|latest|show>
|
|
16
|
+
* hippo card <create|show|list|claim|heartbeat|block|review|complete|reclaim|comment>
|
|
16
17
|
* hippo current <show>
|
|
17
18
|
* hippo forget <id> [--archive --reason "<why>"]
|
|
18
19
|
* hippo reject <id>|--value "<text>" --reason "<why>"
|
|
@@ -37,10 +38,11 @@ import { execFileSync, execSync, spawn } from 'child_process';
|
|
|
37
38
|
import { installJsonHooks, uninstallJsonHooks, resolveJsonHookPaths, detectInstalledTools, defaultSleepLogPath, ensureCodexWrapperInstalled, installCodexWrapper, isCodexWrapperInstalled, repairCodexWrapperIfInstalled, uninstallCodexWrapper, resolveCodexSessionTranscript, resolveCodexWrapperPaths, installOpencodePlugin, uninstallOpencodePlugin, resolveOpencodePluginPath, } from './hooks.js';
|
|
38
39
|
import { createMemory, calculateStrength, calculateRewardFactor, deriveHalfLife, resolveConfidence, confidenceFacets, confidenceLabel, computeSchemaFit, Layer, } from './memory.js';
|
|
39
40
|
import { detectSecret } from './secret-detect.js';
|
|
40
|
-
import { getHippoRoot, isInitialized, initStore, writeEntry, readEntry, deleteEntry, loadAllEntries, loadSearchEntries, loadRecallSearchEntries, loadIndex, saveIndex, loadStats, updateStats, saveActiveTaskSnapshot, loadActiveTaskSnapshot, closeTaskSnapshotsForSession, clearActiveTaskSnapshot, appendSessionEvent, listSessionEvents, listMemoryConflicts, resolveConflict, saveSessionHandoff, loadLatestHandoff, loadHandoffById, stampHandoffOutcome, writeSessionEndHandoff, } from './store.js';
|
|
41
|
+
import { getHippoRoot, isInitialized, initStore, writeEntry, readEntry, deleteEntry, loadAllEntries, loadSearchEntries, loadRecallSearchEntries, loadIndex, saveIndex, loadStats, updateStats, saveActiveTaskSnapshot, loadActiveTaskSnapshot, closeTaskSnapshotsForSession, clearActiveTaskSnapshot, appendSessionEvent, listSessionEvents, listMemoryConflicts, resolveConflict, saveSessionHandoff, loadLatestHandoff, loadHandoffById, stampHandoffOutcome, writeSessionEndHandoff, createCard, loadCard, listCards, loadCardDeps, loadCardRuns, loadCardComments, claimCard, heartbeatCard, blockCard, reviewCard, completeCard, reclaimExpiredCards, addCardComment, loadLatestHandoffForCard, } from './store.js';
|
|
41
42
|
import { rejectValue, unrejectValue, listRejectionsForTenant } from './reject-flow.js';
|
|
42
43
|
import { RejectedValueError } from './rejection.js';
|
|
43
44
|
import { isHandoffOutcome, formatHandoffEvidenceLine } from './handoff.js';
|
|
45
|
+
import { isCardStatus } from './card.js';
|
|
44
46
|
import { passesScopeFilterForRecall } from './recall-scope.js';
|
|
45
47
|
import { markRetrieved, hybridSearch, physicsSearch, explainMatch, textOverlap, tokenize as tokenizeQuery } from './search.js';
|
|
46
48
|
import { compareEntryIdentity } from './compare.js';
|
|
@@ -253,8 +255,8 @@ export function parseArgs(argv) {
|
|
|
253
255
|
i++;
|
|
254
256
|
}
|
|
255
257
|
else {
|
|
256
|
-
// Check if it's a repeatable flag (tag, artifact, link, step, constraint)
|
|
257
|
-
if (key === 'tag' || key === 'artifact' || key === 'link' || key === 'step' || key === 'constraint') {
|
|
258
|
+
// Check if it's a repeatable flag (tag, artifact, link, step, constraint, depends-on)
|
|
259
|
+
if (key === 'tag' || key === 'artifact' || key === 'link' || key === 'step' || key === 'constraint' || key === 'depends-on') {
|
|
258
260
|
if (Array.isArray(flags[key])) {
|
|
259
261
|
flags[key].push(next);
|
|
260
262
|
}
|
|
@@ -4024,6 +4026,348 @@ function cmdHandoff(hippoRoot, args, flags) {
|
|
|
4024
4026
|
console.error('Usage: hippo handoff <create|latest|show>');
|
|
4025
4027
|
process.exit(1);
|
|
4026
4028
|
}
|
|
4029
|
+
// Mirrors ARCHIVE_REASON_REQUIRED so the block message can't drift from its usage line.
|
|
4030
|
+
const CARD_BLOCK_REASON_REQUIRED = 'hippo card block <id> requires --reason "<why>" (recorded as a comment).';
|
|
4031
|
+
function printCard(hippoRoot, tenantId, card) {
|
|
4032
|
+
console.log(`## Card ${card.id}\n`);
|
|
4033
|
+
console.log(`- Title: ${card.title}`);
|
|
4034
|
+
console.log(`- Status: ${card.status}`);
|
|
4035
|
+
if (card.assigneeRuntime)
|
|
4036
|
+
console.log(`- Assignee: ${card.assigneeRuntime}`);
|
|
4037
|
+
if (card.leaseUntil)
|
|
4038
|
+
console.log(`- Lease until: ${card.leaseUntil}`);
|
|
4039
|
+
if (card.heartbeatAt)
|
|
4040
|
+
console.log(`- Heartbeat: ${card.heartbeatAt}`);
|
|
4041
|
+
if (card.repo)
|
|
4042
|
+
console.log(`- Repo: ${card.repo}`);
|
|
4043
|
+
if (card.contract)
|
|
4044
|
+
console.log(`- Contract: ${card.contract}`);
|
|
4045
|
+
if (card.budget !== null)
|
|
4046
|
+
console.log(`- Budget: ${card.budget}`);
|
|
4047
|
+
console.log(`- Updated: ${card.updatedAt}`);
|
|
4048
|
+
const deps = loadCardDeps(hippoRoot, tenantId, card.id);
|
|
4049
|
+
if (deps.parents.length > 0)
|
|
4050
|
+
console.log(`- Parents: ${deps.parents.join(', ')}`);
|
|
4051
|
+
if (deps.children.length > 0)
|
|
4052
|
+
console.log(`- Children: ${deps.children.join(', ')}`);
|
|
4053
|
+
const runs = loadCardRuns(hippoRoot, tenantId, card.id);
|
|
4054
|
+
if (runs.length > 0) {
|
|
4055
|
+
console.log('\n### Runs');
|
|
4056
|
+
for (const run of runs) {
|
|
4057
|
+
console.log(`- run ${run.id}: ${run.runtime} started ${run.started}${run.ended ? ` ended ${run.ended} (${run.outcome})` : ' (open)'}`);
|
|
4058
|
+
}
|
|
4059
|
+
}
|
|
4060
|
+
const comments = loadCardComments(hippoRoot, tenantId, card.id);
|
|
4061
|
+
if (comments.length > 0) {
|
|
4062
|
+
console.log('\n### Comments');
|
|
4063
|
+
for (const comment of comments) {
|
|
4064
|
+
console.log(`- [${comment.createdAt}] ${comment.author}: ${comment.body}`);
|
|
4065
|
+
}
|
|
4066
|
+
}
|
|
4067
|
+
const handoff = loadLatestHandoffForCard(hippoRoot, tenantId, card.id);
|
|
4068
|
+
if (handoff) {
|
|
4069
|
+
console.log('\n### Latest handoff');
|
|
4070
|
+
console.log(`- Session: ${handoff.sessionId}, updated ${handoff.updatedAt}`);
|
|
4071
|
+
console.log(handoff.summary);
|
|
4072
|
+
}
|
|
4073
|
+
console.log('');
|
|
4074
|
+
}
|
|
4075
|
+
// parseArgs turns a value-less flag into `true`; refuse rather than silently
|
|
4076
|
+
// stringifying it (String(true) === 'true'), mirroring cmdHandoff's guard.
|
|
4077
|
+
function cardStringFlag(flags, key) {
|
|
4078
|
+
const v = flags[key];
|
|
4079
|
+
if (v === undefined)
|
|
4080
|
+
return undefined;
|
|
4081
|
+
if (v === true || v === false || Array.isArray(v)) {
|
|
4082
|
+
console.error(`--${key} requires a value`);
|
|
4083
|
+
process.exit(1);
|
|
4084
|
+
}
|
|
4085
|
+
return v.trim();
|
|
4086
|
+
}
|
|
4087
|
+
// A too-large --run would silently round to a different id (mirrors parsePositiveIncidentId).
|
|
4088
|
+
function cardRunFlag(flags) {
|
|
4089
|
+
const raw = cardStringFlag(flags, 'run');
|
|
4090
|
+
if (raw === undefined)
|
|
4091
|
+
return undefined;
|
|
4092
|
+
const n = Number(raw);
|
|
4093
|
+
if (!/^\d+$/.test(raw) || !Number.isSafeInteger(n) || n <= 0) {
|
|
4094
|
+
console.error(`Invalid --run: "${raw}" (expected a positive integer).`);
|
|
4095
|
+
process.exit(1);
|
|
4096
|
+
}
|
|
4097
|
+
return n;
|
|
4098
|
+
}
|
|
4099
|
+
// Reads after the store already refused, so this only explains the refusal, never changes it.
|
|
4100
|
+
function cardRefusal(hippoRoot, tenantId, id) {
|
|
4101
|
+
const card = loadCard(hippoRoot, tenantId, id);
|
|
4102
|
+
const liveRun = loadCardRuns(hippoRoot, tenantId, id).find((r) => !r.ended);
|
|
4103
|
+
return `status ${card?.status ?? 'unknown'}, live run ${liveRun?.id ?? 'none'}`;
|
|
4104
|
+
}
|
|
4105
|
+
const CARD_SUBCOMMAND_FLAGS = {
|
|
4106
|
+
create: ['title', 'repo', 'contract', 'budget', 'depends-on'],
|
|
4107
|
+
show: ['json'],
|
|
4108
|
+
list: ['status', 'json'],
|
|
4109
|
+
claim: ['runtime', 'session'],
|
|
4110
|
+
heartbeat: ['run'],
|
|
4111
|
+
block: ['reason', 'run'],
|
|
4112
|
+
review: ['run'],
|
|
4113
|
+
complete: ['outcome', 'run'],
|
|
4114
|
+
reclaim: new Array(),
|
|
4115
|
+
comment: ['body', 'author'],
|
|
4116
|
+
};
|
|
4117
|
+
function cmdCard(hippoRoot, args, flags) {
|
|
4118
|
+
requireInit(hippoRoot);
|
|
4119
|
+
const tenantId = resolveTenantId({});
|
|
4120
|
+
const subcommand = args[0] ?? '';
|
|
4121
|
+
if (Object.hasOwn(CARD_SUBCOMMAND_FLAGS, subcommand)) {
|
|
4122
|
+
// SAFETY: hasOwn, unlike `in`, skips inherited keys such as constructor, so subcommand is a real key.
|
|
4123
|
+
const allowedFlags = CARD_SUBCOMMAND_FLAGS[subcommand];
|
|
4124
|
+
for (const key of Object.keys(flags)) {
|
|
4125
|
+
if (!allowedFlags.includes(key)) {
|
|
4126
|
+
// --flag=value never splits on '=' (see parseArgs), so it lands here as one long key.
|
|
4127
|
+
const hint = key.includes('=') ? ` Use --${key.slice(0, key.indexOf('='))} <value>, not --${key}.` : '';
|
|
4128
|
+
const valid = allowedFlags.length > 0 ? allowedFlags.map((f) => `--${f}`).join(', ') : '(none)';
|
|
4129
|
+
console.error(`Unknown flag --${key} for hippo card ${subcommand}.${hint} Valid flags: ${valid}`);
|
|
4130
|
+
process.exit(1);
|
|
4131
|
+
}
|
|
4132
|
+
}
|
|
4133
|
+
}
|
|
4134
|
+
if (subcommand === 'create') {
|
|
4135
|
+
const title = cardStringFlag(flags, 'title') ?? '';
|
|
4136
|
+
if (!title) {
|
|
4137
|
+
console.error('Usage: hippo card create --title "..." [--repo <name>] [--contract <text>] [--budget <n>] [--depends-on <id>...]');
|
|
4138
|
+
process.exit(1);
|
|
4139
|
+
}
|
|
4140
|
+
const repo = cardStringFlag(flags, 'repo') || undefined;
|
|
4141
|
+
const contract = cardStringFlag(flags, 'contract') || undefined;
|
|
4142
|
+
const budgetRaw = cardStringFlag(flags, 'budget');
|
|
4143
|
+
let budget;
|
|
4144
|
+
if (budgetRaw !== undefined) {
|
|
4145
|
+
if (!/^\d+$/.test(budgetRaw)) {
|
|
4146
|
+
console.error(`Invalid budget: "${budgetRaw}" (expected a positive integer)`);
|
|
4147
|
+
process.exit(1);
|
|
4148
|
+
}
|
|
4149
|
+
budget = Number(budgetRaw);
|
|
4150
|
+
}
|
|
4151
|
+
const dependsOnFlag = flags['depends-on'];
|
|
4152
|
+
if (dependsOnFlag === true) {
|
|
4153
|
+
console.error('--depends-on requires a value');
|
|
4154
|
+
process.exit(1);
|
|
4155
|
+
}
|
|
4156
|
+
const dependsOn = Array.isArray(dependsOnFlag) ? dependsOnFlag : [];
|
|
4157
|
+
let card;
|
|
4158
|
+
try {
|
|
4159
|
+
card = createCard(hippoRoot, tenantId, { title, repo, contract, budget, dependsOn });
|
|
4160
|
+
}
|
|
4161
|
+
catch (error) {
|
|
4162
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
4163
|
+
process.exit(1);
|
|
4164
|
+
}
|
|
4165
|
+
console.log(`Created card ${card.id} (status: ${card.status})`);
|
|
4166
|
+
return;
|
|
4167
|
+
}
|
|
4168
|
+
if (subcommand === 'show') {
|
|
4169
|
+
const id = args[1];
|
|
4170
|
+
if (!id) {
|
|
4171
|
+
console.error('Usage: hippo card show <id> [--json]');
|
|
4172
|
+
process.exit(1);
|
|
4173
|
+
}
|
|
4174
|
+
const card = loadCard(hippoRoot, tenantId, id);
|
|
4175
|
+
if (!card) {
|
|
4176
|
+
console.error(`No card found with id ${id}.`);
|
|
4177
|
+
process.exit(1);
|
|
4178
|
+
}
|
|
4179
|
+
if (flags['json']) {
|
|
4180
|
+
console.log(JSON.stringify({
|
|
4181
|
+
card,
|
|
4182
|
+
deps: loadCardDeps(hippoRoot, tenantId, id),
|
|
4183
|
+
runs: loadCardRuns(hippoRoot, tenantId, id),
|
|
4184
|
+
comments: loadCardComments(hippoRoot, tenantId, id),
|
|
4185
|
+
handoff: loadLatestHandoffForCard(hippoRoot, tenantId, id),
|
|
4186
|
+
}, null, 2));
|
|
4187
|
+
return;
|
|
4188
|
+
}
|
|
4189
|
+
printCard(hippoRoot, tenantId, card);
|
|
4190
|
+
return;
|
|
4191
|
+
}
|
|
4192
|
+
if (subcommand === 'list') {
|
|
4193
|
+
const status = cardStringFlag(flags, 'status');
|
|
4194
|
+
if (status !== undefined && !isCardStatus(status)) {
|
|
4195
|
+
console.error(`Invalid status: "${status}".`);
|
|
4196
|
+
process.exit(1);
|
|
4197
|
+
}
|
|
4198
|
+
const cards = listCards(hippoRoot, tenantId, { status });
|
|
4199
|
+
if (flags['json']) {
|
|
4200
|
+
console.log(JSON.stringify({ cards }, null, 2));
|
|
4201
|
+
return;
|
|
4202
|
+
}
|
|
4203
|
+
if (cards.length === 0) {
|
|
4204
|
+
console.log('No cards found.');
|
|
4205
|
+
return;
|
|
4206
|
+
}
|
|
4207
|
+
for (const card of cards) {
|
|
4208
|
+
console.log(`${card.id}\t${card.status}\t${card.title}${card.assigneeRuntime ? `\t(${card.assigneeRuntime})` : ''}`);
|
|
4209
|
+
}
|
|
4210
|
+
return;
|
|
4211
|
+
}
|
|
4212
|
+
if (subcommand === 'claim') {
|
|
4213
|
+
const id = args[1];
|
|
4214
|
+
const runtime = cardStringFlag(flags, 'runtime') ?? '';
|
|
4215
|
+
if (!id || !runtime) {
|
|
4216
|
+
console.error('Usage: hippo card claim <id> --runtime <name> [--session <id>]');
|
|
4217
|
+
process.exit(1);
|
|
4218
|
+
}
|
|
4219
|
+
const sessionId = cardStringFlag(flags, 'session') || undefined;
|
|
4220
|
+
let card;
|
|
4221
|
+
try {
|
|
4222
|
+
card = claimCard(hippoRoot, tenantId, id, runtime, sessionId);
|
|
4223
|
+
}
|
|
4224
|
+
catch (error) {
|
|
4225
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
4226
|
+
process.exit(1);
|
|
4227
|
+
}
|
|
4228
|
+
if (!card) {
|
|
4229
|
+
console.error(`Could not claim card ${id} (not ready/blocked, or already claimed).`);
|
|
4230
|
+
process.exit(1);
|
|
4231
|
+
}
|
|
4232
|
+
console.log(`Claimed card ${card.id} for ${runtime} (run ${card.runId}, lease until ${card.leaseUntil})`);
|
|
4233
|
+
return;
|
|
4234
|
+
}
|
|
4235
|
+
if (subcommand === 'heartbeat') {
|
|
4236
|
+
const id = args[1];
|
|
4237
|
+
const runId = cardRunFlag(flags);
|
|
4238
|
+
if (!id || runId === undefined) {
|
|
4239
|
+
console.error('Usage: hippo card heartbeat <id> --run <n>');
|
|
4240
|
+
process.exit(1);
|
|
4241
|
+
}
|
|
4242
|
+
let card;
|
|
4243
|
+
try {
|
|
4244
|
+
card = heartbeatCard(hippoRoot, tenantId, id, runId);
|
|
4245
|
+
}
|
|
4246
|
+
catch (error) {
|
|
4247
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
4248
|
+
process.exit(1);
|
|
4249
|
+
}
|
|
4250
|
+
if (!card) {
|
|
4251
|
+
console.error(`Could not heartbeat card ${id} (${cardRefusal(hippoRoot, tenantId, id)}).`);
|
|
4252
|
+
process.exit(1);
|
|
4253
|
+
}
|
|
4254
|
+
console.log(`Heartbeat card ${card.id}: lease until ${card.leaseUntil}`);
|
|
4255
|
+
return;
|
|
4256
|
+
}
|
|
4257
|
+
if (subcommand === 'block') {
|
|
4258
|
+
const id = args[1];
|
|
4259
|
+
const reason = cardStringFlag(flags, 'reason') ?? '';
|
|
4260
|
+
if (!id || !reason) {
|
|
4261
|
+
console.error(CARD_BLOCK_REASON_REQUIRED);
|
|
4262
|
+
process.exit(1);
|
|
4263
|
+
}
|
|
4264
|
+
const runId = cardRunFlag(flags);
|
|
4265
|
+
let card;
|
|
4266
|
+
try {
|
|
4267
|
+
card = blockCard(hippoRoot, tenantId, id, reason, runId);
|
|
4268
|
+
}
|
|
4269
|
+
catch (error) {
|
|
4270
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
4271
|
+
process.exit(1);
|
|
4272
|
+
}
|
|
4273
|
+
if (!card) {
|
|
4274
|
+
const why = runId === undefined ? 'not running' : cardRefusal(hippoRoot, tenantId, id);
|
|
4275
|
+
console.error(`Could not block card ${id} (${why}).`);
|
|
4276
|
+
process.exit(1);
|
|
4277
|
+
}
|
|
4278
|
+
console.log(`Blocked card ${card.id}`);
|
|
4279
|
+
return;
|
|
4280
|
+
}
|
|
4281
|
+
if (subcommand === 'review') {
|
|
4282
|
+
const id = args[1];
|
|
4283
|
+
if (!id) {
|
|
4284
|
+
console.error('Usage: hippo card review <id> [--run <n>]');
|
|
4285
|
+
process.exit(1);
|
|
4286
|
+
}
|
|
4287
|
+
const runId = cardRunFlag(flags);
|
|
4288
|
+
let card;
|
|
4289
|
+
try {
|
|
4290
|
+
card = reviewCard(hippoRoot, tenantId, id, runId);
|
|
4291
|
+
}
|
|
4292
|
+
catch (error) {
|
|
4293
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
4294
|
+
process.exit(1);
|
|
4295
|
+
}
|
|
4296
|
+
if (!card) {
|
|
4297
|
+
const why = runId === undefined ? 'not running' : cardRefusal(hippoRoot, tenantId, id);
|
|
4298
|
+
console.error(`Could not move card ${id} to review (${why}).`);
|
|
4299
|
+
process.exit(1);
|
|
4300
|
+
}
|
|
4301
|
+
console.log(`Card ${card.id} moved to review`);
|
|
4302
|
+
return;
|
|
4303
|
+
}
|
|
4304
|
+
if (subcommand === 'complete') {
|
|
4305
|
+
const id = args[1];
|
|
4306
|
+
const outcomeRaw = flags['outcome'];
|
|
4307
|
+
if (!id || !isHandoffOutcome(outcomeRaw)) {
|
|
4308
|
+
console.error('Usage: hippo card complete <id> --outcome <success|failure|partial> [--run <n>]');
|
|
4309
|
+
process.exit(1);
|
|
4310
|
+
}
|
|
4311
|
+
const runId = cardRunFlag(flags);
|
|
4312
|
+
let result;
|
|
4313
|
+
try {
|
|
4314
|
+
result = completeCard(hippoRoot, tenantId, id, outcomeRaw, runId);
|
|
4315
|
+
}
|
|
4316
|
+
catch (error) {
|
|
4317
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
4318
|
+
process.exit(1);
|
|
4319
|
+
}
|
|
4320
|
+
if (!result) {
|
|
4321
|
+
const why = runId === undefined ? 'not in review' : cardRefusal(hippoRoot, tenantId, id);
|
|
4322
|
+
console.error(`Could not complete card ${id} (${why}).`);
|
|
4323
|
+
process.exit(1);
|
|
4324
|
+
}
|
|
4325
|
+
console.log(`Completed card ${result.card.id} (status: ${result.card.status})`);
|
|
4326
|
+
if (result.promotedChildren.length > 0) {
|
|
4327
|
+
console.log(`Promoted to ready: ${result.promotedChildren.join(', ')}`);
|
|
4328
|
+
}
|
|
4329
|
+
return;
|
|
4330
|
+
}
|
|
4331
|
+
if (subcommand === 'reclaim') {
|
|
4332
|
+
if (args.length > 1) {
|
|
4333
|
+
console.error('Usage: hippo card reclaim (sweeps every expired lease; use hippo card block <id> for one card)');
|
|
4334
|
+
process.exit(1);
|
|
4335
|
+
}
|
|
4336
|
+
const ids = reclaimExpiredCards(hippoRoot, tenantId);
|
|
4337
|
+
if (ids.length === 0) {
|
|
4338
|
+
console.log('No expired leases.');
|
|
4339
|
+
return;
|
|
4340
|
+
}
|
|
4341
|
+
for (const id of ids) {
|
|
4342
|
+
console.log(`Reclaimed card ${id} (now ready)`);
|
|
4343
|
+
}
|
|
4344
|
+
return;
|
|
4345
|
+
}
|
|
4346
|
+
if (subcommand === 'comment') {
|
|
4347
|
+
const id = args[1];
|
|
4348
|
+
if (!id) {
|
|
4349
|
+
console.error('Usage: hippo card comment <id> --body "..." [--author <name>]');
|
|
4350
|
+
process.exit(1);
|
|
4351
|
+
}
|
|
4352
|
+
// Only show and comment look the card up directly; claim/heartbeat/block/review/complete throw from the store instead.
|
|
4353
|
+
const card = loadCard(hippoRoot, tenantId, id);
|
|
4354
|
+
if (!card) {
|
|
4355
|
+
console.error(`No card found with id ${id}.`);
|
|
4356
|
+
process.exit(1);
|
|
4357
|
+
}
|
|
4358
|
+
const body = cardStringFlag(flags, 'body') ?? '';
|
|
4359
|
+
if (!body) {
|
|
4360
|
+
console.error('Usage: hippo card comment <id> --body "..." [--author <name>]');
|
|
4361
|
+
process.exit(1);
|
|
4362
|
+
}
|
|
4363
|
+
const author = cardStringFlag(flags, 'author') || 'cli';
|
|
4364
|
+
const comment = addCardComment(hippoRoot, tenantId, id, author, body);
|
|
4365
|
+
console.log(`Added comment ${comment.id} to card ${id}`);
|
|
4366
|
+
return;
|
|
4367
|
+
}
|
|
4368
|
+
console.error('Usage: hippo card <create|show|list|claim|heartbeat|block|review|complete|reclaim|comment>');
|
|
4369
|
+
process.exit(1);
|
|
4370
|
+
}
|
|
4027
4371
|
// ---------------------------------------------------------------------------
|
|
4028
4372
|
// E2 prediction first-class object (v0.31)
|
|
4029
4373
|
// docs/plans/2026-05-26-e2-prediction-object.md
|
|
@@ -7936,6 +8280,34 @@ Commands:
|
|
|
7936
8280
|
--session <id> Filter by session
|
|
7937
8281
|
--json Output as JSON
|
|
7938
8282
|
handoff show <id> Show a specific handoff by ID
|
|
8283
|
+
card <sub> Manage claimable work-queue cards
|
|
8284
|
+
card create Create a new card
|
|
8285
|
+
--title <text> Card title (required)
|
|
8286
|
+
--repo <name> Associated repo
|
|
8287
|
+
--contract <text> Associated contract
|
|
8288
|
+
--budget <n> Token/step budget
|
|
8289
|
+
--depends-on <id> Parent card id (repeatable)
|
|
8290
|
+
card show <id> Show a card, its deps, runs, comments and latest handoff
|
|
8291
|
+
--json Output as JSON
|
|
8292
|
+
card list List cards, newest-updated first
|
|
8293
|
+
--status <status> Filter by status
|
|
8294
|
+
card claim <id> Claim a ready or blocked card; prints its run id and lease
|
|
8295
|
+
--runtime <name> Claiming runtime (required)
|
|
8296
|
+
--session <id> Session ID
|
|
8297
|
+
card heartbeat <id> Extend a running card's lease
|
|
8298
|
+
--run <n> Your run id, as card claim printed it (required)
|
|
8299
|
+
card block <id> Block a running card
|
|
8300
|
+
--reason "<why>" Reason recorded as a card comment (required)
|
|
8301
|
+
--run <n> Refuse unless <n> is the card's live run
|
|
8302
|
+
card review <id> Move a running card to review
|
|
8303
|
+
--run <n> Refuse unless <n> is the card's live run
|
|
8304
|
+
card complete <id> Complete a card in review
|
|
8305
|
+
--outcome <o> success | failure | partial (required)
|
|
8306
|
+
--run <n> Refuse unless <n> is the card's live run
|
|
8307
|
+
card reclaim Return every running card whose lease has expired to ready
|
|
8308
|
+
card comment <id> Add a comment to a card
|
|
8309
|
+
--body <text> Comment body (required)
|
|
8310
|
+
--author <name> Comment author (default: cli)
|
|
7939
8311
|
current <sub> Show compact current state for agent injection
|
|
7940
8312
|
current show Active task + recent session events (default)
|
|
7941
8313
|
--json Output as JSON
|
|
@@ -8161,6 +8533,11 @@ Examples:
|
|
|
8161
8533
|
hippo snapshot save --task "Ship feature" --summary "Tests are green" --next-step "Open the PR" --session sess_123
|
|
8162
8534
|
hippo handoff create --summary "PR is open, tests green" --next "Merge after review" --session sess_123 --artifact src/foo.ts
|
|
8163
8535
|
hippo handoff create --summary s --constraint a --constraint b --outcome partial --target-runtime codex --card-id c1 --tests pass
|
|
8536
|
+
hippo card create --title "Add cards table" --repo hippo --depends-on card_abc
|
|
8537
|
+
hippo card claim card_abc --runtime codex
|
|
8538
|
+
hippo card complete card_abc --outcome success
|
|
8539
|
+
hippo card heartbeat card_abc --run 7
|
|
8540
|
+
hippo card reclaim
|
|
8164
8541
|
hippo embed --status
|
|
8165
8542
|
hippo watch "npm run build"
|
|
8166
8543
|
hippo learn --git --days 30
|
|
@@ -8548,6 +8925,9 @@ async function main() {
|
|
|
8548
8925
|
case 'handoff':
|
|
8549
8926
|
cmdHandoff(hippoRoot, args, flags);
|
|
8550
8927
|
break;
|
|
8928
|
+
case 'card':
|
|
8929
|
+
cmdCard(hippoRoot, args, flags);
|
|
8930
|
+
break;
|
|
8551
8931
|
case 'predict':
|
|
8552
8932
|
cmdPredict(hippoRoot, args, flags);
|
|
8553
8933
|
break;
|