@remnic/core 9.65.2 → 9.65.4
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 +44 -44
- package/dist/access-admin-ops-surface.js +21 -19
- package/dist/access-authorization-probe.d.ts +38 -38
- package/dist/access-authorization-probe.js +22 -20
- package/dist/access-boundary.d.ts +40 -40
- package/dist/access-boundary.js +21 -19
- package/dist/access-cli.js +62 -60
- package/dist/access-cli.js.map +1 -1
- package/dist/access-coding-context-resolution.d.ts +3 -1
- package/dist/access-extraction-force-flush.d.ts +25 -25
- package/dist/access-extraction-force-flush.js +21 -19
- package/dist/access-health-types.d.ts +3 -1
- package/dist/access-http-lcm-compaction.d.ts +39 -39
- package/dist/access-http-lifecycle-flush.d.ts +39 -39
- package/dist/access-http-offline-stream.d.ts +25 -25
- package/dist/access-http-query.d.ts +4 -2
- package/dist/access-http-query.js +23 -21
- package/dist/access-http.d.ts +51 -44
- package/dist/access-http.js +37 -35
- package/dist/access-identity-continuity-surface.d.ts +12 -12
- package/dist/access-identity-continuity-surface.js +21 -19
- package/dist/access-lcm-surface.d.ts +27 -27
- package/dist/access-lcm-surface.js +21 -19
- package/dist/access-mcp-admin-tools.d.ts +9 -1
- package/dist/access-mcp-admin-tools.js +3 -1
- package/dist/access-mcp-output-schemas.js +1 -1
- package/dist/access-mcp.d.ts +39 -39
- package/dist/access-mcp.js +31 -29
- package/dist/access-memory-search-fanout.d.ts +4 -2
- package/dist/{access-namespace-preflight-Dhvcl-T0.d.ts → access-namespace-preflight-D_6mo8ms.d.ts} +1 -1
- package/dist/access-namespace-preflight.d.ts +5 -3
- package/dist/access-namespace-preflight.js +21 -19
- package/dist/access-observe-write-surface.d.ts +39 -39
- package/dist/access-observe-write-surface.js +21 -19
- package/dist/access-offline-manifest.d.ts +12 -12
- package/dist/access-offline-manifest.js +3 -3
- package/dist/access-operations-batch.js +25 -23
- package/dist/access-operations.d.ts +40 -40
- package/dist/access-operations.js +28 -26
- package/dist/access-recall-concurrency.d.ts +40 -40
- package/dist/access-recall-concurrency.js +21 -19
- package/dist/access-recall-response.d.ts +24 -24
- package/dist/access-recall-response.js +21 -19
- package/dist/access-recall-surface.d.ts +24 -24
- package/dist/access-recall-surface.js +21 -19
- package/dist/access-schema.d.ts +45 -45
- package/dist/{access-service-Bke1alZO.d.ts → access-service-DqPa3hy4.d.ts} +219 -75
- package/dist/access-service-helpers.d.ts +40 -40
- package/dist/access-service.d.ts +47 -47
- package/dist/access-service.js +21 -19
- package/dist/access-surface-catalog.d.ts +40 -40
- package/dist/access-surface-catalog.js +27 -21
- package/dist/access-surface-catalog.js.map +1 -1
- package/dist/access-wearables-meetings-surface.d.ts +5 -3
- package/dist/action-confidence.d.ts +3 -1
- package/dist/active-memory-bridge.d.ts +3 -1
- package/dist/active-recall.d.ts +3 -1
- package/dist/active-recall.js +10 -1
- package/dist/active-recall.js.map +1 -1
- package/dist/ambient-provenance.d.ts +3 -1
- package/dist/artifact-search.d.ts +3 -1
- package/dist/{auto-sync-H64ZCNIR.js → auto-sync-XVMLL2RH.js} +5 -5
- package/dist/behavior-learner.d.ts +3 -1
- package/dist/behavior-signals.d.ts +3 -1
- package/dist/bootstrap.d.ts +12 -12
- package/dist/briefing-window.d.ts +4 -2
- package/dist/briefing.d.ts +4 -2
- package/dist/briefing.js +4 -4
- package/dist/buffer-surprise-report.d.ts +3 -1
- package/dist/buffer-turn-helpers.d.ts +3 -1
- package/dist/buffer.d.ts +4 -2
- package/dist/bulk-import/index.d.ts +5 -3
- package/dist/calibration.d.ts +3 -1
- package/dist/capabilities.d.ts +3 -1
- package/dist/{catalog-DV6qnbr7.d.ts → catalog-DO_7zfkv.d.ts} +1 -1
- package/dist/causal-behavior.d.ts +1 -1
- package/dist/causal-consolidation.d.ts +3 -1
- package/dist/causal-consolidation.js +12 -9
- package/dist/causal-consolidation.js.map +1 -1
- package/dist/causal-trajectory-graph.d.ts +1 -1
- package/dist/chunk-236PXYG7.js +232 -0
- package/dist/chunk-236PXYG7.js.map +1 -0
- package/dist/{chunk-YB42WZCY.js → chunk-2IXQSGCU.js} +2 -2
- package/dist/{chunk-PE255F4T.js → chunk-2MVTAIL3.js} +2 -2
- package/dist/{chunk-LGO6A7GJ.js → chunk-3JZBPTLD.js} +338 -80
- package/dist/chunk-3JZBPTLD.js.map +1 -0
- package/dist/{chunk-GDFVPUJ5.js → chunk-3NH3VP3U.js} +2 -2
- package/dist/{chunk-TBOJ76ER.js → chunk-AB3A7EFH.js} +18 -2
- package/dist/{chunk-TBOJ76ER.js.map → chunk-AB3A7EFH.js.map} +1 -1
- package/dist/{chunk-GNCCUR6K.js → chunk-AXGFOFNY.js} +2 -2
- package/dist/{chunk-SB7D4BT7.js → chunk-AZLLH6YA.js} +1554 -114
- package/dist/chunk-AZLLH6YA.js.map +1 -0
- package/dist/{chunk-5YQ63T4B.js → chunk-BK5G2MHO.js} +2 -2
- package/dist/{chunk-LZH5FR2W.js → chunk-CQBIN77D.js} +2 -2
- package/dist/{chunk-35NN3ZFA.js → chunk-CTVWELNK.js} +21 -3
- package/dist/chunk-CTVWELNK.js.map +1 -0
- package/dist/{chunk-NKKD4ZAU.js → chunk-DORFF5N5.js} +2 -2
- package/dist/{chunk-BWLRUMOG.js → chunk-DPNXADEI.js} +441 -2
- package/dist/chunk-DPNXADEI.js.map +1 -0
- package/dist/{chunk-SQI57UCJ.js → chunk-DUEDOYZD.js} +2 -2
- package/dist/{chunk-5NM4OH4P.js → chunk-DUZARALI.js} +3 -3
- package/dist/{chunk-2E36UXVW.js → chunk-ECLJ4LA2.js} +2 -2
- package/dist/{chunk-TZNQUNKL.js → chunk-FC7TIRFS.js} +15 -1
- package/dist/chunk-FC7TIRFS.js.map +1 -0
- package/dist/{chunk-IID2CMUU.js → chunk-GHD2LLG4.js} +2 -2
- package/dist/{chunk-ZQHI2BZB.js → chunk-HF5BUTI6.js} +2 -2
- package/dist/{chunk-3DVBKTSO.js → chunk-HSZMX3ZJ.js} +4 -4
- package/dist/{chunk-S2DPMJXH.js → chunk-I23ZHYSL.js} +2 -2
- package/dist/{chunk-ODZG724P.js → chunk-I2HTKOOP.js} +2 -2
- package/dist/chunk-I32NKXE2.js +879 -0
- package/dist/chunk-I32NKXE2.js.map +1 -0
- package/dist/{chunk-NTWN6HB2.js → chunk-IURMJBGP.js} +2 -2
- package/dist/{chunk-OCZIYAE5.js → chunk-IWKGOCL6.js} +3 -3
- package/dist/{chunk-UXO5IMBK.js → chunk-J3ATBB7M.js} +2 -2
- package/dist/{chunk-Y7DK7HHJ.js → chunk-JPAKBNTM.js} +2 -2
- package/dist/{chunk-SPUJNJJC.js → chunk-KFIOPZ25.js} +2 -2
- package/dist/{chunk-JYYBA7TF.js → chunk-KOO37E4Y.js} +2 -2
- package/dist/{chunk-OTHZWLUD.js → chunk-KPB2QHJZ.js} +2 -2
- package/dist/{chunk-SBDCZ2AX.js → chunk-LBFFNDFM.js} +1 -1
- package/dist/chunk-LBFFNDFM.js.map +1 -0
- package/dist/{chunk-CT56C22C.js → chunk-LYBIHM4N.js} +4 -4
- package/dist/{chunk-3I7ASZN3.js → chunk-MFOSZUIT.js} +2 -2
- package/dist/{chunk-DZUJ46DO.js → chunk-NLEH4RTB.js} +2 -2
- package/dist/{chunk-ZO6AXLEH.js → chunk-OCCHUEZB.js} +2 -2
- package/dist/{chunk-EILZG5VC.js → chunk-OU34IQXV.js} +2 -2
- package/dist/{chunk-2XDWWPTN.js → chunk-OZDFB22S.js} +2 -2
- package/dist/{chunk-UIN2BWDD.js → chunk-PTZQYCJ4.js} +2 -2
- package/dist/{chunk-7BY7V4YX.js → chunk-Q4WONDMJ.js} +4 -3
- package/dist/{chunk-7BY7V4YX.js.map → chunk-Q4WONDMJ.js.map} +1 -1
- package/dist/{chunk-Z7PBQPJ4.js → chunk-QJW4MZSW.js} +2 -2
- package/dist/{chunk-R7JAM2XF.js → chunk-R2MZCYA6.js} +3 -3
- package/dist/{chunk-TF77C7SV.js → chunk-SR5LNI6Q.js} +7 -5
- package/dist/chunk-SR5LNI6Q.js.map +1 -0
- package/dist/{chunk-LBWQ65SX.js → chunk-T42L3S6A.js} +4 -4
- package/dist/{chunk-J275IYMG.js → chunk-THVI4Y7X.js} +34 -7
- package/dist/chunk-THVI4Y7X.js.map +1 -0
- package/dist/{chunk-QOS5LBZN.js → chunk-TJFI3K2O.js} +15 -3
- package/dist/chunk-TJFI3K2O.js.map +1 -0
- package/dist/{chunk-2YI72KM3.js → chunk-UCKG36HO.js} +2 -2
- package/dist/{chunk-SIHSCCUQ.js → chunk-UFR6HX7A.js} +1 -1
- package/dist/{chunk-CDUUDBWJ.js → chunk-VFYRYFLL.js} +2 -2
- package/dist/{chunk-XRYZRW54.js → chunk-WDXD6ITO.js} +2 -2
- package/dist/{chunk-4KYOLI6M.js → chunk-XGM3VQWG.js} +120 -14
- package/dist/chunk-XGM3VQWG.js.map +1 -0
- package/dist/{chunk-MN4KB2A4.js → chunk-YDKF4MSJ.js} +3 -3
- package/dist/{chunk-YLDXWCZP.js → chunk-YHGCT462.js} +14 -9
- package/dist/chunk-YHGCT462.js.map +1 -0
- package/dist/{chunk-7YQ4IYS2.js → chunk-YOXVZJFM.js} +2 -2
- package/dist/{chunk-ZWXJBFZU.js → chunk-YZJM6CLF.js} +8 -8
- package/dist/chunk-ZCUTSMR2.js +61 -0
- package/dist/chunk-ZCUTSMR2.js.map +1 -0
- package/dist/{chunk-LUNJ3WFO.js → chunk-ZK4BFABK.js} +4 -4
- package/dist/{chunk-V3MXUL3D.js → chunk-ZPOHEB5Z.js} +329 -653
- package/dist/chunk-ZPOHEB5Z.js.map +1 -0
- package/dist/{chunk-RDFF2NAU.js → chunk-ZXBTTMGZ.js} +184 -88
- package/dist/chunk-ZXBTTMGZ.js.map +1 -0
- package/dist/{cli-cShQ13XG.d.ts → cli-DO3i14pM.d.ts} +5 -5
- package/dist/cli.d.ts +25 -25
- package/dist/cli.js +51 -48
- package/dist/coding/pre-action-gate.d.ts +2 -60
- package/dist/compounding/engine.d.ts +4 -2
- package/dist/compounding/engine.js +4 -4
- package/dist/compounding/preference-consolidator.d.ts +3 -1
- package/dist/compression-optimizer.d.ts +3 -1
- package/dist/config.d.ts +3 -1
- package/dist/config.js +10 -1
- package/dist/connectors/codex-materialize-runner.d.ts +3 -1
- package/dist/connectors/codex-materialize-runner.js +8 -5
- package/dist/connectors/codex-materialize.d.ts +16 -1
- package/dist/connectors/codex-materialize.js +10 -1
- package/dist/connectors/index.d.ts +3 -1
- package/dist/connectors/index.js +8 -5
- package/dist/consolidation-provenance-check.d.ts +4 -2
- package/dist/consolidation-undo.d.ts +4 -2
- package/dist/contradiction/index.d.ts +10 -270
- package/dist/contradiction/index.js +12 -7
- package/dist/{contradiction-review-QKJH5DKD.js → contradiction-review-WRVLRWHS.js} +6 -2
- package/dist/{contradiction-scan-VOTQRRSX.js → contradiction-scan-AKUWPX5N.js} +5 -3
- package/dist/converge-config.d.ts +3 -1
- package/dist/convergence-refresh.d.ts +4 -2
- package/dist/convergence-refresh.js +6 -6
- package/dist/conversation-index/backend.d.ts +3 -1
- package/dist/conversation-index/chunker.d.ts +3 -1
- package/dist/conversation-index/faiss-adapter.d.ts +3 -1
- package/dist/conversation-index/indexer.d.ts +3 -1
- package/dist/conversation-index/search.d.ts +3 -1
- package/dist/corpus-watermark.d.ts +3 -1
- package/dist/corpus-watermark.js +6 -6
- package/dist/cross-lingual-recall.d.ts +3 -1
- package/dist/day-summary.d.ts +3 -1
- package/dist/delinearize.d.ts +3 -1
- package/dist/dependency-propagation-config.d.ts +3 -1
- package/dist/{dependency-propagation-delivery-CsGg-VwS.d.ts → dependency-propagation-delivery-D23_x-s5.d.ts} +1 -1
- package/dist/direct-answer-wiring.d.ts +3 -1
- package/dist/direct-answer.d.ts +3 -1
- package/dist/embedding-fallback.d.ts +3 -1
- package/dist/enrichment/index.d.ts +3 -1
- package/dist/entity-origin-fields.d.ts +3 -1
- package/dist/entity-retrieval.d.ts +4 -2
- package/dist/entity-retrieval.js +4 -4
- package/dist/entity-schema.d.ts +3 -1
- package/dist/episodic-context.d.ts +3 -1
- package/dist/explicit-capture.d.ts +12 -12
- package/dist/explicit-capture.js +1 -1
- package/dist/external-wiki-access.d.ts +40 -40
- package/dist/external-wiki-access.js +22 -20
- package/dist/external-wiki-collection-registration.d.ts +3 -1
- package/dist/external-wiki-collection.d.ts +3 -1
- package/dist/external-wiki-mcp-tools.d.ts +40 -40
- package/dist/extraction-error-classification.d.ts +3 -1
- package/dist/extraction-faithfulness.d.ts +3 -1
- package/dist/extraction-judge-telemetry.d.ts +3 -1
- package/dist/extraction-judge-training.d.ts +3 -1
- package/dist/extraction-judge.d.ts +3 -1
- package/dist/extraction-judge.js +2 -2
- package/dist/extraction-liveness.d.ts +3 -1
- package/dist/extraction-normalization.d.ts +3 -1
- package/dist/extraction-prompt.d.ts +3 -1
- package/dist/extraction-source-grounding-rules.d.ts +3 -1
- package/dist/extraction-source-grounding.d.ts +3 -1
- package/dist/extraction.d.ts +3 -1
- package/dist/extraction.js +9 -7
- package/dist/fallback-llm.d.ts +3 -1
- package/dist/graph-dashboard-diff.d.ts +3 -1
- package/dist/graph-dashboard-key.d.ts +3 -1
- package/dist/graph-dashboard-parser.d.ts +3 -1
- package/dist/graph-edge-reinforcement.d.ts +3 -1
- package/dist/graph-path-reconstruction.d.ts +3 -1
- package/dist/graph-path-scoring.d.ts +3 -1
- package/dist/graph-snapshot.d.ts +3 -1
- package/dist/graph.d.ts +3 -1
- package/dist/harmonic-retrieval.js +1 -1
- package/dist/importance.d.ts +3 -1
- package/dist/importers/index.d.ts +3 -1
- package/dist/in-flight-reads.d.ts +3 -1
- package/dist/index--QhGIXF2.d.ts +284 -0
- package/dist/index.d.ts +314 -45
- package/dist/index.js +163 -88
- package/dist/intent.d.ts +3 -1
- package/dist/lcm/engine.d.ts +3 -1
- package/dist/lcm/index.d.ts +3 -1
- package/dist/lcm/tools.d.ts +3 -1
- package/dist/lifecycle.d.ts +3 -1
- package/dist/live-connectors-runner.d.ts +3 -1
- package/dist/local-llm.d.ts +3 -1
- package/dist/local-model-endpoint.d.ts +3 -1
- package/dist/maintenance/memory-governance.d.ts +3 -1
- package/dist/maintenance/memory-governance.js +4 -4
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.d.ts +4 -2
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +4 -4
- package/dist/maintenance/rebuild-memory-projection.d.ts +4 -2
- package/dist/maintenance/rebuild-memory-projection.js +5 -5
- package/dist/{maintenance-jhMpeGne.d.ts → maintenance-PFvY1Pw_.d.ts} +3 -3
- package/dist/mcp-memory-inspector-app.d.ts +37 -37
- package/dist/memory-action-policy.d.ts +3 -1
- package/dist/memory-cache.d.ts +3 -1
- package/dist/memory-lifecycle-ledger-utils.d.ts +3 -1
- package/dist/memory-projection-store.d.ts +3 -1
- package/dist/memory-provenance.d.ts +3 -1
- package/dist/memory-snapshot.d.ts +3 -1
- package/dist/memory-subject-cli.d.ts +40 -40
- package/dist/memory-subject.d.ts +42 -42
- package/dist/memory-worth-outcomes.d.ts +4 -2
- package/dist/models-json.d.ts +3 -1
- package/dist/namespaces/migrate.d.ts +5 -3
- package/dist/namespaces/migrate.js +5 -5
- package/dist/namespaces/principal.d.ts +3 -1
- package/dist/namespaces/search.d.ts +3 -1
- package/dist/namespaces/storage.d.ts +5 -3
- package/dist/namespaces/storage.js +4 -4
- package/dist/native-knowledge.d.ts +3 -1
- package/dist/offline-sync-impression-drain.d.ts +3 -1
- package/dist/operator-doctor-corpus.d.ts +3 -1
- package/dist/operator-doctor-corpus.js +7 -7
- package/dist/operator-doctor-replica.d.ts +3 -1
- package/dist/operator-toolkit.d.ts +5 -3
- package/dist/operator-toolkit.js +19 -13
- package/dist/orchestration/compression-guideline-coordinator.d.ts +4 -2
- package/dist/orchestration/maintenance.d.ts +6 -4
- package/dist/orchestration/maintenance.js +10 -10
- package/dist/{orchestrator-DyGUC7re.d.ts → orchestrator-4wzJrPVm.d.ts} +23 -9
- package/dist/orchestrator.d.ts +12 -12
- package/dist/orchestrator.js +62 -60
- package/dist/patterns-cli.d.ts +3 -1
- package/dist/{pipeline-C25Oxtp1.d.ts → pipeline-CgXgdIrF.d.ts} +1 -1
- package/dist/policy-runtime.d.ts +3 -1
- package/dist/proactive-contention.d.ts +3 -1
- package/dist/provenance.d.ts +3 -1
- package/dist/{public-http-CMe7NQzj.d.ts → public-http-DhIXYBYI.d.ts} +1 -1
- package/dist/{qmd-DkDVCbgF.d.ts → qmd-BppGGxsi.d.ts} +1 -1
- package/dist/qmd-preflight.d.ts +4 -2
- package/dist/qmd-recall-cache.d.ts +3 -1
- package/dist/qmd.d.ts +4 -2
- package/dist/recall-concurrency-config.d.ts +3 -1
- package/dist/recall-disclosure-escalation.d.ts +3 -1
- package/dist/recall-explain-renderer.d.ts +3 -1
- package/dist/recall-explain-renderer.js +3 -3
- package/dist/recall-memory-map.d.ts +3 -1
- package/dist/recall-planner-llm.d.ts +3 -1
- package/dist/recall-state.d.ts +3 -1
- package/dist/recall-tag-filter.d.ts +3 -1
- package/dist/recall-timings.d.ts +3 -1
- package/dist/recall-xray-cli.d.ts +3 -1
- package/dist/recall-xray-cli.js +4 -4
- package/dist/recall-xray-renderer.d.ts +3 -1
- package/dist/recall-xray-renderer.js +3 -3
- package/dist/recall-xray.d.ts +3 -1
- package/dist/recall-xray.js +2 -2
- package/dist/reconcile/cursor.d.ts +3 -1
- package/dist/reconcile/manifest.d.ts +3 -1
- package/dist/reconcile/manifest.js +2 -2
- package/dist/reconcile/plan.d.ts +3 -1
- package/dist/replay/normalizers/chatgpt.d.ts +3 -1
- package/dist/replay/normalizers/claude.d.ts +3 -1
- package/dist/replay/normalizers/openclaw.d.ts +3 -1
- package/dist/replay/normalizers/shared.d.ts +3 -1
- package/dist/replay/runner.d.ts +3 -1
- package/dist/replay/types.d.ts +3 -1
- package/dist/replica-divergence.d.ts +3 -1
- package/dist/replica-peers-config.d.ts +3 -1
- package/dist/{resolution-MFY72STR.js → resolution-4552BNRV.js} +14 -4
- package/dist/resolve-auth-token.d.ts +3 -1
- package/dist/resume-bundles.js +8 -2
- package/dist/retrieval-agents.d.ts +4 -2
- package/dist/retrieval-tiers.d.ts +3 -1
- package/dist/routing/engine.d.ts +3 -1
- package/dist/routing/store.d.ts +3 -1
- package/dist/salvage-envelope.d.ts +3 -1
- package/dist/salvage-envelope.js +1 -1
- package/dist/schemas.d.ts +44 -44
- package/dist/{scope-profiles-B9xMoFO-.d.ts → scope-profiles-ChZUtxDU.d.ts} +1 -1
- package/dist/search/embed-helper.d.ts +3 -1
- package/dist/search/factory.d.ts +3 -1
- package/dist/search/index.d.ts +3 -1
- package/dist/search/lancedb-backend.d.ts +3 -1
- package/dist/search/meilisearch-backend.d.ts +3 -1
- package/dist/search/noop-backend.d.ts +3 -1
- package/dist/search/orama-backend.d.ts +3 -1
- package/dist/search/port.d.ts +3 -1
- package/dist/search/remote-backend.d.ts +3 -1
- package/dist/{semantic-TddhF0y7.d.ts → semantic-BalR6EXU.d.ts} +1 -1
- package/dist/{semantic-consolidation-C4uykW84.d.ts → semantic-consolidation-mSPwQSmF.d.ts} +1 -1
- package/dist/semantic-consolidation.d.ts +4 -2
- package/dist/semantic-consolidation.js +9 -6
- package/dist/semantic-rule-promotion.js +4 -4
- package/dist/semantic-rule-verifier.d.ts +3 -1
- package/dist/semantic-rule-verifier.js +4 -4
- package/dist/{service-DCZDZNcV.d.ts → service-BICWLwSP.d.ts} +2 -2
- package/dist/session-observer-bands.d.ts +3 -1
- package/dist/session-observer-state.d.ts +3 -1
- package/dist/shared-context/manager.d.ts +3 -1
- package/dist/signal.d.ts +3 -1
- package/dist/skills-registry.js +4 -56
- package/dist/skills-registry.js.map +1 -1
- package/dist/source-agent-qualifier.d.ts +3 -1
- package/dist/source-agent-qualifier.js +8 -5
- package/dist/{storage-CcKmEjL8.d.ts → storage-B7urx1K3.d.ts} +1 -1
- package/dist/storage.d.ts +4 -2
- package/dist/storage.js +6 -6
- package/dist/subject-config.d.ts +3 -1
- package/dist/summarizer.d.ts +3 -1
- package/dist/summary-snapshot.d.ts +3 -1
- package/dist/support-passport/index.d.ts +43 -43
- package/dist/support-passport/index.js +24 -22
- package/dist/temporal-supersession.d.ts +4 -2
- package/dist/temporal-timeline-recall.d.ts +3 -1
- package/dist/temporal-validity.d.ts +3 -1
- package/dist/threading.d.ts +3 -1
- package/dist/tier-migration.d.ts +4 -2
- package/dist/tier-routing.d.ts +3 -1
- package/dist/topics.d.ts +3 -1
- package/dist/transcript.d.ts +3 -1
- package/dist/transfer/types.d.ts +20 -20
- package/dist/trust-score-stage.d.ts +3 -1
- package/dist/trust-score.d.ts +3 -1
- package/dist/{types-Pi0lfaEt.d.ts → types-ETU5N6nB.d.ts} +433 -7
- package/dist/types.d.ts +3 -1
- package/dist/types.js +1 -1
- package/dist/utility-runtime.d.ts +3 -1
- package/dist/verified-recall.js +4 -4
- package/dist/who-knows-cli.d.ts +40 -40
- package/dist/who-knows-cli.js +5 -5
- package/dist/who-knows.d.ts +42 -42
- package/dist/who-knows.js +4 -4
- package/dist/write-envelope.d.ts +3 -1
- package/dist/write-envelope.js +1 -1
- package/package.json +2 -2
- package/src/access-boundary.ts +4 -0
- package/src/access-http.ts +6 -5
- package/src/access-mcp-admin-tools.ts +24 -0
- package/src/access-mcp-output-schemas.ts +14 -0
- package/src/access-mcp.ts +2 -2
- package/src/access-operations-batch.ts +5 -0
- package/src/access-service.ts +27 -0
- package/src/access-surface-catalog.test.ts +24 -0
- package/src/access-surface-catalog.ts +4 -0
- package/src/cli/skills-commands.ts +150 -0
- package/src/cli.ts +4 -2
- package/src/coding/action-gate.test.ts +341 -0
- package/src/coding/action-gate.ts +556 -0
- package/src/config.ts +9 -2
- package/src/connectors/codex-materialize-runner.ts +16 -0
- package/src/connectors/codex-materialize-skills.test.ts +141 -0
- package/src/connectors/codex-materialize.ts +58 -40
- package/src/connectors/materialize-paths.ts +50 -0
- package/src/connectors/materialize-skills.ts +127 -0
- package/src/contradiction/contradiction-review.ts +50 -4
- package/src/contradiction/contradiction-scan.ts +14 -3
- package/src/contradiction/resolution.ts +50 -2
- package/src/index.ts +9 -13
- package/src/orchestration/recall-rerank-coordinator.ts +123 -4
- package/src/orchestration/recall-result-formatter.ts +6 -2
- package/src/orchestration/recall-search-pipeline.ts +14 -1
- package/src/preferences/drift-config.ts +118 -0
- package/src/preferences/drift-recall.ts +133 -0
- package/src/preferences/drift-types.ts +87 -0
- package/src/preferences/index.ts +12 -0
- package/src/preferences/preference-drift.test.ts +690 -0
- package/src/preferences/preference-drift.ts +724 -0
- package/src/procedural/skill-io.test.ts +190 -0
- package/src/procedural/skill-io.ts +220 -0
- package/src/procedural/skill-projection.test.ts +214 -0
- package/src/procedural/skill-projection.ts +356 -0
- package/src/review/review-deck-access.test.ts +325 -0
- package/src/review/review-deck-http-base.ts +133 -0
- package/src/review/review-deck-mutation.test.ts +417 -0
- package/src/review/review-deck-mutation.ts +677 -0
- package/src/review/review-deck-snapshot.ts +196 -0
- package/src/review/review-deck-surface.ts +117 -0
- package/src/review/review-deck.test.ts +347 -0
- package/src/review/review-deck.ts +311 -0
- package/src/storage.ts +4 -6
- package/src/types.ts +9 -6
- package/dist/chunk-35NN3ZFA.js.map +0 -1
- package/dist/chunk-4KYOLI6M.js.map +0 -1
- package/dist/chunk-BWLRUMOG.js.map +0 -1
- package/dist/chunk-J275IYMG.js.map +0 -1
- package/dist/chunk-LGO6A7GJ.js.map +0 -1
- package/dist/chunk-QOS5LBZN.js.map +0 -1
- package/dist/chunk-RDFF2NAU.js.map +0 -1
- package/dist/chunk-SB7D4BT7.js.map +0 -1
- package/dist/chunk-SBDCZ2AX.js.map +0 -1
- package/dist/chunk-TF77C7SV.js.map +0 -1
- package/dist/chunk-TZNQUNKL.js.map +0 -1
- package/dist/chunk-V3MXUL3D.js.map +0 -1
- package/dist/chunk-XCTXS7VY.js +0 -402
- package/dist/chunk-XCTXS7VY.js.map +0 -1
- package/dist/chunk-YLDXWCZP.js.map +0 -1
- /package/dist/{auto-sync-H64ZCNIR.js.map → auto-sync-XVMLL2RH.js.map} +0 -0
- /package/dist/{chunk-YB42WZCY.js.map → chunk-2IXQSGCU.js.map} +0 -0
- /package/dist/{chunk-PE255F4T.js.map → chunk-2MVTAIL3.js.map} +0 -0
- /package/dist/{chunk-GDFVPUJ5.js.map → chunk-3NH3VP3U.js.map} +0 -0
- /package/dist/{chunk-GNCCUR6K.js.map → chunk-AXGFOFNY.js.map} +0 -0
- /package/dist/{chunk-5YQ63T4B.js.map → chunk-BK5G2MHO.js.map} +0 -0
- /package/dist/{chunk-LZH5FR2W.js.map → chunk-CQBIN77D.js.map} +0 -0
- /package/dist/{chunk-NKKD4ZAU.js.map → chunk-DORFF5N5.js.map} +0 -0
- /package/dist/{chunk-SQI57UCJ.js.map → chunk-DUEDOYZD.js.map} +0 -0
- /package/dist/{chunk-5NM4OH4P.js.map → chunk-DUZARALI.js.map} +0 -0
- /package/dist/{chunk-2E36UXVW.js.map → chunk-ECLJ4LA2.js.map} +0 -0
- /package/dist/{chunk-IID2CMUU.js.map → chunk-GHD2LLG4.js.map} +0 -0
- /package/dist/{chunk-ZQHI2BZB.js.map → chunk-HF5BUTI6.js.map} +0 -0
- /package/dist/{chunk-3DVBKTSO.js.map → chunk-HSZMX3ZJ.js.map} +0 -0
- /package/dist/{chunk-S2DPMJXH.js.map → chunk-I23ZHYSL.js.map} +0 -0
- /package/dist/{chunk-ODZG724P.js.map → chunk-I2HTKOOP.js.map} +0 -0
- /package/dist/{chunk-NTWN6HB2.js.map → chunk-IURMJBGP.js.map} +0 -0
- /package/dist/{chunk-OCZIYAE5.js.map → chunk-IWKGOCL6.js.map} +0 -0
- /package/dist/{chunk-UXO5IMBK.js.map → chunk-J3ATBB7M.js.map} +0 -0
- /package/dist/{chunk-Y7DK7HHJ.js.map → chunk-JPAKBNTM.js.map} +0 -0
- /package/dist/{chunk-SPUJNJJC.js.map → chunk-KFIOPZ25.js.map} +0 -0
- /package/dist/{chunk-JYYBA7TF.js.map → chunk-KOO37E4Y.js.map} +0 -0
- /package/dist/{chunk-OTHZWLUD.js.map → chunk-KPB2QHJZ.js.map} +0 -0
- /package/dist/{chunk-CT56C22C.js.map → chunk-LYBIHM4N.js.map} +0 -0
- /package/dist/{chunk-3I7ASZN3.js.map → chunk-MFOSZUIT.js.map} +0 -0
- /package/dist/{chunk-DZUJ46DO.js.map → chunk-NLEH4RTB.js.map} +0 -0
- /package/dist/{chunk-ZO6AXLEH.js.map → chunk-OCCHUEZB.js.map} +0 -0
- /package/dist/{chunk-EILZG5VC.js.map → chunk-OU34IQXV.js.map} +0 -0
- /package/dist/{chunk-2XDWWPTN.js.map → chunk-OZDFB22S.js.map} +0 -0
- /package/dist/{chunk-UIN2BWDD.js.map → chunk-PTZQYCJ4.js.map} +0 -0
- /package/dist/{chunk-Z7PBQPJ4.js.map → chunk-QJW4MZSW.js.map} +0 -0
- /package/dist/{chunk-R7JAM2XF.js.map → chunk-R2MZCYA6.js.map} +0 -0
- /package/dist/{chunk-LBWQ65SX.js.map → chunk-T42L3S6A.js.map} +0 -0
- /package/dist/{chunk-2YI72KM3.js.map → chunk-UCKG36HO.js.map} +0 -0
- /package/dist/{chunk-SIHSCCUQ.js.map → chunk-UFR6HX7A.js.map} +0 -0
- /package/dist/{chunk-CDUUDBWJ.js.map → chunk-VFYRYFLL.js.map} +0 -0
- /package/dist/{chunk-XRYZRW54.js.map → chunk-WDXD6ITO.js.map} +0 -0
- /package/dist/{chunk-MN4KB2A4.js.map → chunk-YDKF4MSJ.js.map} +0 -0
- /package/dist/{chunk-7YQ4IYS2.js.map → chunk-YOXVZJFM.js.map} +0 -0
- /package/dist/{chunk-ZWXJBFZU.js.map → chunk-YZJM6CLF.js.map} +0 -0
- /package/dist/{chunk-LUNJ3WFO.js.map → chunk-ZK4BFABK.js.map} +0 -0
- /package/dist/{contradiction-review-QKJH5DKD.js.map → contradiction-review-WRVLRWHS.js.map} +0 -0
- /package/dist/{contradiction-scan-VOTQRRSX.js.map → contradiction-scan-AKUWPX5N.js.map} +0 -0
- /package/dist/{resolution-MFY72STR.js.map → resolution-4552BNRV.js.map} +0 -0
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Skill bundle export / import I/O (issue #2369).
|
|
3
|
+
*
|
|
4
|
+
* Covers the acceptance matrix:
|
|
5
|
+
* - export → import into a FRESH memory dir reproduces the procedure steps
|
|
6
|
+
* with import provenance;
|
|
7
|
+
* - imported procedures land as `pending_review`, never active;
|
|
8
|
+
* - a bundle carrying `scripts/` imports the SKILL.md text only and flags
|
|
9
|
+
* `hasUnimportedResources`;
|
|
10
|
+
* - symlinked bundle dirs / SKILL.md files and escaping entries are skipped
|
|
11
|
+
* with a reason instead of being walked.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import test from "node:test";
|
|
15
|
+
import assert from "node:assert/strict";
|
|
16
|
+
import os from "node:os";
|
|
17
|
+
import path from "node:path";
|
|
18
|
+
import { mkdir, mkdtemp, rm, symlink, writeFile } from "node:fs/promises";
|
|
19
|
+
|
|
20
|
+
import { StorageManager } from "../storage.js";
|
|
21
|
+
import { stripAttributesSuffix } from "../structured-attributes.js";
|
|
22
|
+
import { buildProcedurePersistBody, parseProcedureStepsFromBody } from "./procedure-types.js";
|
|
23
|
+
import { exportSkillBundles, persistImportedSkills, readSkillBundlesFromDir } from "./skill-io.js";
|
|
24
|
+
import { projectProceduresToSkills, SKILL_FILE_NAME } from "./skill-projection.js";
|
|
25
|
+
import type { MemoryFile } from "../types.js";
|
|
26
|
+
|
|
27
|
+
const STEPS = [
|
|
28
|
+
{ order: 1, intent: "Read the failing output." },
|
|
29
|
+
{ order: 2, intent: "Fix the smallest cause.", expectedOutcome: "The suite passes." },
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
function makeProcedure(id: string, title: string): MemoryFile {
|
|
33
|
+
return {
|
|
34
|
+
path: `procedures/2026-08-18/${id}.md`,
|
|
35
|
+
content: buildProcedurePersistBody(title, STEPS),
|
|
36
|
+
frontmatter: {
|
|
37
|
+
id,
|
|
38
|
+
category: "procedure",
|
|
39
|
+
created: "2026-08-18T00:00:00.000Z",
|
|
40
|
+
updated: "2026-08-18T00:00:00.000Z",
|
|
41
|
+
source: "procedure-miner",
|
|
42
|
+
confidence: 0.8,
|
|
43
|
+
confidenceTier: "implied",
|
|
44
|
+
tags: [],
|
|
45
|
+
} as MemoryFile["frontmatter"],
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async function tempDir(prefix: string): Promise<string> {
|
|
50
|
+
return mkdtemp(path.join(os.tmpdir(), prefix));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
test("export then import into a fresh memory dir reproduces the steps as pending_review", async () => {
|
|
54
|
+
const outDir = await tempDir("remnic-skill-export-");
|
|
55
|
+
const memoryDir = await tempDir("remnic-skill-memory-");
|
|
56
|
+
try {
|
|
57
|
+
const source = makeProcedure("procedure-1", "Fix a failing test");
|
|
58
|
+
const written = await exportSkillBundles({
|
|
59
|
+
bundles: projectProceduresToSkills([source]),
|
|
60
|
+
outDir,
|
|
61
|
+
});
|
|
62
|
+
assert.deepEqual(written.slugs, ["fix-a-failing-test"]);
|
|
63
|
+
|
|
64
|
+
const read = await readSkillBundlesFromDir(outDir);
|
|
65
|
+
assert.equal(read.bundles.length, 1);
|
|
66
|
+
assert.equal(read.bundles[0].hasUnimportedResources, false);
|
|
67
|
+
|
|
68
|
+
const storage = new StorageManager(memoryDir);
|
|
69
|
+
const result = await persistImportedSkills({ storage, bundles: read.bundles });
|
|
70
|
+
assert.equal(result.imported.length, 1);
|
|
71
|
+
assert.equal(result.imported[0].steps, 2);
|
|
72
|
+
assert.deepEqual(result.rejected, []);
|
|
73
|
+
|
|
74
|
+
const memories = await storage.readAllMemories();
|
|
75
|
+
const imported = memories.filter((m) => m.frontmatter.category === "procedure");
|
|
76
|
+
assert.equal(imported.length, 1);
|
|
77
|
+
assert.equal(imported[0].frontmatter.status, "pending_review");
|
|
78
|
+
assert.equal(imported[0].frontmatter.source, "skill-import");
|
|
79
|
+
assert.equal(imported[0].frontmatter.structuredAttributes?.skill_slug, "fix-a-failing-test");
|
|
80
|
+
assert.equal(imported[0].frontmatter.structuredAttributes?.skill_origin_memory_id, "procedure-1");
|
|
81
|
+
assert.equal(imported[0].frontmatter.structuredAttributes?.hasUnimportedResources, undefined);
|
|
82
|
+
|
|
83
|
+
// storage.writeMemory appends a "[Attributes: ...]" footer to any memory
|
|
84
|
+
// carrying structuredAttributes; it is machine provenance, not a step.
|
|
85
|
+
assert.deepEqual(
|
|
86
|
+
parseProcedureStepsFromBody(stripAttributesSuffix(imported[0].content)),
|
|
87
|
+
parseProcedureStepsFromBody(source.content),
|
|
88
|
+
);
|
|
89
|
+
} finally {
|
|
90
|
+
await rm(outDir, { recursive: true, force: true });
|
|
91
|
+
await rm(memoryDir, { recursive: true, force: true });
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
test("a bundle with scripts/ imports text only and flags hasUnimportedResources", async () => {
|
|
96
|
+
const dir = await tempDir("remnic-skill-scripts-");
|
|
97
|
+
const memoryDir = await tempDir("remnic-skill-memory-");
|
|
98
|
+
try {
|
|
99
|
+
const bundleDir = path.join(dir, "risky-skill");
|
|
100
|
+
await mkdir(path.join(bundleDir, "scripts"), { recursive: true });
|
|
101
|
+
await writeFile(
|
|
102
|
+
path.join(bundleDir, SKILL_FILE_NAME),
|
|
103
|
+
["---", "name: risky-skill", "description: Do the risky thing", "---", "", "Do the risky thing", "", "## Step 1", "", "Run the checklist.", ""].join("\n"),
|
|
104
|
+
);
|
|
105
|
+
await writeFile(path.join(bundleDir, "scripts", "install.sh"), "#!/bin/sh\necho nope\n");
|
|
106
|
+
|
|
107
|
+
const read = await readSkillBundlesFromDir(dir);
|
|
108
|
+
assert.equal(read.bundles.length, 1);
|
|
109
|
+
assert.equal(read.bundles[0].hasUnimportedResources, true);
|
|
110
|
+
assert.equal(read.bundles[0].steps?.length, 1);
|
|
111
|
+
assert.ok(!read.bundles[0].body.includes("echo nope"));
|
|
112
|
+
|
|
113
|
+
const storage = new StorageManager(memoryDir);
|
|
114
|
+
await persistImportedSkills({ storage, bundles: read.bundles });
|
|
115
|
+
const imported = (await storage.readAllMemories()).filter((m) => m.frontmatter.category === "procedure");
|
|
116
|
+
assert.equal(imported.length, 1);
|
|
117
|
+
assert.equal(imported[0].frontmatter.structuredAttributes?.hasUnimportedResources, "true");
|
|
118
|
+
assert.equal(imported[0].frontmatter.status, "pending_review");
|
|
119
|
+
} finally {
|
|
120
|
+
await rm(dir, { recursive: true, force: true });
|
|
121
|
+
await rm(memoryDir, { recursive: true, force: true });
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
test("a step-less body is stored verbatim", async () => {
|
|
126
|
+
const dir = await tempDir("remnic-skill-prose-");
|
|
127
|
+
const memoryDir = await tempDir("remnic-skill-memory-");
|
|
128
|
+
try {
|
|
129
|
+
const bundleDir = path.join(dir, "prose-skill");
|
|
130
|
+
await mkdir(bundleDir, { recursive: true });
|
|
131
|
+
await writeFile(path.join(bundleDir, SKILL_FILE_NAME), "---\nname: prose-skill\n---\n\nJust prose guidance.\n");
|
|
132
|
+
|
|
133
|
+
const read = await readSkillBundlesFromDir(dir);
|
|
134
|
+
assert.equal(read.bundles[0].steps, null);
|
|
135
|
+
|
|
136
|
+
const storage = new StorageManager(memoryDir);
|
|
137
|
+
await persistImportedSkills({ storage, bundles: read.bundles });
|
|
138
|
+
const imported = (await storage.readAllMemories()).filter((m) => m.frontmatter.category === "procedure");
|
|
139
|
+
assert.match(imported[0].content, /Just prose guidance\./);
|
|
140
|
+
} finally {
|
|
141
|
+
await rm(dir, { recursive: true, force: true });
|
|
142
|
+
await rm(memoryDir, { recursive: true, force: true });
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
test("symlinked bundles and escaping entries are skipped with a reason", async () => {
|
|
147
|
+
const dir = await tempDir("remnic-skill-symlink-");
|
|
148
|
+
const outside = await tempDir("remnic-skill-outside-");
|
|
149
|
+
try {
|
|
150
|
+
const real = path.join(dir, "real-skill");
|
|
151
|
+
await mkdir(real, { recursive: true });
|
|
152
|
+
await writeFile(path.join(real, SKILL_FILE_NAME), "---\nname: real-skill\n---\n\nReal body\n");
|
|
153
|
+
|
|
154
|
+
// A symlinked bundle directory pointing outside the import root.
|
|
155
|
+
await mkdir(path.join(outside, "evil"), { recursive: true });
|
|
156
|
+
await writeFile(path.join(outside, "evil", SKILL_FILE_NAME), "---\nname: evil\n---\n\nEscaped body\n");
|
|
157
|
+
await symlink(path.join(outside, "evil"), path.join(dir, "linked-skill"), "dir");
|
|
158
|
+
|
|
159
|
+
// A bundle whose SKILL.md is itself a symlink.
|
|
160
|
+
const linkedFile = path.join(dir, "linked-file-skill");
|
|
161
|
+
await mkdir(linkedFile, { recursive: true });
|
|
162
|
+
await symlink(path.join(outside, "evil", SKILL_FILE_NAME), path.join(linkedFile, SKILL_FILE_NAME), "file");
|
|
163
|
+
|
|
164
|
+
// A directory with no SKILL.md at all.
|
|
165
|
+
await mkdir(path.join(dir, "empty-skill"), { recursive: true });
|
|
166
|
+
|
|
167
|
+
const read = await readSkillBundlesFromDir(dir);
|
|
168
|
+
assert.deepEqual(read.bundles.map((b) => b.slug), ["real-skill"]);
|
|
169
|
+
const reasons = new Map(read.skipped.map((s) => [s.entry, s.reason]));
|
|
170
|
+
assert.equal(reasons.get("linked-skill"), "symlink");
|
|
171
|
+
assert.match(reasons.get("linked-file-skill") ?? "", /not a regular file/);
|
|
172
|
+
assert.match(reasons.get("empty-skill") ?? "", /no SKILL\.md/);
|
|
173
|
+
} finally {
|
|
174
|
+
await rm(dir, { recursive: true, force: true });
|
|
175
|
+
await rm(outside, { recursive: true, force: true });
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
test("a symlinked import root is refused outright", async () => {
|
|
180
|
+
const dir = await tempDir("remnic-skill-root-");
|
|
181
|
+
const linkParent = await tempDir("remnic-skill-link-");
|
|
182
|
+
try {
|
|
183
|
+
const link = path.join(linkParent, "linked-root");
|
|
184
|
+
await symlink(dir, link, "dir");
|
|
185
|
+
await assert.rejects(() => readSkillBundlesFromDir(link), /symlinked directory/);
|
|
186
|
+
} finally {
|
|
187
|
+
await rm(dir, { recursive: true, force: true });
|
|
188
|
+
await rm(linkParent, { recursive: true, force: true });
|
|
189
|
+
}
|
|
190
|
+
});
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* skill-io.ts — filesystem export / import for procedural skill bundles
|
|
3
|
+
* (issue #2369).
|
|
4
|
+
*
|
|
5
|
+
* Export writes `<dir>/<slug>/SKILL.md` and never deletes anything it did not
|
|
6
|
+
* just render. Import walks every `<dir>/<slug>/SKILL.md`, rejects symlinks and any entry that
|
|
7
|
+
* resolves outside the requested root, and persists `pending_review` procedure
|
|
8
|
+
* memories through the sealed-envelope write path.
|
|
9
|
+
*
|
|
10
|
+
* Import is INERT: bundle resources (`scripts/`, binaries, extra files) are
|
|
11
|
+
* never read, copied, or executed. Their presence is recorded as
|
|
12
|
+
* `hasUnimportedResources` so a reviewer can act on it.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { lstat, mkdir, readdir, readFile, realpath, writeFile } from "node:fs/promises";
|
|
16
|
+
import path from "node:path";
|
|
17
|
+
|
|
18
|
+
import { log } from "../logger.js";
|
|
19
|
+
import type { StorageManager } from "../storage.js";
|
|
20
|
+
import { pathIsInside } from "../utils/path-containment.js";
|
|
21
|
+
import { composeMemoryEnvelope } from "../write-envelope.js";
|
|
22
|
+
import { buildProcedurePersistBody } from "./procedure-types.js";
|
|
23
|
+
import {
|
|
24
|
+
parseSkillBundle,
|
|
25
|
+
renderSkillBundle,
|
|
26
|
+
sanitizeSkillSlug,
|
|
27
|
+
SKILL_FILE_NAME,
|
|
28
|
+
SKILL_IMPORT_SOURCE,
|
|
29
|
+
type ParsedSkillBundle,
|
|
30
|
+
type SkillBundle,
|
|
31
|
+
} from "./skill-projection.js";
|
|
32
|
+
|
|
33
|
+
export interface ExportSkillsResult {
|
|
34
|
+
outDir: string;
|
|
35
|
+
/** Slugs written this run, in projection order. */
|
|
36
|
+
slugs: string[];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Write bundles as `<outDir>/<slug>/SKILL.md`. Creates directories as needed. */
|
|
40
|
+
export async function exportSkillBundles(options: {
|
|
41
|
+
bundles: readonly SkillBundle[];
|
|
42
|
+
outDir: string;
|
|
43
|
+
}): Promise<ExportSkillsResult> {
|
|
44
|
+
const outDir = path.resolve(options.outDir);
|
|
45
|
+
await mkdir(outDir, { recursive: true });
|
|
46
|
+
const slugs: string[] = [];
|
|
47
|
+
for (const bundle of options.bundles) {
|
|
48
|
+
const dir = path.join(outDir, bundle.slug);
|
|
49
|
+
if (!pathIsInside(outDir, dir)) {
|
|
50
|
+
throw new Error(`skill export: refusing to write outside ${outDir} (slug ${bundle.slug})`);
|
|
51
|
+
}
|
|
52
|
+
try {
|
|
53
|
+
const existing = await lstat(dir);
|
|
54
|
+
if (existing.isSymbolicLink()) {
|
|
55
|
+
throw new Error(`skill export: refusing to write through symlink ${dir}`);
|
|
56
|
+
}
|
|
57
|
+
} catch (err) {
|
|
58
|
+
if ((err as NodeJS.ErrnoException).code !== "ENOENT") throw err;
|
|
59
|
+
}
|
|
60
|
+
await mkdir(dir, { recursive: true });
|
|
61
|
+
const skillPath = path.join(dir, SKILL_FILE_NAME);
|
|
62
|
+
try {
|
|
63
|
+
const existingSkill = await lstat(skillPath);
|
|
64
|
+
if (existingSkill.isSymbolicLink()) {
|
|
65
|
+
throw new Error(`skill export: refusing to write through symlink ${skillPath}`);
|
|
66
|
+
}
|
|
67
|
+
} catch (err) {
|
|
68
|
+
if ((err as NodeJS.ErrnoException).code !== "ENOENT") throw err;
|
|
69
|
+
}
|
|
70
|
+
await writeFile(skillPath, renderSkillBundle(bundle), "utf-8");
|
|
71
|
+
slugs.push(bundle.slug);
|
|
72
|
+
}
|
|
73
|
+
return { outDir, slugs };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** One bundle read off disk, plus the resource flag the reviewer needs. */
|
|
77
|
+
export interface ReadSkillBundle extends ParsedSkillBundle {
|
|
78
|
+
/** True when the bundle directory holds anything besides SKILL.md. */
|
|
79
|
+
hasUnimportedResources: boolean;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export interface ReadSkillBundlesResult {
|
|
83
|
+
bundles: ReadSkillBundle[];
|
|
84
|
+
/** Entries that were not importable, with the reason (never silent). */
|
|
85
|
+
skipped: Array<{ entry: string; reason: string }>;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Read every `<dir>/*/SKILL.md` bundle. Symlinked roots, symlinked bundle
|
|
90
|
+
* directories, symlinked SKILL.md files, and anything resolving outside `dir`
|
|
91
|
+
* are skipped with a reason (Review Prevention Checklist §3, §10).
|
|
92
|
+
*/
|
|
93
|
+
export async function readSkillBundlesFromDir(dir: string): Promise<ReadSkillBundlesResult> {
|
|
94
|
+
const root = path.resolve(dir);
|
|
95
|
+
const rootStat = await lstat(root);
|
|
96
|
+
if (rootStat.isSymbolicLink()) {
|
|
97
|
+
throw new Error(`skill import: refusing to walk symlinked directory ${root}`);
|
|
98
|
+
}
|
|
99
|
+
if (!rootStat.isDirectory()) {
|
|
100
|
+
throw new Error(`skill import: ${root} is not a directory`);
|
|
101
|
+
}
|
|
102
|
+
const rootReal = await realpath(root);
|
|
103
|
+
|
|
104
|
+
const bundles: ReadSkillBundle[] = [];
|
|
105
|
+
const skipped: Array<{ entry: string; reason: string }> = [];
|
|
106
|
+
const entries = await readdir(root, { withFileTypes: true });
|
|
107
|
+
// Stable order — readdir order is not guaranteed (§6).
|
|
108
|
+
entries.sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));
|
|
109
|
+
|
|
110
|
+
for (const entry of entries) {
|
|
111
|
+
try {
|
|
112
|
+
if (entry.isSymbolicLink()) {
|
|
113
|
+
skipped.push({ entry: entry.name, reason: "symlink" });
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
if (!entry.isDirectory()) continue;
|
|
117
|
+
const bundleDir = path.join(root, entry.name);
|
|
118
|
+
const bundleReal = await realpath(bundleDir);
|
|
119
|
+
if (!pathIsInside(rootReal, bundleReal)) {
|
|
120
|
+
skipped.push({ entry: entry.name, reason: "resolves outside the import root" });
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
const skillPath = path.join(bundleDir, SKILL_FILE_NAME);
|
|
124
|
+
let skillStat;
|
|
125
|
+
try {
|
|
126
|
+
skillStat = await lstat(skillPath);
|
|
127
|
+
} catch {
|
|
128
|
+
skipped.push({ entry: entry.name, reason: `no ${SKILL_FILE_NAME}` });
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
if (skillStat.isSymbolicLink() || !skillStat.isFile()) {
|
|
132
|
+
skipped.push({ entry: entry.name, reason: `${SKILL_FILE_NAME} is not a regular file` });
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
const parsed = parseSkillBundle(await readFile(skillPath, "utf-8"), sanitizeSkillSlug(entry.name));
|
|
136
|
+
if (!parsed) {
|
|
137
|
+
skipped.push({ entry: entry.name, reason: "empty body" });
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
const siblings = await readdir(bundleDir);
|
|
141
|
+
bundles.push({
|
|
142
|
+
...parsed,
|
|
143
|
+
hasUnimportedResources: siblings.some((name) => name !== SKILL_FILE_NAME),
|
|
144
|
+
});
|
|
145
|
+
} catch (err) {
|
|
146
|
+
skipped.push({
|
|
147
|
+
entry: entry.name,
|
|
148
|
+
reason: `unreadable: ${err instanceof Error ? err.message : String(err)}`,
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return { bundles, skipped };
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export interface ImportedSkillRecord {
|
|
157
|
+
slug: string;
|
|
158
|
+
memoryId: string;
|
|
159
|
+
steps: number;
|
|
160
|
+
hasUnimportedResources: boolean;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export interface ImportSkillsResult {
|
|
164
|
+
imported: ImportedSkillRecord[];
|
|
165
|
+
/** Bundles that were read but not persisted (e.g. tombstone-blocked). */
|
|
166
|
+
rejected: Array<{ slug: string; reason: string }>;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Persist read bundles as `pending_review` procedure memories.
|
|
171
|
+
*
|
|
172
|
+
* `pending_review` is unconditional: imported procedures never auto-promote,
|
|
173
|
+
* regardless of `procedural.autoPromoteOccurrences`. Recall already excludes
|
|
174
|
+
* non-active procedures, so review is the promotion checkpoint.
|
|
175
|
+
*/
|
|
176
|
+
export async function persistImportedSkills(options: {
|
|
177
|
+
storage: StorageManager;
|
|
178
|
+
bundles: readonly ReadSkillBundle[];
|
|
179
|
+
}): Promise<ImportSkillsResult> {
|
|
180
|
+
const imported: ImportedSkillRecord[] = [];
|
|
181
|
+
const rejected: Array<{ slug: string; reason: string }> = [];
|
|
182
|
+
|
|
183
|
+
for (const bundle of options.bundles) {
|
|
184
|
+
const title = (bundle.description ?? bundle.name ?? bundle.slug).trim() || bundle.slug;
|
|
185
|
+
const content = bundle.steps ? buildProcedurePersistBody(title, bundle.steps) : bundle.body;
|
|
186
|
+
const envelope = composeMemoryEnvelope(
|
|
187
|
+
{
|
|
188
|
+
content,
|
|
189
|
+
category: "procedure",
|
|
190
|
+
tags: ["skill-import", "procedure"],
|
|
191
|
+
structuredAttributes: {
|
|
192
|
+
skill_slug: bundle.slug,
|
|
193
|
+
// Basename only — never an operator's absolute path.
|
|
194
|
+
skill_source: `${bundle.slug}/${SKILL_FILE_NAME}`,
|
|
195
|
+
...(bundle.provenance.memoryId ? { skill_origin_memory_id: bundle.provenance.memoryId } : {}),
|
|
196
|
+
...(bundle.hasUnimportedResources ? { hasUnimportedResources: "true" } : {}),
|
|
197
|
+
},
|
|
198
|
+
},
|
|
199
|
+
{ source: SKILL_IMPORT_SOURCE },
|
|
200
|
+
// Machine-generated / externally authored input — salvage, warn on drops.
|
|
201
|
+
{ salvage: true },
|
|
202
|
+
);
|
|
203
|
+
if (envelope.salvageNotes.length > 0) {
|
|
204
|
+
log.warn(`skill-import write salvaged invalid fields: ${envelope.salvageNotes.join("; ")}`);
|
|
205
|
+
}
|
|
206
|
+
const written = await options.storage.writeSealedMemory(envelope, { status: "pending_review" });
|
|
207
|
+
if (written.tombstoneBlocked) {
|
|
208
|
+
rejected.push({ slug: bundle.slug, reason: "blocked by a tombstone; left for review" });
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
211
|
+
imported.push({
|
|
212
|
+
slug: bundle.slug,
|
|
213
|
+
memoryId: written.id,
|
|
214
|
+
steps: bundle.steps?.length ?? 0,
|
|
215
|
+
hasUnimportedResources: bundle.hasUnimportedResources,
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
return { imported, rejected };
|
|
220
|
+
}
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Regression tests for the skill-bundle projection contract (issue #2369).
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import test from "node:test";
|
|
6
|
+
import assert from "node:assert/strict";
|
|
7
|
+
|
|
8
|
+
import type { MemoryFile, MemoryStatus } from "../types.js";
|
|
9
|
+
import { buildProcedurePersistBody, parseProcedureStepsFromBody } from "./procedure-types.js";
|
|
10
|
+
import {
|
|
11
|
+
parseSkillBundle,
|
|
12
|
+
parseSkillProjectionConfig,
|
|
13
|
+
projectProceduresToSkills,
|
|
14
|
+
renderSkillBundle,
|
|
15
|
+
sanitizeSkillSlug,
|
|
16
|
+
} from "./skill-projection.js";
|
|
17
|
+
|
|
18
|
+
const STEPS = [
|
|
19
|
+
{ order: 1, intent: "Read the failing test output." },
|
|
20
|
+
{ order: 2, intent: "Fix the smallest cause.", expectedOutcome: "The suite passes." },
|
|
21
|
+
];
|
|
22
|
+
|
|
23
|
+
function makeProcedure(options: {
|
|
24
|
+
id: string;
|
|
25
|
+
title: string;
|
|
26
|
+
status?: MemoryStatus;
|
|
27
|
+
updated?: string;
|
|
28
|
+
category?: string;
|
|
29
|
+
}): MemoryFile {
|
|
30
|
+
return {
|
|
31
|
+
path: `procedures/2026-08-18/${options.id}.md`,
|
|
32
|
+
content: buildProcedurePersistBody(options.title, STEPS),
|
|
33
|
+
frontmatter: {
|
|
34
|
+
id: options.id,
|
|
35
|
+
category: (options.category ?? "procedure") as MemoryFile["frontmatter"]["category"],
|
|
36
|
+
created: "2026-08-18T00:00:00.000Z",
|
|
37
|
+
updated: options.updated ?? "2026-08-18T00:00:00.000Z",
|
|
38
|
+
source: "procedure-miner",
|
|
39
|
+
confidence: 0.8,
|
|
40
|
+
confidenceTier: "explicit",
|
|
41
|
+
tags: [],
|
|
42
|
+
...(options.status ? { status: options.status } : {}),
|
|
43
|
+
} as MemoryFile["frontmatter"],
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
test("the stored machine attributes footer never reaches the skill body", () => {
|
|
48
|
+
const memory = makeProcedure({ id: "procedure-1", title: "Fix a failing test" });
|
|
49
|
+
memory.content = `${memory.content}\n[Attributes: procedure_cluster: abc; trajectory_count: 4]`;
|
|
50
|
+
const bundles = projectProceduresToSkills([memory]);
|
|
51
|
+
assert.equal(bundles.length, 1);
|
|
52
|
+
assert.ok(!bundles[0].body.includes("[Attributes:"));
|
|
53
|
+
assert.equal(bundles[0].body.trimEnd().endsWith("The suite passes."), true);
|
|
54
|
+
assert.equal(parseProcedureStepsFromBody(bundles[0].body)?.length, 2);
|
|
55
|
+
assert.equal(parseProcedureStepsFromBody(bundles[0].body)?.[1].intent, "Fix the smallest cause.");
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test("projects active procedures and round-trips the steps through parseProcedureStepsFromBody", () => {
|
|
59
|
+
const bundles = projectProceduresToSkills([
|
|
60
|
+
makeProcedure({ id: "procedure-1", title: "Fix a failing test" }),
|
|
61
|
+
]);
|
|
62
|
+
assert.equal(bundles.length, 1);
|
|
63
|
+
assert.equal(bundles[0].slug, "fix-a-failing-test");
|
|
64
|
+
assert.equal(bundles[0].frontmatter.name, "fix-a-failing-test");
|
|
65
|
+
assert.equal(bundles[0].frontmatter.description, "Fix a failing test");
|
|
66
|
+
assert.equal(bundles[0].provenance.memoryId, "procedure-1");
|
|
67
|
+
|
|
68
|
+
const rendered = renderSkillBundle(bundles[0]);
|
|
69
|
+
const parsed = parseSkillBundle(rendered, bundles[0].slug);
|
|
70
|
+
assert.ok(parsed);
|
|
71
|
+
assert.deepEqual(
|
|
72
|
+
parsed.steps?.map((s) => ({ order: s.order, intent: s.intent })),
|
|
73
|
+
parseProcedureStepsFromBody(bundles[0].body)?.map((s) => ({ order: s.order, intent: s.intent })),
|
|
74
|
+
);
|
|
75
|
+
assert.equal(parsed.steps?.length, 2);
|
|
76
|
+
assert.equal(parsed.steps?.[1].expectedOutcome, "The suite passes.");
|
|
77
|
+
assert.equal(parsed.provenance.memoryId, "procedure-1");
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
test("never projects a non-active status (every status enumerated)", () => {
|
|
81
|
+
const statuses: MemoryStatus[] = [
|
|
82
|
+
"pending_review",
|
|
83
|
+
"rejected",
|
|
84
|
+
"quarantined",
|
|
85
|
+
"superseded",
|
|
86
|
+
"archived",
|
|
87
|
+
"forgotten",
|
|
88
|
+
];
|
|
89
|
+
for (const status of statuses) {
|
|
90
|
+
const bundles = projectProceduresToSkills([
|
|
91
|
+
makeProcedure({ id: `procedure-${status}`, title: `Do ${status} work`, status }),
|
|
92
|
+
]);
|
|
93
|
+
assert.deepEqual(bundles, [], `${status} must not project`);
|
|
94
|
+
}
|
|
95
|
+
// Sanity: the same memory projects once its status is active.
|
|
96
|
+
assert.equal(
|
|
97
|
+
projectProceduresToSkills([makeProcedure({ id: "procedure-ok", title: "Do active work", status: "active" })])
|
|
98
|
+
.length,
|
|
99
|
+
1,
|
|
100
|
+
);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
test("non-procedure categories are excluded", () => {
|
|
104
|
+
const bundles = projectProceduresToSkills([
|
|
105
|
+
makeProcedure({ id: "fact-1", title: "Not a procedure", category: "fact" }),
|
|
106
|
+
]);
|
|
107
|
+
assert.deepEqual(bundles, []);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
test("duplicate titles yield distinct stable slugs", () => {
|
|
111
|
+
const memories = [
|
|
112
|
+
makeProcedure({ id: "procedure-a", title: "Deploy the service", updated: "2026-08-18T03:00:00.000Z" }),
|
|
113
|
+
makeProcedure({ id: "procedure-b", title: "Deploy the service!", updated: "2026-08-18T02:00:00.000Z" }),
|
|
114
|
+
makeProcedure({ id: "procedure-c", title: "Deploy the service?", updated: "2026-08-18T01:00:00.000Z" }),
|
|
115
|
+
];
|
|
116
|
+
const first = projectProceduresToSkills(memories).map((b) => b.slug);
|
|
117
|
+
assert.deepEqual(first, ["deploy-the-service", "deploy-the-service-2", "deploy-the-service-3"]);
|
|
118
|
+
// Stable across runs, and stable when the input order changes (sort is total).
|
|
119
|
+
assert.deepEqual(projectProceduresToSkills([...memories].reverse()).map((b) => b.slug), first);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
test("ties on updated fall back to id order, so ordering is deterministic", () => {
|
|
123
|
+
const memories = [
|
|
124
|
+
makeProcedure({ id: "procedure-z", title: "Zed task", updated: "2026-08-18T00:00:00.000Z" }),
|
|
125
|
+
makeProcedure({ id: "procedure-a", title: "Alpha task", updated: "2026-08-18T00:00:00.000Z" }),
|
|
126
|
+
];
|
|
127
|
+
assert.deepEqual(projectProceduresToSkills(memories).map((b) => b.slug), ["alpha-task", "zed-task"]);
|
|
128
|
+
assert.deepEqual(projectProceduresToSkills([...memories].reverse()).map((b) => b.slug), [
|
|
129
|
+
"alpha-task",
|
|
130
|
+
"zed-task",
|
|
131
|
+
]);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
test("reserved slugs are prefixed rather than shadowed", () => {
|
|
135
|
+
const bundles = projectProceduresToSkills(
|
|
136
|
+
[makeProcedure({ id: "procedure-1", title: "remnic memory workflow" })],
|
|
137
|
+
{ reservedSlugs: ["remnic-memory-workflow"] },
|
|
138
|
+
);
|
|
139
|
+
assert.equal(bundles[0].slug, "user-remnic-memory-workflow");
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
test("maxSkills caps the projection and 0 disables it", () => {
|
|
143
|
+
const memories = [
|
|
144
|
+
makeProcedure({ id: "procedure-a", title: "Task A", updated: "2026-08-18T03:00:00.000Z" }),
|
|
145
|
+
makeProcedure({ id: "procedure-b", title: "Task B", updated: "2026-08-18T02:00:00.000Z" }),
|
|
146
|
+
];
|
|
147
|
+
assert.equal(projectProceduresToSkills(memories, { maxSkills: 1 }).length, 1);
|
|
148
|
+
// Newest first.
|
|
149
|
+
assert.equal(projectProceduresToSkills(memories, { maxSkills: 1 })[0].slug, "task-a");
|
|
150
|
+
assert.deepEqual(projectProceduresToSkills(memories, { maxSkills: 0 }), []);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
test("rendered frontmatter survives titles containing YAML metacharacters", () => {
|
|
154
|
+
const bundles = projectProceduresToSkills([
|
|
155
|
+
makeProcedure({ id: "procedure-1", title: "When you work on goals like: ship #2369" }),
|
|
156
|
+
]);
|
|
157
|
+
const parsed = parseSkillBundle(renderSkillBundle(bundles[0]), bundles[0].slug);
|
|
158
|
+
assert.equal(parsed?.description, "When you work on goals like: ship #2369");
|
|
159
|
+
assert.equal(bundles[0].slug, "when-you-work-on-goals-like-ship-2369");
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
test("parseSkillBundle tolerates a step-less body and a missing frontmatter block", () => {
|
|
163
|
+
const parsed = parseSkillBundle("Just prose, no steps.\n", "prose");
|
|
164
|
+
assert.equal(parsed?.steps, null);
|
|
165
|
+
assert.equal(parsed?.body, "Just prose, no steps.");
|
|
166
|
+
assert.equal(parsed?.provenance.memoryId, undefined);
|
|
167
|
+
assert.equal(parseSkillBundle("---\nname: empty\n---\n\n", "empty"), null);
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
test("parseSkillBundle ignores nested frontmatter sequences", () => {
|
|
171
|
+
const text = [
|
|
172
|
+
"---",
|
|
173
|
+
"name: deploy",
|
|
174
|
+
"description: Deploy the thing",
|
|
175
|
+
"allowed-tools:",
|
|
176
|
+
" - remnic_recall",
|
|
177
|
+
"---",
|
|
178
|
+
"",
|
|
179
|
+
"Deploy the thing",
|
|
180
|
+
"",
|
|
181
|
+
"## Step 1",
|
|
182
|
+
"",
|
|
183
|
+
"Push the button.",
|
|
184
|
+
"",
|
|
185
|
+
].join("\n");
|
|
186
|
+
const parsed = parseSkillBundle(text, "deploy");
|
|
187
|
+
assert.equal(parsed?.description, "Deploy the thing");
|
|
188
|
+
assert.equal(parsed?.steps?.length, 1);
|
|
189
|
+
assert.equal(parsed?.steps?.[0].intent, "Push the button.");
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
test("sanitizeSkillSlug produces valid slugs for hostile titles", () => {
|
|
193
|
+
assert.equal(sanitizeSkillSlug(" /// "), "procedure");
|
|
194
|
+
assert.equal(sanitizeSkillSlug("../../etc/passwd"), "etc-passwd");
|
|
195
|
+
assert.equal(sanitizeSkillSlug("Ünïcodé Title"), "n-cod-title");
|
|
196
|
+
assert.equal(sanitizeSkillSlug("A".repeat(120)).length, 64);
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
test("parseSkillProjectionConfig defaults off, coerces strings, and rejects garbage", () => {
|
|
200
|
+
assert.deepEqual(parseSkillProjectionConfig(undefined), { enabled: false, maxSkills: 20 });
|
|
201
|
+
assert.deepEqual(parseSkillProjectionConfig({ enabled: "true", maxSkills: "5" }), {
|
|
202
|
+
enabled: true,
|
|
203
|
+
maxSkills: 5,
|
|
204
|
+
});
|
|
205
|
+
// String "false"/"0" must read as falsy / as the documented disable value.
|
|
206
|
+
assert.deepEqual(parseSkillProjectionConfig({ enabled: "false", maxSkills: "0" }), {
|
|
207
|
+
enabled: false,
|
|
208
|
+
maxSkills: 0,
|
|
209
|
+
});
|
|
210
|
+
assert.throws(() => parseSkillProjectionConfig({ enabled: "maybe" }), /skillProjection\.enabled/);
|
|
211
|
+
assert.throws(() => parseSkillProjectionConfig({ maxSkills: 2.5 }), /integer/);
|
|
212
|
+
assert.throws(() => parseSkillProjectionConfig({ maxSkills: -1 }), /between 0/);
|
|
213
|
+
assert.throws(() => parseSkillProjectionConfig([]), /must be an object/);
|
|
214
|
+
});
|