@remnic/core 9.63.9 → 9.63.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/access-admin-ops-surface.d.ts +12 -11
- package/dist/access-admin-ops-surface.js +23 -23
- package/dist/access-authorization-probe.d.ts +12 -11
- package/dist/access-authorization-probe.js +24 -24
- package/dist/access-boundary.d.ts +12 -11
- package/dist/access-boundary.js +23 -23
- package/dist/access-cli.js +84 -82
- package/dist/access-cli.js.map +1 -1
- package/dist/access-coding-context-resolution.d.ts +1 -1
- package/dist/access-extraction-force-flush.d.ts +12 -11
- package/dist/access-extraction-force-flush.js +23 -23
- package/dist/access-health-types.d.ts +1 -1
- package/dist/access-http-lcm-compaction.d.ts +11 -11
- package/dist/access-http-lifecycle-flush.d.ts +11 -11
- package/dist/access-http-offline-stream.d.ts +12 -11
- package/dist/access-http-query.d.ts +2 -2
- package/dist/access-http-query.js +25 -25
- package/dist/access-http.d.ts +13 -12
- package/dist/access-http.js +36 -36
- package/dist/access-identity-continuity-surface.d.ts +9 -9
- package/dist/access-identity-continuity-surface.js +23 -23
- package/dist/access-lcm-surface.d.ts +12 -11
- package/dist/access-lcm-surface.js +23 -23
- package/dist/access-mcp.d.ts +12 -11
- package/dist/access-mcp.js +30 -30
- package/dist/access-memory-search-fanout.d.ts +2 -2
- package/dist/{access-namespace-preflight-DB0zXFek.d.ts → access-namespace-preflight-CrF0FKaz.d.ts} +1 -1
- package/dist/access-namespace-preflight.d.ts +3 -3
- package/dist/access-namespace-preflight.js +23 -23
- package/dist/access-observe-write-surface.d.ts +12 -11
- package/dist/access-observe-write-surface.js +23 -23
- package/dist/access-offline-manifest.d.ts +9 -9
- package/dist/access-operations-batch.js +26 -25
- package/dist/access-operations.d.ts +14 -14
- package/dist/access-operations.js +29 -29
- package/dist/access-recall-concurrency.d.ts +12 -11
- package/dist/access-recall-concurrency.js +23 -23
- package/dist/access-recall-response.d.ts +12 -11
- package/dist/access-recall-response.js +23 -23
- package/dist/access-recall-surface.d.ts +12 -11
- package/dist/access-recall-surface.js +23 -23
- package/dist/access-schema.d.ts +92 -92
- package/dist/access-schema.js +3 -3
- package/dist/{access-service-DwfdjqkZ.d.ts → access-service-U6jiERic.d.ts} +86 -161
- package/dist/access-service-helpers.d.ts +12 -11
- package/dist/access-service.d.ts +12 -11
- package/dist/access-service.js +23 -23
- package/dist/access-surface-catalog.d.ts +12 -11
- package/dist/access-surface-catalog.js +25 -25
- package/dist/access-wearables-meetings-surface.d.ts +3 -3
- package/dist/action-confidence.d.ts +1 -1
- package/dist/active-memory-bridge.d.ts +1 -1
- package/dist/active-recall.d.ts +1 -1
- package/dist/ambient-provenance.d.ts +1 -1
- package/dist/artifact-search.d.ts +1 -1
- package/dist/{auto-sync-3SHMZVQS.js → auto-sync-5L7EUZ27.js} +4 -3
- package/dist/{auto-sync-3SHMZVQS.js.map → auto-sync-5L7EUZ27.js.map} +1 -1
- package/dist/behavior-learner.d.ts +1 -1
- package/dist/behavior-signals.d.ts +1 -1
- package/dist/bootstrap.d.ts +9 -9
- package/dist/briefing-window.d.ts +2 -2
- package/dist/briefing.d.ts +5 -3
- package/dist/briefing.js +11 -9
- package/dist/buffer-surprise-report.d.ts +1 -1
- package/dist/buffer-turn-helpers.d.ts +1 -1
- package/dist/buffer.d.ts +2 -2
- package/dist/bulk-import/index.d.ts +3 -3
- package/dist/calibration.d.ts +1 -1
- package/dist/capabilities.d.ts +1 -1
- package/dist/{capsule-crypto-YO5QJ6L3.js → capsule-crypto-GWVG7LGC.js} +2 -2
- package/dist/{catalog-CCzmf8IH.d.ts → catalog-C0KIwZqR.d.ts} +1 -1
- package/dist/causal-behavior.d.ts +1 -1
- package/dist/causal-consolidation.d.ts +1 -1
- package/dist/causal-consolidation.js +10 -10
- package/dist/causal-trajectory-graph.d.ts +1 -1
- package/dist/{chunk-4POAMKV6.js → chunk-27IOSH2W.js} +5 -2
- package/dist/chunk-27IOSH2W.js.map +1 -0
- package/dist/{chunk-OQIRBGUU.js → chunk-2Q4UAPLD.js} +4 -4
- package/dist/{chunk-PHKU6BZ2.js → chunk-36XS2ASP.js} +2 -2
- package/dist/{chunk-BRHKW7GY.js → chunk-4IIDTPSK.js} +2 -2
- package/dist/{chunk-EDZNW3HE.js → chunk-57WMVU4D.js} +54 -45
- package/dist/chunk-57WMVU4D.js.map +1 -0
- package/dist/{chunk-Q5XKKDFQ.js → chunk-5SZ6CLGJ.js} +2 -2
- package/dist/{chunk-VDX2J7OX.js → chunk-5W7CTZ2E.js} +4 -4
- package/dist/{chunk-EUYWQ7TS.js → chunk-652S5A5D.js} +2 -2
- package/dist/{chunk-ULZFZL7K.js → chunk-6IKXAAJI.js} +17 -28
- package/dist/chunk-6IKXAAJI.js.map +1 -0
- package/dist/{chunk-3TDAA7VA.js → chunk-6NMZXEJ7.js} +2 -2
- package/dist/{chunk-ZOAAY2OE.js → chunk-77E7LLMV.js} +7 -7
- package/dist/{chunk-MIUL6AUZ.js → chunk-7ML7STOD.js} +3 -3
- package/dist/{chunk-5SCFDQEJ.js → chunk-BPLPGOUJ.js} +2 -2
- package/dist/{chunk-RWMSMTQP.js → chunk-BS75WJBH.js} +2 -2
- package/dist/{chunk-XE2QCCT2.js → chunk-BTBAHTQ5.js} +2 -2
- package/dist/{chunk-42TIIO2M.js → chunk-BTUIHRRR.js} +2 -2
- package/dist/{chunk-3IMYRX3W.js → chunk-BYFKJXIR.js} +2 -2
- package/dist/chunk-CIO7GCWE.js +29 -0
- package/dist/chunk-CIO7GCWE.js.map +1 -0
- package/dist/{chunk-SNEUAZ6B.js → chunk-CK3DZ6OE.js} +2 -2
- package/dist/{chunk-VYMICVRA.js → chunk-DIJCMHUE.js} +2 -2
- package/dist/{chunk-AJRJDXYR.js → chunk-E7MYSCSL.js} +2 -2
- package/dist/{chunk-3T6HIIKA.js → chunk-EHC3FIJ4.js} +2 -2
- package/dist/{chunk-YTXNDEUS.js → chunk-FEP45MYY.js} +4 -3
- package/dist/{chunk-YTXNDEUS.js.map → chunk-FEP45MYY.js.map} +1 -1
- package/dist/{chunk-KFB4YNXM.js → chunk-G5F4MXXC.js} +2 -2
- package/dist/{chunk-VANZKHTN.js → chunk-G5OB4XZX.js} +4 -4
- package/dist/{chunk-645WOU5I.js → chunk-H7BZBSP6.js} +4 -4
- package/dist/{chunk-SZ4HFZKB.js → chunk-HMKPFW7R.js} +2 -2
- package/dist/{chunk-WBPD6SBZ.js → chunk-HP6B732E.js} +54 -54
- package/dist/{chunk-VFZQXYN5.js → chunk-HZXO2CJ6.js} +2 -2
- package/dist/{chunk-DGUVXXAU.js → chunk-IRXMJLTW.js} +12 -3
- package/dist/{chunk-DGUVXXAU.js.map → chunk-IRXMJLTW.js.map} +1 -1
- package/dist/{chunk-O52TB5SL.js → chunk-IUSV4XJC.js} +2 -2
- package/dist/{chunk-5TF5STAV.js → chunk-JASFZQ76.js} +4 -4
- package/dist/{chunk-ML3U4KPR.js → chunk-K547KS3X.js} +4 -4
- package/dist/{chunk-UHGBNIOS.js → chunk-KVDUKH5E.js} +7 -2
- package/dist/chunk-KVDUKH5E.js.map +1 -0
- package/dist/{chunk-XTMYB2HY.js → chunk-LJQGQUNS.js} +2 -2
- package/dist/{chunk-WAS4HV46.js → chunk-LNH76ZJX.js} +87 -3
- package/dist/chunk-LNH76ZJX.js.map +1 -0
- package/dist/{chunk-J6QIAYNR.js → chunk-LVXBC6YB.js} +7 -2
- package/dist/{chunk-J6QIAYNR.js.map → chunk-LVXBC6YB.js.map} +1 -1
- package/dist/{chunk-IVKIODHO.js → chunk-MB6GLPRY.js} +4 -4
- package/dist/chunk-MKYRCCSN.js +145 -0
- package/dist/chunk-MKYRCCSN.js.map +1 -0
- package/dist/{chunk-6433HV72.js → chunk-MVIYRRVJ.js} +5 -5
- package/dist/{chunk-LTYH2L6E.js → chunk-NWHKHBL4.js} +44 -64
- package/dist/chunk-NWHKHBL4.js.map +1 -0
- package/dist/{chunk-BXLOS5AJ.js → chunk-OWHERGF2.js} +2 -2
- package/dist/{chunk-QQYHYW2N.js → chunk-PQODT53H.js} +3 -3
- package/dist/{chunk-PQHNZ63P.js → chunk-R5AWIILH.js} +37 -29
- package/dist/chunk-R5AWIILH.js.map +1 -0
- package/dist/{chunk-TOP3FSDW.js → chunk-RFFCBYBD.js} +11 -11
- package/dist/{chunk-MYI3RKI6.js → chunk-SYU5JOBQ.js} +2 -2
- package/dist/{chunk-JXS5PDQ7.js → chunk-UOBPK2OS.js} +25 -7
- package/dist/chunk-UOBPK2OS.js.map +1 -0
- package/dist/{chunk-BOSIHHOH.js → chunk-UUYQWKHO.js} +2 -2
- package/dist/{chunk-DQEMWVMT.js → chunk-UVYI6VIX.js} +1 -1
- package/dist/{chunk-KXWE62IH.js → chunk-V2YH7T6T.js} +2 -2
- package/dist/{chunk-M4AHBM72.js → chunk-VCBG52MM.js} +4 -4
- package/dist/{chunk-N7V3P4V5.js → chunk-VCEE4YNA.js} +2 -2
- package/dist/{chunk-62Y5AGCM.js → chunk-VENMDW4U.js} +10 -10
- package/dist/{chunk-6NAPOLRO.js → chunk-VY47VI2T.js} +6 -4
- package/dist/chunk-VY47VI2T.js.map +1 -0
- package/dist/{chunk-QTXNS3HB.js → chunk-WCWIV2JH.js} +2 -2
- package/dist/{chunk-6UKSOXAO.js → chunk-WQ6TRWAE.js} +3 -3
- package/dist/{chunk-CBY54QYW.js → chunk-X6HNXVXS.js} +4 -4
- package/dist/{chunk-SY5JUJ2X.js → chunk-XREXYQ5U.js} +39 -39
- package/dist/{chunk-JPWSYHLB.js → chunk-YGSDWO3U.js} +2 -2
- package/dist/{chunk-BPQ7L27U.js → chunk-YI25J2JD.js} +16 -16
- package/dist/chunk-YI25J2JD.js.map +1 -0
- package/dist/{chunk-GBVRVYF4.js → chunk-YINSSARY.js} +1 -1
- package/dist/{chunk-Q6OL23WM.js → chunk-Z4UPRZR6.js} +2 -2
- package/dist/{chunk-4F67JJPG.js → chunk-ZPN7W3OP.js} +2 -2
- package/dist/{cli-kcUqoV4E.d.ts → cli-BsaYHAH9.d.ts} +5 -5
- package/dist/cli.d.ts +14 -13
- package/dist/cli.js +55 -54
- package/dist/coding/pre-action-gate.d.ts +1 -1
- package/dist/compounding/engine.d.ts +2 -2
- package/dist/compounding/engine.js +9 -9
- package/dist/compounding/preference-consolidator.d.ts +1 -1
- package/dist/compression-optimizer.d.ts +1 -1
- package/dist/config.d.ts +1 -1
- package/dist/connectors/codex-materialize-runner.d.ts +1 -1
- package/dist/connectors/codex-materialize-runner.js +9 -9
- package/dist/connectors/codex-materialize.d.ts +1 -1
- package/dist/connectors/index.d.ts +1 -1
- package/dist/connectors/index.js +9 -9
- package/dist/consolidation-provenance-check.d.ts +2 -2
- package/dist/consolidation-undo.d.ts +2 -2
- package/dist/contradiction/index.d.ts +2 -2
- package/dist/contradiction/index.js +4 -4
- package/dist/converge-config.d.ts +1 -1
- package/dist/convergence-refresh.d.ts +2 -2
- package/dist/convergence-refresh.js +11 -11
- package/dist/conversation-index/backend.d.ts +1 -1
- package/dist/conversation-index/chunker.d.ts +1 -1
- package/dist/conversation-index/faiss-adapter.d.ts +1 -1
- package/dist/conversation-index/indexer.d.ts +1 -1
- package/dist/conversation-index/search.d.ts +1 -1
- package/dist/corpus-watermark.d.ts +1 -1
- package/dist/corpus-watermark.js +11 -11
- package/dist/day-summary.d.ts +1 -1
- package/dist/day-summary.js +1 -1
- package/dist/delinearize.d.ts +1 -1
- package/dist/dependency-propagation-config.d.ts +1 -1
- package/dist/{dependency-propagation-delivery-DiY5T63p.d.ts → dependency-propagation-delivery-Cph4PnBe.d.ts} +1 -1
- package/dist/direct-answer-wiring.d.ts +1 -1
- package/dist/direct-answer-wiring.js +2 -1
- package/dist/direct-answer.d.ts +1 -1
- package/dist/embedding-fallback.d.ts +1 -1
- package/dist/enrichment/index.d.ts +1 -1
- package/dist/entity-origin-fields.d.ts +1 -1
- package/dist/entity-retrieval-i18n.d.ts +34 -0
- package/dist/entity-retrieval-i18n.js +13 -0
- package/dist/entity-retrieval-i18n.js.map +1 -0
- package/dist/entity-retrieval.d.ts +2 -2
- package/dist/entity-retrieval.js +10 -9
- package/dist/entity-schema.d.ts +1 -1
- package/dist/episodic-context.d.ts +1 -1
- package/dist/event-order-recall.js +2 -2
- package/dist/explicit-capture.d.ts +9 -9
- package/dist/external-wiki-access.d.ts +12 -11
- package/dist/external-wiki-access.js +24 -24
- package/dist/external-wiki-collection-registration.d.ts +1 -1
- package/dist/external-wiki-collection.d.ts +1 -1
- package/dist/external-wiki-mcp-tools.d.ts +12 -11
- package/dist/extraction-error-classification.d.ts +1 -1
- package/dist/extraction-faithfulness.d.ts +1 -1
- package/dist/extraction-judge-telemetry.d.ts +1 -1
- package/dist/extraction-judge-training.d.ts +1 -1
- package/dist/extraction-judge.d.ts +1 -1
- package/dist/extraction-liveness.d.ts +1 -1
- package/dist/extraction-normalization.d.ts +1 -1
- package/dist/extraction-prompt.d.ts +1 -1
- package/dist/extraction-source-grounding-helpers.d.ts +10 -1
- package/dist/extraction-source-grounding-helpers.js +3 -1
- package/dist/extraction-source-grounding-rules.d.ts +1 -1
- package/dist/extraction-source-grounding-rules.js +2 -2
- package/dist/extraction-source-grounding.d.ts +1 -1
- package/dist/extraction-source-grounding.js +3 -3
- package/dist/extraction.d.ts +1 -1
- package/dist/extraction.js +18 -18
- package/dist/fallback-llm.d.ts +1 -1
- package/dist/graph-dashboard-diff.d.ts +1 -1
- package/dist/graph-dashboard-key.d.ts +1 -1
- package/dist/graph-dashboard-parser.d.ts +1 -1
- package/dist/graph-edge-reinforcement.d.ts +1 -1
- package/dist/graph-path-reconstruction.d.ts +1 -1
- package/dist/graph-path-scoring.d.ts +1 -1
- package/dist/graph-snapshot.d.ts +1 -1
- package/dist/graph.d.ts +1 -1
- package/dist/harmonic-retrieval.js +1 -1
- package/dist/importance.d.ts +1 -1
- package/dist/importance.js +2 -1
- package/dist/importers/index.d.ts +1 -1
- package/dist/in-flight-reads.d.ts +1 -1
- package/dist/index.d.ts +697 -697
- package/dist/index.js +105 -103
- package/dist/intent.d.ts +1 -1
- package/dist/lcm/engine.d.ts +1 -1
- package/dist/lcm/engine.js +2 -2
- package/dist/lcm/index.d.ts +1 -1
- package/dist/lcm/index.js +4 -4
- package/dist/lcm/tools.d.ts +1 -1
- package/dist/lifecycle.d.ts +1 -1
- package/dist/live-connectors-runner.d.ts +1 -1
- package/dist/local-llm.d.ts +1 -1
- package/dist/local-llm.js +2 -2
- package/dist/local-model-endpoint.d.ts +1 -1
- package/dist/maintenance/memory-governance.d.ts +1 -1
- package/dist/maintenance/memory-governance.js +9 -9
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.d.ts +2 -2
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +9 -9
- package/dist/maintenance/rebuild-memory-projection.d.ts +2 -2
- package/dist/maintenance/rebuild-memory-projection.js +10 -10
- package/dist/{maintenance-CaqA4z7V.d.ts → maintenance-BNq31xpo.d.ts} +3 -3
- package/dist/mcp-memory-inspector-app.d.ts +11 -11
- package/dist/memory-action-policy.d.ts +1 -1
- package/dist/memory-cache.d.ts +1 -1
- package/dist/memory-lifecycle-ledger-utils.d.ts +1 -1
- package/dist/memory-projection-store.d.ts +1 -1
- package/dist/memory-provenance.d.ts +1 -1
- package/dist/memory-snapshot.d.ts +1 -1
- package/dist/memory-worth-outcomes.d.ts +2 -2
- package/dist/models-json.d.ts +1 -1
- package/dist/namespaces/migrate.d.ts +3 -3
- package/dist/namespaces/migrate.js +10 -10
- package/dist/namespaces/principal.d.ts +1 -1
- package/dist/namespaces/search.d.ts +1 -1
- package/dist/namespaces/search.js +7 -7
- package/dist/namespaces/storage.d.ts +3 -3
- package/dist/namespaces/storage.js +9 -9
- package/dist/native-knowledge.d.ts +1 -1
- package/dist/offline-sync-impression-drain.d.ts +1 -1
- package/dist/operator-doctor-corpus.d.ts +1 -1
- package/dist/operator-doctor-corpus.js +12 -12
- package/dist/operator-doctor-replica.d.ts +1 -1
- package/dist/operator-toolkit.d.ts +3 -3
- package/dist/operator-toolkit.js +17 -17
- package/dist/orchestration/compression-guideline-coordinator.d.ts +2 -2
- package/dist/orchestration/maintenance.d.ts +4 -4
- package/dist/orchestration/maintenance.js +15 -15
- package/dist/{orchestrator-Bay_AofR.d.ts → orchestrator-CBuUOP2J.d.ts} +8 -8
- package/dist/orchestrator.d.ts +9 -9
- package/dist/orchestrator.js +84 -82
- package/dist/patterns-cli.d.ts +1 -1
- package/dist/{pipeline-X606GdRJ.d.ts → pipeline-CW_Q4WvX.d.ts} +1 -1
- package/dist/policy-runtime.d.ts +1 -1
- package/dist/proactive-contention.d.ts +1 -1
- package/dist/provenance.d.ts +1 -1
- package/dist/{public-http-DscQtnCx.d.ts → public-http-jMlmLny4.d.ts} +1 -1
- package/dist/qmd-preflight.d.ts +2 -2
- package/dist/{qmd-DCKeR-GX.d.ts → qmd-q-cAzYuC.d.ts} +1 -1
- package/dist/qmd-recall-cache.d.ts +1 -1
- package/dist/qmd.d.ts +2 -2
- package/dist/recall-concurrency-config.d.ts +1 -1
- package/dist/recall-disclosure-escalation.d.ts +1 -1
- package/dist/recall-explain-renderer.d.ts +1 -1
- package/dist/recall-memory-map.d.ts +1 -1
- package/dist/recall-planner-llm.d.ts +1 -1
- package/dist/recall-state.d.ts +1 -1
- package/dist/recall-tag-filter.d.ts +1 -1
- package/dist/recall-timings.d.ts +1 -1
- package/dist/recall-xray-cli.d.ts +1 -1
- package/dist/recall-xray-renderer.d.ts +1 -1
- package/dist/recall-xray.d.ts +1 -1
- package/dist/reconcile/cursor.d.ts +1 -1
- package/dist/reconcile/manifest.d.ts +1 -1
- package/dist/reconcile/plan.d.ts +1 -1
- package/dist/replay/normalizers/chatgpt.d.ts +1 -1
- package/dist/replay/normalizers/claude.d.ts +1 -1
- package/dist/replay/normalizers/openclaw.d.ts +1 -1
- package/dist/replay/normalizers/shared.d.ts +1 -1
- package/dist/replay/runner.d.ts +1 -1
- package/dist/replay/types.d.ts +1 -1
- package/dist/replica-divergence.d.ts +1 -1
- package/dist/replica-peers-config.d.ts +1 -1
- package/dist/resolve-auth-token.d.ts +1 -1
- package/dist/retrieval-agents.d.ts +2 -2
- package/dist/retrieval-tiers.d.ts +1 -1
- package/dist/routing/engine.d.ts +1 -1
- package/dist/routing/store.d.ts +1 -1
- package/dist/salvage-envelope.d.ts +1 -1
- package/dist/schemas.d.ts +108 -108
- package/dist/{scope-profiles-_Y_RHCeE.d.ts → scope-profiles-CIr4mXgp.d.ts} +1 -1
- package/dist/search/embed-helper.d.ts +1 -1
- package/dist/search/factory.d.ts +1 -1
- package/dist/search/factory.js +6 -6
- package/dist/search/index.d.ts +1 -1
- package/dist/search/index.js +11 -11
- package/dist/search/lancedb-backend.d.ts +1 -1
- package/dist/search/lancedb-backend.js +2 -2
- package/dist/search/meilisearch-backend.d.ts +1 -1
- package/dist/search/meilisearch-backend.js +2 -2
- package/dist/search/noop-backend.d.ts +1 -1
- package/dist/search/orama-backend.d.ts +1 -1
- package/dist/search/orama-backend.js +2 -2
- package/dist/search/port.d.ts +1 -1
- package/dist/search/remote-backend.d.ts +1 -1
- package/dist/{semantic-consolidation-DT2VSrU0.d.ts → semantic-consolidation-BZ6J46j-.d.ts} +1 -1
- package/dist/semantic-consolidation.d.ts +2 -2
- package/dist/semantic-consolidation.js +10 -10
- package/dist/semantic-rule-promotion.js +9 -9
- package/dist/semantic-rule-verifier.d.ts +1 -1
- package/dist/semantic-rule-verifier.js +9 -9
- package/dist/{service-BMo-gwDE.d.ts → service-CCl4iyyY.d.ts} +2 -2
- package/dist/session-observer-bands.d.ts +1 -1
- package/dist/session-observer-state.d.ts +1 -1
- package/dist/shared-context/manager.d.ts +9 -9
- package/dist/signal.d.ts +1 -1
- package/dist/source-agent-qualifier.d.ts +1 -1
- package/dist/source-agent-qualifier.js +13 -13
- package/dist/{storage-CGzJGUUu.d.ts → storage-BPXmJKIt.d.ts} +1 -1
- package/dist/storage.d.ts +2 -2
- package/dist/storage.js +8 -8
- package/dist/summarizer.d.ts +1 -1
- package/dist/summarizer.js +3 -3
- package/dist/summary-snapshot.d.ts +1 -1
- package/dist/support-passport/index.d.ts +15 -14
- package/dist/support-passport/index.js +26 -26
- package/dist/temporal-supersession.d.ts +2 -2
- package/dist/temporal-timeline-recall.d.ts +1 -1
- package/dist/temporal-validity.d.ts +1 -1
- package/dist/threading.d.ts +1 -1
- package/dist/tier-migration.d.ts +2 -2
- package/dist/tier-routing.d.ts +1 -1
- package/dist/topics.d.ts +1 -1
- package/dist/topics.js +2 -1
- package/dist/transcript.d.ts +1 -1
- package/dist/transfer/backup.js +2 -2
- package/dist/transfer/capsule-export.js +2 -2
- package/dist/transfer/capsule-import.js +2 -2
- package/dist/transfer/types.d.ts +66 -66
- package/dist/trust-score-stage.d.ts +1 -1
- package/dist/trust-score.d.ts +1 -1
- package/dist/{types-CBEAYU76.d.ts → types-CltSWcND.d.ts} +1 -1
- package/dist/types.d.ts +1 -1
- package/dist/utility-runtime.d.ts +1 -1
- package/dist/verified-recall.js +9 -9
- package/dist/write-envelope.d.ts +1 -1
- package/package.json +2 -2
- package/src/access-http.ts +47 -60
- package/src/access-mcp.test.ts +6 -10
- package/src/access-mcp.ts +21 -22
- package/src/access-operations-batch.ts +66 -18
- package/src/access-schema.ts +12 -1
- package/src/access-service.ts +18 -91
- package/src/briefing.test.ts +50 -0
- package/src/briefing.ts +3 -2
- package/src/chat/chat-engine.test.ts +15 -0
- package/src/chat/chat-engine.ts +13 -2
- package/src/day-summary.test.ts +63 -0
- package/src/day-summary.ts +13 -1
- package/src/entity-retrieval-i18n.test.ts +115 -0
- package/src/entity-retrieval-i18n.ts +161 -0
- package/src/entity-retrieval.ts +34 -34
- package/src/extraction-source-grounding-helpers.ts +112 -2
- package/src/extraction-source-grounding-rules.ts +3 -1
- package/src/extraction-source-grounding.test.ts +52 -0
- package/src/importance.ts +32 -22
- package/src/recall-disclosure.test.ts +1 -1
- package/src/taxonomy/resolver.ts +7 -7
- package/src/topics.ts +5 -2
- package/src/utils/script-aware-text.ts +52 -0
- package/src/write-classifiers-cjk.test.ts +115 -0
- package/dist/chunk-4POAMKV6.js.map +0 -1
- package/dist/chunk-6NAPOLRO.js.map +0 -1
- package/dist/chunk-BPQ7L27U.js.map +0 -1
- package/dist/chunk-EDZNW3HE.js.map +0 -1
- package/dist/chunk-JXS5PDQ7.js.map +0 -1
- package/dist/chunk-LTYH2L6E.js.map +0 -1
- package/dist/chunk-PQHNZ63P.js.map +0 -1
- package/dist/chunk-UHGBNIOS.js.map +0 -1
- package/dist/chunk-ULZFZL7K.js.map +0 -1
- package/dist/chunk-WAS4HV46.js.map +0 -1
- /package/dist/{capsule-crypto-YO5QJ6L3.js.map → capsule-crypto-GWVG7LGC.js.map} +0 -0
- /package/dist/{chunk-OQIRBGUU.js.map → chunk-2Q4UAPLD.js.map} +0 -0
- /package/dist/{chunk-PHKU6BZ2.js.map → chunk-36XS2ASP.js.map} +0 -0
- /package/dist/{chunk-BRHKW7GY.js.map → chunk-4IIDTPSK.js.map} +0 -0
- /package/dist/{chunk-Q5XKKDFQ.js.map → chunk-5SZ6CLGJ.js.map} +0 -0
- /package/dist/{chunk-VDX2J7OX.js.map → chunk-5W7CTZ2E.js.map} +0 -0
- /package/dist/{chunk-EUYWQ7TS.js.map → chunk-652S5A5D.js.map} +0 -0
- /package/dist/{chunk-3TDAA7VA.js.map → chunk-6NMZXEJ7.js.map} +0 -0
- /package/dist/{chunk-ZOAAY2OE.js.map → chunk-77E7LLMV.js.map} +0 -0
- /package/dist/{chunk-MIUL6AUZ.js.map → chunk-7ML7STOD.js.map} +0 -0
- /package/dist/{chunk-5SCFDQEJ.js.map → chunk-BPLPGOUJ.js.map} +0 -0
- /package/dist/{chunk-RWMSMTQP.js.map → chunk-BS75WJBH.js.map} +0 -0
- /package/dist/{chunk-XE2QCCT2.js.map → chunk-BTBAHTQ5.js.map} +0 -0
- /package/dist/{chunk-42TIIO2M.js.map → chunk-BTUIHRRR.js.map} +0 -0
- /package/dist/{chunk-3IMYRX3W.js.map → chunk-BYFKJXIR.js.map} +0 -0
- /package/dist/{chunk-SNEUAZ6B.js.map → chunk-CK3DZ6OE.js.map} +0 -0
- /package/dist/{chunk-VYMICVRA.js.map → chunk-DIJCMHUE.js.map} +0 -0
- /package/dist/{chunk-AJRJDXYR.js.map → chunk-E7MYSCSL.js.map} +0 -0
- /package/dist/{chunk-3T6HIIKA.js.map → chunk-EHC3FIJ4.js.map} +0 -0
- /package/dist/{chunk-KFB4YNXM.js.map → chunk-G5F4MXXC.js.map} +0 -0
- /package/dist/{chunk-VANZKHTN.js.map → chunk-G5OB4XZX.js.map} +0 -0
- /package/dist/{chunk-645WOU5I.js.map → chunk-H7BZBSP6.js.map} +0 -0
- /package/dist/{chunk-SZ4HFZKB.js.map → chunk-HMKPFW7R.js.map} +0 -0
- /package/dist/{chunk-WBPD6SBZ.js.map → chunk-HP6B732E.js.map} +0 -0
- /package/dist/{chunk-VFZQXYN5.js.map → chunk-HZXO2CJ6.js.map} +0 -0
- /package/dist/{chunk-O52TB5SL.js.map → chunk-IUSV4XJC.js.map} +0 -0
- /package/dist/{chunk-5TF5STAV.js.map → chunk-JASFZQ76.js.map} +0 -0
- /package/dist/{chunk-ML3U4KPR.js.map → chunk-K547KS3X.js.map} +0 -0
- /package/dist/{chunk-XTMYB2HY.js.map → chunk-LJQGQUNS.js.map} +0 -0
- /package/dist/{chunk-IVKIODHO.js.map → chunk-MB6GLPRY.js.map} +0 -0
- /package/dist/{chunk-6433HV72.js.map → chunk-MVIYRRVJ.js.map} +0 -0
- /package/dist/{chunk-BXLOS5AJ.js.map → chunk-OWHERGF2.js.map} +0 -0
- /package/dist/{chunk-QQYHYW2N.js.map → chunk-PQODT53H.js.map} +0 -0
- /package/dist/{chunk-TOP3FSDW.js.map → chunk-RFFCBYBD.js.map} +0 -0
- /package/dist/{chunk-MYI3RKI6.js.map → chunk-SYU5JOBQ.js.map} +0 -0
- /package/dist/{chunk-BOSIHHOH.js.map → chunk-UUYQWKHO.js.map} +0 -0
- /package/dist/{chunk-DQEMWVMT.js.map → chunk-UVYI6VIX.js.map} +0 -0
- /package/dist/{chunk-KXWE62IH.js.map → chunk-V2YH7T6T.js.map} +0 -0
- /package/dist/{chunk-M4AHBM72.js.map → chunk-VCBG52MM.js.map} +0 -0
- /package/dist/{chunk-N7V3P4V5.js.map → chunk-VCEE4YNA.js.map} +0 -0
- /package/dist/{chunk-62Y5AGCM.js.map → chunk-VENMDW4U.js.map} +0 -0
- /package/dist/{chunk-QTXNS3HB.js.map → chunk-WCWIV2JH.js.map} +0 -0
- /package/dist/{chunk-6UKSOXAO.js.map → chunk-WQ6TRWAE.js.map} +0 -0
- /package/dist/{chunk-CBY54QYW.js.map → chunk-X6HNXVXS.js.map} +0 -0
- /package/dist/{chunk-SY5JUJ2X.js.map → chunk-XREXYQ5U.js.map} +0 -0
- /package/dist/{chunk-JPWSYHLB.js.map → chunk-YGSDWO3U.js.map} +0 -0
- /package/dist/{chunk-GBVRVYF4.js.map → chunk-YINSSARY.js.map} +0 -0
- /package/dist/{chunk-Q6OL23WM.js.map → chunk-Z4UPRZR6.js.map} +0 -0
- /package/dist/{chunk-4F67JJPG.js.map → chunk-ZPN7W3OP.js.map} +0 -0
package/src/entity-retrieval.ts
CHANGED
|
@@ -10,6 +10,11 @@ import { normalizeEntityText, resolveRequestedEntitySectionKeys } from "./entity
|
|
|
10
10
|
import type { EntityStructuredSection, MemoryFile, PluginConfig, TranscriptEntry } from "./types.js";
|
|
11
11
|
import { truncateGraphemeSafe } from "./whitespace.js";
|
|
12
12
|
import { containsPhrase } from "./entity-retrieval-boundaries.js";
|
|
13
|
+
import {
|
|
14
|
+
detectEntityQueryMode,
|
|
15
|
+
isStructuralEntityFollowUpQuery,
|
|
16
|
+
type EntityQueryMode,
|
|
17
|
+
} from "./entity-retrieval-i18n.js";
|
|
13
18
|
import { buildOriginStructuredSections, sanitizeOriginatedFacts, type EntityOriginStructuredSection } from "./entity-origin-fields.js";
|
|
14
19
|
import {
|
|
15
20
|
checkEntityRecallAbort,
|
|
@@ -21,10 +26,8 @@ const ENTITY_INDEX_VERSION = 3;
|
|
|
21
26
|
const RECENT_TRANSCRIPT_LOOKBACK_HOURS = 24;
|
|
22
27
|
const INSTRUCTION_LIKE_RE = /\b(always|never|must|should|remember to|do not|don't|process|workflow|template|checklist|instruction)\b/i;
|
|
23
28
|
const METADATA_WRAPPER_RE = /^(source|context|metadata|notes?):/i;
|
|
24
|
-
const ENTITY_PRONOUN_RE = /\b(he|him|his|she|her|they|them|their|it|its)\b/i;
|
|
25
29
|
const BELIEF_LEDGER_SECTION_KEY = "belief_ledger";
|
|
26
30
|
const BELIEF_LEDGER_FACT_RE = /^claim=([^;]+);\s*status=([^;]+);\s*updatedAt=([^;]+);\s*(.+)$/;
|
|
27
|
-
type EntityQueryMode = "direct" | "timeline" | "follow_up";
|
|
28
31
|
type EntityMentionIndexEntry = {
|
|
29
32
|
canonicalId: string;
|
|
30
33
|
name: string;
|
|
@@ -171,35 +174,6 @@ function relationLine(entry: EntityMentionIndexEntry, relationship: { target: st
|
|
|
171
174
|
if (normalizedLabel.length === 0) return `${entry.name} is connected to ${relationship.target}`;
|
|
172
175
|
return `${entry.name} ${normalizedLabel} ${relationship.target}`;
|
|
173
176
|
}
|
|
174
|
-
function detectEntityQueryMode(query: string): EntityQueryMode | null {
|
|
175
|
-
const normalized = normalizeEntityText(query);
|
|
176
|
-
if (!normalized) return null;
|
|
177
|
-
if (
|
|
178
|
-
/^(what about|and what about|how about|what happened (with|to) (he|him|his|she|her|they|them|their|it|its)|did (he|she|they|it)|is (he|she|they|it)|was (he|she|they|it))\b/.test(normalized)
|
|
179
|
-
) {
|
|
180
|
-
return "follow_up";
|
|
181
|
-
}
|
|
182
|
-
if (
|
|
183
|
-
/^(who is|who s|what do we know about|what does|tell me about|what can you tell me about|what s new with|what happened with|what happened to|status of|where is|how is)\b/.test(normalized)
|
|
184
|
-
) {
|
|
185
|
-
if (/^what does\b/.test(normalized)) {
|
|
186
|
-
if (/^what does (?:this|that|it|the|a|an|my|our|your|their)\b/.test(normalized)) {
|
|
187
|
-
return null;
|
|
188
|
-
}
|
|
189
|
-
if (
|
|
190
|
-
/^what does [a-z0-9-]+ (?:error|warning|exception|failure|stack|trace|code|message|log)\b/.test(normalized)
|
|
191
|
-
&& /\b(mean|means|indicate|indicates|imply|implies)\b/.test(normalized)
|
|
192
|
-
) {
|
|
193
|
-
return null;
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
return /what happened|what s new|status of|how is|where is/.test(normalized) ? "timeline" : "direct";
|
|
197
|
-
}
|
|
198
|
-
if (ENTITY_PRONOUN_RE.test(normalized) && normalized.split(/\s+/).length <= 8) {
|
|
199
|
-
return "follow_up";
|
|
200
|
-
}
|
|
201
|
-
return null;
|
|
202
|
-
}
|
|
203
177
|
function scoreAliasMatch(query: string, alias: string): number {
|
|
204
178
|
const normalizedQuery = normalizeEntityText(query);
|
|
205
179
|
const normalizedAlias = normalizeEntityText(alias);
|
|
@@ -1061,7 +1035,18 @@ export async function buildEntityRecallSection(options: BuildEntityRecallSection
|
|
|
1061
1035
|
// during a scan is observed here (issue #2291).
|
|
1062
1036
|
checkEntityRecallAbort(options.abortSignal);
|
|
1063
1037
|
const prefixedMode = detectEntityQueryMode(options.query);
|
|
1064
|
-
|
|
1038
|
+
// #2193: in zero-pronoun scripts a short question with no entity name
|
|
1039
|
+
// never trips a pronoun word list, so the structural signal routes it
|
|
1040
|
+
// into the same coreference path as an English "what about him?".
|
|
1041
|
+
// Latin-script queries stay on the cue tables / English rules so the
|
|
1042
|
+
// generic technical-question guard keeps working. Resolution gates.
|
|
1043
|
+
const structuralFollowUp = prefixedMode === null
|
|
1044
|
+
&& isStructuralEntityFollowUpQuery(
|
|
1045
|
+
options.query,
|
|
1046
|
+
options.recentTurns > 0 && options.transcriptEntries.length > 0,
|
|
1047
|
+
);
|
|
1048
|
+
const earlyMode = prefixedMode ?? (structuralFollowUp ? "follow_up" : null);
|
|
1049
|
+
const persistedIndex = earlyMode
|
|
1065
1050
|
? null
|
|
1066
1051
|
: await readCurrentPersistedEntityIndex(
|
|
1067
1052
|
options.storage,
|
|
@@ -1072,7 +1057,7 @@ export async function buildEntityRecallSection(options: BuildEntityRecallSection
|
|
|
1072
1057
|
checkEntityRecallAbort(options.abortSignal);
|
|
1073
1058
|
let nativeChunks: NativeKnowledgeChunk[] | undefined;
|
|
1074
1059
|
if (
|
|
1075
|
-
!
|
|
1060
|
+
!earlyMode &&
|
|
1076
1061
|
persistedIndex &&
|
|
1077
1062
|
resolveLanguageIndependentExplicitCandidates(persistedIndex, options.query).length === 0
|
|
1078
1063
|
) {
|
|
@@ -1129,7 +1114,22 @@ export async function buildEntityRecallSection(options: BuildEntityRecallSection
|
|
|
1129
1114
|
const queryCandidates = prefixedMode
|
|
1130
1115
|
? explicitCandidates
|
|
1131
1116
|
: resolveLanguageIndependentExplicitCandidates(index, options.query);
|
|
1132
|
-
|
|
1117
|
+
if (
|
|
1118
|
+
queryCandidates.length === 0
|
|
1119
|
+
&& /^what does (?:this|that|it|the|a|an|my|our|your|their)\b/i.test(normalizeEntityText(options.query))
|
|
1120
|
+
) {
|
|
1121
|
+
return entityRecallSectionAbsent(options.abortSignal);
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1124
|
+
// Explicit mentions keep #2161 behavior (direct mode) even when the
|
|
1125
|
+
// structural follow-up signal also fired; only name-less short questions
|
|
1126
|
+
// fall through to coreference.
|
|
1127
|
+
const mode = prefixedMode
|
|
1128
|
+
?? (queryCandidates.length > 0
|
|
1129
|
+
? "direct"
|
|
1130
|
+
: structuralFollowUp
|
|
1131
|
+
? "follow_up"
|
|
1132
|
+
: null);
|
|
1133
1133
|
if (!mode) return entityRecallSectionAbsent(options.abortSignal);
|
|
1134
1134
|
|
|
1135
1135
|
const candidates = queryCandidates.length > 0
|
|
@@ -334,9 +334,9 @@ export interface GroundingLexeme {
|
|
|
334
334
|
}
|
|
335
335
|
|
|
336
336
|
export function groundingLexemes(text: string): GroundingLexeme[] {
|
|
337
|
-
const rawTokens = text.normalize("NFKC").match(
|
|
337
|
+
const rawTokens = (text.normalize("NFKC").match(
|
|
338
338
|
/[\p{L}\p{N}]+(?:['’][\p{L}\p{N}]+)?(?:\+\+[\p{L}\p{N}]*|#[\p{L}\p{N}]*)?/gu,
|
|
339
|
-
) ?? [];
|
|
339
|
+
) ?? []).flatMap(splitSpacelessScriptToken);
|
|
340
340
|
const tokens = rawTokens.map((rawToken) =>
|
|
341
341
|
rawToken.replaceAll("’", "'").toLocaleLowerCase());
|
|
342
342
|
let predicateIndex = tokens.findIndex((token, index) => {
|
|
@@ -500,6 +500,116 @@ export function tokenize(text: string): Set<string> {
|
|
|
500
500
|
return tokens;
|
|
501
501
|
}
|
|
502
502
|
|
|
503
|
+
const SPACELESS_SCRIPT_CHARACTER_PATTERN = new RegExp(
|
|
504
|
+
"[\\p{Script=Han}\\p{Script=Hiragana}\\p{Script=Katakana}\\p{Script=Hangul}"
|
|
505
|
+
+ "\\p{Script=Thai}\\p{Script=Lao}\\p{Script=Khmer}\\p{Script=Myanmar}]",
|
|
506
|
+
"u",
|
|
507
|
+
);
|
|
508
|
+
const SPACELESS_SCRIPT_LCS_SOURCE_LIMIT = 4096;
|
|
509
|
+
|
|
510
|
+
function containsSpacelessScriptCharacter(text: string): boolean {
|
|
511
|
+
return SPACELESS_SCRIPT_CHARACTER_PATTERN.test(text);
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
/**
|
|
515
|
+
* Scripts without word separators (CJK, Hangul, Thai, ...) must not become one
|
|
516
|
+
* whitespace-delimited token: a paraphrase never matches a whole-run token, so
|
|
517
|
+
* grounded CJK facts and entity names were dropped. Split those runs into
|
|
518
|
+
* per-character lexemes so contiguous-token matching behaves as substring
|
|
519
|
+
* matching for these scripts.
|
|
520
|
+
*/
|
|
521
|
+
function splitSpacelessScriptToken(rawToken: string): string[] {
|
|
522
|
+
if (!containsSpacelessScriptCharacter(rawToken)) return [rawToken];
|
|
523
|
+
const parts: string[] = [];
|
|
524
|
+
let otherScriptRun = "";
|
|
525
|
+
for (const character of rawToken) {
|
|
526
|
+
if (containsSpacelessScriptCharacter(character)) {
|
|
527
|
+
if (otherScriptRun.length > 0) {
|
|
528
|
+
parts.push(otherScriptRun);
|
|
529
|
+
otherScriptRun = "";
|
|
530
|
+
}
|
|
531
|
+
parts.push(character);
|
|
532
|
+
} else {
|
|
533
|
+
otherScriptRun += character;
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
if (otherScriptRun.length > 0) parts.push(otherScriptRun);
|
|
537
|
+
return parts;
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
function spacelessScriptCharacterSequence(text: string): string[] {
|
|
541
|
+
const characters: string[] = [];
|
|
542
|
+
for (const character of text.normalize("NFKC")) {
|
|
543
|
+
if (containsSpacelessScriptCharacter(character)) characters.push(character);
|
|
544
|
+
}
|
|
545
|
+
return characters;
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
function spacelessScriptCharacterNgrams(text: string): Set<string> {
|
|
549
|
+
const grams = new Set<string>();
|
|
550
|
+
let previousCharacter = "";
|
|
551
|
+
for (const character of spacelessScriptCharacterSequence(text)) {
|
|
552
|
+
if (previousCharacter !== "") grams.add(previousCharacter + character);
|
|
553
|
+
previousCharacter = character;
|
|
554
|
+
}
|
|
555
|
+
return grams;
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
function longestCommonSubstringLength(
|
|
559
|
+
left: ReadonlyArray<string>,
|
|
560
|
+
right: ReadonlyArray<string>,
|
|
561
|
+
): number {
|
|
562
|
+
if (left.length === 0 || right.length === 0) return 0;
|
|
563
|
+
let longest = 0;
|
|
564
|
+
let previousRow = new Array<number>(right.length).fill(0);
|
|
565
|
+
for (const leftCharacter of left) {
|
|
566
|
+
const currentRow = new Array<number>(right.length).fill(0);
|
|
567
|
+
for (let index = 0; index < right.length; index += 1) {
|
|
568
|
+
if (leftCharacter !== right[index]) continue;
|
|
569
|
+
currentRow[index] = (index > 0 ? previousRow[index - 1] : 0) + 1;
|
|
570
|
+
if (currentRow[index] > longest) longest = currentRow[index];
|
|
571
|
+
}
|
|
572
|
+
previousRow = currentRow;
|
|
573
|
+
}
|
|
574
|
+
return longest;
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
/**
|
|
578
|
+
* Script-aware grounding score for candidates written in scripts without word
|
|
579
|
+
* separators. Returns null when the candidate has no such characters, so
|
|
580
|
+
* whitespace-delimited candidates keep the default scorer. The longest-common-
|
|
581
|
+
* span gate rejects topical overlap that shares mostly function characters.
|
|
582
|
+
* ponytail: n-grams and the common-span gate ignore argument order; swap in a
|
|
583
|
+
* segmentation-aware matcher if CJK role confusion surfaces.
|
|
584
|
+
*/
|
|
585
|
+
export function spacelessScriptGroundedTokenScore(candidate: string, source: string): number | null {
|
|
586
|
+
const candidateGrams = spacelessScriptCharacterNgrams(candidate);
|
|
587
|
+
if (candidateGrams.size === 0) return null;
|
|
588
|
+
const sourceGrams = spacelessScriptCharacterNgrams(source);
|
|
589
|
+
let sharedGrams = 0;
|
|
590
|
+
for (const gram of candidateGrams) {
|
|
591
|
+
if (sourceGrams.has(gram)) sharedGrams += 1;
|
|
592
|
+
}
|
|
593
|
+
if (sharedGrams < GROUNDING_MIN_SHARED_TOKENS) return 0;
|
|
594
|
+
const coverage = sharedGrams / candidateGrams.size;
|
|
595
|
+
if (coverage < GROUNDING_MIN_COVERAGE) return 0;
|
|
596
|
+
const candidateCharacters = spacelessScriptCharacterSequence(candidate);
|
|
597
|
+
const sourceCharacters = spacelessScriptCharacterSequence(source);
|
|
598
|
+
if (sourceCharacters.length <= SPACELESS_SCRIPT_LCS_SOURCE_LIMIT) {
|
|
599
|
+
const anchoredFraction = longestCommonSubstringLength(candidateCharacters, sourceCharacters)
|
|
600
|
+
/ candidateCharacters.length;
|
|
601
|
+
if (anchoredFraction < GROUNDING_MIN_COVERAGE) return 0;
|
|
602
|
+
}
|
|
603
|
+
const sourceTokens = tokenize(source);
|
|
604
|
+
for (const token of tokenize(candidate)) {
|
|
605
|
+
if (containsSpacelessScriptCharacter(token)) continue;
|
|
606
|
+
if (![...sourceTokens].some((sourceToken) => areGroundingTokensCompatible(token, sourceToken))) {
|
|
607
|
+
return 0;
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
return coverage;
|
|
611
|
+
}
|
|
612
|
+
|
|
503
613
|
export function normalizeForExactMatch(text: string): string {
|
|
504
614
|
return text.normalize("NFKC").toLocaleLowerCase().replace(/\s+/gu, " ").trim();
|
|
505
615
|
}
|
|
@@ -14,12 +14,12 @@ import {
|
|
|
14
14
|
groundingTokenSequence,
|
|
15
15
|
hasContradictoryPolarity,
|
|
16
16
|
hasExplicitRoleSubjectToken,
|
|
17
|
-
isAttachedNegatedAuxiliary,
|
|
18
17
|
isInterrogativeSourceSentence,
|
|
19
18
|
normalizeForExactMatch,
|
|
20
19
|
normalizedGroundingAlignmentTokenSequence,
|
|
21
20
|
normalizedGroundingTokenSequence,
|
|
22
21
|
sourceSentences,
|
|
22
|
+
spacelessScriptGroundedTokenScore,
|
|
23
23
|
splitGroundingClauses,
|
|
24
24
|
stemToken,
|
|
25
25
|
tokenize,
|
|
@@ -229,6 +229,8 @@ function groundedTokenScore(
|
|
|
229
229
|
requireAlignedArguments = true,
|
|
230
230
|
requireAllCandidateTokensGrounded = true,
|
|
231
231
|
): number {
|
|
232
|
+
const scriptAwareScore = spacelessScriptGroundedTokenScore(candidate, source);
|
|
233
|
+
if (scriptAwareScore !== null) return scriptAwareScore;
|
|
232
234
|
const candidateTokens = tokenize(candidate);
|
|
233
235
|
if (candidateTokens.size === 0) return 0;
|
|
234
236
|
const sourceTokens = tokenize(source);
|
|
@@ -2665,3 +2665,55 @@ test("grounding preserves plural identifiers after a proper-name copular subject
|
|
|
2665
2665
|
assert.deepEqual(result.facts, []);
|
|
2666
2666
|
}
|
|
2667
2667
|
});
|
|
2668
|
+
|
|
2669
|
+
const JAPANESE_OBSERVED_TURN = {
|
|
2670
|
+
role: "user" as const,
|
|
2671
|
+
content: "田中さんは東京に住んでいます。毎日電車で会社に行きます。",
|
|
2672
|
+
timestamp: "2026-07-25T12:00:00.000Z",
|
|
2673
|
+
};
|
|
2674
|
+
|
|
2675
|
+
test("script-aware grounding keeps CJK paraphrased facts and embedded entity names", async () => {
|
|
2676
|
+
const engine = fixtureEngine({
|
|
2677
|
+
localLlmEnabled: true,
|
|
2678
|
+
localLlmModel: "fixture-local",
|
|
2679
|
+
localLlmFallback: false,
|
|
2680
|
+
});
|
|
2681
|
+
Object.assign(engine, {
|
|
2682
|
+
localLlm: {
|
|
2683
|
+
async chatCompletion() {
|
|
2684
|
+
return {
|
|
2685
|
+
content: JSON.stringify({
|
|
2686
|
+
facts: [
|
|
2687
|
+
{
|
|
2688
|
+
category: "fact",
|
|
2689
|
+
content: "田中さんは東京に住んでいる",
|
|
2690
|
+
confidence: 0.9,
|
|
2691
|
+
tags: [],
|
|
2692
|
+
},
|
|
2693
|
+
{
|
|
2694
|
+
category: "fact",
|
|
2695
|
+
content: "田中さんは大阪に住んでいる",
|
|
2696
|
+
confidence: 0.9,
|
|
2697
|
+
tags: [],
|
|
2698
|
+
},
|
|
2699
|
+
],
|
|
2700
|
+
profileUpdates: [],
|
|
2701
|
+
entities: [{
|
|
2702
|
+
name: "田中",
|
|
2703
|
+
type: "person",
|
|
2704
|
+
facts: ["東京に住んでいる"],
|
|
2705
|
+
}],
|
|
2706
|
+
questions: [],
|
|
2707
|
+
}),
|
|
2708
|
+
};
|
|
2709
|
+
},
|
|
2710
|
+
},
|
|
2711
|
+
});
|
|
2712
|
+
|
|
2713
|
+
const result = await engine.extract([JAPANESE_OBSERVED_TURN]);
|
|
2714
|
+
|
|
2715
|
+
assert.deepEqual(result.facts.map((fact) => fact.content), ["田中さんは東京に住んでいる"]);
|
|
2716
|
+
assert.equal(result.entities.length, 1);
|
|
2717
|
+
assert.equal(result.entities[0]?.name, "田中");
|
|
2718
|
+
assert.deepEqual(result.entities[0]?.facts, ["東京に住んでいる"]);
|
|
2719
|
+
});
|
package/src/importance.ts
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import type { ImportanceLevel, ImportanceScore, MemoryCategory, MemoryFile } from "./types.js";
|
|
10
|
+
import { cjkBigrams, hasLatinWord, informationalLength } from "./utils/script-aware-text.js";
|
|
10
11
|
|
|
11
12
|
// ---------------------------------------------------------------------------
|
|
12
13
|
// Marker patterns for each tier
|
|
@@ -48,18 +49,6 @@ const HIGH_PATTERNS = [
|
|
|
48
49
|
/\b(scheduled|appointment|meeting|call)\b/i,
|
|
49
50
|
];
|
|
50
51
|
|
|
51
|
-
/** Normal importance markers (0.4-0.7) */
|
|
52
|
-
const NORMAL_PATTERNS = [
|
|
53
|
-
// Factual content
|
|
54
|
-
/\b(is|are|was|were|has|have|does|do)\b/i,
|
|
55
|
-
/\b(because|since|therefore|thus|so)\b/i,
|
|
56
|
-
// Emotional content
|
|
57
|
-
/\b(happy|sad|frustrated|excited|worried|anxious)\b/i,
|
|
58
|
-
/\b(feel|feeling|felt)\b/i,
|
|
59
|
-
// Technical details
|
|
60
|
-
/\b(version|api|endpoint|database|server|config)\b/i,
|
|
61
|
-
/\b(function|class|method|variable|parameter)\b/i,
|
|
62
|
-
];
|
|
63
52
|
|
|
64
53
|
/** Low importance markers (0.2-0.4) */
|
|
65
54
|
const LOW_PATTERNS = [
|
|
@@ -82,8 +71,6 @@ const TRIVIAL_PATTERNS = [
|
|
|
82
71
|
/^(bye|goodbye|later|see ya|ttyl)[.!]?\s*$/i,
|
|
83
72
|
/^(lol|haha|hehe|lmao|rofl)[.!]?\s*$/i,
|
|
84
73
|
/^(hmm+|uhh*|ahh*|err*|umm*)[.!]?\s*$/i,
|
|
85
|
-
// Very short content
|
|
86
|
-
/^.{1,10}$/,
|
|
87
74
|
];
|
|
88
75
|
|
|
89
76
|
// ---------------------------------------------------------------------------
|
|
@@ -134,16 +121,20 @@ const STOP_WORDS = new Set([
|
|
|
134
121
|
* Returns top N keywords sorted by relevance.
|
|
135
122
|
*/
|
|
136
123
|
function extractKeywords(content: string, maxKeywords: number = 5): string[] {
|
|
137
|
-
//
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
124
|
+
// Latin words plus CJK bigrams, so non-Latin content still yields
|
|
125
|
+
// keywords (pure-CJK prose returned none before #2192).
|
|
126
|
+
const candidates = [
|
|
127
|
+
...content
|
|
128
|
+
.toLowerCase()
|
|
129
|
+
.replace(/[^a-z0-9\s-]/g, " ")
|
|
130
|
+
.split(/\s+/)
|
|
131
|
+
.filter((w) => w.length >= 3 && !STOP_WORDS.has(w)),
|
|
132
|
+
...cjkBigrams(content),
|
|
133
|
+
];
|
|
143
134
|
|
|
144
135
|
// Count frequencies
|
|
145
136
|
const freq = new Map<string, number>();
|
|
146
|
-
for (const word of
|
|
137
|
+
for (const word of candidates) {
|
|
147
138
|
freq.set(word, (freq.get(word) ?? 0) + 1);
|
|
148
139
|
}
|
|
149
140
|
|
|
@@ -170,7 +161,6 @@ export function scoreImportance(
|
|
|
170
161
|
const reasons: string[] = [];
|
|
171
162
|
let score = 0.5; // Start at normal baseline
|
|
172
163
|
|
|
173
|
-
const lowerContent = content.toLowerCase();
|
|
174
164
|
const contentLength = content.length;
|
|
175
165
|
|
|
176
166
|
// Check for trivial content first (short-circuit)
|
|
@@ -185,6 +175,26 @@ export function scoreImportance(
|
|
|
185
175
|
}
|
|
186
176
|
}
|
|
187
177
|
|
|
178
|
+
// Very short content. informationalLength weights CJK/Hangul 3:1, so a
|
|
179
|
+
// dense non-Latin sentence ("予約しました") is not trivial for being
|
|
180
|
+
// under 10 raw characters while its English equivalent is not (#2192).
|
|
181
|
+
const weightedLength = informationalLength(content);
|
|
182
|
+
if (weightedLength >= 1 && weightedLength <= 10) {
|
|
183
|
+
return {
|
|
184
|
+
score: 0.1,
|
|
185
|
+
level: "trivial",
|
|
186
|
+
reasons: ["Trivial content (greeting, filler, or very short)"],
|
|
187
|
+
keywords: [],
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Documented non-Latin fallback (#2192): the English keyword tiers below
|
|
192
|
+
// cannot fire without Latin words, so the score rests on the 0.5 baseline
|
|
193
|
+
// plus script-agnostic signals (category boost, length, digits, tags).
|
|
194
|
+
if (!hasLatinWord(content)) {
|
|
195
|
+
reasons.push("Non-Latin script: keyword tiers not applicable; script-agnostic signals only");
|
|
196
|
+
}
|
|
197
|
+
|
|
188
198
|
// Check critical patterns
|
|
189
199
|
for (const pattern of CRITICAL_PATTERNS) {
|
|
190
200
|
if (pattern.test(content)) {
|
|
@@ -191,7 +191,7 @@ test("MCP engram.recall rejects non-string disclosure with a structured error",
|
|
|
191
191
|
const result = (response as { result?: { isError?: boolean; content?: Array<{ text?: string }> } } | undefined)?.result;
|
|
192
192
|
assert.strictEqual(result?.isError, true);
|
|
193
193
|
const text = result?.content?.[0]?.text ?? "";
|
|
194
|
-
assert.match(text, /disclosure
|
|
194
|
+
assert.match(text, /disclosure/i);
|
|
195
195
|
});
|
|
196
196
|
|
|
197
197
|
test("MCP engram.recall treats explicit `null` disclosure as absent (service applies default)", async () => {
|
package/src/taxonomy/resolver.ts
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
import type { MemoryCategory } from "../types.js";
|
|
9
9
|
import type { ResolverDecision, Taxonomy, TaxonomyCategory } from "./types.js";
|
|
10
|
+
import { cjkBigrams } from "../utils/script-aware-text.js";
|
|
10
11
|
|
|
11
12
|
const DEFAULT_CATEGORY_ID = "facts";
|
|
12
13
|
|
|
@@ -148,13 +149,12 @@ function computeKeywordScoreForTokens(contentTokens: Set<string>, cat: TaxonomyC
|
|
|
148
149
|
}
|
|
149
150
|
|
|
150
151
|
function tokenizeKeywordText(value: string): Set<string> {
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
);
|
|
152
|
+
// CJK bigrams tokenize BOTH sides (content and filing rules), so a
|
|
153
|
+
// taxonomy written in a CJK language matches CJK content; Latin-only
|
|
154
|
+
// taxonomies see no change (issue #2192).
|
|
155
|
+
const latin = (value.toLowerCase().match(/[a-z0-9]+/g) ?? [])
|
|
156
|
+
.filter((word) => word.length >= 3 && !TAXONOMY_KEYWORD_STOPWORDS.has(word));
|
|
157
|
+
return new Set([...latin, ...cjkBigrams(value)]);
|
|
158
158
|
}
|
|
159
159
|
|
|
160
160
|
function selectFallbackCategory(
|
package/src/topics.ts
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import type { MemoryFile, TopicScore } from "./types.js";
|
|
9
|
+
import { cjkBigrams } from "./utils/script-aware-text.js";
|
|
9
10
|
|
|
10
11
|
/** Stop words to exclude from topic extraction */
|
|
11
12
|
const STOP_WORDS = new Set([
|
|
@@ -28,14 +29,16 @@ const STOP_WORDS = new Set([
|
|
|
28
29
|
|
|
29
30
|
/**
|
|
30
31
|
* Extract terms from content.
|
|
31
|
-
* Returns normalized lowercase terms >= 3 chars
|
|
32
|
+
* Returns normalized lowercase terms >= 3 chars, plus CJK bigrams so
|
|
33
|
+
* non-Latin memories contribute topics instead of none (issue #2192).
|
|
32
34
|
*/
|
|
33
35
|
function extractTerms(content: string): string[] {
|
|
34
|
-
|
|
36
|
+
const latin = content
|
|
35
37
|
.toLowerCase()
|
|
36
38
|
.replace(/[^a-z0-9\s-]/g, " ")
|
|
37
39
|
.split(/\s+/)
|
|
38
40
|
.filter((w) => w.length >= 3 && !STOP_WORDS.has(w));
|
|
41
|
+
return [...latin, ...cjkBigrams(content)];
|
|
39
42
|
}
|
|
40
43
|
|
|
41
44
|
/**
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Script-aware text primitives for write-path classifiers (issue #2192).
|
|
3
|
+
*
|
|
4
|
+
* The local classifiers (importance, topics, taxonomy) grew around English
|
|
5
|
+
* keyword regexes and Latin-oriented tokenizers. Non-Latin prose matched no
|
|
6
|
+
* tier and lost every token: a Japanese sentence as dense as a full English
|
|
7
|
+
* clause was scored by raw character count and filed with zero keyword
|
|
8
|
+
* overlap. These helpers provide script-agnostic signals — weighted length,
|
|
9
|
+
* Latin-word detection, CJK bigram tokenization — without language detection.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/** CJK + Hangul: kana (incl. halfwidth), Hangul jamo/syllables, ext A, unified + compat ideographs, ext B. */
|
|
13
|
+
const CJK_CHAR =
|
|
14
|
+
/[\u3040-\u30ff\u3130-\u318f\u3400-\u4dbf\u4e00-\u9fff\uac00-\ud7af\uf900-\ufaff\uff66-\uff9f\u{20000}-\u{2a6df}]/u;
|
|
15
|
+
|
|
16
|
+
const CJK_RUN =
|
|
17
|
+
/[\u3040-\u30ff\u3130-\u318f\u3400-\u4dbf\u4e00-\u9fff\uac00-\ud7af\uf900-\ufaff\uff66-\uff9f\u{20000}-\u{2a6df}]+/gu;
|
|
18
|
+
|
|
19
|
+
/** True when the text contains a Latin word of two or more letters. */
|
|
20
|
+
export function hasLatinWord(text: string): boolean {
|
|
21
|
+
return /[a-z]{2,}/i.test(text);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Length in Latin-equivalent characters: every code point counts 1,
|
|
26
|
+
* CJK/Hangul code points count 3. One han/kana glyph typically carries a
|
|
27
|
+
* whole Latin word of information ("予約しました" ≈ "made a reservation"),
|
|
28
|
+
* so length cutoffs calibrated on English must weight dense scripts up, or
|
|
29
|
+
* short non-Latin sentences are systematically dumped into trivial buckets.
|
|
30
|
+
*/
|
|
31
|
+
export function informationalLength(text: string): number {
|
|
32
|
+
let weight = 0;
|
|
33
|
+
for (const ch of text) {
|
|
34
|
+
weight += CJK_CHAR.test(ch) ? 3 : 1;
|
|
35
|
+
}
|
|
36
|
+
return weight;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Sliding bigrams over contiguous CJK runs — the standard script-agnostic
|
|
41
|
+
* substitute for word tokenization in unsegmented scripts. Runs shorter
|
|
42
|
+
* than two characters yield nothing (a lone glyph is too generic a token).
|
|
43
|
+
*/
|
|
44
|
+
export function cjkBigrams(text: string): string[] {
|
|
45
|
+
const bigrams: string[] = [];
|
|
46
|
+
for (const run of text.match(CJK_RUN) ?? []) {
|
|
47
|
+
for (let i = 0; i + 1 < run.length; i++) {
|
|
48
|
+
bigrams.push(run.slice(i, i + 2));
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return bigrams;
|
|
52
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Issue #2192: write-path classifiers must not dump non-English content
|
|
3
|
+
* into the lowest importance/taxonomy bucket merely for lacking English
|
|
4
|
+
* tokens. Script-agnostic signals (weighted length, CJK bigrams) carry the
|
|
5
|
+
* decision instead of English keywords.
|
|
6
|
+
*/
|
|
7
|
+
import test from "node:test";
|
|
8
|
+
import assert from "node:assert/strict";
|
|
9
|
+
|
|
10
|
+
import { scoreImportance } from "./importance.js";
|
|
11
|
+
import { extractTopics } from "./topics.js";
|
|
12
|
+
import { DEFAULT_TAXONOMY } from "./taxonomy/default-taxonomy.js";
|
|
13
|
+
import { resolveCategory } from "./taxonomy/resolver.js";
|
|
14
|
+
import type { Taxonomy } from "./taxonomy/types.js";
|
|
15
|
+
import type { MemoryFile, MemoryFrontmatter } from "./types.js";
|
|
16
|
+
|
|
17
|
+
function makeMemory(id: string, content: string): MemoryFile {
|
|
18
|
+
const frontmatter: MemoryFrontmatter = {
|
|
19
|
+
id,
|
|
20
|
+
category: "fact",
|
|
21
|
+
created: "2026-08-17T00:00:00.000Z",
|
|
22
|
+
updated: "2026-08-17T00:00:00.000Z",
|
|
23
|
+
source: "extraction",
|
|
24
|
+
confidence: 0.8,
|
|
25
|
+
confidenceTier: "explicit",
|
|
26
|
+
tags: [],
|
|
27
|
+
};
|
|
28
|
+
return { path: `/memories/facts/2026-08-17/${id}.md`, frontmatter, content };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
test("importance: dense Japanese/Chinese facts stay out of the trivial/low buckets", () => {
|
|
32
|
+
// 12 CJK chars ≈ a full English clause ("I will move to Tokyo next month").
|
|
33
|
+
// Before #2192 this matched the raw `^.{1,10}$` short-content rule and was
|
|
34
|
+
// scored trivial — dropping it at the write gate.
|
|
35
|
+
const japanese = scoreImportance("来月、東京に引っ越します。", "commitment");
|
|
36
|
+
assert.ok(
|
|
37
|
+
japanese.score >= 0.4,
|
|
38
|
+
`Japanese commitment scored ${japanese.score} (${japanese.level})`,
|
|
39
|
+
);
|
|
40
|
+
assert.notEqual(japanese.level, "trivial");
|
|
41
|
+
assert.notEqual(japanese.level, "low");
|
|
42
|
+
|
|
43
|
+
const chinese = scoreImportance("会议定在周五下午两点,请准时参加。", "fact");
|
|
44
|
+
assert.ok(
|
|
45
|
+
chinese.score >= 0.4,
|
|
46
|
+
`Chinese meeting fact scored ${chinese.score} (${chinese.level})`,
|
|
47
|
+
);
|
|
48
|
+
assert.notEqual(chinese.level, "trivial");
|
|
49
|
+
assert.notEqual(chinese.level, "low");
|
|
50
|
+
|
|
51
|
+
// Keywords must not be empty for non-Latin content (CJK bigram fallback).
|
|
52
|
+
assert.ok(
|
|
53
|
+
japanese.keywords.length > 0,
|
|
54
|
+
`expected CJK bigram keywords, got ${JSON.stringify(japanese.keywords)}`,
|
|
55
|
+
);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test("importance: genuinely trivial CJK interjections stay trivial", () => {
|
|
59
|
+
// "はい" ≈ "yes" — 2 glyphs, weighted length 6, still under the cutoff.
|
|
60
|
+
assert.equal(scoreImportance("はい", "fact").level, "trivial");
|
|
61
|
+
assert.equal(scoreImportance("嗯", "fact").level, "trivial");
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test("taxonomy: CJK filing rules match CJK content instead of the fallback pick", () => {
|
|
65
|
+
const taxonomy: Taxonomy = {
|
|
66
|
+
version: 1,
|
|
67
|
+
categories: [
|
|
68
|
+
{
|
|
69
|
+
id: "paris-notes",
|
|
70
|
+
name: "Paris Notes",
|
|
71
|
+
description: "Statements about Paris",
|
|
72
|
+
filingRules: ["paris seine arrondissement"],
|
|
73
|
+
// Lower priority wins the pre-fix keyword-less tie-break, so this
|
|
74
|
+
// fixture fails unless CJK content actually matches CJK filing rules.
|
|
75
|
+
priority: 10,
|
|
76
|
+
memoryCategories: ["fact"],
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
id: "tokyo-notes",
|
|
80
|
+
name: "Tokyo Notes",
|
|
81
|
+
description: "東京に関する事実",
|
|
82
|
+
filingRules: ["東京 についての記録"],
|
|
83
|
+
priority: 40,
|
|
84
|
+
memoryCategories: ["fact"],
|
|
85
|
+
},
|
|
86
|
+
],
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const decision = resolveCategory("東京に住んでいます。", "fact", taxonomy);
|
|
90
|
+
assert.equal(decision.categoryId, "tokyo-notes");
|
|
91
|
+
assert.ok(decision.confidence >= 0.9, `confidence ${decision.confidence}`);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test("taxonomy: default taxonomy resolution is unchanged for non-Latin facts", () => {
|
|
95
|
+
const decision = resolveCategory("会議は金曜日の午後です。", "fact", DEFAULT_TAXONOMY);
|
|
96
|
+
assert.equal(decision.categoryId, "facts");
|
|
97
|
+
assert.equal(decision.confidence, 1.0);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
test("topics: CJK memories contribute topics via bigrams", () => {
|
|
101
|
+
const topics = extractTopics(
|
|
102
|
+
[
|
|
103
|
+
makeMemory(
|
|
104
|
+
"fact-cjk",
|
|
105
|
+
"東京に住んでいます。東京の会社で働いています。",
|
|
106
|
+
),
|
|
107
|
+
makeMemory("fact-en", "Deployed the api server to production."),
|
|
108
|
+
],
|
|
109
|
+
20,
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
const tokyo = topics.find((t) => t.term === "東京");
|
|
113
|
+
assert.ok(tokyo, `expected 東京 in topics: ${JSON.stringify(topics)}`);
|
|
114
|
+
assert.ok(tokyo.count >= 2, `expected 東京 twice, got ${tokyo.count}`);
|
|
115
|
+
});
|