@remnic/core 1.1.2 → 1.1.3
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/abort-error.js +1 -0
- package/dist/abstraction-nodes.js +1 -0
- package/dist/access-audit.js +1 -0
- package/dist/access-cli.js +70 -45
- package/dist/access-cli.js.map +1 -1
- package/dist/access-http.d.ts +50 -5
- package/dist/access-http.js +37 -15
- package/dist/access-idempotency.js +1 -0
- package/dist/access-mcp.d.ts +10 -5
- package/dist/access-mcp.js +36 -13
- package/dist/access-schema.d.ts +133 -13
- package/dist/access-schema.js +20 -1
- package/dist/access-service-_AEUMVyX.d.ts +1981 -0
- package/dist/access-service.d.ts +11 -6
- package/dist/access-service.js +38 -14
- package/dist/active-memory-bridge.js +1 -0
- package/dist/active-recall.js +1 -0
- package/dist/active-recall.js.map +1 -1
- package/dist/behavior-learner.js +1 -0
- package/dist/behavior-learner.js.map +1 -1
- package/dist/behavior-signals.js +1 -0
- package/dist/bootstrap.d.ts +6 -4
- package/dist/bootstrap.js +1 -0
- package/dist/boxes.js +1 -0
- package/dist/briefing.d.ts +9 -5
- package/dist/briefing.js +9 -6
- package/dist/buffer-surprise-report.js +1 -0
- package/dist/buffer-surprise.js +1 -0
- package/dist/buffer.d.ts +1 -1
- package/dist/buffer.js +1 -0
- package/dist/calibration.d.ts +8 -1
- package/dist/calibration.js +10 -2
- package/dist/calibration.js.map +1 -1
- package/dist/capsule-cli.d.ts +137 -0
- package/dist/capsule-cli.js +34 -0
- package/dist/capsule-crypto-5CYAGVC5.js +18 -0
- package/dist/capsule-export-NZQPOTQ4.js +17 -0
- package/dist/capsule-export-NZQPOTQ4.js.map +1 -0
- package/dist/capsule-import-SDCUXLEV.js +16 -0
- package/dist/capsule-import-SDCUXLEV.js.map +1 -0
- package/dist/capsule-merge-DI7PNQ2H.js +189 -0
- package/dist/capsule-merge-DI7PNQ2H.js.map +1 -0
- package/dist/causal-behavior.js +1 -0
- package/dist/causal-behavior.js.map +1 -1
- package/dist/causal-chain.js +1 -0
- package/dist/causal-consolidation.js +11 -8
- package/dist/causal-consolidation.js.map +1 -1
- package/dist/causal-retrieval.js +2 -1
- package/dist/causal-retrieval.js.map +1 -1
- package/dist/causal-trajectory-graph.js +4 -1
- package/dist/causal-trajectory-graph.js.map +1 -1
- package/dist/causal-trajectory.js +2 -1
- package/dist/chunk-2LSZVONP.js +67 -0
- package/dist/chunk-2LSZVONP.js.map +1 -0
- package/dist/chunk-32KD5IHZ.js +245 -0
- package/dist/chunk-32KD5IHZ.js.map +1 -0
- package/dist/{chunk-VDX363PS.js → chunk-34F3PLWZ.js} +10 -3
- package/dist/chunk-34F3PLWZ.js.map +1 -0
- package/dist/chunk-3KIS4VGT.js +228 -0
- package/dist/chunk-3KIS4VGT.js.map +1 -0
- package/dist/chunk-3LCWFNVS.js +350 -0
- package/dist/chunk-3LCWFNVS.js.map +1 -0
- package/dist/chunk-43EKP2UK.js +26 -0
- package/dist/chunk-43EKP2UK.js.map +1 -0
- package/dist/chunk-457A4P3L.js +119 -0
- package/dist/chunk-457A4P3L.js.map +1 -0
- package/dist/{chunk-TMYO7B5P.js → chunk-47WOM4YW.js} +2 -2
- package/dist/{chunk-OC5OXUQ4.js → chunk-4PLGJRBV.js} +653 -17
- package/dist/chunk-4PLGJRBV.js.map +1 -0
- package/dist/{chunk-PVICZTKG.js → chunk-55FXRRSJ.js} +5 -5
- package/dist/{chunk-PVICZTKG.js.map → chunk-55FXRRSJ.js.map} +1 -1
- package/dist/{chunk-ULYOGL6R.js → chunk-5HRY2WRF.js} +7 -3
- package/dist/chunk-5HRY2WRF.js.map +1 -0
- package/dist/chunk-6TBWYBJ3.js +236 -0
- package/dist/chunk-6TBWYBJ3.js.map +1 -0
- package/dist/chunk-74EMIVE4.js +329 -0
- package/dist/chunk-74EMIVE4.js.map +1 -0
- package/dist/chunk-74WWN7ZW.js +82 -0
- package/dist/chunk-74WWN7ZW.js.map +1 -0
- package/dist/{chunk-6YJHX2DL.js → chunk-7GCMLT7J.js} +242 -22
- package/dist/chunk-7GCMLT7J.js.map +1 -0
- package/dist/chunk-A6XUJE5D.js +126 -0
- package/dist/chunk-A6XUJE5D.js.map +1 -0
- package/dist/chunk-AJA46VX5.js +393 -0
- package/dist/chunk-AJA46VX5.js.map +1 -0
- package/dist/{chunk-UWB5LMWY.js → chunk-AKUCB2OG.js} +525 -24
- package/dist/chunk-AKUCB2OG.js.map +1 -0
- package/dist/chunk-ASIQZXYO.js +277 -0
- package/dist/chunk-ASIQZXYO.js.map +1 -0
- package/dist/{chunk-DG6YMRDC.js → chunk-B2TL6GA2.js} +2 -2
- package/dist/chunk-BJMBJZ2Y.js +290 -0
- package/dist/chunk-BJMBJZ2Y.js.map +1 -0
- package/dist/chunk-BT7NVCML.js +79 -0
- package/dist/chunk-BT7NVCML.js.map +1 -0
- package/dist/chunk-CK5NTM2S.js +454 -0
- package/dist/chunk-CK5NTM2S.js.map +1 -0
- package/dist/{chunk-AYXIPSZO.js → chunk-CRU27Q4J.js} +2 -2
- package/dist/{chunk-F5VP6YCB.js → chunk-DCE6SQLA.js} +572 -155
- package/dist/chunk-DCE6SQLA.js.map +1 -0
- package/dist/{chunk-CUPFXL3J.js → chunk-DHRQHX36.js} +4 -4
- package/dist/chunk-DHRQHX36.js.map +1 -0
- package/dist/{chunk-STGWEHYR.js → chunk-DR7MCMPS.js} +981 -61
- package/dist/chunk-DR7MCMPS.js.map +1 -0
- package/dist/chunk-FP2373TW.js +149 -0
- package/dist/chunk-FP2373TW.js.map +1 -0
- package/dist/{chunk-RBBWYEFJ.js → chunk-G2WADRQ3.js} +1 -1
- package/dist/chunk-G7D6GZ5J.js +48 -0
- package/dist/chunk-G7D6GZ5J.js.map +1 -0
- package/dist/chunk-H7XKCNR6.js +60 -0
- package/dist/chunk-H7XKCNR6.js.map +1 -0
- package/dist/{chunk-3YGHKTBF.js → chunk-IM3JSE73.js} +953 -322
- package/dist/chunk-IM3JSE73.js.map +1 -0
- package/dist/chunk-IXEJRKCZ.js +18 -0
- package/dist/chunk-IXEJRKCZ.js.map +1 -0
- package/dist/chunk-IYY4MCPG.js +275 -0
- package/dist/chunk-IYY4MCPG.js.map +1 -0
- package/dist/{chunk-BECYBZLX.js → chunk-JWSENLQI.js} +502 -22
- package/dist/chunk-JWSENLQI.js.map +1 -0
- package/dist/chunk-KNKUID7G.js +183 -0
- package/dist/chunk-KNKUID7G.js.map +1 -0
- package/dist/chunk-L2IO2QPY.js +2036 -0
- package/dist/chunk-L2IO2QPY.js.map +1 -0
- package/dist/{chunk-SPI27QT6.js → chunk-L5IIGA5V.js} +9 -4
- package/dist/chunk-L5IIGA5V.js.map +1 -0
- package/dist/{chunk-XXVWLXSG.js → chunk-LVYGDT5V.js} +56 -82
- package/dist/chunk-LVYGDT5V.js.map +1 -0
- package/dist/{chunk-ZAIM4TUE.js → chunk-LW2NMHDW.js} +46 -1
- package/dist/chunk-LW2NMHDW.js.map +1 -0
- package/dist/{chunk-3OGMS3PE.js → chunk-LZRYQK6L.js} +3 -2
- package/dist/chunk-LZRYQK6L.js.map +1 -0
- package/dist/chunk-MDYG7VI7.js +48 -0
- package/dist/chunk-MDYG7VI7.js.map +1 -0
- package/dist/chunk-MXC3AP5I.js +74 -0
- package/dist/chunk-MXC3AP5I.js.map +1 -0
- package/dist/{chunk-DIXB44VE.js → chunk-N7X62G74.js} +25 -10
- package/dist/chunk-N7X62G74.js.map +1 -0
- package/dist/chunk-NN3TS5BM.js +147 -0
- package/dist/chunk-NN3TS5BM.js.map +1 -0
- package/dist/chunk-OA3L7BFR.js +183 -0
- package/dist/chunk-OA3L7BFR.js.map +1 -0
- package/dist/chunk-OZHRDTDX.js +240 -0
- package/dist/chunk-OZHRDTDX.js.map +1 -0
- package/dist/chunk-PCUKNJAZ.js +165 -0
- package/dist/chunk-PCUKNJAZ.js.map +1 -0
- package/dist/{chunk-6PFRXT4K.js → chunk-PFV5C235.js} +11 -6
- package/dist/chunk-PFV5C235.js.map +1 -0
- package/dist/chunk-PZ5AY32C.js +10 -0
- package/dist/chunk-PZ5AY32C.js.map +1 -0
- package/dist/{chunk-Y7R2XJ5Q.js → chunk-Q7FJ5ZHM.js} +6 -2
- package/dist/chunk-Q7FJ5ZHM.js.map +1 -0
- package/dist/{chunk-NBVAS5MT.js → chunk-R2L7SUX2.js} +6 -6
- package/dist/{chunk-WCLICCGB.js → chunk-RILIVK4O.js} +91 -4
- package/dist/chunk-RILIVK4O.js.map +1 -0
- package/dist/{chunk-C2EFFULQ.js → chunk-RK2Y4XOM.js} +163 -20
- package/dist/chunk-RK2Y4XOM.js.map +1 -0
- package/dist/{chunk-TP4FZJIZ.js → chunk-RULE4VG5.js} +5 -1
- package/dist/chunk-RULE4VG5.js.map +1 -0
- package/dist/{chunk-PVPWZSSI.js → chunk-SMA4IMHV.js} +19 -3
- package/dist/chunk-SMA4IMHV.js.map +1 -0
- package/dist/{chunk-L7IXWRYE.js → chunk-SS253RXF.js} +22 -13
- package/dist/chunk-SS253RXF.js.map +1 -0
- package/dist/chunk-TUFG6VXY.js +875 -0
- package/dist/chunk-TUFG6VXY.js.map +1 -0
- package/dist/chunk-TYEOAFH3.js +251 -0
- package/dist/chunk-TYEOAFH3.js.map +1 -0
- package/dist/chunk-UKJAGEXH.js +260 -0
- package/dist/chunk-UKJAGEXH.js.map +1 -0
- package/dist/{chunk-KVBLZUKV.js → chunk-USFPPRAF.js} +93 -3
- package/dist/chunk-USFPPRAF.js.map +1 -0
- package/dist/{chunk-GA5P7RST.js → chunk-VTJVUHRK.js} +22 -36
- package/dist/chunk-VTJVUHRK.js.map +1 -0
- package/dist/chunk-WIICJPET.js +45 -0
- package/dist/chunk-WIICJPET.js.map +1 -0
- package/dist/{chunk-VBVG2M5G.js → chunk-WPGJYVUH.js} +6 -2
- package/dist/chunk-WPGJYVUH.js.map +1 -0
- package/dist/{chunk-4HQS2HPX.js → chunk-WSZIHQBK.js} +29 -9
- package/dist/{chunk-4HQS2HPX.js.map → chunk-WSZIHQBK.js.map} +1 -1
- package/dist/{chunk-NZLQTHS5.js → chunk-WW3QQF4H.js} +4 -1
- package/dist/chunk-WW3QQF4H.js.map +1 -0
- package/dist/{chunk-FVA6TGI3.js → chunk-Y3WQ4ZWK.js} +42 -2
- package/dist/chunk-Y3WQ4ZWK.js.map +1 -0
- package/dist/chunk-YNJHCGDT.js +309 -0
- package/dist/chunk-YNJHCGDT.js.map +1 -0
- package/dist/{chunk-LOIMBRDE.js → chunk-ZGXSCMQN.js} +1993 -411
- package/dist/chunk-ZGXSCMQN.js.map +1 -0
- package/dist/{chunk-W6SL7OFG.js → chunk-ZTSE2ZJ6.js} +12 -2
- package/dist/{chunk-W6SL7OFG.js.map → chunk-ZTSE2ZJ6.js.map} +1 -1
- package/dist/chunking.js +1 -0
- package/dist/cipher-GVE2GQ5H.js +28 -0
- package/dist/cipher-GVE2GQ5H.js.map +1 -0
- package/dist/citations.js +1 -0
- package/dist/{cli-BkeRaYfk.d.ts → cli-x2APT9a6.d.ts} +26 -7
- package/dist/cli.d.ts +11 -6
- package/dist/cli.js +67 -33
- package/dist/codex-thread-key.js +1 -0
- package/dist/commitment-ledger.js +1 -0
- package/dist/compression-optimizer.js +1 -0
- package/dist/config.d.ts +2 -1
- package/dist/config.js +4 -1
- package/dist/connectors-cli-DFGtY2DB.d.ts +257 -0
- package/dist/connectors-cli.d.ts +2 -0
- package/dist/connectors-cli.js +22 -0
- package/dist/connectors-cli.js.map +1 -0
- package/dist/consolidation-operator.d.ts +65 -5
- package/dist/consolidation-operator.js +6 -1
- package/dist/consolidation-provenance-check.d.ts +1 -1
- package/dist/consolidation-provenance-check.js +3 -2
- package/dist/consolidation-undo.d.ts +1 -1
- package/dist/consolidation-undo.js +1 -0
- package/dist/consolidation-undo.js.map +1 -1
- package/dist/{contradiction-review-WIUBAR52.js → contradiction-review-5LTTVDQV.js} +2 -1
- package/dist/contradiction-review-5LTTVDQV.js.map +1 -0
- package/dist/{contradiction-scan-E3GJTI4F.js → contradiction-scan-3Z6YW7YA.js} +2 -1
- package/dist/{contradiction-scan-E3GJTI4F.js.map → contradiction-scan-3Z6YW7YA.js.map} +1 -1
- package/dist/cross-namespace-budget.js +1 -0
- package/dist/cue-anchors.js +1 -0
- package/dist/dashboard-runtime.js +1 -0
- package/dist/day-summary.js +1 -0
- package/dist/delinearize.js +1 -0
- package/dist/direct-answer-wiring.js +1 -0
- package/dist/direct-answer.js +1 -0
- package/dist/dreams-ledger-LR2NBAZE.js +286 -0
- package/dist/dreams-ledger-LR2NBAZE.js.map +1 -0
- package/dist/embedding-fallback.js +1 -0
- package/dist/{engine-72LSIWQP.js → engine-ICC2DSQF.js} +10 -7
- package/dist/engine-ICC2DSQF.js.map +1 -0
- package/dist/entity-retrieval.d.ts +1 -1
- package/dist/entity-retrieval.js +9 -6
- package/dist/entity-schema.js +1 -0
- package/dist/evals.js +1 -0
- package/dist/evidence-pack.d.ts +16 -0
- package/dist/evidence-pack.js +8 -0
- package/dist/evidence-pack.js.map +1 -0
- package/dist/explicit-capture.d.ts +6 -4
- package/dist/explicit-capture.js +1 -0
- package/dist/extraction-judge-telemetry.js +1 -0
- package/dist/extraction-judge-training.js +1 -0
- package/dist/extraction-judge.js +1 -0
- package/dist/extraction.js +7 -6
- package/dist/fallback-llm.js +3 -2
- package/dist/first-start-migration-4MHQEOSD.js +263 -0
- package/dist/first-start-migration-4MHQEOSD.js.map +1 -0
- package/dist/forget-PLR6J5DN.js +69 -0
- package/dist/forget-PLR6J5DN.js.map +1 -0
- package/dist/framework-CyHYDcri.d.ts +153 -0
- package/dist/fs-utils-IRVUFB6G.js +30 -0
- package/dist/fs-utils-IRVUFB6G.js.map +1 -0
- package/dist/graph-dashboard-diff.js +1 -0
- package/dist/graph-dashboard-key.js +1 -0
- package/dist/graph-dashboard-parser.js +1 -0
- package/dist/graph-edge-decay-PWB63GRE.js +207 -0
- package/dist/graph-edge-decay-PWB63GRE.js.map +1 -0
- package/dist/graph-edge-reinforcement.d.ts +81 -0
- package/dist/graph-edge-reinforcement.js +24 -0
- package/dist/graph-edge-reinforcement.js.map +1 -0
- package/dist/graph-events.d.ts +87 -0
- package/dist/graph-events.js +14 -0
- package/dist/graph-events.js.map +1 -0
- package/dist/graph-recall.js +1 -0
- package/dist/graph-retrieval.js +1 -0
- package/dist/graph-snapshot.d.ts +112 -0
- package/dist/graph-snapshot.js +19 -0
- package/dist/graph-snapshot.js.map +1 -0
- package/dist/graph.d.ts +105 -7
- package/dist/graph.js +20 -3
- package/dist/harmonic-retrieval.js +1 -0
- package/dist/himem.js +1 -0
- package/dist/hygiene.js +1 -0
- package/dist/identity-continuity.js +1 -0
- package/dist/importance.js +1 -0
- package/dist/index.d.ts +562 -13
- package/dist/index.js +329 -67
- package/dist/index.js.map +1 -1
- package/dist/intent.js +1 -0
- package/dist/json-extract.js +1 -0
- package/dist/json-store.js +1 -0
- package/dist/kdf-7S6RWKLZ.js +26 -0
- package/dist/kdf-7S6RWKLZ.js.map +1 -0
- package/dist/legacy-hook-compat.js +1 -0
- package/dist/legacy-hook-compat.js.map +1 -1
- package/dist/lifecycle.js +1 -0
- package/dist/live-connectors-runner.d.ts +48 -0
- package/dist/live-connectors-runner.js +17 -0
- package/dist/live-connectors-runner.js.map +1 -0
- package/dist/local-llm.js +1 -0
- package/dist/logger.js +1 -0
- package/dist/memory-action-policy.js +1 -0
- package/dist/memory-cache.d.ts +2 -1
- package/dist/memory-cache.js +4 -1
- package/dist/memory-governance-KG52RITE.js +37 -0
- package/dist/memory-governance-KG52RITE.js.map +1 -0
- package/dist/memory-lifecycle-ledger-utils.d.ts +2 -1
- package/dist/memory-lifecycle-ledger-utils.js +4 -1
- package/dist/memory-projection-format.js +1 -0
- package/dist/{memory-projection-store-DeSXPh1j.d.ts → memory-projection-store-D3vBHS4J.d.ts} +1 -0
- package/dist/memory-projection-store.d.ts +1 -1
- package/dist/memory-projection-store.js +1 -0
- package/dist/memory-worth-bench.js +1 -0
- package/dist/memory-worth-bench.js.map +1 -1
- package/dist/memory-worth-filter.js +1 -0
- package/dist/memory-worth-outcomes.d.ts +1 -1
- package/dist/memory-worth-outcomes.js +1 -0
- package/dist/memory-worth.js +1 -0
- package/dist/metadata-FC3XPDRQ.js +21 -0
- package/dist/metadata-FC3XPDRQ.js.map +1 -0
- package/dist/migrate-from-identity-anchor-TTEDEJGX.js +8 -0
- package/dist/migrate-from-identity-anchor-TTEDEJGX.js.map +1 -0
- package/dist/model-registry.js +1 -0
- package/dist/models-json.js +1 -0
- package/dist/native-knowledge.js +1 -0
- package/dist/negative.js +1 -0
- package/dist/objective-state-writers.js +1 -0
- package/dist/objective-state-writers.js.map +1 -1
- package/dist/objective-state.js +1 -0
- package/dist/openai-chat-compat.js +1 -0
- package/dist/operator-toolkit.d.ts +46 -2
- package/dist/operator-toolkit.js +28 -16
- package/dist/opik-exporter.js +1 -0
- package/dist/opik-exporter.js.map +1 -1
- package/dist/{orchestrator-CmJ-NTdJ.d.ts → orchestrator-ChkesB8U.d.ts} +177 -13
- package/dist/orchestrator.d.ts +6 -4
- package/dist/orchestrator.js +55 -39
- package/dist/page-versioning.js +1 -0
- package/dist/path-RMTY5Y5A.js +9 -0
- package/dist/path-RMTY5Y5A.js.map +1 -0
- package/dist/patterns-cli.d.ts +160 -0
- package/dist/patterns-cli.js +29 -0
- package/dist/patterns-cli.js.map +1 -0
- package/dist/peers-6OSQ3NK6.js +44 -0
- package/dist/peers-6OSQ3NK6.js.map +1 -0
- package/dist/plugin-id.js +1 -0
- package/dist/policy-runtime.js +1 -0
- package/dist/{port-BADbLZU5.d.ts → port-hqGnoStS.d.ts} +6 -0
- package/dist/profiling.js +1 -0
- package/dist/purge-6ATBGT77.js +205 -0
- package/dist/purge-6ATBGT77.js.map +1 -0
- package/dist/qmd-recall-cache.d.ts +1 -1
- package/dist/qmd-recall-cache.js +1 -0
- package/dist/qmd.d.ts +2 -1
- package/dist/qmd.js +4 -3
- package/dist/reasoning-trace-recall.js +1 -0
- package/dist/reasoning-trace-types.js +1 -0
- package/dist/recall-audit-anomaly.js +1 -0
- package/dist/recall-audit.js +1 -0
- package/dist/recall-disclosure-escalation.d.ts +84 -0
- package/dist/recall-disclosure-escalation.js +14 -0
- package/dist/recall-disclosure-escalation.js.map +1 -0
- package/dist/recall-explain-renderer.js +4 -1
- package/dist/recall-mmr.js +1 -0
- package/dist/recall-qos.js +1 -0
- package/dist/recall-query-policy.js +1 -0
- package/dist/recall-state.d.ts +7 -0
- package/dist/recall-state.js +2 -1
- package/dist/recall-tag-filter.d.ts +56 -0
- package/dist/recall-tag-filter.js +14 -0
- package/dist/recall-tag-filter.js.map +1 -0
- package/dist/recall-tokenization.js +1 -0
- package/dist/recall-xray-cli.d.ts +9 -2
- package/dist/recall-xray-cli.js +9 -4
- package/dist/recall-xray-renderer.js +4 -1
- package/dist/recall-xray.d.ts +116 -2
- package/dist/recall-xray.js +9 -3
- package/dist/reconstruct.js +1 -0
- package/dist/release-changelog.js +2 -0
- package/dist/release-changelog.js.map +1 -1
- package/dist/relevance.js +1 -0
- package/dist/rerank.js +1 -0
- package/dist/{resolution-QBTDHTG7.js → resolution-YGIBORXI.js} +2 -1
- package/dist/{resolution-QBTDHTG7.js.map → resolution-YGIBORXI.js.map} +1 -1
- package/dist/resolve-auth-token.d.ts +51 -0
- package/dist/resolve-auth-token.js +12 -0
- package/dist/resolve-auth-token.js.map +1 -0
- package/dist/resolve-provider-secret.d.ts +9 -1
- package/dist/resolve-provider-secret.js +4 -1
- package/dist/resume-bundles.js +4 -3
- package/dist/retrieval-agents.d.ts +1 -1
- package/dist/retrieval-agents.js +1 -0
- package/dist/retrieval-tiers.js +1 -0
- package/dist/retrieval.js +1 -0
- package/dist/sanitize.js +1 -0
- package/dist/schemas.d.ts +37 -24
- package/dist/schemas.js +2 -1
- package/dist/sdk-compat.js +1 -0
- package/dist/sdk-compat.js.map +1 -1
- package/dist/secure-store-4R2GSO7S.js +156 -0
- package/dist/secure-store-4R2GSO7S.js.map +1 -0
- package/dist/semantic-chunking.js +1 -0
- package/dist/{semantic-consolidation-CxJU6MJk.d.ts → semantic-consolidation-ByBXb-sf.d.ts} +3 -3
- package/dist/semantic-consolidation.d.ts +2 -2
- package/dist/semantic-consolidation.js +11 -6
- package/dist/semantic-rule-promotion.d.ts +1 -1
- package/dist/semantic-rule-promotion.js +9 -6
- package/dist/semantic-rule-verifier.d.ts +1 -1
- package/dist/semantic-rule-verifier.js +9 -6
- package/dist/session-integrity.js +1 -0
- package/dist/session-observer-bands.js +1 -0
- package/dist/session-observer-state.js +1 -0
- package/dist/session-toggles.js +2 -0
- package/dist/session-toggles.js.map +1 -1
- package/dist/signal.js +1 -0
- package/dist/skills-registry.js +2 -0
- package/dist/skills-registry.js.map +1 -1
- package/dist/source-attribution.js +1 -0
- package/dist/state-NCHQ4TRG.js +8 -0
- package/dist/state-NCHQ4TRG.js.map +1 -0
- package/dist/state-store-3EH7HYIN.js +16 -0
- package/dist/state-store-3EH7HYIN.js.map +1 -0
- package/dist/storage.d.ts +76 -2
- package/dist/storage.js +8 -5
- package/dist/store-contract.js +1 -0
- package/dist/summarizer.js +5 -4
- package/dist/summary-snapshot.js +1 -0
- package/dist/temporal-index.js +1 -0
- package/dist/temporal-supersession.d.ts +1 -1
- package/dist/temporal-supersession.js +2 -1
- package/dist/temporal-validity.d.ts +52 -0
- package/dist/temporal-validity.js +14 -0
- package/dist/temporal-validity.js.map +1 -0
- package/dist/threading.js +1 -0
- package/dist/tier-migration.d.ts +2 -2
- package/dist/tier-migration.js +1 -0
- package/dist/tier-routing.js +1 -0
- package/dist/tier-stats-62ZVDFKS.js +152 -0
- package/dist/tier-stats-62ZVDFKS.js.map +1 -0
- package/dist/tmt.js +1 -0
- package/dist/tokens.js +1 -0
- package/dist/topics.js +1 -0
- package/dist/trace-C5ETWBEF.js +290 -0
- package/dist/trace-C5ETWBEF.js.map +1 -0
- package/dist/transcript.js +1 -0
- package/dist/trust-zones.js +1 -0
- package/dist/tui-RI7P6PBS.js +13 -0
- package/dist/tui-RI7P6PBS.js.map +1 -0
- package/dist/types-V3FJ26TF.js +30 -0
- package/dist/types-V3FJ26TF.js.map +1 -0
- package/dist/types.d.ts +634 -9
- package/dist/types.js +10 -3
- package/dist/utility-learner.js +1 -0
- package/dist/utility-runtime.js +1 -0
- package/dist/utility-telemetry.js +1 -0
- package/dist/verified-recall.js +9 -6
- package/dist/version-utils.js +1 -0
- package/dist/whitespace.js +1 -0
- package/dist/work-product-ledger.js +1 -0
- package/package.json +2 -1
- package/dist/access-service-Br8ZydTK.d.ts +0 -827
- package/dist/chunk-3OGMS3PE.js.map +0 -1
- package/dist/chunk-3YGHKTBF.js.map +0 -1
- package/dist/chunk-6PFRXT4K.js.map +0 -1
- package/dist/chunk-6YJHX2DL.js.map +0 -1
- package/dist/chunk-BECYBZLX.js.map +0 -1
- package/dist/chunk-C2EFFULQ.js.map +0 -1
- package/dist/chunk-CUPFXL3J.js.map +0 -1
- package/dist/chunk-DIXB44VE.js.map +0 -1
- package/dist/chunk-F5VP6YCB.js.map +0 -1
- package/dist/chunk-FVA6TGI3.js.map +0 -1
- package/dist/chunk-GA5P7RST.js.map +0 -1
- package/dist/chunk-KVBLZUKV.js.map +0 -1
- package/dist/chunk-L7IXWRYE.js.map +0 -1
- package/dist/chunk-LOIMBRDE.js.map +0 -1
- package/dist/chunk-LTCGGW2D.js +0 -14
- package/dist/chunk-LTCGGW2D.js.map +0 -1
- package/dist/chunk-NZLQTHS5.js.map +0 -1
- package/dist/chunk-OC5OXUQ4.js.map +0 -1
- package/dist/chunk-PVPWZSSI.js.map +0 -1
- package/dist/chunk-SPI27QT6.js.map +0 -1
- package/dist/chunk-STGWEHYR.js.map +0 -1
- package/dist/chunk-TP4FZJIZ.js.map +0 -1
- package/dist/chunk-ULYOGL6R.js.map +0 -1
- package/dist/chunk-UWB5LMWY.js.map +0 -1
- package/dist/chunk-VBVG2M5G.js.map +0 -1
- package/dist/chunk-VDX363PS.js.map +0 -1
- package/dist/chunk-WCLICCGB.js.map +0 -1
- package/dist/chunk-X6GF3FX2.js +0 -26
- package/dist/chunk-X6GF3FX2.js.map +0 -1
- package/dist/chunk-XXVWLXSG.js.map +0 -1
- package/dist/chunk-Y7R2XJ5Q.js.map +0 -1
- package/dist/chunk-ZAIM4TUE.js.map +0 -1
- /package/dist/{contradiction-review-WIUBAR52.js.map → capsule-cli.js.map} +0 -0
- /package/dist/{engine-72LSIWQP.js.map → capsule-crypto-5CYAGVC5.js.map} +0 -0
- /package/dist/{chunk-TMYO7B5P.js.map → chunk-47WOM4YW.js.map} +0 -0
- /package/dist/{chunk-DG6YMRDC.js.map → chunk-B2TL6GA2.js.map} +0 -0
- /package/dist/{chunk-AYXIPSZO.js.map → chunk-CRU27Q4J.js.map} +0 -0
- /package/dist/{chunk-RBBWYEFJ.js.map → chunk-G2WADRQ3.js.map} +0 -0
- /package/dist/{chunk-NBVAS5MT.js.map → chunk-R2L7SUX2.js.map} +0 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/connectors/codex-materialize-runner.ts","../src/connectors/codex-materialize.ts","../src/semantic-consolidation.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 path from \"node:path\";\nimport { existsSync } from \"node:fs\";\n\nimport { log } from \"../logger.js\";\nimport { StorageManager } from \"../storage.js\";\nimport type { PluginConfig, MemoryFile } from \"../types.js\";\nimport {\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 // session-end.sh passes reason=\"session_end\"; when the user has turned off the\n // session-end trigger 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 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 const nsDir = resolveNamespaceDir(memoryDir, namespace, cfg);\n const storage = new StorageManager(nsDir);\n try {\n memories = await storage.readAllMemories();\n } catch (error) {\n log.warn(\n `[codex-materialize] skipped — failed to read memories from ${nsDir}: ${\n error instanceof Error ? error.message : String(error)\n }`,\n );\n return null;\n }\n }\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: options.codexHome,\n maxSummaryTokens: cfg.codexMaterializeMaxSummaryTokens,\n rolloutRetentionDays: cfg.codexMaterializeRolloutRetentionDays,\n rolloutSummaries: options.rolloutSummaries,\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 if (requested.length === 0 || requested === \"auto\") {\n // When the caller asks for \"auto\", fall back to the configured default\n // namespace (if any) so storage-root resolution lines up with what\n // NamespaceStorageRouter would do for the same install.\n return cfg.defaultNamespace && cfg.defaultNamespace.length > 0\n ? cfg.defaultNamespace\n : \"default\";\n }\n return requested;\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 ns = namespace || cfg.defaultNamespace || \"default\";\n const namespacedRoot = path.join(memoryDir, \"namespaces\", ns);\n\n if (ns === cfg.defaultNamespace) {\n return existsSync(namespacedRoot) ? namespacedRoot : memoryDir;\n }\n return namespacedRoot;\n}\n","/**\n * codex-materialize.ts — Codex CLI native memory artifact materialization (#378)\n *\n * Periodically writes Remnic memories into the file layout that Codex CLI's\n * phase-2 consolidation reads directly under `<codex_home>/memories/`:\n *\n * memory_summary.md — always-loaded at session start (tight budget)\n * MEMORY.md — searchable handbook (task-group schema)\n * raw_memories.md — mechanical merge of raw memories, latest first\n * rollout_summaries/<slug>.md — per-session recaps\n *\n * Codex's own read path is agnostic to which producer wrote these files — it\n * tags reads by `memory_md` / `memory_summary` / `raw_memories` /\n * `rollout_summaries` / `skills`. By materializing Remnic content into this\n * exact layout we let Codex pick up Remnic memories without a single MCP call.\n *\n * Safety invariants\n * ─────────────────\n * - **Atomic writes.** Every file is rendered under `.remnic-tmp/` and then\n * `rename()`d into place so Codex never observes a half-written file.\n * - **Sentinel-based opt-in.** If `<codex_home>/memories/.remnic-managed` is\n * missing, we SKIP materialization entirely and log a warning. This honors\n * user hand-edits to the directory — a user who manually curated their\n * Codex memory layout will never have those edits overwritten.\n * - **Schema validation.** `MEMORY.md` content is validated against the\n * task-group schema before write. Invalid content throws and nothing is\n * written.\n * - **Idempotent no-ops.** A content hash is written into the sentinel. If\n * the re-rendered hash matches the previous run, we skip writes entirely.\n * - **Token budget.** `memory_summary.md` is truncated to fit under the\n * configured token budget (whitespace-tokenized approximation), leaving\n * headroom under Codex's 5000-token summary cap.\n *\n * Privacy\n * ───────\n * This module does not persist any user content outside `<codex_home>/memories`\n * — it only mirrors the memories that Remnic already wrote. It does not log\n * memory content to stdout; it logs file names, counts, and hashes.\n */\n\nimport {\n createHash,\n} from \"node:crypto\";\nimport {\n existsSync,\n mkdirSync,\n readdirSync,\n readFileSync,\n renameSync,\n rmSync,\n statSync,\n unlinkSync,\n writeFileSync,\n} from \"node:fs\";\nimport path from \"node:path\";\n\nimport { log } from \"../logger.js\";\nimport { readEnvVar, resolveHomeDir } from \"../runtime/env.js\";\nimport type { MemoryFile } from \"../types.js\";\n\n// ─── Types ─────────────────────────────────────────────────────────────────\n\n/**\n * Input for {@link materializeForNamespace}. Prefer passing pre-loaded\n * `memories` so this module stays I/O-agnostic and trivially testable.\n */\nexport interface MaterializeOptions {\n /** Pre-loaded Remnic memories for this namespace (required). */\n memories: MemoryFile[];\n /** Override `<codex_home>`. Defaults to `$CODEX_HOME` or `~/.codex`. */\n codexHome?: string;\n /** Maximum whitespace-tokenized size of memory_summary.md. Default 4500. */\n maxSummaryTokens?: number;\n /** Maximum age of rollout_summaries/*.md in days. Default 30. */\n rolloutRetentionDays?: number;\n /** Per-session rollout summaries to render. */\n rolloutSummaries?: RolloutSummaryInput[];\n /** Current time, injected for deterministic tests. */\n now?: Date;\n /** Optional logger override for tests. */\n logger?: { info: (msg: string) => void; warn: (msg: string) => void; debug?: (msg: string) => void };\n}\n\n/** Input describing one Codex rollout summary file. */\nexport interface RolloutSummaryInput {\n /** Stable slug for the file (becomes `<slug>.md`). */\n slug: string;\n /** Working directory used during the rollout. */\n cwd?: string;\n /** Path to the raw Codex rollout log, if known. */\n rolloutPath?: string;\n /** ISO-8601 timestamp of the last update. */\n updatedAt?: string;\n /** Opaque thread / session id. */\n threadId?: string;\n /** Markdown body for the recap. */\n body: string;\n /** Freeform keywords / search hints. */\n keywords?: string[];\n}\n\n/** Result of a materialization run. */\nexport interface MaterializeResult {\n /** Namespace that was materialized. */\n namespace: string;\n /** `<codex_home>/memories` path this run targeted. */\n memoriesDir: string;\n /** Was anything actually written (vs. skipped / idempotent no-op)? */\n wrote: boolean;\n /** True if the sentinel was missing and we skipped with a warning. */\n skippedNoSentinel: boolean;\n /** True if the hash matched the previous run and we short-circuited. */\n skippedIdempotent: boolean;\n /** Files that were written this run (relative to `memoriesDir`). */\n filesWritten: string[];\n /** Content hash computed for this run. */\n contentHash: string;\n}\n\n/** On-disk shape of the `.remnic-managed` sentinel. */\ninterface SentinelFile {\n version: number;\n namespace: string;\n updated_at: string;\n content_hash: string;\n}\n\n// ─── Constants ─────────────────────────────────────────────────────────────\n\n/** Bump when the on-disk layout or semantics change. */\nexport const MATERIALIZE_VERSION = 1;\n\n/** Sentinel file name at the root of the materialized memories dir. */\nexport const SENTINEL_FILE = \".remnic-managed\";\n\n/** Scratch directory used for atomic renames. */\nexport const TMP_DIR = \".remnic-tmp\";\n\n/** File names we own. Anything else in the directory is considered user-managed. */\nconst OWNED_FILES = new Set<string>([\n \"memory_summary.md\",\n \"MEMORY.md\",\n \"raw_memories.md\",\n]);\n\n/** Sub-directory for per-session rollout recaps. */\nconst ROLLOUT_SUBDIR = \"rollout_summaries\";\n\n// ─── Public entry points ───────────────────────────────────────────────────\n\n/**\n * Materialize a Remnic namespace into Codex's native memory layout.\n *\n * Returns a {@link MaterializeResult} describing what happened. Callers\n * should treat \"skipped\" as success — the sentinel / idempotent cases are\n * expected and intentional.\n *\n * @throws if `MEMORY.md` fails schema validation (we do not write garbage).\n */\nexport function materializeForNamespace(\n namespace: string,\n options: MaterializeOptions,\n): MaterializeResult {\n const logger = options.logger ?? {\n info: (msg) => log.info(`[codex-materialize] ${msg}`),\n warn: (msg) => log.warn(`[codex-materialize] ${msg}`),\n debug: (msg) => log.debug(`[codex-materialize] ${msg}`),\n };\n const codexHome = resolveCodexHome(options.codexHome);\n const memoriesDir = path.join(codexHome, \"memories\");\n const now = options.now ?? new Date();\n // Honor `0` as \"no summary budget\" — parseConfig already clamps to non-\n // negative integers, so any provided number is meaningful. Only fall back\n // to the default when the caller did not provide the option at all.\n const maxSummaryTokens =\n typeof options.maxSummaryTokens === \"number\" && options.maxSummaryTokens >= 0\n ? options.maxSummaryTokens\n : 4500;\n const rolloutRetentionDays =\n typeof options.rolloutRetentionDays === \"number\" && options.rolloutRetentionDays >= 0\n ? options.rolloutRetentionDays\n : 30;\n\n // ── Sentinel check ─────────────────────────────────────────────────────\n // We deliberately do NOT `mkdirSync(memoriesDir)` before reading the\n // sentinel: creating `~/.codex/memories/` for every user (including ones\n // who never use Codex) would make Remnic's post-consolidation hook leave\n // empty opt-in directories behind on disk. Instead we only check whether\n // the sentinel already exists — if the parent dir doesn't exist, the\n // sentinel can't exist either and we fall straight through to the skip\n // path without touching the filesystem. The `mkdirSync` for `memoriesDir`\n // happens later, only once we know we're actually going to write.\n const sentinelPath = path.join(memoriesDir, SENTINEL_FILE);\n const existingSentinel = readSentinel(sentinelPath);\n if (!existingSentinel) {\n // Log at `debug` when the entire memories dir doesn't exist — that's\n // the common \"user never opted in\" case and should not be noisy.\n // Keep the `warn` level only when the dir exists but lacks a sentinel,\n // which is the \"user hand-curated layout, don't overwrite\" case that\n // genuinely warrants attention.\n if (existsSync(memoriesDir)) {\n logger.warn(\n `sentinel ${SENTINEL_FILE} missing in ${memoriesDir}; skipping materialization to preserve hand-edits`,\n );\n } else {\n logger.debug?.(\n `skipping materialization — ${memoriesDir} does not exist (user not opted in)`,\n );\n }\n return {\n namespace,\n memoriesDir,\n wrote: false,\n skippedNoSentinel: true,\n skippedIdempotent: false,\n filesWritten: [],\n contentHash: \"\",\n };\n }\n\n // Now that we know the user has opted in (sentinel exists), it's safe to\n // ensure the memories dir is present. In practice this is almost always a\n // no-op because the sentinel read above already succeeded, but a defensive\n // mkdirSync protects against a race where the dir was removed between the\n // sentinel read and the first write.\n mkdirSync(memoriesDir, { recursive: true });\n\n // ── Render ─────────────────────────────────────────────────────────────\n const memories = [...options.memories];\n // Track whether the caller actually supplied a rollout set. `undefined`\n // means \"don't touch rollout_summaries/\"; an empty array is still\n // authoritative and means \"we own this dir and it should be empty\".\n const rolloutsSupplied = options.rolloutSummaries !== undefined;\n const rolloutSummaries = options.rolloutSummaries ?? [];\n\n // Prune-before-render: MEMORY.md and memory_summary.md both embed rollout\n // filenames in their body, so they must only ever see the *retained* set.\n // Running pruneRollouts after the renderers (as an earlier revision did)\n // caused MEMORY.md to list `rollout_summaries/<slug>.md` paths for rollouts\n // that were then pruned and never written — a broken link pointing at a\n // ghost file. See review feedback on PR #392.\n const retainedRollouts = pruneRollouts(rolloutSummaries, rolloutRetentionDays, now);\n\n // Deduplicate on sanitized filename. Two different slugs (\"Session 1\" and\n // \"session!!!1\") can sanitize to the same output (\"session-1\"), which would\n // otherwise make the first entry's tmp file get overwritten and cause the\n // later rename step to crash with ENOENT. For each collision slot we keep\n // the entry with the newest `updatedAt` so an unsorted input (or a caller\n // that accidentally appends older recaps after newer ones) can't have an\n // older recap clobber a newer one.\n // We do this at the retained-input level (not just at the written-file\n // level) so MEMORY.md's \"rollout_summary_files\" section lists each slot\n // exactly once and matches what actually gets written to disk.\n const dedupedRollouts: RolloutSummaryInput[] = [];\n const seenNames = new Map<string, number>();\n const parseTs = (value: string | undefined): number => {\n if (!value) return Number.NEGATIVE_INFINITY;\n const parsed = Date.parse(value);\n return Number.isFinite(parsed) ? parsed : Number.NEGATIVE_INFINITY;\n };\n for (const r of retainedRollouts) {\n const name = `${sanitizeSlug(r.slug)}.md`;\n const existingIdx = seenNames.get(name);\n if (existingIdx === undefined) {\n seenNames.set(name, dedupedRollouts.length);\n dedupedRollouts.push(r);\n continue;\n }\n // Newest-wins: only replace the existing entry if the incoming one has a\n // strictly newer timestamp. Ties keep the earlier entry (stable for\n // unsorted inputs) because overwriting on ties would flip rendering output\n // for benign call-order changes.\n const existing = dedupedRollouts[existingIdx];\n if (parseTs(r.updatedAt) > parseTs(existing.updatedAt)) {\n dedupedRollouts[existingIdx] = r;\n }\n }\n\n const memorySummary = renderMemorySummary({\n namespace,\n memories,\n rolloutSummaries: dedupedRollouts,\n maxTokens: maxSummaryTokens,\n });\n\n const memoryMd = renderMemoryMd({\n namespace,\n memories,\n rolloutSummaries: dedupedRollouts,\n });\n\n // Fail fast on schema issues — do not write garbage.\n const validation = validateMemoryMd(memoryMd);\n if (!validation.valid) {\n const reason = validation.errors.join(\"; \");\n logger.warn(`MEMORY.md failed schema validation: ${reason}`);\n throw new Error(`codex-materialize: MEMORY.md schema validation failed: ${reason}`);\n }\n\n const rawMemories = renderRawMemories({ memories });\n\n const rolloutFiles = dedupedRollouts.map((r) => ({\n name: `${sanitizeSlug(r.slug)}.md`,\n body: renderRolloutSummary(r),\n }));\n\n // ── Idempotence check ──────────────────────────────────────────────────\n const hash = computeContentHash({\n namespace,\n memorySummary,\n memoryMd,\n rawMemories,\n rolloutFiles,\n });\n\n if (existingSentinel.content_hash === hash) {\n // Idempotence early-return is only safe when the managed files we would\n // have written are still on disk. If a user or external process deleted\n // `MEMORY.md` / `memory_summary.md` / `raw_memories.md` (or any retained\n // rollout file) while the sentinel's hash stayed the same, we must fall\n // through and rewrite — otherwise Codex would be stuck with missing\n // artifacts until a memory-content change happens to flip the hash.\n const requiredFiles = [\n path.join(memoriesDir, \"memory_summary.md\"),\n path.join(memoriesDir, \"MEMORY.md\"),\n path.join(memoriesDir, \"raw_memories.md\"),\n ...rolloutFiles.map((r) => path.join(memoriesDir, ROLLOUT_SUBDIR, r.name)),\n ];\n const allPresent = requiredFiles.every((f) => existsSync(f));\n if (allPresent) {\n logger.debug?.(`no-op materialization for namespace=${namespace} (hash unchanged)`);\n return {\n namespace,\n memoriesDir,\n wrote: false,\n skippedNoSentinel: false,\n skippedIdempotent: true,\n filesWritten: [],\n contentHash: hash,\n };\n }\n logger.debug?.(\n `hash unchanged for namespace=${namespace} but managed file missing — forcing rewrite`,\n );\n }\n\n // ── Atomic writes ──────────────────────────────────────────────────────\n // Use a unique, per-run staging sub-directory so two overlapping runs\n // (e.g. a session-end trigger overlapping with a consolidation post-hook)\n // can't stomp each other's tmp files mid-rename. The old \"fixed TMP_DIR,\n // wipe-on-entry\" layout meant run B would delete run A's staging area out\n // from under it, causing ENOENT on A's rename loop. Per-run uniqueness\n // turns the shared dir into an insulated workspace. See review feedback on\n // PR #392.\n const runTag = `${process.pid}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;\n const tmpDir = path.join(memoriesDir, `${TMP_DIR}-${runTag}`);\n // Opportunistic GC for stale scratch dirs left behind by a previous\n // crashed run. We only remove entries whose mtime is older than the\n // stale-threshold below — that way we never delete another in-flight\n // run's staging area out from under it. The threshold is deliberately\n // generous (1h) because a healthy materialize completes in milliseconds\n // and there's no legitimate reason for a live staging dir to be older.\n //\n // NB: we compare against `Date.now()` (wall-clock), not against the\n // injected `options.now`. Tests and deterministic replays commonly\n // inject a non-current timestamp, but file mtimes on disk are always\n // wall-clock, so mixing the two would either false-positive delete\n // fresh dirs (test-time in the past) or false-negative skip stale ones\n // (test-time in the future).\n const TMP_STALE_MS = 60 * 60 * 1000;\n const wallClockMs = Date.now();\n try {\n for (const entry of readdirSync(memoriesDir, { withFileTypes: true })) {\n if (!entry.isDirectory()) continue;\n if (!entry.name.startsWith(TMP_DIR)) continue;\n const stalePath = path.join(memoriesDir, entry.name);\n try {\n const stat = statSync(stalePath);\n if (wallClockMs - stat.mtimeMs < TMP_STALE_MS) continue;\n rmSync(stalePath, { recursive: true, force: true });\n } catch {\n // ignore — another concurrent run may own it, or we lack perms\n }\n }\n } catch {\n // ignore — dir may not exist yet\n }\n mkdirSync(tmpDir, { recursive: true });\n mkdirSync(path.join(tmpDir, ROLLOUT_SUBDIR), { recursive: true });\n\n const filesWritten: string[] = [];\n\n writeFileSync(path.join(tmpDir, \"memory_summary.md\"), memorySummary);\n filesWritten.push(\"memory_summary.md\");\n\n writeFileSync(path.join(tmpDir, \"MEMORY.md\"), memoryMd);\n filesWritten.push(\"MEMORY.md\");\n\n writeFileSync(path.join(tmpDir, \"raw_memories.md\"), rawMemories);\n filesWritten.push(\"raw_memories.md\");\n\n for (const rollout of rolloutFiles) {\n writeFileSync(path.join(tmpDir, ROLLOUT_SUBDIR, rollout.name), rollout.body);\n filesWritten.push(path.join(ROLLOUT_SUBDIR, rollout.name));\n }\n\n // Rename into place. Atomic per-file is sufficient — Codex reads each file\n // independently and tolerates an inconsistent in-between snapshot across\n // files for the duration of the rename loop (milliseconds).\n for (const rel of [\"memory_summary.md\", \"MEMORY.md\", \"raw_memories.md\"]) {\n const src = path.join(tmpDir, rel);\n const dest = path.join(memoriesDir, rel);\n renameSync(src, dest);\n }\n\n const destRolloutsDir = path.join(memoriesDir, ROLLOUT_SUBDIR);\n mkdirSync(destRolloutsDir, { recursive: true });\n // Only garbage-collect rollout files when the caller actually supplied a\n // `rolloutSummaries` array — otherwise we'd wipe legitimately\n // user/Codex-created recap files on every session-end run, since those\n // calls typically omit the rollout set entirely. When rollouts were\n // supplied (even as an empty array — meaning \"we own this dir and it\n // should be empty\"), we clear the stale files we previously owned.\n if (rolloutsSupplied) {\n const retainedRolloutNames = new Set(rolloutFiles.map((r) => r.name));\n try {\n for (const entry of readdirSync(destRolloutsDir, { withFileTypes: true })) {\n if (!entry.isFile()) continue;\n if (!entry.name.endsWith(\".md\")) continue;\n if (retainedRolloutNames.has(entry.name)) continue;\n try {\n unlinkSync(path.join(destRolloutsDir, entry.name));\n } catch {\n // ignore\n }\n }\n } catch {\n // ignore — directory may not exist yet\n }\n }\n\n for (const rollout of rolloutFiles) {\n const src = path.join(tmpDir, ROLLOUT_SUBDIR, rollout.name);\n const dest = path.join(destRolloutsDir, rollout.name);\n renameSync(src, dest);\n }\n\n // Update sentinel last so a crash leaves hash mismatched → next run rewrites.\n const sentinel: SentinelFile = {\n version: MATERIALIZE_VERSION,\n namespace,\n updated_at: now.toISOString(),\n content_hash: hash,\n };\n writeFileSync(sentinelPath, `${JSON.stringify(sentinel, null, 2)}\\n`);\n\n try {\n rmSync(tmpDir, { recursive: true, force: true });\n } catch {\n // ignore\n }\n\n logger.info(\n `materialized namespace=${namespace} files=${filesWritten.length} hash=${hash.slice(0, 12)}`,\n );\n\n return {\n namespace,\n memoriesDir,\n wrote: true,\n skippedNoSentinel: false,\n skippedIdempotent: false,\n filesWritten,\n contentHash: hash,\n };\n}\n\n/**\n * Create (or refresh) the `.remnic-managed` sentinel. Callers must do this\n * explicitly the first time they want Remnic to start managing a directory —\n * we never write it implicitly, because its presence is the user's opt-in.\n */\nexport function ensureSentinel(memoriesDir: string, namespace: string, now: Date = new Date()): void {\n mkdirSync(memoriesDir, { recursive: true });\n const sentinelPath = path.join(memoriesDir, SENTINEL_FILE);\n if (existsSync(sentinelPath)) return;\n const sentinel: SentinelFile = {\n version: MATERIALIZE_VERSION,\n namespace,\n updated_at: now.toISOString(),\n content_hash: \"\",\n };\n writeFileSync(sentinelPath, `${JSON.stringify(sentinel, null, 2)}\\n`);\n}\n\n// ─── Rendering ─────────────────────────────────────────────────────────────\n\ninterface RenderContext {\n namespace: string;\n memories: MemoryFile[];\n rolloutSummaries: RolloutSummaryInput[];\n // Historically this interface exposed a `now: Date` field, but neither\n // `renderMemoryMd` nor `renderMemorySummary` ever read it (rendered output\n // is purely a function of `namespace`, `memories`, and `rolloutSummaries`).\n // The field was flagged as dead weight in PR #392 review and removed.\n // If a future renderer needs a timestamp, re-add it here and update both\n // call sites and the schema test.\n}\n\ninterface SummaryRenderContext extends RenderContext {\n maxTokens: number;\n}\n\n/**\n * Render `memory_summary.md` — the always-loaded file.\n * Budget-capped at `maxTokens` whitespace tokens.\n */\nexport function renderMemorySummary(ctx: SummaryRenderContext): string {\n const lines: string[] = [];\n lines.push(\"# Memory Summary\");\n lines.push(\"\");\n lines.push(`_namespace: ${ctx.namespace}_`);\n lines.push(`_source: remnic_`);\n lines.push(\"\");\n\n const highValue = selectSummaryMemories(ctx.memories, 12);\n if (highValue.length > 0) {\n lines.push(\"## Top memories\");\n lines.push(\"\");\n for (const mem of highValue) {\n lines.push(`- ${oneLineSummary(mem)}`);\n }\n lines.push(\"\");\n }\n\n if (ctx.rolloutSummaries.length > 0) {\n lines.push(\"## Recent rollouts\");\n lines.push(\"\");\n const sorted = [...ctx.rolloutSummaries]\n .sort((a, b) => (b.updatedAt ?? \"\").localeCompare(a.updatedAt ?? \"\"))\n .slice(0, 5);\n for (const r of sorted) {\n const when = r.updatedAt ? ` (${r.updatedAt})` : \"\";\n lines.push(`- ${r.slug}${when}`);\n }\n lines.push(\"\");\n }\n\n const full = lines.join(\"\\n\").replace(/\\n+$/u, \"\\n\");\n return truncateToTokenBudget(full, ctx.maxTokens);\n}\n\n/**\n * Render `MEMORY.md` — the searchable handbook in Codex's task-group schema.\n */\nexport function renderMemoryMd(ctx: RenderContext): string {\n const lines: string[] = [];\n lines.push(`# Task Group: ${ctx.namespace}`);\n lines.push(`scope: ${ctx.namespace}`);\n lines.push(`applies_to: cwd=*; reuse_rule=namespace-match`);\n lines.push(\"\");\n\n // One \"task\" per top-level topic cluster. For the first cut we group by\n // memory category so the schema validator always sees at least one task.\n const byCategory = groupMemoriesByCategory(ctx.memories);\n let taskIndex = 1;\n if (byCategory.size === 0) {\n lines.push(`## Task ${taskIndex}: baseline — no memories yet`);\n lines.push(\"\");\n lines.push(\"### rollout_summary_files\");\n for (const r of ctx.rolloutSummaries) {\n lines.push(\n `- rollout_summaries/${sanitizeSlug(r.slug)}.md (cwd=${r.cwd ?? \"*\"}, rollout_path=${r.rolloutPath ?? \"\"}, updated_at=${r.updatedAt ?? \"\"}, thread_id=${r.threadId ?? \"\"})`,\n );\n }\n if (ctx.rolloutSummaries.length === 0) {\n lines.push(\"- (none)\");\n }\n lines.push(\"\");\n lines.push(\"### keywords\");\n lines.push(`- ${ctx.namespace}`);\n lines.push(\"\");\n taskIndex += 1;\n } else {\n for (const [category, mems] of byCategory) {\n lines.push(`## Task ${taskIndex}: ${category} memories, outcome=surface-to-codex`);\n lines.push(\"\");\n lines.push(\"### rollout_summary_files\");\n const relevantRollouts = ctx.rolloutSummaries.slice(0, 5);\n if (relevantRollouts.length === 0) {\n lines.push(\"- (none)\");\n } else {\n for (const r of relevantRollouts) {\n lines.push(\n `- rollout_summaries/${sanitizeSlug(r.slug)}.md (cwd=${r.cwd ?? \"*\"}, rollout_path=${r.rolloutPath ?? \"\"}, updated_at=${r.updatedAt ?? \"\"}, thread_id=${r.threadId ?? \"\"})`,\n );\n }\n }\n lines.push(\"\");\n lines.push(\"### keywords\");\n const keywords = collectKeywords(mems, category, ctx.namespace);\n lines.push(`- ${keywords.join(\", \")}`);\n lines.push(\"\");\n taskIndex += 1;\n }\n }\n\n lines.push(\"## User preferences\");\n const prefs = pickCategory(ctx.memories, [\"preference\"]);\n if (prefs.length === 0) {\n lines.push(\"- (none recorded)\");\n } else {\n for (const pref of prefs.slice(0, 20)) {\n lines.push(`- ${oneLineSummary(pref)}`);\n }\n }\n lines.push(\"\");\n\n lines.push(\"## Reusable knowledge\");\n const knowledge = pickCategory(ctx.memories, [\"fact\", \"decision\", \"principle\", \"rule\", \"skill\"]);\n if (knowledge.length === 0) {\n lines.push(\"- (none recorded)\");\n } else {\n for (const mem of knowledge.slice(0, 30)) {\n lines.push(`- ${oneLineSummary(mem)}`);\n }\n }\n lines.push(\"\");\n\n lines.push(\"## Failures and how to do differently\");\n const corrections = pickCategory(ctx.memories, [\"correction\"]);\n if (corrections.length === 0) {\n lines.push(\"- (none recorded)\");\n } else {\n for (const mem of corrections.slice(0, 20)) {\n lines.push(`- ${oneLineSummary(mem)}`);\n }\n }\n lines.push(\"\");\n\n return lines.join(\"\\n\");\n}\n\n/** Render `raw_memories.md` — mechanical dump, latest first. */\nexport function renderRawMemories(ctx: { memories: MemoryFile[] }): string {\n const sorted = [...ctx.memories].sort((a, b) => {\n const aUpdated = a.frontmatter.updated ?? a.frontmatter.created ?? \"\";\n const bUpdated = b.frontmatter.updated ?? b.frontmatter.created ?? \"\";\n return bUpdated.localeCompare(aUpdated);\n });\n\n const lines: string[] = [\"# Raw Memories\", \"\", \"_source: remnic — latest first_\", \"\"];\n for (const mem of sorted) {\n const fm = mem.frontmatter;\n const id = fm.id ?? \"unknown\";\n const category = fm.category ?? \"unknown\";\n const updated = fm.updated ?? fm.created ?? \"\";\n lines.push(`## ${id} (${category}, updated=${updated})`);\n lines.push(\"\");\n lines.push(mem.content.trim());\n lines.push(\"\");\n }\n return lines.join(\"\\n\");\n}\n\n/** Render a single rollout summary file. */\nexport function renderRolloutSummary(input: RolloutSummaryInput): string {\n const lines: string[] = [];\n lines.push(`# Rollout Summary: ${input.slug}`);\n lines.push(\"\");\n const meta: string[] = [];\n if (input.cwd) meta.push(`cwd=${input.cwd}`);\n if (input.rolloutPath) meta.push(`rollout_path=${input.rolloutPath}`);\n if (input.updatedAt) meta.push(`updated_at=${input.updatedAt}`);\n if (input.threadId) meta.push(`thread_id=${input.threadId}`);\n if (meta.length > 0) {\n lines.push(`_${meta.join(\"; \")}_`);\n lines.push(\"\");\n }\n if (input.keywords && input.keywords.length > 0) {\n lines.push(`**keywords:** ${input.keywords.join(\", \")}`);\n lines.push(\"\");\n }\n lines.push(input.body.trim());\n lines.push(\"\");\n return lines.join(\"\\n\");\n}\n\n// ─── Schema validation ─────────────────────────────────────────────────────\n\nexport interface MemoryMdValidation {\n valid: boolean;\n errors: string[];\n}\n\n/**\n * Validate that a rendered `MEMORY.md` matches Codex's task-group schema.\n * We enforce the minimum set of structural requirements called out in #378:\n *\n * - one `# Task Group:` header\n * - `scope:` and `applies_to:` lines directly beneath it\n * - at least one `## Task N:` section\n * - each task section has `### rollout_summary_files` and `### keywords`\n * - `## User preferences`, `## Reusable knowledge`,\n * `## Failures and how to do differently` sections all present\n */\nexport function validateMemoryMd(content: string): MemoryMdValidation {\n const errors: string[] = [];\n const lines = content.split(/\\r?\\n/u);\n\n const taskGroupIndex = lines.findIndex((l) => /^#\\s+Task Group:\\s+\\S+/u.test(l));\n if (taskGroupIndex === -1) {\n errors.push(\"missing `# Task Group:` header\");\n } else {\n const tail = lines.slice(taskGroupIndex + 1, taskGroupIndex + 5);\n if (!tail.some((l) => /^scope:\\s*\\S+/u.test(l))) {\n errors.push(\"missing `scope:` line under Task Group header\");\n }\n if (!tail.some((l) => /^applies_to:\\s*\\S+/u.test(l))) {\n errors.push(\"missing `applies_to:` line under Task Group header\");\n }\n }\n\n const taskHeaders = lines.filter((l) => /^##\\s+Task\\s+\\d+:/u.test(l));\n if (taskHeaders.length === 0) {\n errors.push(\"at least one `## Task N:` section is required\");\n }\n\n // For every task section, make sure we have rollout_summary_files + keywords\n // before the next `##` header at the same level.\n const sectionRegex = /^##\\s+/u;\n for (let i = 0; i < lines.length; i++) {\n if (!/^##\\s+Task\\s+\\d+:/u.test(lines[i])) continue;\n let hasRollout = false;\n let hasKeywords = false;\n for (let j = i + 1; j < lines.length; j++) {\n if (sectionRegex.test(lines[j])) break;\n if (/^###\\s+rollout_summary_files\\s*$/u.test(lines[j])) hasRollout = true;\n if (/^###\\s+keywords\\s*$/u.test(lines[j])) hasKeywords = true;\n }\n if (!hasRollout) errors.push(`task block at line ${i + 1} missing \\`### rollout_summary_files\\``);\n if (!hasKeywords) errors.push(`task block at line ${i + 1} missing \\`### keywords\\``);\n }\n\n const requiredSections = [\n /^##\\s+User preferences\\s*$/u,\n /^##\\s+Reusable knowledge\\s*$/u,\n /^##\\s+Failures and how to do differently\\s*$/u,\n ];\n for (const re of requiredSections) {\n if (!lines.some((l) => re.test(l))) {\n errors.push(`missing required section: ${re.source}`);\n }\n }\n\n return { valid: errors.length === 0, errors };\n}\n\n// ─── Helpers ───────────────────────────────────────────────────────────────\n\nfunction resolveCodexHome(override?: string): string {\n if (override && override.trim().length > 0) return override;\n const fromEnv = readEnvVar(\"CODEX_HOME\");\n if (fromEnv && fromEnv.trim().length > 0) return fromEnv;\n return path.join(resolveHomeDir(), \".codex\");\n}\n\nfunction readSentinel(sentinelPath: string): SentinelFile | null {\n if (!existsSync(sentinelPath)) return null;\n try {\n const raw = readFileSync(sentinelPath, \"utf-8\");\n const parsed = JSON.parse(raw) as Partial<SentinelFile>;\n if (typeof parsed !== \"object\" || parsed === null) return null;\n return {\n version: typeof parsed.version === \"number\" ? parsed.version : MATERIALIZE_VERSION,\n namespace: typeof parsed.namespace === \"string\" ? parsed.namespace : \"\",\n updated_at: typeof parsed.updated_at === \"string\" ? parsed.updated_at : \"\",\n content_hash: typeof parsed.content_hash === \"string\" ? parsed.content_hash : \"\",\n };\n } catch {\n return null;\n }\n}\n\nfunction selectSummaryMemories(memories: MemoryFile[], limit: number): MemoryFile[] {\n const scored = memories\n .filter((m) => !m.frontmatter.status || m.frontmatter.status === \"active\")\n .map((m) => {\n const confidence = typeof m.frontmatter.confidence === \"number\" ? m.frontmatter.confidence : 0;\n const importance =\n typeof m.frontmatter.importance === \"object\" &&\n m.frontmatter.importance !== null &&\n typeof (m.frontmatter.importance as { score?: number }).score === \"number\"\n ? ((m.frontmatter.importance as { score: number }).score ?? 0)\n : 0;\n const updated = m.frontmatter.updated ?? m.frontmatter.created ?? \"\";\n return { memory: m, score: importance * 2 + confidence, updated };\n });\n\n scored.sort((a, b) => {\n if (b.score !== a.score) return b.score - a.score;\n return b.updated.localeCompare(a.updated);\n });\n\n return scored.slice(0, limit).map((s) => s.memory);\n}\n\nfunction oneLineSummary(memory: MemoryFile): string {\n const raw = memory.content.replace(/\\s+/gu, \" \").trim();\n if (raw.length <= 160) return raw;\n return `${raw.slice(0, 157)}...`;\n}\n\nfunction groupMemoriesByCategory(memories: MemoryFile[]): Map<string, MemoryFile[]> {\n const map = new Map<string, MemoryFile[]>();\n for (const memory of memories) {\n if (memory.frontmatter.status && memory.frontmatter.status !== \"active\") continue;\n const category = memory.frontmatter.category ?? \"unknown\";\n const list = map.get(category) ?? [];\n list.push(memory);\n map.set(category, list);\n }\n return map;\n}\n\nfunction pickCategory(memories: MemoryFile[], categories: string[]): MemoryFile[] {\n const allowed = new Set(categories);\n return memories.filter(\n (m) =>\n (!m.frontmatter.status || m.frontmatter.status === \"active\") &&\n allowed.has(m.frontmatter.category ?? \"\"),\n );\n}\n\nfunction collectKeywords(memories: MemoryFile[], category: string, namespace: string): string[] {\n const keywords = new Set<string>();\n keywords.add(category);\n keywords.add(namespace);\n for (const mem of memories.slice(0, 10)) {\n for (const tag of mem.frontmatter.tags ?? []) {\n if (typeof tag === \"string\" && tag.trim().length > 0) keywords.add(tag.trim());\n }\n }\n return [...keywords].slice(0, 16);\n}\n\nfunction pruneRollouts(\n rollouts: RolloutSummaryInput[],\n retentionDays: number,\n now: Date,\n): RolloutSummaryInput[] {\n // Negative retention → \"infinite retention\" escape hatch. `parseConfig`\n // clamps the knob to >= 0, so in practice only callers passing a negative\n // value intentionally get the all-pass behavior.\n if (retentionDays < 0) return rollouts;\n // retentionDays === 0 → cutoff is exactly `now`, which prunes every\n // rollout whose `updatedAt` is in the past (i.e. all of them in practice).\n // This matches the documented semantics of \"retain for 0 days\".\n const cutoffMs = now.getTime() - retentionDays * 24 * 60 * 60 * 1000;\n return rollouts.filter((r) => {\n if (!r.updatedAt) return true;\n const t = Date.parse(r.updatedAt);\n if (!Number.isFinite(t)) return true;\n return t >= cutoffMs;\n });\n}\n\nfunction sanitizeSlug(slug: string): string {\n return slug\n .toLowerCase()\n .replace(/[^a-z0-9._-]+/gu, \"-\")\n .replace(/^-+|-+$/gu, \"\")\n .slice(0, 96)\n || \"rollout\";\n}\n\n/**\n * Whitespace-tokenized approximation used by the budget check. Matches the\n * simple heuristic Codex's usage.rs reporting uses for the \"5000 token\"\n * memory_summary cap.\n */\nexport function approximateTokenCount(text: string): number {\n const trimmed = text.trim();\n if (trimmed.length === 0) return 0;\n return trimmed.split(/\\s+/u).length;\n}\n\n/**\n * Truncate `text` so it fits under `maxTokens` whitespace tokens. We drop\n * trailing lines until we're under the budget and then append an ellipsis\n * marker so downstream readers can see that truncation happened.\n */\nexport function truncateToTokenBudget(text: string, maxTokens: number): string {\n if (maxTokens <= 0) return \"\";\n if (approximateTokenCount(text) <= maxTokens) return text;\n\n // Reserve headroom for the truncation marker so the line-preserving path\n // can actually fit the marker without flipping to the hard-cut fallback.\n // Both markers are counted with the same whitespace heuristic the budget\n // check uses, so the arithmetic stays consistent.\n const lineMarker = \"_[truncated for summary budget]_\";\n const tailMarker = \"[truncated]\";\n const lineMarkerTokens = approximateTokenCount(lineMarker);\n const tailMarkerTokens = approximateTokenCount(tailMarker);\n\n const lines = text.split(/\\r?\\n/u);\n const lineBudget = Math.max(0, maxTokens - lineMarkerTokens);\n while (lines.length > 0 && approximateTokenCount(lines.join(\"\\n\")) > lineBudget) {\n lines.pop();\n }\n lines.push(lineMarker);\n let result = lines.join(\"\\n\");\n\n // If a single huge line still blows the budget, hard-cut tokens. Reserve\n // space for the tail marker's own token count so the final string stays\n // within maxTokens rather than sneaking over by a few tokens.\n if (approximateTokenCount(result) > maxTokens) {\n const tokens = result.split(/\\s+/u);\n const keep = Math.max(0, maxTokens - tailMarkerTokens);\n result = keep > 0 ? `${tokens.slice(0, keep).join(\" \")} ${tailMarker}` : tailMarker;\n }\n return result;\n}\n\nfunction computeContentHash(input: {\n namespace: string;\n memorySummary: string;\n memoryMd: string;\n rawMemories: string;\n rolloutFiles: Array<{ name: string; body: string }>;\n}): string {\n const hash = createHash(\"sha256\");\n hash.update(`v${MATERIALIZE_VERSION}\\n`);\n hash.update(`namespace=${input.namespace}\\n`);\n hash.update(\"---memory_summary---\\n\");\n hash.update(input.memorySummary);\n hash.update(\"\\n---memory_md---\\n\");\n hash.update(input.memoryMd);\n hash.update(\"\\n---raw_memories---\\n\");\n hash.update(input.rawMemories);\n const sortedRollouts = [...input.rolloutFiles].sort((a, b) => a.name.localeCompare(b.name));\n for (const r of sortedRollouts) {\n hash.update(`\\n---rollout:${r.name}---\\n`);\n hash.update(r.body);\n }\n return hash.digest(\"hex\");\n}\n\n// ─── Stat helper for tests / debugging ─────────────────────────────────────\n\n/**\n * Return basic stats about a materialized memories dir. Useful for tests and\n * debug CLI output. Returns `null` if the dir does not exist.\n */\nexport function describeMemoriesDir(memoriesDir: string): {\n exists: boolean;\n hasSentinel: boolean;\n files: string[];\n sentinel: SentinelFile | null;\n} | null {\n if (!existsSync(memoriesDir)) return null;\n const sentinelPath = path.join(memoriesDir, SENTINEL_FILE);\n const sentinel = readSentinel(sentinelPath);\n const files: string[] = [];\n for (const entry of readdirSync(memoriesDir, { withFileTypes: true })) {\n if (entry.isFile() && OWNED_FILES.has(entry.name)) files.push(entry.name);\n }\n const rolloutsDir = path.join(memoriesDir, ROLLOUT_SUBDIR);\n if (existsSync(rolloutsDir)) {\n try {\n for (const entry of readdirSync(rolloutsDir, { withFileTypes: true })) {\n if (entry.isFile() && entry.name.endsWith(\".md\")) {\n files.push(path.join(ROLLOUT_SUBDIR, entry.name));\n }\n }\n } catch {\n // ignore\n }\n }\n return {\n exists: true,\n hasSentinel: sentinel !== null,\n files: files.sort(),\n sentinel,\n };\n}\n","/**\n * semantic-consolidation.ts — Semantic Consolidation Engine\n *\n * Finds clusters of semantically similar memories using token overlap,\n * synthesizes canonical versions via LLM, and archives the originals.\n * Reduces memory store bloat while preserving all unique information.\n */\n\nimport type { MemoryFile, PluginConfig } from \"./types.js\";\nimport { normalizeRecallTokens, countRecallTokenOverlap } from \"./recall-tokenization.js\";\nimport { runPostConsolidationMaterialize } from \"./connectors/codex-materialize-runner.js\";\nimport type { MaterializeResult, RolloutSummaryInput } from \"./connectors/codex-materialize.js\";\nimport { discoverMemoryExtensions, renderExtensionsBlock, resolveExtensionsRoot } from \"./memory-extension-host/index.js\";\nimport { log } from \"./logger.js\";\n\n// Re-export resolveExtensionsRoot for backward compatibility — existing\n// consumers that import from semantic-consolidation.ts continue to work.\nexport { resolveExtensionsRoot } from \"./memory-extension-host/index.js\";\n\n// Operator vocabulary (issue #561). The types and validators live in a\n// standalone `consolidation-operator.ts` module so `storage.ts` can import\n// them without creating a `storage → semantic-consolidation →\n// codex-materialize-runner → storage` cycle. Re-exported here so existing\n// consumers that reach for `./semantic-consolidation.js` keep working.\nexport {\n CONSOLIDATION_OPERATORS,\n isConsolidationOperator,\n isValidDerivedFromEntry,\n type ConsolidationOperator,\n} from \"./consolidation-operator.js\";\n\nimport {\n CONSOLIDATION_OPERATORS as _CONSOLIDATION_OPERATORS,\n isConsolidationOperator as _isConsolidationOperator,\n type ConsolidationOperator as _ConsolidationOperator,\n} from \"./consolidation-operator.js\";\n\nexport interface ConsolidationCluster {\n category: string;\n memories: MemoryFile[];\n overlapScore: number;\n canonicalContent?: string;\n}\n\nexport interface SemanticConsolidationResult {\n clustersFound: number;\n memoriesConsolidated: number;\n memoriesArchived: number;\n errors: number;\n clusters: ConsolidationCluster[];\n}\n\n/**\n * Find clusters of semantically similar memories using token overlap.\n */\nexport function findSimilarClusters(\n memories: MemoryFile[],\n config: {\n threshold: number;\n minClusterSize: number;\n excludeCategories: string[];\n maxPerRun: number;\n },\n): ConsolidationCluster[] {\n const excluded = new Set(config.excludeCategories);\n\n // Group by category first\n const byCategory = new Map<string, MemoryFile[]>();\n for (const m of memories) {\n const cat = m.frontmatter.category;\n if (excluded.has(cat)) continue;\n if (m.frontmatter.status && m.frontmatter.status !== \"active\") continue;\n const list = byCategory.get(cat) ?? [];\n list.push(m);\n byCategory.set(cat, list);\n }\n\n const clusters: ConsolidationCluster[] = [];\n let totalCandidates = 0;\n\n for (const [category, mems] of byCategory) {\n if (totalCandidates >= config.maxPerRun) break;\n\n // Token-normalize all memories in this category\n const tokenized = mems.map((m) => ({\n memory: m,\n tokens: new Set(normalizeRecallTokens(m.content, [])),\n }));\n\n // Track which memories are already clustered\n const clustered = new Set<string>();\n\n for (let i = 0; i < tokenized.length && totalCandidates < config.maxPerRun; i++) {\n if (clustered.has(tokenized[i].memory.frontmatter.id)) continue;\n\n const cluster: MemoryFile[] = [tokenized[i].memory];\n let totalOverlap = 0;\n let comparisons = 0;\n\n for (let j = i + 1; j < tokenized.length; j++) {\n if (clustered.has(tokenized[j].memory.frontmatter.id)) continue;\n\n const aTokens = tokenized[i].tokens;\n const bTokens = tokenized[j].tokens;\n if (aTokens.size === 0 || bTokens.size === 0) continue;\n\n // Bidirectional overlap: what fraction of tokens are shared\n const overlap = countRecallTokenOverlap(aTokens, [...bTokens].join(\" \"));\n const maxTokens = Math.max(aTokens.size, bTokens.size);\n const score = maxTokens > 0 ? overlap / maxTokens : 0;\n\n if (score >= config.threshold) {\n cluster.push(tokenized[j].memory);\n totalOverlap += score;\n comparisons++;\n // Enforce maxPerRun within a single cluster\n if (totalCandidates + cluster.length >= config.maxPerRun) break;\n }\n }\n\n if (cluster.length >= config.minClusterSize) {\n for (const m of cluster) clustered.add(m.frontmatter.id);\n clusters.push({\n category,\n memories: cluster,\n overlapScore: comparisons > 0 ? totalOverlap / comparisons : 0,\n });\n totalCandidates += cluster.length;\n }\n }\n }\n\n return clusters;\n}\n\n/**\n * Build the LLM prompt for synthesizing a canonical memory from a cluster.\n */\nexport function buildConsolidationPrompt(cluster: ConsolidationCluster): string {\n const memoryTexts = cluster.memories\n .map(\n (m, i) =>\n `Memory ${i + 1} (${m.frontmatter.id}, created ${m.frontmatter.created}):\\n${m.content}`,\n )\n .join(\"\\n\\n\");\n\n return `You are a memory consolidation system. The following ${cluster.memories.length} memories in the \"${cluster.category}\" category contain overlapping information.\n\nSynthesize them into ONE canonical memory that:\n1. Preserves ALL unique information from every source memory\n2. Removes redundancy and repetition\n3. Uses clear, concise language\n4. Maintains the same category and tone\n5. Does NOT add information that isn't in the sources\n\n${memoryTexts}\n\nWrite ONLY the consolidated memory content (no metadata, no explanation, no preamble):`;\n}\n\n/**\n * Parse the LLM response to extract the canonical content.\n */\nexport function parseConsolidationResponse(response: string): string {\n return response.trim();\n}\n\n// ─── Operator-aware prompt / parse (issue #561 PR 3) ─────────────────────────\n\n/**\n * Structured result from an operator-aware consolidation LLM call.\n *\n * - `operator` — the consolidation operator the LLM chose for this cluster.\n * Falls back to the heuristic default when the LLM omits or returns an\n * unknown value (the parser never surfaces an invalid operator; see\n * `parseOperatorAwareConsolidationResponse`).\n * - `output` — the canonical content (same format the legacy prompt\n * returns). Callers persist this as the body of the new memory.\n */\nexport interface OperatorAwareConsolidationResult {\n operator: _ConsolidationOperator;\n output: string;\n}\n\n/**\n * Heuristic default operator for a cluster. Used as the fallback when the\n * LLM does not return a parseable operator, and as the \"floor\" decision in\n * `parseOperatorAwareConsolidationResponse`.\n *\n * Current heuristic (kept deliberately conservative — PR 3 only):\n *\n * - Two or more memories being collapsed into one canonical blob is a\n * MERGE by definition. This is the path the current clustering\n * pipeline exercises.\n * - A cluster of size 1 that still reaches consolidation (future path,\n * e.g. supersession of a single older memory by a newer value) is an\n * UPDATE.\n * - SPLIT is never selected by the heuristic because the current write\n * path emits exactly one canonical memory per cluster. The prompt\n * reserves SPLIT for future cluster shapes where the LLM decides one\n * logical source actually encodes several distinct facts — at which\n * point the orchestrator would need to write multiple outputs.\n */\nexport function chooseConsolidationOperator(\n cluster: ConsolidationCluster,\n): _ConsolidationOperator {\n if (cluster.memories.length <= 1) return \"update\";\n return \"merge\";\n}\n\n/**\n * Build the operator-aware LLM prompt. The LLM is asked to return a\n * JSON object `{ \"operator\": <split|merge|update>, \"output\": <content> }`.\n *\n * The prompt is additive: it still asks for a single canonical blob under\n * `output`, so the upstream write path does not change. Future expansions\n * (SPLIT emitting multiple outputs) are explicitly documented as\n * out-of-scope for the parser — `parseOperatorAwareConsolidationResponse`\n * collapses any SPLIT response into a single canonical output for now.\n */\nexport function buildOperatorAwareConsolidationPrompt(\n cluster: ConsolidationCluster,\n): string {\n const memoryTexts = cluster.memories\n .map(\n (m, i) =>\n `Memory ${i + 1} (${m.frontmatter.id}, created ${m.frontmatter.created}):\\n${m.content}`,\n )\n .join(\"\\n\\n\");\n\n return `You are a memory consolidation system. The following ${cluster.memories.length} memories in the \"${cluster.category}\" category contain overlapping information.\n\nPick exactly ONE consolidation operator for this cluster and return a JSON object.\n\nOperator vocabulary:\n - \"merge\" — multiple distinct source memories overlap and should be collapsed into one canonical memory (most common).\n - \"update\" — one source memory carries a stale value that a newer source supersedes within the same logical fact.\n - \"split\" — a single logical source really encodes multiple distinct facts that should be separated (rare; if you pick split, still emit ONE canonical body — the write path will chunk it later).\n\nOutput JSON ONLY, no prose before or after. The \"operator\" key MUST be set to exactly one of the three strings \"merge\", \"update\", or \"split\" — never a pipe-separated placeholder like \"merge|update|split\". Example shape:\n {\n \"operator\": \"merge\",\n \"output\": \"<the canonical memory text>\"\n }\n\nThe \"output\" value must:\n1. Preserve ALL unique information from every source memory\n2. Remove redundancy and repetition\n3. Use clear, concise language\n4. Match the \"${cluster.category}\" category and tone\n5. NOT add information that isn't in the sources\n\n${memoryTexts}\n\nReturn ONLY the JSON object:`;\n}\n\n/**\n * Parse an operator-aware consolidation response.\n *\n * Contract:\n * - Accepts strict JSON `{ \"operator\": \"...\", \"output\": \"...\" }`.\n * - Tolerates a JSON payload wrapped in a fenced code block (```json ...```).\n * - Falls back to the heuristic operator when the JSON is malformed,\n * the `operator` field is missing / unknown, or the raw response is\n * a plain blob with no JSON at all. This keeps PR 3 backwards\n * compatible with older models that ignore the JSON instruction.\n * - Never throws. A missing / empty `output` field falls back to the\n * trimmed raw response so the caller still writes something rather\n * than dropping the cluster.\n */\nexport function parseOperatorAwareConsolidationResponse(\n response: string,\n cluster: ConsolidationCluster,\n): OperatorAwareConsolidationResult {\n const fallback: OperatorAwareConsolidationResult = {\n operator: chooseConsolidationOperator(cluster),\n output: response.trim(),\n };\n\n const trimmed = response.trim();\n if (trimmed.length === 0) return fallback;\n\n // Strip a fenced code block if present.\n const fenced = /^```(?:json)?\\s*([\\s\\S]*?)```\\s*$/u.exec(trimmed);\n const payload = fenced ? fenced[1].trim() : trimmed;\n\n // Find a balanced brace-delimited JSON object that has an `operator`\n // key (PR #632 round-4 review, codex P1). A first/last-brace slice\n // breaks when the model prepends an earlier brace block (e.g.\n // `Example: {\"note\":\"...\"} ... {\"operator\":\"merge\",...}`). Walk the\n // payload tracking nesting + string/escape state and skip past\n // objects that don't look like our target shape.\n const parsed = findLastJsonObjectWithOperator(payload);\n if (parsed === undefined) return fallback;\n if (typeof parsed !== \"object\" || parsed === null) return fallback;\n\n const obj = parsed as Record<string, unknown>;\n const rawOperator = typeof obj.operator === \"string\" ? obj.operator.trim().toLowerCase() : \"\";\n const rawOutput = typeof obj.output === \"string\" ? obj.output : \"\";\n\n const operator = _isConsolidationOperator(rawOperator)\n ? rawOperator\n : chooseConsolidationOperator(cluster);\n const output = rawOutput.trim().length > 0 ? rawOutput.trim() : response.trim();\n\n return { operator, output };\n}\n\n/**\n * Walk `text`, find all balanced top-level `{ ... }` blocks whose\n * JSON.parse result is an object with an `operator` key, and return\n * the LAST one (function name reflects this — PR #632 review,\n * cursor Low). Returns `undefined` when nothing matches. Tracks\n * string state + escape sequences so braces inside string values\n * don't throw off the depth counter.\n *\n * Used by `parseOperatorAwareConsolidationResponse` to tolerate models\n * that prepend an explanatory JSON example block before the real\n * payload (PR #632 round-4 + round-5 review, codex P1). We take the\n * LAST candidate so that an instructional example with an `operator`\n * key ahead of the real answer doesn't steal precedence — models\n * typically write the example first and the real answer last.\n */\nfunction findLastJsonObjectWithOperator(text: string): unknown {\n let searchFrom = 0;\n let last: unknown = undefined;\n while (searchFrom < text.length) {\n const start = text.indexOf(\"{\", searchFrom);\n if (start < 0) return last;\n let depth = 0;\n let inString = false;\n let escape = false;\n let closed = false;\n let endIdx = -1;\n for (let i = start; i < text.length; i++) {\n const ch = text[i];\n if (inString) {\n if (escape) {\n escape = false;\n } else if (ch === \"\\\\\") {\n escape = true;\n } else if (ch === '\"') {\n inString = false;\n }\n continue;\n }\n if (ch === '\"') {\n inString = true;\n } else if (ch === \"{\") {\n depth += 1;\n } else if (ch === \"}\") {\n depth -= 1;\n if (depth === 0) {\n closed = true;\n endIdx = i;\n break;\n }\n }\n }\n if (!closed) return last;\n const slice = text.slice(start, endIdx + 1);\n try {\n const parsed = JSON.parse(slice);\n if (\n typeof parsed === \"object\" &&\n parsed !== null &&\n \"operator\" in (parsed as Record<string, unknown>)\n ) {\n last = parsed;\n }\n } catch {\n // Fall through to the next candidate.\n }\n searchFrom = endIdx + 1;\n }\n return last;\n}\n\n// Silence unused-import warnings when tsup tree-shakes: these are used\n// above in chooseConsolidationOperator + parse helpers.\nvoid _CONSOLIDATION_OPERATORS;\n\n/**\n * Discover extensions and build the block to append to a consolidation prompt.\n * Returns \"\" when extensions are disabled or none are found.\n */\nexport async function buildExtensionsBlockForConsolidation(\n config: PluginConfig,\n): Promise<string> {\n if (!config.memoryExtensionsEnabled) return \"\";\n const root = resolveExtensionsRoot(config);\n const extensions = await discoverMemoryExtensions(root, log);\n if (extensions.length === 0) return \"\";\n return renderExtensionsBlock(extensions);\n}\n\n/**\n * Optional post-consolidation hook — materializes the namespace into Codex's\n * native memory layout when the consolidation run finishes. Kept here (rather\n * than in orchestrator.ts) so #378 doesn't conflict with Wave 1 edits.\n *\n * Safe to call regardless of config state: honors `codexMaterializeMemories`\n * and `codexMaterializeOnConsolidation` and silently becomes a no-op when\n * either is disabled.\n */\nexport async function materializeAfterSemanticConsolidation(options: {\n config: PluginConfig;\n namespace?: string;\n memories?: MemoryFile[];\n memoryDir?: string;\n codexHome?: string;\n rolloutSummaries?: RolloutSummaryInput[];\n now?: Date;\n}): Promise<MaterializeResult | null> {\n // Delegates to the shared post-consolidation helper so semantic and causal\n // flows stay in lock-step — any guard/logging change happens in one place.\n return runPostConsolidationMaterialize(\"[semantic-consolidation]\", options);\n}\n"],"mappings":"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","names":["path","existsSync","path","existsSync"]}
|
|
1
|
+
{"version":3,"sources":["../src/connectors/codex-materialize-runner.ts","../src/connectors/codex-materialize.ts","../src/semantic-consolidation.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 path from \"node:path\";\nimport { existsSync } from \"node:fs\";\n\nimport { log } from \"../logger.js\";\nimport { StorageManager } from \"../storage.js\";\nimport type { PluginConfig, MemoryFile } from \"../types.js\";\nimport {\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 // session-end.sh passes reason=\"session_end\"; when the user has turned off the\n // session-end trigger 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 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 const nsDir = resolveNamespaceDir(memoryDir, namespace, cfg);\n const storage = new StorageManager(nsDir);\n try {\n memories = await storage.readAllMemories();\n } catch (error) {\n log.warn(\n `[codex-materialize] skipped — failed to read memories from ${nsDir}: ${\n error instanceof Error ? error.message : String(error)\n }`,\n );\n return null;\n }\n }\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: options.codexHome,\n maxSummaryTokens: cfg.codexMaterializeMaxSummaryTokens,\n rolloutRetentionDays: cfg.codexMaterializeRolloutRetentionDays,\n rolloutSummaries: options.rolloutSummaries,\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 if (requested.length === 0 || requested === \"auto\") {\n // When the caller asks for \"auto\", fall back to the configured default\n // namespace (if any) so storage-root resolution lines up with what\n // NamespaceStorageRouter would do for the same install.\n return cfg.defaultNamespace && cfg.defaultNamespace.length > 0\n ? cfg.defaultNamespace\n : \"default\";\n }\n return requested;\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 ns = namespace || cfg.defaultNamespace || \"default\";\n const namespacedRoot = path.join(memoryDir, \"namespaces\", ns);\n\n if (ns === cfg.defaultNamespace) {\n return existsSync(namespacedRoot) ? namespacedRoot : memoryDir;\n }\n return namespacedRoot;\n}\n","/**\n * codex-materialize.ts — Codex CLI native memory artifact materialization (#378)\n *\n * Periodically writes Remnic memories into the file layout that Codex CLI's\n * phase-2 consolidation reads directly under `<codex_home>/memories/`:\n *\n * memory_summary.md — always-loaded at session start (tight budget)\n * MEMORY.md — searchable handbook (task-group schema)\n * raw_memories.md — mechanical merge of raw memories, latest first\n * rollout_summaries/<slug>.md — per-session recaps\n *\n * Codex's own read path is agnostic to which producer wrote these files — it\n * tags reads by `memory_md` / `memory_summary` / `raw_memories` /\n * `rollout_summaries` / `skills`. By materializing Remnic content into this\n * exact layout we let Codex pick up Remnic memories without a single MCP call.\n *\n * Safety invariants\n * ─────────────────\n * - **Atomic writes.** Every file is rendered under `.remnic-tmp/` and then\n * `rename()`d into place so Codex never observes a half-written file.\n * - **Sentinel-based opt-in.** If `<codex_home>/memories/.remnic-managed` is\n * missing, we SKIP materialization entirely and log a warning. This honors\n * user hand-edits to the directory — a user who manually curated their\n * Codex memory layout will never have those edits overwritten.\n * - **Schema validation.** `MEMORY.md` content is validated against the\n * task-group schema before write. Invalid content throws and nothing is\n * written.\n * - **Idempotent no-ops.** A content hash is written into the sentinel. If\n * the re-rendered hash matches the previous run, we skip writes entirely.\n * - **Token budget.** `memory_summary.md` is truncated to fit under the\n * configured token budget (whitespace-tokenized approximation), leaving\n * headroom under Codex's 5000-token summary cap.\n *\n * Privacy\n * ───────\n * This module does not persist any user content outside `<codex_home>/memories`\n * — it only mirrors the memories that Remnic already wrote. It does not log\n * memory content to stdout; it logs file names, counts, and hashes.\n */\n\nimport {\n createHash,\n} from \"node:crypto\";\nimport {\n existsSync,\n mkdirSync,\n readdirSync,\n readFileSync,\n renameSync,\n rmSync,\n statSync,\n unlinkSync,\n writeFileSync,\n} from \"node:fs\";\nimport path from \"node:path\";\n\nimport { log } from \"../logger.js\";\nimport { readEnvVar, resolveHomeDir } from \"../runtime/env.js\";\nimport type { MemoryFile } from \"../types.js\";\n\n// ─── Types ─────────────────────────────────────────────────────────────────\n\n/**\n * Input for {@link materializeForNamespace}. Prefer passing pre-loaded\n * `memories` so this module stays I/O-agnostic and trivially testable.\n */\nexport interface MaterializeOptions {\n /** Pre-loaded Remnic memories for this namespace (required). */\n memories: MemoryFile[];\n /** Override `<codex_home>`. Defaults to `$CODEX_HOME` or `~/.codex`. */\n codexHome?: string;\n /** Maximum whitespace-tokenized size of memory_summary.md. Default 4500. */\n maxSummaryTokens?: number;\n /** Maximum age of rollout_summaries/*.md in days. Default 30. */\n rolloutRetentionDays?: number;\n /** Per-session rollout summaries to render. */\n rolloutSummaries?: RolloutSummaryInput[];\n /** Current time, injected for deterministic tests. */\n now?: Date;\n /** Optional logger override for tests. */\n logger?: { info: (msg: string) => void; warn: (msg: string) => void; debug?: (msg: string) => void };\n}\n\n/** Input describing one Codex rollout summary file. */\nexport interface RolloutSummaryInput {\n /** Stable slug for the file (becomes `<slug>.md`). */\n slug: string;\n /** Working directory used during the rollout. */\n cwd?: string;\n /** Path to the raw Codex rollout log, if known. */\n rolloutPath?: string;\n /** ISO-8601 timestamp of the last update. */\n updatedAt?: string;\n /** Opaque thread / session id. */\n threadId?: string;\n /** Markdown body for the recap. */\n body: string;\n /** Freeform keywords / search hints. */\n keywords?: string[];\n}\n\n/** Result of a materialization run. */\nexport interface MaterializeResult {\n /** Namespace that was materialized. */\n namespace: string;\n /** `<codex_home>/memories` path this run targeted. */\n memoriesDir: string;\n /** Was anything actually written (vs. skipped / idempotent no-op)? */\n wrote: boolean;\n /** True if the sentinel was missing and we skipped with a warning. */\n skippedNoSentinel: boolean;\n /** True if the hash matched the previous run and we short-circuited. */\n skippedIdempotent: boolean;\n /** Files that were written this run (relative to `memoriesDir`). */\n filesWritten: string[];\n /** Content hash computed for this run. */\n contentHash: string;\n}\n\n/** On-disk shape of the `.remnic-managed` sentinel. */\ninterface SentinelFile {\n version: number;\n namespace: string;\n updated_at: string;\n content_hash: string;\n}\n\n// ─── Constants ─────────────────────────────────────────────────────────────\n\n/** Bump when the on-disk layout or semantics change. */\nexport const MATERIALIZE_VERSION = 1;\n\n/** Sentinel file name at the root of the materialized memories dir. */\nexport const SENTINEL_FILE = \".remnic-managed\";\n\n/** Scratch directory used for atomic renames. */\nexport const TMP_DIR = \".remnic-tmp\";\n\n/** File names we own. Anything else in the directory is considered user-managed. */\nconst OWNED_FILES = new Set<string>([\n \"memory_summary.md\",\n \"MEMORY.md\",\n \"raw_memories.md\",\n]);\n\n/** Sub-directory for per-session rollout recaps. */\nconst ROLLOUT_SUBDIR = \"rollout_summaries\";\n\n// ─── Public entry points ───────────────────────────────────────────────────\n\n/**\n * Materialize a Remnic namespace into Codex's native memory layout.\n *\n * Returns a {@link MaterializeResult} describing what happened. Callers\n * should treat \"skipped\" as success — the sentinel / idempotent cases are\n * expected and intentional.\n *\n * @throws if `MEMORY.md` fails schema validation (we do not write garbage).\n */\nexport function materializeForNamespace(\n namespace: string,\n options: MaterializeOptions,\n): MaterializeResult {\n const logger = options.logger ?? {\n info: (msg) => log.info(`[codex-materialize] ${msg}`),\n warn: (msg) => log.warn(`[codex-materialize] ${msg}`),\n debug: (msg) => log.debug(`[codex-materialize] ${msg}`),\n };\n const codexHome = resolveCodexHome(options.codexHome);\n const memoriesDir = path.join(codexHome, \"memories\");\n const now = options.now ?? new Date();\n // Honor `0` as \"no summary budget\" — parseConfig already clamps to non-\n // negative integers, so any provided number is meaningful. Only fall back\n // to the default when the caller did not provide the option at all.\n const maxSummaryTokens =\n typeof options.maxSummaryTokens === \"number\" && options.maxSummaryTokens >= 0\n ? options.maxSummaryTokens\n : 4500;\n const rolloutRetentionDays =\n typeof options.rolloutRetentionDays === \"number\" && options.rolloutRetentionDays >= 0\n ? options.rolloutRetentionDays\n : 30;\n\n // ── Sentinel check ─────────────────────────────────────────────────────\n // We deliberately do NOT `mkdirSync(memoriesDir)` before reading the\n // sentinel: creating `~/.codex/memories/` for every user (including ones\n // who never use Codex) would make Remnic's post-consolidation hook leave\n // empty opt-in directories behind on disk. Instead we only check whether\n // the sentinel already exists — if the parent dir doesn't exist, the\n // sentinel can't exist either and we fall straight through to the skip\n // path without touching the filesystem. The `mkdirSync` for `memoriesDir`\n // happens later, only once we know we're actually going to write.\n const sentinelPath = path.join(memoriesDir, SENTINEL_FILE);\n const existingSentinel = readSentinel(sentinelPath);\n if (!existingSentinel) {\n // Log at `debug` when the entire memories dir doesn't exist — that's\n // the common \"user never opted in\" case and should not be noisy.\n // Keep the `warn` level only when the dir exists but lacks a sentinel,\n // which is the \"user hand-curated layout, don't overwrite\" case that\n // genuinely warrants attention.\n if (existsSync(memoriesDir)) {\n logger.warn(\n `sentinel ${SENTINEL_FILE} missing in ${memoriesDir}; skipping materialization to preserve hand-edits`,\n );\n } else {\n logger.debug?.(\n `skipping materialization — ${memoriesDir} does not exist (user not opted in)`,\n );\n }\n return {\n namespace,\n memoriesDir,\n wrote: false,\n skippedNoSentinel: true,\n skippedIdempotent: false,\n filesWritten: [],\n contentHash: \"\",\n };\n }\n\n // Now that we know the user has opted in (sentinel exists), it's safe to\n // ensure the memories dir is present. In practice this is almost always a\n // no-op because the sentinel read above already succeeded, but a defensive\n // mkdirSync protects against a race where the dir was removed between the\n // sentinel read and the first write.\n mkdirSync(memoriesDir, { recursive: true });\n\n // ── Render ─────────────────────────────────────────────────────────────\n const memories = [...options.memories];\n // Track whether the caller actually supplied a rollout set. `undefined`\n // means \"don't touch rollout_summaries/\"; an empty array is still\n // authoritative and means \"we own this dir and it should be empty\".\n const rolloutsSupplied = options.rolloutSummaries !== undefined;\n const rolloutSummaries = options.rolloutSummaries ?? [];\n\n // Prune-before-render: MEMORY.md and memory_summary.md both embed rollout\n // filenames in their body, so they must only ever see the *retained* set.\n // Running pruneRollouts after the renderers (as an earlier revision did)\n // caused MEMORY.md to list `rollout_summaries/<slug>.md` paths for rollouts\n // that were then pruned and never written — a broken link pointing at a\n // ghost file. See review feedback on PR #392.\n const retainedRollouts = pruneRollouts(rolloutSummaries, rolloutRetentionDays, now);\n\n // Deduplicate on sanitized filename. Two different slugs (\"Session 1\" and\n // \"session!!!1\") can sanitize to the same output (\"session-1\"), which would\n // otherwise make the first entry's tmp file get overwritten and cause the\n // later rename step to crash with ENOENT. For each collision slot we keep\n // the entry with the newest `updatedAt` so an unsorted input (or a caller\n // that accidentally appends older recaps after newer ones) can't have an\n // older recap clobber a newer one.\n // We do this at the retained-input level (not just at the written-file\n // level) so MEMORY.md's \"rollout_summary_files\" section lists each slot\n // exactly once and matches what actually gets written to disk.\n const dedupedRollouts: RolloutSummaryInput[] = [];\n const seenNames = new Map<string, number>();\n const parseTs = (value: string | undefined): number => {\n if (!value) return Number.NEGATIVE_INFINITY;\n const parsed = Date.parse(value);\n return Number.isFinite(parsed) ? parsed : Number.NEGATIVE_INFINITY;\n };\n for (const r of retainedRollouts) {\n const name = `${sanitizeSlug(r.slug)}.md`;\n const existingIdx = seenNames.get(name);\n if (existingIdx === undefined) {\n seenNames.set(name, dedupedRollouts.length);\n dedupedRollouts.push(r);\n continue;\n }\n // Newest-wins: only replace the existing entry if the incoming one has a\n // strictly newer timestamp. Ties keep the earlier entry (stable for\n // unsorted inputs) because overwriting on ties would flip rendering output\n // for benign call-order changes.\n const existing = dedupedRollouts[existingIdx];\n if (parseTs(r.updatedAt) > parseTs(existing.updatedAt)) {\n dedupedRollouts[existingIdx] = r;\n }\n }\n\n const memorySummary = renderMemorySummary({\n namespace,\n memories,\n rolloutSummaries: dedupedRollouts,\n maxTokens: maxSummaryTokens,\n });\n\n const memoryMd = renderMemoryMd({\n namespace,\n memories,\n rolloutSummaries: dedupedRollouts,\n });\n\n // Fail fast on schema issues — do not write garbage.\n const validation = validateMemoryMd(memoryMd);\n if (!validation.valid) {\n const reason = validation.errors.join(\"; \");\n logger.warn(`MEMORY.md failed schema validation: ${reason}`);\n throw new Error(`codex-materialize: MEMORY.md schema validation failed: ${reason}`);\n }\n\n const rawMemories = renderRawMemories({ memories });\n\n const rolloutFiles = dedupedRollouts.map((r) => ({\n name: `${sanitizeSlug(r.slug)}.md`,\n body: renderRolloutSummary(r),\n }));\n\n // ── Idempotence check ──────────────────────────────────────────────────\n const hash = computeContentHash({\n namespace,\n memorySummary,\n memoryMd,\n rawMemories,\n rolloutFiles,\n });\n\n if (existingSentinel.content_hash === hash) {\n // Idempotence early-return is only safe when the managed files we would\n // have written are still on disk. If a user or external process deleted\n // `MEMORY.md` / `memory_summary.md` / `raw_memories.md` (or any retained\n // rollout file) while the sentinel's hash stayed the same, we must fall\n // through and rewrite — otherwise Codex would be stuck with missing\n // artifacts until a memory-content change happens to flip the hash.\n const requiredFiles = [\n path.join(memoriesDir, \"memory_summary.md\"),\n path.join(memoriesDir, \"MEMORY.md\"),\n path.join(memoriesDir, \"raw_memories.md\"),\n ...rolloutFiles.map((r) => path.join(memoriesDir, ROLLOUT_SUBDIR, r.name)),\n ];\n const allPresent = requiredFiles.every((f) => existsSync(f));\n if (allPresent) {\n logger.debug?.(`no-op materialization for namespace=${namespace} (hash unchanged)`);\n return {\n namespace,\n memoriesDir,\n wrote: false,\n skippedNoSentinel: false,\n skippedIdempotent: true,\n filesWritten: [],\n contentHash: hash,\n };\n }\n logger.debug?.(\n `hash unchanged for namespace=${namespace} but managed file missing — forcing rewrite`,\n );\n }\n\n // ── Atomic writes ──────────────────────────────────────────────────────\n // Use a unique, per-run staging sub-directory so two overlapping runs\n // (e.g. a session-end trigger overlapping with a consolidation post-hook)\n // can't stomp each other's tmp files mid-rename. The old \"fixed TMP_DIR,\n // wipe-on-entry\" layout meant run B would delete run A's staging area out\n // from under it, causing ENOENT on A's rename loop. Per-run uniqueness\n // turns the shared dir into an insulated workspace. See review feedback on\n // PR #392.\n const runTag = `${process.pid}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;\n const tmpDir = path.join(memoriesDir, `${TMP_DIR}-${runTag}`);\n // Opportunistic GC for stale scratch dirs left behind by a previous\n // crashed run. We only remove entries whose mtime is older than the\n // stale-threshold below — that way we never delete another in-flight\n // run's staging area out from under it. The threshold is deliberately\n // generous (1h) because a healthy materialize completes in milliseconds\n // and there's no legitimate reason for a live staging dir to be older.\n //\n // NB: we compare against `Date.now()` (wall-clock), not against the\n // injected `options.now`. Tests and deterministic replays commonly\n // inject a non-current timestamp, but file mtimes on disk are always\n // wall-clock, so mixing the two would either false-positive delete\n // fresh dirs (test-time in the past) or false-negative skip stale ones\n // (test-time in the future).\n const TMP_STALE_MS = 60 * 60 * 1000;\n const wallClockMs = Date.now();\n try {\n for (const entry of readdirSync(memoriesDir, { withFileTypes: true })) {\n if (!entry.isDirectory()) continue;\n if (!entry.name.startsWith(TMP_DIR)) continue;\n const stalePath = path.join(memoriesDir, entry.name);\n try {\n const stat = statSync(stalePath);\n if (wallClockMs - stat.mtimeMs < TMP_STALE_MS) continue;\n rmSync(stalePath, { recursive: true, force: true });\n } catch {\n // ignore — another concurrent run may own it, or we lack perms\n }\n }\n } catch {\n // ignore — dir may not exist yet\n }\n mkdirSync(tmpDir, { recursive: true });\n mkdirSync(path.join(tmpDir, ROLLOUT_SUBDIR), { recursive: true });\n\n const filesWritten: string[] = [];\n\n writeFileSync(path.join(tmpDir, \"memory_summary.md\"), memorySummary);\n filesWritten.push(\"memory_summary.md\");\n\n writeFileSync(path.join(tmpDir, \"MEMORY.md\"), memoryMd);\n filesWritten.push(\"MEMORY.md\");\n\n writeFileSync(path.join(tmpDir, \"raw_memories.md\"), rawMemories);\n filesWritten.push(\"raw_memories.md\");\n\n for (const rollout of rolloutFiles) {\n writeFileSync(path.join(tmpDir, ROLLOUT_SUBDIR, rollout.name), rollout.body);\n filesWritten.push(path.join(ROLLOUT_SUBDIR, rollout.name));\n }\n\n // Rename into place. Atomic per-file is sufficient — Codex reads each file\n // independently and tolerates an inconsistent in-between snapshot across\n // files for the duration of the rename loop (milliseconds).\n for (const rel of [\"memory_summary.md\", \"MEMORY.md\", \"raw_memories.md\"]) {\n const src = path.join(tmpDir, rel);\n const dest = path.join(memoriesDir, rel);\n renameSync(src, dest);\n }\n\n const destRolloutsDir = path.join(memoriesDir, ROLLOUT_SUBDIR);\n mkdirSync(destRolloutsDir, { recursive: true });\n // Only garbage-collect rollout files when the caller actually supplied a\n // `rolloutSummaries` array — otherwise we'd wipe legitimately\n // user/Codex-created recap files on every session-end run, since those\n // calls typically omit the rollout set entirely. When rollouts were\n // supplied (even as an empty array — meaning \"we own this dir and it\n // should be empty\"), we clear the stale files we previously owned.\n if (rolloutsSupplied) {\n const retainedRolloutNames = new Set(rolloutFiles.map((r) => r.name));\n try {\n for (const entry of readdirSync(destRolloutsDir, { withFileTypes: true })) {\n if (!entry.isFile()) continue;\n if (!entry.name.endsWith(\".md\")) continue;\n if (retainedRolloutNames.has(entry.name)) continue;\n try {\n unlinkSync(path.join(destRolloutsDir, entry.name));\n } catch {\n // ignore\n }\n }\n } catch {\n // ignore — directory may not exist yet\n }\n }\n\n for (const rollout of rolloutFiles) {\n const src = path.join(tmpDir, ROLLOUT_SUBDIR, rollout.name);\n const dest = path.join(destRolloutsDir, rollout.name);\n renameSync(src, dest);\n }\n\n // Update sentinel last so a crash leaves hash mismatched → next run rewrites.\n const sentinel: SentinelFile = {\n version: MATERIALIZE_VERSION,\n namespace,\n updated_at: now.toISOString(),\n content_hash: hash,\n };\n writeFileSync(sentinelPath, `${JSON.stringify(sentinel, null, 2)}\\n`);\n\n try {\n rmSync(tmpDir, { recursive: true, force: true });\n } catch {\n // ignore\n }\n\n logger.info(\n `materialized namespace=${namespace} files=${filesWritten.length} hash=${hash.slice(0, 12)}`,\n );\n\n return {\n namespace,\n memoriesDir,\n wrote: true,\n skippedNoSentinel: false,\n skippedIdempotent: false,\n filesWritten,\n contentHash: hash,\n };\n}\n\n/**\n * Create (or refresh) the `.remnic-managed` sentinel. Callers must do this\n * explicitly the first time they want Remnic to start managing a directory —\n * we never write it implicitly, because its presence is the user's opt-in.\n */\nexport function ensureSentinel(memoriesDir: string, namespace: string, now: Date = new Date()): void {\n mkdirSync(memoriesDir, { recursive: true });\n const sentinelPath = path.join(memoriesDir, SENTINEL_FILE);\n if (existsSync(sentinelPath)) return;\n const sentinel: SentinelFile = {\n version: MATERIALIZE_VERSION,\n namespace,\n updated_at: now.toISOString(),\n content_hash: \"\",\n };\n writeFileSync(sentinelPath, `${JSON.stringify(sentinel, null, 2)}\\n`);\n}\n\n// ─── Rendering ─────────────────────────────────────────────────────────────\n\ninterface RenderContext {\n namespace: string;\n memories: MemoryFile[];\n rolloutSummaries: RolloutSummaryInput[];\n // Historically this interface exposed a `now: Date` field, but neither\n // `renderMemoryMd` nor `renderMemorySummary` ever read it (rendered output\n // is purely a function of `namespace`, `memories`, and `rolloutSummaries`).\n // The field was flagged as dead weight in PR #392 review and removed.\n // If a future renderer needs a timestamp, re-add it here and update both\n // call sites and the schema test.\n}\n\ninterface SummaryRenderContext extends RenderContext {\n maxTokens: number;\n}\n\n/**\n * Render `memory_summary.md` — the always-loaded file.\n * Budget-capped at `maxTokens` whitespace tokens.\n */\nexport function renderMemorySummary(ctx: SummaryRenderContext): string {\n const lines: string[] = [];\n lines.push(\"# Memory Summary\");\n lines.push(\"\");\n lines.push(`_namespace: ${ctx.namespace}_`);\n lines.push(`_source: remnic_`);\n lines.push(\"\");\n\n const highValue = selectSummaryMemories(ctx.memories, 12);\n if (highValue.length > 0) {\n lines.push(\"## Top memories\");\n lines.push(\"\");\n for (const mem of highValue) {\n lines.push(`- ${oneLineSummary(mem)}`);\n }\n lines.push(\"\");\n }\n\n if (ctx.rolloutSummaries.length > 0) {\n lines.push(\"## Recent rollouts\");\n lines.push(\"\");\n const sorted = [...ctx.rolloutSummaries]\n .sort((a, b) => (b.updatedAt ?? \"\").localeCompare(a.updatedAt ?? \"\"))\n .slice(0, 5);\n for (const r of sorted) {\n const when = r.updatedAt ? ` (${r.updatedAt})` : \"\";\n lines.push(`- ${r.slug}${when}`);\n }\n lines.push(\"\");\n }\n\n const full = lines.join(\"\\n\").replace(/\\n+$/u, \"\\n\");\n return truncateToTokenBudget(full, ctx.maxTokens);\n}\n\n/**\n * Render `MEMORY.md` — the searchable handbook in Codex's task-group schema.\n */\nexport function renderMemoryMd(ctx: RenderContext): string {\n const lines: string[] = [];\n lines.push(`# Task Group: ${ctx.namespace}`);\n lines.push(`scope: ${ctx.namespace}`);\n lines.push(`applies_to: cwd=*; reuse_rule=namespace-match`);\n lines.push(\"\");\n\n // One \"task\" per top-level topic cluster. For the first cut we group by\n // memory category so the schema validator always sees at least one task.\n const byCategory = groupMemoriesByCategory(ctx.memories);\n let taskIndex = 1;\n if (byCategory.size === 0) {\n lines.push(`## Task ${taskIndex}: baseline — no memories yet`);\n lines.push(\"\");\n lines.push(\"### rollout_summary_files\");\n for (const r of ctx.rolloutSummaries) {\n lines.push(\n `- rollout_summaries/${sanitizeSlug(r.slug)}.md (cwd=${r.cwd ?? \"*\"}, rollout_path=${r.rolloutPath ?? \"\"}, updated_at=${r.updatedAt ?? \"\"}, thread_id=${r.threadId ?? \"\"})`,\n );\n }\n if (ctx.rolloutSummaries.length === 0) {\n lines.push(\"- (none)\");\n }\n lines.push(\"\");\n lines.push(\"### keywords\");\n lines.push(`- ${ctx.namespace}`);\n lines.push(\"\");\n taskIndex += 1;\n } else {\n for (const [category, mems] of byCategory) {\n lines.push(`## Task ${taskIndex}: ${category} memories, outcome=surface-to-codex`);\n lines.push(\"\");\n lines.push(\"### rollout_summary_files\");\n const relevantRollouts = ctx.rolloutSummaries.slice(0, 5);\n if (relevantRollouts.length === 0) {\n lines.push(\"- (none)\");\n } else {\n for (const r of relevantRollouts) {\n lines.push(\n `- rollout_summaries/${sanitizeSlug(r.slug)}.md (cwd=${r.cwd ?? \"*\"}, rollout_path=${r.rolloutPath ?? \"\"}, updated_at=${r.updatedAt ?? \"\"}, thread_id=${r.threadId ?? \"\"})`,\n );\n }\n }\n lines.push(\"\");\n lines.push(\"### keywords\");\n const keywords = collectKeywords(mems, category, ctx.namespace);\n lines.push(`- ${keywords.join(\", \")}`);\n lines.push(\"\");\n taskIndex += 1;\n }\n }\n\n lines.push(\"## User preferences\");\n const prefs = pickCategory(ctx.memories, [\"preference\"]);\n if (prefs.length === 0) {\n lines.push(\"- (none recorded)\");\n } else {\n for (const pref of prefs.slice(0, 20)) {\n lines.push(`- ${oneLineSummary(pref)}`);\n }\n }\n lines.push(\"\");\n\n lines.push(\"## Reusable knowledge\");\n const knowledge = pickCategory(ctx.memories, [\"fact\", \"decision\", \"principle\", \"rule\", \"skill\"]);\n if (knowledge.length === 0) {\n lines.push(\"- (none recorded)\");\n } else {\n for (const mem of knowledge.slice(0, 30)) {\n lines.push(`- ${oneLineSummary(mem)}`);\n }\n }\n lines.push(\"\");\n\n lines.push(\"## Failures and how to do differently\");\n const corrections = pickCategory(ctx.memories, [\"correction\"]);\n if (corrections.length === 0) {\n lines.push(\"- (none recorded)\");\n } else {\n for (const mem of corrections.slice(0, 20)) {\n lines.push(`- ${oneLineSummary(mem)}`);\n }\n }\n lines.push(\"\");\n\n return lines.join(\"\\n\");\n}\n\n/** Render `raw_memories.md` — mechanical dump, latest first. */\nexport function renderRawMemories(ctx: { memories: MemoryFile[] }): string {\n const sorted = [...ctx.memories].sort((a, b) => {\n const aUpdated = a.frontmatter.updated ?? a.frontmatter.created ?? \"\";\n const bUpdated = b.frontmatter.updated ?? b.frontmatter.created ?? \"\";\n return bUpdated.localeCompare(aUpdated);\n });\n\n const lines: string[] = [\"# Raw Memories\", \"\", \"_source: remnic — latest first_\", \"\"];\n for (const mem of sorted) {\n const fm = mem.frontmatter;\n const id = fm.id ?? \"unknown\";\n const category = fm.category ?? \"unknown\";\n const updated = fm.updated ?? fm.created ?? \"\";\n lines.push(`## ${id} (${category}, updated=${updated})`);\n lines.push(\"\");\n lines.push(mem.content.trim());\n lines.push(\"\");\n }\n return lines.join(\"\\n\");\n}\n\n/** Render a single rollout summary file. */\nexport function renderRolloutSummary(input: RolloutSummaryInput): string {\n const lines: string[] = [];\n lines.push(`# Rollout Summary: ${input.slug}`);\n lines.push(\"\");\n const meta: string[] = [];\n if (input.cwd) meta.push(`cwd=${input.cwd}`);\n if (input.rolloutPath) meta.push(`rollout_path=${input.rolloutPath}`);\n if (input.updatedAt) meta.push(`updated_at=${input.updatedAt}`);\n if (input.threadId) meta.push(`thread_id=${input.threadId}`);\n if (meta.length > 0) {\n lines.push(`_${meta.join(\"; \")}_`);\n lines.push(\"\");\n }\n if (input.keywords && input.keywords.length > 0) {\n lines.push(`**keywords:** ${input.keywords.join(\", \")}`);\n lines.push(\"\");\n }\n lines.push(input.body.trim());\n lines.push(\"\");\n return lines.join(\"\\n\");\n}\n\n// ─── Schema validation ─────────────────────────────────────────────────────\n\nexport interface MemoryMdValidation {\n valid: boolean;\n errors: string[];\n}\n\n/**\n * Validate that a rendered `MEMORY.md` matches Codex's task-group schema.\n * We enforce the minimum set of structural requirements called out in #378:\n *\n * - one `# Task Group:` header\n * - `scope:` and `applies_to:` lines directly beneath it\n * - at least one `## Task N:` section\n * - each task section has `### rollout_summary_files` and `### keywords`\n * - `## User preferences`, `## Reusable knowledge`,\n * `## Failures and how to do differently` sections all present\n */\nexport function validateMemoryMd(content: string): MemoryMdValidation {\n const errors: string[] = [];\n const lines = content.split(/\\r?\\n/u);\n\n const taskGroupIndex = lines.findIndex((l) => /^#\\s+Task Group:\\s+\\S+/u.test(l));\n if (taskGroupIndex === -1) {\n errors.push(\"missing `# Task Group:` header\");\n } else {\n const tail = lines.slice(taskGroupIndex + 1, taskGroupIndex + 5);\n if (!tail.some((l) => /^scope:\\s*\\S+/u.test(l))) {\n errors.push(\"missing `scope:` line under Task Group header\");\n }\n if (!tail.some((l) => /^applies_to:\\s*\\S+/u.test(l))) {\n errors.push(\"missing `applies_to:` line under Task Group header\");\n }\n }\n\n const taskHeaders = lines.filter((l) => /^##\\s+Task\\s+\\d+:/u.test(l));\n if (taskHeaders.length === 0) {\n errors.push(\"at least one `## Task N:` section is required\");\n }\n\n // For every task section, make sure we have rollout_summary_files + keywords\n // before the next `##` header at the same level.\n const sectionRegex = /^##\\s+/u;\n for (let i = 0; i < lines.length; i++) {\n if (!/^##\\s+Task\\s+\\d+:/u.test(lines[i])) continue;\n let hasRollout = false;\n let hasKeywords = false;\n for (let j = i + 1; j < lines.length; j++) {\n if (sectionRegex.test(lines[j])) break;\n if (/^###\\s+rollout_summary_files\\s*$/u.test(lines[j])) hasRollout = true;\n if (/^###\\s+keywords\\s*$/u.test(lines[j])) hasKeywords = true;\n }\n if (!hasRollout) errors.push(`task block at line ${i + 1} missing \\`### rollout_summary_files\\``);\n if (!hasKeywords) errors.push(`task block at line ${i + 1} missing \\`### keywords\\``);\n }\n\n const requiredSections = [\n /^##\\s+User preferences\\s*$/u,\n /^##\\s+Reusable knowledge\\s*$/u,\n /^##\\s+Failures and how to do differently\\s*$/u,\n ];\n for (const re of requiredSections) {\n if (!lines.some((l) => re.test(l))) {\n errors.push(`missing required section: ${re.source}`);\n }\n }\n\n return { valid: errors.length === 0, errors };\n}\n\n// ─── Helpers ───────────────────────────────────────────────────────────────\n\nfunction resolveCodexHome(override?: string): string {\n if (override && override.trim().length > 0) return override;\n const fromEnv = readEnvVar(\"CODEX_HOME\");\n if (fromEnv && fromEnv.trim().length > 0) return fromEnv;\n return path.join(resolveHomeDir(), \".codex\");\n}\n\nfunction readSentinel(sentinelPath: string): SentinelFile | null {\n if (!existsSync(sentinelPath)) return null;\n try {\n const raw = readFileSync(sentinelPath, \"utf-8\");\n const parsed = JSON.parse(raw) as Partial<SentinelFile>;\n if (typeof parsed !== \"object\" || parsed === null) return null;\n return {\n version: typeof parsed.version === \"number\" ? parsed.version : MATERIALIZE_VERSION,\n namespace: typeof parsed.namespace === \"string\" ? parsed.namespace : \"\",\n updated_at: typeof parsed.updated_at === \"string\" ? parsed.updated_at : \"\",\n content_hash: typeof parsed.content_hash === \"string\" ? parsed.content_hash : \"\",\n };\n } catch {\n return null;\n }\n}\n\nfunction selectSummaryMemories(memories: MemoryFile[], limit: number): MemoryFile[] {\n const scored = memories\n .filter((m) => !m.frontmatter.status || m.frontmatter.status === \"active\")\n .map((m) => {\n const confidence = typeof m.frontmatter.confidence === \"number\" ? m.frontmatter.confidence : 0;\n const importance =\n typeof m.frontmatter.importance === \"object\" &&\n m.frontmatter.importance !== null &&\n typeof (m.frontmatter.importance as { score?: number }).score === \"number\"\n ? ((m.frontmatter.importance as { score: number }).score ?? 0)\n : 0;\n const updated = m.frontmatter.updated ?? m.frontmatter.created ?? \"\";\n return { memory: m, score: importance * 2 + confidence, updated };\n });\n\n scored.sort((a, b) => {\n if (b.score !== a.score) return b.score - a.score;\n return b.updated.localeCompare(a.updated);\n });\n\n return scored.slice(0, limit).map((s) => s.memory);\n}\n\nfunction oneLineSummary(memory: MemoryFile): string {\n const raw = memory.content.replace(/\\s+/gu, \" \").trim();\n if (raw.length <= 160) return raw;\n return `${raw.slice(0, 157)}...`;\n}\n\nfunction groupMemoriesByCategory(memories: MemoryFile[]): Map<string, MemoryFile[]> {\n const map = new Map<string, MemoryFile[]>();\n for (const memory of memories) {\n if (memory.frontmatter.status && memory.frontmatter.status !== \"active\") continue;\n const category = memory.frontmatter.category ?? \"unknown\";\n const list = map.get(category) ?? [];\n list.push(memory);\n map.set(category, list);\n }\n return map;\n}\n\nfunction pickCategory(memories: MemoryFile[], categories: string[]): MemoryFile[] {\n const allowed = new Set(categories);\n return memories.filter(\n (m) =>\n (!m.frontmatter.status || m.frontmatter.status === \"active\") &&\n allowed.has(m.frontmatter.category ?? \"\"),\n );\n}\n\nfunction collectKeywords(memories: MemoryFile[], category: string, namespace: string): string[] {\n const keywords = new Set<string>();\n keywords.add(category);\n keywords.add(namespace);\n for (const mem of memories.slice(0, 10)) {\n for (const tag of mem.frontmatter.tags ?? []) {\n if (typeof tag === \"string\" && tag.trim().length > 0) keywords.add(tag.trim());\n }\n }\n return [...keywords].slice(0, 16);\n}\n\nfunction pruneRollouts(\n rollouts: RolloutSummaryInput[],\n retentionDays: number,\n now: Date,\n): RolloutSummaryInput[] {\n // Negative retention → \"infinite retention\" escape hatch. `parseConfig`\n // clamps the knob to >= 0, so in practice only callers passing a negative\n // value intentionally get the all-pass behavior.\n if (retentionDays < 0) return rollouts;\n // retentionDays === 0 → cutoff is exactly `now`, which prunes every\n // rollout whose `updatedAt` is in the past (i.e. all of them in practice).\n // This matches the documented semantics of \"retain for 0 days\".\n const cutoffMs = now.getTime() - retentionDays * 24 * 60 * 60 * 1000;\n return rollouts.filter((r) => {\n if (!r.updatedAt) return true;\n const t = Date.parse(r.updatedAt);\n if (!Number.isFinite(t)) return true;\n return t >= cutoffMs;\n });\n}\n\nfunction sanitizeSlug(slug: string): string {\n return slug\n .toLowerCase()\n .replace(/[^a-z0-9._-]+/gu, \"-\")\n .replace(/^-+|-+$/gu, \"\")\n .slice(0, 96)\n || \"rollout\";\n}\n\n/**\n * Whitespace-tokenized approximation used by the budget check. Matches the\n * simple heuristic Codex's usage.rs reporting uses for the \"5000 token\"\n * memory_summary cap.\n */\nexport function approximateTokenCount(text: string): number {\n const trimmed = text.trim();\n if (trimmed.length === 0) return 0;\n return trimmed.split(/\\s+/u).length;\n}\n\n/**\n * Truncate `text` so it fits under `maxTokens` whitespace tokens. We drop\n * trailing lines until we're under the budget and then append an ellipsis\n * marker so downstream readers can see that truncation happened.\n */\nexport function truncateToTokenBudget(text: string, maxTokens: number): string {\n if (maxTokens <= 0) return \"\";\n if (approximateTokenCount(text) <= maxTokens) return text;\n\n // Reserve headroom for the truncation marker so the line-preserving path\n // can actually fit the marker without flipping to the hard-cut fallback.\n // Both markers are counted with the same whitespace heuristic the budget\n // check uses, so the arithmetic stays consistent.\n const lineMarker = \"_[truncated for summary budget]_\";\n const tailMarker = \"[truncated]\";\n const lineMarkerTokens = approximateTokenCount(lineMarker);\n const tailMarkerTokens = approximateTokenCount(tailMarker);\n\n const lines = text.split(/\\r?\\n/u);\n const lineBudget = Math.max(0, maxTokens - lineMarkerTokens);\n while (lines.length > 0 && approximateTokenCount(lines.join(\"\\n\")) > lineBudget) {\n lines.pop();\n }\n lines.push(lineMarker);\n let result = lines.join(\"\\n\");\n\n // If a single huge line still blows the budget, hard-cut tokens. Reserve\n // space for the tail marker's own token count so the final string stays\n // within maxTokens rather than sneaking over by a few tokens.\n if (approximateTokenCount(result) > maxTokens) {\n const tokens = result.split(/\\s+/u);\n const keep = Math.max(0, maxTokens - tailMarkerTokens);\n result = keep > 0 ? `${tokens.slice(0, keep).join(\" \")} ${tailMarker}` : tailMarker;\n }\n return result;\n}\n\nfunction computeContentHash(input: {\n namespace: string;\n memorySummary: string;\n memoryMd: string;\n rawMemories: string;\n rolloutFiles: Array<{ name: string; body: string }>;\n}): string {\n const hash = createHash(\"sha256\");\n hash.update(`v${MATERIALIZE_VERSION}\\n`);\n hash.update(`namespace=${input.namespace}\\n`);\n hash.update(\"---memory_summary---\\n\");\n hash.update(input.memorySummary);\n hash.update(\"\\n---memory_md---\\n\");\n hash.update(input.memoryMd);\n hash.update(\"\\n---raw_memories---\\n\");\n hash.update(input.rawMemories);\n const sortedRollouts = [...input.rolloutFiles].sort((a, b) => a.name.localeCompare(b.name));\n for (const r of sortedRollouts) {\n hash.update(`\\n---rollout:${r.name}---\\n`);\n hash.update(r.body);\n }\n return hash.digest(\"hex\");\n}\n\n// ─── Stat helper for tests / debugging ─────────────────────────────────────\n\n/**\n * Return basic stats about a materialized memories dir. Useful for tests and\n * debug CLI output. Returns `null` if the dir does not exist.\n */\nexport function describeMemoriesDir(memoriesDir: string): {\n exists: boolean;\n hasSentinel: boolean;\n files: string[];\n sentinel: SentinelFile | null;\n} | null {\n if (!existsSync(memoriesDir)) return null;\n const sentinelPath = path.join(memoriesDir, SENTINEL_FILE);\n const sentinel = readSentinel(sentinelPath);\n const files: string[] = [];\n for (const entry of readdirSync(memoriesDir, { withFileTypes: true })) {\n if (entry.isFile() && OWNED_FILES.has(entry.name)) files.push(entry.name);\n }\n const rolloutsDir = path.join(memoriesDir, ROLLOUT_SUBDIR);\n if (existsSync(rolloutsDir)) {\n try {\n for (const entry of readdirSync(rolloutsDir, { withFileTypes: true })) {\n if (entry.isFile() && entry.name.endsWith(\".md\")) {\n files.push(path.join(ROLLOUT_SUBDIR, entry.name));\n }\n }\n } catch {\n // ignore\n }\n }\n return {\n exists: true,\n hasSentinel: sentinel !== null,\n files: files.sort(),\n sentinel,\n };\n}\n","/**\n * semantic-consolidation.ts — Semantic Consolidation Engine\n *\n * Finds clusters of semantically similar memories using token overlap,\n * synthesizes canonical versions via LLM, and archives the originals.\n * Reduces memory store bloat while preserving all unique information.\n */\n\nimport type { MemoryFile, PluginConfig } from \"./types.js\";\nimport { normalizeRecallTokens, countRecallTokenOverlap } from \"./recall-tokenization.js\";\nimport { runPostConsolidationMaterialize } from \"./connectors/codex-materialize-runner.js\";\nimport type { MaterializeResult, RolloutSummaryInput } from \"./connectors/codex-materialize.js\";\nimport { discoverMemoryExtensions, renderExtensionsBlock, resolveExtensionsRoot } from \"./memory-extension-host/index.js\";\nimport { log } from \"./logger.js\";\n\n// Re-export resolveExtensionsRoot for backward compatibility — existing\n// consumers that import from semantic-consolidation.ts continue to work.\nexport { resolveExtensionsRoot } from \"./memory-extension-host/index.js\";\n\n// Operator vocabulary (issue #561). The types and validators live in a\n// standalone `consolidation-operator.ts` module so `storage.ts` can import\n// them without creating a `storage → semantic-consolidation →\n// codex-materialize-runner → storage` cycle. Re-exported here so existing\n// consumers that reach for `./semantic-consolidation.js` keep working.\nexport {\n CONSOLIDATION_OPERATORS,\n isConsolidationOperator,\n isSemanticConsolidationLlmOperator,\n isValidDerivedFromEntry,\n type ConsolidationOperator,\n type SemanticConsolidationLlmOperator,\n} from \"./consolidation-operator.js\";\n\nimport {\n CONSOLIDATION_OPERATORS as _CONSOLIDATION_OPERATORS,\n isConsolidationOperator as _isConsolidationOperator,\n isSemanticConsolidationLlmOperator as _isSemanticConsolidationLlmOperator,\n type ConsolidationOperator as _ConsolidationOperator,\n type SemanticConsolidationLlmOperator as _SemanticConsolidationLlmOperator,\n} from \"./consolidation-operator.js\";\n\nexport interface ConsolidationCluster {\n category: string;\n memories: MemoryFile[];\n overlapScore: number;\n canonicalContent?: string;\n}\n\nexport interface SemanticConsolidationResult {\n clustersFound: number;\n memoriesConsolidated: number;\n memoriesArchived: number;\n errors: number;\n clusters: ConsolidationCluster[];\n}\n\n/**\n * Find clusters of semantically similar memories using token overlap.\n */\nexport function findSimilarClusters(\n memories: MemoryFile[],\n config: {\n threshold: number;\n minClusterSize: number;\n excludeCategories: string[];\n maxPerRun: number;\n },\n): ConsolidationCluster[] {\n const excluded = new Set(config.excludeCategories);\n\n // Group by category first\n const byCategory = new Map<string, MemoryFile[]>();\n for (const m of memories) {\n const cat = m.frontmatter.category;\n if (excluded.has(cat)) continue;\n if (m.frontmatter.status && m.frontmatter.status !== \"active\") continue;\n const list = byCategory.get(cat) ?? [];\n list.push(m);\n byCategory.set(cat, list);\n }\n\n const clusters: ConsolidationCluster[] = [];\n let totalCandidates = 0;\n\n for (const [category, mems] of byCategory) {\n if (totalCandidates >= config.maxPerRun) break;\n\n // Token-normalize all memories in this category\n const tokenized = mems.map((m) => ({\n memory: m,\n tokens: new Set(normalizeRecallTokens(m.content, [])),\n }));\n\n // Track which memories are already clustered\n const clustered = new Set<string>();\n\n for (let i = 0; i < tokenized.length && totalCandidates < config.maxPerRun; i++) {\n if (clustered.has(tokenized[i].memory.frontmatter.id)) continue;\n\n const cluster: MemoryFile[] = [tokenized[i].memory];\n let totalOverlap = 0;\n let comparisons = 0;\n\n for (let j = i + 1; j < tokenized.length; j++) {\n if (clustered.has(tokenized[j].memory.frontmatter.id)) continue;\n\n const aTokens = tokenized[i].tokens;\n const bTokens = tokenized[j].tokens;\n if (aTokens.size === 0 || bTokens.size === 0) continue;\n\n // Bidirectional overlap: what fraction of tokens are shared\n const overlap = countRecallTokenOverlap(aTokens, [...bTokens].join(\" \"));\n const maxTokens = Math.max(aTokens.size, bTokens.size);\n const score = maxTokens > 0 ? overlap / maxTokens : 0;\n\n if (score >= config.threshold) {\n cluster.push(tokenized[j].memory);\n totalOverlap += score;\n comparisons++;\n // Enforce maxPerRun within a single cluster\n if (totalCandidates + cluster.length >= config.maxPerRun) break;\n }\n }\n\n if (cluster.length >= config.minClusterSize) {\n for (const m of cluster) clustered.add(m.frontmatter.id);\n clusters.push({\n category,\n memories: cluster,\n overlapScore: comparisons > 0 ? totalOverlap / comparisons : 0,\n });\n totalCandidates += cluster.length;\n }\n }\n }\n\n return clusters;\n}\n\n/**\n * Build the LLM prompt for synthesizing a canonical memory from a cluster.\n */\nexport function buildConsolidationPrompt(cluster: ConsolidationCluster): string {\n const memoryTexts = cluster.memories\n .map(\n (m, i) =>\n `Memory ${i + 1} (${m.frontmatter.id}, created ${m.frontmatter.created}):\\n${m.content}`,\n )\n .join(\"\\n\\n\");\n\n return `You are a memory consolidation system. The following ${cluster.memories.length} memories in the \"${cluster.category}\" category contain overlapping information.\n\nSynthesize them into ONE canonical memory that:\n1. Preserves ALL unique information from every source memory\n2. Removes redundancy and repetition\n3. Uses clear, concise language\n4. Maintains the same category and tone\n5. Does NOT add information that isn't in the sources\n\n${memoryTexts}\n\nWrite ONLY the consolidated memory content (no metadata, no explanation, no preamble):`;\n}\n\n/**\n * Parse the LLM response to extract the canonical content.\n */\nexport function parseConsolidationResponse(response: string): string {\n return response.trim();\n}\n\n// ─── Operator-aware prompt / parse (issue #561 PR 3) ─────────────────────────\n\n/**\n * Structured result from an operator-aware consolidation LLM call.\n *\n * - `operator` — the consolidation operator the LLM chose for this cluster.\n * Falls back to the heuristic default when the LLM omits or returns an\n * unknown value (the parser never surfaces an invalid operator; see\n * `parseOperatorAwareConsolidationResponse`).\n * - `output` — the canonical content (same format the legacy prompt\n * returns). Callers persist this as the body of the new memory.\n */\nexport interface OperatorAwareConsolidationResult {\n // Restricted to the LLM-allowed subset (Cursor Bugbot, PR #730):\n // `pattern-reinforcement` is reserved for the maintenance job and\n // must never reach this struct from a consolidation LLM response.\n operator: _SemanticConsolidationLlmOperator;\n output: string;\n}\n\n/**\n * Heuristic default operator for a cluster. Used as the fallback when the\n * LLM does not return a parseable operator, and as the \"floor\" decision in\n * `parseOperatorAwareConsolidationResponse`.\n *\n * Current heuristic (kept deliberately conservative — PR 3 only):\n *\n * - Two or more memories being collapsed into one canonical blob is a\n * MERGE by definition. This is the path the current clustering\n * pipeline exercises.\n * - A cluster of size 1 that still reaches consolidation (future path,\n * e.g. supersession of a single older memory by a newer value) is an\n * UPDATE.\n * - SPLIT is never selected by the heuristic because the current write\n * path emits exactly one canonical memory per cluster. The prompt\n * reserves SPLIT for future cluster shapes where the LLM decides one\n * logical source actually encodes several distinct facts — at which\n * point the orchestrator would need to write multiple outputs.\n */\nexport function chooseConsolidationOperator(\n cluster: ConsolidationCluster,\n): _SemanticConsolidationLlmOperator {\n if (cluster.memories.length <= 1) return \"update\";\n return \"merge\";\n}\n\n/**\n * Build the operator-aware LLM prompt. The LLM is asked to return a\n * JSON object `{ \"operator\": <split|merge|update>, \"output\": <content> }`.\n *\n * The prompt is additive: it still asks for a single canonical blob under\n * `output`, so the upstream write path does not change. Future expansions\n * (SPLIT emitting multiple outputs) are explicitly documented as\n * out-of-scope for the parser — `parseOperatorAwareConsolidationResponse`\n * collapses any SPLIT response into a single canonical output for now.\n */\nexport function buildOperatorAwareConsolidationPrompt(\n cluster: ConsolidationCluster,\n): string {\n const memoryTexts = cluster.memories\n .map(\n (m, i) =>\n `Memory ${i + 1} (${m.frontmatter.id}, created ${m.frontmatter.created}):\\n${m.content}`,\n )\n .join(\"\\n\\n\");\n\n return `You are a memory consolidation system. The following ${cluster.memories.length} memories in the \"${cluster.category}\" category contain overlapping information.\n\nPick exactly ONE consolidation operator for this cluster and return a JSON object.\n\nOperator vocabulary:\n - \"merge\" — multiple distinct source memories overlap and should be collapsed into one canonical memory (most common).\n - \"update\" — one source memory carries a stale value that a newer source supersedes within the same logical fact.\n - \"split\" — a single logical source really encodes multiple distinct facts that should be separated (rare; if you pick split, still emit ONE canonical body — the write path will chunk it later).\n\nOutput JSON ONLY, no prose before or after. The \"operator\" key MUST be set to exactly one of the three strings \"merge\", \"update\", or \"split\" — never a pipe-separated placeholder like \"merge|update|split\". Example shape:\n {\n \"operator\": \"merge\",\n \"output\": \"<the canonical memory text>\"\n }\n\nThe \"output\" value must:\n1. Preserve ALL unique information from every source memory\n2. Remove redundancy and repetition\n3. Use clear, concise language\n4. Match the \"${cluster.category}\" category and tone\n5. NOT add information that isn't in the sources\n\n${memoryTexts}\n\nReturn ONLY the JSON object:`;\n}\n\n/**\n * Parse an operator-aware consolidation response.\n *\n * Contract:\n * - Accepts strict JSON `{ \"operator\": \"...\", \"output\": \"...\" }`.\n * - Tolerates a JSON payload wrapped in a fenced code block (```json ...```).\n * - Falls back to the heuristic operator when the JSON is malformed,\n * the `operator` field is missing / unknown, or the raw response is\n * a plain blob with no JSON at all. This keeps PR 3 backwards\n * compatible with older models that ignore the JSON instruction.\n * - Never throws. A missing / empty `output` field falls back to the\n * trimmed raw response so the caller still writes something rather\n * than dropping the cluster.\n */\nexport function parseOperatorAwareConsolidationResponse(\n response: string,\n cluster: ConsolidationCluster,\n): OperatorAwareConsolidationResult {\n const fallback: OperatorAwareConsolidationResult = {\n operator: chooseConsolidationOperator(cluster),\n output: response.trim(),\n };\n\n const trimmed = response.trim();\n if (trimmed.length === 0) return fallback;\n\n // Strip a fenced code block if present.\n const fenced = /^```(?:json)?\\s*([\\s\\S]*?)```\\s*$/u.exec(trimmed);\n const payload = fenced ? fenced[1].trim() : trimmed;\n\n // Find a balanced brace-delimited JSON object that has an `operator`\n // key (PR #632 round-4 review, codex P1). A first/last-brace slice\n // breaks when the model prepends an earlier brace block (e.g.\n // `Example: {\"note\":\"...\"} ... {\"operator\":\"merge\",...}`). Walk the\n // payload tracking nesting + string/escape state and skip past\n // objects that don't look like our target shape.\n const parsed = findLastJsonObjectWithOperator(payload);\n if (parsed === undefined) return fallback;\n if (typeof parsed !== \"object\" || parsed === null) return fallback;\n\n const obj = parsed as Record<string, unknown>;\n const rawOperator = typeof obj.operator === \"string\" ? obj.operator.trim().toLowerCase() : \"\";\n const rawOutput = typeof obj.output === \"string\" ? obj.output : \"\";\n\n // Narrow gate (Cursor Bugbot review on PR #730 head `aa1c2a8`):\n // accept ONLY the legacy split/merge/update LLM vocabulary here.\n // `pattern-reinforcement` joined the broader `ConsolidationOperator`\n // type in #687 PR 2/4 but is reserved for the maintenance job — if\n // an LLM hallucinates that operator we must NOT promote it onto\n // `derived_via`.\n const operator = _isSemanticConsolidationLlmOperator(rawOperator)\n ? rawOperator\n : chooseConsolidationOperator(cluster);\n const output = rawOutput.trim().length > 0 ? rawOutput.trim() : response.trim();\n\n return { operator, output };\n}\n\n/**\n * Walk `text`, find all balanced top-level `{ ... }` blocks whose\n * JSON.parse result is an object with an `operator` key, and return\n * the LAST one (function name reflects this — PR #632 review,\n * cursor Low). Returns `undefined` when nothing matches. Tracks\n * string state + escape sequences so braces inside string values\n * don't throw off the depth counter.\n *\n * Used by `parseOperatorAwareConsolidationResponse` to tolerate models\n * that prepend an explanatory JSON example block before the real\n * payload (PR #632 round-4 + round-5 review, codex P1). We take the\n * LAST candidate so that an instructional example with an `operator`\n * key ahead of the real answer doesn't steal precedence — models\n * typically write the example first and the real answer last.\n */\nfunction findLastJsonObjectWithOperator(text: string): unknown {\n let searchFrom = 0;\n let last: unknown = undefined;\n while (searchFrom < text.length) {\n const start = text.indexOf(\"{\", searchFrom);\n if (start < 0) return last;\n let depth = 0;\n let inString = false;\n let escape = false;\n let closed = false;\n let endIdx = -1;\n for (let i = start; i < text.length; i++) {\n const ch = text[i];\n if (inString) {\n if (escape) {\n escape = false;\n } else if (ch === \"\\\\\") {\n escape = true;\n } else if (ch === '\"') {\n inString = false;\n }\n continue;\n }\n if (ch === '\"') {\n inString = true;\n } else if (ch === \"{\") {\n depth += 1;\n } else if (ch === \"}\") {\n depth -= 1;\n if (depth === 0) {\n closed = true;\n endIdx = i;\n break;\n }\n }\n }\n if (!closed) return last;\n const slice = text.slice(start, endIdx + 1);\n try {\n const parsed = JSON.parse(slice);\n if (\n typeof parsed === \"object\" &&\n parsed !== null &&\n \"operator\" in (parsed as Record<string, unknown>)\n ) {\n last = parsed;\n }\n } catch {\n // Fall through to the next candidate.\n }\n searchFrom = endIdx + 1;\n }\n return last;\n}\n\n// Silence unused-import warnings when tsup tree-shakes: these are used\n// above in chooseConsolidationOperator + parse helpers.\nvoid _CONSOLIDATION_OPERATORS;\n\n/**\n * Discover extensions and build the block to append to a consolidation prompt.\n * Returns \"\" when extensions are disabled or none are found.\n */\nexport async function buildExtensionsBlockForConsolidation(\n config: PluginConfig,\n): Promise<string> {\n if (!config.memoryExtensionsEnabled) return \"\";\n const root = resolveExtensionsRoot(config);\n const extensions = await discoverMemoryExtensions(root, log);\n if (extensions.length === 0) return \"\";\n return renderExtensionsBlock(extensions);\n}\n\n/**\n * Optional post-consolidation hook — materializes the namespace into Codex's\n * native memory layout when the consolidation run finishes. Kept here (rather\n * than in orchestrator.ts) so #378 doesn't conflict with Wave 1 edits.\n *\n * Safe to call regardless of config state: honors `codexMaterializeMemories`\n * and `codexMaterializeOnConsolidation` and silently becomes a no-op when\n * either is disabled.\n */\nexport async function materializeAfterSemanticConsolidation(options: {\n config: PluginConfig;\n namespace?: string;\n memories?: MemoryFile[];\n memoryDir?: string;\n codexHome?: string;\n rolloutSummaries?: RolloutSummaryInput[];\n now?: Date;\n}): Promise<MaterializeResult | null> {\n // Delegates to the shared post-consolidation helper so semantic and causal\n // flows stay in lock-step — any guard/logging change happens in one place.\n return runPostConsolidationMaterialize(\"[semantic-consolidation]\", options);\n}\n"],"mappings":"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","names":["path","existsSync","path","existsSync"]}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
|
+
DERIVED_FROM_MEMORY_ID_RE,
|
|
2
3
|
isConsolidationOperator
|
|
3
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-G7D6GZ5J.js";
|
|
4
5
|
import {
|
|
5
6
|
sidecarKey
|
|
6
7
|
} from "./chunk-FAAFWE4G.js";
|
|
@@ -215,7 +216,7 @@ async function runConsolidationProvenanceCheck(options) {
|
|
|
215
216
|
continue;
|
|
216
217
|
}
|
|
217
218
|
if (!derivedFrom.includes(tok)) {
|
|
218
|
-
if (!/^(.+):(\d+)$/u.test(tok)) {
|
|
219
|
+
if (!/^(.+):(\d+)$/u.test(tok) && !DERIVED_FROM_MEMORY_ID_RE.test(tok)) {
|
|
219
220
|
report.issues.push({
|
|
220
221
|
memoryPath: memory.path,
|
|
221
222
|
memoryId: fm.id,
|
|
@@ -237,6 +238,9 @@ async function runConsolidationProvenanceCheck(options) {
|
|
|
237
238
|
}
|
|
238
239
|
if (hasFrom) {
|
|
239
240
|
for (const entry of derivedFrom) {
|
|
241
|
+
if (derivedVia === "pattern-reinforcement" && DERIVED_FROM_MEMORY_ID_RE.test(entry)) {
|
|
242
|
+
continue;
|
|
243
|
+
}
|
|
240
244
|
const resolved = resolveSnapshotPath(memoryDir, sidecarDir, entry);
|
|
241
245
|
if (!resolved.ok) {
|
|
242
246
|
report.issues.push({
|
|
@@ -319,4 +323,4 @@ async function* walkMarkdownFiles(root) {
|
|
|
319
323
|
export {
|
|
320
324
|
runConsolidationProvenanceCheck
|
|
321
325
|
};
|
|
322
|
-
//# sourceMappingURL=chunk-
|
|
326
|
+
//# sourceMappingURL=chunk-5HRY2WRF.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/consolidation-provenance-check.ts"],"sourcesContent":["/**\n * Consolidation provenance integrity check (issue #561 PR 4).\n *\n * Validates that every memory carrying consolidation provenance frontmatter\n * (`derived_from`, `derived_via`) resolves to real data:\n *\n * - Each `derived_from` entry `\"<path>:<version>\"` must name a\n * page-version snapshot that exists on disk (via the sidecar layout\n * documented in `page-versioning.ts`).\n * - Each `derived_via` must be one of the known\n * `ConsolidationOperator` values — malformed values are surfaced as\n * warnings rather than crashes so legacy or future operators survive a\n * rollback.\n *\n * Non-fatal: every failure renders a warning with the offending file path\n * and a human-readable reason. Integrity problems are informational for\n * now — we do not auto-heal or archive broken memories.\n */\n\nimport path from \"node:path\";\nimport { access, readdir, readFile, stat } from \"node:fs/promises\";\nimport { constants as fsConstants } from \"node:fs\";\nimport type { StorageManager } from \"./storage.js\";\nimport {\n DERIVED_FROM_MEMORY_ID_RE,\n isConsolidationOperator,\n} from \"./consolidation-operator.js\";\n// Import the canonical `sidecarKey` from page-versioning (PR #634\n// review, cursor Medium) so a future key-format change stays in\n// lock-step with the doctor scan.\nimport { sidecarKey } from \"./page-versioning.js\";\n\n/**\n * Regex to spot a `derived_via: <value>` line in the raw YAML frontmatter\n * between the opening and first closing `---` delimiters. We use the raw\n * text rather than the parsed `frontmatter.derived_via` because the\n * read-path parser coerces unknown values back to `undefined` — that\n * would silently hide corrupted-or-future operators from the doctor scan\n * (PR #634 review feedback, codex P2).\n */\n// Allow empty capture groups so truncated/blank `derived_via:` and\n// `derived_from:` lines (key present, no value) are distinguishable\n// from \"key missing entirely\" (regex returns null). Optional\n// leading whitespace accepts indented keys which `parseFrontmatter`\n// also accepts (PR #634 round-6 review, codex P2).\nconst DERIVED_VIA_RAW_RE = /^[\\t ]*derived_via:[\\t ]*(.*)$/mu;\nconst DERIVED_FROM_RAW_RE = /^[\\t ]*derived_from:[\\t ]*(.*)$/mu;\n\n/**\n * Tokenize a YAML-block-style list under `key:` in the given\n * frontmatter slice. Looks for lines matching `^ - <value>` after a\n * `key:` line and before the next non-list line. Returns `null` when\n * the key is missing or the value is a scalar / flow list (no block\n * entries found).\n *\n * Only used for the mixed-list malformed-entry detection — it does\n * not try to decode YAML escape sequences since we only need the\n * entry count + raw token text to compare against the parsed array.\n */\nfunction tokenizeRawBlockList(fmSlice: string, key: string): string[] | null {\n const lines = fmSlice.split(\"\\n\");\n // Accept indented keys too — parseFrontmatter does (PR #634 round-7\n // review, codex P2 / cursor Low).\n const keyRe = new RegExp(`^[\\\\t ]*${key}:[\\\\t ]*(.*)$`, \"u\");\n let startIdx = -1;\n for (let i = 0; i < lines.length; i++) {\n const m = lines[i].match(keyRe);\n if (m) {\n if (m[1].trim().length === 0) {\n startIdx = i + 1;\n }\n break;\n }\n }\n if (startIdx < 0) return null;\n const items: string[] = [];\n for (let i = startIdx; i < lines.length; i++) {\n const line = lines[i];\n if (!/^\\s+-/.test(line)) break; // not a block-list entry\n const m = line.match(/^\\s+-\\s*(.*)$/u);\n if (!m) break;\n let tok = m[1].trim();\n if (\n (tok.startsWith('\"') && tok.endsWith('\"') && tok.length >= 2) ||\n (tok.startsWith(\"'\") && tok.endsWith(\"'\") && tok.length >= 2)\n ) {\n tok = tok.slice(1, -1);\n }\n items.push(tok);\n }\n return items.length > 0 ? items : null;\n}\n\n/**\n * Tokenize a YAML-flow-style list (`[\"a\", \"b\", ...]`) into a flat\n * string array. Returns `null` when the input isn't a flow list.\n * Best-effort — we don't implement a full YAML parser, just enough to\n * detect mixed valid/invalid entries for the doctor integrity check.\n */\nfunction tokenizeRawFlowList(raw: string): string[] | null {\n const trimmed = raw.trim();\n if (!trimmed.startsWith(\"[\") || !trimmed.endsWith(\"]\")) return null;\n const inner = trimmed.slice(1, -1);\n const parts: string[] = [];\n let current = \"\";\n let inSingle = false;\n let inDouble = false;\n for (let i = 0; i < inner.length; i++) {\n const ch = inner[i];\n if (inDouble) {\n if (ch === \"\\\\\" && i + 1 < inner.length) {\n current += inner[++i];\n continue;\n }\n if (ch === '\"') {\n inDouble = false;\n continue;\n }\n current += ch;\n } else if (inSingle) {\n if (ch === \"'\" && inner[i + 1] === \"'\") {\n current += \"'\";\n i++;\n continue;\n }\n if (ch === \"'\") {\n inSingle = false;\n continue;\n }\n current += ch;\n } else if (ch === '\"') {\n inDouble = true;\n } else if (ch === \"'\") {\n inSingle = true;\n } else if (ch === \",\") {\n parts.push(current.trim());\n current = \"\";\n } else {\n current += ch;\n }\n }\n if (current.trim().length > 0 || parts.length > 0) {\n parts.push(current.trim());\n }\n return parts;\n}\n\n/**\n * One integrity warning attached to a specific memory.\n */\nexport interface ConsolidationProvenanceIssue {\n /** Absolute path to the memory markdown file. */\n memoryPath: string;\n /** Memory id from frontmatter. */\n memoryId: string;\n /** Type of integrity issue. */\n kind:\n | \"derived_from_missing_snapshot\"\n | \"derived_from_malformed_entry\"\n | \"derived_via_unknown_operator\";\n /** Human-readable detail — includes the offending value when relevant. */\n detail: string;\n}\n\n/**\n * Summary of a provenance-integrity scan. Used by the operator-doctor\n * report and surfaced in the CLI output.\n */\nexport interface ConsolidationProvenanceReport {\n /** Total memories inspected. */\n scanned: number;\n /** Memories that carry `derived_from` and/or `derived_via`. */\n withProvenance: number;\n /** One entry per problem detected (may be empty). */\n issues: ConsolidationProvenanceIssue[];\n}\n\nconst DERIVED_FROM_ENTRY_RE = /^(.+):(\\d+)$/;\n\n/**\n * Build the on-disk snapshot path for a `\"<relpath>:<version>\"` entry,\n * relative to the given memory directory. Mirrors the layout documented\n * in `page-versioning.ts`:\n *\n * memoryDir/<sidecarDir>/<sidecarKey>/<version><ext>\n */\nfunction resolveSnapshotPath(\n memoryDir: string,\n sidecarDir: string,\n entry: string,\n): { ok: true; snapshotPath: string } | { ok: false; reason: string } {\n const match = entry.match(DERIVED_FROM_ENTRY_RE);\n if (!match) {\n return { ok: false, reason: `malformed entry (expected \"<path>:<version>\")` };\n }\n const pagePath = match[1];\n const versionId = match[2];\n const ext = path.extname(pagePath) || \".md\";\n const key = sidecarKey(pagePath);\n const snapshotPath = path.join(memoryDir, sidecarDir, key, `${versionId}${ext}`);\n return { ok: true, snapshotPath };\n}\n\n/**\n * Scan every memory under `storage` and flag consolidation-provenance\n * problems. Does not throw on individual failures — collects them in the\n * returned report.\n */\nexport async function runConsolidationProvenanceCheck(options: {\n storage: StorageManager;\n memoryDir: string;\n /**\n * Page-versioning sidecar directory name. Defaults to `.versions` —\n * matches the baked-in default used by `setVersioningConfig` when\n * versioning is enabled via config.\n */\n sidecarDir?: string;\n}): Promise<ConsolidationProvenanceReport> {\n const { storage, memoryDir } = options;\n const sidecarDir = options.sidecarDir ?? \".versions\";\n\n const report: ConsolidationProvenanceReport = {\n scanned: 0,\n withProvenance: 0,\n issues: [],\n };\n\n let memories;\n try {\n memories = await storage.readAllMemories();\n } catch {\n // If we can't enumerate memories at all, surface a single synthetic\n // issue rather than throwing — the doctor wrapper treats an empty\n // issues list as \"ok\" and we don't want a filesystem hiccup to crash\n // the whole diagnostic.\n return {\n scanned: 0,\n withProvenance: 0,\n issues: [\n {\n memoryPath: memoryDir,\n memoryId: \"(unreadable)\",\n kind: \"derived_from_malformed_entry\",\n detail: \"Could not enumerate memory directory to scan provenance.\",\n },\n ],\n };\n }\n\n for (const memory of memories) {\n report.scanned += 1;\n const fm = memory.frontmatter;\n const derivedFrom = fm.derived_from;\n const derivedVia = fm.derived_via;\n\n // Raw frontmatter values from disk — the read-path parser coerces\n // malformed `derived_from` and unknown `derived_via` back to\n // `undefined`, which would silently hide on-disk corruption from\n // the doctor scan (PR #634 review feedback, codex P2). We\n // re-extract both via regex so integrity issues are reported even\n // when the parser normalized them away. `rawDerivedVia` /\n // `rawDerivedFrom` being `\"\"` (empty string) represents a\n // corrupted file with the key present but the value truncated —\n // that's distinct from \"key missing entirely\" (undefined).\n let rawDerivedVia: string | undefined;\n let rawDerivedFrom: string | undefined;\n let rawDerivedViaKeyPresent = false;\n let rawDerivedFromKeyPresent = false;\n let duplicateViaKeys = false;\n let duplicateFromKeys = false;\n let viaMatchCount = 0;\n let fromMatchCount = 0;\n let fmSlice = \"\";\n try {\n const raw = await readFile(memory.path, \"utf-8\");\n const frontmatterEnd = raw.indexOf(\"\\n---\", raw.indexOf(\"---\") + 3);\n fmSlice = frontmatterEnd > 0 ? raw.slice(0, frontmatterEnd) : raw;\n // Use matchAll to find ALL occurrences of `derived_via` / `derived_from`\n // in the raw YAML. `parseFrontmatter` keeps the LAST assignment when\n // duplicate keys appear, so the doctor must read the last occurrence\n // to match what the storage reader actually uses (PR #634 review,\n // codex P2 — duplicate `derived_via` keys caused false-clean or\n // false-unknown-operator warnings depending on order).\n const viaMatches = [...fmSlice.matchAll(new RegExp(DERIVED_VIA_RAW_RE.source, DERIVED_VIA_RAW_RE.flags + \"g\"))];\n viaMatchCount = viaMatches.length;\n duplicateViaKeys = viaMatches.length > 1;\n if (viaMatches.length > 0) {\n rawDerivedViaKeyPresent = true;\n // Use the last occurrence — `parseFrontmatter` keeps the last\n // assignment when duplicate keys appear, so the doctor must\n // match that behavior to produce accurate warnings (PR #634\n // review, codex P2).\n const lastVia = viaMatches[viaMatches.length - 1];\n let val = lastVia[1].trim();\n if (\n (val.startsWith('\"') && val.endsWith('\"')) ||\n (val.startsWith(\"'\") && val.endsWith(\"'\"))\n ) {\n val = val.slice(1, -1);\n }\n rawDerivedVia = val;\n }\n const fromMatches = [...fmSlice.matchAll(new RegExp(DERIVED_FROM_RAW_RE.source, DERIVED_FROM_RAW_RE.flags + \"g\"))];\n fromMatchCount = fromMatches.length;\n duplicateFromKeys = fromMatches.length > 1;\n if (fromMatches.length > 0) {\n rawDerivedFromKeyPresent = true;\n const lastFrom = fromMatches[fromMatches.length - 1];\n rawDerivedFrom = lastFrom[1].trim();\n }\n } catch {\n // Fall through to the parsed values.\n }\n\n const hasFrom = Array.isArray(derivedFrom) && derivedFrom.length > 0;\n const hasVia = derivedVia !== undefined && derivedVia !== null;\n const hasRawVia = rawDerivedVia !== undefined && rawDerivedVia.length > 0;\n // A raw `derived_from` that the parser dropped indicates on-disk\n // corruption we must surface. We detect this by: (a) the raw YAML\n // contains a `derived_from:` key, AND (b) the parsed frontmatter\n // has no valid array. A scalar like `derived_from: facts/a.md:7`\n // (list brackets omitted) or a blank `derived_from:` both hit this\n // branch.\n const hasRawMalformedFrom = rawDerivedFromKeyPresent && !hasFrom;\n // A blank `derived_via:` with no value is also corrupt — the\n // parser drops it to undefined, but the raw key is still present\n // on disk (PR #634 round-3 review, codex P2).\n const hasBlankRawVia =\n rawDerivedViaKeyPresent &&\n (rawDerivedVia === undefined || rawDerivedVia.length === 0) &&\n !hasVia;\n if (\n !hasFrom && !hasVia && !hasRawVia &&\n !hasRawMalformedFrom && !hasBlankRawVia\n ) continue;\n report.withProvenance += 1;\n\n // Duplicate-key detection (PR #634 review, codex P2): when the raw\n // YAML contains multiple `derived_via` or `derived_from` lines,\n // `parseFrontmatter` silently uses the last one. Flag this as a\n // malformed entry so operators can inspect and fix the file.\n if (duplicateViaKeys) {\n report.issues.push({\n memoryPath: memory.path,\n memoryId: fm.id,\n kind: \"derived_via_unknown_operator\",\n detail: `raw YAML contains ${viaMatchCount} \"derived_via\" keys; parseFrontmatter uses the last occurrence`,\n });\n }\n if (duplicateFromKeys) {\n report.issues.push({\n memoryPath: memory.path,\n memoryId: fm.id,\n kind: \"derived_from_malformed_entry\",\n detail: `raw YAML contains ${fromMatchCount} \"derived_from\" keys; parseFrontmatter uses the last occurrence`,\n });\n }\n\n if (hasRawMalformedFrom) {\n const display = rawDerivedFrom ?? \"(blank)\";\n report.issues.push({\n memoryPath: memory.path,\n memoryId: fm.id,\n kind: \"derived_from_malformed_entry\",\n detail: `raw YAML \"derived_from: ${display}\" could not be parsed as a list`,\n });\n }\n\n // Mixed-list detection (PR #634 round-4 + round-5 review, codex\n // P2): when the parser DID return a valid list but the raw YAML\n // includes additional tokens that got dropped, flag those as\n // malformed. Handles both flow-style (`[\"a\", \"\", \"b\"]`) and\n // block-style (`\\n - a\\n - \\n - b`) YAML lists.\n if (hasFrom && rawDerivedFromKeyPresent) {\n let rawList: string[] | null = null;\n if (rawDerivedFrom && rawDerivedFrom.length > 0) {\n rawList = tokenizeRawFlowList(rawDerivedFrom);\n }\n if (rawList === null) {\n // Fall back to block-list tokenization by re-reading the full\n // frontmatter (already loaded above as `raw`) and scanning\n // the lines following `derived_from:`.\n rawList = tokenizeRawBlockList(fmSlice, \"derived_from\");\n }\n if (rawList !== null && rawList.length > derivedFrom!.length) {\n for (const tok of rawList) {\n if (tok.length === 0) {\n report.issues.push({\n memoryPath: memory.path,\n memoryId: fm.id,\n kind: \"derived_from_malformed_entry\",\n detail: `raw YAML derived_from contains an empty entry (mixed list)`,\n });\n continue;\n }\n if (!derivedFrom!.includes(tok)) {\n // Accept either the snapshot format `<path>:<version>` or\n // a bare memory id (issue #687 PR 2/4 — pattern\n // reinforcement uses ID-shaped entries). PR #730\n // review feedback, Codex P2.\n if (\n !/^(.+):(\\d+)$/u.test(tok) &&\n !DERIVED_FROM_MEMORY_ID_RE.test(tok)\n ) {\n report.issues.push({\n memoryPath: memory.path,\n memoryId: fm.id,\n kind: \"derived_from_malformed_entry\",\n detail: `raw YAML derived_from contains a malformed entry: ${JSON.stringify(tok)}`,\n });\n }\n }\n }\n }\n }\n if (hasBlankRawVia) {\n report.issues.push({\n memoryPath: memory.path,\n memoryId: fm.id,\n kind: \"derived_via_unknown_operator\",\n detail: \"raw YAML has `derived_via:` key with empty value\",\n });\n }\n\n if (hasFrom) {\n for (const entry of derivedFrom!) {\n // Pattern-reinforcement (issue #687 PR 2/4) records source\n // memory IDs directly in `derived_from` rather than\n // page-versioning snapshot references. Memory IDs may\n // contain `:` for namespace-prefixed forms like\n // `global:fact-abc-123`, but never `/` or `.` — those remain\n // exclusive to snapshot paths (PR #730 review feedback,\n // Codex P1). For ID-shaped entries we skip the snapshot\n // file check entirely — but ONLY when the operator is\n // `pattern-reinforcement`, which is the sole operator that\n // legitimately stores IDs rather than snapshot references.\n // Allowing the bypass for split/merge/update would weaken\n // validation on those existing consolidation paths (PR #730\n // review, Codex P2).\n if (\n derivedVia === \"pattern-reinforcement\" &&\n DERIVED_FROM_MEMORY_ID_RE.test(entry)\n ) {\n continue;\n }\n const resolved = resolveSnapshotPath(memoryDir, sidecarDir, entry);\n if (!resolved.ok) {\n report.issues.push({\n memoryPath: memory.path,\n memoryId: fm.id,\n kind: \"derived_from_malformed_entry\",\n detail: `${JSON.stringify(entry)}: ${resolved.reason}`,\n });\n continue;\n }\n // Require a regular file at the snapshot path (PR #634\n // round-8 review, codex P2) — a directory or device node at\n // that path means the sidecar was corrupted and the snapshot\n // is effectively missing.\n let snapshotOk = false;\n try {\n const st = await stat(resolved.snapshotPath);\n snapshotOk = st.isFile();\n } catch {\n snapshotOk = false;\n }\n if (!snapshotOk) {\n report.issues.push({\n memoryPath: memory.path,\n memoryId: fm.id,\n kind: \"derived_from_missing_snapshot\",\n detail: `${entry} → ${resolved.snapshotPath} (not a regular file)`,\n });\n }\n }\n }\n\n // Check the RAW YAML value for unknown operators. The parsed value\n // (`fm.derived_via`) is always known-good because the read-path\n // normalizer dropped anything else to undefined.\n if (hasRawVia && !isConsolidationOperator(rawDerivedVia)) {\n report.issues.push({\n memoryPath: memory.path,\n memoryId: fm.id,\n kind: \"derived_via_unknown_operator\",\n detail: `unknown operator: ${JSON.stringify(rawDerivedVia)}`,\n });\n }\n }\n\n // Parse-failure detection (PR #634 round-4 review, codex P2):\n // `readAllMemories()` silently drops files whose frontmatter\n // doesn't parse. Walk the facts/ and corrections/ directories for\n // `.md` files that DO reference provenance frontmatter but didn't\n // come back from the reader — those are the corruption cases the\n // doctor is meant to surface.\n try {\n const seenPaths = new Set(memories.map((m) => m.path));\n const scanRoots = [\"facts\", \"corrections\", \"procedures\", \"reasoning-traces\"];\n for (const rootName of scanRoots) {\n const rootPath = path.join(memoryDir, rootName);\n for await (const file of walkMarkdownFiles(rootPath)) {\n if (seenPaths.has(file)) continue;\n try {\n const raw = await readFile(file, \"utf-8\");\n if (\n DERIVED_FROM_RAW_RE.test(raw) ||\n DERIVED_VIA_RAW_RE.test(raw)\n ) {\n report.withProvenance += 1;\n report.issues.push({\n memoryPath: file,\n memoryId: \"(parse failed)\",\n kind: \"derived_from_malformed_entry\",\n detail:\n \"frontmatter could not be parsed by storage reader; provenance fields visible in raw YAML\",\n });\n }\n } catch {\n // Unreadable file — skip.\n }\n }\n }\n } catch {\n // Best-effort; don't fail the whole scan on a filesystem hiccup.\n }\n\n return report;\n}\n\n/**\n * Recursively yield all `.md` file paths under `root`. Silent on\n * missing directories — the facts/corrections dirs may not exist in\n * fresh installs.\n */\nasync function* walkMarkdownFiles(root: string): AsyncGenerator<string> {\n let entries;\n try {\n entries = await readdir(root, { withFileTypes: true });\n } catch {\n return;\n }\n for (const entry of entries) {\n const full = path.join(root, entry.name);\n if (entry.isDirectory()) {\n yield* walkMarkdownFiles(full);\n } else if (entry.isFile() && entry.name.endsWith(\".md\")) {\n yield full;\n }\n }\n}\n"],"mappings":"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","names":[]}
|