@merittdev/horus 0.21.6 → 0.21.7
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/dist/horus_source.whl +0 -0
- package/dist/index.cjs +400 -2
- package/package.json +1 -1
package/dist/horus_source.whl
CHANGED
|
Binary file
|
package/dist/index.cjs
CHANGED
|
@@ -3057,7 +3057,7 @@ var init_version = __esm({
|
|
|
3057
3057
|
"../../packages/core/src/version.ts"() {
|
|
3058
3058
|
"use strict";
|
|
3059
3059
|
init_cjs_shims();
|
|
3060
|
-
HORUS_VERSION = true ? "0.21.
|
|
3060
|
+
HORUS_VERSION = true ? "0.21.7" : "dev";
|
|
3061
3061
|
PINNED_SOURCE_VERSION = HORUS_VERSION;
|
|
3062
3062
|
SOURCE_PIN_ENFORCED = HORUS_VERSION !== "dev";
|
|
3063
3063
|
EXPECTED_INDEX_CAPABILITY = 2;
|
|
@@ -77378,6 +77378,35 @@ var CloudClient = class {
|
|
|
77378
77378
|
`/v1/projects/${projectId}/memory-items${qs ? `?${qs}` : ""}`
|
|
77379
77379
|
);
|
|
77380
77380
|
}
|
|
77381
|
+
// ── Team memory (HOR-464 / M4) ────────────────────────────────────────────
|
|
77382
|
+
/**
|
|
77383
|
+
* One-way PROMOTE local items into the workspace's shared team-memory store. Server-wins &
|
|
77384
|
+
* idempotent (`ON CONFLICT (workspace_id, origin_client_id) DO NOTHING`); it clamps/skips
|
|
77385
|
+
* confirmed-outcome (privacy). The body is a positive allowlist — vectors NEVER cross the wire.
|
|
77386
|
+
*/
|
|
77387
|
+
promoteTeamMemory(projectId, body2) {
|
|
77388
|
+
return this.request(
|
|
77389
|
+
"POST",
|
|
77390
|
+
`/v1/projects/${projectId}/team-memory/promote`,
|
|
77391
|
+
body2
|
|
77392
|
+
);
|
|
77393
|
+
}
|
|
77394
|
+
/**
|
|
77395
|
+
* Read the workspace's team-memory items with `seq > since`, ORDER BY seq ASC. The v1 client pulls
|
|
77396
|
+
* `since:'0'` and paginates on `nextCursor` until `hasMore` is false, then reconciles the local
|
|
77397
|
+
* cache. `includeDeleted` surfaces tombstones (deletedAt) so the cache can drop removed rows.
|
|
77398
|
+
*/
|
|
77399
|
+
listTeamMemorySince(projectId, q2) {
|
|
77400
|
+
const params = new URLSearchParams();
|
|
77401
|
+
params.set("since", q2.since);
|
|
77402
|
+
if (q2.limit !== void 0) params.set("limit", String(q2.limit));
|
|
77403
|
+
if (q2.includeDeleted) params.set("includeDeleted", "true");
|
|
77404
|
+
if (q2.search) params.set("search", q2.search);
|
|
77405
|
+
return this.request(
|
|
77406
|
+
"GET",
|
|
77407
|
+
`/v1/projects/${projectId}/team-memory?${params.toString()}`
|
|
77408
|
+
);
|
|
77409
|
+
}
|
|
77381
77410
|
};
|
|
77382
77411
|
|
|
77383
77412
|
// ../../packages/cli/src/lib/cloud/auth-store.ts
|
|
@@ -84445,6 +84474,21 @@ var memoryItem = pgTable(
|
|
|
84445
84474
|
userId: text("user_id"),
|
|
84446
84475
|
visibility: text("visibility").notNull().default("private"),
|
|
84447
84476
|
// private|team
|
|
84477
|
+
// HOR-464 (M4): team-memory cache provenance. `origin` distinguishes a locally-authored row
|
|
84478
|
+
// (`local`) from a disposable read-cache row pulled from the cloud team store (`cloud`). The
|
|
84479
|
+
// cloud-owned copy is server-authoritative — `origin='cloud'` rows are readonly locally (any
|
|
84480
|
+
// local mutation throws `MemoryCacheReadonlyError`; re-promote to change) and are NEVER re-pushed
|
|
84481
|
+
// by `memory sync` (which pushes `origin='local'` only). `cloudId` is the server team-memory id,
|
|
84482
|
+
// `authorName` the promoting teammate (DISPLAY-ONLY — never feeds rank), `pulledAt` the last
|
|
84483
|
+
// refresh time (staleness).
|
|
84484
|
+
origin: text("origin").notNull().default("local"),
|
|
84485
|
+
// local|cloud
|
|
84486
|
+
cloudId: text("cloud_id"),
|
|
84487
|
+
// server team_memory id (provenance/join)
|
|
84488
|
+
authorName: text("author_name"),
|
|
84489
|
+
// promoting teammate's display name (attribution, display-only)
|
|
84490
|
+
pulledAt: timestamp("pulled_at", { withTimezone: true }),
|
|
84491
|
+
// last cache-refresh time
|
|
84448
84492
|
payload: jsonb("payload"),
|
|
84449
84493
|
// forward-compat, zero-migration extension
|
|
84450
84494
|
// Incident-family recall keys, populated at WRITE for incident-derived kinds
|
|
@@ -84461,7 +84505,9 @@ var memoryItem = pgTable(
|
|
|
84461
84505
|
index("memory_item_scope_idx").on(t2.scope),
|
|
84462
84506
|
index("memory_item_status_idx").on(t2.status),
|
|
84463
84507
|
index("memory_item_tenancy_idx").on(t2.orgId, t2.workspaceId, t2.repo, t2.userId),
|
|
84464
|
-
index("memory_item_signature_idx").on(t2.signature)
|
|
84508
|
+
index("memory_item_signature_idx").on(t2.signature),
|
|
84509
|
+
// HOR-464: fast repo-scoped split of local-authored vs cloud-cached rows (pull reconcile + sync filter).
|
|
84510
|
+
index("memory_item_origin_idx").on(t2.repo, t2.origin)
|
|
84465
84511
|
]
|
|
84466
84512
|
);
|
|
84467
84513
|
var memoryLink = pgTable(
|
|
@@ -92771,6 +92817,16 @@ WHERE im.investigation_id IS NOT NULL AND im.project IS NULL;`,
|
|
|
92771
92817
|
WHERE "project" IS NULL AND incident_input->>'repo' IS NOT NULL;`,
|
|
92772
92818
|
'CREATE INDEX "investigations_project_created_idx" ON "investigations" ("project","created_at");'
|
|
92773
92819
|
]
|
|
92820
|
+
},
|
|
92821
|
+
{
|
|
92822
|
+
"tag": "0012_memory_item_origin",
|
|
92823
|
+
"statements": [
|
|
92824
|
+
`ALTER TABLE "memory_item" ADD COLUMN "origin" text DEFAULT 'local' NOT NULL;`,
|
|
92825
|
+
'ALTER TABLE "memory_item" ADD COLUMN "cloud_id" text;',
|
|
92826
|
+
'ALTER TABLE "memory_item" ADD COLUMN "author_name" text;',
|
|
92827
|
+
'ALTER TABLE "memory_item" ADD COLUMN "pulled_at" timestamp with time zone;',
|
|
92828
|
+
'CREATE INDEX IF NOT EXISTS "memory_item_origin_idx" ON "memory_item" ("repo","origin");'
|
|
92829
|
+
]
|
|
92774
92830
|
}
|
|
92775
92831
|
];
|
|
92776
92832
|
|
|
@@ -96403,6 +96459,16 @@ var MemorySecretError = class extends Error {
|
|
|
96403
96459
|
this.kind = kind;
|
|
96404
96460
|
}
|
|
96405
96461
|
};
|
|
96462
|
+
var MemoryCacheReadonlyError = class extends Error {
|
|
96463
|
+
id;
|
|
96464
|
+
constructor(id) {
|
|
96465
|
+
super(
|
|
96466
|
+
`refusing to mutate a cloud-owned cached memory item (${id}); the team store owns it \u2014 re-promote to change it`
|
|
96467
|
+
);
|
|
96468
|
+
this.name = "MemoryCacheReadonlyError";
|
|
96469
|
+
this.id = id;
|
|
96470
|
+
}
|
|
96471
|
+
};
|
|
96406
96472
|
var CLAIM_SECRET_PATTERNS = [
|
|
96407
96473
|
{ label: "private-key", re: /-----BEGIN (?:[A-Z ]+ )?PRIVATE KEY-----/ },
|
|
96408
96474
|
{ label: "jwt", re: /\beyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\b/ },
|
|
@@ -96562,6 +96628,7 @@ function createLocalMemoryStore(db) {
|
|
|
96562
96628
|
async update(id, patch, opts) {
|
|
96563
96629
|
const current = await getById(id);
|
|
96564
96630
|
if (current === null) throw new Error(`memory item not found: ${id}`);
|
|
96631
|
+
if (current.origin === "cloud") throw new MemoryCacheReadonlyError(id);
|
|
96565
96632
|
const set = {};
|
|
96566
96633
|
if (patch.claim !== void 0) {
|
|
96567
96634
|
assertClaimClean(patch.claim);
|
|
@@ -96594,6 +96661,7 @@ function createLocalMemoryStore(db) {
|
|
|
96594
96661
|
if (repo === "") return [];
|
|
96595
96662
|
const conds = [eq(memoryItem.repo, repo)];
|
|
96596
96663
|
if (q2.scope !== void 0) conds.push(eq(memoryItem.scope, q2.scope));
|
|
96664
|
+
if (q2.origin !== void 0) conds.push(eq(memoryItem.origin, q2.origin));
|
|
96597
96665
|
if (q2.visibility !== void 0) conds.push(eq(memoryItem.visibility, q2.visibility));
|
|
96598
96666
|
if (q2.orgId !== void 0) conds.push(eq(memoryItem.orgId, q2.orgId));
|
|
96599
96667
|
if (q2.workspaceId !== void 0) conds.push(eq(memoryItem.workspaceId, q2.workspaceId));
|
|
@@ -96614,6 +96682,7 @@ function createLocalMemoryStore(db) {
|
|
|
96614
96682
|
async setStatus(id, status, audit) {
|
|
96615
96683
|
const current = await getById(id);
|
|
96616
96684
|
if (current === null) throw new Error(`memory item not found: ${id}`);
|
|
96685
|
+
if (current.origin === "cloud") throw new MemoryCacheReadonlyError(id);
|
|
96617
96686
|
await db.update(memoryItem).set({ status }).where(eq(memoryItem.id, id));
|
|
96618
96687
|
await appendAudit({
|
|
96619
96688
|
memoryId: id,
|
|
@@ -96627,6 +96696,7 @@ function createLocalMemoryStore(db) {
|
|
|
96627
96696
|
async setVisibility(id, v4, audit) {
|
|
96628
96697
|
const current = await getById(id);
|
|
96629
96698
|
if (current === null) throw new Error(`memory item not found: ${id}`);
|
|
96699
|
+
if (current.origin === "cloud") throw new MemoryCacheReadonlyError(id);
|
|
96630
96700
|
await db.update(memoryItem).set({ visibility: v4 }).where(eq(memoryItem.id, id));
|
|
96631
96701
|
await appendAudit({
|
|
96632
96702
|
memoryId: id,
|
|
@@ -96771,6 +96841,96 @@ function createLocalMemoryStore(db) {
|
|
|
96771
96841
|
},
|
|
96772
96842
|
async history(id) {
|
|
96773
96843
|
return db.select().from(memoryAudit).where(eq(memoryAudit.memoryId, id)).orderBy(desc(memoryAudit.at), desc(memoryAudit.id));
|
|
96844
|
+
},
|
|
96845
|
+
// ---- HOR-464 / M4: disposable team-memory read-cache + one-way promote ----
|
|
96846
|
+
async upsertCached(item, audit) {
|
|
96847
|
+
assertClaimClean(item.claim);
|
|
96848
|
+
const repo = (item.repo ?? "").trim();
|
|
96849
|
+
if (repo === "") throw new Error("memory_item requires a repo (HOR-46 fail-closed)");
|
|
96850
|
+
const id = (item.id ?? "").trim();
|
|
96851
|
+
if (id === "") throw new Error("upsertCached requires a stable item id (the origin client id)");
|
|
96852
|
+
const status = item.status ?? "fresh";
|
|
96853
|
+
const now = /* @__PURE__ */ new Date();
|
|
96854
|
+
const values2 = {
|
|
96855
|
+
...item,
|
|
96856
|
+
id,
|
|
96857
|
+
repo,
|
|
96858
|
+
status,
|
|
96859
|
+
origin: "cloud",
|
|
96860
|
+
pulledAt: now,
|
|
96861
|
+
signature: null,
|
|
96862
|
+
tags: null
|
|
96863
|
+
};
|
|
96864
|
+
await db.insert(memoryItem).values(values2).onConflictDoUpdate({
|
|
96865
|
+
target: memoryItem.id,
|
|
96866
|
+
set: {
|
|
96867
|
+
kind: values2.kind,
|
|
96868
|
+
claim: values2.claim,
|
|
96869
|
+
scope: values2.scope,
|
|
96870
|
+
source: values2.source,
|
|
96871
|
+
status,
|
|
96872
|
+
confidence: values2.confidence,
|
|
96873
|
+
visibility: values2.visibility ?? "team",
|
|
96874
|
+
evidence: values2.evidence,
|
|
96875
|
+
cloudId: values2.cloudId ?? null,
|
|
96876
|
+
authorName: values2.authorName ?? null,
|
|
96877
|
+
lastVerifiedAt: values2.lastVerifiedAt ?? null,
|
|
96878
|
+
lastVerifiedHash: values2.lastVerifiedHash ?? null,
|
|
96879
|
+
origin: "cloud",
|
|
96880
|
+
// Re-scope to the repo we're pulling into. The cache is disposable and
|
|
96881
|
+
// project-isolated (HOR-46): a team item's id is the promoter's local id and is
|
|
96882
|
+
// stable across a workspace, so pulling it from a different repo on the same store
|
|
96883
|
+
// must move the cache row to the current repo — otherwise "pull here" silently
|
|
96884
|
+
// leaves it invisible here, stuck in whichever repo first pulled it.
|
|
96885
|
+
repo: values2.repo,
|
|
96886
|
+
pulledAt: now
|
|
96887
|
+
}
|
|
96888
|
+
});
|
|
96889
|
+
const row = await getById(id);
|
|
96890
|
+
if (row === null) throw new Error("memory_item upsertCached returned no row");
|
|
96891
|
+
await appendAudit({
|
|
96892
|
+
memoryId: id,
|
|
96893
|
+
action: "pulled",
|
|
96894
|
+
actor: audit.actor,
|
|
96895
|
+
fromStatus: null,
|
|
96896
|
+
toStatus: status,
|
|
96897
|
+
note: audit.note ?? null,
|
|
96898
|
+
detail: { origin: "cloud", ...values2.cloudId ? { cloudId: values2.cloudId } : {} }
|
|
96899
|
+
});
|
|
96900
|
+
return row;
|
|
96901
|
+
},
|
|
96902
|
+
async reconcileCache(repo, keepIds) {
|
|
96903
|
+
const r = repo.trim();
|
|
96904
|
+
if (r === "") return 0;
|
|
96905
|
+
const conds = keepIds.length > 0 ? and(
|
|
96906
|
+
eq(memoryItem.repo, r),
|
|
96907
|
+
eq(memoryItem.origin, "cloud"),
|
|
96908
|
+
notInArray(memoryItem.id, keepIds)
|
|
96909
|
+
) : and(eq(memoryItem.repo, r), eq(memoryItem.origin, "cloud"));
|
|
96910
|
+
const deleted = await db.delete(memoryItem).where(conds).returning();
|
|
96911
|
+
return deleted.length;
|
|
96912
|
+
},
|
|
96913
|
+
async markPromoted(id, provenance, audit) {
|
|
96914
|
+
const current = await getById(id);
|
|
96915
|
+
if (current === null) throw new Error(`memory item not found: ${id}`);
|
|
96916
|
+
await db.update(memoryItem).set({
|
|
96917
|
+
visibility: "team",
|
|
96918
|
+
origin: "cloud",
|
|
96919
|
+
cloudId: provenance.cloudId,
|
|
96920
|
+
authorName: provenance.authorName ?? current.authorName ?? null
|
|
96921
|
+
}).where(eq(memoryItem.id, id));
|
|
96922
|
+
await appendAudit({
|
|
96923
|
+
memoryId: id,
|
|
96924
|
+
action: "promote",
|
|
96925
|
+
actor: audit.actor,
|
|
96926
|
+
fromStatus: null,
|
|
96927
|
+
toStatus: null,
|
|
96928
|
+
note: audit.note ?? `promoted to team (cloud id ${provenance.cloudId})`,
|
|
96929
|
+
detail: { origin: "cloud", visibility: "team", cloudId: provenance.cloudId }
|
|
96930
|
+
});
|
|
96931
|
+
const updated = await getById(id);
|
|
96932
|
+
if (updated === null) throw new Error(`memory item vanished during promote: ${id}`);
|
|
96933
|
+
return updated;
|
|
96774
96934
|
}
|
|
96775
96935
|
};
|
|
96776
96936
|
}
|
|
@@ -122314,6 +122474,12 @@ function fromWire(r, repo) {
|
|
|
122314
122474
|
repo,
|
|
122315
122475
|
userId: r.createdByUserId ?? null,
|
|
122316
122476
|
visibility: r.visibility,
|
|
122477
|
+
// This is the write-only private mirror path (dualWrite reads local): a round-tripped record is
|
|
122478
|
+
// the device's OWN authored item, so it carries no team-cache provenance (HOR-464).
|
|
122479
|
+
origin: "local",
|
|
122480
|
+
cloudId: null,
|
|
122481
|
+
authorName: null,
|
|
122482
|
+
pulledAt: null,
|
|
122317
122483
|
payload: null,
|
|
122318
122484
|
// Incident-family recall keys are LOCAL-ONLY context (like vectors): they never enter the
|
|
122319
122485
|
// cloud-sync path, so a cloud-read row carries neither.
|
|
@@ -122375,6 +122541,10 @@ function createCloudMemoryStore(client, cfg) {
|
|
|
122375
122541
|
repo: item.repo ?? repoFallback,
|
|
122376
122542
|
userId: item.userId ?? null,
|
|
122377
122543
|
visibility: input.visibility ?? "private",
|
|
122544
|
+
origin: "local",
|
|
122545
|
+
cloudId: null,
|
|
122546
|
+
authorName: null,
|
|
122547
|
+
pulledAt: null,
|
|
122378
122548
|
payload: null,
|
|
122379
122549
|
// Incident-family recall keys stay LOCAL-ONLY — never synced, so the cloud echo carries neither.
|
|
122380
122550
|
signature: null,
|
|
@@ -122409,6 +122579,10 @@ function createCloudMemoryStore(client, cfg) {
|
|
|
122409
122579
|
repo: repoFallback,
|
|
122410
122580
|
userId: null,
|
|
122411
122581
|
visibility: "private",
|
|
122582
|
+
origin: "local",
|
|
122583
|
+
cloudId: null,
|
|
122584
|
+
authorName: null,
|
|
122585
|
+
pulledAt: null,
|
|
122412
122586
|
payload: null,
|
|
122413
122587
|
signature: null,
|
|
122414
122588
|
tags: null
|
|
@@ -122667,6 +122841,10 @@ function recalledToJSON(r) {
|
|
|
122667
122841
|
effectiveStatus: r.freshness.status,
|
|
122668
122842
|
confidence: r.item.confidence,
|
|
122669
122843
|
visibility: r.item.visibility,
|
|
122844
|
+
// HOR-464: provenance + teammate attribution. `origin='cloud'` marks a pulled team item; the
|
|
122845
|
+
// `authorName` is DISPLAY-ONLY (it is never read into rank/order — the honesty invariant).
|
|
122846
|
+
origin: r.item.origin,
|
|
122847
|
+
authorName: r.item.authorName ?? null,
|
|
122670
122848
|
evidence: r.item.evidence,
|
|
122671
122849
|
freshness: {
|
|
122672
122850
|
label: r.freshness.label,
|
|
@@ -122693,6 +122871,10 @@ function renderStoredItems(items) {
|
|
|
122693
122871
|
lines.push(
|
|
122694
122872
|
` - ${r.item.kind} \xB7 scope: ${r.item.scope} \xB7 ${r.freshness.label}${drift} \xB7 confidence: ${conf}`
|
|
122695
122873
|
);
|
|
122874
|
+
if (r.item.origin === "cloud") {
|
|
122875
|
+
const who = r.item.authorName ? `teammate ${r.item.authorName}` : "a teammate";
|
|
122876
|
+
lines.push(` - ${import_picocolors36.default.dim(`shared by ${who} (team)`)}`);
|
|
122877
|
+
}
|
|
122696
122878
|
}
|
|
122697
122879
|
return lines.join("\n");
|
|
122698
122880
|
}
|
|
@@ -123171,6 +123353,7 @@ async function runMemorySync(opts) {
|
|
|
123171
123353
|
const store = createLocalMemoryStore(db);
|
|
123172
123354
|
items = await store.query({
|
|
123173
123355
|
repo: project,
|
|
123356
|
+
origin: "local",
|
|
123174
123357
|
status: SYNCABLE_STATUSES,
|
|
123175
123358
|
limit: opts.limit ?? 5e3
|
|
123176
123359
|
});
|
|
@@ -123305,6 +123488,215 @@ async function runMemorySync(opts) {
|
|
|
123305
123488
|
}
|
|
123306
123489
|
return failed > 0 ? 1 : 0;
|
|
123307
123490
|
}
|
|
123491
|
+
function toPromoteInput(item) {
|
|
123492
|
+
return {
|
|
123493
|
+
originClientId: item.id,
|
|
123494
|
+
kind: item.kind,
|
|
123495
|
+
claim: item.claim,
|
|
123496
|
+
scope: item.scope,
|
|
123497
|
+
source: item.source,
|
|
123498
|
+
status: item.status,
|
|
123499
|
+
confidence: item.confidence,
|
|
123500
|
+
contentHash: item.lastVerifiedHash ?? null,
|
|
123501
|
+
provenance: {}
|
|
123502
|
+
};
|
|
123503
|
+
}
|
|
123504
|
+
async function runMemoryPromote(id, opts) {
|
|
123505
|
+
const json2 = opts.json === true;
|
|
123506
|
+
const fail = (msg) => {
|
|
123507
|
+
if (json2) console.log(JSON.stringify({ ok: false, error: msg }, null, 2));
|
|
123508
|
+
else console.error(import_picocolors36.default.red(msg));
|
|
123509
|
+
return 1;
|
|
123510
|
+
};
|
|
123511
|
+
const memId = (id ?? "").trim();
|
|
123512
|
+
if (memId === "") return fail("A memory item id is required.");
|
|
123513
|
+
const root = repoRootOrCwd(opts.cwd);
|
|
123514
|
+
const cfg = readCloudConfig(root);
|
|
123515
|
+
if (!isCloudActive(cfg) || !cfg.project) {
|
|
123516
|
+
return fail("This repo isn't linked to a cloud project. Run `horus cloud link` first.");
|
|
123517
|
+
}
|
|
123518
|
+
const session = authedClient();
|
|
123519
|
+
if (!session) return fail("Not logged in. Run `horus login` first.");
|
|
123520
|
+
let config2;
|
|
123521
|
+
try {
|
|
123522
|
+
config2 = await loadConfig(opts.config);
|
|
123523
|
+
} catch (err2) {
|
|
123524
|
+
return fail(err2.message);
|
|
123525
|
+
}
|
|
123526
|
+
const project = resolveProject(config2, opts.repo);
|
|
123527
|
+
if (!project) return 1;
|
|
123528
|
+
const { db, sql: sql2 } = await openDb(config2.database.url);
|
|
123529
|
+
try {
|
|
123530
|
+
const store = createLocalMemoryStore(db);
|
|
123531
|
+
const row = await store.get(memId);
|
|
123532
|
+
if (row === null) return fail(`Memory item not found: ${memId}`);
|
|
123533
|
+
if (row.kind === "confirmed-outcome" || row.source === "confirmed-outcome") {
|
|
123534
|
+
return fail("Refusing to promote a confirmed-outcome item \u2014 it stays private (spec \xA75.2).");
|
|
123535
|
+
}
|
|
123536
|
+
if (row.origin === "cloud") {
|
|
123537
|
+
if (json2) console.log(JSON.stringify({ ok: true, id: memId, alreadyTeam: true }, null, 2));
|
|
123538
|
+
else console.log(import_picocolors36.default.dim(`${memId} is already a team item \u2014 nothing to promote.`));
|
|
123539
|
+
return 0;
|
|
123540
|
+
}
|
|
123541
|
+
const res = await session.client.promoteTeamMemory(cfg.project.id, {
|
|
123542
|
+
items: [toPromoteInput(row)]
|
|
123543
|
+
});
|
|
123544
|
+
const promoted = res.items.find((it2) => it2.originClientId === memId);
|
|
123545
|
+
if (!promoted) {
|
|
123546
|
+
return fail(
|
|
123547
|
+
`The server did not promote ${memId} (skipped ${res.skipped}/${res.total}). Local state unchanged.`
|
|
123548
|
+
);
|
|
123549
|
+
}
|
|
123550
|
+
const updated = await store.markPromoted(
|
|
123551
|
+
memId,
|
|
123552
|
+
{ cloudId: promoted.id, authorName: promoted.authorName ?? null },
|
|
123553
|
+
{ actor: { kind: "user" }, note: `promoted to team ${cfg.project.slug}` }
|
|
123554
|
+
);
|
|
123555
|
+
if (json2) {
|
|
123556
|
+
console.log(
|
|
123557
|
+
JSON.stringify(
|
|
123558
|
+
{
|
|
123559
|
+
ok: true,
|
|
123560
|
+
id: memId,
|
|
123561
|
+
cloudId: promoted.id,
|
|
123562
|
+
visibility: updated.visibility,
|
|
123563
|
+
origin: updated.origin
|
|
123564
|
+
},
|
|
123565
|
+
null,
|
|
123566
|
+
2
|
|
123567
|
+
)
|
|
123568
|
+
);
|
|
123569
|
+
} else {
|
|
123570
|
+
console.log(
|
|
123571
|
+
import_picocolors36.default.green(`Promoted ${memId} to team memory (${cfg.project.slug}) \u2014 now visible to teammates.`)
|
|
123572
|
+
);
|
|
123573
|
+
}
|
|
123574
|
+
return 0;
|
|
123575
|
+
} catch (err2) {
|
|
123576
|
+
return fail(err2.message);
|
|
123577
|
+
} finally {
|
|
123578
|
+
await sql2.end();
|
|
123579
|
+
}
|
|
123580
|
+
}
|
|
123581
|
+
var TEAM_PULL_PAGE_LIMIT = 200;
|
|
123582
|
+
var TEAM_PULL_MAX_PAGES = 1e4;
|
|
123583
|
+
function formatAge3(ms) {
|
|
123584
|
+
const mins = Math.floor(ms / 6e4);
|
|
123585
|
+
if (mins < 1) return "just now";
|
|
123586
|
+
if (mins < 60) return `${mins}m ago`;
|
|
123587
|
+
const hrs = Math.floor(mins / 60);
|
|
123588
|
+
if (hrs < 24) return `${hrs}h ago`;
|
|
123589
|
+
return `${Math.floor(hrs / 24)}d ago`;
|
|
123590
|
+
}
|
|
123591
|
+
async function runMemoryPull(opts) {
|
|
123592
|
+
const json2 = opts.json === true;
|
|
123593
|
+
const fail = (msg) => {
|
|
123594
|
+
if (json2) console.log(JSON.stringify({ ok: false, error: msg }, null, 2));
|
|
123595
|
+
else console.error(import_picocolors36.default.red(msg));
|
|
123596
|
+
return 1;
|
|
123597
|
+
};
|
|
123598
|
+
const root = repoRootOrCwd(opts.cwd);
|
|
123599
|
+
const cfg = readCloudConfig(root);
|
|
123600
|
+
if (!isCloudActive(cfg) || !cfg.project) {
|
|
123601
|
+
return fail("This repo isn't linked to a cloud project. Run `horus cloud link` first.");
|
|
123602
|
+
}
|
|
123603
|
+
const session = authedClient();
|
|
123604
|
+
if (!session) return fail("Not logged in. Run `horus login` first.");
|
|
123605
|
+
let config2;
|
|
123606
|
+
try {
|
|
123607
|
+
config2 = await loadConfig(opts.config);
|
|
123608
|
+
} catch (err2) {
|
|
123609
|
+
return fail(err2.message);
|
|
123610
|
+
}
|
|
123611
|
+
const project = resolveProject(config2, opts.repo);
|
|
123612
|
+
if (!project) return 1;
|
|
123613
|
+
const { db, sql: sql2 } = await openDb(config2.database.url);
|
|
123614
|
+
try {
|
|
123615
|
+
const store = createLocalMemoryStore(db);
|
|
123616
|
+
const vectorIndex = buildVectorIndex(config2, project);
|
|
123617
|
+
const items = [];
|
|
123618
|
+
let since = "0";
|
|
123619
|
+
try {
|
|
123620
|
+
for (let page = 0; page < TEAM_PULL_MAX_PAGES; page++) {
|
|
123621
|
+
const res = await session.client.listTeamMemorySince(cfg.project.id, {
|
|
123622
|
+
since,
|
|
123623
|
+
limit: TEAM_PULL_PAGE_LIMIT,
|
|
123624
|
+
includeDeleted: true
|
|
123625
|
+
});
|
|
123626
|
+
items.push(...res.items);
|
|
123627
|
+
if (!res.hasMore || !res.nextCursor) break;
|
|
123628
|
+
since = res.nextCursor;
|
|
123629
|
+
}
|
|
123630
|
+
} catch (err2) {
|
|
123631
|
+
if (err2 instanceof CloudOfflineError) {
|
|
123632
|
+
const cached2 = await store.query({ repo: project, origin: "cloud" });
|
|
123633
|
+
const newest = cached2.map((c4) => c4.pulledAt ? new Date(c4.pulledAt).getTime() : 0).reduce((a2, b5) => Math.max(a2, b5), 0);
|
|
123634
|
+
const note = newest > 0 ? `stale \u2014 last pulled ${formatAge3(Date.now() - newest)}` : "stale \u2014 never pulled";
|
|
123635
|
+
if (json2) {
|
|
123636
|
+
console.log(
|
|
123637
|
+
JSON.stringify({ ok: true, offline: true, stale: true, cached: cached2.length, note }, null, 2)
|
|
123638
|
+
);
|
|
123639
|
+
} else {
|
|
123640
|
+
console.log(import_picocolors36.default.yellow(`Offline \u2014 serving ${cached2.length} cached team item(s) (${note}).`));
|
|
123641
|
+
}
|
|
123642
|
+
return 0;
|
|
123643
|
+
}
|
|
123644
|
+
throw err2;
|
|
123645
|
+
}
|
|
123646
|
+
const keepIds = [];
|
|
123647
|
+
let upserted = 0;
|
|
123648
|
+
let removed = 0;
|
|
123649
|
+
for (const it2 of items) {
|
|
123650
|
+
if (it2.deletedAt) {
|
|
123651
|
+
await bestEffortRemove(vectorIndex, it2.originClientId);
|
|
123652
|
+
continue;
|
|
123653
|
+
}
|
|
123654
|
+
const cacheRow = {
|
|
123655
|
+
id: it2.originClientId,
|
|
123656
|
+
// stable cross-device id (the promoting device's local id)
|
|
123657
|
+
kind: it2.kind,
|
|
123658
|
+
claim: it2.claim,
|
|
123659
|
+
scope: it2.scope,
|
|
123660
|
+
source: it2.source,
|
|
123661
|
+
status: it2.status ?? "fresh",
|
|
123662
|
+
// A null server confidence stores as 0 — honest (unknown confidence ranks low, never inflated).
|
|
123663
|
+
confidence: it2.confidence ?? 0,
|
|
123664
|
+
evidence: [],
|
|
123665
|
+
repo: project,
|
|
123666
|
+
visibility: "team",
|
|
123667
|
+
cloudId: it2.id,
|
|
123668
|
+
authorName: it2.authorName
|
|
123669
|
+
};
|
|
123670
|
+
await store.upsertCached(cacheRow, { actor: { kind: "system" }, note: "memory pull" });
|
|
123671
|
+
await bestEffortUpsert(vectorIndex, {
|
|
123672
|
+
memoryId: it2.originClientId,
|
|
123673
|
+
claim: it2.claim,
|
|
123674
|
+
repo: project,
|
|
123675
|
+
scope: it2.scope
|
|
123676
|
+
});
|
|
123677
|
+
keepIds.push(it2.originClientId);
|
|
123678
|
+
upserted += 1;
|
|
123679
|
+
}
|
|
123680
|
+
removed = await store.reconcileCache(project, keepIds);
|
|
123681
|
+
if (json2) {
|
|
123682
|
+
console.log(
|
|
123683
|
+
JSON.stringify({ ok: true, project, pulled: upserted, removed, total: items.length }, null, 2)
|
|
123684
|
+
);
|
|
123685
|
+
} else {
|
|
123686
|
+
console.log(
|
|
123687
|
+
import_picocolors36.default.green(
|
|
123688
|
+
`Pulled ${upserted} team memory item(s)` + (removed ? `, dropped ${removed} stale` : "") + "."
|
|
123689
|
+
)
|
|
123690
|
+
);
|
|
123691
|
+
console.log(import_picocolors36.default.dim("Team items are read-only locally \u2014 re-promote to change one. Vectors stay local."));
|
|
123692
|
+
}
|
|
123693
|
+
return 0;
|
|
123694
|
+
} catch (err2) {
|
|
123695
|
+
return fail(err2.message);
|
|
123696
|
+
} finally {
|
|
123697
|
+
await sql2.end();
|
|
123698
|
+
}
|
|
123699
|
+
}
|
|
123308
123700
|
function outcomeLabelToJSON(l2) {
|
|
123309
123701
|
return {
|
|
123310
123702
|
id: l2.id,
|
|
@@ -129302,6 +129694,12 @@ Examples:
|
|
|
129302
129694
|
process.exitCode = await runMemorySync(opts);
|
|
129303
129695
|
}
|
|
129304
129696
|
);
|
|
129697
|
+
memory.command("promote <id>").description("Promote a local memory item into the shared team memory (one-way; refuses confirmed-outcome)").option("-c, --config <path>", "path to horus.config.ts").option("--repo <name>", "repository/project WITHIN the loaded config (default: inferred from cwd) \u2014 cross-repo targeting is --config or cd").option("--json", "output JSON").action(async (id, opts) => {
|
|
129698
|
+
process.exitCode = await runMemoryPromote(id, opts);
|
|
129699
|
+
});
|
|
129700
|
+
memory.command("pull").description("Refresh the local cache of shared team memory (full pull; offline serves stale cache)").option("-c, --config <path>", "path to horus.config.ts").option("--repo <name>", "repository/project WITHIN the loaded config (default: inferred from cwd) \u2014 cross-repo targeting is --config or cd").option("--force", "force a full refresh even if the cache looks current").option("--json", "output JSON").action(async (opts) => {
|
|
129701
|
+
process.exitCode = await runMemoryPull(opts);
|
|
129702
|
+
});
|
|
129305
129703
|
const evalCmd = program2.command("eval").description("Read-only accuracy harness over Horus's outcome-label eval set (never writes labels)");
|
|
129306
129704
|
evalCmd.command("build").description("Emit a byte-stable corpus-<version>.jsonl + manifest from the eval set (read-only)").option("-c, --config <path>", "path to horus.config.ts").option("--repo <name>", "repository/project WITHIN the loaded config (default: inferred from cwd) \u2014 cross-repo targeting is --config or cd").option("--source <source>", "filter to one signal source: feedback | confirm").option("--days <n>", "only include labels from the last N days", (v4) => Number(v4)).option("--limit <n>", "max labels to scan", (v4) => Number(v4)).option("--out <dir>", "output directory for the corpus + manifest (default: ./.horus/eval)").option("--version <v>", "corpus schema version tag (default: v1)").option("--json", "output JSON").action(
|
|
129307
129705
|
async (opts) => {
|