@remnic/core 9.66.9 → 9.66.11
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/access-admin-ops-surface.js +21 -20
- package/dist/access-authorization-probe.js +22 -21
- package/dist/access-boundary.js +21 -20
- package/dist/access-cli.js +87 -86
- package/dist/access-cli.js.map +1 -1
- package/dist/access-extraction-force-flush.js +21 -20
- package/dist/access-http-query.js +23 -22
- package/dist/access-http.js +38 -37
- package/dist/access-identity-continuity-surface.js +21 -20
- package/dist/access-lcm-surface.js +21 -20
- package/dist/access-mcp.js +30 -29
- package/dist/access-namespace-preflight.js +21 -20
- package/dist/access-observe-write-surface.js +21 -20
- package/dist/access-operations-batch.js +24 -23
- package/dist/access-operations.js +29 -28
- package/dist/access-recall-concurrency.js +21 -20
- package/dist/access-recall-response.js +21 -20
- package/dist/access-recall-surface.js +21 -20
- package/dist/access-schema.js +3 -3
- package/dist/access-service.js +21 -20
- package/dist/access-surface-catalog.js +23 -22
- package/dist/access-surface-catalog.js.map +1 -1
- package/dist/adapters/index.js +4 -4
- package/dist/adapters/registry.js +2 -2
- package/dist/briefing.js +8 -8
- package/dist/causal-consolidation.js +10 -9
- package/dist/causal-consolidation.js.map +1 -1
- package/dist/{chunk-5GZ6UWY5.js → chunk-245QROHZ.js} +5 -5
- package/dist/{chunk-A7GQBJP5.js → chunk-2L5FSNPC.js} +10 -10
- package/dist/{chunk-SJ5L4ZZX.js → chunk-2OXBS2LV.js} +2 -2
- package/dist/chunk-3UXOZBHV.js +20 -0
- package/dist/chunk-3UXOZBHV.js.map +1 -0
- package/dist/{chunk-G775FE2E.js → chunk-4PHT7KE2.js} +11 -11
- package/dist/{chunk-TKSBOV7N.js → chunk-4PWANX5V.js} +6 -6
- package/dist/{chunk-TEJKBBWS.js → chunk-4QKWBVEE.js} +11 -11
- package/dist/{chunk-MFI4QPW5.js → chunk-54BRW3BL.js} +2 -2
- package/dist/{chunk-23F7HGBV.js → chunk-5E7KXID5.js} +1 -1
- package/dist/{chunk-VX5YHEGJ.js → chunk-6H7JWYTC.js} +17 -12
- package/dist/chunk-6H7JWYTC.js.map +1 -0
- package/dist/{chunk-5CSKIWEB.js → chunk-7BS5UVXA.js} +3 -3
- package/dist/{chunk-Q5YKVBIV.js → chunk-7FLINMUE.js} +2 -2
- package/dist/{chunk-ZC5L6ME2.js → chunk-7MOYAT4Y.js} +3 -3
- package/dist/{chunk-NABJLNLU.js → chunk-BCM6ZAJ3.js} +190 -52
- package/dist/chunk-BCM6ZAJ3.js.map +1 -0
- package/dist/{chunk-KVXMN33W.js → chunk-C6TMLF3A.js} +4 -4
- package/dist/{chunk-47BX23NA.js → chunk-D3AUQXTJ.js} +2 -2
- package/dist/{chunk-3HHLOKKU.js → chunk-DJLH3XWM.js} +2 -2
- package/dist/{chunk-SRILZVR4.js → chunk-E2PORO7O.js} +2 -2
- package/dist/{chunk-G3TUQJFU.js → chunk-E62M4H4M.js} +2 -2
- package/dist/{chunk-GB4R6B5M.js → chunk-EG4FHU5C.js} +2 -2
- package/dist/{chunk-QU2V4IO3.js → chunk-EMAEDM2J.js} +2 -2
- package/dist/{chunk-FNYVWAIK.js → chunk-G3B3LP6A.js} +18 -18
- package/dist/{chunk-D6Q5EPOV.js → chunk-HCERYDRI.js} +2 -2
- package/dist/{chunk-5NZIRM4Q.js → chunk-HJQZQUPC.js} +7 -7
- package/dist/{chunk-CIWOW2FH.js → chunk-HPGXB56F.js} +2 -2
- package/dist/{chunk-KQAFEZQX.js → chunk-IPLYGWQF.js} +5 -5
- package/dist/{chunk-UAWSKMWQ.js → chunk-ISX7JODR.js} +2 -2
- package/dist/{chunk-RKNJBZ55.js → chunk-JBPKEARU.js} +4 -4
- package/dist/{chunk-HWWGPS2N.js → chunk-JNZSNJ43.js} +2 -2
- package/dist/{chunk-JHU543UU.js → chunk-K6PWPC7O.js} +2 -2
- package/dist/{chunk-JVGBDNXA.js → chunk-KT3ERTF6.js} +2 -2
- package/dist/{chunk-SOQLXIQX.js → chunk-KWLDB64F.js} +2 -2
- package/dist/{chunk-7JPKRZD5.js → chunk-LPSTQR26.js} +2 -2
- package/dist/{chunk-OT7WQ2I3.js → chunk-LTWFU6VJ.js} +2 -2
- package/dist/{chunk-NSP3HFJZ.js → chunk-PVV42HM4.js} +6 -3
- package/dist/chunk-PVV42HM4.js.map +1 -0
- package/dist/{chunk-TWMGWLHD.js → chunk-PWVRMD37.js} +2 -2
- package/dist/{chunk-AAJK3PZ6.js → chunk-QB66LBXA.js} +2 -2
- package/dist/{chunk-XVNDP3RO.js → chunk-QKTGCL6M.js} +2 -2
- package/dist/{chunk-LTDN5WYF.js → chunk-S47XNAWD.js} +2 -2
- package/dist/{chunk-IBHUZEKL.js → chunk-SQEIPTDV.js} +2 -2
- package/dist/{chunk-J67E4JNT.js → chunk-TGKLVAML.js} +2 -2
- package/dist/{chunk-VCYY7XEL.js → chunk-TS37MXC7.js} +2 -2
- package/dist/{chunk-ZJGHOPEO.js → chunk-UWOA5L6N.js} +70 -73
- package/dist/{chunk-ZJGHOPEO.js.map → chunk-UWOA5L6N.js.map} +1 -1
- package/dist/{chunk-ZVUGRXSY.js → chunk-WD424KYT.js} +2 -2
- package/dist/{chunk-B3TZLAO4.js → chunk-X5SDZMO7.js} +2 -2
- package/dist/{chunk-FUPIAP6Q.js → chunk-XN33JLRF.js} +3 -3
- package/dist/{chunk-MBDUNO7J.js → chunk-XZ7QLOTJ.js} +5 -21
- package/dist/chunk-XZ7QLOTJ.js.map +1 -0
- package/dist/{chunk-OJSCH7QT.js → chunk-Y2ECPAJI.js} +7 -7
- package/dist/{chunk-DGKVUPBM.js → chunk-Y6KDJQLK.js} +7 -7
- package/dist/{chunk-EZ25VE3G.js → chunk-YNDLCWXS.js} +4 -4
- package/dist/{chunk-Q3UMNZRK.js → chunk-ZIGSNLYI.js} +3 -3
- package/dist/{chunk-RF3LMSQW.js → chunk-ZRQPX2DW.js} +2 -2
- package/dist/cli.js +61 -60
- package/dist/coding/export-okf-codegraph.js +8 -8
- package/dist/compounding/engine.js +8 -8
- package/dist/connectors/codex-materialize-runner.js +9 -8
- package/dist/connectors/index.js +9 -8
- package/dist/convergence-refresh.js +10 -10
- package/dist/corpus-watermark.js +10 -10
- package/dist/dashboard-runtime.js +2 -2
- package/dist/entity-retrieval.js +8 -8
- package/dist/external-wiki-access.js +22 -21
- package/dist/extraction-span-tokens.d.ts +16 -0
- package/dist/extraction-span-tokens.js +34 -0
- package/dist/extraction-span-tokens.js.map +1 -0
- package/dist/extraction.js +9 -8
- package/dist/{first-start-migration-UG32BSG2.js → first-start-migration-KHIMCBIC.js} +4 -4
- package/dist/harmonic-retrieval.js +1 -1
- package/dist/index.d.ts +102 -1
- package/dist/index.js +126 -117
- package/dist/lcm/engine.js +4 -4
- package/dist/lcm/index.js +9 -9
- package/dist/location/index.js +10 -10
- package/dist/maintenance/memory-governance.js +8 -8
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +8 -8
- package/dist/maintenance/rebuild-memory-projection.js +9 -9
- package/dist/namespaces/migrate.js +9 -9
- package/dist/namespaces/search.js +4 -4
- package/dist/namespaces/storage.js +8 -8
- package/dist/operator-doctor-corpus.js +11 -11
- package/dist/operator-toolkit.js +16 -16
- package/dist/orchestration/maintenance.js +16 -16
- package/dist/orchestrator.js +88 -87
- package/dist/recall-deep-frontier.d.ts +15 -0
- package/dist/recall-deep-frontier.js +29 -0
- package/dist/recall-deep-frontier.js.map +1 -0
- package/dist/search/factory.js +3 -3
- package/dist/search/index.js +7 -7
- package/dist/semantic-consolidation.js +10 -9
- package/dist/semantic-rule-promotion.js +8 -8
- package/dist/semantic-rule-verifier.js +8 -8
- package/dist/source-agent-qualifier.js +9 -8
- package/dist/standup/build.js +8 -8
- package/dist/storage.js +7 -7
- package/dist/support-passport/index.js +24 -23
- package/dist/transfer/autodetect.js +1 -1
- package/dist/transfer/backup.js +1 -1
- package/dist/transfer/capsule-export.js +2 -2
- package/dist/transfer/export-okf.d.ts +8 -0
- package/dist/transfer/export-okf.js +10 -9
- package/dist/verified-recall.js +8 -8
- package/dist/who-knows-cli.js +9 -9
- package/dist/who-knows.js +8 -8
- package/package.json +2 -2
- package/src/activity/index.ts +2 -0
- package/src/activity/privacy-status.test.ts +117 -0
- package/src/activity/privacy-status.ts +65 -0
- package/src/activity/timeline/index.ts +1 -0
- package/src/activity/timeline/week-previous.test.ts +119 -0
- package/src/activity/timeline/week-previous.ts +83 -0
- package/src/activity/vault-insert.test.ts +105 -0
- package/src/activity/vault-insert.ts +86 -0
- package/src/cli/okf-commands.ts +2 -5
- package/src/extraction-span-tokens.test.ts +69 -0
- package/src/extraction-span-tokens.ts +44 -0
- package/src/okf/render.ts +11 -2
- package/src/recall-deep-frontier.test.ts +85 -0
- package/src/recall-deep-frontier.ts +39 -0
- package/src/transfer/export-okf.test.ts +77 -1
- package/src/transfer/export-okf.ts +25 -9
- package/dist/chunk-MBDUNO7J.js.map +0 -1
- package/dist/chunk-NABJLNLU.js.map +0 -1
- package/dist/chunk-NSP3HFJZ.js.map +0 -1
- package/dist/chunk-VX5YHEGJ.js.map +0 -1
- /package/dist/{chunk-5GZ6UWY5.js.map → chunk-245QROHZ.js.map} +0 -0
- /package/dist/{chunk-A7GQBJP5.js.map → chunk-2L5FSNPC.js.map} +0 -0
- /package/dist/{chunk-SJ5L4ZZX.js.map → chunk-2OXBS2LV.js.map} +0 -0
- /package/dist/{chunk-G775FE2E.js.map → chunk-4PHT7KE2.js.map} +0 -0
- /package/dist/{chunk-TKSBOV7N.js.map → chunk-4PWANX5V.js.map} +0 -0
- /package/dist/{chunk-TEJKBBWS.js.map → chunk-4QKWBVEE.js.map} +0 -0
- /package/dist/{chunk-MFI4QPW5.js.map → chunk-54BRW3BL.js.map} +0 -0
- /package/dist/{chunk-23F7HGBV.js.map → chunk-5E7KXID5.js.map} +0 -0
- /package/dist/{chunk-5CSKIWEB.js.map → chunk-7BS5UVXA.js.map} +0 -0
- /package/dist/{chunk-Q5YKVBIV.js.map → chunk-7FLINMUE.js.map} +0 -0
- /package/dist/{chunk-ZC5L6ME2.js.map → chunk-7MOYAT4Y.js.map} +0 -0
- /package/dist/{chunk-KVXMN33W.js.map → chunk-C6TMLF3A.js.map} +0 -0
- /package/dist/{chunk-47BX23NA.js.map → chunk-D3AUQXTJ.js.map} +0 -0
- /package/dist/{chunk-3HHLOKKU.js.map → chunk-DJLH3XWM.js.map} +0 -0
- /package/dist/{chunk-SRILZVR4.js.map → chunk-E2PORO7O.js.map} +0 -0
- /package/dist/{chunk-G3TUQJFU.js.map → chunk-E62M4H4M.js.map} +0 -0
- /package/dist/{chunk-GB4R6B5M.js.map → chunk-EG4FHU5C.js.map} +0 -0
- /package/dist/{chunk-QU2V4IO3.js.map → chunk-EMAEDM2J.js.map} +0 -0
- /package/dist/{chunk-FNYVWAIK.js.map → chunk-G3B3LP6A.js.map} +0 -0
- /package/dist/{chunk-D6Q5EPOV.js.map → chunk-HCERYDRI.js.map} +0 -0
- /package/dist/{chunk-5NZIRM4Q.js.map → chunk-HJQZQUPC.js.map} +0 -0
- /package/dist/{chunk-CIWOW2FH.js.map → chunk-HPGXB56F.js.map} +0 -0
- /package/dist/{chunk-KQAFEZQX.js.map → chunk-IPLYGWQF.js.map} +0 -0
- /package/dist/{chunk-UAWSKMWQ.js.map → chunk-ISX7JODR.js.map} +0 -0
- /package/dist/{chunk-RKNJBZ55.js.map → chunk-JBPKEARU.js.map} +0 -0
- /package/dist/{chunk-HWWGPS2N.js.map → chunk-JNZSNJ43.js.map} +0 -0
- /package/dist/{chunk-JHU543UU.js.map → chunk-K6PWPC7O.js.map} +0 -0
- /package/dist/{chunk-JVGBDNXA.js.map → chunk-KT3ERTF6.js.map} +0 -0
- /package/dist/{chunk-SOQLXIQX.js.map → chunk-KWLDB64F.js.map} +0 -0
- /package/dist/{chunk-7JPKRZD5.js.map → chunk-LPSTQR26.js.map} +0 -0
- /package/dist/{chunk-OT7WQ2I3.js.map → chunk-LTWFU6VJ.js.map} +0 -0
- /package/dist/{chunk-TWMGWLHD.js.map → chunk-PWVRMD37.js.map} +0 -0
- /package/dist/{chunk-AAJK3PZ6.js.map → chunk-QB66LBXA.js.map} +0 -0
- /package/dist/{chunk-XVNDP3RO.js.map → chunk-QKTGCL6M.js.map} +0 -0
- /package/dist/{chunk-LTDN5WYF.js.map → chunk-S47XNAWD.js.map} +0 -0
- /package/dist/{chunk-IBHUZEKL.js.map → chunk-SQEIPTDV.js.map} +0 -0
- /package/dist/{chunk-J67E4JNT.js.map → chunk-TGKLVAML.js.map} +0 -0
- /package/dist/{chunk-VCYY7XEL.js.map → chunk-TS37MXC7.js.map} +0 -0
- /package/dist/{chunk-ZVUGRXSY.js.map → chunk-WD424KYT.js.map} +0 -0
- /package/dist/{chunk-B3TZLAO4.js.map → chunk-X5SDZMO7.js.map} +0 -0
- /package/dist/{chunk-FUPIAP6Q.js.map → chunk-XN33JLRF.js.map} +0 -0
- /package/dist/{chunk-OJSCH7QT.js.map → chunk-Y2ECPAJI.js.map} +0 -0
- /package/dist/{chunk-DGKVUPBM.js.map → chunk-Y6KDJQLK.js.map} +0 -0
- /package/dist/{chunk-EZ25VE3G.js.map → chunk-YNDLCWXS.js.map} +0 -0
- /package/dist/{chunk-Q3UMNZRK.js.map → chunk-ZIGSNLYI.js.map} +0 -0
- /package/dist/{chunk-RF3LMSQW.js.map → chunk-ZRQPX2DW.js.map} +0 -0
- /package/dist/{first-start-migration-UG32BSG2.js.map → first-start-migration-KHIMCBIC.js.map} +0 -0
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deep-recall frontier ranker (issue #2332).
|
|
3
|
+
*
|
|
4
|
+
* Pure. After every working-set change the frontier is capped at
|
|
5
|
+
* DEEP_RECALL_FRONTIER_CAP items ordered by shared-anchor count
|
|
6
|
+
* descending, then nodeId ascending — a total comparator.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export const DEEP_RECALL_FRONTIER_CAP = 20;
|
|
10
|
+
|
|
11
|
+
export interface DeepRecallFrontierCandidate {
|
|
12
|
+
nodeId: string;
|
|
13
|
+
sharedAnchorCount: number;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function rankDeepRecallFrontier(
|
|
17
|
+
candidates: readonly DeepRecallFrontierCandidate[],
|
|
18
|
+
): DeepRecallFrontierCandidate[] {
|
|
19
|
+
const byNodeId = new Map<string, DeepRecallFrontierCandidate>();
|
|
20
|
+
for (const candidate of candidates) {
|
|
21
|
+
const nodeId = typeof candidate.nodeId === "string" ? candidate.nodeId.trim() : "";
|
|
22
|
+
if (nodeId === "") continue;
|
|
23
|
+
const count = candidate.sharedAnchorCount;
|
|
24
|
+
if (typeof count !== "number" || !Number.isFinite(count) || count < 1) continue;
|
|
25
|
+
const kept = byNodeId.get(nodeId);
|
|
26
|
+
if (kept === undefined || count > kept.sharedAnchorCount) {
|
|
27
|
+
byNodeId.set(nodeId, { nodeId, sharedAnchorCount: count });
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return [...byNodeId.values()]
|
|
31
|
+
.sort((a, b) => {
|
|
32
|
+
if (a.sharedAnchorCount !== b.sharedAnchorCount) {
|
|
33
|
+
return b.sharedAnchorCount - a.sharedAnchorCount;
|
|
34
|
+
}
|
|
35
|
+
if (a.nodeId !== b.nodeId) return a.nodeId < b.nodeId ? -1 : 1;
|
|
36
|
+
return 0;
|
|
37
|
+
})
|
|
38
|
+
.slice(0, DEEP_RECALL_FRONTIER_CAP);
|
|
39
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import assert from "node:assert/strict";
|
|
2
|
-
import { mkdtemp, readFile, rm, writeFile, symlink } from "node:fs/promises";
|
|
2
|
+
import { mkdir, mkdtemp, readFile, rm, writeFile, symlink } from "node:fs/promises";
|
|
3
3
|
import { existsSync, lstatSync, readdirSync } from "node:fs";
|
|
4
4
|
import os from "node:os";
|
|
5
5
|
import path from "node:path";
|
|
@@ -162,3 +162,79 @@ function collectFiles(root: string): string[] {
|
|
|
162
162
|
walk(root);
|
|
163
163
|
return out.sort();
|
|
164
164
|
}
|
|
165
|
+
|
|
166
|
+
test("--namespace cannot escape the namespace root", async () => {
|
|
167
|
+
const memoryDir = await mkdtemp(path.join(os.tmpdir(), "remnic-okf-ns-"));
|
|
168
|
+
const outDir = path.join(await mkdtemp(path.join(os.tmpdir(), "remnic-okf-nsout-")), "bundle");
|
|
169
|
+
try {
|
|
170
|
+
await seedStore(memoryDir);
|
|
171
|
+
// A raw path.join on the operator value used to resolve outside
|
|
172
|
+
// <memoryDir>/namespaces and export an arbitrary tree.
|
|
173
|
+
for (const namespace of ["../../..", "../sibling", path.resolve(memoryDir)]) {
|
|
174
|
+
await assert.rejects(
|
|
175
|
+
() => exportOkfBundle({ memoryDir, namespace, outDir }),
|
|
176
|
+
/invalid --namespace/,
|
|
177
|
+
`namespace ${namespace} must be rejected`,
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
assert.equal(existsSync(outDir), false, "a rejected namespace must not create the bundle");
|
|
181
|
+
} finally {
|
|
182
|
+
await rm(memoryDir, { recursive: true, force: true });
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
test("a plain --namespace still exports from the namespace subtree", async () => {
|
|
187
|
+
const root = await mkdtemp(path.join(os.tmpdir(), "remnic-okf-nsok-"));
|
|
188
|
+
const outDir = path.join(root, "bundle");
|
|
189
|
+
try {
|
|
190
|
+
await seedStore(path.join(root, "namespaces", "team-a"));
|
|
191
|
+
const result = await exportOkfBundle({ memoryDir: root, namespace: "team-a", outDir });
|
|
192
|
+
assert.ok(result.exported > 0, "namespaced export must find its own memories");
|
|
193
|
+
assert.ok(existsSync(path.join(outDir, "index.md")));
|
|
194
|
+
} finally {
|
|
195
|
+
await rm(root, { recursive: true, force: true });
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
test("a leftover backup directory does not block a forced export", async () => {
|
|
200
|
+
const memoryDir = await mkdtemp(path.join(os.tmpdir(), "remnic-okf-bk-"));
|
|
201
|
+
const root = await mkdtemp(path.join(os.tmpdir(), "remnic-okf-bkout-"));
|
|
202
|
+
const outDir = path.join(root, "bundle");
|
|
203
|
+
try {
|
|
204
|
+
await seedStore(memoryDir);
|
|
205
|
+
await exportOkfBundle({ memoryDir, outDir });
|
|
206
|
+
// Simulate a crash between the two publish renames, or any directory a
|
|
207
|
+
// user happens to own at the old fixed backup path.
|
|
208
|
+
const stale = `${outDir}.okf-prev`;
|
|
209
|
+
await mkdir(stale, { recursive: true });
|
|
210
|
+
await writeFile(path.join(stale, "keep.md"), "stale\n", "utf8");
|
|
211
|
+
|
|
212
|
+
const again = await exportOkfBundle({ memoryDir, outDir, force: true });
|
|
213
|
+
assert.ok(again.exported > 0);
|
|
214
|
+
assert.ok(existsSync(path.join(outDir, "index.md")), "forced export must republish");
|
|
215
|
+
assert.equal(existsSync(path.join(stale, "keep.md")), true, "an unrelated directory is left alone");
|
|
216
|
+
const leftovers = readdirSync(root).filter((name) => name.startsWith("bundle.okf-prev-"));
|
|
217
|
+
assert.deepEqual(leftovers, [], "each attempt removes its own backup");
|
|
218
|
+
} finally {
|
|
219
|
+
await rm(memoryDir, { recursive: true, force: true });
|
|
220
|
+
await rm(root, { recursive: true, force: true });
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
test("--out that is a file is rejected before anything is written", async () => {
|
|
225
|
+
const memoryDir = await mkdtemp(path.join(os.tmpdir(), "remnic-okf-file-"));
|
|
226
|
+
const root = await mkdtemp(path.join(os.tmpdir(), "remnic-okf-fileout-"));
|
|
227
|
+
const outDir = path.join(root, "bundle");
|
|
228
|
+
try {
|
|
229
|
+
await seedStore(memoryDir);
|
|
230
|
+
await writeFile(outDir, "not a directory\n", "utf8");
|
|
231
|
+
await assert.rejects(
|
|
232
|
+
() => exportOkfBundle({ memoryDir, outDir, force: true }),
|
|
233
|
+
/--out exists and is not a directory/,
|
|
234
|
+
);
|
|
235
|
+
assert.equal(await readFile(outDir, "utf8"), "not a directory\n", "the file is left untouched");
|
|
236
|
+
} finally {
|
|
237
|
+
await rm(memoryDir, { recursive: true, force: true });
|
|
238
|
+
await rm(root, { recursive: true, force: true });
|
|
239
|
+
}
|
|
240
|
+
});
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import { rmSync } from "node:fs";
|
|
1
|
+
import { mkdirSync, rmSync } from "node:fs";
|
|
2
2
|
import { mkdtemp } from "node:fs/promises";
|
|
3
|
-
import os from "node:os";
|
|
4
3
|
import path from "node:path";
|
|
5
4
|
|
|
6
5
|
import { parseOaiMemCitation } from "../citations.js";
|
|
@@ -8,6 +7,7 @@ import { inferMemoryStatus } from "../memory-lifecycle-ledger-utils.js";
|
|
|
8
7
|
import { normalizeProjectionPreview } from "../memory-projection-format.js";
|
|
9
8
|
import { lintOkfDir } from "../okf/lint.js";
|
|
10
9
|
import { okfTypeForMemory, OKF_PROFILE_TYPE } from "../okf/type-mapping.js";
|
|
10
|
+
import { resolveNamespaceChildRoot } from "../namespaces/path.js";
|
|
11
11
|
import {
|
|
12
12
|
OKF_EXPORT_VERSION,
|
|
13
13
|
publishBundle,
|
|
@@ -38,6 +38,14 @@ const MEMORY_STATUSES: readonly MemoryStatus[] = [
|
|
|
38
38
|
|
|
39
39
|
export interface ExportOkfOptions {
|
|
40
40
|
memoryDir: string;
|
|
41
|
+
/**
|
|
42
|
+
* Optional namespace segment. Resolved here, once, through the shared
|
|
43
|
+
* containment guard: both CLI entry points used to join the raw operator
|
|
44
|
+
* value onto `memoryDir/namespaces`, so `--namespace ../../..` escaped the
|
|
45
|
+
* namespace root and exported an arbitrary tree (rule 9 — one resolver, not
|
|
46
|
+
* a per-caller guard).
|
|
47
|
+
*/
|
|
48
|
+
namespace?: string;
|
|
41
49
|
outDir: string;
|
|
42
50
|
includeStatus?: readonly string[];
|
|
43
51
|
includeCategories?: readonly string[];
|
|
@@ -71,15 +79,19 @@ export function parseIncludeStatus(raw: unknown): MemoryStatus[] {
|
|
|
71
79
|
export async function exportOkfBundle(opts: ExportOkfOptions): Promise<ExportOkfResult> {
|
|
72
80
|
const outDir = path.resolve(opts.outDir);
|
|
73
81
|
rejectSymlinkPath(outDir);
|
|
82
|
+
const namespace = (opts.namespace ?? "").trim();
|
|
83
|
+
const memoryDir = namespace
|
|
84
|
+
? resolveNamespaceChildRoot(opts.memoryDir, namespace, "--namespace")
|
|
85
|
+
: opts.memoryDir;
|
|
74
86
|
const includeStatus = new Set(parseIncludeStatus(opts.includeStatus));
|
|
75
87
|
const includeCategories = opts.includeCategories?.length ? new Set(opts.includeCategories) : null;
|
|
76
88
|
const excludeTags = new Set(opts.excludeTags ?? []);
|
|
77
|
-
const storage = new StorageManager(
|
|
89
|
+
const storage = new StorageManager(memoryDir);
|
|
78
90
|
const memories = await storage.readAllMemories();
|
|
79
91
|
const included: MemoryFile[] = [];
|
|
80
92
|
let excluded = 0;
|
|
81
93
|
for (const memory of memories) {
|
|
82
|
-
const rel = toRel(memory.path,
|
|
94
|
+
const rel = toRel(memory.path, memoryDir);
|
|
83
95
|
if (!opts.includeWearables && rel.startsWith("wearables/")) {
|
|
84
96
|
excluded += 1;
|
|
85
97
|
continue;
|
|
@@ -99,17 +111,21 @@ export async function exportOkfBundle(opts: ExportOkfOptions): Promise<ExportOkf
|
|
|
99
111
|
}
|
|
100
112
|
included.push(memory);
|
|
101
113
|
}
|
|
102
|
-
included.sort((a, b) => toRel(a.path,
|
|
114
|
+
included.sort((a, b) => toRel(a.path, memoryDir).localeCompare(toRel(b.path, memoryDir)));
|
|
103
115
|
|
|
104
|
-
|
|
116
|
+
// Stage inside the destination's parent, not the OS temp dir: publishBundle
|
|
117
|
+
// finishes with a rename, and a rename across filesystems fails with EXDEV
|
|
118
|
+
// whenever --out lives on a different mount than /tmp.
|
|
119
|
+
mkdirSync(path.dirname(outDir), { recursive: true });
|
|
120
|
+
const staging = await mkdtemp(path.join(path.dirname(outDir), ".remnic-okf-export-"));
|
|
105
121
|
const byCategory: Record<string, number> = {};
|
|
106
122
|
const idToRel = new Map<string, string>();
|
|
107
123
|
for (const memory of included) {
|
|
108
|
-
const rel = toRel(memory.path,
|
|
124
|
+
const rel = toRel(memory.path, memoryDir);
|
|
109
125
|
idToRel.set(memory.frontmatter.id, rel);
|
|
110
126
|
}
|
|
111
127
|
for (const memory of included) {
|
|
112
|
-
const rel = toRel(memory.path,
|
|
128
|
+
const rel = toRel(memory.path, memoryDir);
|
|
113
129
|
const rendered = renderMemory(memory, rel, idToRel);
|
|
114
130
|
writeBundleFile(staging, rel, rendered);
|
|
115
131
|
const category = memory.frontmatter.category;
|
|
@@ -140,7 +156,7 @@ export async function exportOkfBundle(opts: ExportOkfOptions): Promise<ExportOkf
|
|
|
140
156
|
writeBundleFile(staging, "log.md", renderLog(events, idToRel, opts.logMaxEntries ?? DEFAULT_OKF_LOG_MAX_ENTRIES));
|
|
141
157
|
}
|
|
142
158
|
|
|
143
|
-
writeIndexes(staging, included,
|
|
159
|
+
writeIndexes(staging, included, memoryDir);
|
|
144
160
|
const lint = lintOkfDir(staging);
|
|
145
161
|
const blocking = lint.findings.filter((f) => f.code !== "skipped_encrypted" && f.code !== "reserved_basename");
|
|
146
162
|
if (blocking.length > 0) {
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/connectors/codex-materialize-runner.ts","../src/namespaces/path.ts"],"sourcesContent":["/**\n * codex-materialize-runner.ts — Thin I/O bridge for the Codex materializer.\n *\n * The pure rendering logic lives in {@link ./codex-materialize.js}. This file\n * is the place callers (consolidation hooks, CLI, session-end hook) go when\n * they want the whole \"load memories from storage → render → write\" flow.\n *\n * Kept deliberately small so #378 never has to reach into orchestrator.ts /\n * importance.ts — the two files Wave 1 agents are editing concurrently.\n */\n\nimport { existsSync } from \"node:fs\";\n\nimport { log } from \"../logger.js\";\nimport { resolveNamespaceChildRoot } from \"../namespaces/path.js\";\nimport { isSafeRouteNamespace } from \"../routing/engine.js\";\nimport { projectProceduresToSkills } from \"../procedural/skill-projection.js\";\nimport { BUILTIN_SKILLS } from \"../skills-registry.js\";\nimport { StorageManager } from \"../storage.js\";\nimport { excludeSupportPassportPrivateMemories } from \"../support-passport/card-projection.js\";\nimport type { PluginConfig, MemoryFile } from \"../types.js\";\nimport {\n hasCodexMaterializeSentinel,\n materializeForNamespace,\n type MaterializeResult,\n type RolloutSummaryInput,\n} from \"./codex-materialize.js\";\n\n/** Options accepted by the shared post-consolidation materialize helper. */\nexport interface PostConsolidationMaterializeOptions {\n config: PluginConfig;\n namespace?: string;\n memories?: MemoryFile[];\n memoryDir?: string;\n codexHome?: string;\n rolloutSummaries?: RolloutSummaryInput[];\n now?: Date;\n}\n\n/** Options accepted by the runner. */\nexport interface RunMaterializeOptions {\n /** Remnic config — we only read the `codexMaterialize*` fields. */\n config: PluginConfig;\n /** Namespace to materialize. Overrides the config's `codexMaterializeNamespace`. */\n namespace?: string;\n /** Override the memory directory (defaults to `config.memoryDir`). */\n memoryDir?: string;\n /** Override `<codex_home>` (useful for tests). */\n codexHome?: string;\n /** Optional pre-loaded memories (bypasses disk read — used in tests). */\n memories?: MemoryFile[];\n /** Optional rollout summaries supplied by the caller. */\n rolloutSummaries?: RolloutSummaryInput[];\n /** Current time injection for deterministic runs. */\n now?: Date;\n /** Reason string — logged for observability. */\n reason?: \"consolidation\" | \"session_end\" | \"manual\" | \"cli\";\n}\n\n/**\n * Run the Codex materialization end-to-end. Returns `null` when the feature\n * is disabled in config or when the user hasn't opted in via the sentinel.\n * Never throws for \"expected\" skips; only throws on schema validation or I/O\n * errors that callers actually need to surface.\n */\nexport async function runCodexMaterialize(\n options: RunMaterializeOptions,\n): Promise<MaterializeResult | null> {\n const cfg = options.config;\n if (!cfg.codexMaterializeMemories) {\n log.debug(`[codex-materialize] skipped — codexMaterializeMemories=false`);\n return null;\n }\n\n // Per-trigger gate: session-end runs must honor codexMaterializeOnSessionEnd.\n // The Codex Stop hook (remnic-codex-hook.cjs, session-end event) passes\n // reason=\"session_end\"; when the user has turned off the session-end trigger\n // we short-circuit here without touching disk.\n if (options.reason === \"session_end\" && cfg.codexMaterializeOnSessionEnd === false) {\n log.debug(\n `[codex-materialize] skipped — session-end disabled via codexMaterializeOnSessionEnd=false`,\n );\n return null;\n }\n\n const namespace = resolveNamespace(options.namespace, cfg);\n const memoryDir = options.memoryDir ?? cfg.memoryDir;\n const codexHome = options.codexHome ?? cfg.codex?.codexHome ?? undefined;\n if (!memoryDir) {\n log.warn(`[codex-materialize] skipped — no memoryDir available`);\n return null;\n }\n\n let memories: MemoryFile[];\n if (options.memories) {\n memories = options.memories;\n } else {\n if (!hasCodexMaterializeSentinel(codexHome)) {\n return materializeForNamespace(namespace, {\n memories: [],\n codexHome,\n maxSummaryTokens: cfg.codexMaterializeMaxSummaryTokens,\n rolloutRetentionDays: cfg.codexMaterializeRolloutRetentionDays,\n rolloutSummaries: options.rolloutSummaries,\n now: options.now,\n });\n }\n const nsDir = resolveNamespaceDir(memoryDir, namespace, cfg);\n const storage = new StorageManager(nsDir);\n memories = await storage.readAllMemories();\n }\n memories = excludeSupportPassportPrivateMemories(memories);\n\n // Skill projection (issue #2369). Gated on `procedural.enabled` AND\n // `procedural.skillProjection.enabled`; `maxSkills: 0` disables it too.\n // Passing `undefined` leaves `skills/` untouched, so a disabled gate never\n // deletes a folder the user curated.\n const skillProjection = cfg.procedural?.skillProjection;\n const skills =\n cfg.procedural?.enabled === true && skillProjection?.enabled === true\n ? projectProceduresToSkills(memories, {\n maxSkills: skillProjection.maxSkills,\n reservedSlugs: BUILTIN_SKILLS.map((skill) => skill.slug),\n })\n : undefined;\n\n // Intentionally NOT catching here: per the JSDoc contract above,\n // schema-validation and I/O errors from `materializeForNamespace` must\n // surface to callers so they can exit non-zero (CLI) or log + recover\n // (consolidation post-hook, which has its own narrower try/catch).\n // Catching everything would make a broken MEMORY.md render look like a\n // successful skip, and invisible failures are strictly worse than loud\n // ones for a feature that writes to `~/.codex/memories`.\n const result = materializeForNamespace(namespace, {\n memories,\n codexHome,\n maxSummaryTokens: cfg.codexMaterializeMaxSummaryTokens,\n rolloutRetentionDays: cfg.codexMaterializeRolloutRetentionDays,\n rolloutSummaries: options.rolloutSummaries,\n skills,\n now: options.now,\n });\n if (options.reason) {\n log.debug(\n `[codex-materialize] ran reason=${options.reason} wrote=${result.wrote} files=${result.filesWritten.length}`,\n );\n }\n return result;\n}\n\n/**\n * Shared helper for post-consolidation materialize hooks.\n *\n * `materializeAfterSemanticConsolidation` and `materializeAfterCausalConsolidation`\n * used to be two nearly-identical copies of this logic; keeping the actual\n * body here means any future guard/logging change happens in one place.\n *\n * The only per-caller knob is `logPrefix`, which is used to tag the\n * non-fatal warning emitted when the materializer throws.\n */\nexport async function runPostConsolidationMaterialize(\n logPrefix: string,\n options: PostConsolidationMaterializeOptions,\n): Promise<MaterializeResult | null> {\n if (!options.config.codexMaterializeMemories) return null;\n if (!options.config.codexMaterializeOnConsolidation) return null;\n try {\n return await runCodexMaterialize({\n config: options.config,\n namespace: options.namespace,\n memories: options.memories,\n memoryDir: options.memoryDir,\n codexHome: options.codexHome,\n rolloutSummaries: options.rolloutSummaries,\n now: options.now,\n reason: \"consolidation\",\n });\n } catch (error) {\n log.warn(\n `${logPrefix} Codex materialize post-hook failed (non-fatal): ${\n error instanceof Error ? error.message : String(error)\n }`,\n );\n return null;\n }\n}\n\nfunction resolveNamespace(override: string | undefined, cfg: PluginConfig): string {\n const requested = (override ?? cfg.codexMaterializeNamespace ?? \"auto\").trim();\n const defaultNamespace = (cfg.defaultNamespace ?? \"\").trim();\n const namespace =\n requested.length === 0 || requested === \"auto\"\n ? (defaultNamespace.length > 0 ? defaultNamespace : \"default\")\n : requested;\n if (!isSafeRouteNamespace(namespace)) {\n throw new Error(`invalid materialize namespace: ${namespace}`);\n }\n return namespace;\n}\n\n/**\n * Resolve the on-disk storage root for a namespace, matching\n * `NamespaceStorageRouter` in `packages/remnic-core/src/namespaces/storage.ts`.\n *\n * Contract:\n * - When namespaces are disabled, every namespace maps to `memoryDir` itself.\n * - When namespaces are enabled, non-default namespaces always live under\n * `memoryDir/namespaces/<namespace>`.\n * - The default namespace prefers `memoryDir/namespaces/<defaultNamespace>`\n * when that directory already exists (migrated install); otherwise it\n * falls back to the legacy `memoryDir` root so materialization does not\n * silently switch directories out from under an existing install.\n */\nfunction resolveNamespaceDir(\n memoryDir: string,\n namespace: string,\n cfg: PluginConfig,\n): string {\n if (!cfg.namespacesEnabled) return memoryDir;\n\n const configuredDefaultNamespace = (cfg.defaultNamespace ?? \"\").trim();\n const defaultNamespace =\n configuredDefaultNamespace.length > 0 ? configuredDefaultNamespace : \"default\";\n const ns = (namespace || defaultNamespace).trim();\n if (!isSafeRouteNamespace(ns)) {\n throw new Error(`invalid materialize namespace: ${ns}`);\n }\n const namespacedRoot = resolveNamespaceChildRoot(memoryDir, ns, \"materialize namespace path\");\n\n if (ns === defaultNamespace) {\n return existsSync(namespacedRoot) ? namespacedRoot : memoryDir;\n }\n return namespacedRoot;\n}\n","import path from \"node:path\";\n\nexport function assertNamespacePathInsideRoot(\n root: string,\n candidate: string,\n namespace: string,\n label = \"namespace path\",\n): void {\n const relative = path.relative(root, candidate);\n if (relative === \"\" || (!relative.startsWith(\"..\") && !path.isAbsolute(relative))) {\n return;\n }\n throw new Error(`invalid ${label}: ${namespace}`);\n}\n\nexport function resolveNamespaceChildRoot(\n memoryDir: string,\n namespace: string,\n label = \"namespace path\",\n): string {\n const namespaceRoot = path.resolve(memoryDir, \"namespaces\");\n const candidate = path.resolve(namespaceRoot, namespace);\n assertNamespacePathInsideRoot(namespaceRoot, candidate, namespace, label);\n return candidate;\n}\n"],"mappings":"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","names":[]}
|