@remnic/core 9.63.6 → 9.63.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/access-admin-ops-surface.d.ts +1 -1
- package/dist/access-admin-ops-surface.js +27 -25
- package/dist/access-authorization-probe.d.ts +1 -1
- package/dist/access-authorization-probe.js +28 -26
- package/dist/access-boundary.d.ts +1 -1
- package/dist/access-boundary.js +27 -25
- package/dist/access-cli.js +83 -81
- package/dist/access-cli.js.map +1 -1
- package/dist/access-extraction-force-flush.d.ts +1 -1
- package/dist/access-extraction-force-flush.js +27 -25
- package/dist/access-http-lcm-compaction.d.ts +1 -1
- package/dist/access-http-lifecycle-flush.d.ts +1 -1
- package/dist/access-http-offline-stream.d.ts +1 -1
- package/dist/access-http-query.js +29 -27
- package/dist/access-http.d.ts +2 -2
- package/dist/access-http.js +43 -41
- package/dist/access-identity-continuity-surface.js +27 -25
- package/dist/access-lcm-surface.d.ts +1 -1
- package/dist/access-lcm-surface.js +27 -25
- package/dist/access-mcp.d.ts +1 -1
- package/dist/access-mcp.js +34 -32
- package/dist/access-namespace-preflight.js +27 -25
- package/dist/access-observe-write-surface.d.ts +1 -1
- package/dist/access-observe-write-surface.js +27 -25
- package/dist/access-offline-manifest.js +6 -5
- package/dist/access-operations-batch.js +29 -27
- package/dist/access-operations.d.ts +4 -4
- package/dist/access-operations.js +33 -31
- package/dist/access-recall-concurrency.d.ts +1 -1
- package/dist/access-recall-concurrency.js +27 -25
- package/dist/access-recall-response.d.ts +1 -1
- package/dist/access-recall-response.js +27 -25
- package/dist/access-recall-surface.d.ts +1 -1
- package/dist/access-recall-surface.js +27 -25
- package/dist/access-schema.d.ts +88 -88
- package/dist/access-schema.js +6 -5
- package/dist/access-service-helpers.d.ts +1 -1
- package/dist/access-service.d.ts +1 -1
- package/dist/access-service.js +27 -25
- package/dist/access-surface-catalog.d.ts +1 -1
- package/dist/access-surface-catalog.js +29 -27
- package/dist/access-surface-catalog.js.map +1 -1
- package/dist/active-recall.js +5 -3
- package/dist/active-recall.js.map +1 -1
- package/dist/adapters/index.js +4 -4
- package/dist/adapters/registry.js +2 -2
- package/dist/{auto-sync-BKRJDMKX.js → auto-sync-3SHMZVQS.js} +5 -3
- package/dist/{auto-sync-BKRJDMKX.js.map → auto-sync-3SHMZVQS.js.map} +1 -1
- package/dist/briefing.js +10 -10
- package/dist/calibration.d.ts +2 -1
- package/dist/calibration.js +7 -0
- package/dist/calibration.js.map +1 -1
- package/dist/causal-consolidation.js +11 -11
- package/dist/census-validation.d.ts +19 -0
- package/dist/census-validation.js +14 -0
- package/dist/census-validation.js.map +1 -0
- package/dist/{chunk-LEPOP46W.js → chunk-227UFSEQ.js} +4 -4
- package/dist/{chunk-Z4N7SVVF.js → chunk-2L7DKNVE.js} +12 -7
- package/dist/chunk-2L7DKNVE.js.map +1 -0
- package/dist/{chunk-3Z5TGRFA.js → chunk-3NZAVWU5.js} +9 -9
- package/dist/{chunk-I5FLIB2J.js → chunk-3S3PNR5H.js} +2 -2
- package/dist/{chunk-R6UMNP5C.js → chunk-3T6HIIKA.js} +2 -2
- package/dist/{chunk-77QZW2D6.js → chunk-3TDAA7VA.js} +2 -2
- package/dist/{chunk-TWTUGCTY.js → chunk-3YTTAGPB.js} +6 -1
- package/dist/chunk-3YTTAGPB.js.map +1 -0
- package/dist/{chunk-DXDAQZ2K.js → chunk-42TIIO2M.js} +2 -2
- package/dist/{chunk-Y7COCXY2.js → chunk-4F67JJPG.js} +2 -2
- package/dist/{chunk-PB4WVYGC.js → chunk-5S5DUJFJ.js} +2 -2
- package/dist/{chunk-KYTRF64R.js → chunk-5SCFDQEJ.js} +2 -2
- package/dist/{chunk-GG4DN63W.js → chunk-62Y5AGCM.js} +2 -2
- package/dist/{chunk-KHCUVYCY.js → chunk-6UKSOXAO.js} +3 -3
- package/dist/{chunk-F6JYBVL6.js → chunk-7ZAC2C2U.js} +24 -24
- package/dist/{chunk-GF7RQWXC.js → chunk-AJRJDXYR.js} +2 -2
- package/dist/{chunk-YEQCMA5L.js → chunk-BOSIHHOH.js} +2 -2
- package/dist/{chunk-7LF2OHIJ.js → chunk-BRHKW7GY.js} +2 -2
- package/dist/{chunk-LX4TNSAE.js → chunk-CBY54QYW.js} +4 -4
- package/dist/chunk-FEHIOYML.js +168 -0
- package/dist/chunk-FEHIOYML.js.map +1 -0
- package/dist/{chunk-6YMEU6OQ.js → chunk-G72IPMR7.js} +2 -2
- package/dist/{chunk-7AF243WZ.js → chunk-GBVRVYF4.js} +1 -1
- package/dist/{chunk-GTD4Q62X.js → chunk-H4GQ2Y24.js} +3 -3
- package/dist/{chunk-QI5RDDEL.js → chunk-HXZNTZ3Y.js} +6 -6
- package/dist/{chunk-CIAI24TQ.js → chunk-IBZQQOM5.js} +3 -3
- package/dist/{chunk-RYST72N5.js → chunk-IINVXO3U.js} +4 -154
- package/dist/chunk-IINVXO3U.js.map +1 -0
- package/dist/{chunk-R7TZ2HCS.js → chunk-JG22O53B.js} +58 -3
- package/dist/chunk-JG22O53B.js.map +1 -0
- package/dist/{chunk-VPHSMJ2E.js → chunk-JPWSYHLB.js} +2 -2
- package/dist/{chunk-X7LEDA74.js → chunk-KFB4YNXM.js} +2 -2
- package/dist/{chunk-IPLYGWQF.js → chunk-KQAFEZQX.js} +5 -5
- package/dist/chunk-LQWOFU6L.js +17 -0
- package/dist/chunk-LQWOFU6L.js.map +1 -0
- package/dist/{chunk-ZUPKNWRZ.js → chunk-MIUL6AUZ.js} +3 -3
- package/dist/{chunk-EJY3HJMT.js → chunk-MYI3RKI6.js} +2 -2
- package/dist/{chunk-NA56NSKL.js → chunk-OOJCXJBN.js} +2 -2
- package/dist/{chunk-7KQB4EBS.js → chunk-OPPBTPUU.js} +4 -4
- package/dist/{chunk-ERPBRXVZ.js → chunk-PZKWPXRF.js} +2 -2
- package/dist/{chunk-FHKXVTM5.js → chunk-Q5XKKDFQ.js} +16 -16
- package/dist/{chunk-QRWMS6SI.js → chunk-Q6OL23WM.js} +2 -2
- package/dist/{chunk-OW7NVMGQ.js → chunk-QIZOU3FN.js} +2 -2
- package/dist/{chunk-V3KIRICY.js → chunk-QQYHYW2N.js} +3 -3
- package/dist/{chunk-YFCEBB5P.js → chunk-QTXNS3HB.js} +2 -2
- package/dist/{chunk-2QSZNTDO.js → chunk-RKNJBZ55.js} +4 -4
- package/dist/{chunk-E4KMY4KR.js → chunk-SNEUAZ6B.js} +2 -2
- package/dist/{chunk-TSTI6A3A.js → chunk-SYJW273H.js} +11 -11
- package/dist/{chunk-EFKZT6AY.js → chunk-T42L3S6A.js} +6 -11
- package/dist/chunk-T42L3S6A.js.map +1 -0
- package/dist/{chunk-YXMHUBN2.js → chunk-TOP3FSDW.js} +25 -102
- package/dist/chunk-TOP3FSDW.js.map +1 -0
- package/dist/{chunk-PBRCYYO7.js → chunk-ULZFZL7K.js} +2 -2
- package/dist/{chunk-UGHCZS6K.js → chunk-VFZQXYN5.js} +2 -2
- package/dist/{chunk-IVXVLDNT.js → chunk-WKJTMXME.js} +37 -54
- package/dist/chunk-WKJTMXME.js.map +1 -0
- package/dist/{chunk-NKB43PGI.js → chunk-WZDTSBGQ.js} +2 -2
- package/dist/{chunk-2UM6WHME.js → chunk-XE2QCCT2.js} +2 -2
- package/dist/{chunk-FPEZ74F7.js → chunk-XNJQZLAP.js} +3 -3
- package/dist/{chunk-SYVGVK4S.js → chunk-XTMYB2HY.js} +2 -2
- package/dist/{chunk-OETHJBAL.js → chunk-XV6PUXJ5.js} +71 -71
- package/dist/{chunk-UCLMKNHA.js → chunk-YIMV2KYM.js} +2 -2
- package/dist/{chunk-DYZR3Z6D.js → chunk-YMQKPSB2.js} +4 -4
- package/dist/{chunk-AYVCKGA6.js → chunk-YTXNDEUS.js} +2 -2
- package/dist/{chunk-L3CHRBHE.js → chunk-ZRUEP4IK.js} +17 -11
- package/dist/chunk-ZRUEP4IK.js.map +1 -0
- package/dist/{cli-BFBDYV0D.d.ts → cli-BTNj5DzK.d.ts} +1 -1
- package/dist/cli.d.ts +2 -2
- package/dist/cli.js +59 -57
- package/dist/compounding/engine.js +10 -10
- package/dist/config.js +5 -3
- package/dist/connectors/codex-materialize-runner.js +10 -10
- package/dist/connectors/index.js +10 -10
- package/dist/consolidation-provenance-check.js +2 -2
- package/dist/convergence-refresh.js +12 -12
- package/dist/conversation-index/backend.js +2 -2
- package/dist/corpus-watermark.js +12 -12
- package/dist/entity-retrieval.js +10 -10
- package/dist/external-wiki-access.d.ts +1 -1
- package/dist/external-wiki-access.js +28 -26
- package/dist/external-wiki-mcp-tools.d.ts +1 -1
- package/dist/extraction-judge.d.ts +2 -1
- package/dist/extraction-judge.js +6 -1
- package/dist/extraction-normalization.js +2 -2
- package/dist/extraction-prompt.d.ts +20 -1
- package/dist/extraction-prompt.js +11 -1
- package/dist/extraction.js +13 -13
- package/dist/{first-start-migration-4NETATFF.js → first-start-migration-Q2VB2E2D.js} +4 -4
- package/dist/harmonic-retrieval.js +1 -1
- package/dist/index.d.ts +680 -680
- package/dist/index.js +92 -90
- package/dist/maintenance/memory-governance.js +10 -10
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +10 -10
- package/dist/maintenance/rebuild-memory-projection.js +11 -11
- package/dist/mcp-memory-inspector-app.d.ts +1 -1
- package/dist/namespaces/migrate.js +11 -11
- package/dist/namespaces/search.js +4 -4
- package/dist/namespaces/storage.js +10 -10
- package/dist/offline-sync.js +3 -2
- package/dist/operator-doctor-corpus.js +13 -13
- package/dist/operator-toolkit.js +24 -22
- package/dist/orchestration/maintenance.js +16 -16
- package/dist/orchestrator.js +85 -83
- package/dist/{public-http-8PcrDIYi.d.ts → public-http-BmPktTe3.d.ts} +1 -1
- package/dist/reconcile/cursor.js +7 -3
- package/dist/reconcile/cursor.js.map +1 -1
- package/dist/reconcile/manifest.js +3 -7
- package/dist/reconcile/plan.d.ts +15 -1
- package/dist/reconcile/plan.js +6 -10
- package/dist/resume-bundles.js +6 -4
- package/dist/schemas.d.ts +108 -108
- package/dist/search/factory.js +3 -3
- package/dist/search/index.js +3 -3
- package/dist/semantic-consolidation.js +16 -16
- package/dist/semantic-rule-promotion.js +10 -10
- package/dist/semantic-rule-verifier.js +10 -10
- package/dist/shared-context/manager.d.ts +8 -8
- package/dist/source-agent-qualifier.js +10 -10
- package/dist/storage.js +9 -9
- package/dist/support-passport/index.d.ts +4 -4
- package/dist/support-passport/index.js +30 -28
- package/dist/transfer/autodetect.js +1 -1
- package/dist/transfer/backup.js +1 -1
- package/dist/transfer/capsule-export.js +2 -2
- package/dist/transfer/types.d.ts +66 -66
- package/dist/verified-recall.js +10 -10
- package/package.json +2 -2
- package/src/calibration.ts +3 -1
- package/src/census-validation.ts +25 -0
- package/src/coding/architecture-surfaces.test.ts +9 -10
- package/src/coding/architecture-surfaces.ts +2 -35
- package/src/coding/codegraph-runtime-loader.test.ts +228 -0
- package/src/coding/codegraph-runtime.ts +30 -50
- package/src/coding/codegraph-surfaces.ts +2 -2
- package/src/coding/coding-knowledge-config.ts +35 -1
- package/src/coding/decision-surfaces.test.ts +9 -10
- package/src/coding/decision-surfaces.ts +2 -40
- package/src/coding/session-delta-surfaces.test.ts +9 -10
- package/src/coding/session-delta-surfaces.ts +2 -33
- package/src/extraction-judge.ts +3 -1
- package/src/extraction-output-language.test.ts +116 -0
- package/src/extraction-prompt.ts +70 -1
- package/src/extraction.ts +23 -95
- package/src/offline-sync.ts +4 -4
- package/src/reconcile/census-parity.test.ts +152 -0
- package/src/reconcile/cursor.ts +5 -5
- package/src/reconcile/manifest.ts +4 -11
- package/src/reconcile/plan.ts +18 -15
- package/dist/chunk-EFKZT6AY.js.map +0 -1
- package/dist/chunk-IVXVLDNT.js.map +0 -1
- package/dist/chunk-L3CHRBHE.js.map +0 -1
- package/dist/chunk-R7TZ2HCS.js.map +0 -1
- package/dist/chunk-RYST72N5.js.map +0 -1
- package/dist/chunk-TWTUGCTY.js.map +0 -1
- package/dist/chunk-YXMHUBN2.js.map +0 -1
- package/dist/chunk-Z4N7SVVF.js.map +0 -1
- package/dist/{access-service-cKQrte9Z.d.ts → access-service-CocW74uA.d.ts} +64 -64
- /package/dist/{chunk-LEPOP46W.js.map → chunk-227UFSEQ.js.map} +0 -0
- /package/dist/{chunk-3Z5TGRFA.js.map → chunk-3NZAVWU5.js.map} +0 -0
- /package/dist/{chunk-I5FLIB2J.js.map → chunk-3S3PNR5H.js.map} +0 -0
- /package/dist/{chunk-R6UMNP5C.js.map → chunk-3T6HIIKA.js.map} +0 -0
- /package/dist/{chunk-77QZW2D6.js.map → chunk-3TDAA7VA.js.map} +0 -0
- /package/dist/{chunk-DXDAQZ2K.js.map → chunk-42TIIO2M.js.map} +0 -0
- /package/dist/{chunk-Y7COCXY2.js.map → chunk-4F67JJPG.js.map} +0 -0
- /package/dist/{chunk-PB4WVYGC.js.map → chunk-5S5DUJFJ.js.map} +0 -0
- /package/dist/{chunk-KYTRF64R.js.map → chunk-5SCFDQEJ.js.map} +0 -0
- /package/dist/{chunk-GG4DN63W.js.map → chunk-62Y5AGCM.js.map} +0 -0
- /package/dist/{chunk-KHCUVYCY.js.map → chunk-6UKSOXAO.js.map} +0 -0
- /package/dist/{chunk-F6JYBVL6.js.map → chunk-7ZAC2C2U.js.map} +0 -0
- /package/dist/{chunk-GF7RQWXC.js.map → chunk-AJRJDXYR.js.map} +0 -0
- /package/dist/{chunk-YEQCMA5L.js.map → chunk-BOSIHHOH.js.map} +0 -0
- /package/dist/{chunk-7LF2OHIJ.js.map → chunk-BRHKW7GY.js.map} +0 -0
- /package/dist/{chunk-LX4TNSAE.js.map → chunk-CBY54QYW.js.map} +0 -0
- /package/dist/{chunk-6YMEU6OQ.js.map → chunk-G72IPMR7.js.map} +0 -0
- /package/dist/{chunk-7AF243WZ.js.map → chunk-GBVRVYF4.js.map} +0 -0
- /package/dist/{chunk-GTD4Q62X.js.map → chunk-H4GQ2Y24.js.map} +0 -0
- /package/dist/{chunk-QI5RDDEL.js.map → chunk-HXZNTZ3Y.js.map} +0 -0
- /package/dist/{chunk-CIAI24TQ.js.map → chunk-IBZQQOM5.js.map} +0 -0
- /package/dist/{chunk-VPHSMJ2E.js.map → chunk-JPWSYHLB.js.map} +0 -0
- /package/dist/{chunk-X7LEDA74.js.map → chunk-KFB4YNXM.js.map} +0 -0
- /package/dist/{chunk-IPLYGWQF.js.map → chunk-KQAFEZQX.js.map} +0 -0
- /package/dist/{chunk-ZUPKNWRZ.js.map → chunk-MIUL6AUZ.js.map} +0 -0
- /package/dist/{chunk-EJY3HJMT.js.map → chunk-MYI3RKI6.js.map} +0 -0
- /package/dist/{chunk-NA56NSKL.js.map → chunk-OOJCXJBN.js.map} +0 -0
- /package/dist/{chunk-7KQB4EBS.js.map → chunk-OPPBTPUU.js.map} +0 -0
- /package/dist/{chunk-ERPBRXVZ.js.map → chunk-PZKWPXRF.js.map} +0 -0
- /package/dist/{chunk-FHKXVTM5.js.map → chunk-Q5XKKDFQ.js.map} +0 -0
- /package/dist/{chunk-QRWMS6SI.js.map → chunk-Q6OL23WM.js.map} +0 -0
- /package/dist/{chunk-OW7NVMGQ.js.map → chunk-QIZOU3FN.js.map} +0 -0
- /package/dist/{chunk-V3KIRICY.js.map → chunk-QQYHYW2N.js.map} +0 -0
- /package/dist/{chunk-YFCEBB5P.js.map → chunk-QTXNS3HB.js.map} +0 -0
- /package/dist/{chunk-2QSZNTDO.js.map → chunk-RKNJBZ55.js.map} +0 -0
- /package/dist/{chunk-E4KMY4KR.js.map → chunk-SNEUAZ6B.js.map} +0 -0
- /package/dist/{chunk-TSTI6A3A.js.map → chunk-SYJW273H.js.map} +0 -0
- /package/dist/{chunk-PBRCYYO7.js.map → chunk-ULZFZL7K.js.map} +0 -0
- /package/dist/{chunk-UGHCZS6K.js.map → chunk-VFZQXYN5.js.map} +0 -0
- /package/dist/{chunk-NKB43PGI.js.map → chunk-WZDTSBGQ.js.map} +0 -0
- /package/dist/{chunk-2UM6WHME.js.map → chunk-XE2QCCT2.js.map} +0 -0
- /package/dist/{chunk-FPEZ74F7.js.map → chunk-XNJQZLAP.js.map} +0 -0
- /package/dist/{chunk-SYVGVK4S.js.map → chunk-XTMYB2HY.js.map} +0 -0
- /package/dist/{chunk-OETHJBAL.js.map → chunk-XV6PUXJ5.js.map} +0 -0
- /package/dist/{chunk-UCLMKNHA.js.map → chunk-YIMV2KYM.js.map} +0 -0
- /package/dist/{chunk-DYZR3Z6D.js.map → chunk-YMQKPSB2.js.map} +0 -0
- /package/dist/{chunk-AYVCKGA6.js.map → chunk-YTXNDEUS.js.map} +0 -0
- /package/dist/{first-start-migration-4NETATFF.js.map → first-start-migration-Q2VB2E2D.js.map} +0 -0
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
// Regression tests for issue #2478: the runtime bridge must resolve
|
|
2
|
+
// @remnic/coding-graph through the shared loader
|
|
3
|
+
// (optional-coding-graph.ts) -- ONE import, ONE cache, ONE contract
|
|
4
|
+
// check -- and the miss path must surface the canonical install hint,
|
|
5
|
+
// never a raw ERR_MODULE_NOT_FOUND.
|
|
6
|
+
//
|
|
7
|
+
// Both tests drive the REAL dynamic-import machinery in a child process
|
|
8
|
+
// with a registered resolve hook (the established technique from
|
|
9
|
+
// packages/remnic-cli/src/openclaw-managed-upgrade.test.ts):
|
|
10
|
+
//
|
|
11
|
+
// 1. pass-through hook that COUNTS resolutions of the specifier --
|
|
12
|
+
// the shared loader caches success, so a probe + a store open must
|
|
13
|
+
// resolve "@remnic/coding-graph" exactly once. A parallel loader
|
|
14
|
+
// in codegraph-runtime.ts resolves it a second time and fails.
|
|
15
|
+
// 2. throwing hook that simulates the absent package -- the store
|
|
16
|
+
// open must reject with the tagged CodegraphRuntimeError whose
|
|
17
|
+
// message is the canonical install hint (buildCodingGraphInstallHint).
|
|
18
|
+
|
|
19
|
+
import assert from "node:assert/strict";
|
|
20
|
+
import { spawnSync } from "node:child_process";
|
|
21
|
+
import fs from "node:fs";
|
|
22
|
+
import os from "node:os";
|
|
23
|
+
import path from "node:path";
|
|
24
|
+
import test from "node:test";
|
|
25
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
26
|
+
|
|
27
|
+
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../../../..");
|
|
28
|
+
const tsxCli = path.join(repoRoot, "node_modules", "tsx", "dist", "cli.mjs");
|
|
29
|
+
const optionalLoaderSource = path.join(
|
|
30
|
+
repoRoot,
|
|
31
|
+
"packages",
|
|
32
|
+
"remnic-core",
|
|
33
|
+
"src",
|
|
34
|
+
"coding",
|
|
35
|
+
"optional-coding-graph.ts",
|
|
36
|
+
);
|
|
37
|
+
const runtimeSource = path.join(
|
|
38
|
+
repoRoot,
|
|
39
|
+
"packages",
|
|
40
|
+
"remnic-core",
|
|
41
|
+
"src",
|
|
42
|
+
"coding",
|
|
43
|
+
"codegraph-runtime.ts",
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
interface DriverReport {
|
|
47
|
+
opened?: boolean;
|
|
48
|
+
threw?: boolean;
|
|
49
|
+
name?: string;
|
|
50
|
+
code?: unknown;
|
|
51
|
+
message?: string;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function makeScratch(prefix: string): string {
|
|
55
|
+
return fs.mkdtempSync(path.join(os.tmpdir(), prefix));
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function writeCountingResolveHook(root: string): { hookPath: string; logPath: string } {
|
|
59
|
+
const logPath = path.join(root, "resolves.log");
|
|
60
|
+
const hookPath = path.join(root, "count-resolves.mjs");
|
|
61
|
+
fs.writeFileSync(
|
|
62
|
+
hookPath,
|
|
63
|
+
`import { appendFileSync } from "node:fs";
|
|
64
|
+
import { pathToFileURL } from "node:url";
|
|
65
|
+
export async function resolve(specifier, context, nextResolve) {
|
|
66
|
+
if (specifier === "@remnic/coding-graph") {
|
|
67
|
+
if (process.env.CODEGRAPH_RESOLVE_LOG) {
|
|
68
|
+
appendFileSync(process.env.CODEGRAPH_RESOLVE_LOG, specifier + "\\n");
|
|
69
|
+
}
|
|
70
|
+
try {
|
|
71
|
+
return await nextResolve(specifier, context);
|
|
72
|
+
} catch (err) {
|
|
73
|
+
if (err && err.code === "ERR_MODULE_NOT_FOUND" && process.env.CODEGRAPH_SOURCE_FALLBACK) {
|
|
74
|
+
// Worktree/base install where the optional peer is not linked:
|
|
75
|
+
// map the specifier to the workspace source so the import
|
|
76
|
+
// SUCCEEDS and the shared loader's success cache is observable.
|
|
77
|
+
return { url: pathToFileURL(process.env.CODEGRAPH_SOURCE_FALLBACK).href, shortCircuit: true };
|
|
78
|
+
}
|
|
79
|
+
throw err;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return nextResolve(specifier, context);
|
|
83
|
+
}
|
|
84
|
+
`,
|
|
85
|
+
"utf8",
|
|
86
|
+
);
|
|
87
|
+
return { hookPath, logPath };
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function writeMissingPackageResolveHook(root: string): string {
|
|
91
|
+
const hookPath = path.join(root, "missing-coding-graph.mjs");
|
|
92
|
+
fs.writeFileSync(
|
|
93
|
+
hookPath,
|
|
94
|
+
`export async function resolve(specifier, context, nextResolve) {
|
|
95
|
+
if (specifier === "@remnic/coding-graph") {
|
|
96
|
+
const error = new Error("Cannot find package '@remnic/coding-graph' imported from test");
|
|
97
|
+
error.code = "ERR_MODULE_NOT_FOUND";
|
|
98
|
+
throw error;
|
|
99
|
+
}
|
|
100
|
+
return nextResolve(specifier, context);
|
|
101
|
+
}
|
|
102
|
+
`,
|
|
103
|
+
"utf8",
|
|
104
|
+
);
|
|
105
|
+
return hookPath;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function writeStoreOpenDriver(root: string): string {
|
|
109
|
+
const driverPath = path.join(root, "store-open.mts");
|
|
110
|
+
fs.writeFileSync(
|
|
111
|
+
driverPath,
|
|
112
|
+
`import { mkdirSync } from "node:fs";
|
|
113
|
+
import path from "node:path";
|
|
114
|
+
import { pathToFileURL } from "node:url";
|
|
115
|
+
|
|
116
|
+
const [, , runtimePath, optionalLoaderPath, scratch] = process.argv;
|
|
117
|
+
// Prime the shared loader exactly like the real surfaces do (the
|
|
118
|
+
// runtime-availability probe runs before any store open).
|
|
119
|
+
const optional = await import(pathToFileURL(optionalLoaderPath).href);
|
|
120
|
+
await optional.isCodingGraphInstalled();
|
|
121
|
+
const runtime = await import(pathToFileURL(runtimePath).href);
|
|
122
|
+
const memoryDir = path.join(scratch, "memory");
|
|
123
|
+
mkdirSync(path.join(memoryDir, "codegraph", "loader-principal"), { recursive: true });
|
|
124
|
+
const config = {
|
|
125
|
+
codingKnowledge: { enabled: true, codegraphTools: true, codegraphDbDir: "" },
|
|
126
|
+
};
|
|
127
|
+
const request = {
|
|
128
|
+
config,
|
|
129
|
+
memoryDir,
|
|
130
|
+
principal: "loader-principal",
|
|
131
|
+
projectId: "loader-test",
|
|
132
|
+
};
|
|
133
|
+
try {
|
|
134
|
+
const store = await runtime.getCodegraphStore(request);
|
|
135
|
+
const schemaVersion = store.schemaVersion();
|
|
136
|
+
await store.close();
|
|
137
|
+
console.log(JSON.stringify({ opened: true, schemaVersion }));
|
|
138
|
+
} catch (err) {
|
|
139
|
+
const e = err instanceof Error ? err : new Error(String(err));
|
|
140
|
+
console.log(
|
|
141
|
+
JSON.stringify({ threw: true, name: e.name, code: e.code, message: e.message }),
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
`,
|
|
145
|
+
"utf8",
|
|
146
|
+
);
|
|
147
|
+
return driverPath;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
function runChild(
|
|
152
|
+
scriptPath: string,
|
|
153
|
+
args: string[],
|
|
154
|
+
env: NodeJS.ProcessEnv,
|
|
155
|
+
): { status: number | null; stdout: string; stderr: string } {
|
|
156
|
+
const result = spawnSync(process.execPath, [tsxCli, scriptPath, ...args], {
|
|
157
|
+
encoding: "utf8",
|
|
158
|
+
cwd: repoRoot,
|
|
159
|
+
env: { ...process.env, ...env },
|
|
160
|
+
timeout: 60_000,
|
|
161
|
+
});
|
|
162
|
+
return { status: result.status, stdout: result.stdout ?? "", stderr: result.stderr ?? "" };
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function lastJsonLine(stdout: string): DriverReport {
|
|
166
|
+
const lines = stdout.trim().split(/\n/).filter(Boolean);
|
|
167
|
+
let line: string | undefined;
|
|
168
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
169
|
+
if (lines[i].startsWith("{")) {
|
|
170
|
+
line = lines[i];
|
|
171
|
+
break;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
assert.ok(line, `expected a JSON line in child stdout, got: ${stdout}`);
|
|
175
|
+
return JSON.parse(line) as DriverReport;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
test("codegraph-runtime resolves @remnic/coding-graph exactly once (shared loader, no parallel import)", async () => {
|
|
179
|
+
// The counting hook maps the specifier to the workspace source when the
|
|
180
|
+
// optional peer is not linked, so the import SUCCEEDS in every install
|
|
181
|
+
// state. The shared loader then caches the success: a probe + a store
|
|
182
|
+
// open must resolve the specifier exactly once. A parallel loader in
|
|
183
|
+
// codegraph-runtime.ts imports the package again and fails this count.
|
|
184
|
+
const root = makeScratch("remnic-2478-single-load-");
|
|
185
|
+
try {
|
|
186
|
+
const { hookPath, logPath } = writeCountingResolveHook(root);
|
|
187
|
+
const driver = writeStoreOpenDriver(root);
|
|
188
|
+
const result = runChild(driver, [runtimeSource, optionalLoaderSource, root], {
|
|
189
|
+
CODEGRAPH_RESOLVE_LOG: logPath,
|
|
190
|
+
CODEGRAPH_SOURCE_FALLBACK: path.join(repoRoot, "packages", "coding-graph", "src", "index.ts"),
|
|
191
|
+
NODE_OPTIONS: `--experimental-loader=${pathToFileURL(hookPath).href} --conditions=remnic-source`,
|
|
192
|
+
});
|
|
193
|
+
assert.equal(result.status, 0, result.stderr);
|
|
194
|
+
const report = lastJsonLine(result.stdout);
|
|
195
|
+
assert.equal(report.opened, true, `store must open against the loaded module: ${JSON.stringify(report)}`);
|
|
196
|
+
const resolves = fs.existsSync(logPath)
|
|
197
|
+
? fs.readFileSync(logPath, "utf8").trim().split(/\n/).filter(Boolean)
|
|
198
|
+
: [];
|
|
199
|
+
assert.equal(
|
|
200
|
+
resolves.length,
|
|
201
|
+
1,
|
|
202
|
+
`the shared loader must resolve "@remnic/coding-graph" exactly once; saw ${resolves.length} resolutions -- a second resolve means codegraph-runtime still imports the package in parallel`,
|
|
203
|
+
);
|
|
204
|
+
} finally {
|
|
205
|
+
fs.rmSync(root, { recursive: true, force: true });
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
test("codegraph-runtime miss path throws the canonical install hint, not raw MODULE_NOT_FOUND", async () => {
|
|
210
|
+
// The throwing resolve hook simulates the absent package even on a dev
|
|
211
|
+
// workspace where it is installed, so this runs everywhere.
|
|
212
|
+
const root = makeScratch("remnic-2478-miss-");
|
|
213
|
+
try {
|
|
214
|
+
const hookPath = writeMissingPackageResolveHook(root);
|
|
215
|
+
const driver = writeStoreOpenDriver(root);
|
|
216
|
+
const result = runChild(driver, [runtimeSource, optionalLoaderSource, root], {
|
|
217
|
+
NODE_OPTIONS: `--experimental-loader=${pathToFileURL(hookPath).href} --conditions=remnic-source`,
|
|
218
|
+
});
|
|
219
|
+
assert.equal(result.status, 0, result.stderr);
|
|
220
|
+
const report = lastJsonLine(result.stdout);
|
|
221
|
+
assert.equal(report.threw, true, `store open must fail when the package is absent: ${JSON.stringify(report)}`);
|
|
222
|
+
assert.equal(report.code, "package_missing");
|
|
223
|
+
assert.match(report.message ?? "", /npm install @remnic\/coding-graph/);
|
|
224
|
+
assert.doesNotMatch(report.message ?? "", /ERR_MODULE_NOT_FOUND|Cannot find module/);
|
|
225
|
+
} finally {
|
|
226
|
+
fs.rmSync(root, { recursive: true, force: true });
|
|
227
|
+
}
|
|
228
|
+
});
|
|
@@ -9,9 +9,11 @@
|
|
|
9
9
|
* dependency that hosts opt into. Every code path that needs a GraphStore
|
|
10
10
|
* calls {@link getCodegraphStore} here; this module owns:
|
|
11
11
|
*
|
|
12
|
-
* - the
|
|
13
|
-
*
|
|
14
|
-
*
|
|
12
|
+
* - the store-module boundary — @remnic/coding-graph is imported by the
|
|
13
|
+
* ONE shared loader (optional-coding-graph.ts; single cache and
|
|
14
|
+
* contract check per process). This module narrows that shared module
|
|
15
|
+
* to the runtime's GraphStore view; it never imports the package
|
|
16
|
+
* itself;
|
|
15
17
|
* - per-project GraphStore caching (one open SQLite handle per
|
|
16
18
|
* `(principal, projectId)` pair, lifecycle-tracked so a host process can
|
|
17
19
|
* close them on shutdown);
|
|
@@ -30,20 +32,14 @@ import { createHash } from "node:crypto";
|
|
|
30
32
|
import { expandTildePath } from "../utils/path.js";
|
|
31
33
|
|
|
32
34
|
import type { PluginConfig, CodingKnowledgeConfig, CodingContext } from "../types.js";
|
|
33
|
-
import { isCodingGraphInstalled } from "./optional-coding-graph.js";
|
|
35
|
+
import { buildCodingGraphInstallHint, isCodingGraphInstalled, tryLoadCodingGraphModule } from "./optional-coding-graph.js";
|
|
34
36
|
import { displayErrorDetail } from "../runtime/better-sqlite.js";
|
|
35
37
|
// Type-only reference to the surface context shape (ts-import-type rule).
|
|
36
38
|
// Erased at compile time, so the runtime → surfaces → runtime import cycle is
|
|
37
39
|
// type-only and has no runtime effect.
|
|
38
40
|
import type { CodegraphSurfaceContext } from "./codegraph-surfaces.js";
|
|
39
41
|
|
|
40
|
-
|
|
41
|
-
// is the documented exception to the static-import rule: @remnic/coding-graph
|
|
42
|
-
// is an optional peer dependency that is genuinely absent on base installs,
|
|
43
|
-
// so a static import would either fail the base install or be bundled into
|
|
44
|
-
// @remnic/core's dist. The specifier is composed from string literals to
|
|
45
|
-
// keep this a runtime-only reference.
|
|
46
|
-
const CODEGRAPH_SPECIFIER = "@remnic/" + "coding-graph";
|
|
42
|
+
|
|
47
43
|
|
|
48
44
|
/**
|
|
49
45
|
* Minimal structural shape of the @remnic/coding-graph public surface this
|
|
@@ -270,8 +266,6 @@ interface CachedStore {
|
|
|
270
266
|
}
|
|
271
267
|
|
|
272
268
|
const storeCache = new Map<string, CachedStore>();
|
|
273
|
-
let cachedModule: CodegraphModule | null | undefined;
|
|
274
|
-
let moduleLoadPromise: Promise<CodegraphModule | null> | null = null;
|
|
275
269
|
|
|
276
270
|
function storeCacheKey(principalSafe: string, projectSafe: string, dbPath: string): string {
|
|
277
271
|
// Thread 8 (issue #1554): include the resolved DB path so two configs
|
|
@@ -282,29 +276,24 @@ function storeCacheKey(principalSafe: string, projectSafe: string, dbPath: strin
|
|
|
282
276
|
}
|
|
283
277
|
|
|
284
278
|
/**
|
|
285
|
-
*
|
|
286
|
-
*
|
|
287
|
-
*
|
|
279
|
+
* Narrow the module served by the shared loader (optional-coding-graph.ts)
|
|
280
|
+
* to this runtime's structural view. The shared loader owns the single
|
|
281
|
+
* dynamic import, cache, and miss/incompatible diagnostics; this predicate
|
|
282
|
+
* only checks the GraphStore capability the runtime needs. A missing,
|
|
283
|
+
* broken, or engine-incompatible install collapses to `null` (graceful
|
|
284
|
+
* degradation -- surfaces translate to the install hint).
|
|
288
285
|
*/
|
|
289
|
-
|
|
290
|
-
if (
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
return null;
|
|
301
|
-
} catch {
|
|
302
|
-
cachedModule = null;
|
|
303
|
-
return null;
|
|
304
|
-
}
|
|
305
|
-
})();
|
|
306
|
-
}
|
|
307
|
-
return moduleLoadPromise;
|
|
286
|
+
function isCodegraphModule(value: object): value is CodegraphModule {
|
|
287
|
+
if (!value || typeof value !== "object") return false;
|
|
288
|
+
const candidate = value as Record<string, unknown>;
|
|
289
|
+
const graphStore = candidate.GraphStore as { open?: unknown } | undefined;
|
|
290
|
+
return graphStore !== undefined && typeof graphStore.open === "function";
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
async function resolveCodegraphModule(): Promise<CodegraphModule | null> {
|
|
294
|
+
const mod = await tryLoadCodingGraphModule();
|
|
295
|
+
if (mod === null || !isCodegraphModule(mod)) return null;
|
|
296
|
+
return mod;
|
|
308
297
|
}
|
|
309
298
|
|
|
310
299
|
/**
|
|
@@ -332,12 +321,9 @@ export async function getCodegraphStore(params: {
|
|
|
332
321
|
"codegraph tools are disabled (codingKnowledge.enabled or codingKnowledge.codegraphTools is false)",
|
|
333
322
|
);
|
|
334
323
|
}
|
|
335
|
-
const mod = await
|
|
324
|
+
const mod = await resolveCodegraphModule();
|
|
336
325
|
if (mod === null) {
|
|
337
|
-
throw new CodegraphRuntimeError(
|
|
338
|
-
"package_missing",
|
|
339
|
-
"The @remnic/coding-graph package is not installed; install it to use codegraph tools.",
|
|
340
|
-
);
|
|
326
|
+
throw new CodegraphRuntimeError("package_missing", buildCodingGraphInstallHint());
|
|
341
327
|
}
|
|
342
328
|
// Key the cache on SANITIZED values AND the resolved DB path (thread 8):
|
|
343
329
|
// two different raw identifiers that sanitize to the same path segment
|
|
@@ -533,7 +519,7 @@ export async function runCodegraphReindex(params: {
|
|
|
533
519
|
readonly repoRoot: string;
|
|
534
520
|
readonly mode: string;
|
|
535
521
|
}): Promise<CodegraphDelegateOutcome<{ mode: string; filesIngested: number; head: string | null }>> {
|
|
536
|
-
const mod = await
|
|
522
|
+
const mod = await resolveCodegraphModule();
|
|
537
523
|
if (mod === null) {
|
|
538
524
|
return { ok: false, code: "package_missing", message: "The @remnic/coding-graph package is not installed." };
|
|
539
525
|
}
|
|
@@ -676,7 +662,7 @@ export async function runCodegraphLspResolution(params: {
|
|
|
676
662
|
}
|
|
677
663
|
| { ok: false; code: string; message: string }
|
|
678
664
|
> {
|
|
679
|
-
const mod = await
|
|
665
|
+
const mod = await resolveCodegraphModule();
|
|
680
666
|
if (mod === null) {
|
|
681
667
|
return { ok: false, code: "package_missing", message: "@remnic/coding-graph is not installed." };
|
|
682
668
|
}
|
|
@@ -1112,7 +1098,7 @@ export async function reportCodegraphIndexStatus(params: {
|
|
|
1112
1098
|
readonly store: CodegraphStore;
|
|
1113
1099
|
readonly repoRoot: string;
|
|
1114
1100
|
}): Promise<CodegraphDelegateOutcome<{ status: Record<string, unknown> }>> {
|
|
1115
|
-
const mod = await
|
|
1101
|
+
const mod = await resolveCodegraphModule();
|
|
1116
1102
|
if (mod === null) {
|
|
1117
1103
|
return { ok: false, code: "package_missing", message: "The @remnic/coding-graph package is not installed." };
|
|
1118
1104
|
}
|
|
@@ -1140,7 +1126,7 @@ export async function detectCodegraphChanges(params: {
|
|
|
1140
1126
|
readonly repoRoot: string;
|
|
1141
1127
|
readonly head: string;
|
|
1142
1128
|
}): Promise<CodegraphDelegateOutcome<{ affected: readonly unknown[] }>> {
|
|
1143
|
-
const mod = await
|
|
1129
|
+
const mod = await resolveCodegraphModule();
|
|
1144
1130
|
if (mod === null) {
|
|
1145
1131
|
return { ok: false, code: "package_missing", message: "The @remnic/coding-graph package is not installed." };
|
|
1146
1132
|
}
|
|
@@ -1229,12 +1215,6 @@ export async function ingestCodegraphTraces(params: {
|
|
|
1229
1215
|
}
|
|
1230
1216
|
}
|
|
1231
1217
|
|
|
1232
|
-
/** Test seam: reset the module cache so a fresh import can be observed. */
|
|
1233
|
-
export function __resetCodegraphRuntimeForTest(): void {
|
|
1234
|
-
cachedModule = undefined;
|
|
1235
|
-
moduleLoadPromise = null;
|
|
1236
|
-
storeCache.clear();
|
|
1237
|
-
}
|
|
1238
1218
|
|
|
1239
1219
|
/**
|
|
1240
1220
|
* Derive a stable per-repo project id from an absolute repoRoot (issue #1554
|
|
@@ -32,11 +32,11 @@ import {
|
|
|
32
32
|
import {
|
|
33
33
|
DECISION_SUBCOMMANDS,
|
|
34
34
|
handleCodingDecision,
|
|
35
|
-
isDecisionRecordSurfaceEnabled,
|
|
36
35
|
type DecisionSurfaceContext,
|
|
37
36
|
type DecisionSurfaceRequest,
|
|
38
37
|
type DecisionSurfaceResponse,
|
|
39
38
|
} from "./decision-surfaces.js";
|
|
39
|
+
import { isCodingKnowledgeFeatureEnabled } from "./coding-knowledge-config.js";
|
|
40
40
|
|
|
41
41
|
// ──────────────────────────────────────────────────────────────────────────
|
|
42
42
|
// Canonical tool names — mirror the external codebase-memory-mcp surface
|
|
@@ -674,7 +674,7 @@ async function handleManageAdr(
|
|
|
674
674
|
// done-when) asserts that `manage_adr record` and `coding_decision record`
|
|
675
675
|
// write byte-identical records -- both routes call handleCodingDecision.
|
|
676
676
|
const codingContext = resolveCodingContext(request, ctx);
|
|
677
|
-
if (!
|
|
677
|
+
if (!isCodingKnowledgeFeatureEnabled(ctx.config.codingKnowledge, "decisionRecords", codingContext)) {
|
|
678
678
|
return disabledResponse(
|
|
679
679
|
request.tool,
|
|
680
680
|
"manage_adr requires codingKnowledge.enabled + decisionRecords + an attached coding context",
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
* the existing `ENGRAM_*` env vars from earlier releases do not pair them up.
|
|
22
22
|
*/
|
|
23
23
|
|
|
24
|
-
import type {
|
|
24
|
+
import type { CodingContext, CodingGraphLspConfig, CodingKnowledgeConfig } from "../types.js";
|
|
25
25
|
import { TIER_1_LANGUAGES } from "./coding-graph-types.js";
|
|
26
26
|
import { coerceBool } from "../connectors/coerce.js";
|
|
27
27
|
|
|
@@ -81,6 +81,40 @@ export function parseCodingKnowledgeConfig(raw: unknown): CodingKnowledgeConfig
|
|
|
81
81
|
};
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
+
// ──────────────────────────────────────────────────────────────────────────
|
|
85
|
+
// Shared surface gates (rule 39: ONE predicate pair, identical on every
|
|
86
|
+
// surface — issue #2478 dedupe). Each Track A surface used to carry its own
|
|
87
|
+
// copy of this logic; they all resolve here now. The booleans are already
|
|
88
|
+
// strict-coerced by parseCodingKnowledgeConfig, so `=== true` is safe here.
|
|
89
|
+
// ──────────────────────────────────────────────────────────────────────────
|
|
90
|
+
|
|
91
|
+
/** Boolean feature switches governed by the master `codingKnowledge.enabled` gate. */
|
|
92
|
+
export type CodingKnowledgeFeatureFlag = "decisionRecords" | "architectureCard" | "sessionDelta";
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Config-only visibility gate — used by the MCP constructor to decide
|
|
96
|
+
* whether a coding tool is advertised in `tools/list`. True only when the
|
|
97
|
+
* master gate AND the feature switch are both on.
|
|
98
|
+
*/
|
|
99
|
+
export function isCodingKnowledgeFeatureVisible(
|
|
100
|
+
config: CodingKnowledgeConfig,
|
|
101
|
+
feature: CodingKnowledgeFeatureFlag,
|
|
102
|
+
): boolean {
|
|
103
|
+
return config.enabled === true && config[feature] === true;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Full call-time gate: visibility PLUS an attached coding context
|
|
108
|
+
* (project/branch-scoped session). Handlers check this before dispatch.
|
|
109
|
+
*/
|
|
110
|
+
export function isCodingKnowledgeFeatureEnabled(
|
|
111
|
+
config: CodingKnowledgeConfig,
|
|
112
|
+
feature: CodingKnowledgeFeatureFlag,
|
|
113
|
+
codingContext: CodingContext | null | undefined,
|
|
114
|
+
): boolean {
|
|
115
|
+
return isCodingKnowledgeFeatureVisible(config, feature) && codingContext != null;
|
|
116
|
+
}
|
|
117
|
+
|
|
84
118
|
/**
|
|
85
119
|
* Read the optional `codingKnowledge.lsp` sub-object. Absent → empty
|
|
86
120
|
* spread (no own key). Present-but-malformed (string/number/array) is
|
|
@@ -21,12 +21,11 @@ import { EngramMcpServer } from "../access-mcp.js";
|
|
|
21
21
|
import {
|
|
22
22
|
DECISION_SUBCOMMANDS,
|
|
23
23
|
formatDecisionSubcommands,
|
|
24
|
-
isDecisionRecordSurfaceEnabled,
|
|
25
|
-
isDecisionRecordSurfaceVisible,
|
|
26
24
|
isDecisionSubcommand,
|
|
27
25
|
type DecisionSurfaceRequest,
|
|
28
26
|
type DecisionSurfaceResponse,
|
|
29
27
|
} from "./decision-surfaces.js";
|
|
28
|
+
import { isCodingKnowledgeFeatureEnabled, isCodingKnowledgeFeatureVisible } from "./coding-knowledge-config.js";
|
|
30
29
|
import type { CodingContext, CodingKnowledgeConfig, PluginConfig } from "../types.js";
|
|
31
30
|
|
|
32
31
|
// ──────────────────────────────────────────────────────────────────────────
|
|
@@ -104,28 +103,28 @@ function makeMockServiceWithConfig(
|
|
|
104
103
|
// ──────────────────────────────────────────────────────────────────────────
|
|
105
104
|
|
|
106
105
|
test("gate: disabled when config.enabled is false", () => {
|
|
107
|
-
assert.equal(
|
|
106
|
+
assert.equal(isCodingKnowledgeFeatureEnabled(GATE_OFF_CONFIG, "decisionRecords", CODING_CONTEXT), false);
|
|
108
107
|
});
|
|
109
108
|
|
|
110
109
|
test("gate: disabled when decisionRecords is false", () => {
|
|
111
110
|
const cfg: CodingKnowledgeConfig = { ...GATE_ON_CONFIG, decisionRecords: false };
|
|
112
|
-
assert.equal(
|
|
111
|
+
assert.equal(isCodingKnowledgeFeatureEnabled(cfg, "decisionRecords", CODING_CONTEXT), false);
|
|
113
112
|
});
|
|
114
113
|
|
|
115
114
|
test("gate: disabled when no coding context attached", () => {
|
|
116
|
-
assert.equal(
|
|
117
|
-
assert.equal(
|
|
115
|
+
assert.equal(isCodingKnowledgeFeatureEnabled(GATE_ON_CONFIG, "decisionRecords", null), false);
|
|
116
|
+
assert.equal(isCodingKnowledgeFeatureEnabled(GATE_ON_CONFIG, "decisionRecords", undefined), false);
|
|
118
117
|
});
|
|
119
118
|
|
|
120
119
|
test("gate: enabled only when all three conditions hold", () => {
|
|
121
|
-
assert.equal(
|
|
120
|
+
assert.equal(isCodingKnowledgeFeatureEnabled(GATE_ON_CONFIG, "decisionRecords", CODING_CONTEXT), true);
|
|
122
121
|
});
|
|
123
122
|
|
|
124
123
|
test("visibility gate: config-only check for tools/list construction", () => {
|
|
125
|
-
assert.equal(
|
|
126
|
-
assert.equal(
|
|
124
|
+
assert.equal(isCodingKnowledgeFeatureVisible(GATE_OFF_CONFIG, "decisionRecords"), false);
|
|
125
|
+
assert.equal(isCodingKnowledgeFeatureVisible(GATE_ON_CONFIG, "decisionRecords"), true);
|
|
127
126
|
assert.equal(
|
|
128
|
-
|
|
127
|
+
isCodingKnowledgeFeatureVisible({ ...GATE_ON_CONFIG, decisionRecords: false }, "decisionRecords"),
|
|
129
128
|
false,
|
|
130
129
|
);
|
|
131
130
|
});
|
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
import { log } from "../logger.js";
|
|
25
25
|
import { composeMemoryEnvelope, type SealedMemoryEnvelope } from "../write-envelope.js";
|
|
26
26
|
import type { MemoryWriteResult } from "../storage.js";
|
|
27
|
+
import { isCodingKnowledgeFeatureEnabled } from "./coding-knowledge-config.js";
|
|
27
28
|
|
|
28
29
|
// ──────────────────────────────────────────────────────────────────────────
|
|
29
30
|
// Subcommands
|
|
@@ -56,45 +57,6 @@ export function formatDecisionSubcommands(): string {
|
|
|
56
57
|
return DECISION_SUBCOMMANDS.join(", ");
|
|
57
58
|
}
|
|
58
59
|
|
|
59
|
-
// ──────────────────────────────────────────────────────────────────────────
|
|
60
|
-
// Gate predicate — rule 39: one predicate, identical on every surface
|
|
61
|
-
// ──────────────────────────────────────────────────────────────────────────
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* The single decision-record surface gate. Returns `true` only when:
|
|
65
|
-
* 1. `codingKnowledge.enabled` is on (the master Track A gate),
|
|
66
|
-
* 2. `codingKnowledge.decisionRecords` is on (the feature switch), AND
|
|
67
|
-
* 3. A coding context is attached (the session is project/branch scoped —
|
|
68
|
-
* decision records live *in* the coding namespace, rule 42).
|
|
69
|
-
*
|
|
70
|
-
* Every surface — MCP `engram.coding_decision`, HTTP
|
|
71
|
-
* `POST /engram/v1/coding/decisions`, CLI `engram-access decision` — MUST call
|
|
72
|
-
* this predicate (or the handler that embeds it) before dispatching. The
|
|
73
|
-
* tool-visibility gate in the MCP constructor checks conditions 1–2 only
|
|
74
|
-
* (coding context is per-session and cannot be evaluated at construction
|
|
75
|
-
* time); the call-time gate checks all three.
|
|
76
|
-
*/
|
|
77
|
-
export function isDecisionRecordSurfaceEnabled(
|
|
78
|
-
config: CodingKnowledgeConfig,
|
|
79
|
-
codingContext: CodingContext | null | undefined,
|
|
80
|
-
): boolean {
|
|
81
|
-
return (
|
|
82
|
-
config.enabled === true &&
|
|
83
|
-
config.decisionRecords === true &&
|
|
84
|
-
codingContext != null
|
|
85
|
-
);
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
* Config-only visibility gate — used by the MCP constructor to decide whether
|
|
90
|
-
* to advertise `engram.coding_decision` in `tools/list`. When this returns
|
|
91
|
-
* `false` the tools array is byte-identical to pre-feature (rule 39).
|
|
92
|
-
*/
|
|
93
|
-
export function isDecisionRecordSurfaceVisible(
|
|
94
|
-
config: CodingKnowledgeConfig,
|
|
95
|
-
): boolean {
|
|
96
|
-
return config.enabled === true && config.decisionRecords === true;
|
|
97
|
-
}
|
|
98
60
|
|
|
99
61
|
// ──────────────────────────────────────────────────────────────────────────
|
|
100
62
|
// Surface request / response shapes
|
|
@@ -228,7 +190,7 @@ export async function handleCodingDecision(
|
|
|
228
190
|
const codingContext = request.sessionKey
|
|
229
191
|
? ctx.getCodingContext(request.sessionKey)
|
|
230
192
|
: null;
|
|
231
|
-
if (!
|
|
193
|
+
if (!isCodingKnowledgeFeatureEnabled(ctx.codingKnowledge, "decisionRecords", codingContext)) {
|
|
232
194
|
ctx.throwInputError(
|
|
233
195
|
"coding_decision requires codingKnowledge.enabled, codingKnowledge.decisionRecords, and an attached coding context",
|
|
234
196
|
);
|
|
@@ -18,11 +18,10 @@ import {
|
|
|
18
18
|
formatDeltaSubcommands,
|
|
19
19
|
handleCodingDelta,
|
|
20
20
|
isDeltaSubcommand,
|
|
21
|
-
isSessionDeltaSurfaceEnabled,
|
|
22
|
-
isSessionDeltaSurfaceVisible,
|
|
23
21
|
type DeltaSurfaceContext,
|
|
24
22
|
type DeltaSurfaceStorage,
|
|
25
23
|
} from "./session-delta-surfaces.js";
|
|
24
|
+
import { isCodingKnowledgeFeatureEnabled, isCodingKnowledgeFeatureVisible } from "./coding-knowledge-config.js";
|
|
26
25
|
import type {
|
|
27
26
|
CodingKnowledgeConfig,
|
|
28
27
|
CodingContext,
|
|
@@ -111,28 +110,28 @@ test("isDeltaSubcommand: narrows valid + rejects unknown", () => {
|
|
|
111
110
|
});
|
|
112
111
|
|
|
113
112
|
test("gate: surface enabled when config + sessionDelta + coding context all true", () => {
|
|
114
|
-
assert.equal(
|
|
113
|
+
assert.equal(isCodingKnowledgeFeatureEnabled(DEFAULT_CONFIG, "sessionDelta", CODING_CONTEXT), true);
|
|
115
114
|
});
|
|
116
115
|
|
|
117
116
|
test("gate: surface disabled when master gate off", () => {
|
|
118
117
|
const config: CodingKnowledgeConfig = { ...DEFAULT_CONFIG, enabled: false };
|
|
119
|
-
assert.equal(
|
|
118
|
+
assert.equal(isCodingKnowledgeFeatureEnabled(config, "sessionDelta", CODING_CONTEXT), false);
|
|
120
119
|
});
|
|
121
120
|
|
|
122
121
|
test("gate: surface disabled when sessionDelta off", () => {
|
|
123
122
|
const config: CodingKnowledgeConfig = { ...DEFAULT_CONFIG, sessionDelta: false };
|
|
124
|
-
assert.equal(
|
|
123
|
+
assert.equal(isCodingKnowledgeFeatureEnabled(config, "sessionDelta", CODING_CONTEXT), false);
|
|
125
124
|
});
|
|
126
125
|
|
|
127
126
|
test("gate: surface disabled when no coding context", () => {
|
|
128
|
-
assert.equal(
|
|
129
|
-
assert.equal(
|
|
127
|
+
assert.equal(isCodingKnowledgeFeatureEnabled(DEFAULT_CONFIG, "sessionDelta", null), false);
|
|
128
|
+
assert.equal(isCodingKnowledgeFeatureEnabled(DEFAULT_CONFIG, "sessionDelta", undefined), false);
|
|
130
129
|
});
|
|
131
130
|
|
|
132
131
|
test("visibility gate: config-only check mirrors full gate minus context", () => {
|
|
133
|
-
assert.equal(
|
|
134
|
-
assert.equal(
|
|
135
|
-
assert.equal(
|
|
132
|
+
assert.equal(isCodingKnowledgeFeatureVisible(DEFAULT_CONFIG, "sessionDelta"), true);
|
|
133
|
+
assert.equal(isCodingKnowledgeFeatureVisible({ ...DEFAULT_CONFIG, enabled: false }, "sessionDelta"), false);
|
|
134
|
+
assert.equal(isCodingKnowledgeFeatureVisible({ ...DEFAULT_CONFIG, sessionDelta: false }, "sessionDelta"), false);
|
|
136
135
|
});
|
|
137
136
|
|
|
138
137
|
// ──────────────────────────────────────────────────────────────────────────
|